From 41d460c2d47aa8a6288029803d0a393b7613aa76 Mon Sep 17 00:00:00 2001 From: White Date: Wed, 16 Sep 2020 10:25:36 -0600 Subject: [PATCH 1/5] first time thru with black --- autotest/pst_from_tests.py | 7 +- pyemu/__init__.py | 37 +- pyemu/_version.py | 154 +- pyemu/en.py | 535 ++++-- pyemu/ev.py | 229 ++- pyemu/la.py | 450 +++-- pyemu/logger.py | 47 +- pyemu/mat/__init__.py | 1 - pyemu/mat/mat_handler.py | 1508 +++++++++------ pyemu/mc.py | 132 +- pyemu/plot/__init__.py | 2 - pyemu/plot/plot_utils.py | 882 +++++---- pyemu/prototypes/__init__.py | 2 - pyemu/prototypes/da.py | 269 ++- pyemu/prototypes/ensemble_method.py | 192 +- pyemu/prototypes/moouu.py | 631 +++--- pyemu/pst/__init__.py | 2 +- pyemu/pst/pst_controldata.py | 281 +-- pyemu/pst/pst_handler.py | 1640 +++++++++------- pyemu/pst/pst_utils.py | 894 +++++---- pyemu/pyemu_warnings.py | 3 +- pyemu/sc.py | 443 +++-- pyemu/utils/__init__.py | 1 - pyemu/utils/geostats.py | 1098 ++++++----- pyemu/utils/gw_utils.py | 1596 ++++++++++------ pyemu/utils/helpers.py | 2758 ++++++++++++++++----------- pyemu/utils/optimization.py | 37 +- pyemu/utils/os_utils.py | 161 +- pyemu/utils/pp_utils.py | 352 ++-- pyemu/utils/pst_from.py | 1764 ++++++++++------- pyemu/utils/smp_utils.py | 126 +- 31 files changed, 9820 insertions(+), 6414 deletions(-) diff --git a/autotest/pst_from_tests.py b/autotest/pst_from_tests.py index 81a9be450..79d33416a 100644 --- a/autotest/pst_from_tests.py +++ b/autotest/pst_from_tests.py @@ -1202,7 +1202,8 @@ def mf6_freyberg_direct_test(): # index_cols='obsnme', use_cols='obsval', prefix='hds') df = pd.read_csv(os.path.join(tmp_model_ws, "sfr.csv"), index_col=0) - pf.add_observations("sfr.csv", insfile="sfr.csv.ins", index_cols="time", use_cols=list(df.columns.values)) + pf.add_observations("sfr.csv", insfile="sfr.csv.ins", index_cols="time", + use_cols=["gage_1","headwaters","tailwaters"],ofile_sep=",") v = pyemu.geostats.ExpVario(contribution=1.0, a=1000) gr_gs = pyemu.geostats.GeoStruct(variograms=v,transform="log") rch_temporal_gs = pyemu.geostats.GeoStruct(variograms=pyemu.geostats.ExpVario(contribution=1.0, a=60)) @@ -1217,6 +1218,7 @@ def mf6_freyberg_direct_test(): arr_files = [f for f in os.listdir(tmp_model_ws) if tag in f and f.endswith(".txt")] if "rch" in tag: for arr_file in arr_files: + recharge_files = ["recharge_1.txt","recharge_2.txt","recharge_3.txt"] pf.add_parameters(filenames=arr_file, par_type="grid", par_name_base="rch_gr", pargp="rch_gr", zone_array=ib, upper_bound=1.0e-3, lower_bound=1.0e-7, geostruct=gr_gs,par_style="direct") @@ -1287,7 +1289,8 @@ def mf6_freyberg_direct_test(): pe.to_binary(os.path.join(template_ws, "prior.jcb")) assert pe.shape[1] == pst.npar_adj, "{0} vs {1}".format(pe.shape[0], pst.npar_adj) assert pe.shape[0] == num_reals - + cov = pf.buid_prior() + cov.to_uncfile("prior.unc") pst.control_data.noptmax = 0 pst.pestpp_options["additional_ins_delimiters"] = "," diff --git a/pyemu/__init__.py b/pyemu/__init__.py index 99187f361..9e5e8eb6e 100644 --- a/pyemu/__init__.py +++ b/pyemu/__init__.py @@ -11,11 +11,20 @@ from .sc import Schur from .ev import ErrVar from .en import Ensemble, ParameterEnsemble, ObservationEnsemble + # from .mc import MonteCarlo # from .inf import Influence from .mat import Matrix, Jco, Cov from .pst import Pst, pst_utils -from .utils import helpers, gw_utils, optimization, geostats, pp_utils, os_utils, smp_utils +from .utils import ( + helpers, + gw_utils, + optimization, + geostats, + pp_utils, + os_utils, + smp_utils, +) from .plot import plot_utils from .logger import Logger @@ -23,9 +32,25 @@ from ._version import get_versions -__version__ = get_versions()['version'] -__all__ = ["LinearAnalysis", "Schur", "ErrVar", "Ensemble", - "ParameterEnsemble", "ObservationEnsemble", "Matrix", - "Jco", "Cov", "Pst", "pst_utils", "helpers", "gw_utils", - "geostats", "pp_utils", "os_utils", "smp_utils", "plot_utils"] +__version__ = get_versions()["version"] +__all__ = [ + "LinearAnalysis", + "Schur", + "ErrVar", + "Ensemble", + "ParameterEnsemble", + "ObservationEnsemble", + "Matrix", + "Jco", + "Cov", + "Pst", + "pst_utils", + "helpers", + "gw_utils", + "geostats", + "pp_utils", + "os_utils", + "smp_utils", + "plot_utils", +] # del get_versions diff --git a/pyemu/_version.py b/pyemu/_version.py index 2c7973964..a533fd712 100644 --- a/pyemu/_version.py +++ b/pyemu/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -58,17 +57,18 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None @@ -76,10 +76,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + p = subprocess.Popen( + [c] + args, + cwd=cwd, + env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None), + ) break except EnvironmentError: e = sys.exc_info()[1] @@ -116,16 +119,22 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + "version": dirname[len(parentdir_prefix) :], + "full-revisionid": None, + "dirty": False, + "error": None, + "date": None, + } else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print( + "Tried directories %s but none started with prefix %s" + % (str(rootdirs), parentdir_prefix) + ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -181,7 +190,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -190,7 +199,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = set([r for r in refs if re.search(r"\d", r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -198,19 +207,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None, + "date": date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags", + "date": None, + } @register_vcs_handler("git", "pieces_from_vcs") @@ -225,8 +241,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -234,10 +249,19 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = run_command( + GITS, + [ + "describe", + "--tags", + "--dirty", + "--always", + "--long", + "--match", + "%s*" % tag_prefix, + ], + cwd=root, + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -260,17 +284,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -279,10 +302,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( + full_tag, + tag_prefix, + ) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -293,13 +318,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[ + 0 + ].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -330,8 +355,7 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -445,11 +469,13 @@ def render_git_describe_long(pieces): def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None, + } if not style or style == "default": style = "pep440" # the default @@ -469,9 +495,13 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None, + "date": pieces.get("date"), + } def get_versions(): @@ -485,8 +515,7 @@ def get_versions(): verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -495,13 +524,16 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for i in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None, + } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -515,6 +547,10 @@ def get_versions(): except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", + "date": None, + } diff --git a/pyemu/en.py b/pyemu/en.py index e896bca05..fc11d78a1 100644 --- a/pyemu/en.py +++ b/pyemu/en.py @@ -7,7 +7,8 @@ import pyemu from .pyemu_warnings import PyemuWarning -SEED = 358183147 #from random.org on 5 Dec 2016 +SEED = 358183147 # from random.org on 5 Dec 2016 + class Loc(object): """thin wrapper around `pandas.DataFrame.loc` to make sure returned type @@ -21,19 +22,21 @@ class Loc(object): to each `Ensemble` instance """ - def __init__(self,ensemble): + + def __init__(self, ensemble): self._ensemble = ensemble - def __getitem__(self,item): - return type(self._ensemble)(self._ensemble.pst, + def __getitem__(self, item): + return type(self._ensemble)( + self._ensemble.pst, self._ensemble._df.loc[item], - istransformed=self._ensemble.istransformed) + istransformed=self._ensemble.istransformed, + ) - def __setitem__(self,idx,value): + def __setitem__(self, idx, value): self._ensemble._df.loc[idx] = value - class Iloc(object): """thin wrapper around `pandas.DataFrame.iloc` to make sure returned type is `Ensemble` (instead of `pandas.DataFrame)` @@ -46,15 +49,18 @@ class Iloc(object): to each `Ensemble` instance """ - def __init__(self,ensemble): + + def __init__(self, ensemble): self._ensemble = ensemble - def __getitem__(self,item): - return type(self._ensemble)(self._ensemble.pst, + def __getitem__(self, item): + return type(self._ensemble)( + self._ensemble.pst, self._ensemble._df.iloc[item], - istransformed=self._ensemble.istransformed) + istransformed=self._ensemble.istransformed, + ) - def __setitem__(self,idx,value): + def __setitem__(self, idx, value): self._ensemble._df.iloc[idx] = value @@ -75,7 +81,8 @@ class Ensemble(object): pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst) """ - def __init__(self,pst,df,istransformed=False): + + def __init__(self, pst, df, istransformed=False): self._df = df """`pandas.DataFrame`: the underlying dataframe that stores the realized values""" self.pst = pst @@ -90,13 +97,13 @@ def __repr__(self): def __str__(self): return self._df.__str__() - def __sub__(self,other): + def __sub__(self, other): try: return self._df - other except: return self._df - other._df - def __mul__(self,other): + def __mul__(self, other): try: return self._df * other except: @@ -108,7 +115,7 @@ def __truediv__(self, other): except: return self._df / other._df - def __add__(self,other): + def __add__(self, other): try: return self._df + other except: @@ -137,9 +144,9 @@ def copy(self): copies both `Ensemble.pst` and `Ensemble._df` """ - return type(self)(pst=self.pst.get(), - df=self._df.copy(), - istransformed=self.istransformed) + return type(self)( + pst=self.pst.get(), df=self._df.copy(), istransformed=self.istransformed + ) @property def istransformed(self): @@ -193,7 +200,7 @@ def back_transform(self): self._transformed = False return - def __getattr__(self,item): + def __getattr__(self, item): if item == "loc": return self.loc[item] elif item == "iloc": @@ -203,27 +210,41 @@ def __getattr__(self,item): elif item == "columns": return self._df.columns elif item in set(dir(self)): - return getattr(self,item) + return getattr(self, item) elif item in set(dir(self._df)): lhs = self._df.__getattr__(item) if type(lhs) == type(self._df): - return type(self)(pst=self.pst,df=lhs,istransformed=self.istransformed) + return type(self)( + pst=self.pst, df=lhs, istransformed=self.istransformed + ) elif "DataFrame" in str(lhs): - warnings.warn("return type uncaught, losing Ensemble type, returning DataFrame", PyemuWarning) + warnings.warn( + "return type uncaught, losing Ensemble type, returning DataFrame", + PyemuWarning, + ) print("return type uncaught, losing Ensemble type, returning DataFrame") return lhs else: return lhs else: - raise AttributeError("Ensemble error: the following item was not" +\ - "found in Ensemble or DataFrame attributes:{0}".format(item)) + raise AttributeError( + "Ensemble error: the following item was not" + + "found in Ensemble or DataFrame attributes:{0}".format(item) + ) return - #def plot(self,*args,**kwargs): - #self._df.plot(*args,**kwargs) - def plot(self, bins=10, facecolor='0.5', plot_cols=None, - filename="ensemble.pdf", func_dict=None, - **kwargs): + # def plot(self,*args,**kwargs): + # self._df.plot(*args,**kwargs) + + def plot( + self, + bins=10, + facecolor="0.5", + plot_cols=None, + filename="ensemble.pdf", + func_dict=None, + **kwargs + ): """plot ensemble histograms to multipage pdf Args: @@ -245,8 +266,9 @@ def plot(self, bins=10, facecolor='0.5', plot_cols=None, pe.plot(bins=30) """ - pyemu.plot_utils.ensemble_helper(self, bins=bins, facecolor=facecolor, plot_cols=plot_cols, - filename=filename) + pyemu.plot_utils.ensemble_helper( + self, bins=bins, facecolor=facecolor, plot_cols=plot_cols, filename=filename + ) @classmethod def from_binary(cls, pst, filename): @@ -269,7 +291,6 @@ def from_binary(cls, pst, filename): df = pyemu.Matrix.from_binary(filename).to_dataframe() return cls(pst=pst, df=df) - @classmethod def from_csv(cls, pst, filename, *args, **kwargs): """create an `Ensemble` from a CSV file @@ -294,14 +315,12 @@ def from_csv(cls, pst, filename, *args, **kwargs): """ - - if "index_col" not in kwargs: kwargs["index_col"] = 0 - df = pd.read_csv(filename,*args,**kwargs) + df = pd.read_csv(filename, *args, **kwargs) return cls(pst=pst, df=df) - def to_csv(self,filename,*args,**kwargs): + def to_csv(self, filename, *args, **kwargs): """write `Ensemble` to a CSV file Args: @@ -327,12 +346,12 @@ def to_csv(self,filename,*args,**kwargs): self.back_transform() retrans = True if self._df.isnull().values.any(): - warnings.warn("NaN in ensemble",PyemuWarning) - self._df.to_csv(filename,*args,**kwargs) + warnings.warn("NaN in ensemble", PyemuWarning) + self._df.to_csv(filename, *args, **kwargs) if retrans: self.transform() - def to_binary(self,filename): + def to_binary(self, filename): """write `Ensemble` to a PEST-style binary file Args: @@ -355,35 +374,44 @@ def to_binary(self,filename): self.back_transform() retrans = True if self._df.isnull().values.any(): - warnings.warn("NaN in ensemble",PyemuWarning) + warnings.warn("NaN in ensemble", PyemuWarning) pyemu.Matrix.from_dataframe(self._df).to_coo(filename) if retrans: self.transform() @classmethod - def from_dataframe(cls,pst,df,istransformed=False): - warnings.warn("Ensemble.from_dataframe() is deprecated and has been " - "replaced with the standard constructor, which takes" - "the same arguments") - return cls(pst=pst,df=df,istransformed=istransformed) - + def from_dataframe(cls, pst, df, istransformed=False): + warnings.warn( + "Ensemble.from_dataframe() is deprecated and has been " + "replaced with the standard constructor, which takes" + "the same arguments" + ) + return cls(pst=pst, df=df, istransformed=istransformed) @staticmethod - def _gaussian_draw(cov,mean_values,num_reals,grouper=None,fill=True, factor="eigen"): + def _gaussian_draw( + cov, mean_values, num_reals, grouper=None, fill=True, factor="eigen" + ): factor = factor.lower() - if factor not in ["eigen","svd"]: - raise Exception("Ensemble._gaussian_draw() error: unrecognized"+\ - "'factor': {0}".format(factor)) + if factor not in ["eigen", "svd"]: + raise Exception( + "Ensemble._gaussian_draw() error: unrecognized" + + "'factor': {0}".format(factor) + ) # make sure all cov names are found in mean_values cov_names = set(cov.row_names) mv_names = set(mean_values.index.values) missing = cov_names - mv_names if len(missing) > 0: - raise Exception("Ensemble._gaussian_draw() error: the following cov names are not in " - "mean_values: {0}".format(','.join(missing))) + raise Exception( + "Ensemble._gaussian_draw() error: the following cov names are not in " + "mean_values: {0}".format(",".join(missing)) + ) if cov.isdiagonal: - stds = {name: std for name, std in zip(cov.row_names, np.sqrt(cov.x.flatten()))} + stds = { + name: std for name, std in zip(cov.row_names, np.sqrt(cov.x.flatten())) + } snv = np.random.randn(num_reals, mean_values.shape[0]) reals = np.zeros_like(snv) reals[:, :] = np.NaN @@ -391,19 +419,21 @@ def _gaussian_draw(cov,mean_values,num_reals,grouper=None,fill=True, factor="eig if name in cov_names: reals[:, i] = (snv[:, i] * stds[name]) + mean_values.loc[name] elif fill: - reals[:, i] = mean_values.loc[name] + reals[:, i] = mean_values.loc[name] else: reals = np.zeros((num_reals, mean_values.shape[0])) reals[:, :] = np.NaN if fill: - for i,v in enumerate(mean_values.values): - reals[:,i] = v - cov_map = {n:i for n,i in zip(cov.row_names,np.arange(cov.shape[0]))} - mv_map = {n: i for n, i in zip(mean_values.index, np.arange(mean_values.shape[0]))} + for i, v in enumerate(mean_values.values): + reals[:, i] = v + cov_map = {n: i for n, i in zip(cov.row_names, np.arange(cov.shape[0]))} + mv_map = { + n: i for n, i in zip(mean_values.index, np.arange(mean_values.shape[0])) + } if grouper is not None: - for grp_name,names in grouper.items(): - print("drawing from group",grp_name) + for grp_name, names in grouper.items(): + print("drawing from group", grp_name) idxs = [mv_map[name] for name in names] snv = np.random.randn(num_reals, len(names)) cov_grp = cov.get(names) @@ -417,15 +447,17 @@ def _gaussian_draw(cov,mean_values,num_reals,grouper=None,fill=True, factor="eig except: covname = "trouble_{0}.cov".format(grp_name) cov_grp.to_ascii(covname) - raise Exception("error inverting cov for group '{0}'," + \ - "saved trouble cov to {1}". - format(grp_name, covname)) - + raise Exception( + "error inverting cov for group '{0}'," + + "saved trouble cov to {1}".format( + grp_name, covname + ) + ) a, i = Ensemble._get_eigen_projection_matrix(cov_grp.as_2d) elif factor == "svd": a, i = Ensemble._get_svd_projection_matrix(cov_grp.as_2d) - snv[:,i:] = 0.0 + snv[:, i:] = 0.0 # process each realization group_mean_values = mean_values.loc[names] for i in range(num_reals): @@ -437,40 +469,40 @@ def _gaussian_draw(cov,mean_values,num_reals,grouper=None,fill=True, factor="eig a, i = Ensemble._get_eigen_projection_matrix(cov.as_2d) elif factor == "svd": a, i = Ensemble._get_svd_projection_matrix(cov.as_2d) - snv[:,i:] = 0.0 + snv[:, i:] = 0.0 cov_mean_values = mean_values.loc[cov.row_names].values idxs = [mv_map[name] for name in cov.row_names] for i in range(num_reals): reals[i, idxs] = cov_mean_values + np.dot(a, snv[i, :]) - df = pd.DataFrame(reals,columns=mean_values.index.values) - df.dropna(inplace=True,axis=1) + df = pd.DataFrame(reals, columns=mean_values.index.values) + df.dropna(inplace=True, axis=1) return df - @staticmethod - def _get_svd_projection_matrix(x,maxsing=None,eigthresh=1.0e-7): + def _get_svd_projection_matrix(x, maxsing=None, eigthresh=1.0e-7): if x.shape[0] != x.shape[1]: raise Exception("matrix not square") - u,s,v = np.linalg.svd(x,full_matrices=True) + u, s, v = np.linalg.svd(x, full_matrices=True) v = v.transpose() if maxsing is None: - maxsing = pyemu.Matrix.get_maxsing_from_s(s,eigthresh=eigthresh) - u = u[:,:maxsing] + maxsing = pyemu.Matrix.get_maxsing_from_s(s, eigthresh=eigthresh) + u = u[:, :maxsing] s = s[:maxsing] - v = v[:,:maxsing] + v = v[:, :maxsing] # fill in full size svd component matrices s_full = np.zeros(x.shape) - s_full[:s.shape[0], :s.shape[1]] = np.sqrt(s) # sqrt since sing vals are eigvals**2 + s_full[: s.shape[0], : s.shape[1]] = np.sqrt( + s + ) # sqrt since sing vals are eigvals**2 v_full = np.zeros_like(s_full) - v_full[:v.shape[0], :v.shape[1]] = v + v_full[: v.shape[0], : v.shape[1]] = v # form the projection matrix proj = np.dot(v_full, s_full) return proj, maxsing - @staticmethod def _get_eigen_projection_matrix(x): # eigen factorization @@ -481,20 +513,25 @@ def _get_eigen_projection_matrix(x): if v[i] > 1.0e-10: pass else: - print("near zero eigen value found", v[i], \ - "at index", i, " of ", v.shape[0]) + print( + "near zero eigen value found", + v[i], + "at index", + i, + " of ", + v.shape[0], + ) v[i] = 0.0 # form the projection matrix vsqrt = np.sqrt(v) - vsqrt[i+1:] = 0.0 + vsqrt[i + 1 :] = 0.0 v = np.diag(vsqrt) a = np.dot(w, v) return a, i - - def get_deviations(self,center_on=None): + def get_deviations(self, center_on=None): """get the deviations of the realizations around a certain point in ensemble space @@ -524,17 +561,19 @@ def get_deviations(self,center_on=None): mean_vec = self.mean() if center_on is not None: if center_on not in self.index: - raise Exception("'center_on' realization {0} not found".format(center_on)) - mean_vec = self._df.loc[center_on,:].copy() + raise Exception( + "'center_on' realization {0} not found".format(center_on) + ) + mean_vec = self._df.loc[center_on, :].copy() df = self._df.copy() for col in df.columns: - df.loc[:,col] -= mean_vec[col] + df.loc[:, col] -= mean_vec[col] if retrans: self.back_transform() - return type(self)(pst=self.pst,df=df,istransformed=self.istransformed) + return type(self)(pst=self.pst, df=df, istransformed=self.istransformed) - def as_pyemu_matrix(self,typ=None): + def as_pyemu_matrix(self, typ=None): """get a `pyemu.Matrix` instance of `Ensemble` Args: @@ -557,7 +596,7 @@ def as_pyemu_matrix(self,typ=None): typ = pyemu.Matrix return typ.from_dataframe(self._df) - def covariance_matrix(self,localizer=None,center_on=None): + def covariance_matrix(self, localizer=None, center_on=None): """get a empirical covariance matrix implied by the correlations between realizations @@ -574,14 +613,13 @@ def covariance_matrix(self,localizer=None,center_on=None): """ devs = self.get_deviations(center_on=center_on).as_pyemu_matrix() - devs *= (1.0 / np.sqrt(float(self.shape[0] - 1.0))) + devs *= 1.0 / np.sqrt(float(self.shape[0] - 1.0)) if localizer is not None: devs = devs.T * devs return devs.hadamard_product(localizer) - return pyemu.Cov((devs.T * devs).x,names=devs.col_names) - + return pyemu.Cov((devs.T * devs).x, names=devs.col_names) def dropna(self, *args, **kwargs): """override of `pandas.DataFrame.dropna()` @@ -593,8 +631,8 @@ def dropna(self, *args, **kwargs): to `pandas.DataFrame.dropna()`. """ - df = self._df.dropna(*args,**kwargs) - return type(self)(pst=self.pst,df=df,istransformed=self.istransformed) + df = self._df.dropna(*args, **kwargs) + return type(self)(pst=self.pst, df=df, istransformed=self.istransformed) class ObservationEnsemble(Ensemble): @@ -614,12 +652,14 @@ class ObservationEnsemble(Ensemble): oe = pyemu.ObservationEnsemble.from_gaussian_draw(pst) """ - def __init__(self,pst,df,istransformed=False): - super(ObservationEnsemble,self).__init__(pst,df,istransformed) + + def __init__(self, pst, df, istransformed=False): + super(ObservationEnsemble, self).__init__(pst, df, istransformed) @classmethod - def from_gaussian_draw(cls,pst,cov=None,num_reals=100,by_groups=True,fill=False, - factor="eigen"): + def from_gaussian_draw( + cls, pst, cov=None, num_reals=100, by_groups=True, fill=False, factor="eigen" + ): """generate an `ObservationEnsemble` from a (multivariate) gaussian distribution @@ -683,17 +723,23 @@ def from_gaussian_draw(cls,pst,cov=None,num_reals=100,by_groups=True,fill=False, grouper = None if not cov.isdiagonal and by_groups: - nz_obs = obs.loc[pst.nnz_obs_names,:].copy() + nz_obs = obs.loc[pst.nnz_obs_names, :].copy() grouper = nz_obs.groupby("obgnme").groups for grp in grouper.keys(): grouper[grp] = list(grouper[grp]) - df = Ensemble._gaussian_draw(cov=nz_cov,mean_values=mean_values, - num_reals=num_reals,grouper=grouper, - fill=fill, factor=factor) + df = Ensemble._gaussian_draw( + cov=nz_cov, + mean_values=mean_values, + num_reals=num_reals, + grouper=grouper, + fill=fill, + factor=factor, + ) if fill: - df.loc[:,pst.zero_weight_obs_names] = pst.observation_data.loc[pst.zero_weight_obs_names, - "obsval"].values - return cls(pst,df,istransformed=False) + df.loc[:, pst.zero_weight_obs_names] = pst.observation_data.loc[ + pst.zero_weight_obs_names, "obsval" + ].values + return cls(pst, df, istransformed=False) @property def phi_vector(self): @@ -728,8 +774,8 @@ def add_base(self): """ if "base" in self.index: raise Exception("'base' already in ensemble") - self._df = self._df.iloc[:-1,:] - self._df.loc["base",:] = self.pst.observation_data.loc[self.columns,"obsval"] + self._df = self._df.iloc[:-1, :] + self._df.loc["base", :] = self.pst.observation_data.loc[self.columns, "obsval"] @property def nonzero(self): @@ -745,7 +791,10 @@ def nonzero(self): """ df = self._df.loc[:, self.pst.nnz_obs_names] - return ObservationEnsemble(pst=self.pst.get(obs_names=self.pst.nnz_obs_names),df=df) + return ObservationEnsemble( + pst=self.pst.get(obs_names=self.pst.nnz_obs_names), df=df + ) + class ParameterEnsemble(Ensemble): """Parameter ensembles in the PEST(++) realm @@ -764,12 +813,14 @@ class ParameterEnsemble(Ensemble): pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst) """ - def __init__(self,pst,df,istransformed=False): - super(ParameterEnsemble,self).__init__(pst,df,istransformed) + + def __init__(self, pst, df, istransformed=False): + super(ParameterEnsemble, self).__init__(pst, df, istransformed) @classmethod - def from_gaussian_draw(cls,pst,cov=None,num_reals=100,by_groups=True, - fill=True, factor="eigen"): + def from_gaussian_draw( + cls, pst, cov=None, num_reals=100, by_groups=True, fill=True, factor="eigen" + ): """generate a `ParameterEnsemble` from a (multivariate) (log) gaussian distribution @@ -829,18 +880,22 @@ def from_gaussian_draw(cls,pst,cov=None,num_reals=100,by_groups=True, mean_values.loc[li] = mean_values.loc[li].apply(np.log10) grouper = None if not cov.isdiagonal and by_groups: - adj_par = par.loc[pst.adj_par_names,:] + adj_par = par.loc[pst.adj_par_names, :] grouper = adj_par.groupby("pargp").groups for grp in grouper.keys(): grouper[grp] = list(grouper[grp]) - df = Ensemble._gaussian_draw(cov=cov,mean_values=mean_values, - num_reals=num_reals,grouper=grouper, - fill=fill) - df.loc[:,li] = 10.0**df.loc[:,li] - return cls(pst,df,istransformed=False) + df = Ensemble._gaussian_draw( + cov=cov, + mean_values=mean_values, + num_reals=num_reals, + grouper=grouper, + fill=fill, + ) + df.loc[:, li] = 10.0 ** df.loc[:, li] + return cls(pst, df, istransformed=False) @classmethod - def from_triangular_draw(cls, pst, num_reals=100,fill=True): + def from_triangular_draw(cls, pst, num_reals=100, fill=True): """generate a `ParameterEnsemble` from a (multivariate) (log) triangular distribution Args: @@ -893,23 +948,22 @@ def from_triangular_draw(cls, pst, num_reals=100,fill=True): for i, pname in enumerate(pst.parameter_data.parnme): # print(pname, lb[pname], ub[pname]) if pname in adj_par_names: - arr[:, i] = np.random.triangular(lb[pname], - pv[pname], - ub[pname], - size=num_reals) + arr[:, i] = np.random.triangular( + lb[pname], pv[pname], ub[pname], size=num_reals + ) elif fill: - arr[:, i] = np.zeros((num_reals)) + \ - pst.parameter_data. \ - loc[pname, "parval1"] + arr[:, i] = ( + np.zeros((num_reals)) + pst.parameter_data.loc[pname, "parval1"] + ) df = pd.DataFrame(arr, index=real_names, columns=pst.par_names) - df.dropna(inplace=True,axis=1) + df.dropna(inplace=True, axis=1) df.loc[:, li] = 10.0 ** df.loc[:, li] new_pe = cls(pst=pst, df=df) return new_pe @classmethod - def from_uniform_draw(cls, pst, num_reals,fill=True): + def from_uniform_draw(cls, pst, num_reals, fill=True): """ generate a `ParameterEnsemble` from a (multivariate) (log) uniform distribution @@ -949,29 +1003,37 @@ def from_uniform_draw(cls, pst, num_reals,fill=True): real_names = np.arange(num_reals, dtype=np.int64) arr = np.empty((num_reals, len(ub))) - arr[:,:] = np.NaN + arr[:, :] = np.NaN adj_par_names = set(pst.adj_par_names) for i, pname in enumerate(pst.parameter_data.parnme): # print(pname,lb[pname],ub[pname]) if pname in adj_par_names: - arr[:, i] = np.random.uniform(lb[pname], - ub[pname], - size=num_reals) + arr[:, i] = np.random.uniform(lb[pname], ub[pname], size=num_reals) elif fill: - arr[:, i] = np.zeros((num_reals)) + \ - pst.parameter_data. \ - loc[pname, "parval1"] + arr[:, i] = ( + np.zeros((num_reals)) + pst.parameter_data.loc[pname, "parval1"] + ) df = pd.DataFrame(arr, index=real_names, columns=pst.par_names) - df.dropna(inplace=True,axis=1) + df.dropna(inplace=True, axis=1) df.loc[:, li] = 10.0 ** df.loc[:, li] new_pe = cls(pst=pst, df=df) return new_pe @classmethod - def from_mixed_draws(cls, pst, how_dict, default="gaussian", num_reals=100, cov=None, sigma_range=6, - enforce_bounds=True, partial=False, fill=True): + def from_mixed_draws( + cls, + pst, + how_dict, + default="gaussian", + num_reals=100, + cov=None, + sigma_range=6, + enforce_bounds=True, + partial=False, + fill=True, + ): """generate a `ParameterEnsemble` using a mixture of distributions. Available distributions include (log) "uniform", (log) "triangular", and (log) "gaussian". log transformation is respected. @@ -1001,27 +1063,39 @@ def from_mixed_draws(cls, pst, how_dict, default="gaussian", num_reals=100, cov= # error checking accept = {"uniform", "triangular", "gaussian"} - assert default in accept, "ParameterEnsemble.from_mixed_draw() error: 'default' must be in {0}".format(accept) + assert ( + default in accept + ), "ParameterEnsemble.from_mixed_draw() error: 'default' must be in {0}".format( + accept + ) par_org = pst.parameter_data.copy() pset = set(pst.adj_par_names) hset = set(how_dict.keys()) missing = pset.difference(hset) if not partial and len(missing) > 0: - print("{0} par names missing in how_dict, these parameters will be sampled using {1} (the 'default')". \ - format(len(missing), default)) + print( + "{0} par names missing in how_dict, these parameters will be sampled using {1} (the 'default')".format( + len(missing), default + ) + ) for m in missing: how_dict[m] = default missing = hset.difference(pset) - assert len(missing) == 0, "ParameterEnsemble.from_mixed_draws() error: the following par names are not in " + \ - " in the pst: {0}".format(','.join(missing)) + assert len(missing) == 0, ( + "ParameterEnsemble.from_mixed_draws() error: the following par names are not in " + + " in the pst: {0}".format(",".join(missing)) + ) unknown_draw = [] for pname, how in how_dict.items(): if how not in accept: unknown_draw.append("{0}:{1}".format(pname, how)) if len(unknown_draw) > 0: - raise Exception("ParameterEnsemble.from_mixed_draws() error: the following hows are not recognized:{0}" \ - .format(','.join(unknown_draw))) + raise Exception( + "ParameterEnsemble.from_mixed_draws() error: the following hows are not recognized:{0}".format( + ",".join(unknown_draw) + ) + ) # work out 'how' grouping how_groups = {how: [] for how in accept} @@ -1041,13 +1115,18 @@ def from_mixed_draws(cls, pst, how_dict, default="gaussian", num_reals=100, cov= cset = set(cov.row_names) # gset = set(how_groups["gaussian"]) diff = gset.difference(cset) - assert len(diff) == 0, "ParameterEnsemble.from_mixed_draws() error: the 'cov' is not compatible with " + \ - " the parameters listed as 'gaussian' in how_dict, the following are not in the cov:{0}". \ - format(','.join(diff)) + assert len(diff) == 0, ( + "ParameterEnsemble.from_mixed_draws() error: the 'cov' is not compatible with " + + " the parameters listed as 'gaussian' in how_dict, the following are not in the cov:{0}".format( + ",".join(diff) + ) + ) else: cov = pyemu.Cov.from_parameter_data(pst, sigma_range=sigma_range) - pe_gauss = ParameterEnsemble.from_gaussian_draw(pst, cov, num_reals=num_reals) + pe_gauss = ParameterEnsemble.from_gaussian_draw( + pst, cov, num_reals=num_reals + ) pes.append(pe_gauss) if len(how_groups["uniform"]) > 0: @@ -1070,14 +1149,15 @@ def from_mixed_draws(cls, pst, how_dict, default="gaussian", num_reals=100, cov= df.loc[:, :] = np.NaN if fill: - fixed_tied = par_org.loc[par_org.partrans.apply(lambda x: x in ["fixed", "tied"]), "parval1"].to_dict() + fixed_tied = par_org.loc[ + par_org.partrans.apply(lambda x: x in ["fixed", "tied"]), "parval1" + ].to_dict() for p, v in fixed_tied.items(): df.loc[:, p] = v for pe in pes: df.loc[pe.index, pe.columns] = pe - # this dropna covers both "fill" and "partial" df = df.dropna(axis=1) @@ -1087,7 +1167,6 @@ def from_mixed_draws(cls, pst, how_dict, default="gaussian", num_reals=100, cov= pe.enforce() return pe - @classmethod def from_parfiles(cls, pst, parfile_names, real_names=None): """ create a parameter ensemble from PEST-style parameter value files. @@ -1115,15 +1194,19 @@ def from_parfiles(cls, pst, parfile_names, real_names=None): real_names = np.arange(len(parfile_names)) for rname, pfile in zip(real_names, parfile_names): - assert os.path.exists(pfile), "ParameterEnsemble.from_parfiles() error: " + \ - "file: {0} not found".format(pfile) + assert os.path.exists(pfile), ( + "ParameterEnsemble.from_parfiles() error: " + + "file: {0} not found".format(pfile) + ) df = pyemu.pst_utils.read_parfile(pfile) # check for scale differences - I don't who is dumb enough # to change scale between par files and pst... diff = df.scale - pst.parameter_data.scale if diff.apply(np.abs).sum() > 0.0: - warnings.warn("differences in scale detected, applying scale in par file", - PyemuWarning) + warnings.warn( + "differences in scale detected, applying scale in par file", + PyemuWarning, + ) # df.loc[:,"parval1"] *= df.scale dfs[rname] = df.parval1.values @@ -1138,16 +1221,24 @@ def from_parfiles(cls, pst, parfile_names, real_names=None): dset = set(df_all.columns) diff = pset.difference(dset) if len(diff) > 0: - warnings.warn("the following parameters are not in the par files (getting NaNs) :{0}". - format(','.join(diff)), PyemuWarning) + warnings.warn( + "the following parameters are not in the par files (getting NaNs) :{0}".format( + ",".join(diff) + ), + PyemuWarning, + ) blank_df = pd.DataFrame(index=df_all.index, columns=diff) df_all = pd.concat([df_all, blank_df], axis=1) diff = dset.difference(pset) if len(diff) > 0: - warnings.warn("the following par file parameters are not in the control (being dropped):{0}". - format(','.join(diff)), PyemuWarning) + warnings.warn( + "the following par file parameters are not in the control (being dropped):{0}".format( + ",".join(diff) + ), + PyemuWarning, + ) df_all = df_all.loc[:, pst.par_names] return ParameterEnsemble(pst=pst, df=df_all) @@ -1164,9 +1255,9 @@ def back_transform(self): """ if not self.istransformed: return - li = self.pst.parameter_data.loc[:,"partrans"] == "log" - self.loc[:,li] = 10.0**(self._df.loc[:,li]) - #self.loc[:,:] = (self.loc[:,:] -\ + li = self.pst.parameter_data.loc[:, "partrans"] == "log" + self.loc[:, li] = 10.0 ** (self._df.loc[:, li]) + # self.loc[:,:] = (self.loc[:,:] -\ # self.pst.parameter_data.offset)/\ # self.pst.parameter_data.scale self._istransformed = False @@ -1183,9 +1274,9 @@ def transform(self): """ if self.istransformed: return - li = self.pst.parameter_data.loc[:,"partrans"] == "log" + li = self.pst.parameter_data.loc[:, "partrans"] == "log" df = self._df - #self.loc[:,:] = (self.loc[:,:] * self.pst.parameter_data.scale) +\ + # self.loc[:,:] = (self.loc[:,:] * self.pst.parameter_data.scale) +\ # self.pst.parameter_data.offset df.loc[:, li] = df.loc[:, li].apply(np.log10) self._istransformed = True @@ -1203,9 +1294,11 @@ def add_base(self): self._df = self._df.iloc[:-1, :] if self.istransformed: self.pst.add_transform_columns() - self.loc["base", :] = self.pst.parameter_data.loc[self.columns, "parval1_trans"] + self.loc["base", :] = self.pst.parameter_data.loc[ + self.columns, "parval1_trans" + ] else: - self.loc["base",:] = self.pst.parameter_data.loc[self.columns,"parval1"] + self.loc["base", :] = self.pst.parameter_data.loc[self.columns, "parval1"] @property def adj_names(self): @@ -1271,13 +1364,13 @@ def fixed_indexer(self): """ # isfixed = self.pst.parameter_data.partrans == "fixed" - tf = set(["tied","fixed"]) - isfixed = self.pst.parameter_data.partrans. \ - apply(lambda x: x in tf) + tf = set(["tied", "fixed"]) + isfixed = self.pst.parameter_data.partrans.apply(lambda x: x in tf) return isfixed.values - def project(self,projection_matrix,center_on=None, - log=None,enforce_bounds="reset"): + def project( + self, projection_matrix, center_on=None, log=None, enforce_bounds="reset" + ): """ project the ensemble using the null-space Monte Carlo method Args: @@ -1307,23 +1400,27 @@ def project(self,projection_matrix,center_on=None, self.transform() retrans = True - #base = self._df.mean() + # base = self._df.mean() self.pst.add_transform_columns() base = self.pst.parameter_data.parval1_trans if center_on is not None: - if isinstance(center_on,pd.Series): + if isinstance(center_on, pd.Series): base = center_on elif center_on in self._df.index: - base = self._df.loc[center_on,:].copy() - elif isinstance(center_on,"str"): + base = self._df.loc[center_on, :].copy() + elif isinstance(center_on, "str"): try: base = pyemu.pst_utils.read_parfile(center_on) except: - raise Exception("'center_on' arg not found in index and couldnt be loaded as a '.par' file") + raise Exception( + "'center_on' arg not found in index and couldnt be loaded as a '.par' file" + ) else: - raise Exception("error processing 'center_on' arg. should be realization names, par file, or series") + raise Exception( + "error processing 'center_on' arg. should be realization names, par file, or series" + ) names = list(base.index) - projection_matrix = projection_matrix.get(names,names) + projection_matrix = projection_matrix.get(names, names) new_en = self.copy() @@ -1332,9 +1429,9 @@ def project(self,projection_matrix,center_on=None, log("projecting realization {0}".format(real)) # null space projection of difference vector - pdiff = self._df.loc[real,names] - base - pdiff = np.dot(projection_matrix.x,pdiff.values) - new_en.loc[real,names] = base + pdiff + pdiff = self._df.loc[real, names] - base + pdiff = np.dot(projection_matrix.x, pdiff.values) + new_en.loc[real, names] = base + pdiff if log is not None: log("projecting realization {0}".format(real)) @@ -1346,7 +1443,7 @@ def project(self,projection_matrix,center_on=None, self.transform() return new_en - def enforce(self,how="reset",bound_tol=0.0): + def enforce(self, how="reset", bound_tol=0.0): """ entry point for bounds enforcement. This gets called for the draw method(s), so users shouldn't need to call this @@ -1371,9 +1468,10 @@ def enforce(self,how="reset",bound_tol=0.0): elif how.lower().strip() == "scale": self._enforce_scale(bound_tol=bound_tol) else: - raise Exception("unrecognized enforce_bounds arg:"+\ - "{0}, should be 'reset' or 'drop'".\ - format(enforce_bounds)) + raise Exception( + "unrecognized enforce_bounds arg:" + + "{0}, should be 'reset' or 'drop'".format(enforce_bounds) + ) def _enforce_scale(self, bound_tol): retrans = False @@ -1382,24 +1480,32 @@ def _enforce_scale(self, bound_tol): retrans = True ub = self.ubnd * (1.0 - bound_tol) lb = self.lbnd * (1.0 + bound_tol) - base_vals = self.pst.parameter_data.loc[self._df.columns,"parval1"].copy() + base_vals = self.pst.parameter_data.loc[self._df.columns, "parval1"].copy() ub_dist = (ub - base_vals).apply(np.abs) lb_dist = (base_vals - lb).apply(np.abs) if ub_dist.min() <= 0.0: - raise Exception("Ensemble._enforce_scale() error: the following parameter" +\ - "are at or over ubnd: {0}".format(ub_dist.loc[ub_dist<=0.0].index.values)) + raise Exception( + "Ensemble._enforce_scale() error: the following parameter" + + "are at or over ubnd: {0}".format( + ub_dist.loc[ub_dist <= 0.0].index.values + ) + ) if lb_dist.min() <= 0.0: - raise Exception("Ensemble._enforce_scale() error: the following parameter" +\ - "are at or under lbnd: {0}".format(lb_dist.loc[ub_dist<=0.0].index.values)) + raise Exception( + "Ensemble._enforce_scale() error: the following parameter" + + "are at or under lbnd: {0}".format( + lb_dist.loc[ub_dist <= 0.0].index.values + ) + ) for ridx in self._df.index: - real = self._df.loc[ridx,:] + real = self._df.loc[ridx, :] real_dist = (real - base_vals).apply(np.abs) - out_ubnd = (real - ub) - out_ubnd = out_ubnd.loc[out_ubnd>0.0] + out_ubnd = real - ub + out_ubnd = out_ubnd.loc[out_ubnd > 0.0] ubnd_facs = ub_dist.loc[out_ubnd.index] / real_dist.loc[out_ubnd.index] - out_lbnd = (lb - real) + out_lbnd = lb - real out_lbnd = out_lbnd.loc[out_lbnd > 0.0] lbnd_facs = lb_dist.loc[out_lbnd.index] / real_dist.loc[out_lbnd.index] @@ -1408,23 +1514,31 @@ def _enforce_scale(self, bound_tol): lmin = 1.0 umin = 1.0 sign = np.ones((self.pst.npar_adj)) - sign[real.loc[self.pst.adj_par_names] < base_vals.loc[self.pst.adj_par_names]] = -1.0 + sign[ + real.loc[self.pst.adj_par_names] < base_vals.loc[self.pst.adj_par_names] + ] = -1.0 if ubnd_facs.shape[0] > 0: umin = ubnd_facs.min() umin_idx = ubnd_facs.idxmin() - print("enforce_scale ubnd controlling parameter, scale factor, " + \ - "current value for realization {0}: {1}, {2}, {3}". \ - format(ridx, umin_idx, umin, real.loc[umin_idx])) + print( + "enforce_scale ubnd controlling parameter, scale factor, " + + "current value for realization {0}: {1}, {2}, {3}".format( + ridx, umin_idx, umin, real.loc[umin_idx] + ) + ) if lbnd_facs.shape[0] > 0: lmin = lbnd_facs.min() lmin_idx = lbnd_facs.idxmin() - print("enforce_scale lbnd controlling parameter, scale factor, " + \ - "current value for realization {0}: {1}, {2}. {3}". \ - format(ridx, lmin_idx, lmin, real.loc[lmin_idx])) + print( + "enforce_scale lbnd controlling parameter, scale factor, " + + "current value for realization {0}: {1}, {2}. {3}".format( + ridx, lmin_idx, lmin, real.loc[lmin_idx] + ) + ) min_fac = min(umin, lmin) - self._df.loc[ridx,:] = base_vals + (sign * real_dist * min_fac) + self._df.loc[ridx, :] = base_vals + (sign * real_dist * min_fac) if retrans: self.transform() @@ -1444,12 +1558,13 @@ def _enforce_drop(self, bound_tol): lb = self.lbnd * (1.0 + bound_tol) drop = [] for ridx in self._df.index: - #mx = (ub - self.loc[id,:]).min() - #mn = (lb - self.loc[id,:]).max() - if (ub - self._df.loc[ridx,:]).min() < 0.0 or\ - (lb - self._df.loc[ridx,:]).max() > 0.0: + # mx = (ub - self.loc[id,:]).min() + # mn = (lb - self.loc[id,:]).max() + if (ub - self._df.loc[ridx, :]).min() < 0.0 or ( + lb - self._df.loc[ridx, :] + ).max() > 0.0: drop.append(ridx) - self.loc[drop,:] = np.NaN + self.loc[drop, :] = np.NaN self.dropna(inplace=True) def _enforce_reset(self, bound_tol): @@ -1462,5 +1577,5 @@ def _enforce_reset(self, bound_tol): val_arr = self._df.values for iname, name in enumerate(self.columns): - val_arr[val_arr[:,iname] > ub[name],iname] = ub[name] - val_arr[val_arr[:, iname] < lb[name],iname] = lb[name] + val_arr[val_arr[:, iname] > ub[name], iname] = ub[name] + val_arr[val_arr[:, iname] < lb[name], iname] = lb[name] diff --git a/pyemu/ev.py b/pyemu/ev.py index 93e38b469..50fc9b409 100644 --- a/pyemu/ev.py +++ b/pyemu/ev.py @@ -4,6 +4,7 @@ from pyemu.la import LinearAnalysis from pyemu.mat.mat_handler import Matrix, Jco, Cov + class ErrVar(LinearAnalysis): """FOSM-based error variance analysis @@ -59,15 +60,15 @@ class ErrVar(LinearAnalysis): """ - def __init__(self,jco,**kwargs): + def __init__(self, jco, **kwargs): self.__need_omitted = False if "omitted_parameters" in kwargs.keys(): - self.omitted_par_arg = kwargs["omitted_parameters"] - kwargs.pop("omitted_parameters") - self.__need_omitted = True + self.omitted_par_arg = kwargs["omitted_parameters"] + kwargs.pop("omitted_parameters") + self.__need_omitted = True else: - self.omitted_par_arg = None + self.omitted_par_arg = None if "omitted_parcov" in kwargs.keys(): self.omitted_parcov_arg = kwargs["omitted_parcov"] kwargs.pop("omitted_parcov") @@ -91,7 +92,6 @@ def __init__(self,jco,**kwargs): kl = bool(kwargs["kl"]) kwargs.pop("kl") - self.__qhalfx = None self.__R = None self.__R_sv = None @@ -107,10 +107,10 @@ def __init__(self,jco,**kwargs): super(ErrVar, self).__init__(jco, **kwargs) if self.__need_omitted: self.log("pre-loading omitted components") - #self._LinearAnalysis__load_jco() - #self._LinearAnalysis__load_parcov() - #self._LinearAnalysis__load_obscov() - #if self.prediction_arg is not None: + # self._LinearAnalysis__load_jco() + # self._LinearAnalysis__load_parcov() + # self._LinearAnalysis__load_obscov() + # if self.prediction_arg is not None: # self._LinearAnalysis__load_predictions() self.__load_omitted_jco() self.__load_omitted_parcov() @@ -122,7 +122,7 @@ def __init__(self,jco,**kwargs): self.apply_karhunen_loeve_scaling() self.log("applying KL scaling") - self.valid_terms = ["null","solution", "omitted", "all"] + self.valid_terms = ["null", "solution", "omitted", "all"] self.valid_return_types = ["parameters", "predictions"] def __load_omitted_predictions(self): @@ -130,19 +130,20 @@ def __load_omitted_predictions(self): """ # if there are no base predictions if self.predictions is None: - raise Exception("ErrVar.__load_omitted_predictions(): " + - "no 'included' predictions is None") - if self.omitted_predictions_arg is None and \ - self.omitted_par_arg is None: - raise Exception("ErrVar.__load_omitted_predictions: " + - "both omitted args are None") + raise Exception( + "ErrVar.__load_omitted_predictions(): " + + "no 'included' predictions is None" + ) + if self.omitted_predictions_arg is None and self.omitted_par_arg is None: + raise Exception( + "ErrVar.__load_omitted_predictions: " + "both omitted args are None" + ) # try to set omitted_predictions by # extracting from existing predictions - if self.omitted_predictions_arg is None and \ - self.omitted_par_arg is not None: + if self.omitted_predictions_arg is None and self.omitted_par_arg is not None: # check to see if omitted par names are in each predictions found = True - missing_par,missing_pred = None, None + missing_par, missing_pred = None, None for par_name in self.omitted_jco.col_names: for prediction in self.predictions_iter: if par_name not in prediction.row_names: @@ -155,18 +156,22 @@ def __load_omitted_predictions(self): # need to access the attribute directly, # not a view of attribute opred_mat = self._LinearAnalysis__predictions.extract( - row_names=self.omitted_jco.col_names) + row_names=self.omitted_jco.col_names + ) opreds = [opred_mat.get(col_names=name) for name in self.forecast_names] - #for prediction in self._LinearAnalysis__predictions: + # for prediction in self._LinearAnalysis__predictions: # opred = prediction.extract(self.omitted_jco.col_names) # opreds.append(opred) self.__omitted_predictions = opreds else: - raise Exception("ErrVar.__load_omitted_predictions(): " + - " omitted parameter " + str(missing_par) +\ - " not found in prediction vector " + - str(missing_pred)) + raise Exception( + "ErrVar.__load_omitted_predictions(): " + + " omitted parameter " + + str(missing_par) + + " not found in prediction vector " + + str(missing_pred) + ) elif self.omitted_parcov_arg is not None: raise NotImplementedError() @@ -174,8 +179,9 @@ def __load_omitted_parcov(self): """private: set the omitted_parcov attribute """ if self.omitted_parcov_arg is None and self.omitted_par_arg is None: - raise Exception("ErrVar.__load_omitted_parcov: " + - "both omitted args are None") + raise Exception( + "ErrVar.__load_omitted_parcov: " + "both omitted args are None" + ) # try to set omitted_parcov by extracting from base parcov if self.omitted_parcov_arg is None and self.omitted_par_arg is not None: # check to see if omitted par names are in parcov @@ -186,16 +192,20 @@ def __load_omitted_parcov(self): break if found: # need to access attribute directly, not view of attribute - self.__omitted_parcov = \ - self._LinearAnalysis__parcov.extract( - row_names=self.omitted_jco.col_names) + self.__omitted_parcov = self._LinearAnalysis__parcov.extract( + row_names=self.omitted_jco.col_names + ) else: - self.logger.warn("ErrVar.__load_omitted_parun: " + - "no omitted parcov arg passed: " + - "setting omitted parcov as identity Matrix") + self.logger.warn( + "ErrVar.__load_omitted_parun: " + + "no omitted parcov arg passed: " + + "setting omitted parcov as identity Matrix" + ) self.__omitted_parcov = Cov( x=np.ones(self.omitted_jco.shape[1]), - names=self.omitted_jco.col_names, isdiagonal=True) + names=self.omitted_jco.col_names, + isdiagonal=True, + ) elif self.omitted_parcov_arg is not None: raise NotImplementedError() @@ -204,32 +214,37 @@ def __load_omitted_jco(self): """ if self.omitted_par_arg is None: raise Exception("ErrVar.__load_omitted: omitted_arg is None") - if isinstance(self.omitted_par_arg,str): + if isinstance(self.omitted_par_arg, str): if self.omitted_par_arg in self.jco.col_names: # need to access attribute directly, not view of attribute - self.__omitted_jco = \ - self._LinearAnalysis__jco.extract( - col_names=self.omitted_par_arg) + self.__omitted_jco = self._LinearAnalysis__jco.extract( + col_names=self.omitted_par_arg + ) else: # must be a filename self.__omitted_jco = self.__fromfile(self.omitted_par_arg) # if the arg is an already instantiated Matrix (or jco) object - elif isinstance(self.omitted_par_arg,Jco) or \ - isinstance(self.omitted_par_arg,Matrix): - self.__omitted_jco = \ - Jco(x=self.omitted_par_arg.newx(), - row_names=self.omitted_par_arg.row_names, - col_names=self.omitted_par_arg.col_names) + elif isinstance(self.omitted_par_arg, Jco) or isinstance( + self.omitted_par_arg, Matrix + ): + self.__omitted_jco = Jco( + x=self.omitted_par_arg.newx(), + row_names=self.omitted_par_arg.row_names, + col_names=self.omitted_par_arg.col_names, + ) # if it is a list, then it must be a list # of parameter names in self.jco - elif isinstance(self.omitted_par_arg,list): + elif isinstance(self.omitted_par_arg, list): for arg in self.omitted_par_arg: - if isinstance(arg,str): - assert arg in self.jco.col_names,\ - "ErrVar.__load_omitted_jco: omitted_jco " +\ - "arg str not in jco par_names: " + str(arg) - self.__omitted_jco = \ - self._LinearAnalysis__jco.extract(col_names=self.omitted_par_arg) + if isinstance(arg, str): + assert arg in self.jco.col_names, ( + "ErrVar.__load_omitted_jco: omitted_jco " + + "arg str not in jco par_names: " + + str(arg) + ) + self.__omitted_jco = self._LinearAnalysis__jco.extract( + col_names=self.omitted_par_arg + ) # these property decorators help keep from loading potentially # unneeded items until they are called @@ -300,10 +315,10 @@ def get_errvar_dataframe(self, singular_values=None): """ if singular_values is None: - singular_values = \ - np.arange(0, min(self.pst.nnz_obs, self.pst.npar_adj) + 1) - if not isinstance(singular_values, list) and \ - not isinstance(singular_values, np.ndarray): + singular_values = np.arange(0, min(self.pst.nnz_obs, self.pst.npar_adj) + 1) + if not isinstance(singular_values, list) and not isinstance( + singular_values, np.ndarray + ): singular_values = [singular_values] results = {} for singular_value in singular_values: @@ -314,8 +329,7 @@ def get_errvar_dataframe(self, singular_values=None): results[key].append(val) return pd.DataFrame(results, index=singular_values) - - def get_identifiability_dataframe(self,singular_value=None,precondition=False): + def get_identifiability_dataframe(self, singular_value=None, precondition=False): """primary entry point for identifiability analysis Args: @@ -338,11 +352,11 @@ def get_identifiability_dataframe(self,singular_value=None,precondition=False): """ if singular_value is None: singular_value = int(min(self.pst.nnz_obs, self.pst.npar_adj)) - #v1_df = self.qhalfx.v[:, :singular_value].to_dataframe() ** 2 + # v1_df = self.qhalfx.v[:, :singular_value].to_dataframe() ** 2 xtqx = self.xtqx if precondition: xtqx = xtqx + self.parcov.inv - #v1_df = self.xtqx.v[:, :singular_value].to_dataframe() ** 2 + # v1_df = self.xtqx.v[:, :singular_value].to_dataframe() ** 2 v1_df = xtqx.v[:, :singular_value].to_dataframe() ** 2 v1_df["ident"] = v1_df.sum(axis=1) return v1_df @@ -382,14 +396,14 @@ def R(self, singular_value): return self.parcov.identity else: self.log("calc R @" + str(singular_value)) - #v1 = self.qhalfx.v[:, :singular_value] + # v1 = self.qhalfx.v[:, :singular_value] v1 = self.xtqx.v[:, :singular_value] self.__R = v1 * v1.T self.__R_sv = singular_value self.log("calc R @" + str(singular_value)) return self.__R - def I_minus_R(self,singular_value): + def I_minus_R(self, singular_value): """get I - R at a given singular value Args: @@ -405,7 +419,7 @@ def I_minus_R(self,singular_value): if singular_value > self.jco.ncol: return self.parcov.zero else: - #v2 = self.qhalfx.v[:, singular_value:] + # v2 = self.qhalfx.v[:, singular_value:] v2 = self.xtqx.v[:, singular_value:] self.__I_R = v2 * v2.T self.__I_R_sv = singular_value @@ -427,8 +441,10 @@ def G(self, singular_value): if singular_value == 0: self.__G_sv = 0 self.__G = Matrix( - x=np.zeros((self.jco.ncol,self.jco.nrow)), - row_names=self.jco.col_names, col_names=self.jco.row_names) + x=np.zeros((self.jco.ncol, self.jco.nrow)), + row_names=self.jco.col_names, + col_names=self.jco.row_names, + ) return self.__G mn = min(self.jco.shape) try: @@ -437,14 +453,15 @@ def G(self, singular_value): pass if singular_value > mn: self.logger.warn( - "ErrVar.G(): singular_value > min(npar,nobs):" + - "resetting to min(npar,nobs): " + - str(min(self.pst.npar_adj, self.pst.nnz_obs))) + "ErrVar.G(): singular_value > min(npar,nobs):" + + "resetting to min(npar,nobs): " + + str(min(self.pst.npar_adj, self.pst.nnz_obs)) + ) singular_value = min(self.pst.npar_adj, self.pst.nnz_obs) self.log("calc G @" + str(singular_value)) - #v1 = self.qhalfx.v[:, :singular_value] + # v1 = self.qhalfx.v[:, :singular_value] v1 = self.xtqx.v[:, :singular_value] - #s1 = ((self.qhalfx.s[:singular_value]) ** 2).inv + # s1 = ((self.qhalfx.s[:singular_value]) ** 2).inv s1 = (self.xtqx.s[:singular_value]).inv self.__G = v1 * s1 * v1.T * self.jco.T * self.obscov.inv self.__G_sv = singular_value @@ -454,7 +471,7 @@ def G(self, singular_value): self.log("calc G @" + str(singular_value)) return self.__G - def first_forecast(self,singular_value): + def first_forecast(self, singular_value): """get the null space term (first term) contribution to forecast (e.g. prediction) error variance at a given singular value. @@ -472,7 +489,6 @@ def first_forecast(self,singular_value): """ return self.first_prediction(singular_value) - def first_prediction(self, singular_value): """get the null space term (first term) contribution to prediction error variance at a given singular value. @@ -495,13 +511,17 @@ def first_prediction(self, singular_value): zero_preds[("first", pred.col_names[0])] = 0.0 return zero_preds self.log("calc first term parameter @" + str(singular_value)) - first_term = self.I_minus_R(singular_value).T * self.parcov *\ - self.I_minus_R(singular_value) + first_term = ( + self.I_minus_R(singular_value).T + * self.parcov + * self.I_minus_R(singular_value) + ) if self.predictions: results = {} for prediction in self.predictions_iter: - results[("first",prediction.col_names[0])] = \ - float((prediction.T * first_term * prediction).x) + results[("first", prediction.col_names[0])] = float( + (prediction.T * first_term * prediction).x + ) self.log("calc first term parameter @" + str(singular_value)) return results @@ -517,13 +537,15 @@ def first_parameter(self, singular_value): """ self.log("calc first term parameter @" + str(singular_value)) - first_term = self.I_minus_R(singular_value) * self.parcov * \ - self.I_minus_R(singular_value) + first_term = ( + self.I_minus_R(singular_value) + * self.parcov + * self.I_minus_R(singular_value) + ) self.log("calc first term parameter @" + str(singular_value)) return first_term - - def second_forecast(self,singular_value): + def second_forecast(self, singular_value): """get the solution space contribution to forecast (e.g. "prediction") error variance at a given singular value @@ -570,15 +592,17 @@ def second_prediction(self, singular_value): if singular_value > mn: inf_pred = {} for pred in self.predictions_iter: - inf_pred[("second",pred.col_names[0])] = 1.0E+35 + inf_pred[("second", pred.col_names[0])] = 1.0e35 return inf_pred else: - second_term = self.G(singular_value) * self.obscov * \ - self.G(singular_value).T + second_term = ( + self.G(singular_value) * self.obscov * self.G(singular_value).T + ) results = {} for prediction in self.predictions_iter: - results[("second",prediction.col_names[0])] = \ - float((prediction.T * second_term * prediction).x) + results[("second", prediction.col_names[0])] = float( + (prediction.T * second_term * prediction).x + ) self.log("calc second term prediction @" + str(singular_value)) return results @@ -599,7 +623,7 @@ def second_parameter(self, singular_value): self.log("calc second term parameter @" + str(singular_value)) return result - def third_forecast(self,singular_value): + def third_forecast(self, singular_value): """get the omitted parameter contribution to forecast (`prediction`) error variance at a given singular value. @@ -616,7 +640,7 @@ def third_forecast(self,singular_value): """ return self.third_prediction(singular_value) - def third_prediction(self,singular_value): + def third_prediction(self, singular_value): """get the omitted parameter contribution to prediction error variance at a given singular value. @@ -645,15 +669,18 @@ def third_prediction(self,singular_value): if singular_value > mn: inf_pred = {} for pred in self.predictions_iter: - inf_pred[("third",pred.col_names[0])] = 1.0E+35 + inf_pred[("third", pred.col_names[0])] = 1.0e35 return inf_pred else: results = {} - for prediction,omitted_prediction in \ - zip(self.predictions_iter, self.omitted_predictions): + for prediction, omitted_prediction in zip( + self.predictions_iter, self.omitted_predictions + ): # comes out as row vector, but needs to be a column vector - p = ((prediction.T * self.G(singular_value) * self.omitted_jco) - - omitted_prediction.T).T + p = ( + (prediction.T * self.G(singular_value) * self.omitted_jco) + - omitted_prediction.T + ).T result = float((p.T * self.omitted_parcov * p).x) results[("third", prediction.col_names[0])] = result self.log("calc third term prediction @" + str(singular_value)) @@ -681,7 +708,7 @@ def third_parameter(self, singular_value): self.log("calc third term parameter @" + str(singular_value)) return result - def get_null_proj(self, maxsing=None, eigthresh=1.0e-6 ): + def get_null_proj(self, maxsing=None, eigthresh=1.0e-6): """ get a null-space projection matrix of XTQX Args: @@ -703,12 +730,16 @@ def get_null_proj(self, maxsing=None, eigthresh=1.0e-6 ): if maxsing is None: maxsing = self.xtqx.get_maxsing(eigthresh=eigthresh) print("using {0} singular components".format(maxsing)) - self.log("forming null space projection matrix with " + \ - "{0} of {1} singular components".format(maxsing, self.jco.shape[1])) - - v2_proj = (self.xtqx.v[:, maxsing:] * self.xtqx.v[:, maxsing:].T) - self.log("forming null space projection matrix with " + \ - "{0} of {1} singular components".format(maxsing, self.jco.shape[1])) + self.log( + "forming null space projection matrix with " + + "{0} of {1} singular components".format(maxsing, self.jco.shape[1]) + ) + + v2_proj = self.xtqx.v[:, maxsing:] * self.xtqx.v[:, maxsing:].T + self.log( + "forming null space projection matrix with " + + "{0} of {1} singular components".format(maxsing, self.jco.shape[1]) + ) return v2_proj diff --git a/pyemu/la.py b/pyemu/la.py index db1c03deb..d76819049 100644 --- a/pyemu/la.py +++ b/pyemu/la.py @@ -13,6 +13,7 @@ from pyemu.utils.os_utils import _istextfile from .logger import Logger + class LinearAnalysis(object): """The base/parent class for linear analysis. @@ -67,18 +68,30 @@ class LinearAnalysis(object): """ - def __init__(self, jco=None, pst=None, parcov=None, obscov=None, - predictions=None, ref_var=1.0, verbose=False, - resfile=False, forecasts=None,sigma_range=4.0, - scale_offset=True,**kwargs): + + def __init__( + self, + jco=None, + pst=None, + parcov=None, + obscov=None, + predictions=None, + ref_var=1.0, + verbose=False, + resfile=False, + forecasts=None, + sigma_range=4.0, + scale_offset=True, + **kwargs + ): self.logger = Logger(verbose) self.log = self.logger.log self.jco_arg = jco - #if jco is None: + # if jco is None: self.__jco = jco if pst is None: if isinstance(jco, str): - pst_case = jco.replace(".jco", ".pst").replace(".jcb",".pst") + pst_case = jco.replace(".jco", ".pst").replace(".jcb", ".pst") if os.path.exists(pst_case): pst = pst_case self.pst_arg = pst @@ -92,11 +105,10 @@ def __init__(self, jco=None, pst=None, parcov=None, obscov=None, if forecasts is not None and predictions is not None: raise Exception("can't pass both forecasts and predictions") - self.sigma_range = sigma_range self.scale_offset = scale_offset - #private attributes - access is through @decorated functions + # private attributes - access is through @decorated functions self.__pst = None self.__parcov = None self.__obscov = None @@ -129,34 +141,37 @@ def __init__(self, jco=None, pst=None, parcov=None, obscov=None, if self.prediction_arg: self.__load_predictions() - self.log("pre-loading base components") if len(kwargs.keys()) > 0: - self.logger.warn("unused kwargs in type " + - str(self.__class__.__name__) + - " : " + str(kwargs)) - raise Exception("unused kwargs" + - " : " + str(kwargs)) + self.logger.warn( + "unused kwargs in type " + + str(self.__class__.__name__) + + " : " + + str(kwargs) + ) + raise Exception("unused kwargs" + " : " + str(kwargs)) # automatically do some things that should be done self.log("dropping prior information") pi = None try: pi = self.pst.prior_information except: - self.logger.statement("unable to access self.pst: can't tell if " + - " any prior information needs to be dropped.") + self.logger.statement( + "unable to access self.pst: can't tell if " + + " any prior information needs to be dropped." + ) if pi is not None: self.drop_prior_information() self.log("dropping prior information") - if resfile != False: self.log("scaling obscov by residual phi components") try: self.adjust_obscov_resfile(resfile=resfile) except: - self.logger.statement("unable to a find a residuals file for " +\ - " scaling obscov") + self.logger.statement( + "unable to a find a residuals file for " + " scaling obscov" + ) self.resfile = None self.res = None self.log("scaling obscov by residual phi components") @@ -169,42 +184,45 @@ def __fromfile(self, filename, astype=None): 'unc': pest uncertainty file. """ - assert os.path.exists(filename),"LinearAnalysis.__fromfile(): " +\ - "file not found:" + filename - ext = filename.split('.')[-1].lower() + assert os.path.exists(filename), ( + "LinearAnalysis.__fromfile(): " + "file not found:" + filename + ) + ext = filename.split(".")[-1].lower() if ext in ["jco", "jcb"]: - self.log("loading jco: "+filename) + self.log("loading jco: " + filename) if astype is None: astype = Jco m = astype.from_binary(filename) - self.log("loading jco: "+filename) - elif ext in ["mat","vec"]: - self.log("loading ascii: "+filename) + self.log("loading jco: " + filename) + elif ext in ["mat", "vec"]: + self.log("loading ascii: " + filename) if astype is None: astype = Matrix m = astype.from_ascii(filename) - self.log("loading ascii: "+filename) + self.log("loading ascii: " + filename) elif ext in ["cov"]: - self.log("loading cov: "+filename) + self.log("loading cov: " + filename) if astype is None: astype = Cov if _istextfile(filename): m = astype.from_ascii(filename) else: m = astype.from_binary(filename) - self.log("loading cov: "+filename) - elif ext in["unc"]: - self.log("loading unc: "+filename) + self.log("loading cov: " + filename) + elif ext in ["unc"]: + self.log("loading unc: " + filename) if astype is None: astype = Cov m = astype.from_uncfile(filename) - self.log("loading unc: "+filename) + self.log("loading unc: " + filename) else: - raise Exception("linear_analysis.__fromfile(): unrecognized" + - " filename extension:" + str(ext)) + raise Exception( + "linear_analysis.__fromfile(): unrecognized" + + " filename extension:" + + str(ext) + ) return m - def __load_pst(self): """private method set the pst attribute """ @@ -220,25 +238,30 @@ def __load_pst(self): self.log("loading pst: " + str(self.pst_arg)) return self.pst except Exception as e: - raise Exception("linear_analysis.__load_pst(): error loading"+\ - " pest control from argument: " + - str(self.pst_arg) + '\n->' + str(e)) - + raise Exception( + "linear_analysis.__load_pst(): error loading" + + " pest control from argument: " + + str(self.pst_arg) + + "\n->" + + str(e) + ) def __load_jco(self): """private method to set the jco attribute from a file or a matrix object """ if self.jco_arg is None: return None - #raise Exception("linear_analysis.__load_jco(): jco_arg is None") + # raise Exception("linear_analysis.__load_jco(): jco_arg is None") if isinstance(self.jco_arg, Matrix): self.__jco = self.jco_arg elif isinstance(self.jco_arg, str): - self.__jco = self.__fromfile(self.jco_arg,astype=Jco) + self.__jco = self.__fromfile(self.jco_arg, astype=Jco) else: - raise Exception("linear_analysis.__load_jco(): jco_arg must " + - "be a matrix object or a file name: " + - str(self.jco_arg)) + raise Exception( + "linear_analysis.__load_jco(): jco_arg must " + + "be a matrix object or a file name: " + + str(self.jco_arg) + ) def __load_parcov(self): """private method to set the parcov attribute from: @@ -254,8 +277,9 @@ def __load_parcov(self): if self.pst_arg: self.parcov_arg = self.pst_arg else: - raise Exception("linear_analysis.__load_parcov(): " + - "parcov_arg is None") + raise Exception( + "linear_analysis.__load_parcov(): " + "parcov_arg is None" + ) if isinstance(self.parcov_arg, Matrix): self.__parcov = self.parcov_arg return @@ -269,36 +293,45 @@ def __load_parcov(self): assert self.parcov_arg.shape[0] == self.jco.shape[1] assert self.parcov_arg.shape[1] == self.jco.shape[1] isdiagonal = False - self.logger.warn("linear_analysis.__load_parcov(): " + - "instantiating parcov from ndarray, can't " + - "verify parameters alignment with jco") - self.__parcov = Matrix(x=self.parcov_arg, - isdiagonal=isdiagonal, - row_names=self.jco.col_names, - col_names=self.jco.col_names) + self.logger.warn( + "linear_analysis.__load_parcov(): " + + "instantiating parcov from ndarray, can't " + + "verify parameters alignment with jco" + ) + self.__parcov = Matrix( + x=self.parcov_arg, + isdiagonal=isdiagonal, + row_names=self.jco.col_names, + col_names=self.jco.col_names, + ) self.log("loading parcov") - if isinstance(self.parcov_arg,str): + if isinstance(self.parcov_arg, str): # if the arg is a string ending with "pst" # then load parcov from parbounds if self.parcov_arg.lower().endswith(".pst"): - self.__parcov = Cov.from_parbounds(self.parcov_arg, - sigma_range=self.sigma_range, - scale_offset=self.scale_offset) + self.__parcov = Cov.from_parbounds( + self.parcov_arg, + sigma_range=self.sigma_range, + scale_offset=self.scale_offset, + ) else: self.__parcov = self.__fromfile(self.parcov_arg, astype=Cov) # if the arg is a pst object - elif isinstance(self.parcov_arg,Pst): - self.__parcov = Cov.from_parameter_data(self.parcov_arg, - sigma_range=self.sigma_range, - scale_offset=self.scale_offset) + elif isinstance(self.parcov_arg, Pst): + self.__parcov = Cov.from_parameter_data( + self.parcov_arg, + sigma_range=self.sigma_range, + scale_offset=self.scale_offset, + ) else: - raise Exception("linear_analysis.__load_parcov(): " + - "parcov_arg must be a " + - "matrix object or a file name: " + - str(self.parcov_arg)) + raise Exception( + "linear_analysis.__load_parcov(): " + + "parcov_arg must be a " + + "matrix object or a file name: " + + str(self.parcov_arg) + ) self.log("loading parcov") - def __load_obscov(self): """private method to set the obscov attribute from: a pest control file (observation weights) @@ -313,12 +346,13 @@ def __load_obscov(self): if self.pst_arg: self.obscov_arg = self.pst_arg else: - raise Exception("linear_analysis.__load_obscov(): " + - "obscov_arg is None") + raise Exception( + "linear_analysis.__load_obscov(): " + "obscov_arg is None" + ) if isinstance(self.obscov_arg, Matrix): self.__obscov = self.obscov_arg return - if isinstance(self.obscov_arg,np.ndarray): + if isinstance(self.obscov_arg, np.ndarray): # if the ndarray arg is a vector, # assume it is the diagonal of the obscov matrix if len(self.obscov_arg.shape) == 1: @@ -328,13 +362,17 @@ def __load_obscov(self): assert self.obscov_arg.shape[0] == self.jco.shape[0] assert self.obscov_arg.shape[1] == self.jco.shape[0] isdiagonal = False - self.logger.warn("linear_analysis.__load_obscov(): " + - "instantiating obscov from ndarray, " + - "can't verify observation alignment with jco") - self.__obscov = Matrix(x=self.obscov_arg, - isdiagonal=isdiagonal, - row_names=self.jco.row_names, - col_names=self.jco.row_names) + self.logger.warn( + "linear_analysis.__load_obscov(): " + + "instantiating obscov from ndarray, " + + "can't verify observation alignment with jco" + ) + self.__obscov = Matrix( + x=self.obscov_arg, + isdiagonal=isdiagonal, + row_names=self.jco.row_names, + col_names=self.jco.row_names, + ) self.log("loading obscov") if isinstance(self.obscov_arg, str): if self.obscov_arg.lower().endswith(".pst"): @@ -344,13 +382,14 @@ def __load_obscov(self): elif isinstance(self.obscov_arg, Pst): self.__obscov = Cov.from_observation_data(self.obscov_arg) else: - raise Exception("linear_analysis.__load_obscov(): " + - "obscov_arg must be a " + - "matrix object or a file name: " + - str(self.obscov_arg)) + raise Exception( + "linear_analysis.__load_obscov(): " + + "obscov_arg must be a " + + "matrix object or a file name: " + + str(self.obscov_arg) + ) self.log("loading obscov") - def __load_predictions(self): """private method set the predictions attribute from: mixed list of row names, matrix files and ndarrays @@ -376,12 +415,15 @@ def __load_predictions(self): vecs.append(arg) else: if self.jco is not None: - assert arg.shape[0] == self.jco.shape[1],\ - "linear_analysis.__load_predictions(): " +\ - "multi-prediction matrix(npar,npred) not aligned " +\ - "with jco(nobs,npar): " + str(arg.shape) +\ - ' ' + str(self.jco.shape) - #for pred_name in arg.row_names: + assert arg.shape[0] == self.jco.shape[1], ( + "linear_analysis.__load_predictions(): " + + "multi-prediction matrix(npar,npred) not aligned " + + "with jco(nobs,npar): " + + str(arg.shape) + + " " + + str(self.jco.shape) + ) + # for pred_name in arg.row_names: # vecs.append(arg.extract(row_names=pred_name).T) mat = arg elif isinstance(arg, str): @@ -389,49 +431,60 @@ def __load_predictions(self): row_names.append(arg.lower()) else: try: - pred_mat = self.__fromfile(arg,astype=Matrix) + pred_mat = self.__fromfile(arg, astype=Matrix) except Exception as e: - raise Exception("forecast argument: "+arg+" not found in " +\ - "jco row names and could not be " +\ - "loaded from a file.") + raise Exception( + "forecast argument: " + + arg + + " not found in " + + "jco row names and could not be " + + "loaded from a file." + ) # vector if pred_mat.shape[1] == 1: vecs.append(pred_mat) else: - #for pred_name in pred_mat.row_names: + # for pred_name in pred_mat.row_names: # vecs.append(pred_mat.get(row_names=pred_name)) if mat is None: mat = pred_mat else: mat = mat.extend((pred_mat)) elif isinstance(arg, np.ndarray): - self.logger.warn("linear_analysis.__load_predictions(): " + - "instantiating prediction matrix from " + - "ndarray, can't verify alignment") - self.logger.warn("linear_analysis.__load_predictions(): " + - "instantiating prediction matrix from " + - "ndarray, generating generic prediction names") - - pred_names = ["pred_{0}".format(i+1) for i in range(arg.shape[0])] + self.logger.warn( + "linear_analysis.__load_predictions(): " + + "instantiating prediction matrix from " + + "ndarray, can't verify alignment" + ) + self.logger.warn( + "linear_analysis.__load_predictions(): " + + "instantiating prediction matrix from " + + "ndarray, generating generic prediction names" + ) + + pred_names = ["pred_{0}".format(i + 1) for i in range(arg.shape[0])] if self.jco: names = self.jco.col_names elif self.parcov: names = self.parcov.col_names else: - raise Exception("linear_analysis.__load_predictions(): " + - "ndarray passed for predicitons " + - "requires jco or parcov to get " + - "parameter names") + raise Exception( + "linear_analysis.__load_predictions(): " + + "ndarray passed for predicitons " + + "requires jco or parcov to get " + + "parameter names" + ) if mat is None: - mat = Matrix(x=arg,row_names=pred_names,col_names=names).T + mat = Matrix(x=arg, row_names=pred_names, col_names=names).T else: - mat = mat.extend(Matrix(x=arg,row_names=pred_names,col_names=names).T) - #for pred_name in pred_names: + mat = mat.extend( + Matrix(x=arg, row_names=pred_names, col_names=names).T + ) + # for pred_name in pred_names: # vecs.append(pred_matrix.get(row_names=pred_name).T) else: - raise Exception("unrecognized predictions argument: " + - str(arg)) + raise Exception("unrecognized predictions argument: " + str(arg)) # turn vecs into a pyemu.Matrix if len(vecs) > 0: @@ -440,12 +493,11 @@ def __load_predictions(self): xs = xs.extend(vec.x) names = [vec.col_names[0] for vec in vecs] if mat is None: - mat = Matrix(x=xs,row_names=vecs[0].row_names, - col_names=names) + mat = Matrix(x=xs, row_names=vecs[0].row_names, col_names=names) else: - mat = mat.extend(Matrix(x = np.array(xs), - row_names=vecs[0].row_names, - col_names=names)) + mat = mat.extend( + Matrix(x=np.array(xs), row_names=vecs[0].row_names, col_names=names) + ) if len(row_names) > 0: extract = self.jco.extract(row_names=row_names).T @@ -453,7 +505,7 @@ def __load_predictions(self): mat = extract else: mat = mat.extend(extract) - #for row_name in row_names: + # for row_name in row_names: # vecs.append(extract.get(row_names=row_name).T) # call obscov to load __obscov so that __obscov # (priavte) can be manipulated @@ -474,11 +526,13 @@ def __load_predictions(self): except: fnames = [] if len(fnames) > 0: - self.logger.warn("forecasts with non-zero weight in pst: {0}...".format(','.join(fnames)) + - "\n -> re-setting these forecast weights to zero...") - self.pst.observation_data.loc[fnames,"weight"] = 0.0 + self.logger.warn( + "forecasts with non-zero weight in pst: {0}...".format(",".join(fnames)) + + "\n -> re-setting these forecast weights to zero..." + ) + self.pst.observation_data.loc[fnames, "weight"] = 0.0 self.log("loading forecasts") - self.logger.statement("forecast names: {0}".format(','.join(mat.col_names))) + self.logger.statement("forecast names: {0}".format(",".join(mat.col_names))) return self.__predictions # these property decorators help keep from loading potentially @@ -495,7 +549,7 @@ def forecast_names(self): """ if self.forecasts is None: return [] - #return [fore.col_names[0] for fore in self.forecasts] + # return [fore.col_names[0] for fore in self.forecasts] return list(self.predictions.col_names) @property @@ -510,7 +564,6 @@ def parcov(self): self.__load_parcov() return self.__parcov - @property def obscov(self): """ get the observation noise covariance matrix attribute @@ -567,7 +620,6 @@ def jco(self): self.__load_jco() return self.__jco - @property def predictions(self): """the prediction (aka forecast) sentivity vectors attribute @@ -626,15 +678,16 @@ def pst(self): """ if self.__pst is None and self.pst_arg is None: - raise Exception("linear_analysis.pst: can't access self.pst:" + - "no pest control argument passed") + raise Exception( + "linear_analysis.pst: can't access self.pst:" + + "no pest control argument passed" + ) elif self.__pst: return self.__pst else: self.__load_pst() return self.__pst - @property def fehalf(self): """Karhunen-Loeve scaling matrix attribute. @@ -651,7 +704,6 @@ def fehalf(self): self.log("fehalf") return self.__fehalf - @property def qhalf(self): """square root of the cofactor matrix attribute. Create the attribute if @@ -737,13 +789,13 @@ def mle_parameter_estimate(self): res = self.pst.res assert res is not None # build the prior expectation parameter vector - prior_expt = self.pst.parameter_data.loc[:,["parval1"]].copy() + prior_expt = self.pst.parameter_data.loc[:, ["parval1"]].copy() islog = self.pst.parameter_data.partrans == "log" prior_expt.loc[islog] = prior_expt.loc[islog].apply(np.log10) prior_expt = Matrix.from_dataframe(prior_expt) prior_expt.col_names = ["prior_expt"] # build the residual vector - res_vec = Matrix.from_dataframe(res.loc[:,["residual"]]) + res_vec = Matrix.from_dataframe(res.loc[:, ["residual"]]) # calc posterior expectation upgrade = self.mle_covariance * self.jco.T * res_vec @@ -751,9 +803,10 @@ def mle_parameter_estimate(self): post_expt = prior_expt + upgrade # post processing - back log transform - post_expt = pd.DataFrame(data=post_expt.x,index=post_expt.row_names, - columns=["post_expt"]) - post_expt.loc[islog,:] = 10.0**post_expt.loc[islog,:] + post_expt = pd.DataFrame( + data=post_expt.x, index=post_expt.row_names, columns=["post_expt"] + ) + post_expt.loc[islog, :] = 10.0 ** post_expt.loc[islog, :] return post_expt @property @@ -769,17 +822,15 @@ def prior_prediction(self): else: if self.predictions is not None: self.log("propagating prior to predictions") - prior_cov = self.predictions.T *\ - self.parcov * self.predictions - self.__prior_prediction = {n:v for n,v in - zip(prior_cov.row_names, - np.diag(prior_cov.x))} + prior_cov = self.predictions.T * self.parcov * self.predictions + self.__prior_prediction = { + n: v for n, v in zip(prior_cov.row_names, np.diag(prior_cov.x)) + } self.log("propagating prior to predictions") else: self.__prior_prediction = {} return self.__prior_prediction - def apply_karhunen_loeve_scaling(self): """apply karhuene-loeve scaling to the jacobian matrix. @@ -797,7 +848,6 @@ def apply_karhunen_loeve_scaling(self): self.__jco.col_names = cnames self.__parcov = self.parcov.identity - def clean(self): """drop regularization and prior information observation from the jco """ @@ -807,7 +857,7 @@ def clean(self): if not self.pst.estimation and self.pst.nprior > 0: self.drop_prior_information() - def reset_pst(self,arg): + def reset_pst(self, arg): """ reset the LinearAnalysis.pst attribute Args: @@ -818,7 +868,7 @@ def reset_pst(self,arg): self.__pst = None self.pst_arg = arg - def reset_parcov(self,arg=None): + def reset_parcov(self, arg=None): """reset the parcov attribute to None Args: @@ -831,8 +881,7 @@ def reset_parcov(self,arg=None): if arg is not None: self.parcov_arg = arg - - def reset_obscov(self,arg=None): + def reset_obscov(self, arg=None): """reset the obscov attribute to None Args: @@ -845,7 +894,6 @@ def reset_obscov(self,arg=None): if arg is not None: self.obscov_arg = arg - def drop_prior_information(self): """drop the prior information from the jco and pst attributes """ @@ -853,24 +901,27 @@ def drop_prior_information(self): self.logger.statement("can't drop prior info, LinearAnalysis.jco is None") return nprior_str = str(self.pst.nprior) - self.log("removing " + nprior_str + " prior info from jco, pst, and " + - "obs cov") - #pi_names = list(self.pst.prior_information.pilbl.values) + self.log( + "removing " + nprior_str + " prior info from jco, pst, and " + "obs cov" + ) + # pi_names = list(self.pst.prior_information.pilbl.values) pi_names = list(self.pst.prior_names) missing = [name for name in pi_names if name not in self.jco.obs_names] if len(missing) > 0: - raise Exception("LinearAnalysis.drop_prior_information(): "+ - " prior info not found: {0}".format(missing)) + raise Exception( + "LinearAnalysis.drop_prior_information(): " + + " prior info not found: {0}".format(missing) + ) if self.jco is not None: self.__jco.drop(pi_names, axis=0) self.__pst.prior_information = self.pst.null_prior self.__pst.control_data.pestmode = "estimation" - #self.__obscov.drop(pi_names,axis=0) - self.log("removing " + nprior_str + " prior info from jco, pst, and " + - "obs cov") - + # self.__obscov.drop(pi_names,axis=0) + self.log( + "removing " + nprior_str + " prior info from jco, pst, and " + "obs cov" + ) - def get(self,par_names=None,obs_names=None,astype=None): + def get(self, par_names=None, obs_names=None, astype=None): """method to get a new LinearAnalysis class using a subset of parameters and/or observations @@ -890,10 +941,12 @@ def get(self,par_names=None,obs_names=None,astype=None): # if there is nothing to do but copy if par_names is None and obs_names is None: if astype is not None: - self.logger.warn("LinearAnalysis.get(): astype is not None, " + - "but par_names and obs_names are None so" + - "\n ->Omitted attributes will not be " + - "propagated to new instance") + self.logger.warn( + "LinearAnalysis.get(): astype is not None, " + + "but par_names and obs_names are None so" + + "\n ->Omitted attributes will not be " + + "propagated to new instance" + ) else: return copy.deepcopy(self) # make sure the args are lists @@ -908,9 +961,11 @@ def get(self,par_names=None,obs_names=None,astype=None): obs_names = self.jco.row_names # if possible, get a new parcov if self.parcov: - new_parcov = self.parcov.get(col_names=[pname for pname in\ - par_names if pname in\ - self.parcov.col_names]) + new_parcov = self.parcov.get( + col_names=[ + pname for pname in par_names if pname in self.parcov.col_names + ] + ) else: new_parcov = None # if possible, get a new obscov @@ -920,7 +975,7 @@ def get(self,par_names=None,obs_names=None,astype=None): new_obscov = None # if possible, get a new pst if self.pst_arg is not None: - new_pst = self.pst.get(par_names=par_names,obs_names=obs_names) + new_pst = self.pst.get(par_names=par_names, obs_names=obs_names) else: new_pst = None new_extract = None @@ -937,14 +992,24 @@ def get(self,par_names=None,obs_names=None,astype=None): else: new_jco = None if astype is not None: - new = astype(jco=new_jco, pst=new_pst, parcov=new_parcov, - obscov=new_obscov, predictions=new_preds, - verbose=False) + new = astype( + jco=new_jco, + pst=new_pst, + parcov=new_parcov, + obscov=new_obscov, + predictions=new_preds, + verbose=False, + ) else: # return a new object of the same type - new = type(self)(jco=new_jco, pst=new_pst, parcov=new_parcov, - obscov=new_obscov, predictions=new_preds, - verbose=False) + new = type(self)( + jco=new_jco, + pst=new_pst, + parcov=new_parcov, + obscov=new_obscov, + predictions=new_preds, + verbose=False, + ) return new def adjust_obscov_resfile(self, resfile=None): @@ -963,7 +1028,6 @@ def adjust_obscov_resfile(self, resfile=None): self.pst.adjust_weights_resfile(resfile) self.__obscov.from_observation_data(self.pst) - def get_par_css_dataframe(self): """ get a dataframe of composite scaled sensitivities. Includes both PEST-style and Hill-style. @@ -979,7 +1043,7 @@ def get_par_css_dataframe(self): if self.pst is None: raise Exception("pst is None") jco = self.jco.to_dataframe() - weights = self.pst.observation_data.loc[jco.index,"weight"].copy().values + weights = self.pst.observation_data.loc[jco.index, "weight"].copy().values jco = (jco.T * weights).T dss_sum = jco.apply(np.linalg.norm) @@ -987,8 +1051,8 @@ def get_par_css_dataframe(self): css.columns = ["pest_css"] # log transform stuff self.pst.add_transform_columns() - parval1 = self.pst.parameter_data.loc[dss_sum.index,"parval1_trans"].values - css.loc[:,"hill_css"] = (dss_sum * parval1) / (float(self.pst.nnz_obs)**2) + parval1 = self.pst.parameter_data.loc[dss_sum.index, "parval1_trans"].values + css.loc[:, "hill_css"] = (dss_sum * parval1) / (float(self.pst.nnz_obs) ** 2) return css def get_cso_dataframe(self): @@ -1011,11 +1075,19 @@ def get_cso_dataframe(self): raise Exception("jco is None") if self.pst is None: raise Exception("pst is None") - weights = self.pst.observation_data.loc[self.jco.to_dataframe().index,"weight"].copy().values - cso = np.diag(np.sqrt((self.qhalfx.x.dot(self.qhalfx.x.T))))/(float(self.pst.npar-1)) - cso_df = pd.DataFrame.from_dict({'obnme':self.jco.to_dataframe().index,'cso':cso}) - cso_df.index=cso_df['obnme'] - cso_df.drop('obnme', axis=1, inplace=True) + weights = ( + self.pst.observation_data.loc[self.jco.to_dataframe().index, "weight"] + .copy() + .values + ) + cso = np.diag(np.sqrt((self.qhalfx.x.dot(self.qhalfx.x.T)))) / ( + float(self.pst.npar - 1) + ) + cso_df = pd.DataFrame.from_dict( + {"obnme": self.jco.to_dataframe().index, "cso": cso} + ) + cso_df.index = cso_df["obnme"] + cso_df.drop("obnme", axis=1, inplace=True) return cso_df def get_obs_competition_dataframe(self): @@ -1034,16 +1106,20 @@ def get_obs_competition_dataframe(self): if self.pst.res is None: raise Exception("res is None") onames = self.pst.nnz_obs_names - weights = self.pst.observation_data.loc[onames,"weight"].to_dict() - residuals = self.pst.res.loc[onames,"residual"].to_dict() + weights = self.pst.observation_data.loc[onames, "weight"].to_dict() + residuals = self.pst.res.loc[onames, "residual"].to_dict() jco = self.jco.to_dataframe() - df = pd.DataFrame(columns=onames,index=onames) - for i,oname in enumerate(onames): - df.loc[oname,oname] = 0.0 - for ooname in onames[i+1:]: - oc = weights[oname] * weights[ooname] * np.dot(jco.loc[oname,:].values, jco.loc[ooname,:].values.transpose()) - df.loc[oname,ooname] = oc - df.loc[ooname,oname] = oc + df = pd.DataFrame(columns=onames, index=onames) + for i, oname in enumerate(onames): + df.loc[oname, oname] = 0.0 + for ooname in onames[i + 1 :]: + oc = ( + weights[oname] + * weights[ooname] + * np.dot( + jco.loc[oname, :].values, jco.loc[ooname, :].values.transpose() + ) + ) + df.loc[oname, ooname] = oc + df.loc[ooname, oname] = oc return df - - diff --git a/pyemu/logger.py b/pyemu/logger.py index acc150c34..d504070ce 100644 --- a/pyemu/logger.py +++ b/pyemu/logger.py @@ -6,6 +6,7 @@ from .pyemu_warnings import PyemuWarning import copy + class Logger(object): """ a basic class for logging events during the linear analysis calculations if filename is passed, then a file handle is opened. @@ -16,7 +17,8 @@ class Logger(object): echo (`bool`): Flag to cause logged events to be echoed to the screen. """ - def __init__(self,filename, echo=False): + + def __init__(self, filename, echo=False): self.items = {} self.echo = bool(echo) if filename == True: @@ -24,14 +26,13 @@ def __init__(self,filename, echo=False): self.filename = None elif filename: self.filename = filename - self.f = open(filename, 'w') + self.f = open(filename, "w") self.t = datetime.now() self.log("opening " + str(filename) + " for logging") else: self.filename = None - - def statement(self,phrase): + def statement(self, phrase): """ log a one-time statement Arg: @@ -39,15 +40,14 @@ def statement(self,phrase): """ t = datetime.now() - s = str(t) + ' ' + str(phrase) + '\n' + s = str(t) + " " + str(phrase) + "\n" if self.echo: - print(s,end='') + print(s, end="") if self.filename: self.f.write(s) self.f.flush() - - def log(self,phrase): + def log(self, phrase): """log something that happened. Arg: @@ -60,24 +60,30 @@ def log(self,phrase): pass t = datetime.now() if phrase in self.items.keys(): - s = str(t) + ' finished: ' + str(phrase) + " took: " + \ - str(t - self.items[phrase]) + '\n' + s = ( + str(t) + + " finished: " + + str(phrase) + + " took: " + + str(t - self.items[phrase]) + + "\n" + ) if self.echo: - print(s,end='') + print(s, end="") if self.filename: self.f.write(s) self.f.flush() self.items.pop(phrase) else: - s = str(t) + ' starting: ' + str(phrase) + '\n' + s = str(t) + " starting: " + str(phrase) + "\n" if self.echo: - print(s,end='') + print(s, end="") if self.filename: self.f.write(s) self.f.flush() self.items[phrase] = copy.deepcopy(t) - def warn(self,message): + def warn(self, message): """write a warning to the log file. Arg: @@ -85,26 +91,25 @@ def warn(self,message): """ - s = str(datetime.now()) + " WARNING: " + message + '\n' + s = str(datetime.now()) + " WARNING: " + message + "\n" if self.echo: - print(s,end='') + print(s, end="") if self.filename: self.f.write(s) self.f.flush - warnings.warn(s,PyemuWarning) + warnings.warn(s, PyemuWarning) - def lraise(self,message): + def lraise(self, message): """log an exception, close the log file, then raise the exception Arg: phrase (`str`): exception statement to log and raise """ - s = str(datetime.now()) + " ERROR: " + message + '\n' - print(s,end='') + s = str(datetime.now()) + " ERROR: " + message + "\n" + print(s, end="") if self.filename: self.f.write(s) self.f.flush self.f.close() raise Exception(message) - diff --git a/pyemu/mat/__init__.py b/pyemu/mat/__init__.py index 903f69964..77328d579 100644 --- a/pyemu/mat/__init__.py +++ b/pyemu/mat/__init__.py @@ -3,4 +3,3 @@ operators to autoalign the elements based on row and column names.""" from .mat_handler import Matrix, Cov, Jco, concat, save_coo - diff --git a/pyemu/mat/mat_handler.py b/pyemu/mat/mat_handler.py index 856175b0b..6abced7d0 100644 --- a/pyemu/mat/mat_handler.py +++ b/pyemu/mat/mat_handler.py @@ -5,13 +5,15 @@ import warnings import numpy as np import pandas as pd -#import scipy.linalg as la + +# import scipy.linalg as la from pyemu.pst.pst_handler import Pst from ..pyemu_warnings import PyemuWarning -def save_coo(x, row_names, col_names, filename, chunk=None): + +def save_coo(x, row_names, col_names, filename, chunk=None): """write a PEST-compatible binary file. The data format is [int,int,float] for i,j,value. It is autodetected during the read with Matrix.from_binary(). @@ -28,11 +30,10 @@ def save_coo(x, row_names, col_names, filename, chunk=None): """ - f = open(filename, 'wb') + f = open(filename, "wb") # print("counting nnz") # write the header - header = np.array((x.shape[1], x.shape[0], x.nnz), - dtype=Matrix.binary_header_dt) + header = np.array((x.shape[1], x.shape[0], x.nnz), dtype=Matrix.binary_header_dt) header.tofile(f) data = np.core.records.fromarrays([x.row, x.col, x.data], dtype=Matrix.coo_rec_dt) @@ -40,17 +41,17 @@ def save_coo(x, row_names, col_names, filename, chunk=None): for name in col_names: if len(name) > Matrix.new_par_length: - name = name[:Matrix.new_par_length - 1] + name = name[: Matrix.new_par_length - 1] elif len(name) < Matrix.new_par_length: for _ in range(len(name), Matrix.new_par_length): - name = name + ' ' + name = name + " " f.write(name.encode()) for name in row_names: if len(name) > Matrix.new_obs_length: - name = name[:Matrix.new_obs_length - 1] + name = name[: Matrix.new_obs_length - 1] elif len(name) < Matrix.new_obs_length: for i in range(len(name), Matrix.new_obs_length): - name = name + ' ' + name = name + " " f.write(name.encode()) f.close() @@ -76,12 +77,15 @@ def concat(mats): if sorted(mats[0].col_names) != sorted(mat.col_names): col_match = False if not row_match and not col_match: - raise Exception("mat_handler.concat(): all Matrix objects"+\ - "must share either rows or cols") + raise Exception( + "mat_handler.concat(): all Matrix objects" + + "must share either rows or cols" + ) if row_match and col_match: - raise Exception("mat_handler.concat(): all Matrix objects"+\ - "share both rows and cols") + raise Exception( + "mat_handler.concat(): all Matrix objects" + "share both rows and cols" + ) if row_match: row_names = copy.deepcopy(mats[0].row_names) @@ -153,25 +157,25 @@ class Matrix(object): private attributes """ + integer = np.int32 double = np.float64 char = np.uint8 - binary_header_dt = np.dtype([('itemp1', integer), - ('itemp2', integer), - ('icount', integer)]) - binary_rec_dt = np.dtype([('j', integer), - ('dtemp', double)]) - coo_rec_dt = np.dtype([('i', integer),('j', integer), - ('dtemp', double)]) + binary_header_dt = np.dtype( + [("itemp1", integer), ("itemp2", integer), ("icount", integer)] + ) + binary_rec_dt = np.dtype([("j", integer), ("dtemp", double)]) + coo_rec_dt = np.dtype([("i", integer), ("j", integer), ("dtemp", double)]) par_length = 12 obs_length = 20 new_par_length = 200 new_obs_length = 200 - def __init__(self, x=None, row_names=[], col_names=[], isdiagonal=False, - autoalign=True): + def __init__( + self, x=None, row_names=[], col_names=[], isdiagonal=False, autoalign=True + ): self.col_names, self.row_names = [], [] _ = [self.col_names.append(str(c).lower()) for c in col_names] @@ -183,36 +187,48 @@ def __init__(self, x=None, row_names=[], col_names=[], isdiagonal=False, if x is not None: if x.ndim != 2: raise Exception("ndim != 2") - #x = np.atleast_2d(x) + # x = np.atleast_2d(x) if isdiagonal and len(row_names) > 0: - #assert 1 in x.shape,"Matrix error: diagonal matrix must have " +\ + # assert 1 in x.shape,"Matrix error: diagonal matrix must have " +\ # "one dimension == 1,shape is {0}".format(x.shape) mx_dim = max(x.shape) if len(row_names) != mx_dim: - raise Exception('Matrix.__init__(): diagonal shape[1] != len(row_names) ' +\ - str(x.shape) + ' ' + str(len(row_names))) + raise Exception( + "Matrix.__init__(): diagonal shape[1] != len(row_names) " + + str(x.shape) + + " " + + str(len(row_names)) + ) if mx_dim != x.shape[0]: x = x.transpose() - #x = x.transpose() + # x = x.transpose() else: if len(row_names) > 0: if len(row_names) != x.shape[0]: - raise Exception('Matrix.__init__(): shape[0] != len(row_names) ' +\ - str(x.shape) + ' ' + str(len(row_names))) + raise Exception( + "Matrix.__init__(): shape[0] != len(row_names) " + + str(x.shape) + + " " + + str(len(row_names)) + ) if len(col_names) > 0: # if this a row vector if len(row_names) == 0 and x.shape[1] == 1: x.transpose() if len(col_names) != x.shape[1]: - raise Exception('Matrix.__init__(): shape[1] != len(col_names) ' + \ - str(x.shape) + ' ' + str(len(col_names))) + raise Exception( + "Matrix.__init__(): shape[1] != len(col_names) " + + str(x.shape) + + " " + + str(len(col_names)) + ) self.__x = x self.isdiagonal = bool(isdiagonal) self.autoalign = bool(autoalign) - def reset_x(self,x,copy=True): + def reset_x(self, x, copy=True): """reset self.__x private attribute Args: @@ -234,8 +250,16 @@ def __str__(self): `str`: string representation """ - s = "shape:{0}:{1}".format(*self.shape)+" row names: " + str(self.row_names) + \ - '\n' + "col names: " + str(self.col_names) + '\n' + str(self.__x) + s = ( + "shape:{0}:{1}".format(*self.shape) + + " row names: " + + str(self.row_names) + + "\n" + + "col names: " + + str(self.col_names) + + "\n" + + str(self.__x) + ) return s def __getitem__(self, item): @@ -255,15 +279,18 @@ def __getitem__(self, item): # transpose a row vector to a column vector if submat.shape[0] == 1: submat = submat.transpose() - row_names = self.row_names[:submat.shape[0]] + row_names = self.row_names[: submat.shape[0]] if self.isdiagonal: col_names = row_names else: - col_names = self.col_names[:submat.shape[1]] - return type(self)(x=submat, isdiagonal=self.isdiagonal, - row_names=row_names, col_names=col_names, - autoalign=self.autoalign) - + col_names = self.col_names[: submat.shape[1]] + return type(self)( + x=submat, + isdiagonal=self.isdiagonal, + row_names=row_names, + col_names=col_names, + autoalign=self.autoalign, + ) def __pow__(self, power): """overload of numpy.ndarray.__pow__() operator @@ -284,22 +311,26 @@ def __pow__(self, power): elif power == -0.5: return (self.inv).sqrt else: - raise NotImplementedError("Matrix.__pow__() not implemented " + - "for negative powers except for -1") + raise NotImplementedError( + "Matrix.__pow__() not implemented " + + "for negative powers except for -1" + ) elif int(power) != float(power): if power == 0.5: return self.sqrt else: - raise NotImplementedError("Matrix.__pow__() not implemented " + - "for fractional powers except 0.5") + raise NotImplementedError( + "Matrix.__pow__() not implemented " + + "for fractional powers except 0.5" + ) else: - return type(self)(self.__x**power, row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) - - - + return type(self)( + self.__x ** power, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + ) def __sub__(self, other): """numpy.ndarray.__sub__() overload. Tries to speedup by @@ -319,74 +350,91 @@ def __sub__(self, other): """ if np.isscalar(other): - return Matrix(x=self.x - other, row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) + return Matrix( + x=self.x - other, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + ) else: - if isinstance(other,pd.DataFrame): + if isinstance(other, pd.DataFrame): other = Matrix.from_dataframe(other) if isinstance(other, np.ndarray): if self.shape != other.shape: - raise Exception("Matrix.__sub__() shape" + \ - "mismatch: " + \ - str(self.shape) + ' ' + \ - str(other.shape)) + raise Exception( + "Matrix.__sub__() shape" + + "mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) if self.isdiagonal: elem_sub = -1.0 * other for j in range(self.shape[0]): elem_sub[j, j] += self.x[j] - return type(self)(x=elem_sub, row_names=self.row_names, - col_names=self.col_names) + return type(self)( + x=elem_sub, row_names=self.row_names, col_names=self.col_names + ) else: - return type(self)(x=self.x - other, - row_names=self.row_names, - col_names=self.col_names) + return type(self)( + x=self.x - other, + row_names=self.row_names, + col_names=self.col_names, + ) elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) + if ( + self.autoalign + and other.autoalign + and not self.element_isaligned(other) + ): + common_rows = get_common_elements(self.row_names, other.row_names) + common_cols = get_common_elements(self.col_names, other.col_names) if len(common_rows) == 0: raise Exception("Matrix.__sub__ error: no common rows") if len(common_cols) == 0: raise Exception("Matrix.__sub__ error: no common cols") - first = self.get(row_names=common_rows, - col_names=common_cols) - second = other.get(row_names=common_rows, - col_names=common_cols) + first = self.get(row_names=common_rows, col_names=common_cols) + second = other.get(row_names=common_rows, col_names=common_cols) else: - assert self.shape == other.shape, \ - "Matrix.__sub__():shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) + assert self.shape == other.shape, ( + "Matrix.__sub__():shape mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) first = self second = other if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x - second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=first.x - second.x, + isdiagonal=True, + row_names=first.row_names, + col_names=first.col_names, + ) elif first.isdiagonal: elem_sub = -1.0 * second.newx for j in range(first.shape[0]): elem_sub[j, j] += first.x[j, 0] - return type(self)(x=elem_sub, row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=elem_sub, row_names=first.row_names, col_names=first.col_names + ) elif second.isdiagonal: elem_sub = first.newx for j in range(second.shape[0]): elem_sub[j, j] -= second.x[j, 0] - return type(self)(x=elem_sub, row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=elem_sub, row_names=first.row_names, col_names=first.col_names + ) else: - return type(self)(x=first.x - second.x, - row_names=first.row_names, - col_names=first.col_names) - + return type(self)( + x=first.x - second.x, + row_names=first.row_names, + col_names=first.col_names, + ) def __add__(self, other): """Overload of numpy.ndarray.__add__(). Tries to speedup by checking for @@ -406,30 +454,36 @@ def __add__(self, other): """ if np.isscalar(other): - return type(self)(x=self.x + other,row_names=self.row_names, - col_names=self.col_names,isdiagonal=self.isdiagonal) + return type(self)( + x=self.x + other, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + ) - if isinstance(other,pd.DataFrame): + if isinstance(other, pd.DataFrame): other = Matrix.from_dataframe(other) if isinstance(other, np.ndarray): - assert self.shape == other.shape, \ - "Matrix.__add__(): shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) + assert self.shape == other.shape, ( + "Matrix.__add__(): shape mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) if self.isdiagonal: - raise NotImplementedError("Matrix.__add__ not supported for" + - "diagonal self") + raise NotImplementedError( + "Matrix.__add__ not supported for" + "diagonal self" + ) else: - return type(self)(x=self.x + other, row_names=self.row_names, - col_names=self.col_names) + return type(self)( + x=self.x + other, row_names=self.row_names, col_names=self.col_names + ) elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) + if self.autoalign and other.autoalign and not self.element_isaligned(other): + common_rows = get_common_elements(self.row_names, other.row_names) + common_cols = get_common_elements(self.col_names, other.col_names) if len(common_rows) == 0: raise Exception("Matrix.__add__ error: no common rows") @@ -439,34 +493,47 @@ def __add__(self, other): first = self.get(row_names=common_rows, col_names=common_cols) second = other.get(row_names=common_rows, col_names=common_cols) else: - assert self.shape == other.shape, \ - "Matrix.__add__(): shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) + assert self.shape == other.shape, ( + "Matrix.__add__(): shape mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) first = self second = other if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x + second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=first.x + second.x, + isdiagonal=True, + row_names=first.row_names, + col_names=first.col_names, + ) elif first.isdiagonal: ox = second.newx for j in range(first.shape[0]): ox[j, j] += first.__x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=ox, row_names=first.row_names, col_names=first.col_names + ) elif second.isdiagonal: x = first.x for j in range(second.shape[0]): x[j, j] += second.x[j] - return type(self)(x=x, row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=x, row_names=first.row_names, col_names=first.col_names + ) else: - return type(self)(x=first.x + second.x, - row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=first.x + second.x, + row_names=first.row_names, + col_names=first.col_names, + ) else: - raise Exception("Matrix.__add__(): unrecognized type for " + - "other in __add__: " + str(type(other))) + raise Exception( + "Matrix.__add__(): unrecognized type for " + + "other in __add__: " + + str(type(other)) + ) def hadamard_product(self, other): """Overload of numpy.ndarray.__mult__(): element-wise multiplication. @@ -489,26 +556,29 @@ def hadamard_product(self, other): if np.isscalar(other): return type(self)(x=self.x * other) - if isinstance(other,pd.DataFrame): + if isinstance(other, pd.DataFrame): other = Matrix.from_dataframe(other) if isinstance(other, np.ndarray): if other.shape != self.shape: - raise Exception("Matrix.hadamard_product(): shape mismatch: " + \ - str(self.shape) + ' ' + str(other.shape)) + raise Exception( + "Matrix.hadamard_product(): shape mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) if self.isdiagonal: - raise NotImplementedError("Matrix.hadamard_product() not supported for" + - "diagonal self") + raise NotImplementedError( + "Matrix.hadamard_product() not supported for" + "diagonal self" + ) else: - return type(self)(x=self.x * other, row_names=self.row_names, - col_names=self.col_names) + return type(self)( + x=self.x * other, row_names=self.row_names, col_names=self.col_names + ) elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) + if self.autoalign and other.autoalign and not self.element_isaligned(other): + common_rows = get_common_elements(self.row_names, other.row_names) + common_cols = get_common_elements(self.col_names, other.col_names) if len(common_rows) == 0: raise Exception("Matrix.hadamard_product error: no common rows") @@ -519,15 +589,22 @@ def hadamard_product(self, other): second = other.get(row_names=common_rows, col_names=common_cols) else: if other.shape != self.shape: - raise Exception("Matrix.hadamard_product(): shape mismatch: " + \ - str(self.shape) + ' ' + str(other.shape)) + raise Exception( + "Matrix.hadamard_product(): shape mismatch: " + + str(self.shape) + + " " + + str(other.shape) + ) first = self second = other if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x * second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=first.x * second.x, + isdiagonal=True, + row_names=first.row_names, + col_names=first.col_names, + ) # elif first.isdiagonal: # #ox = second.as_2d # #for j in range(first.shape[0]): @@ -541,13 +618,17 @@ def hadamard_product(self, other): # return type(self)(x=first.x * second.as_2d, row_names=first.row_names, # col_names=first.col_names) else: - return type(self)(x=first.as_2d * second.as_2d, - row_names=first.row_names, - col_names=first.col_names) + return type(self)( + x=first.as_2d * second.as_2d, + row_names=first.row_names, + col_names=first.col_names, + ) else: - raise Exception("Matrix.hadamard_product(): unrecognized type for " + - "other: " + str(type(other))) - + raise Exception( + "Matrix.hadamard_product(): unrecognized type for " + + "other: " + + str(type(other)) + ) def __mul__(self, other): """Dot product multiplication overload. Tries to speedup by @@ -570,31 +651,40 @@ def __mul__(self, other): other = Matrix.from_dataframe(other) if np.isscalar(other): - return type(self)(x=self.x.copy() * other, - row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) + return type(self)( + x=self.x.copy() * other, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + ) elif isinstance(other, np.ndarray): if self.shape[1] != other.shape[0]: - raise Exception("Matrix.__mul__(): matrices are not aligned: " +\ - str(self.shape) + ' ' + str(other.shape)) + raise Exception( + "Matrix.__mul__(): matrices are not aligned: " + + str(self.shape) + + " " + + str(other.shape) + ) if self.isdiagonal: - return type(self)(x=np.dot(np.diag(self.__x.flatten()).transpose(), - other)) + return type(self)( + x=np.dot(np.diag(self.__x.flatten()).transpose(), other) + ) else: return type(self)(x=np.atleast_2d(np.dot(self.__x, other))) elif isinstance(other, Matrix): - if self.autoalign and other.autoalign\ - and not self.mult_isaligned(other): + if self.autoalign and other.autoalign and not self.mult_isaligned(other): common = get_common_elements(self.col_names, other.row_names) if len(common) == 0: - raise Exception("Matrix.__mult__():self.col_names " +\ - "and other.row_names" +\ - "don't share any common elements. first 10: " +\ - ','.join(self.col_names[:9]) + '...and..' +\ - ','.join(other.row_names[:9])) + raise Exception( + "Matrix.__mult__():self.col_names " + + "and other.row_names" + + "don't share any common elements. first 10: " + + ",".join(self.col_names[:9]) + + "...and.." + + ",".join(other.row_names[:9]) + ) # these should be aligned if isinstance(self, Cov): first = self.get(row_names=common, col_names=common) @@ -603,42 +693,53 @@ def __mul__(self, other): if isinstance(other, Cov): second = other.get(row_names=common, col_names=common) else: - second = other.get(row_names=common, - col_names=other.col_names) + second = other.get(row_names=common, col_names=other.col_names) else: if self.shape[1] != other.shape[0]: - raise Exception("Matrix.__mul__(): matrices are not aligned: " +\ - str(self.shape) + ' ' + str(other.shape)) + raise Exception( + "Matrix.__mul__(): matrices are not aligned: " + + str(self.shape) + + " " + + str(other.shape) + ) first = self second = other if first.isdiagonal and second.isdiagonal: - elem_prod = type(self)(x=first.x.transpose() * second.x, - row_names=first.row_names, - col_names=second.col_names) + elem_prod = type(self)( + x=first.x.transpose() * second.x, + row_names=first.row_names, + col_names=second.col_names, + ) elem_prod.isdiagonal = True return elem_prod elif first.isdiagonal: ox = second.newx for j in range(first.shape[0]): ox[j, :] *= first.x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=second.col_names) + return type(self)( + x=ox, row_names=first.row_names, col_names=second.col_names + ) elif second.isdiagonal: x = first.newx ox = second.x for j in range(first.shape[1]): x[:, j] *= ox[j] - return type(self)(x=x, row_names=first.row_names, - col_names=second.col_names) + return type(self)( + x=x, row_names=first.row_names, col_names=second.col_names + ) else: - return type(self)(np.dot(first.x, second.x), - row_names=first.row_names, - col_names=second.col_names) + return type(self)( + np.dot(first.x, second.x), + row_names=first.row_names, + col_names=second.col_names, + ) else: - raise Exception("Matrix.__mul__(): unrecognized " + - "other arg type in __mul__: " + str(type(other))) - + raise Exception( + "Matrix.__mul__(): unrecognized " + + "other arg type in __mul__: " + + str(type(other)) + ) def __rmul__(self, other): """Reverse order Dot product multiplication overload. @@ -661,26 +762,36 @@ def __rmul__(self, other): # other = Matrix.from_dataframe(other) if np.isscalar(other): - return type(self)(x=self.x.copy() * other,row_names=self.row_names,\ - col_names=self.col_names,isdiagonal=self.isdiagonal) + return type(self)( + x=self.x.copy() * other, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + ) elif isinstance(other, np.ndarray): if self.shape[0] != other.shape[1]: - raise Exception("Matrix.__rmul__(): matrices are not aligned: " +\ - str(other.shape) + ' ' + str(self.shape)) + raise Exception( + "Matrix.__rmul__(): matrices are not aligned: " + + str(other.shape) + + " " + + str(self.shape) + ) if self.isdiagonal: - return type(self)(x=np.dot(other,np.diag(self.__x.flatten()).\ - transpose())) + return type(self)( + x=np.dot(other, np.diag(self.__x.flatten()).transpose()) + ) else: - return type(self)(x=np.dot(other,self.__x)) + return type(self)(x=np.dot(other, self.__x)) elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.mult_isaligned(other): + if self.autoalign and other.autoalign and not self.mult_isaligned(other): common = get_common_elements(self.row_names, other.col_names) if len(common) == 0: - raise Exception("Matrix.__rmul__():self.col_names " +\ - "and other.row_names" +\ - "don't share any common elements") + raise Exception( + "Matrix.__rmul__():self.col_names " + + "and other.row_names" + + "don't share any common elements" + ) # these should be aligned if isinstance(self, Cov): first = self.get(row_names=common, col_names=common) @@ -689,42 +800,53 @@ def __rmul__(self, other): if isinstance(other, Cov): second = other.get(row_names=common, col_names=common) else: - second = other.get(col_names=common, - row_names=other.col_names) + second = other.get(col_names=common, row_names=other.col_names) else: if self.shape[0] != other.shape[1]: - raise Exception("Matrix.__rmul__(): matrices are not aligned: " +\ - str(other.shape) + ' ' + str(self.shape)) + raise Exception( + "Matrix.__rmul__(): matrices are not aligned: " + + str(other.shape) + + " " + + str(self.shape) + ) first = other second = self if first.isdiagonal and second.isdiagonal: - elem_prod = type(self)(x=first.x.transpose() * second.x, - row_names=first.row_names, - col_names=second.col_names) + elem_prod = type(self)( + x=first.x.transpose() * second.x, + row_names=first.row_names, + col_names=second.col_names, + ) elem_prod.isdiagonal = True return elem_prod elif first.isdiagonal: ox = second.newx for j in range(first.shape[0]): ox[j, :] *= first.x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=second.col_names) + return type(self)( + x=ox, row_names=first.row_names, col_names=second.col_names + ) elif second.isdiagonal: x = first.newx ox = second.x for j in range(first.shape[1]): x[:, j] *= ox[j] - return type(self)(x=x, row_names=first.row_names, - col_names=second.col_names) + return type(self)( + x=x, row_names=first.row_names, col_names=second.col_names + ) else: - return type(self)(np.dot(first.x, second.x), - row_names=first.row_names, - col_names=second.col_names) + return type(self)( + np.dot(first.x, second.x), + row_names=first.row_names, + col_names=second.col_names, + ) else: - raise Exception("Matrix.__rmul__(): unrecognized " + - "other arg type in __mul__: " + str(type(other))) - + raise Exception( + "Matrix.__rmul__(): unrecognized " + + "other arg type in __mul__: " + + str(type(other)) + ) def __set_svd(self): """private method to set SVD components. @@ -747,24 +869,31 @@ def __set_svd(self): v, s, u = np.linalg.svd(x.transpose(), full_matrices=True) u = u.transpose() except Exception as e: - np.savetxt("failed_svd.dat",x,fmt="%15.6E") - raise Exception("Matrix.__set_svd(): " + - "unable to compute SVD of self.x, " + - "saved matrix to 'failed_svd.dat' -- {0}".\ - format(str(e))) + np.savetxt("failed_svd.dat", x, fmt="%15.6E") + raise Exception( + "Matrix.__set_svd(): " + + "unable to compute SVD of self.x, " + + "saved matrix to 'failed_svd.dat' -- {0}".format(str(e)) + ) col_names = ["left_sing_vec_" + str(i + 1) for i in range(u.shape[1])] - self.__u = Matrix(x=u, row_names=self.row_names, - col_names=col_names, autoalign=False) + self.__u = Matrix( + x=u, row_names=self.row_names, col_names=col_names, autoalign=False + ) sing_names = ["sing_val_" + str(i + 1) for i in range(s.shape[0])] - self.__s = Matrix(x=np.atleast_2d(s).transpose(), row_names=sing_names, - col_names=sing_names, isdiagonal=True, - autoalign=False) + self.__s = Matrix( + x=np.atleast_2d(s).transpose(), + row_names=sing_names, + col_names=sing_names, + isdiagonal=True, + autoalign=False, + ) col_names = ["right_sing_vec_" + str(i + 1) for i in range(v.shape[0])] - self.__v = Matrix(v, row_names=self.col_names, col_names=col_names, - autoalign=False) + self.__v = Matrix( + v, row_names=self.col_names, col_names=col_names, autoalign=False + ) def mult_isaligned(self, other): """check if matrices are aligned for dot product multiplication @@ -776,15 +905,15 @@ def mult_isaligned(self, other): `bool`: True if aligned, False if not aligned """ - assert isinstance(other, Matrix), \ - "Matrix.isaligned(): other argumnent must be type Matrix, not: " +\ - str(type(other)) + assert isinstance(other, Matrix), ( + "Matrix.isaligned(): other argumnent must be type Matrix, not: " + + str(type(other)) + ) if self.col_names == other.row_names: return True else: return False - def element_isaligned(self, other): """check if matrices are aligned for element-wise operations @@ -795,16 +924,16 @@ def element_isaligned(self, other): `bool`: True if aligned, False if not aligned """ - if not isinstance(other,Matrix): - raise Exception("Matrix.isaligned(): other argument must be type Matrix, not: " +\ - str(type(other))) - if self.row_names == other.row_names \ - and self.col_names == other.col_names: + if not isinstance(other, Matrix): + raise Exception( + "Matrix.isaligned(): other argument must be type Matrix, not: " + + str(type(other)) + ) + if self.row_names == other.row_names and self.col_names == other.col_names: return True else: return False - @property def newx(self): """return a copy of `Matrix.x` attribute @@ -815,7 +944,6 @@ def newx(self): """ return self.__x.copy() - @property def x(self): """return a reference to `Matrix.x` @@ -840,7 +968,6 @@ def as_2d(self): return self.x return np.diag(self.x.flatten()) - def to_2d(self): """ get a 2D `Matrix` representation of `Matrix`. If not `Matrix.isdiagonal`, simply return a copy of `Matrix`, otherwise, constructs and returns a new `Matrix` @@ -852,8 +979,12 @@ def to_2d(self): """ if not self.isdiagonal: return self.copy() - return type(self)(x=np.diag(self.x.flatten()),row_names=self.row_names, - col_names=self.col_names,isdiagonal=False) + return type(self)( + x=np.diag(self.x.flatten()), + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=False, + ) @property def shape(self): @@ -901,7 +1032,6 @@ def T(self): """ return self.transpose - @property def transpose(self): """transpose operation of self @@ -911,15 +1041,20 @@ def transpose(self): """ if not self.isdiagonal: - return type(self)(x=self.__x.copy().transpose(), - row_names=self.col_names, - col_names=self.row_names, - autoalign=self.autoalign) + return type(self)( + x=self.__x.copy().transpose(), + row_names=self.col_names, + col_names=self.row_names, + autoalign=self.autoalign, + ) else: - return type(self)(x=self.__x.copy(), row_names=self.row_names, - col_names=self.col_names, - isdiagonal=True, autoalign=self.autoalign) - + return type(self)( + x=self.__x.copy(), + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=True, + autoalign=self.autoalign, + ) @property def inv(self): @@ -941,23 +1076,34 @@ def inv(self): if self.isdiagonal: inv = 1.0 / self.__x - if (np.any(~np.isfinite(inv))): + if np.any(~np.isfinite(inv)): idx = np.isfinite(inv) - np.savetxt("testboo.dat",idx) - invalid = [self.row_names[i] for i in range(idx.shape[0]) if idx[i] == 0.0] - raise Exception("Matrix.inv has produced invalid floating points " + - " for the following elements:" + ','.join(invalid)) - return type(self)(x=inv, isdiagonal=True, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) + np.savetxt("testboo.dat", idx) + invalid = [ + self.row_names[i] for i in range(idx.shape[0]) if idx[i] == 0.0 + ] + raise Exception( + "Matrix.inv has produced invalid floating points " + + " for the following elements:" + + ",".join(invalid) + ) + return type(self)( + x=inv, + isdiagonal=True, + row_names=self.row_names, + col_names=self.col_names, + autoalign=self.autoalign, + ) else: - return type(self)(x=np.linalg.inv(self.__x), row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) + return type(self)( + x=np.linalg.inv(self.__x), + row_names=self.row_names, + col_names=self.col_names, + autoalign=self.autoalign, + ) @staticmethod - def get_maxsing_from_s(s,eigthresh=1.0e-5): + def get_maxsing_from_s(s, eigthresh=1.0e-5): """static method to work out the maxsing for a given singular spectrum @@ -985,7 +1131,7 @@ def get_maxsing_from_s(s,eigthresh=1.0e-5): break return max(1, ising) - def get_maxsing(self,eigthresh=1.0e-5): + def get_maxsing(self, eigthresh=1.0e-5): """ Get the number of singular components with a singular value ratio greater than or equal to eigthresh @@ -1008,7 +1154,7 @@ def get_maxsing(self,eigthresh=1.0e-5): return Matrix.get_maxsing_from_s(self.s.x, eigthresh=eigthresh) - def pseudo_inv_components(self,maxsing=None,eigthresh=1.0e-5,truncate=True): + def pseudo_inv_components(self, maxsing=None, eigthresh=1.0e-5, truncate=True): """ Get the (optionally) truncated SVD components Args: @@ -1039,16 +1185,16 @@ def pseudo_inv_components(self,maxsing=None,eigthresh=1.0e-5,truncate=True): if maxsing is None: maxsing = self.get_maxsing(eigthresh=eigthresh) else: - maxsing = min(self.get_maxsing(eigthresh=eigthresh),maxsing) + maxsing = min(self.get_maxsing(eigthresh=eigthresh), maxsing) s = self.full_s.copy() v = self.v.copy() u = self.u.copy() if truncate: - s = s[:maxsing,:maxsing] - v = v[:,:maxsing] - u = u[:,:maxsing] + s = s[:maxsing, :maxsing] + v = v[:, :maxsing] + u = u[:, :maxsing] else: new_s = self.full_s.copy() s = new_s @@ -1056,9 +1202,9 @@ def pseudo_inv_components(self,maxsing=None,eigthresh=1.0e-5,truncate=True): v.x[:, maxsing:] = 0.0 u.x[:, maxsing:] = 0.0 - return u,s,v + return u, s, v - def pseudo_inv(self,maxsing=None,eigthresh=1.0e-5): + def pseudo_inv(self, maxsing=None, eigthresh=1.0e-5): """ The pseudo inverse of self. Formed using truncated singular value decomposition and `Matrix.pseudo_inv_components` @@ -1077,9 +1223,9 @@ def pseudo_inv(self,maxsing=None,eigthresh=1.0e-5): full_s = self.full_s.T for i in range(self.s.shape[0]): if i <= maxsing: - full_s.x[i,i] = 1.0 / full_s.x[i,i] + full_s.x[i, i] = 1.0 / full_s.x[i, i] else: - full_s.x[i,i] = 0.0 + full_s.x[i, i] = 0.0 return self.v * full_s * self.u.T @property @@ -1095,19 +1241,29 @@ def sqrt(self): """ if self.isdiagonal: - return type(self)(x=np.sqrt(self.__x), isdiagonal=True, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - elif self.shape[1] == 1: #a vector - return type(self)(x=np.sqrt(self.__x), isdiagonal=False, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) + return type(self)( + x=np.sqrt(self.__x), + isdiagonal=True, + row_names=self.row_names, + col_names=self.col_names, + autoalign=self.autoalign, + ) + elif self.shape[1] == 1: # a vector + return type(self)( + x=np.sqrt(self.__x), + isdiagonal=False, + row_names=self.row_names, + col_names=self.col_names, + autoalign=self.autoalign, + ) else: - return type(self)(x=np.sqrt(self.__x), row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) + return type(self)( + x=np.sqrt(self.__x), + row_names=self.row_names, + col_names=self.col_names, + autoalign=self.autoalign, + ) + @property def full_s(self): """ Get the full singular value matrix @@ -1117,12 +1273,16 @@ def full_s(self): with zeros along the diagonal from `min(Matrix.shape)` to `max(Matrix.shape)` """ - x = np.zeros((self.shape),dtype=np.float32) + x = np.zeros((self.shape), dtype=np.float32) - x[:self.s.shape[0],:self.s.shape[0]] = self.s.as_2d - s = Matrix(x=x, row_names=self.row_names, - col_names=self.col_names, isdiagonal=False, - autoalign=False) + x[: self.s.shape[0], : self.s.shape[0]] = self.s.as_2d + s = Matrix( + x=x, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=False, + autoalign=False, + ) return s @property @@ -1137,7 +1297,6 @@ def s(self): self.__set_svd() return self.__s - @property def u(self): """the left singular vector Matrix @@ -1150,7 +1309,6 @@ def u(self): self.__set_svd() return self.__u - @property def v(self): """the right singular vector Matrix @@ -1171,14 +1329,15 @@ def zero2d(self): `Matrix`: `Matrix of zeros` """ - return type(self)(x=np.atleast_2d(np.zeros((self.shape[0],self.shape[1]))), - row_names=self.row_names, - col_names=self.col_names, - isdiagonal=False) - + return type(self)( + x=np.atleast_2d(np.zeros((self.shape[0], self.shape[1]))), + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=False, + ) @staticmethod - def find_rowcol_indices(names,row_names,col_names,axis=None): + def find_rowcol_indices(names, row_names, col_names, axis=None): """fast(er) look of row and colum names indices Args: @@ -1203,29 +1362,33 @@ def find_rowcol_indices(names,row_names,col_names,axis=None): col_idxs = [] for name in names: name = name.lower() - if name not in scol \ - and name not in srow: - raise Exception('Matrix.indices(): name not found: ' + name) + if name not in scol and name not in srow: + raise Exception("Matrix.indices(): name not found: " + name) if name in scol: col_idxs.append(self_col_idxs[name]) if name.lower() in srow: row_idxs.append(self_row_idxs[name]) if axis is None: - return np.array(row_idxs, dtype=np.int32), \ - np.array(col_idxs, dtype=np.int32) + return ( + np.array(row_idxs, dtype=np.int32), + np.array(col_idxs, dtype=np.int32), + ) elif axis == 0: if len(row_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in row_names") + raise Exception( + "Matrix.indices(): " + "not all names found in row_names" + ) return np.array(row_idxs, dtype=np.int32) elif axis == 1: if len(col_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in col_names") + raise Exception( + "Matrix.indices(): " + "not all names found in col_names" + ) return np.array(col_idxs, dtype=np.int32) else: - raise Exception("Matrix.indices(): " + - "axis argument must 0 or 1, not:" + str(axis)) + raise Exception( + "Matrix.indices(): " + "axis argument must 0 or 1, not:" + str(axis) + ) def indices(self, names, axis=None): """get the row and col indices of names. If axis is None, two ndarrays @@ -1246,8 +1409,9 @@ def indices(self, names, axis=None): thin wrapper around `Matrix.find_rowcol_indices` static method """ - return Matrix.find_rowcol_indices(names,self.row_names,self.col_names,axis=axis) - + return Matrix.find_rowcol_indices( + names, self.row_names, self.col_names, axis=axis + ) def align(self, names, axis=None): """reorder `Matrix` by names in place. If axis is None, reorder both indices @@ -1278,26 +1442,33 @@ def align(self, names, axis=None): else: if axis is None: - raise Exception("Matrix.align(): must specify axis in " + - "align call for non-diagonal instances") + raise Exception( + "Matrix.align(): must specify axis in " + + "align call for non-diagonal instances" + ) if axis == 0: if row_idxs.shape[0] != self.shape[0]: - raise Exception("Matrix.align(): not all names found in self.row_names") + raise Exception( + "Matrix.align(): not all names found in self.row_names" + ) self.__x = self.__x[row_idxs, :] row_names = [] _ = [row_names.append(self.row_names[i]) for i in row_idxs] self.row_names = row_names elif axis == 1: if col_idxs.shape[0] != self.shape[1]: - raise Exception("Matrix.align(): not all names found in self.col_names") + raise Exception( + "Matrix.align(): not all names found in self.col_names" + ) self.__x = self.__x[:, col_idxs] col_names = [] _ = [col_names.append(self.col_names[i]) for i in row_idxs] self.col_names = col_names else: - raise Exception("Matrix.align(): axis argument to align()" + - " must be either 0 or 1") - + raise Exception( + "Matrix.align(): axis argument to align()" + + " must be either 0 or 1" + ) def get(self, row_names=None, col_names=None, drop=False): """get a new `Matrix` instance ordered on row_names or col_names @@ -1314,15 +1485,16 @@ def get(self, row_names=None, col_names=None, drop=False): """ if row_names is None and col_names is None: - raise Exception("Matrix.get(): must pass at least" + - " row_names or col_names") + raise Exception( + "Matrix.get(): must pass at least" + " row_names or col_names" + ) if row_names is not None and not isinstance(row_names, list): row_names = [row_names] if col_names is not None and not isinstance(col_names, list): col_names = [col_names] - if isinstance(self,Cov) and (row_names is None or col_names is None ): + if isinstance(self, Cov) and (row_names is None or col_names is None): if row_names is not None: idxs = self.indices(row_names, axis=0) names = row_names @@ -1359,7 +1531,6 @@ def get(self, row_names=None, col_names=None, drop=False): return type(self)(x=extract, row_names=row_names, col_names=col_names) - def copy(self): """get a copy of `Matrix` @@ -1367,10 +1538,13 @@ def copy(self): `Matrix`: copy of this `Matrix` """ - return type(self)(x=self.newx,row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal,autoalign=self.autoalign) - + return type(self)( + x=self.newx, + row_names=self.row_names, + col_names=self.col_names, + isdiagonal=self.isdiagonal, + autoalign=self.autoalign, + ) def drop(self, names, axis): """ drop elements from `Matrix` in place @@ -1397,22 +1571,26 @@ def drop(self, names, axis): self.__x = np.delete(self.__x, idxs, 0) keep_names = [name for name in self.row_names if name not in names] if len(keep_names) != self.__x.shape[0]: - raise Exception("shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape)) + raise Exception( + "shape-name mismatch:" + + "{0}:{0}".format(len(keep_names), self.__x.shape) + ) self.row_names = keep_names self.col_names = copy.deepcopy(keep_names) # idxs = np.sort(idxs) # for idx in idxs[::-1]: # del self.row_names[idx] # del self.col_names[idx] - elif isinstance(self,Cov): + elif isinstance(self, Cov): self.__x = np.delete(self.__x, idxs, 0) self.__x = np.delete(self.__x, idxs, 1) keep_names = [name for name in self.row_names if name not in names] if len(keep_names) != self.__x.shape[0]: - raise Exception("shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape)) + raise Exception( + "shape-name mismatch:" + + "{0}:{0}".format(len(keep_names), self.__x.shape) + ) self.row_names = keep_names self.col_names = copy.deepcopy(keep_names) # idxs = np.sort(idxs) @@ -1427,8 +1605,10 @@ def drop(self, names, axis): self.__x = np.delete(self.__x, idxs, 0) keep_names = [name for name in self.row_names if name not in names] if len(keep_names) != self.__x.shape[0]: - raise Exception("shape-name mismatch:"+\ - "{0}:{1}".format(len(keep_names),self.__x.shape)) + raise Exception( + "shape-name mismatch:" + + "{0}:{1}".format(len(keep_names), self.__x.shape) + ) self.row_names = keep_names # idxs = np.sort(idxs) # for idx in idxs[::-1]: @@ -1441,8 +1621,10 @@ def drop(self, names, axis): self.__x = np.delete(self.__x, idxs, 1) keep_names = [name for name in self.col_names if name not in names] if len(keep_names) != self.__x.shape[1]: - raise Exception("shape-name mismatch:"+\ - "{0}:{1}".format(len(keep_names),self.__x.shape)) + raise Exception( + "shape-name mismatch:" + + "{0}:{1}".format(len(keep_names), self.__x.shape) + ) self.col_names = keep_names # idxs = np.sort(idxs) # for idx in idxs[::-1]: @@ -1450,7 +1632,6 @@ def drop(self, names, axis): else: raise Exception("Matrix.drop(): axis argument must be 0 or 1") - def extract(self, row_names=None, col_names=None): """wrapper method that `Matrix.gets()` then `Matrix.drops()` elements. one of row_names or col_names must be not None. @@ -1467,8 +1648,7 @@ def extract(self, row_names=None, col_names=None): """ if row_names is None and col_names is None: - raise Exception("Matrix.extract() " + - "row_names and col_names both None") + raise Exception("Matrix.extract() " + "row_names and col_names both None") extract = self.get(row_names, col_names, drop=True) return extract @@ -1486,14 +1666,16 @@ def get_diagonal_vector(self, col_name="diag"): raise Exception("not diagonal") if self.isdiagonal: raise Exception("already diagonal") - if not isinstance(col_name,str): + if not isinstance(col_name, str): raise Exception("col_name must be type str") - return type(self)(x=np.atleast_2d(np.diag(self.x)).transpose(), - row_names=self.row_names, - col_names=[col_name],isdiagonal=False) - - - def to_coo(self,filename,droptol=None,chunk=None): + return type(self)( + x=np.atleast_2d(np.diag(self.x)).transpose(), + row_names=self.row_names, + col_names=[col_name], + isdiagonal=False, + ) + + def to_coo(self, filename, droptol=None, chunk=None): """write an extended PEST-format binary file. The data format is [int,int,float] for i,j,value. It is autodetected during the read with `Matrix.from_binary()`. @@ -1508,66 +1690,73 @@ def to_coo(self,filename,droptol=None,chunk=None): """ if self.isdiagonal: - #raise NotImplementedError() + # raise NotImplementedError() self.__x = self.as_2d self.isdiagonal = False if droptol is not None: self.x[np.abs(self.x) < droptol] = 0.0 - f = open(filename, 'wb') - #print("counting nnz") - nnz = np.count_nonzero(self.x) #number of non-zero entries + f = open(filename, "wb") + # print("counting nnz") + nnz = np.count_nonzero(self.x) # number of non-zero entries # write the header - header = np.array((self.shape[1], self.shape[0], nnz), - dtype=self.binary_header_dt) + header = np.array( + (self.shape[1], self.shape[0], nnz), dtype=self.binary_header_dt + ) header.tofile(f) # get the indices of non-zero entries - #print("getting nnz idxs") + # print("getting nnz idxs") row_idxs, col_idxs = np.nonzero(self.x) if chunk is None: flat = self.x[row_idxs, col_idxs].flatten() - data = np.core.records.fromarrays([row_idxs,col_idxs,flat],dtype=self.coo_rec_dt) + data = np.core.records.fromarrays( + [row_idxs, col_idxs, flat], dtype=self.coo_rec_dt + ) data.tofile(f) else: - start,end = 0,min(chunk,row_idxs.shape[0]) + start, end = 0, min(chunk, row_idxs.shape[0]) while True: - #print(row_idxs[start],row_idxs[end]) - #print("chunk",start,end) - flat = self.x[row_idxs[start:end],col_idxs[start:end]].flatten() - data = np.core.records.fromarrays([row_idxs[start:end],col_idxs[start:end], - flat], - dtype=self.coo_rec_dt) + # print(row_idxs[start],row_idxs[end]) + # print("chunk",start,end) + flat = self.x[row_idxs[start:end], col_idxs[start:end]].flatten() + data = np.core.records.fromarrays( + [row_idxs[start:end], col_idxs[start:end], flat], + dtype=self.coo_rec_dt, + ) data.tofile(f) if end == row_idxs.shape[0]: break start = end - end = min(row_idxs.shape[0],start + chunk) - + end = min(row_idxs.shape[0], start + chunk) for name in self.col_names: if len(name) > self.new_par_length: - warnings.warn("par name '{0}' greater than {1} chars"\ - .format(name,self.new_par_length)) - name = name[:self.new_par_length - 1] + warnings.warn( + "par name '{0}' greater than {1} chars".format( + name, self.new_par_length + ) + ) + name = name[: self.new_par_length - 1] elif len(name) < self.new_par_length: for _ in range(len(name), self.new_par_length): - name = name + ' ' + name = name + " " f.write(name.encode()) for name in self.row_names: if len(name) > self.new_obs_length: - warnings.warn("obs name '{0}' greater than {1} chars"\ - .format(name, self.new_obs_length)) - name = name[:self.new_obs_length - 1] + warnings.warn( + "obs name '{0}' greater than {1} chars".format( + name, self.new_obs_length + ) + ) + name = name[: self.new_obs_length - 1] elif len(name) < self.new_obs_length: for _ in range(len(name), self.new_obs_length): - name = name + ' ' + name = name + " " f.write(name.encode()) f.close() - - - def to_binary(self, filename,droptol=None, chunk=None): + def to_binary(self, filename, droptol=None, chunk=None): """write a PEST-compatible binary file. The format is the same as the format used to storage a PEST Jacobian matrix @@ -1580,31 +1769,31 @@ def to_binary(self, filename,droptol=None, chunk=None): `Matrix` at once. This is faster but requires more memory. """ - #print(self.x) - #print(type(self.x)) + # print(self.x) + # print(type(self.x)) if np.any(np.isnan(self.x)): raise Exception("Matrix.to_binary(): nans found") if self.isdiagonal: - #raise NotImplementedError() + # raise NotImplementedError() self.__x = self.as_2d self.isdiagonal = False if droptol is not None: self.x[np.abs(self.x) < droptol] = 0.0 - f = open(filename, 'wb') - nnz = np.count_nonzero(self.x) #number of non-zero entries + f = open(filename, "wb") + nnz = np.count_nonzero(self.x) # number of non-zero entries # write the header - header = np.array((-self.shape[1], -self.shape[0], nnz), - dtype=self.binary_header_dt) + header = np.array( + (-self.shape[1], -self.shape[0], nnz), dtype=self.binary_header_dt + ) header.tofile(f) # get the indices of non-zero entries row_idxs, col_idxs = np.nonzero(self.x) icount = row_idxs + 1 + col_idxs * self.shape[0] # flatten the array - #flat = self.x[row_idxs, col_idxs].flatten() + # flat = self.x[row_idxs, col_idxs].flatten() # zip up the index position and value pairs - #data = np.array(list(zip(icount, flat)), dtype=self.binary_rec_dt) - + # data = np.array(list(zip(icount, flat)), dtype=self.binary_rec_dt) if chunk is None: flat = self.x[row_idxs, col_idxs].flatten() @@ -1612,42 +1801,48 @@ def to_binary(self, filename,droptol=None, chunk=None): # write data.tofile(f) else: - start,end = 0,min(chunk,row_idxs.shape[0]) + start, end = 0, min(chunk, row_idxs.shape[0]) while True: - #print(row_idxs[start],row_idxs[end]) + # print(row_idxs[start],row_idxs[end]) flat = self.x[row_idxs[start:end], col_idxs[start:end]].flatten() - data = np.core.records.fromarrays([icount[start:end], - flat], - dtype=self.binary_rec_dt) + data = np.core.records.fromarrays( + [icount[start:end], flat], dtype=self.binary_rec_dt + ) data.tofile(f) if end == row_idxs.shape[0]: break start = end - end = min(row_idxs.shape[0],start + chunk) - + end = min(row_idxs.shape[0], start + chunk) for name in self.col_names: if len(name) > self.par_length: - warnings.warn("par name '{0}' greater than {1} chars".format(name, self.par_length)) - name = name[:self.par_length - 1] + warnings.warn( + "par name '{0}' greater than {1} chars".format( + name, self.par_length + ) + ) + name = name[: self.par_length - 1] elif len(name) < self.par_length: for i in range(len(name), self.par_length): - name = name + ' ' + name = name + " " f.write(name.encode()) for name in self.row_names: if len(name) > self.obs_length: - warnings.warn("obs name '{0}' greater than {1} chars".format(name, self.obs_length)) - name = name[:self.obs_length - 1] + warnings.warn( + "obs name '{0}' greater than {1} chars".format( + name, self.obs_length + ) + ) + name = name[: self.obs_length - 1] elif len(name) < self.obs_length: for i in range(len(name), self.obs_length): - name = name + ' ' + name = name + " " f.write(name.encode()) f.close() - @classmethod - def from_binary(cls,filename): + def from_binary(cls, filename): """class method load from PEST-compatible binary file into a Matrix instance @@ -1663,9 +1858,9 @@ def from_binary(cls,filename): cov = pyemi.Cov.from_binary("large_cov.jcb") """ - x,row_names,col_names = Matrix.read_binary(filename) + x, row_names, col_names = Matrix.read_binary(filename) if np.any(np.isnan(x)): - warnings.warn("Matrix.from_binary(): nans in matrix",PyemuWarning) + warnings.warn("Matrix.from_binary(): nans in matrix", PyemuWarning) return cls(x=x, row_names=row_names, col_names=col_names) @staticmethod @@ -1683,14 +1878,16 @@ def read_binary(filename): - **[`str`]**: list of col_names """ - f = open(filename, 'rb') + f = open(filename, "rb") # the header datatype itemp1, itemp2, icount = np.fromfile(f, Matrix.binary_header_dt, 1)[0] if itemp1 > 0 and itemp2 < 0 and icount < 0: - print(" WARNING: it appears this file was \n" +\ - " written with 'sequential` " +\ - " binary fortran specification\n...calling " +\ - " Matrix.from_fortranfile()") + print( + " WARNING: it appears this file was \n" + + " written with 'sequential` " + + " binary fortran specification\n...calling " + + " Matrix.from_fortranfile()" + ) f.close() return Matrix.from_fortranfile(filename) ncol, nrow = abs(itemp1), abs(itemp2) @@ -1698,28 +1895,38 @@ def read_binary(filename): # raise TypeError('Matrix.from_binary(): Jco produced by ' + # 'deprecated version of PEST,' + # 'Use JcoTRANS to convert to new format') - #print("new binary format detected...") + # print("new binary format detected...") data = np.fromfile(f, Matrix.coo_rec_dt, icount) - if data['i'].min() < 0: + if data["i"].min() < 0: raise Exception("Matrix.from_binary(): 'i' index values less than 0") - if data['j'].min() < 0: + if data["j"].min() < 0: raise Exception("Matrix.from_binary(): 'j' index values less than 0") x = np.zeros((nrow, ncol)) - x[data['i'], data['j']] = data["dtemp"] + x[data["i"], data["j"]] = data["dtemp"] data = x # read obs and parameter names col_names = [] row_names = [] for j in range(ncol): - name = struct.unpack(str(Matrix.new_par_length) + "s", - f.read(Matrix.new_par_length))[0] \ - .strip().lower().decode() + name = ( + struct.unpack( + str(Matrix.new_par_length) + "s", f.read(Matrix.new_par_length) + )[0] + .strip() + .lower() + .decode() + ) col_names.append(name) for i in range(nrow): - name = struct.unpack(str(Matrix.new_obs_length) + "s", - f.read(Matrix.new_obs_length))[0] \ - .strip().lower().decode() + name = ( + struct.unpack( + str(Matrix.new_obs_length) + "s", f.read(Matrix.new_obs_length) + )[0] + .strip() + .lower() + .decode() + ) row_names.append(name) f.close() else: @@ -1727,8 +1934,8 @@ def read_binary(filename): # read all data records # using this a memory hog, but really fast data = np.fromfile(f, Matrix.binary_rec_dt, icount) - icols = ((data['j'] - 1) // nrow) + 1 - irows = data['j'] - ((icols - 1) * nrow) + icols = ((data["j"] - 1) // nrow) + 1 + irows = data["j"] - ((icols - 1) * nrow) x = np.zeros((nrow, ncol)) x[irows - 1, icols - 1] = data["dtemp"] data = x @@ -1736,25 +1943,44 @@ def read_binary(filename): col_names = [] row_names = [] for j in range(ncol): - name = struct.unpack(str(Matrix.par_length) + "s", - f.read(Matrix.par_length))[0]\ - .strip().lower().decode() + name = ( + struct.unpack( + str(Matrix.par_length) + "s", f.read(Matrix.par_length) + )[0] + .strip() + .lower() + .decode() + ) col_names.append(name) for i in range(nrow): - name = struct.unpack(str(Matrix.obs_length) + "s", - f.read(Matrix.obs_length))[0]\ - .strip().lower().decode() + name = ( + struct.unpack( + str(Matrix.obs_length) + "s", f.read(Matrix.obs_length) + )[0] + .strip() + .lower() + .decode() + ) row_names.append(name) f.close() if len(row_names) != data.shape[0]: - raise Exception("Matrix.read_binary() len(row_names) (" + str(len(row_names)) +\ - ") != x.shape[0] (" + str(data.shape[0]) + ")") + raise Exception( + "Matrix.read_binary() len(row_names) (" + + str(len(row_names)) + + ") != x.shape[0] (" + + str(data.shape[0]) + + ")" + ) if len(col_names) != data.shape[1]: - raise Exception("Matrix.read_binary() len(col_names) (" + str(len(col_names)) +\ - ") != self.shape[1] (" + str(data.shape[1]) + ")") - return data,row_names,col_names - + raise Exception( + "Matrix.read_binary() len(col_names) (" + + str(len(col_names)) + + ") != self.shape[1] (" + + str(data.shape[1]) + + ")" + ) + return data, row_names, col_names @staticmethod def from_fortranfile(filename): @@ -1777,21 +2003,23 @@ def from_fortranfile(filename): from scipy.io import FortranFile except Exception as e: raise Exception("Matrix.from_fortranfile requires scipy") - f = FortranFile(filename,mode='r') + f = FortranFile(filename, mode="r") itemp1, itemp2 = f.read_ints() icount = int(f.read_ints()) if itemp1 >= 0: - raise TypeError('Matrix.from_binary(): Jco produced by ' + - 'deprecated version of PEST,' + - 'Use JcoTRANS to convert to new format') + raise TypeError( + "Matrix.from_binary(): Jco produced by " + + "deprecated version of PEST," + + "Use JcoTRANS to convert to new format" + ) ncol, nrow = abs(itemp1), abs(itemp2) data = [] for _ in range(icount): d = f.read_record(Matrix.binary_rec_dt)[0] data.append(d) - data = np.array(data,dtype=Matrix.binary_rec_dt) - icols = ((data['j'] - 1) // nrow) + 1 - irows = data['j'] - ((icols - 1) * nrow) + data = np.array(data, dtype=Matrix.binary_rec_dt) + icols = ((data["j"] - 1) // nrow) + 1 + irows = data["j"] - ((icols - 1) * nrow) x = np.zeros((nrow, ncol)) x[irows - 1, icols - 1] = data["dtemp"] row_names = [] @@ -1799,19 +2027,27 @@ def from_fortranfile(filename): for j in range(ncol): name = f.read_record("|S12")[0].strip().decode() col_names.append(name) - #obs_rec = np.dtype((np.str_, self.obs_length)) + # obs_rec = np.dtype((np.str_, self.obs_length)) for i in range(nrow): name = f.read_record("|S20")[0].strip().decode() row_names.append(name) if len(row_names) != x.shape[0]: - raise Exception("Matrix.from_fortranfile() len(row_names) (" + \ - str(len(row_names)) +\ - ") != self.shape[0] (" + str(x.shape[0]) + ")") + raise Exception( + "Matrix.from_fortranfile() len(row_names) (" + + str(len(row_names)) + + ") != self.shape[0] (" + + str(x.shape[0]) + + ")" + ) if len(col_names) != x.shape[1]: - raise Exception("Matrix.from_fortranfile() len(col_names) (" + \ - str(len(col_names)) +\ - ") != self.shape[1] (" + str(x.shape[1]) + ")") - #return cls(x=x,row_names=row_names,col_names=col_names) + raise Exception( + "Matrix.from_fortranfile() len(col_names) (" + + str(len(col_names)) + + ") != self.shape[1] (" + + str(x.shape[1]) + + ")" + ) + # return cls(x=x,row_names=row_names,col_names=col_names) return x, row_names, col_names def to_ascii(self, filename, icode=2): @@ -1824,34 +2060,32 @@ def to_ascii(self, filename, icode=2): """ nrow, ncol = self.shape - f_out = open(filename, 'w') - f_out.write(' {0:7.0f} {1:7.0f} {2:7.0f}\n'. - format(nrow, ncol, icode)) + f_out = open(filename, "w") + f_out.write(" {0:7.0f} {1:7.0f} {2:7.0f}\n".format(nrow, ncol, icode)) f_out.close() - f_out = open(filename,'ab') + f_out = open(filename, "ab") if self.isdiagonal: x = np.diag(self.__x[:, 0]) else: x = self.__x - np.savetxt(f_out, x, fmt='%15.7E', delimiter='') + np.savetxt(f_out, x, fmt="%15.7E", delimiter="") f_out.close() - f_out = open(filename,'a') + f_out = open(filename, "a") if icode == 1: - f_out.write('* row and column names\n') + f_out.write("* row and column names\n") for r in self.row_names: - f_out.write(r + '\n') + f_out.write(r + "\n") else: - f_out.write('* row names\n') + f_out.write("* row names\n") for r in self.row_names: - f_out.write(r + '\n') - f_out.write('* column names\n') + f_out.write(r + "\n") + f_out.write("* column names\n") for c in self.col_names: - f_out.write(c + '\n') + f_out.write(c + "\n") f_out.close() - @classmethod - def from_ascii(cls,filename): + def from_ascii(cls, filename): """load a PEST-compatible ASCII matrix/vector file into a `Matrix` instance @@ -1867,9 +2101,8 @@ def from_ascii(cls,filename): cov = pyemi.Cov.from_ascii("my.cov") """ - x,row_names,col_names,isdiag = Matrix.read_ascii(filename) - return cls(x=x,row_names=row_names,col_names=col_names,isdiagonal=isdiag) - + x, row_names, col_names, isdiag = Matrix.read_ascii(filename) + return cls(x=x, row_names=row_names, col_names=col_names, isdiagonal=isdiag) @staticmethod def read_ascii(filename): @@ -1888,10 +2121,10 @@ def read_ascii(filename): """ - f = open(filename, 'r') + f = open(filename, "r") raw = f.readline().strip().split() nrow, ncol = int(raw[0]), int(raw[1]) - #x = np.fromfile(f, dtype=self.double, count=nrow * ncol, sep=' ') + # x = np.fromfile(f, dtype=self.double, count=nrow * ncol, sep=' ') # this painfully slow and ugly read is needed to catch the # fortran floating points that have 3-digit exponents, # which leave out the base (e.g. 'e') : "-1.23455+300" @@ -1899,7 +2132,7 @@ def read_ascii(filename): x = [] while True: line = f.readline() - if line == '': + if line == "": raise Exception("Matrix.from_ascii() error: EOF") raw = line.strip().split() for r in raw: @@ -1907,27 +2140,34 @@ def read_ascii(filename): x.append(float(r)) except Exception as e: # overflow - if '+' in r: - x.append(1.0e+30) + if "+" in r: + x.append(1.0e30) # underflow - elif '-' in r: + elif "-" in r: x.append(0.0) else: - raise Exception("Matrix.from_ascii() error: " + - " can't cast " + r + " to float") + raise Exception( + "Matrix.from_ascii() error: " + + " can't cast " + + r + + " to float" + ) count += 1 if count == (nrow * ncol): break if count == (nrow * ncol): - break + break - x = np.array(x,dtype=Matrix.double) + x = np.array(x, dtype=Matrix.double) x.resize(nrow, ncol) line = f.readline().strip().lower() - if not line.startswith('*'): - raise Exception('Matrix.from_ascii(): error loading ascii file," +\ - "line should start with * not ' + line) - if 'row' in line and 'column' in line: + if not line.startswith("*"): + raise Exception( + 'Matrix.from_ascii(): error loading ascii file," +\ + "line should start with * not ' + + line + ) + if "row" in line and "column" in line: if nrow != ncol: raise Exception("nrow != ncol") names = [] @@ -1945,8 +2185,11 @@ def read_ascii(filename): row_names = names line = f.readline().strip().lower() if "column" not in line: - raise Exception("Matrix.from_ascii(): line should be * column names " +\ - "instead of: " + line) + raise Exception( + "Matrix.from_ascii(): line should be * column names " + + "instead of: " + + line + ) names = [] for _ in range(ncol): line = f.readline().strip().lower() @@ -1954,7 +2197,7 @@ def read_ascii(filename): col_names = names f.close() # test for diagonal - isdiagonal=False + isdiagonal = False if nrow == ncol: diag = np.diag(np.diag(x)) diag_tol = 1.0e-6 @@ -1962,7 +2205,7 @@ def read_ascii(filename): if diag_delta < diag_tol: isdiagonal = True x = np.atleast_2d(np.diag(x)).transpose() - return x,row_names,col_names,isdiagonal + return x, row_names, col_names, isdiagonal def df(self): """wrapper of Matrix.to_dataframe() @@ -1986,15 +2229,16 @@ def from_dataframe(cls, df): mat = pyemu.Matrix.from_dataframe(df) """ - if not isinstance(df,pd.DataFrame): + if not isinstance(df, pd.DataFrame): raise Exception("df is not a DataFrame") row_names = copy.deepcopy(list(df.index)) col_names = copy.deepcopy(list(df.columns)) - return cls(x=df.values,row_names=row_names,col_names=col_names) - + return cls(x=df.values, row_names=row_names, col_names=col_names) @classmethod - def from_names(cls,row_names,col_names,isdiagonal=False,autoalign=True, random=False): + def from_names( + cls, row_names, col_names, isdiagonal=False, autoalign=True, random=False + ): """ class method to create a new Matrix instance from row names and column names, filled with trash @@ -2012,12 +2256,21 @@ def from_names(cls,row_names,col_names,isdiagonal=False,autoalign=True, random=F """ if random: - return cls(x=np.random.random((len(row_names), len(col_names))), row_names=row_names, - col_names=col_names, isdiagonal=isdiagonal, autoalign=autoalign) + return cls( + x=np.random.random((len(row_names), len(col_names))), + row_names=row_names, + col_names=col_names, + isdiagonal=isdiagonal, + autoalign=autoalign, + ) else: - return cls(x=np.empty((len(row_names),len(col_names))),row_names=row_names, - col_names=col_names,isdiagonal=isdiagonal,autoalign=autoalign) - + return cls( + x=np.empty((len(row_names), len(col_names))), + row_names=row_names, + col_names=col_names, + isdiagonal=isdiagonal, + autoalign=autoalign, + ) def to_dataframe(self): """return a pandas.DataFrame representation of `Matrix` @@ -2030,10 +2283,9 @@ def to_dataframe(self): x = np.diag(self.__x[:, 0]) else: x = self.__x - return pd.DataFrame(data=x,index=self.row_names,columns=self.col_names) - + return pd.DataFrame(data=x, index=self.row_names, columns=self.col_names) - def extend(self,other): + def extend(self, other): """ extend `Matrix` with the elements of other. Args: @@ -2057,21 +2309,29 @@ def extend(self,other): new_col_names = copy.copy(self.col_names) new_col_names.extend(other.col_names) - new_x = np.zeros((len(new_row_names),len(new_col_names))) - new_x[0:self.shape[0],0:self.shape[1]] = self.as_2d - new_x[self.shape[0]:self.shape[0]+other.shape[0], - self.shape[1]:self.shape[1]+other.shape[1]] = other.as_2d + new_x = np.zeros((len(new_row_names), len(new_col_names))) + new_x[0 : self.shape[0], 0 : self.shape[1]] = self.as_2d + new_x[ + self.shape[0] : self.shape[0] + other.shape[0], + self.shape[1] : self.shape[1] + other.shape[1], + ] = other.as_2d isdiagonal = True if not self.isdiagonal or not other.isdiagonal: isdiagonal = False - return type(self)(x=new_x,row_names=new_row_names, - col_names=new_col_names,isdiagonal=isdiagonal) + return type(self)( + x=new_x, + row_names=new_row_names, + col_names=new_col_names, + isdiagonal=isdiagonal, + ) + class Jco(Matrix): """a thin wrapper class to get more intuitive attribute names. Functions exactly like `Matrix` """ + def __init(self, **kwargs): """ Jco constuctor takes the same arguments as Matrix. @@ -2087,7 +2347,6 @@ def __init(self, **kwargs): super(Jco, self).__init__(kwargs) - @property def par_names(self): """ thin wrapper around `Matrix.col_names` @@ -2098,7 +2357,6 @@ def par_names(self): """ return self.col_names - @property def obs_names(self): """ thin wrapper around `Matrix.row_names` @@ -2109,7 +2367,6 @@ def obs_names(self): """ return self.row_names - @property def npar(self): """ number of parameters in the Jco @@ -2120,7 +2377,6 @@ def npar(self): """ return self.shape[1] - @property def nobs(self): """ number of observations in the Jco @@ -2131,7 +2387,6 @@ def nobs(self): """ return self.shape[0] - @classmethod def from_pst(cls, pst, random=False): """construct a new empty Jco from a control file filled @@ -2149,11 +2404,12 @@ def from_pst(cls, pst, random=False): """ - if isinstance(pst,str): + if isinstance(pst, str): pst = Pst(pst) return Jco.from_names(pst.obs_names, pst.adj_par_names, random=random) + class Cov(Matrix): """Diagonal and/or dense Covariance matrices @@ -2176,12 +2432,20 @@ class Cov(Matrix): so support inheritance. However, users should only pass `names` """ - def __init__(self, x=None, names=[],row_names=[],col_names=[], - isdiagonal=False, autoalign=True): + + def __init__( + self, + x=None, + names=[], + row_names=[], + col_names=[], + isdiagonal=False, + autoalign=True, + ): self.__identity = None self.__zero = None - #if len(row_names) > 0 and len(col_names) > 0: + # if len(row_names) > 0 and len(col_names) > 0: # assert row_names == col_names self.__identity = None self.__zero = None @@ -2191,15 +2455,20 @@ def __init__(self, x=None, names=[],row_names=[],col_names=[], row_names = names if len(names) != 0 and len(col_names) == 0: col_names = names - super(Cov, self).__init__(x=x, isdiagonal=isdiagonal, - row_names=row_names, - col_names=col_names, - autoalign=autoalign) - super(Cov, self).__init__(x=x, isdiagonal=isdiagonal, - row_names=row_names, - col_names=col_names, - autoalign=autoalign) - + super(Cov, self).__init__( + x=x, + isdiagonal=isdiagonal, + row_names=row_names, + col_names=col_names, + autoalign=autoalign, + ) + super(Cov, self).__init__( + x=x, + isdiagonal=isdiagonal, + row_names=row_names, + col_names=col_names, + autoalign=autoalign, + ) @property def identity(self): @@ -2210,12 +2479,13 @@ def identity(self): """ if self.__identity is None: - self.__identity = Cov(x=np.atleast_2d(np.ones(self.shape[0])) - .transpose(), names=self.row_names, - isdiagonal=True) + self.__identity = Cov( + x=np.atleast_2d(np.ones(self.shape[0])).transpose(), + names=self.row_names, + isdiagonal=True, + ) return self.__identity - @property def zero(self): """ get an instance of `Cov` with all zeros @@ -2225,13 +2495,14 @@ def zero(self): """ if self.__zero is None: - self.__zero = Cov(x=np.atleast_2d(np.zeros(self.shape[0])) - .transpose(), names=self.row_names, - isdiagonal=True) + self.__zero = Cov( + x=np.atleast_2d(np.zeros(self.shape[0])).transpose(), + names=self.row_names, + isdiagonal=True, + ) return self.__zero - - def condition_on(self,conditioning_elements): + def condition_on(self, conditioning_elements): """get a new Covariance object that is conditional on knowing some elements. uses Schur's complement for conditional Covariance propagation @@ -2242,7 +2513,7 @@ def condition_on(self,conditioning_elements): Returns: `Cov`: new conditional `Cov` that assumes `conditioning_elements` have become known """ - if not isinstance(conditioning_elements,list): + if not isinstance(conditioning_elements, list): conditioning_elements = [conditioning_elements] for iname, name in enumerate(conditioning_elements): conditioning_elements[iname] = name.lower() @@ -2252,19 +2523,17 @@ def condition_on(self,conditioning_elements): for name in self.col_names: if name not in conditioning_elements: keep_names.append(name) - #C11 + # C11 new_Cov = self.get(keep_names) if self.isdiagonal: return new_Cov - #C22^1 + # C22^1 cond_Cov = self.get(conditioning_elements).inv - #C12 + # C12 upper_off_diag = self.get(keep_names, conditioning_elements) - #print(new_Cov.shape,upper_off_diag.shape,cond_Cov.shape) + # print(new_Cov.shape,upper_off_diag.shape,cond_Cov.shape) return new_Cov - (upper_off_diag * cond_Cov * upper_off_diag.T) - - @property def names(self): """wrapper for getting row_names. row_names == col_names for Cov @@ -2275,8 +2544,7 @@ def names(self): """ return self.row_names - - def replace(self,other): + def replace(self, other): """replace elements in the covariance matrix with elements from other. if other is not diagonal, then this `Cov` becomes non diagonal @@ -2287,16 +2555,19 @@ def replace(self,other): operates in place """ - if not isinstance(other,Cov): - raise Exception("Cov.replace() other must be Cov, not {0}".\ - format(type(other))) + if not isinstance(other, Cov): + raise Exception( + "Cov.replace() other must be Cov, not {0}".format(type(other)) + ) # make sure the names of other are in self missing = [n for n in other.names if n not in self.names] if len(missing) > 0: - raise Exception("Cov.replace(): the following other names are not" +\ - " in self names: {0}".format(','.join(missing))) - self_idxs = self.indices(other.names,0) - other_idxs = other.indices(other.names,0) + raise Exception( + "Cov.replace(): the following other names are not" + + " in self names: {0}".format(",".join(missing)) + ) + self_idxs = self.indices(other.names, 0) + other_idxs = other.indices(other.names, 0) if self.isdiagonal and other.isdiagonal: self._Matrix__x[self_idxs] = other.x[other_idxs] @@ -2305,16 +2576,18 @@ def replace(self,other): self._Matrix__x = self.as_2d self.isdiagonal = False - #print("allocating other_x") + # print("allocating other_x") other_x = other.as_2d - #print("replacing") - for i,ii in zip(self_idxs,other_idxs): - self._Matrix__x[i,self_idxs] = other_x[ii,other_idxs].copy() - #print("resetting") - #self.reset_x(self_x) - #self.isdiagonal = False - - def to_uncfile(self, unc_file, covmat_file="cov.mat", var_mult=1.0, include_path=True): + # print("replacing") + for i, ii in zip(self_idxs, other_idxs): + self._Matrix__x[i, self_idxs] = other_x[ii, other_idxs].copy() + # print("resetting") + # self.reset_x(self_x) + # self.isdiagonal = False + + def to_uncfile( + self, unc_file, covmat_file="cov.mat", var_mult=1.0, include_path=True + ): """write a PEST-compatible uncertainty file Args: @@ -2331,12 +2604,13 @@ def to_uncfile(self, unc_file, covmat_file="cov.mat", var_mult=1.0, include_path cov.to_uncfile("my.unc") """ - assert len(self.row_names) == self.shape[0], \ - "Cov.to_uncfile(): len(row_names) != x.shape[0] " + assert ( + len(self.row_names) == self.shape[0] + ), "Cov.to_uncfile(): len(row_names) != x.shape[0] " if len(self.row_names) != self.shape[0]: raise Exception("Cov.to_uncfile(): len(row_names) != x.shape[0]") if covmat_file: - f = open(unc_file, 'w') + f = open(unc_file, "w") f.write("START COVARIANCE_MATRIX\n") if include_path: f.write(" file " + covmat_file + "\n") @@ -2348,16 +2622,19 @@ def to_uncfile(self, unc_file, covmat_file="cov.mat", var_mult=1.0, include_path self.to_ascii(covmat_file, icode=1) else: if self.isdiagonal: - f = open(unc_file, 'w') + f = open(unc_file, "w") f.write("START STANDARD_DEVIATION\n") for iname, name in enumerate(self.row_names): - f.write(" {0:20s} {1:15.6E}\n". - format(name, np.sqrt(self.x[iname, 0]))) + f.write( + " {0:20s} {1:15.6E}\n".format(name, np.sqrt(self.x[iname, 0])) + ) f.write("END STANDARD_DEVIATION\n") f.close() else: - raise Exception("Cov.to_uncfile(): can't write non-diagonal " + - "object as standard deviation block") + raise Exception( + "Cov.to_uncfile(): can't write non-diagonal " + + "object as standard deviation block" + ) @classmethod def from_obsweights(cls, pst_file): @@ -2406,16 +2683,18 @@ def from_observation_data(cls, pst): x = np.zeros((nobs, 1)) onames = [] ocount = 0 - for weight,obsnme in zip(pst.observation_data.weight,pst.observation_data.obsnme): + for weight, obsnme in zip( + pst.observation_data.weight, pst.observation_data.obsnme + ): w = float(weight) w = max(w, 1.0e-30) x[ocount] = (1.0 / w) ** 2 ocount += 1 onames.append(obsnme.lower()) - return cls(x=x,names=onames,isdiagonal=True) + return cls(x=x, names=onames, isdiagonal=True) @classmethod - def from_parbounds(cls, pst_file, sigma_range = 4.0,scale_offset=True): + def from_parbounds(cls, pst_file, sigma_range=4.0, scale_offset=True): """Instantiates a `Cov` from a pest control file parameter data section using parameter bounds as a proxy for uncertainty. @@ -2443,7 +2722,7 @@ def from_parbounds(cls, pst_file, sigma_range = 4.0,scale_offset=True): return Cov.from_parameter_data(new_pst, sigma_range, scale_offset) @classmethod - def from_parameter_data(cls, pst, sigma_range = 4.0, scale_offset=True): + def from_parameter_data(cls, pst, sigma_range=4.0, scale_offset=True): """Instantiates a `Cov` from a pest control file parameter data section using parameter bounds as a proxy for uncertainty. @@ -2485,12 +2764,15 @@ def from_parameter_data(cls, pst, sigma_range = 4.0, scale_offset=True): else: var = ((ub - lb) / sigma_range) ** 2 if np.isnan(var) or not np.isfinite(var): - raise Exception("Cov.from_parameter_data() error: " +\ - "variance for parameter {0} is nan".\ - format(row["parnme"])) - if (var == 0.0): - s = "Cov.from_parameter_data() error: " +\ - "variance for parameter {0} is 0.0.".format(row["parnme"]) + raise Exception( + "Cov.from_parameter_data() error: " + + "variance for parameter {0} is nan".format(row["parnme"]) + ) + if var == 0.0: + s = ( + "Cov.from_parameter_data() error: " + + "variance for parameter {0} is 0.0.".format(row["parnme"]) + ) s += " This might be from enforcement of scale/offset and log transform." s += " Try changing 'scale_offset' arg" raise Exception(s) @@ -2498,7 +2780,7 @@ def from_parameter_data(cls, pst, sigma_range = 4.0, scale_offset=True): names.append(row["parnme"].lower()) idx += 1 - return cls(x=x,names=names,isdiagonal=True) + return cls(x=x, names=names, isdiagonal=True) @classmethod def from_uncfile(cls, filename): @@ -2520,7 +2802,7 @@ def from_uncfile(cls, filename): x = np.zeros((nentries, nentries)) row_names = [] col_names = [] - f = open(filename, 'r') + f = open(filename, "r") isdiagonal = True idx = 0 while True: @@ -2528,78 +2810,87 @@ def from_uncfile(cls, filename): if len(line) == 0: break line = line.strip() - if 'start' in line: - if 'standard_deviation' in line: + if "start" in line: + if "standard_deviation" in line: std_mult = 1.0 while True: line2 = f.readline().strip().lower() if line2.strip().lower().startswith("end"): break - raw = line2.strip().split() - name,val = raw[0], float(raw[1]) + name, val = raw[0], float(raw[1]) if name == "std_multiplier": std_mult = val else: - x[idx, idx] = (val*std_mult)**2 + x[idx, idx] = (val * std_mult) ** 2 if name in row_names: - raise Exception("Cov.from_uncfile():" + - "duplicate name: " + str(name)) + raise Exception( + "Cov.from_uncfile():" + + "duplicate name: " + + str(name) + ) row_names.append(name) col_names.append(name) idx += 1 - elif 'covariance_matrix' in line: + elif "covariance_matrix" in line: isdiagonal = False var = 1.0 while True: line2 = f.readline().strip().lower() if line2.strip().lower().startswith("end"): break - if line2.startswith('file'): - filename = line2.split()[1].replace("'",'').replace('"','') + if line2.startswith("file"): + filename = ( + line2.split()[1].replace("'", "").replace('"', "") + ) cov = Matrix.from_ascii(filename) - elif line2.startswith('variance_multiplier'): + elif line2.startswith("variance_multiplier"): var = float(line2.split()[1]) else: - raise Exception("Cov.from_uncfile(): " + - "unrecognized keyword in" + - "std block: " + line2) + raise Exception( + "Cov.from_uncfile(): " + + "unrecognized keyword in" + + "std block: " + + line2 + ) if var != 1.0: cov *= var for name in cov.row_names: if name in row_names: - raise Exception("Cov.from_uncfile():" + - " duplicate name: " + str(name)) + raise Exception( + "Cov.from_uncfile():" + " duplicate name: " + str(name) + ) row_names.extend(cov.row_names) col_names.extend(cov.col_names) for i in range(cov.shape[0]): - x[idx + i,idx:idx + cov.shape[0]] = cov.x[i, :].copy() + x[idx + i, idx : idx + cov.shape[0]] = cov.x[i, :].copy() idx += cov.shape[0] else: - raise Exception('Cov.from_uncfile(): ' + - 'unrecognized block:' + str(line)) + raise Exception( + "Cov.from_uncfile(): " + "unrecognized block:" + str(line) + ) f.close() if isdiagonal: x = np.atleast_2d(np.diag(x)).transpose() - return cls(x=x,names=row_names,isdiagonal=isdiagonal) + return cls(x=x, names=row_names, isdiagonal=isdiagonal) @staticmethod def _get_uncfile_dimensions(filename): """quickly read an uncertainty file to find the dimensions """ - f = open(filename, 'r') + f = open(filename, "r") nentries = 0 while True: line = f.readline().lower() if len(line) == 0: break line = line.strip() - if 'start' in line: - if 'standard_deviation' in line: + if "start" in line: + if "standard_deviation" in line: while True: line2 = f.readline().strip().lower() if "std_multiplier" in line2: @@ -2608,29 +2899,36 @@ def _get_uncfile_dimensions(filename): break nentries += 1 - elif 'covariance_matrix' in line: + elif "covariance_matrix" in line: while True: line2 = f.readline().strip().lower() if line2.strip().lower().startswith("end"): break - if line2.startswith('file'): - filename = line2.split()[1].replace("'", '').replace('"', '') + if line2.startswith("file"): + filename = ( + line2.split()[1].replace("'", "").replace('"', "") + ) cov = Matrix.from_ascii(filename) nentries += len(cov.row_names) - elif line2.startswith('variance_multiplier'): + elif line2.startswith("variance_multiplier"): pass else: - raise Exception('Cov.get_uncfile_dimensions(): ' + - 'unrecognized keyword in Covariance block: ' + - line2) + raise Exception( + "Cov.get_uncfile_dimensions(): " + + "unrecognized keyword in Covariance block: " + + line2 + ) else: - raise Exception('Cov.get_uncfile_dimensions():' + - 'unrecognized block:' + str(line)) + raise Exception( + "Cov.get_uncfile_dimensions():" + + "unrecognized block:" + + str(line) + ) f.close() return nentries @classmethod - def identity_like(cls,other): + def identity_like(cls, other): """ Get an identity matrix Cov instance like other `Cov` Args: @@ -2643,7 +2941,7 @@ def identity_like(cls,other): if other.shape[0] != other.shape[1]: raise Exception("not square") x = np.identity(other.shape[0]) - return cls(x=x,names=other.row_names,isdiagonal=False) + return cls(x=x, names=other.row_names, isdiagonal=False) def to_pearson(self): """ Convert Cov instance to Pearson correlation coefficient @@ -2654,30 +2952,26 @@ def to_pearson(self): on purpose so that it is clear the returned instance is not a Cov """ - std_dict = self.get_diagonal_vector().to_dataframe()["diag"].\ - apply(np.sqrt).to_dict() + std_dict = ( + self.get_diagonal_vector().to_dataframe()["diag"].apply(np.sqrt).to_dict() + ) pearson = self.identity.as_2d if self.isdiagonal: - return Matrix(x=pearson,row_names=self.row_names, - col_names=self.col_names) + return Matrix(x=pearson, row_names=self.row_names, col_names=self.col_names) df = self.to_dataframe() # fill the lower triangle - for i,iname in enumerate(self.row_names): - for j,jname in enumerate(self.row_names[i+1:]): + for i, iname in enumerate(self.row_names): + for j, jname in enumerate(self.row_names[i + 1 :]): # cv = df.loc[iname,jname] # std1,std2 = std_dict[iname],std_dict[jname] # cc = cv / (std1*std2) # v1 = np.sqrt(df.loc[iname,iname]) # v2 = np.sqrt(df.loc[jname,jname]) - pearson[i,j+i+1] = df.loc[iname,jname] / (std_dict[iname] * std_dict[jname]) + pearson[i, j + i + 1] = df.loc[iname, jname] / ( + std_dict[iname] * std_dict[jname] + ) # replicate across diagonal - for i,iname in enumerate(self.row_names[:-1]): - pearson[i+1:,i] = pearson[i,i+1:] - return Matrix(x=pearson,row_names=self.row_names, - col_names=self.col_names) - - - - - + for i, iname in enumerate(self.row_names[:-1]): + pearson[i + 1 :, i] = pearson[i, i + 1 :] + return Matrix(x=pearson, row_names=self.row_names, col_names=self.col_names) diff --git a/pyemu/mc.py b/pyemu/mc.py index 47f6680da..6176ea1d4 100644 --- a/pyemu/mc.py +++ b/pyemu/mc.py @@ -10,7 +10,9 @@ from pyemu.en import ObservationEnsemble, ParameterEnsemble from pyemu.mat import Cov from .pyemu_warnings import PyemuWarning -#from pyemu.utils.helpers import zero_order_tikhonov + +# from pyemu.utils.helpers import zero_order_tikhonov + class MonteCarlo(LinearAnalysis): """LinearAnalysis derived type for monte carlo analysis @@ -46,12 +48,15 @@ class MonteCarlo(LinearAnalysis): ``>>>mc = pyemu.MonteCarlo(pst="pest.pst")`` """ - def __init__(self,**kwargs): - warnings.warn("pyemu.MonteCarlo class is deprecated. "+\ - "Please use the ensemble classes directly",PyemuWarning) - super(MonteCarlo,self).__init__(**kwargs) - assert self.pst is not None, \ - "monte carlo requires a pest control file" + + def __init__(self, **kwargs): + warnings.warn( + "pyemu.MonteCarlo class is deprecated. " + + "Please use the ensemble classes directly", + PyemuWarning, + ) + super(MonteCarlo, self).__init__(**kwargs) + assert self.pst is not None, "monte carlo requires a pest control file" self.parensemble = ParameterEnsemble(pst=self.pst) self.obsensemble = ObservationEnsemble(pst=self.pst) @@ -66,7 +71,7 @@ def num_reals(self): """ return self.parensemble.shape[0] - def get_nsing(self,epsilon=1.0e-4): + def get_nsing(self, epsilon=1.0e-4): """ get the number of solution space dimensions given a ratio between the largest and smallest singular values @@ -87,13 +92,14 @@ def get_nsing(self,epsilon=1.0e-4): """ mx = self.xtqx.shape[0] nsing = mx - np.searchsorted( - np.sort((self.xtqx.s.x / self.xtqx.s.x.max())[:,0]),epsilon) + np.sort((self.xtqx.s.x / self.xtqx.s.x.max())[:, 0]), epsilon + ) if nsing == mx: self.logger.warn("optimal nsing=npar") nsing = None return nsing - def get_null_proj(self,nsing=None): + def get_null_proj(self, nsing=None): """ get a null-space projection matrix of XTQX Parameters @@ -114,17 +120,28 @@ def get_null_proj(self,nsing=None): if nsing is None: raise Exception("nsing is None") print("using {0} singular components".format(nsing)) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) + self.log( + "forming null space projection matrix with " + + "{0} of {1} singular components".format(nsing, self.jco.shape[1]) + ) - v2_proj = (self.xtqx.v[:,nsing:] * self.xtqx.v[:,nsing:].T) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) + v2_proj = self.xtqx.v[:, nsing:] * self.xtqx.v[:, nsing:].T + self.log( + "forming null space projection matrix with " + + "{0} of {1} singular components".format(nsing, self.jco.shape[1]) + ) return v2_proj - def draw(self, num_reals=1, par_file = None, obs=False, - enforce_bounds=None, cov=None, how="gaussian"): + def draw( + self, + num_reals=1, + par_file=None, + obs=False, + enforce_bounds=None, + cov=None, + how="gaussian", + ): """draw stochastic realizations of parameters and optionally observations, filling MonteCarlo.parensemble and optionally MonteCarlo.obsensemble. @@ -159,48 +176,57 @@ def draw(self, num_reals=1, par_file = None, obs=False, if par_file is not None: self.pst.parrep(par_file) how = how.lower().strip() - assert how in ["gaussian","uniform"] + assert how in ["gaussian", "uniform"] if cov is not None: - assert isinstance(cov,Cov) + assert isinstance(cov, Cov) if how == "uniform": - raise Exception("MonteCarlo.draw() error: 'how'='uniform'," +\ - " 'cov' arg cannot be passed") + raise Exception( + "MonteCarlo.draw() error: 'how'='uniform'," + + " 'cov' arg cannot be passed" + ) else: cov = self.parcov self.log("generating {0:d} parameter realizations".format(num_reals)) if how == "gaussian": - self.parensemble = ParameterEnsemble.from_gaussian_draw(pst=self.pst,cov=cov, - num_reals=num_reals, - use_homegrown=True, - enforce_bounds=False) + self.parensemble = ParameterEnsemble.from_gaussian_draw( + pst=self.pst, + cov=cov, + num_reals=num_reals, + use_homegrown=True, + enforce_bounds=False, + ) elif how == "uniform": - self.parensemble = ParameterEnsemble.from_uniform_draw(pst=self.pst,num_reals=num_reals) + self.parensemble = ParameterEnsemble.from_uniform_draw( + pst=self.pst, num_reals=num_reals + ) else: - raise Exception("MonteCarlo.draw(): unrecognized 'how' arg: {0}".format(how)) + raise Exception( + "MonteCarlo.draw(): unrecognized 'how' arg: {0}".format(how) + ) - #self.parensemble = ParameterEnsemble(pst=self.pst) - #self.obsensemble = ObservationEnsemble(pst=self.pst) - #self.parensemble.draw(cov,num_reals=num_reals, how=how, + # self.parensemble = ParameterEnsemble(pst=self.pst) + # self.obsensemble = ObservationEnsemble(pst=self.pst) + # self.parensemble.draw(cov,num_reals=num_reals, how=how, # enforce_bounds=enforce_bounds) - if enforce_bounds is not None: + if enforce_bounds is not None: self.parensemble.enforce(enforce_bounds) self.log("generating {0:d} parameter realizations".format(num_reals)) if obs: self.log("generating {0:d} observation realizations".format(num_reals)) - self.obsensemble = ObservationEnsemble.from_id_gaussian_draw(pst=self.pst,num_reals=num_reals) + self.obsensemble = ObservationEnsemble.from_id_gaussian_draw( + pst=self.pst, num_reals=num_reals + ) self.log("generating {0:d} observation realizations".format(num_reals)) - - - - def project_parensemble(self,par_file=None,nsing=None, - inplace=True,enforce_bounds='reset'): + def project_parensemble( + self, par_file=None, nsing=None, inplace=True, enforce_bounds="reset" + ): """ perform the null-space projection operations for null-space monte carlo Parameters @@ -237,20 +263,24 @@ def project_parensemble(self,par_file=None,nsing=None, ``>>>mc.project_parensemble(par_file="final.par",nsing=100)`` """ - assert self.jco is not None,"MonteCarlo.project_parensemble()" +\ - "requires a jacobian attribute" + assert self.jco is not None, ( + "MonteCarlo.project_parensemble()" + "requires a jacobian attribute" + ) if par_file is not None: - assert os.path.exists(par_file),"monte_carlo.draw() error: par_file not found:" +\ - par_file + assert os.path.exists(par_file), ( + "monte_carlo.draw() error: par_file not found:" + par_file + ) self.parensemble.pst.parrep(par_file) # project the ensemble self.log("projecting parameter ensemble") - en = self.parensemble.project(self.get_null_proj(nsing),inplace=inplace,log=self.log) + en = self.parensemble.project( + self.get_null_proj(nsing), inplace=inplace, log=self.log + ) self.log("projecting parameter ensemble") return en - def write_psts(self,prefix,existing_jco=None,noptmax=None): + def write_psts(self, prefix, existing_jco=None, noptmax=None): """ write parameter and optionally observation realizations to a series of pest control files @@ -282,7 +312,7 @@ def write_psts(self,prefix,existing_jco=None,noptmax=None): """ self.log("writing realized pest control files") # get a copy of the pest control file - pst = self.pst.get(par_names=self.pst.par_names,obs_names=self.pst.obs_names) + pst = self.pst.get(par_names=self.pst.par_names, obs_names=self.pst.obs_names) if noptmax is not None: pst.control_data.noptmax = noptmax @@ -303,19 +333,19 @@ def write_psts(self,prefix,existing_jco=None,noptmax=None): for i in range(self.num_reals): pst_name = prefix + "{0:d}.pst".format(i) self.log("writing realized pest control file " + pst_name) - pst.parameter_data.loc[par_en.columns,"parval1"] = par_en.iloc[i, :].T + pst.parameter_data.loc[par_en.columns, "parval1"] = par_en.iloc[i, :].T # reset the regularization - #if pst.control_data.pestmode == "regularization": - #pst.zero_order_tikhonov(parbounds=True) - #zero_order_tikhonov(pst,parbounds=True) + # if pst.control_data.pestmode == "regularization": + # pst.zero_order_tikhonov(parbounds=True) + # zero_order_tikhonov(pst,parbounds=True) # add the obs noise realization if needed if self.obsensemble.shape[0] == self.num_reals: - pst.observation_data.loc[self.obsensemble.columns,"obsval"] = \ - self.obsensemble.iloc[i, :].T + pst.observation_data.loc[ + self.obsensemble.columns, "obsval" + ] = self.obsensemble.iloc[i, :].T # write pst.write(pst_name) self.log("writing realized pest control file " + pst_name) self.log("writing realized pest control files") - diff --git a/pyemu/plot/__init__.py b/pyemu/plot/__init__.py index 7ed195b89..28e730d89 100644 --- a/pyemu/plot/__init__.py +++ b/pyemu/plot/__init__.py @@ -3,5 +3,3 @@ """ from . import plot_utils - - diff --git a/pyemu/plot/plot_utils.py b/pyemu/plot/plot_utils.py index 85fb38e0a..fe4ad483f 100644 --- a/pyemu/plot/plot_utils.py +++ b/pyemu/plot/plot_utils.py @@ -8,27 +8,38 @@ from pyemu.logger import Logger from pyemu.pst import pst_utils from ..pyemu_warnings import PyemuWarning -font = {'size' : 6} + +font = {"size": 6} try: import matplotlib - matplotlib.rc("font",**font) + + matplotlib.rc("font", **font) import matplotlib.pyplot as plt from matplotlib.backends.backend_pdf import PdfPages from matplotlib.gridspec import GridSpec except Exception as e: - #raise Exception("error importing matplotlib: {0}".format(str(e))) - warnings.warn("error importing matplotlib: {0}".format(str(e)),PyemuWarning) + # raise Exception("error importing matplotlib: {0}".format(str(e))) + warnings.warn("error importing matplotlib: {0}".format(str(e)), PyemuWarning) import pyemu -figsize=(8,10.5) -nr,nc = 4,2 -#page_gs = GridSpec(nr,nc) + +figsize = (8, 10.5) +nr, nc = 4, 2 +# page_gs = GridSpec(nr,nc) abet = string.ascii_uppercase -def plot_summary_distributions(df,ax=None,label_post=False,label_prior=False, - subplots=False,figsize=(11,8.5),pt_color='b'): + +def plot_summary_distributions( + df, + ax=None, + label_post=False, + label_prior=False, + subplots=False, + figsize=(11, 8.5), + pt_color="b", +): """ helper function to plot gaussian distrbutions from prior and posterior means and standard deviations @@ -64,50 +75,52 @@ def plot_summary_distributions(df,ax=None,label_post=False,label_prior=False, """ import matplotlib.pyplot as plt - if isinstance(df,str): - df = pd.read_csv(df,index_col=0) + + if isinstance(df, str): + df = pd.read_csv(df, index_col=0) if ax is None and not subplots: fig = plt.figure(figsize=figsize) ax = plt.subplot(111) ax.grid() - if "post_stdev" not in df.columns and "post_var" in df.columns: - df.loc[:,"post_stdev"] = df.post_var.apply(np.sqrt) + df.loc[:, "post_stdev"] = df.post_var.apply(np.sqrt) if "prior_stdev" not in df.columns and "prior_var" in df.columns: - df.loc[:,"prior_stdev"] = df.prior_var.apply(np.sqrt) + df.loc[:, "prior_stdev"] = df.prior_var.apply(np.sqrt) if "prior_expt" not in df.columns and "prior_mean" in df.columns: - df.loc[:,"prior_expt"] = df.prior_mean + df.loc[:, "prior_expt"] = df.prior_mean if "post_expt" not in df.columns and "post_mean" in df.columns: - df.loc[:,"post_expt"] = df.post_mean + df.loc[:, "post_expt"] = df.post_mean if subplots: fig = plt.figure(figsize=figsize) - ax = plt.subplot(2,3,1) + ax = plt.subplot(2, 3, 1) ax_per_page = 6 ax_count = 0 axes = [] figs = [] for name in df.index: - x,y = gaussian_distribution(df.loc[name,"post_expt"], - df.loc[name,"post_stdev"]) - ax.fill_between(x,0,y,facecolor=pt_color,edgecolor="none",alpha=0.25) + x, y = gaussian_distribution( + df.loc[name, "post_expt"], df.loc[name, "post_stdev"] + ) + ax.fill_between(x, 0, y, facecolor=pt_color, edgecolor="none", alpha=0.25) if label_post: mx_idx = np.argmax(y) - xtxt,ytxt = x[mx_idx],y[mx_idx] * 1.001 - ax.text(xtxt,ytxt,name,ha="center",alpha=0.5) + xtxt, ytxt = x[mx_idx], y[mx_idx] * 1.001 + ax.text(xtxt, ytxt, name, ha="center", alpha=0.5) - x,y = gaussian_distribution(df.loc[name,"prior_expt"], - df.loc[name,"prior_stdev"]) - ax.plot(x,y,color='0.5',lw=3.0,dashes=(2,1)) + x, y = gaussian_distribution( + df.loc[name, "prior_expt"], df.loc[name, "prior_stdev"] + ) + ax.plot(x, y, color="0.5", lw=3.0, dashes=(2, 1)) if label_prior: mx_idx = np.argmax(y) - xtxt,ytxt = x[mx_idx],y[mx_idx] * 1.001 - ax.text(xtxt,ytxt,name,ha="center",alpha=0.5) - #ylim = list(ax.get_ylim()) - #ylim[1] *= 1.2 - #ylim[0] = 0.0 - #ax.set_ylim(ylim) + xtxt, ytxt = x[mx_idx], y[mx_idx] * 1.001 + ax.text(xtxt, ytxt, name, ha="center", alpha=0.5) + # ylim = list(ax.get_ylim()) + # ylim[1] *= 1.2 + # ylim[0] = 0.0 + # ax.set_ylim(ylim) if subplots: ax.set_title(name) ax_count += 1 @@ -119,7 +132,7 @@ def plot_summary_distributions(df,ax=None,label_post=False,label_prior=False, figs.append(fig) fig = plt.figure(figsize=figsize) ax_count = 0 - ax = plt.subplot(2,3,ax_count+1) + ax = plt.subplot(2, 3, ax_count + 1) if subplots: figs.append(fig) return figs, axes @@ -130,6 +143,7 @@ def plot_summary_distributions(df,ax=None,label_post=False,label_prior=False, ax.set_yticklabels([]) return ax + def gaussian_distribution(mean, stdev, num_pts=50): """ get an x and y numpy.ndarray that spans the +/- 4 standard deviation range of a gaussian distribution with @@ -150,11 +164,14 @@ def gaussian_distribution(mean, stdev, num_pts=50): """ xstart = mean - (4.0 * stdev) xend = mean + (4.0 * stdev) - x = np.linspace(xstart,xend,num_pts) - y = (1.0/np.sqrt(2.0*np.pi*stdev*stdev)) * np.exp(-1.0 * ((x - mean)**2)/(2.0*stdev*stdev)) - return x,y + x = np.linspace(xstart, xend, num_pts) + y = (1.0 / np.sqrt(2.0 * np.pi * stdev * stdev)) * np.exp( + -1.0 * ((x - mean) ** 2) / (2.0 * stdev * stdev) + ) + return x, y + -def pst_helper(pst,kind=None,**kwargs): +def pst_helper(pst, kind=None, **kwargs): """`pyemu.Pst` plot helper - takes the handoff from `pyemu.Pst.plot()` @@ -169,32 +186,38 @@ def pst_helper(pst,kind=None,**kwargs): """ - echo = kwargs.get("echo",False) - logger = pyemu.Logger("plot_pst_helper.log",echo=echo) + echo = kwargs.get("echo", False) + logger = pyemu.Logger("plot_pst_helper.log", echo=echo) logger.statement("plot_utils.pst_helper()") - kinds = {"prior":pst_prior,"1to1":res_1to1, - "phi_pie":res_phi_pie, - "phi_progress":phi_progress} + kinds = { + "prior": pst_prior, + "1to1": res_1to1, + "phi_pie": res_phi_pie, + "phi_progress": phi_progress, + } if kind is None: returns = [] base_filename = pst.filename if pst.new_filename is not None: base_filename = pst.new_filename - base_filename = base_filename.replace(".pst",'') - for name,func in kinds.items(): - plt_name = base_filename+"."+name+".pdf" - returns.append(func(pst,logger=logger,filename=plt_name)) + base_filename = base_filename.replace(".pst", "") + for name, func in kinds.items(): + plt_name = base_filename + "." + name + ".pdf" + returns.append(func(pst, logger=logger, filename=plt_name)) return returns elif kind not in kinds: - logger.lraise("unrecognized kind:{0}, should one of {1}" - .format(kind,','.join(list(kinds.keys())))) + logger.lraise( + "unrecognized kind:{0}, should one of {1}".format( + kind, ",".join(list(kinds.keys())) + ) + ) return kinds[kind](pst, logger, **kwargs) -def phi_progress(pst,logger=None,filename=None,**kwargs): +def phi_progress(pst, logger=None, filename=None, **kwargs): """ make plot of phi vs number of model runs - requires available ".iobj" file generated by a PESTPP-GLM run. @@ -218,10 +241,10 @@ def phi_progress(pst,logger=None,filename=None,**kwargs): """ if logger is None: - logger = Logger('Default_Loggger.log', echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot phi_progress") - iobj_file = pst.filename.replace(".pst",".iobj") + iobj_file = pst.filename.replace(".pst", ".iobj") if not os.path.exists(iobj_file): logger.lraise("couldn't find iobj file {0}".format(iobj_file)) df = pd.read_csv(iobj_file) @@ -229,8 +252,8 @@ def phi_progress(pst,logger=None,filename=None,**kwargs): ax = kwargs["ax"] else: fig = plt.figure(figsize=figsize) - ax = plt.subplot(1,1,1) - ax.plot(df.model_runs_completed,df.total_phi,marker='.') + ax = plt.subplot(1, 1, 1) + ax.plot(df.model_runs_completed, df.total_phi, marker=".") ax.set_xlabel("model runs") ax.set_ylabel("$\phi$") ax.grid() @@ -241,11 +264,14 @@ def phi_progress(pst,logger=None,filename=None,**kwargs): def _get_page_axes(count=nr * nc): - axes = [plt.subplot(nr,nc,i+1) for i in range(min(count,nr*nc))] - #[ax.set_yticks([]) for ax in axes] + axes = [plt.subplot(nr, nc, i + 1) for i in range(min(count, nr * nc))] + # [ax.set_yticks([]) for ax in axes] return axes -def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**kwargs): + +def res_1to1( + pst, logger=None, filename=None, plot_hexbin=False, histogram=False, **kwargs +): """ make 1-to-1 plots and also observed vs residual by observation group Args: @@ -272,15 +298,15 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot res_1to1") if "ensemble" in kwargs: - res = pst_utils.res_from_en(pst, kwargs['ensemble']) + res = pst_utils.res_from_en(pst, kwargs["ensemble"]) try: - res=pst_utils.res_from_en(pst,kwargs['ensemble']) + res = pst_utils.res_from_en(pst, kwargs["ensemble"]) except Exception as e: - logger.lraise("res_1to1: error loading ensemble file: {0}".format( str(e))) + logger.lraise("res_1to1: error loading ensemble file: {0}".format(str(e))) else: try: res = pst.res @@ -296,13 +322,19 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k fig = plt.figure(figsize=figsize) if "fig_title" in kwargs: - plt.figtext(0.5,0.5,kwargs["fig_title"]) + plt.figtext(0.5, 0.5, kwargs["fig_title"]) else: - plt.figtext(0.5, 0.5, "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}" - .format(pst.filename, str(datetime.now())), ha="center") - #if plot_hexbin: + plt.figtext( + 0.5, + 0.5, + "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}".format( + pst.filename, str(datetime.now()) + ), + ha="center", + ) + # if plot_hexbin: # pdfname = pst.filename.replace(".pst", ".1to1.hexbin.pdf") - #else: + # else: # pdfname = pst.filename.replace(".pst", ".1to1.pdf") figs = [] ax_count = 0 @@ -312,7 +344,7 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k obs_g = obs.loc[names, :] obs_g.loc[:, "sim"] = res.loc[names, "modelled"] logger.statement("using control file obsvals to calculate residuals") - obs_g.loc[:,'res'] = obs_g.sim - obs_g.obsval + obs_g.loc[:, "res"] = obs_g.sim - obs_g.obsval if "include_zero" not in kwargs or kwargs["include_zero"] is True: obs_g = obs_g.loc[obs_g.weight > 0, :] if obs_g.shape[0] == 0: @@ -323,8 +355,8 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k if ax_count % (nr * nc) == 0: if ax_count > 0: plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) fig = plt.figure(figsize=figsize) axes = _get_page_axes() @@ -332,81 +364,96 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k ax = axes[ax_count] - #if obs_g.shape[0] == 1: + # if obs_g.shape[0] == 1: # ax.scatter(list(obs_g.sim),list(obs_g.obsval),marker='.',s=30,color='b') - #else: + # else: mx = max(obs_g.obsval.max(), obs_g.sim.max()) mn = min(obs_g.obsval.min(), obs_g.sim.min()) - #if obs_g.shape[0] == 1: + # if obs_g.shape[0] == 1: mx *= 1.1 mn *= 0.9 - ax.axis('square') + ax.axis("square") if plot_hexbin: - ax.hexbin(obs_g.obsval.values, obs_g.sim.values, mincnt=1, gridsize=(75, 75), - extent=(mn, mx, mn, mx), bins='log', edgecolors=None) -# plt.colorbar(ax=ax) + ax.hexbin( + obs_g.obsval.values, + obs_g.sim.values, + mincnt=1, + gridsize=(75, 75), + extent=(mn, mx, mn, mx), + bins="log", + edgecolors=None, + ) + # plt.colorbar(ax=ax) else: - ax.scatter([obs_g.obsval], [obs_g.sim], marker='.', s=10, color='b') - + ax.scatter([obs_g.obsval], [obs_g.sim], marker=".", s=10, color="b") - - ax.plot([mn,mx],[mn,mx],'k--',lw=1.0) - xlim = (mn,mx) - ax.set_xlim(mn,mx) - ax.set_ylim(mn,mx) + ax.plot([mn, mx], [mn, mx], "k--", lw=1.0) + xlim = (mn, mx) + ax.set_xlim(mn, mx) + ax.set_ylim(mn, mx) ax.grid() - ax.set_xlabel("observed",labelpad=0.1) - ax.set_ylabel("simulated",labelpad=0.1) - ax.set_title("{0}) group:{1}, {2} observations". - format(abet[ax_count], g, obs_g.shape[0]), loc="left") + ax.set_xlabel("observed", labelpad=0.1) + ax.set_ylabel("simulated", labelpad=0.1) + ax.set_title( + "{0}) group:{1}, {2} observations".format( + abet[ax_count], g, obs_g.shape[0] + ), + loc="left", + ) ax_count += 1 - if histogram==False: + if histogram == False: ax = axes[ax_count] - ax.scatter(obs_g.obsval, obs_g.res, marker='.', s=10, color='b') + ax.scatter(obs_g.obsval, obs_g.res, marker=".", s=10, color="b") ylim = ax.get_ylim() mx = max(np.abs(ylim[0]), np.abs(ylim[1])) if obs_g.shape[0] == 1: mx *= 1.1 ax.set_ylim(-mx, mx) - #show a zero residuals line - ax.plot(xlim, [0,0], 'k--', lw=1.0) - meanres= obs_g.res.mean() + # show a zero residuals line + ax.plot(xlim, [0, 0], "k--", lw=1.0) + meanres = obs_g.res.mean() # show mean residuals line - ax.plot(xlim,[meanres,meanres], 'r-', lw=1.0) + ax.plot(xlim, [meanres, meanres], "r-", lw=1.0) ax.set_xlim(xlim) - ax.set_ylabel("residual",labelpad=0.1) - ax.set_xlabel("observed",labelpad=0.1) - ax.set_title("{0}) group:{1}, {2} observations". - format(abet[ax_count], g, obs_g.shape[0]), loc="left") + ax.set_ylabel("residual", labelpad=0.1) + ax.set_xlabel("observed", labelpad=0.1) + ax.set_title( + "{0}) group:{1}, {2} observations".format( + abet[ax_count], g, obs_g.shape[0] + ), + loc="left", + ) ax.grid() ax_count += 1 else: - #need max and min res to set xlim, otherwise wonky figsize + # need max and min res to set xlim, otherwise wonky figsize mxr = obs_g.res.max() mnr = obs_g.res.min() - #if obs_g.shape[0] == 1: + # if obs_g.shape[0] == 1: mxr *= 1.1 mnr *= 0.9 - rlim = (mnr,mxr) + rlim = (mnr, mxr) ax = axes[ax_count] - ax.hist(obs_g.res, bins=50, color='b') - meanres= obs_g.res.mean() - ax.axvline(meanres, color='r', lw=1) - b,t = ax.get_ylim() - ax.text(meanres + meanres/10, - t - t/10, - 'Mean: {:.2f}'.format(meanres)) + ax.hist(obs_g.res, bins=50, color="b") + meanres = obs_g.res.mean() + ax.axvline(meanres, color="r", lw=1) + b, t = ax.get_ylim() + ax.text(meanres + meanres / 10, t - t / 10, "Mean: {:.2f}".format(meanres)) ax.set_xlim(rlim) - ax.set_ylabel("count",labelpad=0.1) - ax.set_xlabel("residual",labelpad=0.1) - ax.set_title("{0}) group:{1}, {2} observations". - format(abet[ax_count], g, obs_g.shape[0]), loc="left") + ax.set_ylabel("count", labelpad=0.1) + ax.set_xlabel("residual", labelpad=0.1) + ax.set_title( + "{0}) group:{1}, {2} observations".format( + abet[ax_count], g, obs_g.shape[0] + ), + loc="left", + ) ax.grid() ax_count += 1 logger.log("plotting 1to1 for {0}".format(g)) @@ -417,8 +464,8 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k axes[a].set_xticks([]) plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) if filename is not None: with PdfPages(filename) as pdf: @@ -430,6 +477,7 @@ def res_1to1(pst,logger=None,filename=None,plot_hexbin=False,histogram=False,**k logger.log("plot res_1to1") return figs + def plot_id_bar(id_df, nsv=None, logger=None, **kwargs): """Plot a stacked bar chart of identifiability based on a the `pyemu.ErrVar.get_identifiability()` dataframe @@ -455,38 +503,37 @@ def plot_id_bar(id_df, nsv=None, logger=None, **kwargs): """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot id bar") - df = id_df.copy() # drop the final `ident` column - if 'ident' in df.columns: - df.drop('ident', inplace=True, axis=1) + if "ident" in df.columns: + df.drop("ident", inplace=True, axis=1) if nsv is None or nsv > len(df.columns): nsv = len(df.columns) - logger.log('set number of SVs and number in the dataframe') + logger.log("set number of SVs and number in the dataframe") df = df[df.columns[:nsv]] - df['ident'] = df.sum(axis=1) - df.sort_values(by='ident', inplace=True, ascending=False) - df.drop('ident', inplace=True, axis=1) + df["ident"] = df.sum(axis=1) + df.sort_values(by="ident", inplace=True, ascending=False) + df.drop("ident", inplace=True, axis=1) - if 'figsize' in kwargs: - figsize=kwargs['figsize'] + if "figsize" in kwargs: + figsize = kwargs["figsize"] else: figsize = (8, 10.5) if "ax" in kwargs: ax = kwargs["ax"] else: fig = plt.figure(figsize=figsize) - ax = plt.subplot(1,1,1) + ax = plt.subplot(1, 1, 1) # plto the stacked bar chart (the easy part!) - df.plot.bar(stacked=True, cmap='jet_r', legend=False, ax=ax) + df.plot.bar(stacked=True, cmap="jet_r", legend=False, ax=ax) # # horrible shenanigans to make a colorbar rather than a legend @@ -494,11 +541,18 @@ def plot_id_bar(id_df, nsv=None, logger=None, **kwargs): # special case colormap just dark red if one SV if nsv == 1: - tcm = matplotlib.colors.LinearSegmentedColormap.from_list('one_sv', [plt.get_cmap('jet_r')(0)] * 2, N=2) - sm = plt.cm.ScalarMappable(cmap=tcm, norm=matplotlib.colors.Normalize(vmin=0, vmax=nsv + 1)) + tcm = matplotlib.colors.LinearSegmentedColormap.from_list( + "one_sv", [plt.get_cmap("jet_r")(0)] * 2, N=2 + ) + sm = plt.cm.ScalarMappable( + cmap=tcm, norm=matplotlib.colors.Normalize(vmin=0, vmax=nsv + 1) + ) # or typically just rock the jet_r colormap over the range of SVs else: - sm = plt.cm.ScalarMappable(cmap=plt.get_cmap('jet_r'), norm=matplotlib.colors.Normalize(vmin=1, vmax=nsv)) + sm = plt.cm.ScalarMappable( + cmap=plt.get_cmap("jet_r"), + norm=matplotlib.colors.Normalize(vmin=1, vmax=nsv), + ) sm._A = [] # now, if too many ticks for the colorbar, summarize them @@ -510,11 +564,12 @@ def plot_id_bar(id_df, nsv=None, logger=None, **kwargs): cb = plt.colorbar(sm) cb.set_ticks(ticks) - logger.log('plot id bar') + logger.log("plot id bar") return ax -def res_phi_pie(pst,logger=None, **kwargs): + +def res_phi_pie(pst, logger=None, **kwargs): """plot current phi components as a pie chart. Args: @@ -537,14 +592,16 @@ def res_phi_pie(pst,logger=None, **kwargs): """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot res_phi_pie") if "ensemble" in kwargs: try: - res=pst_utils.res_from_en(pst,kwargs['ensemble']) + res = pst_utils.res_from_en(pst, kwargs["ensemble"]) except: - logger.statement("res_1to1: could not find ensemble file {0}".format(kwargs['ensemble'])) + logger.statement( + "res_1to1: could not find ensemble file {0}".format(kwargs["ensemble"]) + ) else: try: res = pst.res @@ -557,23 +614,26 @@ def res_phi_pie(pst,logger=None, **kwargs): norm_phi_comps = pst.phi_components_normalized keys = list(phi_comps.keys()) if "include_zero" not in kwargs or kwargs["include_zero"] is True: - phi_comps = {k:phi_comps[k] for k in keys if phi_comps[k] > 0.0} + phi_comps = {k: phi_comps[k] for k in keys if phi_comps[k] > 0.0} keys = list(phi_comps.keys()) - norm_phi_comps = {k:norm_phi_comps[k] for k in keys} + norm_phi_comps = {k: norm_phi_comps[k] for k in keys} if "ax" in kwargs: ax = kwargs["ax"] else: fig = plt.figure(figsize=figsize) - ax = plt.subplot(1,1,1,aspect="equal") - labels = ["{0}\n{1:4G}\n({2:3.1f}%)".format(k,phi_comps[k],100. * (phi_comps[k] / phi)) for k in keys] - ax.pie([float(norm_phi_comps[k]) for k in keys],labels=labels) + ax = plt.subplot(1, 1, 1, aspect="equal") + labels = [ + "{0}\n{1:4G}\n({2:3.1f}%)".format(k, phi_comps[k], 100.0 * (phi_comps[k] / phi)) + for k in keys + ] + ax.pie([float(norm_phi_comps[k]) for k in keys], labels=labels) logger.log("plot res_phi_pie") if "filename" in kwargs: plt.savefig(kwargs["filename"]) return ax -def pst_prior(pst,logger=None, filename=None, **kwargs): +def pst_prior(pst, logger=None, filename=None, **kwargs): """ helper to plot prior parameter histograms implied by parameter bounds. Saves a multipage pdf named .prior.pdf @@ -596,7 +656,7 @@ def pst_prior(pst,logger=None, filename=None, **kwargs): """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot pst_prior") par = pst.parameter_data @@ -609,9 +669,9 @@ def pst_prior(pst,logger=None, filename=None, **kwargs): logger.log("building mean parameter values") li = par.partrans.loc[cov.names] == "log" mean = par.parval1.loc[cov.names] - info = par.loc[cov.names,:].copy() - info.loc[:,"mean"] = mean - info.loc[li,"mean"] = mean[li].apply(np.log10) + info = par.loc[cov.names, :].copy() + info.loc[:, "mean"] = mean + info.loc[li, "mean"] = mean[li].apply(np.log10) logger.log("building mean parameter values") logger.log("building stdev parameter values") @@ -620,46 +680,50 @@ def pst_prior(pst,logger=None, filename=None, **kwargs): else: std = np.diag(cov.x) std = np.sqrt(std) - info.loc[:,"prior_std"] = std + info.loc[:, "prior_std"] = std logger.log("building stdev parameter values") if std.shape != mean.shape: - logger.lraise("mean.shape {0} != std.shape {1}". - format(mean.shape,std.shape)) + logger.lraise("mean.shape {0} != std.shape {1}".format(mean.shape, std.shape)) if "grouper" in kwargs: raise NotImplementedError() - #check for consistency here + # check for consistency here else: - par_adj = par.loc[par.partrans.apply(lambda x: x in ["log","none"]),:] + par_adj = par.loc[par.partrans.apply(lambda x: x in ["log", "none"]), :] grouper = par_adj.groupby(par_adj.pargp).groups - #grouper = par.groupby(par.pargp).groups + # grouper = par.groupby(par.pargp).groups if len(grouper) == 0: raise Exception("no adustable parameters to plot") fig = plt.figure(figsize=figsize) if "fig_title" in kwargs: - plt.figtext(0.5,0.5,kwargs["fig_title"]) + plt.figtext(0.5, 0.5, kwargs["fig_title"]) else: - plt.figtext(0.5,0.5,"pyemu.Pst.plot(kind='prior')\nfrom pest control file '{0}'\n at {1}" - .format(pst.filename,str(datetime.now())),ha="center") + plt.figtext( + 0.5, + 0.5, + "pyemu.Pst.plot(kind='prior')\nfrom pest control file '{0}'\n at {1}".format( + pst.filename, str(datetime.now()) + ), + ha="center", + ) figs = [] ax_count = 0 grps_names = list(grouper.keys()) grps_names.sort() for g in grps_names: names = grouper[g] - logger.log("plotting priors for {0}". - format(','.join(list(names)))) + logger.log("plotting priors for {0}".format(",".join(list(names)))) if ax_count % (nr * nc) == 0: plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) - fig = plt.figure(figsize=figsize) + fig = plt.figure(figsize=figsize) axes = _get_page_axes() ax_count = 0 @@ -672,40 +736,41 @@ def pst_prior(pst,logger=None, filename=None, **kwargs): ax = axes[ax_count] if "unique_only" in kwargs and kwargs["unique_only"]: - - ms = info.loc[names,:].apply(lambda x: (x["mean"],x["prior_std"]),axis=1).unique() - for (m,s) in ms: + ms = ( + info.loc[names, :] + .apply(lambda x: (x["mean"], x["prior_std"]), axis=1) + .unique() + ) + for (m, s) in ms: x, y = gaussian_distribution(m, s) - ax.fill_between(x, 0, y, facecolor='0.5', alpha=0.5, - edgecolor="none") - + ax.fill_between(x, 0, y, facecolor="0.5", alpha=0.5, edgecolor="none") else: - for m,s in zip(info.loc[names,'mean'],info.loc[names,'prior_std']): - x,y = gaussian_distribution(m,s) - ax.fill_between(x,0,y,facecolor='0.5',alpha=0.5, - edgecolor="none") - ax.set_title("{0}) group:{1}, {2} parameters". - format(abet[ax_count],g,names.shape[0]),loc="left") + for m, s in zip(info.loc[names, "mean"], info.loc[names, "prior_std"]): + x, y = gaussian_distribution(m, s) + ax.fill_between(x, 0, y, facecolor="0.5", alpha=0.5, edgecolor="none") + ax.set_title( + "{0}) group:{1}, {2} parameters".format(abet[ax_count], g, names.shape[0]), + loc="left", + ) ax.set_yticks([]) if islog: - ax.set_xlabel("$log_{10}$ parameter value",labelpad=0.1) + ax.set_xlabel("$log_{10}$ parameter value", labelpad=0.1) else: ax.set_xlabel("parameter value", labelpad=0.1) - logger.log("plotting priors for {0}". - format(','.join(list(names)))) + logger.log("plotting priors for {0}".format(",".join(list(names)))) ax_count += 1 - for a in range(ax_count,nr*nc): + for a in range(ax_count, nr * nc): axes[a].set_axis_off() axes[a].set_yticks([]) axes[a].set_xticks([]) plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) if filename is not None: with PdfPages(filename) as pdf: @@ -718,10 +783,19 @@ def pst_prior(pst,logger=None, filename=None, **kwargs): return figs -def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, - filename=None,func_dict = None, - sync_bins=True,deter_vals=None,std_window=None, - deter_range=False,**kwargs): +def ensemble_helper( + ensemble, + bins=10, + facecolor="0.5", + plot_cols=None, + filename=None, + func_dict=None, + sync_bins=True, + deter_vals=None, + std_window=None, + deter_range=False, + **kwargs +): """helper function to plot ensemble histograms Args: @@ -762,20 +836,19 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, """ logger = pyemu.Logger("ensemble_helper.log") logger.log("pyemu.plot_utils.ensemble_helper()") - ensembles = _process_ensemble_arg(ensemble,facecolor,logger) + ensembles = _process_ensemble_arg(ensemble, facecolor, logger) if len(ensembles) == 0: raise Exception("plot_uitls.ensemble_helper() error processing `ensemble` arg") - #apply any functions + # apply any functions if func_dict is not None: logger.log("applying functions") - for col,func in func_dict.items(): - for fc,en in ensembles.items(): + for col, func in func_dict.items(): + for fc, en in ensembles.items(): if col in en.columns: - en.loc[:,col] = en.loc[:,col].apply(func) + en.loc[:, col] = en.loc[:, col].apply(func) logger.log("applying functions") - - #get a list of all cols (union) + # get a list of all cols (union) all_cols = set() for fc, en in ensembles.items(): cols = set(en.columns) @@ -783,33 +856,41 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, if plot_cols is None: plot_cols = {i: [v] for i, v in (zip(all_cols, all_cols))} else: - if isinstance(plot_cols,list): + if isinstance(plot_cols, list): splot_cols = set(plot_cols) plot_cols = {i: [v] for i, v in (zip(plot_cols, plot_cols))} - elif isinstance(plot_cols,dict): + elif isinstance(plot_cols, dict): splot_cols = [] - for label,pcols in plot_cols.items(): + for label, pcols in plot_cols.items(): splot_cols.extend(list(pcols)) splot_cols = set(splot_cols) else: - logger.lraise("unrecognized plot_cols type: {0}, should be list or dict". - format(type(plot_cols))) + logger.lraise( + "unrecognized plot_cols type: {0}, should be list or dict".format( + type(plot_cols) + ) + ) missing = splot_cols - all_cols if len(missing) > 0: - logger.lraise("the following plot_cols are missing: {0}". - format(','.join(missing))) + logger.lraise( + "the following plot_cols are missing: {0}".format(",".join(missing)) + ) logger.statement("plotting {0} histograms".format(len(plot_cols))) fig = plt.figure(figsize=figsize) if "fig_title" in kwargs: - plt.figtext(0.5,0.5,kwargs["fig_title"]) + plt.figtext(0.5, 0.5, kwargs["fig_title"]) else: - plt.figtext(0.5, 0.5, "pyemu.plot_utils.ensemble_helper()\n at {0}" - .format(str(datetime.now())), ha="center") - #plot_cols = list(plot_cols) - #plot_cols.sort() + plt.figtext( + 0.5, + 0.5, + "pyemu.plot_utils.ensemble_helper()\n at {0}".format(str(datetime.now())), + ha="center", + ) + # plot_cols = list(plot_cols) + # plot_cols.sort() labels = list(plot_cols.keys()) labels.sort() logger.statement("saving pdf to {0}".format(filename)) @@ -817,14 +898,14 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, ax_count = 0 - #for label,plot_col in plot_cols.items(): + # for label,plot_col in plot_cols.items(): for label in labels: plot_col = plot_cols[label] logger.log("plotting reals for {0}".format(label)) if ax_count % (nr * nc) == 0: plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) fig = plt.figure(figsize=figsize) axes = _get_page_axes() @@ -834,31 +915,35 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, ax = axes[ax_count] if sync_bins: - mx,mn = -1.0e+30,1.0e+30 - for fc,en in ensembles.items(): + mx, mn = -1.0e30, 1.0e30 + for fc, en in ensembles.items(): # for pc in plot_col: # if pc in en.columns: # emx,emn = en.loc[:,pc].max(),en.loc[:,pc].min() # mx = max(mx,emx) # mn = min(mn,emn) - emn = np.nanmin(en.loc[:,plot_col].values) + emn = np.nanmin(en.loc[:, plot_col].values) emx = np.nanmax(en.loc[:, plot_col].values) mx = max(mx, emx) - mn = min(mn,emn) - if mx == -1.0e+30 and mn == 1.0e+30: + mn = min(mn, emn) + if mx == -1.0e30 and mn == 1.0e30: logger.warn("all NaNs for label: {0}".format(label)) - ax.set_title("{0}) {1}, count:{2} - all NaN".format(abet[ax_count], - label, len(plot_col)), loc="left") + ax.set_title( + "{0}) {1}, count:{2} - all NaN".format( + abet[ax_count], label, len(plot_col) + ), + loc="left", + ) ax.set_yticks([]) ax.set_xticks([]) ax_count += 1 continue - plot_bins = np.linspace(mn,mx,num=bins) - logger.statement("{0} min:{1:5G}, max:{2:5G}".format(label,mn,mx)) + plot_bins = np.linspace(mn, mx, num=bins) + logger.statement("{0} min:{1:5G}, max:{2:5G}".format(label, mn, mx)) else: - plot_bins=bins - for fc,en in ensembles.items(): - #for pc in plot_col: + plot_bins = bins + for fc, en in ensembles.items(): + # for pc in plot_col: # if pc in en.columns: # try: # en.loc[:,pc].hist(bins=plot_bins,facecolor=fc, @@ -867,44 +952,61 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, # except Exception as e: # logger.warn("error plotting histogram for {0}:{1}". # format(pc,str(e))) - vals = en.loc[:,plot_col].values.flatten() - #print(plot_bins) - #print(vals) - - ax.hist(vals,bins=plot_bins,edgecolor="none",alpha=0.5,density=True,facecolor=fc) + vals = en.loc[:, plot_col].values.flatten() + # print(plot_bins) + # print(vals) + + ax.hist( + vals, + bins=plot_bins, + edgecolor="none", + alpha=0.5, + density=True, + facecolor=fc, + ) v = None if deter_vals is not None: for pc in plot_col: if pc in deter_vals: ylim = ax.get_ylim() v = deter_vals[pc] - ax.plot([v,v],ylim,"k--",lw=1.5) + ax.plot([v, v], ylim, "k--", lw=1.5) ax.set_ylim(ylim) - if std_window is not None: try: ylim = ax.get_ylim() - mn, st = en.loc[:,pc].mean(), en.loc[:,pc].std() * (std_window / 2.0) + mn, st = ( + en.loc[:, pc].mean(), + en.loc[:, pc].std() * (std_window / 2.0), + ) - ax.plot([mn - st, mn - st], ylim, color=fc, lw=1.5,ls='--') - ax.plot([mn + st, mn + st], ylim, color=fc, lw=1.5,ls='--') + ax.plot([mn - st, mn - st], ylim, color=fc, lw=1.5, ls="--") + ax.plot([mn + st, mn + st], ylim, color=fc, lw=1.5, ls="--") ax.set_ylim(ylim) if deter_range and v is not None: xmn = v - st xmx = v + st - ax.set_xlim(xmn,xmx) + ax.set_xlim(xmn, xmx) except: - logger.warn("error plotting std window for {0}". - format(pc)) + logger.warn("error plotting std window for {0}".format(pc)) ax.grid() if len(ensembles) > 1: - ax.set_title("{0}) {1}, count: {2}".format(abet[ax_count], label,len(plot_col)), loc="left") + ax.set_title( + "{0}) {1}, count: {2}".format(abet[ax_count], label, len(plot_col)), + loc="left", + ) else: - ax.set_title("{0}) {1}, count:{2}\nmin:{3:3.1E}, max:{4:3.1E}".format(abet[ax_count], - label,len(plot_col), - np.nanmin(vals), - np.nanmax(vals)), loc="left") + ax.set_title( + "{0}) {1}, count:{2}\nmin:{3:3.1E}, max:{4:3.1E}".format( + abet[ax_count], + label, + len(plot_col), + np.nanmin(vals), + np.nanmax(vals), + ), + loc="left", + ) ax_count += 1 for a in range(ax_count, nr * nc): @@ -913,8 +1015,8 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, axes[a].set_xticks([]) plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) if filename is not None: plt.tight_layout() @@ -924,7 +1026,17 @@ def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None, plt.close(fig) logger.log("pyemu.plot_utils.ensemble_helper()") -def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',logger=None,filename=None,**kwargs): + +def ensemble_change_summary( + ensemble1, + ensemble2, + pst, + bins=10, + facecolor="0.5", + logger=None, + filename=None, + **kwargs +): """helper function to plot first and second moment change histograms between two ensembles @@ -941,43 +1053,50 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot ensemble change") if isinstance(ensemble1, str): - ensemble1 = pd.read_csv(ensemble1,index_col=0) + ensemble1 = pd.read_csv(ensemble1, index_col=0) ensemble1.columns = ensemble1.columns.str.lower() if isinstance(ensemble2, str): - ensemble2 = pd.read_csv(ensemble2,index_col=0) + ensemble2 = pd.read_csv(ensemble2, index_col=0) ensemble2.columns = ensemble2.columns.str.lower() # better to ensure this is caught by pestpp-ies ensemble csvs unnamed1 = [col for col in ensemble1.columns if "unnamed:" in col] if len(unnamed1) != 0: - ensemble1 = ensemble1.iloc[:,:-1] # ensure unnamed col result of poor csv read only (ie last col) + ensemble1 = ensemble1.iloc[ + :, :-1 + ] # ensure unnamed col result of poor csv read only (ie last col) unnamed2 = [col for col in ensemble2.columns if "unnamed:" in col] if len(unnamed2) != 0: - ensemble2 = ensemble2.iloc[:,:-1] # ensure unnamed col result of poor csv read only (ie last col) + ensemble2 = ensemble2.iloc[ + :, :-1 + ] # ensure unnamed col result of poor csv read only (ie last col) - d = set(ensemble1.columns).symmetric_difference(set(ensemble2. columns)) + d = set(ensemble1.columns).symmetric_difference(set(ensemble2.columns)) if len(d) != 0: - logger.lraise("ensemble1 does not have the same columns as ensemble2: {0}". - format(','.join(d))) + logger.lraise( + "ensemble1 does not have the same columns as ensemble2: {0}".format( + ",".join(d) + ) + ) if "grouper" in kwargs: raise NotImplementedError() else: en_cols = set(ensemble1.columns) if len(en_cols.difference(set(pst.par_names))) == 0: - par = pst.parameter_data.loc[en_cols,:] + par = pst.parameter_data.loc[en_cols, :] grouper = par.groupby(par.pargp).groups grouper["all"] = pst.adj_par_names - li = par.loc[par.partrans == "log","parnme"] - ensemble1.loc[:,li] = ensemble1.loc[:,li].apply(np.log10) + li = par.loc[par.partrans == "log", "parnme"] + ensemble1.loc[:, li] = ensemble1.loc[:, li].apply(np.log10) ensemble2.loc[:, li] = ensemble2.loc[:, li].apply(np.log10) elif len(en_cols.difference(set(pst.obs_names))) == 0: - obs = pst.observation_data.loc[en_cols,:] + obs = pst.observation_data.loc[en_cols, :] grouper = obs.groupby(obs.obgnme).groups grouper["all"] = pst.nnz_obs_names else: @@ -991,25 +1110,28 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l mn_diff = -1 * (en2_mn - en1_mn) std_diff = 100 * (((en1_std - en2_std) / en1_std)) - #set en1_std==0 to nan - #std_diff[en1_std.index[en1_std==0]] = np.nan - - - - #diff = ensemble1 - ensemble2 - #mn_diff = diff.mean(axis=0) - #std_diff = diff.std(axis=0) + # set en1_std==0 to nan + # std_diff[en1_std.index[en1_std==0]] = np.nan + # diff = ensemble1 - ensemble2 + # mn_diff = diff.mean(axis=0) + # std_diff = diff.std(axis=0) fig = plt.figure(figsize=figsize) if "fig_title" in kwargs: - plt.figtext(0.5,0.5,kwargs["fig_title"]) + plt.figtext(0.5, 0.5, kwargs["fig_title"]) else: - plt.figtext(0.5, 0.5, "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}" - .format(pst.filename, str(datetime.now())), ha="center") - #if plot_hexbin: + plt.figtext( + 0.5, + 0.5, + "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}".format( + pst.filename, str(datetime.now()) + ), + ha="center", + ) + # if plot_hexbin: # pdfname = pst.filename.replace(".pst", ".1to1.hexbin.pdf") - #else: + # else: # pdfname = pst.filename.replace(".pst", ".1to1.pdf") figs = [] ax_count = 0 @@ -1027,27 +1149,29 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l if ax_count % (nr * nc) == 0: if ax_count > 0: plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) fig = plt.figure(figsize=figsize) axes = _get_page_axes() ax_count = 0 ax = axes[ax_count] - mn_g.hist(ax=ax,facecolor=facecolor,alpha=0.5,edgecolor=None,bins=bins) - #mx = max(mn_g.max(), mn_g.min(),np.abs(mn_g.max()),np.abs(mn_g.min())) * 1.2 - #ax.set_xlim(-mx,mx) - - #std_g.hist(ax=ax,facecolor='b',alpha=0.5,edgecolor=None) - + mn_g.hist(ax=ax, facecolor=facecolor, alpha=0.5, edgecolor=None, bins=bins) + # mx = max(mn_g.max(), mn_g.min(),np.abs(mn_g.max()),np.abs(mn_g.min())) * 1.2 + # ax.set_xlim(-mx,mx) + # std_g.hist(ax=ax,facecolor='b',alpha=0.5,edgecolor=None) - #ax.set_xlim(xlim) + # ax.set_xlim(xlim) ax.set_yticklabels([]) - ax.set_xlabel("mean change",labelpad=0.1) - ax.set_title("{0}) mean change group:{1}, {2} entries\nmax:{3:10G}, min:{4:10G}". - format(abet[ax_count], g, mn_g.shape[0],mn_g.max(),mn_g.min()), loc="left") + ax.set_xlabel("mean change", labelpad=0.1) + ax.set_title( + "{0}) mean change group:{1}, {2} entries\nmax:{3:10G}, min:{4:10G}".format( + abet[ax_count], g, mn_g.shape[0], mn_g.max(), mn_g.min() + ), + loc="left", + ) ax.grid() ax_count += 1 @@ -1055,13 +1179,15 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l std_g.hist(ax=ax, facecolor=facecolor, alpha=0.5, edgecolor=None, bins=bins) # std_g.hist(ax=ax,facecolor='b',alpha=0.5,edgecolor=None) - - # ax.set_xlim(xlim) ax.set_yticklabels([]) ax.set_xlabel("sigma percent reduction", labelpad=0.1) - ax.set_title("{0}) sigma change group:{1}, {2} entries\nmax:{3:10G}, min:{4:10G}". - format(abet[ax_count], g, mn_g.shape[0], std_g.max(), std_g.min()), loc="left") + ax.set_title( + "{0}) sigma change group:{1}, {2} entries\nmax:{3:10G}, min:{4:10G}".format( + abet[ax_count], g, mn_g.shape[0], std_g.max(), std_g.min() + ), + loc="left", + ) ax.grid() ax_count += 1 @@ -1073,8 +1199,8 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l axes[a].set_xticks([]) plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) if filename is not None: plt.tight_layout() @@ -1087,9 +1213,10 @@ def ensemble_change_summary(ensemble1, ensemble2, pst,bins=10, facecolor='0.5',l logger.log("plot ensemble change") return figs -def _process_ensemble_arg(ensemble,facecolor, logger): + +def _process_ensemble_arg(ensemble, facecolor, logger): ensembles = {} - if isinstance(ensemble, pd.DataFrame) or isinstance(ensemble,pyemu.Ensemble): + if isinstance(ensemble, pd.DataFrame) or isinstance(ensemble, pyemu.Ensemble): if not isinstance(facecolor, str): logger.lraise("facecolor must be str") ensembles[facecolor] = ensemble @@ -1097,18 +1224,18 @@ def _process_ensemble_arg(ensemble,facecolor, logger): if not isinstance(facecolor, str): logger.lraise("facecolor must be str") - logger.log('loading ensemble from csv file {0}'.format(ensemble)) + logger.log("loading ensemble from csv file {0}".format(ensemble)) en = pd.read_csv(ensemble, index_col=0) logger.statement("{0} shape: {1}".format(ensemble, en.shape)) ensembles[facecolor] = en - logger.log('loading ensemble from csv file {0}'.format(ensemble)) + logger.log("loading ensemble from csv file {0}".format(ensemble)) elif isinstance(ensemble, list): if isinstance(facecolor, list): if len(ensemble) != len(facecolor): logger.lraise("facecolor list len != ensemble list len") else: - colors = ['m', 'c', 'b', 'r', 'g', 'y'] + colors = ["m", "c", "b", "r", "g", "y"] facecolor = [colors[i] for i in range(len(ensemble))] ensembles = {} @@ -1119,7 +1246,7 @@ def _process_ensemble_arg(ensemble,facecolor, logger): logger.log("loading ensemble from csv file {0}".format(en_arg)) logger.statement("ensemble {0} gets facecolor {1}".format(en_arg, fc)) - elif isinstance(en_arg, pd.DataFrame) or isinstance(en_arg,pyemu.Ensemble): + elif isinstance(en_arg, pd.DataFrame) or isinstance(en_arg, pyemu.Ensemble): en = en_arg else: logger.lraise("unrecognized ensemble list arg:{0}".format(en_arg)) @@ -1127,7 +1254,7 @@ def _process_ensemble_arg(ensemble,facecolor, logger): elif isinstance(ensemble, dict): for fc, en_arg in ensemble.items(): - if isinstance(en_arg, pd.DataFrame) or isinstance(en_arg,pyemu.Ensemble): + if isinstance(en_arg, pd.DataFrame) or isinstance(en_arg, pyemu.Ensemble): ensembles[fc] = en_arg elif isinstance(en_arg, str): logger.log("loading ensemble from csv file {0}".format(en_arg)) @@ -1144,8 +1271,17 @@ def _process_ensemble_arg(ensemble,facecolor, logger): return ensembles -def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, - skip_groups=[],base_ensemble=None,**kwargs): + +def ensemble_res_1to1( + ensemble, + pst, + facecolor="0.5", + logger=None, + filename=None, + skip_groups=[], + base_ensemble=None, + **kwargs +): """helper function to plot ensemble 1-to-1 plots sbowing the simulated range Args: @@ -1161,13 +1297,13 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, """ if logger is None: - logger=Logger('Default_Loggger.log',echo=False) + logger = Logger("Default_Loggger.log", echo=False) logger.log("plot res_1to1") obs = pst.observation_data - ensembles = _process_ensemble_arg(ensemble,facecolor,logger) + ensembles = _process_ensemble_arg(ensemble, facecolor, logger) if base_ensemble is not None: - base_ensemble = _process_ensemble_arg(base_ensemble,"r",logger) + base_ensemble = _process_ensemble_arg(base_ensemble, "r", logger) if "grouper" in kwargs: raise NotImplementedError() @@ -1178,13 +1314,19 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, fig = plt.figure(figsize=figsize) if "fig_title" in kwargs: - plt.figtext(0.5,0.5,kwargs["fig_title"]) + plt.figtext(0.5, 0.5, kwargs["fig_title"]) else: - plt.figtext(0.5, 0.5, "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}" - .format(pst.filename, str(datetime.now())), ha="center") - #if plot_hexbin: + plt.figtext( + 0.5, + 0.5, + "pyemu.Pst.plot(kind='1to1')\nfrom pest control file '{0}'\n at {1}".format( + pst.filename, str(datetime.now()) + ), + ha="center", + ) + # if plot_hexbin: # pdfname = pst.filename.replace(".pst", ".1to1.hexbin.pdf") - #else: + # else: # pdfname = pst.filename.replace(".pst", ".1to1.pdf") figs = [] ax_count = 0 @@ -1204,8 +1346,8 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, if ax_count % (nr * nc) == 0: if ax_count > 0: plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) fig = plt.figure(figsize=figsize) axes = _get_page_axes() @@ -1214,17 +1356,17 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, ax = axes[ax_count] if base_ensemble is None: mx = obs_g.obsval.max() - mn = obs_g.obsval.min() + mn = obs_g.obsval.min() else: ben = base_ensemble["r"] - ben = ben.loc[:,ben.columns.intersection(names)] + ben = ben.loc[:, ben.columns.intersection(names)] mn = ben.min().min() mx = ben.max().max() - #if obs_g.shape[0] == 1: + # if obs_g.shape[0] == 1: mx *= 1.1 mn *= 0.9 - #ax.axis('square') + # ax.axis('square') if base_ensemble is not None: obs_gg = obs_g.sort_values(by="obsval") @@ -1232,33 +1374,39 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, en_g = en.loc[:, obs_gg.obsnme] ex = en_g.max() en = en_g.min() - #[ax.plot([ov, ov], [een, eex], color=c,alpha=0.3) for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values)] - ax.fill_between(obs_gg.obsval,en,ex,facecolor=c,alpha=0.2) - #ax.scatter([obs_g.sim], [obs_g.obsval], marker='.', s=10, color='b') - for c,en in ensembles.items(): - en_g = en.loc[:,obs_g.obsnme] + # [ax.plot([ov, ov], [een, eex], color=c,alpha=0.3) for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values)] + ax.fill_between(obs_gg.obsval, en, ex, facecolor=c, alpha=0.2) + # ax.scatter([obs_g.sim], [obs_g.obsval], marker='.', s=10, color='b') + for c, en in ensembles.items(): + en_g = en.loc[:, obs_g.obsnme] ex = en_g.max() en = en_g.min() - [ax.plot([ov,ov],[een,eex],color=c) for ov,een,eex in zip(obs_g.obsval.values,en.values,ex.values)] - - - ax.plot([mn,mx],[mn,mx],'k--',lw=1.0) - xlim = (mn,mx) - ax.set_xlim(mn,mx) - ax.set_ylim(mn,mx) + [ + ax.plot([ov, ov], [een, eex], color=c) + for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values) + ] + + ax.plot([mn, mx], [mn, mx], "k--", lw=1.0) + xlim = (mn, mx) + ax.set_xlim(mn, mx) + ax.set_ylim(mn, mx) if mx > 1.0e5: - ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%1.0e')) - ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%1.0e')) + ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%1.0e")) + ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%1.0e")) ax.grid() - ax.set_xlabel("observed",labelpad=0.1) - ax.set_ylabel("simulated",labelpad=0.1) - ax.set_title("{0}) group:{1}, {2} observations". - format(abet[ax_count], g, obs_g.shape[0]), loc="left") + ax.set_xlabel("observed", labelpad=0.1) + ax.set_ylabel("simulated", labelpad=0.1) + ax.set_title( + "{0}) group:{1}, {2} observations".format( + abet[ax_count], g, obs_g.shape[0] + ), + loc="left", + ) ax_count += 1 ax = axes[ax_count] - #ax.scatter(obs_g.obsval, obs_g.res, marker='.', s=10, color='b') + # ax.scatter(obs_g.obsval, obs_g.res, marker='.', s=10, color='b') if base_ensemble is not None: obs_gg = obs_g.sort_values(by="obsval") @@ -1267,14 +1415,17 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, en_g = en.loc[:, obs_gg.obsnme].subtract(obs_gg.obsval) ex = en_g.max() en = en_g.min() - #[ax.plot([ov, ov], [een, eex], color=c,alpha=0.3) for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values)] - ax.fill_between(obs_gg.obsval,en,ex,facecolor=c,alpha=0.2) + # [ax.plot([ov, ov], [een, eex], color=c,alpha=0.3) for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values)] + ax.fill_between(obs_gg.obsval, en, ex, facecolor=c, alpha=0.2) - for c,en in ensembles.items(): - en_g = en.loc[:,obs_g.obsnme].subtract(obs_g.obsval,axis=1) + for c, en in ensembles.items(): + en_g = en.loc[:, obs_g.obsnme].subtract(obs_g.obsval, axis=1) ex = en_g.max() en = en_g.min() - [ax.plot([ov,ov],[een,eex],color=c) for ov,een,eex in zip(obs_g.obsval.values,en.values,ex.values)] + [ + ax.plot([ov, ov], [een, eex], color=c) + for ov, een, eex in zip(obs_g.obsval.values, en.values, ex.values) + ] # if base_ensemble is not None: # if base_ensemble is not None: # for c, en in base_ensemble.items(): @@ -1288,17 +1439,21 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, if obs_g.shape[0] == 1: mx *= 1.1 ax.set_ylim(-mx, mx) - #show a zero residuals line - ax.plot(xlim, [0,0], 'k--', lw=1.0) + # show a zero residuals line + ax.plot(xlim, [0, 0], "k--", lw=1.0) ax.set_xlim(xlim) - ax.set_ylabel("residual",labelpad=0.1) - ax.set_xlabel("observed",labelpad=0.1) - ax.set_title("{0}) group:{1}, {2} observations". - format(abet[ax_count], g, obs_g.shape[0]), loc="left") + ax.set_ylabel("residual", labelpad=0.1) + ax.set_xlabel("observed", labelpad=0.1) + ax.set_title( + "{0}) group:{1}, {2} observations".format( + abet[ax_count], g, obs_g.shape[0] + ), + loc="left", + ) ax.grid() if ax.get_xlim()[1] > 1.0e5: - ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%1.0e')) + ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%1.0e")) ax_count += 1 @@ -1310,8 +1465,8 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, axes[a].set_xticks([]) plt.tight_layout() - #pdf.savefig() - #plt.close(fig) + # pdf.savefig() + # plt.close(fig) figs.append(fig) if filename is not None: plt.tight_layout() @@ -1325,10 +1480,9 @@ def ensemble_res_1to1(ensemble, pst,facecolor='0.5',logger=None,filename=None, return figs - - - -def plot_jac_test(csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1, outputdirectory=None): +def plot_jac_test( + csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1, outputdirectory=None +): """ helper function to plot results of the Jacobian test performed using the pest++ program pestpp-swp. @@ -1362,7 +1516,9 @@ def plot_jac_test(csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1 localhome = os.getcwd() # check if the output directory exists, if not make it - if outputdirectory is not None and not os.path.exists(os.path.join(localhome, outputdirectory)): + if outputdirectory is not None and not os.path.exists( + os.path.join(localhome, outputdirectory) + ): os.mkdir(os.path.join(localhome, outputdirectory)) if outputdirectory is None: figures_dir = localhome @@ -1370,25 +1526,25 @@ def plot_jac_test(csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1 figures_dir = os.path.join(localhome, outputdirectory) # read the input and output files into pandas dataframes - jactest_in_df = pd.read_csv(csvin, engine='python', index_col=0) - jactest_in_df.index.name = 'input_run_id' - jactest_out_df = pd.read_csv(csvout, engine='python', index_col=1) + jactest_in_df = pd.read_csv(csvin, engine="python", index_col=0) + jactest_in_df.index.name = "input_run_id" + jactest_out_df = pd.read_csv(csvout, engine="python", index_col=1) # subtract the base run from every row, leaves the one parameter that # was perturbed in any row as only non-zero value. Set zeros to nan # so round-off doesn't get us and sum across rows to get a column of # the perturbation for each row, finally extract to a series. First # the input csv and then the output. - base_par = jactest_in_df.loc['base'] - delta_par_df = jactest_in_df.subtract(base_par, axis='columns') + base_par = jactest_in_df.loc["base"] + delta_par_df = jactest_in_df.subtract(base_par, axis="columns") delta_par_df.replace(0, np.nan, inplace=True) - delta_par_df.drop('base', axis='index', inplace=True) - delta_par_df['change'] = delta_par_df.sum(axis='columns') - delta_par = pd.Series(delta_par_df['change']) + delta_par_df.drop("base", axis="index", inplace=True) + delta_par_df["change"] = delta_par_df.sum(axis="columns") + delta_par = pd.Series(delta_par_df["change"]) - base_obs = jactest_out_df.loc['base'] + base_obs = jactest_out_df.loc["base"] delta_obs = jactest_out_df.subtract(base_obs) - delta_obs.drop('base', axis='index', inplace=True) + delta_obs.drop("base", axis="index", inplace=True) # if targetobs is None, then reset it to all the observations. if targetobs is None: targetobs = jactest_out_df.columns.tolist()[8:] @@ -1396,25 +1552,25 @@ def plot_jac_test(csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1 # get the Jacobian by dividing the change in observation by the change in parameter # for the perturbed parameters - jacobian = delta_obs.divide(delta_par, axis='index') + jacobian = delta_obs.divide(delta_par, axis="index") # use the index created by build_jac_test_csv to get a column of parameter names # and increments, then we can plot derivative vs. increment for each parameter extr_df = pd.Series(jacobian.index.values).str.extract("(.+)(_\d+$)", expand=True) - extr_df[1] = pd.to_numeric(extr_df[1].str.replace('_', '')) + 1 - extr_df.rename(columns={0: 'parameter', 1: 'increment'}, inplace=True) + extr_df[1] = pd.to_numeric(extr_df[1].str.replace("_", "")) + 1 + extr_df.rename(columns={0: "parameter", 1: "increment"}, inplace=True) extr_df.index = jacobian.index # make a dataframe for plotting the Jacobian by combining the parameter name # and increments frame with the Jacobian frame - plotframe = pd.concat([extr_df, jacobian], axis=1, join='inner') + plotframe = pd.concat([extr_df, jacobian], axis=1, join="inner") # get a list of observations to keep based on maxoutputpages. if maxoutputpages > 10: print("WARNING, more than 10 pages of output requested per parameter") print("maxoutputpage reset to 10.") - maxoutputpages=10 - num_obs_plotted = np.min(np.array([maxoutputpages*32, len(targetobs)])) + maxoutputpages = 10 + num_obs_plotted = np.min(np.array([maxoutputpages * 32, len(targetobs)])) if num_obs_plotted < len(targetobs): # get random sample index_plotted = np.random.choice(len(targetobs), num_obs_plotted, replace=False) @@ -1422,28 +1578,36 @@ def plot_jac_test(csvin, csvout, targetobs=None, filetype=None, maxoutputpages=1 real_pages = maxoutputpages else: obs_plotted = targetobs - real_pages = int(targetobs/32) + 1 + real_pages = int(targetobs / 32) + 1 # make a subplot of derivative vs. increment one plot for each of the # observations in targetobs, and outputs grouped by parameter. - for param, group in plotframe.groupby('parameter'): + for param, group in plotframe.groupby("parameter"): for page in range(0, real_pages): fig, axes = plt.subplots(8, 4, sharex=True, figsize=(10, 15)) for row in range(0, 8): for col in range(0, 4): count = 32 * page + 4 * row + col if count < num_obs_plotted: - axes[row, col].scatter(group['increment'], group[obs_plotted[count]]) - axes[row, col].plot(group['increment'], group[obs_plotted[count]], 'r') + axes[row, col].scatter( + group["increment"], group[obs_plotted[count]] + ) + axes[row, col].plot( + group["increment"], group[obs_plotted[count]], "r" + ) axes[row, col].set_title(obs_plotted[count]) axes[row, col].set_xticks([1, 2, 3, 4, 5]) - axes[row, col].tick_params(direction='in') + axes[row, col].tick_params(direction="in") if row == 3: - axes[row, col].set_xlabel('Increment') + axes[row, col].set_xlabel("Increment") plt.tight_layout() if filetype is None: plt.show() else: - plt.savefig(os.path.join(figures_dir, "{0}_jactest_{1}.{2}".format(param, page, filetype))) + plt.savefig( + os.path.join( + figures_dir, "{0}_jactest_{1}.{2}".format(param, page, filetype) + ) + ) plt.close() diff --git a/pyemu/prototypes/__init__.py b/pyemu/prototypes/__init__.py index 97657ebd4..530cc2883 100644 --- a/pyemu/prototypes/__init__.py +++ b/pyemu/prototypes/__init__.py @@ -11,5 +11,3 @@ from .ensemble_method import * from .moouu import * from .da import * - - diff --git a/pyemu/prototypes/da.py b/pyemu/prototypes/da.py index 9f3bdf045..308811529 100644 --- a/pyemu/prototypes/da.py +++ b/pyemu/prototypes/da.py @@ -9,14 +9,36 @@ class EnsembleKalmanFilter(EnsembleMethod): - def __init__(self, pst, parcov = None, obscov = None, num_workers = 0, - submit_file = None, verbose = False, port = 4004, worker_dir = "template"): - super(EnsembleKalmanFilter, self).__init__(pst=pst, parcov=parcov, obscov=obscov, num_workers=num_workers, - submit_file=submit_file, verbose=verbose, port=port, - worker_dir=worker_dir) - - def initialize(self,num_reals=1,enforce_bounds="reset", - parensemble=None,obsensemble=None,restart_obsensemble=None): + def __init__( + self, + pst, + parcov=None, + obscov=None, + num_workers=0, + submit_file=None, + verbose=False, + port=4004, + worker_dir="template", + ): + super(EnsembleKalmanFilter, self).__init__( + pst=pst, + parcov=parcov, + obscov=obscov, + num_workers=num_workers, + submit_file=submit_file, + verbose=verbose, + port=port, + worker_dir=worker_dir, + ) + + def initialize( + self, + num_reals=1, + enforce_bounds="reset", + parensemble=None, + obsensemble=None, + restart_obsensemble=None, + ): """Initialize. Depending on arguments, draws or loads initial parameter observations ensembles and runs the initial parameter ensemble @@ -59,45 +81,55 @@ def initialize(self,num_reals=1,enforce_bounds="reset", if parensemble is not None and obsensemble is not None: self.logger.log("initializing with existing ensembles") - if isinstance(parensemble,str): + if isinstance(parensemble, str): self.logger.log("loading parensemble from file") if not os.path.exists(obsensemble): - self.logger.lraise("can not find parensemble file: {0}".\ - format(parensemble)) - df = pd.read_csv(parensemble,index_col=0) + self.logger.lraise( + "can not find parensemble file: {0}".format(parensemble) + ) + df = pd.read_csv(parensemble, index_col=0) df.columns = df.columns.str.lower() - #df.index = [str(i) for i in df.index] - self.parensemble_0 = pyemu.ParameterEnsemble.from_dataframe(df=df,pst=self.pst) + # df.index = [str(i) for i in df.index] + self.parensemble_0 = pyemu.ParameterEnsemble.from_dataframe( + df=df, pst=self.pst + ) self.logger.log("loading parensemble from file") - elif isinstance(parensemble,ParameterEnsemble): + elif isinstance(parensemble, ParameterEnsemble): self.parensemble_0 = parensemble.copy() else: - raise Exception("unrecognized arg type for parensemble, " +\ - "should be filename or ParameterEnsemble" +\ - ", not {0}".format(type(parensemble))) + raise Exception( + "unrecognized arg type for parensemble, " + + "should be filename or ParameterEnsemble" + + ", not {0}".format(type(parensemble)) + ) self.parensemble = self.parensemble_0.copy() - if isinstance(obsensemble,str): + if isinstance(obsensemble, str): self.logger.log("loading obsensemble from file") if not os.path.exists(obsensemble): - self.logger.lraise("can not find obsensemble file: {0}".\ - format(obsensemble)) - df = pd.read_csv(obsensemble,index_col=0) + self.logger.lraise( + "can not find obsensemble file: {0}".format(obsensemble) + ) + df = pd.read_csv(obsensemble, index_col=0) df.columns = df.columns.str.lower() - df = df.loc[:,self.pst.nnz_obs_names] - #df.index = [str(i) for i in df.index] - self.obsensemble_0 = pyemu.ObservationEnsemble.from_dataframe(df=df,pst=self.pst) + df = df.loc[:, self.pst.nnz_obs_names] + # df.index = [str(i) for i in df.index] + self.obsensemble_0 = pyemu.ObservationEnsemble.from_dataframe( + df=df, pst=self.pst + ) self.logger.log("loading obsensemble from file") - elif isinstance(obsensemble,ObservationEnsemble): + elif isinstance(obsensemble, ObservationEnsemble): self.obsensemble_0 = obsensemble.copy() else: - raise Exception("unrecognized arg type for obsensemble, " +\ - "should be filename or ObservationEnsemble" +\ - ", not {0}".format(type(obsensemble))) + raise Exception( + "unrecognized arg type for obsensemble, " + + "should be filename or ObservationEnsemble" + + ", not {0}".format(type(obsensemble)) + ) assert self.parensemble_0.shape[0] == self.obsensemble_0.shape[0] - #self.num_reals = self.parensemble_0.shape[0] + # self.num_reals = self.parensemble_0.shape[0] num_reals = self.parensemble.shape[0] self.logger.log("initializing with existing ensembles") @@ -105,46 +137,53 @@ def initialize(self,num_reals=1,enforce_bounds="reset", self.reset_parcov(self.parensemble.covariance_matrix()) if self.save_mats: - self.parcov.to_binary(self.pst.filename+".empcov.jcb") + self.parcov.to_binary(self.pst.filename + ".empcov.jcb") else: if build_empirical_prior: - self.logger.lraise("can't use build_emprirical_prior without parensemble...") + self.logger.lraise( + "can't use build_emprirical_prior without parensemble..." + ) self.logger.log("initializing with {0} realizations".format(num_reals)) self.logger.log("initializing parensemble") - self.parensemble_0 = pyemu.ParameterEnsemble.from_gaussian_draw(self.pst, - self.parcov,num_reals=num_reals) + self.parensemble_0 = pyemu.ParameterEnsemble.from_gaussian_draw( + self.pst, self.parcov, num_reals=num_reals + ) self.parensemble_0.enforce(enforce_bounds=enforce_bounds) self.logger.log("initializing parensemble") self.parensemble = self.parensemble_0.copy() - self.parensemble_0.to_csv(self.pst.filename +\ - self.paren_prefix.format(0)) + self.parensemble_0.to_csv(self.pst.filename + self.paren_prefix.format(0)) self.logger.log("initializing parensemble") self.logger.log("initializing obsensemble") - self.obsensemble_0 = pyemu.ObservationEnsemble.from_id_gaussian_draw(self.pst, - num_reals=num_reals) - #self.obsensemble = self.obsensemble_0.copy() + self.obsensemble_0 = pyemu.ObservationEnsemble.from_id_gaussian_draw( + self.pst, num_reals=num_reals + ) + # self.obsensemble = self.obsensemble_0.copy() # save the base obsensemble - self.obsensemble_0.to_csv(self.pst.filename +\ - self.obsen_prefix.format(-1)) + self.obsensemble_0.to_csv(self.pst.filename + self.obsen_prefix.format(-1)) self.logger.log("initializing obsensemble") self.logger.log("initializing with {0} realizations".format(num_reals)) - self.enforce_bounds = enforce_bounds if restart_obsensemble is not None: - self.logger.log("loading restart_obsensemble {0}".format(restart_obsensemble)) - #failed_runs,self.obsensemble = self._load_obs_ensemble(restart_obsensemble) + self.logger.log( + "loading restart_obsensemble {0}".format(restart_obsensemble) + ) + # failed_runs,self.obsensemble = self._load_obs_ensemble(restart_obsensemble) df = pd.read_csv(restart_obsensemble, index_col=0) df.columns = df.columns.str.lower() - #df = df.loc[:, self.pst.nnz_obs_names] + # df = df.loc[:, self.pst.nnz_obs_names] # df.index = [str(i) for i in df.index] - self.obsensemble = pyemu.ObservationEnsemble.from_dataframe(df=df, pst=self.pst) + self.obsensemble = pyemu.ObservationEnsemble.from_dataframe( + df=df, pst=self.pst + ) assert self.obsensemble.shape[0] == self.obsensemble_0.shape[0] assert list(self.obsensemble.columns) == list(self.obsensemble_0.columns) - self.logger.log("loading restart_obsensemble {0}".format(restart_obsensemble)) + self.logger.log( + "loading restart_obsensemble {0}".format(restart_obsensemble) + ) else: # run the initial parameter ensemble @@ -152,14 +191,13 @@ def initialize(self,num_reals=1,enforce_bounds="reset", self.obsensemble = self.forecast() self.logger.log("evaluating initial ensembles") - #if not self.parensemble.istransformed: + # if not self.parensemble.istransformed: self.parensemble._transform(inplace=True) - #if not self.parensemble_0.istransformed: + # if not self.parensemble_0.istransformed: self.parensemble_0._transform(inplace=True) self._initialized = True - - def forecast(self,parensemble=None): + def forecast(self, parensemble=None): """for the enkf formulation, this simply moves the ensemble forward by running the model once for each realization""" if parensemble is None: @@ -167,7 +205,6 @@ def forecast(self,parensemble=None): self.logger.log("evaluating ensemble") failed_runs, obsensemble = self._calc_obs(parensemble) - if failed_runs is not None: self.logger.warn("dropping failed realizations") parensemble.loc[failed_runs, :] = np.NaN @@ -183,35 +220,43 @@ def analysis(self): nz_names = self.pst.nnz_obs_names nreals = self.obsensemble.shape[0] - h_dash = pyemu.Matrix.from_dataframe(self.obsensemble.get_deviations().loc[:,nz_names].T) + h_dash = pyemu.Matrix.from_dataframe( + self.obsensemble.get_deviations().loc[:, nz_names].T + ) R = self.obscov - Chh = ((h_dash * h_dash.T) * (1.0 / nreals - 1)) + R + Chh = ((h_dash * h_dash.T) * (1.0 / nreals - 1)) + R - Cinv = Chh.pseudo_inv(maxsing=1,eigthresh=self.pst.svd_data.eigthresh) + Cinv = Chh.pseudo_inv(maxsing=1, eigthresh=self.pst.svd_data.eigthresh) - #Chh = None + # Chh = None - d_dash = pyemu.Matrix.from_dataframe(self.obsensemble_0.loc[self.obsensemble.index,nz_names] - self.obsensemble.loc[:,nz_names]).T + d_dash = pyemu.Matrix.from_dataframe( + self.obsensemble_0.loc[self.obsensemble.index, nz_names] + - self.obsensemble.loc[:, nz_names] + ).T - k_dash = pyemu.Matrix.from_dataframe(self.parensemble.get_deviations().loc[:,self.pst.adj_par_names]).T + k_dash = pyemu.Matrix.from_dataframe( + self.parensemble.get_deviations().loc[:, self.pst.adj_par_names] + ).T Chk = (k_dash * h_dash.T) * (1.0 / nreals - 1) Chk_dot_Cinv = Chk * Cinv - #Chk = None + # Chk = None upgrade = Chk_dot_Cinv * d_dash parensemble = self.parensemble.copy() upgrade = upgrade.to_dataframe().T upgrade.index = parensemble.index parensemble += upgrade - parensemble = pyemu.ParameterEnsemble.from_dataframe(df=parensemble,pst=self.pst,istransformed=True) + parensemble = pyemu.ParameterEnsemble.from_dataframe( + df=parensemble, pst=self.pst, istransformed=True + ) parensemble.enforce() return parensemble - def analysis_evensen(self): """Ayman here!!!. some peices that may be useful: self.parcov = parameter covariance matrix @@ -238,69 +283,78 @@ def analysis_evensen(self): nreals = self.obsensemble.shape[0] self.parensemble._transform() # nonzero weighted state deviations - HA_prime = self.obsensemble.get_deviations().loc[:,nz_names].T + HA_prime = self.obsensemble.get_deviations().loc[:, nz_names].T # obs noise pertubations - move to constuctor - E = (self.obsensemble_0.loc[:,nz_names] - self.pst.observation_data.obsval.loc[nz_names]).T - #print(E) + E = ( + self.obsensemble_0.loc[:, nz_names] + - self.pst.observation_data.obsval.loc[nz_names] + ).T + # print(E) # innovations: account for any failed runs (use obsensemble index) - D_prime = (self.obsensemble_0.loc[self.obsensemble.index,nz_names] - self.obsensemble.loc[:,nz_names]).T + D_prime = ( + self.obsensemble_0.loc[self.obsensemble.index, nz_names] + - self.obsensemble.loc[:, nz_names] + ).T - ES = HA_prime.loc[nz_names,E.columns] + E.loc[nz_names,:] + ES = HA_prime.loc[nz_names, E.columns] + E.loc[nz_names, :] assert ES.shape == ES.dropna().shape ES = pyemu.Matrix.from_dataframe(ES) - nrmin = min(self.pst.nnz_obs,self.obsensemble.shape[0]) - U,s,v = ES.pseudo_inv_components(maxsing=nrmin, - eigthresh=ES.get_maxsing(self.pst.svd_data.eigthresh), - truncate=True) + nrmin = min(self.pst.nnz_obs, self.obsensemble.shape[0]) + U, s, v = ES.pseudo_inv_components( + maxsing=nrmin, + eigthresh=ES.get_maxsing(self.pst.svd_data.eigthresh), + truncate=True, + ) - #half assed inverse + # half assed inverse s_inv = s.T - for i,sval in enumerate(np.diag(s.x)): - if sval == 0.0: - break - s_inv.x[i,i] = 1.0 / (sval * sval) + for i, sval in enumerate(np.diag(s.x)): + if sval == 0.0: + break + s_inv.x[i, i] = 1.0 / (sval * sval) X1 = s_inv * U.T - X1.autoalign = False #since the row/col names don't mean anything for singular components and everything is aligned + X1.autoalign = False # since the row/col names don't mean anything for singular components and everything is aligned - X2 = X1 * D_prime #these are aligned through the use of nz_names + X2 = X1 * D_prime # these are aligned through the use of nz_names - X3 = U * X2 #also aligned + X3 = U * X2 # also aligned X4 = pyemu.Matrix.from_dataframe(HA_prime.T) * X3 I = np.identity(X4.shape[0]) X5 = X4 + I - #print(X5.x.sum(axis=1)) - + # print(X5.x.sum(axis=1)) # deviations of adj pars - A_prime = pyemu.Matrix.from_dataframe(self.parensemble.get_deviations().loc[:,self.pst.adj_par_names]).T - + A_prime = pyemu.Matrix.from_dataframe( + self.parensemble.get_deviations().loc[:, self.pst.adj_par_names] + ).T upgrade = (A_prime * X4).to_dataframe().T assert upgrade.shape == upgrade.dropna().shape upgrade.index = self.parensemble.index - #print(upgrade) + # print(upgrade) parensemble = self.parensemble + upgrade - parensemble = pyemu.ParameterEnsemble.from_dataframe(df=parensemble,pst=self.pst,istransformed=True) + parensemble = pyemu.ParameterEnsemble.from_dataframe( + df=parensemble, pst=self.pst, istransformed=True + ) assert parensemble.shape == parensemble.dropna().shape return parensemble - def update(self): """update performs the analysis, then runs the forecast using the updated self.parensemble. This can be called repeatedly to iterate...""" parensemble = self.analysis_evensen() - obsensemble = self.forecast(parensemble=parensemble) + obsensemble = self.forecast(parensemble=parensemble) # todo: check for phi improvement if True: self.obsensemble = obsensemble @@ -309,26 +363,34 @@ def update(self): self.iter_num += 1 -class Assimilator(): - def __init__(self, type='Smoother', iterate=False, pst=None, mode='stochastic', options={}): +class Assimilator: + def __init__( + self, type="Smoother", iterate=False, pst=None, mode="stochastic", options={} + ): """ A clase to implement one or multiple update cycle. For the Ensemble smoother (ES), the update cycle includes all available observations. Ror Ensemble Kalman Filter (EnKF), the update is acheived on multiple cycles (time windows); and finally the nsemble Kalman Smoother (EnKS) updat parameters given all observations available up to a certain time """ - self.mode_options = ['Stochastic', 'Deterministic'] - self.type_options = ['Smoother', 'Kalman Filter', 'Kalman Smoother'] + self.mode_options = ["Stochastic", "Deterministic"] + self.type_options = ["Smoother", "Kalman Filter", "Kalman Smoother"] if not isinstance(iterate, bool): - raise ValueError("Error, 'iterate' must be boolian") # I think warning would better + raise ValueError( + "Error, 'iterate' must be boolian" + ) # I think warning would better self.iterate = iterate # If true Chen-Oliver scheme will be used, otherwise classical data assimilation will used. if type not in self.type_options: - raise ValueError("Assimilation type [{}] specified is not supported".format(type)) + raise ValueError( + "Assimilation type [{}] specified is not supported".format(type) + ) self.type = type if mode not in self.mode_options: - raise ValueError("Assimilation mode [{}] specified is not supported".format(mode)) + raise ValueError( + "Assimilation mode [{}] specified is not supported".format(mode) + ) self.mode = mode # Obtain problem setting from the pst object. @@ -372,15 +434,13 @@ def update(self, Pst): Solve the anlaysis step Xa = Xf + C'H(H'CH + R) (do - df) """ - if self.mode == 'stochastic': + if self.mode == "stochastic": pass else: # deterministic # Xa' = Xf' + C'H(H'CH + R) (do' - df') pass - - def run(self): """ @@ -415,7 +475,9 @@ def enkf(self): # Logging : Last Update cycle has finished break - print("Print information about this assimilation Cycle ???") # should be handeled in Logger + print( + "Print information about this assimilation Cycle ???" + ) # should be handeled in Logger # each cycle should have a dictionary of template files and instruction files to update the model inout # files @@ -441,7 +503,6 @@ def enks(self, pst): pass - def model_temporal_evolotion(self, time_index, cycle_files): """ - The function prepares the model for this time cycle @@ -456,17 +517,21 @@ def model_temporal_evolotion(self, time_index, cycle_files): """ for file in cycle_files: # generate log here about files being updated - self.update_inputfile(file[0], file[1]) # Jeremy: do we have something like this in python? + self.update_inputfile( + file[0], file[1] + ) # Jeremy: do we have something like this in python? if __name__ == "__main__": - sm = Assimilator(type='Smoother', iterate=False, mode='stochastic', pst='pst.control') + sm = Assimilator( + type="Smoother", iterate=False, mode="stochastic", pst="pst.control" + ) sm.run() # Ensemble Kalman Filter - kf = Assimilator(type='Kalman_filter', pst='pst.control') + kf = Assimilator(type="Kalman_filter", pst="pst.control") kf.run() # Kalman Smoother - ks = Assimilator(type='Kalman_smoother', pst='pst.control') + ks = Assimilator(type="Kalman_smoother", pst="pst.control") ks.run() diff --git a/pyemu/prototypes/ensemble_method.py b/pyemu/prototypes/ensemble_method.py index d02f379e2..bfa2fdd67 100644 --- a/pyemu/prototypes/ensemble_method.py +++ b/pyemu/prototypes/ensemble_method.py @@ -11,14 +11,13 @@ import numpy as np import pandas as pd import pyemu -from pyemu.en import ParameterEnsemble,ObservationEnsemble -from pyemu.mat import Cov,Matrix +from pyemu.en import ParameterEnsemble, ObservationEnsemble +from pyemu.mat import Cov, Matrix from pyemu.pst import Pst from ..logger import Logger - class EnsembleMethod(object): """Base class for ensemble-type methods. Should not be instantiated directly @@ -47,8 +46,18 @@ class EnsembleMethod(object): """ - def __init__(self,pst,parcov=None,obscov=None,num_workers=0,use_approx_prior=True, - submit_file=None,verbose=False,port=4004,worker_dir="template"): + def __init__( + self, + pst, + parcov=None, + obscov=None, + num_workers=0, + use_approx_prior=True, + submit_file=None, + verbose=False, + port=4004, + worker_dir="template", + ): self.logger = Logger(verbose) if verbose is not False: self.logger.echo = True @@ -66,18 +75,22 @@ def __init__(self,pst,parcov=None,obscov=None,num_workers=0,use_approx_prior=Tru self.paren_prefix = ".parensemble.{0:04d}.csv" self.obsen_prefix = ".obsensemble.{0:04d}.csv" - if isinstance(pst,str): + if isinstance(pst, str): pst = Pst(pst) - assert isinstance(pst,Pst) + assert isinstance(pst, Pst) self.pst = pst - self.sweep_in_csv = pst.pestpp_options.get("sweep_parameter_csv_file","sweep_in.csv") - self.sweep_out_csv = pst.pestpp_options.get("sweep_output_csv_file","sweep_out.csv") + self.sweep_in_csv = pst.pestpp_options.get( + "sweep_parameter_csv_file", "sweep_in.csv" + ) + self.sweep_out_csv = pst.pestpp_options.get( + "sweep_output_csv_file", "sweep_out.csv" + ) if parcov is not None: - assert isinstance(parcov,Cov) + assert isinstance(parcov, Cov) else: parcov = Cov.from_parameter_data(self.pst) if obscov is not None: - assert isinstance(obscov,Cov) + assert isinstance(obscov, Cov) else: obscov = Cov.from_observation_data(pst) @@ -89,23 +102,25 @@ def __init__(self,pst,parcov=None,obscov=None,num_workers=0,use_approx_prior=Tru self.total_runs = 0 self.raw_sweep_out = None - def initialize(self,*args,**kwargs): - raise Exception("EnsembleMethod.initialize() must be implemented by the derived types") + def initialize(self, *args, **kwargs): + raise Exception( + "EnsembleMethod.initialize() must be implemented by the derived types" + ) - def _calc_delta(self,ensemble,scaling_matrix=None): - ''' + def _calc_delta(self, ensemble, scaling_matrix=None): + """ calc the scaled ensemble differences from the mean - ''' + """ mean = np.array(ensemble.mean(axis=0)) delta = ensemble.as_pyemu_matrix() for i in range(ensemble.shape[0]): - delta.x[i,:] -= mean + delta.x[i, :] -= mean if scaling_matrix is not None: delta = scaling_matrix * delta.T - delta *= (1.0 / np.sqrt(float(ensemble.shape[0] - 1.0))) + delta *= 1.0 / np.sqrt(float(ensemble.shape[0] - 1.0)) return delta - def _calc_obs(self,parensemble): + def _calc_obs(self, parensemble): self.logger.log("removing existing sweep in/out files") try: os.remove(self.sweep_in_csv) @@ -114,12 +129,16 @@ def _calc_obs(self,parensemble): try: os.remove(self.sweep_out_csv) except Exception as e: - self.logger.warn("error removing existing sweep out file:{0}".format(str(e))) + self.logger.warn( + "error removing existing sweep out file:{0}".format(str(e)) + ) self.logger.log("removing existing sweep in/out files") if parensemble.isnull().values.any(): parensemble.to_csv("_nan.csv") - self.logger.lraise("_calc_obs() error: NaNs in parensemble (written to '_nan.csv')") + self.logger.lraise( + "_calc_obs() error: NaNs in parensemble (written to '_nan.csv')" + ) if self.submit_file is None: self._calc_obs_local(parensemble) @@ -133,28 +152,33 @@ def _calc_obs(self,parensemble): # shutil.copy2(self.sweep_out_csv,sweep_out) self.logger.log("reading sweep out csv {0}".format(self.sweep_out_csv)) - failed_runs,obs = self._load_obs_ensemble(self.sweep_out_csv) + failed_runs, obs = self._load_obs_ensemble(self.sweep_out_csv) self.logger.log("reading sweep out csv {0}".format(self.sweep_out_csv)) self.total_runs += obs.shape[0] self.logger.statement("total runs:{0}".format(self.total_runs)) - return failed_runs,obs + return failed_runs, obs - def _load_obs_ensemble(self,filename): + def _load_obs_ensemble(self, filename): if not os.path.exists(filename): self.logger.lraise("obsensemble file {0} does not exists".format(filename)) obs = pd.read_csv(filename) obs.columns = [item.lower() for item in obs.columns] - self.raw_sweep_out = obs.copy() # save this for later to support restart - assert "input_run_id" in obs.columns,\ - "'input_run_id' col missing...need newer version of sweep" + self.raw_sweep_out = obs.copy() # save this for later to support restart + assert ( + "input_run_id" in obs.columns + ), "'input_run_id' col missing...need newer version of sweep" obs.index = obs.input_run_id failed_runs = None if 1 in obs.failed_flag.values: failed_runs = obs.loc[obs.failed_flag == 1].index.values - self.logger.warn("{0} runs failed (indices: {1})".\ - format(len(failed_runs),','.join([str(f) for f in failed_runs]))) - obs = ObservationEnsemble.from_dataframe(df=obs.loc[:,self.obscov.row_names], - pst=self.pst) + self.logger.warn( + "{0} runs failed (indices: {1})".format( + len(failed_runs), ",".join([str(f) for f in failed_runs]) + ) + ) + obs = ObservationEnsemble.from_dataframe( + df=obs.loc[:, self.obscov.row_names], pst=self.pst + ) if obs.isnull().values.any(): self.logger.lraise("_calc_obs() error: NaNs in obsensemble") return failed_runs, obs @@ -162,52 +186,69 @@ def _load_obs_ensemble(self,filename): def _get_master_thread(self): master_stdout = "_master_stdout.dat" master_stderr = "_master_stderr.dat" + def master(): try: - #os.system("sweep {0} /h :{1} 1>{2} 2>{3}". \ + # os.system("sweep {0} /h :{1} 1>{2} 2>{3}". \ # format(self.pst.filename, self.port, master_stdout, master_stderr)) - pyemu.os_utils.run("pestpp-swp {0} /h :{1} 1>{2} 2>{3}". \ - format(self.pst.filename, self.port, master_stdout, master_stderr)) + pyemu.os_utils.run( + "pestpp-swp {0} /h :{1} 1>{2} 2>{3}".format( + self.pst.filename, self.port, master_stdout, master_stderr + ) + ) except Exception as e: self.logger.lraise("error starting condor master: {0}".format(str(e))) - with open(master_stderr, 'r') as f: + with open(master_stderr, "r") as f: err_lines = f.readlines() if len(err_lines) > 0: - self.logger.warn("master stderr lines: {0}". - format(','.join([l.strip() for l in err_lines]))) + self.logger.warn( + "master stderr lines: {0}".format( + ",".join([l.strip() for l in err_lines]) + ) + ) master_thread = threading.Thread(target=master) master_thread.start() time.sleep(2.0) return master_thread - def _calc_obs_condor(self,parensemble): - self.logger.log("evaluating ensemble of size {0} with htcondor".\ - format(parensemble.shape[0])) + def _calc_obs_condor(self, parensemble): + self.logger.log( + "evaluating ensemble of size {0} with htcondor".format(parensemble.shape[0]) + ) parensemble.to_csv(self.sweep_in_csv) master_thread = self._get_master_thread() condor_temp_file = "_condor_submit_stdout.dat" condor_err_file = "_condor_submit_stderr.dat" - self.logger.log("calling condor_submit with submit file {0}".format(self.submit_file)) + self.logger.log( + "calling condor_submit with submit file {0}".format(self.submit_file) + ) try: - os.system("condor_submit {0} 1>{1} 2>{2}".\ - format(self.submit_file,condor_temp_file,condor_err_file)) + os.system( + "condor_submit {0} 1>{1} 2>{2}".format( + self.submit_file, condor_temp_file, condor_err_file + ) + ) except Exception as e: self.logger.lraise("error in condor_submit: {0}".format(str(e))) - self.logger.log("calling condor_submit with submit file {0}".format(self.submit_file)) - time.sleep(2.0) #some time for condor to submit the job and echo to stdout + self.logger.log( + "calling condor_submit with submit file {0}".format(self.submit_file) + ) + time.sleep(2.0) # some time for condor to submit the job and echo to stdout condor_submit_string = "submitted to cluster" - with open(condor_temp_file,'r') as f: + with open(condor_temp_file, "r") as f: lines = f.readlines() - self.logger.statement("condor_submit stdout: {0}".\ - format(','.join([l.strip() for l in lines]))) - with open(condor_err_file,'r') as f: + self.logger.statement( + "condor_submit stdout: {0}".format(",".join([l.strip() for l in lines])) + ) + with open(condor_err_file, "r") as f: err_lines = f.readlines() if len(err_lines) > 0: - self.logger.warn("stderr from condor_submit:{0}".\ - format([l.strip() for l in err_lines])) + self.logger.warn( + "stderr from condor_submit:{0}".format([l.strip() for l in err_lines]) + ) cluster_number = None for line in lines: if condor_submit_string in line.lower(): @@ -220,30 +261,43 @@ def _calc_obs_condor(self,parensemble): self.logger.log("calling condor_rm on cluster {0}".format(cluster_number)) os.system("condor_rm cluster {0}".format(cluster_number)) self.logger.log("calling condor_rm on cluster {0}".format(cluster_number)) - self.logger.log("evaluating ensemble of size {0} with htcondor".\ - format(parensemble.shape[0])) - + self.logger.log( + "evaluating ensemble of size {0} with htcondor".format(parensemble.shape[0]) + ) - def _calc_obs_local(self,parensemble): - ''' + def _calc_obs_local(self, parensemble): + """ propagate the ensemble forward using sweep. - ''' - self.logger.log("evaluating ensemble of size {0} locally with sweep".\ - format(parensemble.shape[0])) + """ + self.logger.log( + "evaluating ensemble of size {0} locally with sweep".format( + parensemble.shape[0] + ) + ) parensemble.to_csv(self.sweep_in_csv) if self.num_workers > 0: master_thread = self._get_master_thread() - pyemu.utils.start_workers(self.worker_dir,"pestpp-swp",self.pst.filename, - self.num_workers,worker_root='..',port=self.port) + pyemu.utils.start_workers( + self.worker_dir, + "pestpp-swp", + self.pst.filename, + self.num_workers, + worker_root="..", + port=self.port, + ) master_thread.join() else: os.system("pestpp-swp {0}".format(self.pst.filename)) - self.logger.log("evaluating ensemble of size {0} locally with sweep".\ - format(parensemble.shape[0])) - - - def update(self,lambda_mults=[1.0],localizer=None,run_subset=None,use_approx=True): - raise Exception("EnsembleMethod.update() must be implemented by the derived types") - - + self.logger.log( + "evaluating ensemble of size {0} locally with sweep".format( + parensemble.shape[0] + ) + ) + + def update( + self, lambda_mults=[1.0], localizer=None, run_subset=None, use_approx=True + ): + raise Exception( + "EnsembleMethod.update() must be implemented by the derived types" + ) diff --git a/pyemu/prototypes/moouu.py b/pyemu/prototypes/moouu.py index 61a2813aa..7dc7e541a 100644 --- a/pyemu/prototypes/moouu.py +++ b/pyemu/prototypes/moouu.py @@ -16,11 +16,11 @@ def __init__(self, pst, obj_function_dict, logger): self.logger = logger self.pst = pst - self.max_distance = 1.0e+30 + self.max_distance = 1.0e30 obs = pst.observation_data pi = pst.prior_information self.obs_dict, self.pi_dict = {}, {} - for name,direction in obj_function_dict.items(): + for name, direction in obj_function_dict.items(): if name in obs.obsnme: if direction.lower().startswith("max"): @@ -28,38 +28,47 @@ def __init__(self, pst, obj_function_dict, logger): elif direction.lower().startswith("min"): self.obs_dict[name] = "min" else: - self.logger.lraise("unrecognized direction for obs obj func {0}:'{1}'".\ - format(name,direction)) - elif name in pi.pilbl: + self.logger.lraise( + "unrecognized direction for obs obj func {0}:'{1}'".format( + name, direction + ) + ) + elif name in pi.pilbl: if direction.lower().startswith("max"): self.pi_dict[name] = "max" elif direction.lower().startswith("min"): self.pi_dict[name] = "min" else: - self.logger.lraise("unrecognized direction for pi obj func {0}:'{1}'".\ - format(name,direction)) + self.logger.lraise( + "unrecognized direction for pi obj func {0}:'{1}'".format( + name, direction + ) + ) else: self.logger.lraise("objective function not found:{0}".format(name)) if len(self.pi_dict) > 0: self.logger.lraise("pi obj function not yet supported") - self.logger.statement("{0} obs objective functions registered".\ - format(len(self.obs_dict))) - for name,direction in self.obs_dict.items(): - self.logger.statement("obs obj function: {0}, direction: {1}".\ - format(name,direction)) - - self.logger.statement("{0} pi objective functions registered". \ - format(len(self.pi_dict))) + self.logger.statement( + "{0} obs objective functions registered".format(len(self.obs_dict)) + ) + for name, direction in self.obs_dict.items(): + self.logger.statement( + "obs obj function: {0}, direction: {1}".format(name, direction) + ) + + self.logger.statement( + "{0} pi objective functions registered".format(len(self.pi_dict)) + ) for name, direction in self.pi_dict.items(): - self.logger.statement("pi obj function: {0}, direction: {1}". \ - format(name, direction)) + self.logger.statement( + "pi obj function: {0}, direction: {1}".format(name, direction) + ) self.is_nondominated = self.is_nondominated_continuous self.obs_obj_names = list(self.obs_dict.keys()) - def is_feasible(self, obs_df, risk=0.5): """identify which candidate solutions in obs_df (rows) are feasible with respect obs constraints (obs_df) @@ -84,19 +93,18 @@ def is_feasible(self, obs_df, risk=0.5): is_feasible = pd.Series(data=True, index=obs_df.index) for lt_obs in self.pst.less_than_obs_constraints: if risk != 0.5: - val = self.get_risk_shifted_value(risk,obs_df.loc[lt_obs]) + val = self.get_risk_shifted_value(risk, obs_df.loc[lt_obs]) else: - val = self.pst.observation_data.loc[lt_obs,"obsval"] - is_feasible.loc[obs_df.loc[:,lt_obs]>=val] = False + val = self.pst.observation_data.loc[lt_obs, "obsval"] + is_feasible.loc[obs_df.loc[:, lt_obs] >= val] = False for gt_obs in self.pst.greater_than_obs_constraints: if risk != 0.5: - val = self.get_risk_shifted_value(risk,obs_df.loc[gt_obs]) + val = self.get_risk_shifted_value(risk, obs_df.loc[gt_obs]) else: - val = self.pst.observation_data.loc[gt_obs,"obsval"] - is_feasible.loc[obs_df.loc[:,gt_obs] <= val] = False + val = self.pst.observation_data.loc[gt_obs, "obsval"] + is_feasible.loc[obs_df.loc[:, gt_obs] <= val] = False return is_feasible - @property def obs_obj_signs(self): signs = [] @@ -108,8 +116,7 @@ def obs_obj_signs(self): signs = np.array(signs) return signs - - def dominates(self,sol1,sol2): + def dominates(self, sol1, sol2): d = self.obs_obj_signs * (sol1 - sol2) if np.all(d >= 0.0) and np.any(d > 0.0): return True @@ -129,9 +136,9 @@ def is_nondominated_pathetic(self, obs_df): is_dominated : pandas.Series series with index of obs_df and bool series """ - obj_df = obs_df.loc[:,self.obs_obj_names] + obj_df = obs_df.loc[:, self.obs_obj_names] is_nondom = [] - for i,iidx in enumerate(obj_df.index): + for i, iidx in enumerate(obj_df.index): ind = True for jidx in obj_df.index: if iidx == jidx: @@ -139,12 +146,12 @@ def is_nondominated_pathetic(self, obs_df): # if dominates(jidx,iidx): # ind = False # break - if self.dominates(obj_df.loc[jidx,:], obj_df.loc[iidx,:]): + if self.dominates(obj_df.loc[jidx, :], obj_df.loc[iidx, :]): ind = False break is_nondom.append(ind) - is_nondom = pd.Series(data=is_nondom,index=obs_df.index,dtype=bool) + is_nondom = pd.Series(data=is_nondom, index=obs_df.index, dtype=bool) return is_nondom def is_nondominated_continuous(self, obs_df): @@ -162,13 +169,13 @@ def is_nondominated_continuous(self, obs_df): series with index of obs_df and bool series """ - obj_df = obs_df.loc[:,self.obs_obj_names] + obj_df = obs_df.loc[:, self.obs_obj_names] P = list(obj_df.index) PP = set() PP.add(P[0]) - #iidx = 1 - #while iidx < len(P): + # iidx = 1 + # while iidx < len(P): for iidx in P: jidx = 0 drop = [] @@ -190,16 +197,12 @@ def is_nondominated_continuous(self, obs_df): PP.remove(d) if keep: PP.add(iidx) - #iidx += 1 - - + # iidx += 1 - - is_nondom = pd.Series(data=False,index=obs_df.index,dtype=bool) + is_nondom = pd.Series(data=False, index=obs_df.index, dtype=bool) is_nondom.loc[PP] = True return is_nondom - def is_nondominated_kung(self, obs_df): """identify which candidate solutions are pareto non-dominated using Kungs algorithm @@ -214,19 +217,21 @@ def is_nondominated_kung(self, obs_df): series with index of obs_df and bool series """ - obj_df = obs_df.loc[:,self.obs_obj_names] + obj_df = obs_df.loc[:, self.obs_obj_names] obj_names = self.obs_obj_names ascending = False if self.obs_dict[obj_names[0]] == "min": ascending = True - obj_df.sort_values(by=obj_names[0],ascending=ascending,inplace=True) + obj_df.sort_values(by=obj_names[0], ascending=ascending, inplace=True) P = list(obj_df.index) def front(p): if len(p) == 1: return p - p = list(obj_df.loc[p,:].sort_values(by=obj_names[0],ascending=ascending).index) + p = list( + obj_df.loc[p, :].sort_values(by=obj_names[0], ascending=ascending).index + ) half = int(len(p) / 2) T = front(p[:half]) B = front(p[half:]) @@ -235,24 +240,22 @@ def front(p): while i < len(B): j = 0 while j < len(T): - #if dominates(T[j],B[i]): - if self.dominates(obj_df.loc[T[j],:], obj_df.loc[B[i],:]): + # if dominates(T[j],B[i]): + if self.dominates(obj_df.loc[T[j], :], obj_df.loc[B[i], :]): break j += 1 - if (j == len(T)): + if j == len(T): M.append(B[i]) i += 1 T.extend(M) return T - PP = front(P) - is_nondom = pd.Series(data=False,index=obs_df.index,dtype=bool) + is_nondom = pd.Series(data=False, index=obs_df.index, dtype=bool) is_nondom.loc[PP] = True return is_nondom - - def crowd_distance(self,obs_df): + def crowd_distance(self, obs_df): """determine the crowding distance for each candidate solution Parameters @@ -267,14 +270,14 @@ def crowd_distance(self,obs_df): """ # initialize the distance container - crowd_distance = pd.Series(data=0.0,index=obs_df.index) + crowd_distance = pd.Series(data=0.0, index=obs_df.index) - for name,direction in self.obs_dict.items(): + for name, direction in self.obs_dict.items(): # make a copy - wasteful, but easier - obj_df = obs_df.loc[:,name].copy() + obj_df = obs_df.loc[:, name].copy() # sort so that largest values are first - obj_df.sort_values(ascending=False,inplace=True) + obj_df.sort_values(ascending=False, inplace=True) # set the ends so they are always retained crowd_distance.loc[obj_df.index[0]] += self.max_distance @@ -283,13 +286,12 @@ def crowd_distance(self,obs_df): # process the vector i = 1 for idx in obj_df.index[1:-1]: - crowd_distance.loc[idx] += obj_df.iloc[i-1] - obj_df.iloc[i+1] + crowd_distance.loc[idx] += obj_df.iloc[i - 1] - obj_df.iloc[i + 1] i += 1 return crowd_distance - - def get_risk_shifted_value(self,risk,series): + def get_risk_shifted_value(self, risk, series): n = series.name if n in self.obs_dict.keys(): @@ -302,8 +304,11 @@ def get_risk_shifted_value(self,risk,series): d = "max" t = "gt_obs" else: - self.logger.lraise("series is not an obs obj func or obs inequality contraint:{0}".\ - format(n)) + self.logger.lraise( + "series is not an obs obj func or obs inequality contraint:{0}".format( + n + ) + ) ascending = False if d == "min": @@ -315,12 +320,12 @@ def get_risk_shifted_value(self,risk,series): cdf = series.sort_values(ascending=ascending).apply(np.cumsum) val = float(cdf.iloc[shift]) - #print(cdf) - #print(shift,cdf.iloc[shift]) - #self.logger.statement("risk-shift for {0}->type:{1}dir:{2},shift:{3},val:{4}".format(n,t,d,shift,val)) + # print(cdf) + # print(shift,cdf.iloc[shift]) + # self.logger.statement("risk-shift for {0}->type:{1}dir:{2},shift:{3},val:{4}".format(n,t,d,shift,val)) return val - def reduce_stack_with_risk_shift(self,oe,num_reals,risk): + def reduce_stack_with_risk_shift(self, oe, num_reals, risk): stochastic_cols = list(self.obs_dict.keys()) stochastic_cols.extend(self.pst.less_than_obs_constraints) @@ -328,32 +333,56 @@ def reduce_stack_with_risk_shift(self,oe,num_reals,risk): stochastic_cols = set(stochastic_cols) vvals = [] - for i in range(0,oe.shape[0],num_reals): - oes = oe.iloc[i:i+num_reals] + for i in range(0, oe.shape[0], num_reals): + oes = oe.iloc[i : i + num_reals] vals = [] for col in oes.columns: if col in stochastic_cols: - val = self.get_risk_shifted_value(risk=risk,series=oes.loc[:,col]) + val = self.get_risk_shifted_value(risk=risk, series=oes.loc[:, col]) # otherwise, just fill with the mean value else: - val = oes.loc[:,col].mean() + val = oes.loc[:, col].mean() vals.append(val) vvals.append(vals) - df = pd.DataFrame(data=vvals,columns=oe.columns) + df = pd.DataFrame(data=vvals, columns=oe.columns) return df class EvolAlg(EnsembleMethod): - def __init__(self, pst, parcov = None, obscov = None, num_workers = 0, use_approx_prior = True, - submit_file = None, verbose = False, port = 4004, worker_dir = "template"): - super(EvolAlg, self).__init__(pst=pst, parcov=parcov, obscov=obscov, num_workers=num_workers, - submit_file=submit_file, verbose=verbose, port=port, - worker_dir=worker_dir) - - - def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, - dv_ensemble=None,par_ensemble=None,risk=0.5, - dv_names=None,par_names=None): + def __init__( + self, + pst, + parcov=None, + obscov=None, + num_workers=0, + use_approx_prior=True, + submit_file=None, + verbose=False, + port=4004, + worker_dir="template", + ): + super(EvolAlg, self).__init__( + pst=pst, + parcov=parcov, + obscov=obscov, + num_workers=num_workers, + submit_file=submit_file, + verbose=verbose, + port=port, + worker_dir=worker_dir, + ) + + def initialize( + self, + obj_func_dict, + num_par_reals=100, + num_dv_reals=100, + dv_ensemble=None, + par_ensemble=None, + risk=0.5, + dv_names=None, + par_names=None, + ): # todo : setup a run results store for all candidate solutions? or maybe # just nondom, feasible solutions? @@ -366,7 +395,7 @@ def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, if risk > 1.0 or risk < 0.0: self.logger.lraise("risk not in 0.0:1.0 range") self.risk = risk - self.obj_func = ParetoObjFunc(self.pst,obj_func_dict, self.logger) + self.obj_func = ParetoObjFunc(self.pst, obj_func_dict, self.logger) self.par_ensemble = None @@ -378,30 +407,37 @@ def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, dvset = set(dv_names) diff = dvset - aset if len(diff) > 0: - self.logger.lraise("the following dv_names were not " + \ - "found in the adjustable parameters: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following dv_names were not " + + "found in the adjustable parameters: {0}".format( + ",".join(diff) + ) + ) how = {p: "uniform" for p in dv_names} else: if risk != 0.5: - self.logger.lraise("risk != 0.5 but all adjustable pars are dec vars") + self.logger.lraise( + "risk != 0.5 but all adjustable pars are dec vars" + ) how = {p: "uniform" for p in self.pst.adj_par_names} - self.dv_ensemble = pyemu.ParameterEnsemble.from_mixed_draws(self.pst, how_dict=how, - num_reals=num_dv_reals, cov=self.parcov) + self.dv_ensemble = pyemu.ParameterEnsemble.from_mixed_draws( + self.pst, how_dict=how, num_reals=num_dv_reals, cov=self.parcov + ) if risk != 0.5: aset = set(self.pst.adj_par_names) dvset = set(self.dv_ensemble.columns) diff = aset - dvset if len(diff) > 0: - self.logger.lraise("risk!=0.5 but all adjustable parameters are dec vars") - self.par_ensemble = pyemu.ParameterEnsemble.from_gaussian_draw(self.pst, - num_reals=num_par_reals, - cov=self.parcov) + self.logger.lraise( + "risk!=0.5 but all adjustable parameters are dec vars" + ) + self.par_ensemble = pyemu.ParameterEnsemble.from_gaussian_draw( + self.pst, num_reals=num_par_reals, cov=self.parcov + ) else: self.par_ensemble = None - # both par ensemble and dv ensemble were passed elif par_ensemble is not None and dv_ensemble is not None: self.num_dv_reals = dv_ensemble.shape[0] @@ -411,18 +447,19 @@ def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, pset = set(par_ensemble.columns) diff = ppset - aset if len(diff) > 0: - self.logger.lraise("the following par_ensemble names were not " + \ - "found in the pst par names: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following par_ensemble names were not " + + "found in the pst par names: {0}".format(",".join(diff)) + ) if len(diff) > 0: - self.logger.lraise("the following dv_ensemble names were not " + \ - "found in the adjustable parameters: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following dv_ensemble names were not " + + "found in the adjustable parameters: {0}".format(",".join(diff)) + ) self.par_ensemble = par_ensemble self.dv_ensemble = dv_ensemble - # dv_ensemble supplied, but not pars, so check if any adjustable pars are not # in dv_ensemble, and if so, draw reals for them elif dv_ensemble is not None and par_ensemble is None: @@ -431,34 +468,44 @@ def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, dvset = set(dv_ensemble.columns) diff = dvset - aset if len(diff) > 0: - self.logger.lraise("the following dv_ensemble names were not " + \ - "found in the adjustable parameters: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following dv_ensemble names were not " + + "found in the adjustable parameters: {0}".format(",".join(diff)) + ) self.dv_ensemble = dv_ensemble if risk != 0.5: if par_names is not None: pset = set(par_names) diff = pset - aset if len(diff) > 0: - self.logger.lraise("the following par_names were not " + \ - "found in the adjustable parameters: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following par_names were not " + + "found in the adjustable parameters: {0}".format( + ",".join(diff) + ) + ) how = {p: "gaussian" for p in par_names} else: adj_pars = aset - dvset if len(adj_pars) == 0: - self.logger.lraise("risk!=0.5 but all adjustable pars are dec vars") - how = {p:"gaussian" for p in adj_pars} - self.par_ensemble = pyemu.ParameterEnsemble.from_mixed_draws(self.pst,how_dict=how, - num_reals=num_par_reals,cov=self.parcov) + self.logger.lraise( + "risk!=0.5 but all adjustable pars are dec vars" + ) + how = {p: "gaussian" for p in adj_pars} + self.par_ensemble = pyemu.ParameterEnsemble.from_mixed_draws( + self.pst, how_dict=how, num_reals=num_par_reals, cov=self.parcov + ) else: diff = aset - dvset if len(diff) > 0: - self.logger.warn("adj pars {0} missing from dv_ensemble".\ - format(','.join(diff))) - df = pd.DataFrame(self.pst.parameter_data.loc[:,"parval1"]).T - - self.par_ensemble = pyemu.ParameterEnsemble.from_dataframe(df=df,pst=self.pst) + self.logger.warn( + "adj pars {0} missing from dv_ensemble".format(",".join(diff)) + ) + df = pd.DataFrame(self.pst.parameter_data.loc[:, "parval1"]).T + + self.par_ensemble = pyemu.ParameterEnsemble.from_dataframe( + df=df, pst=self.pst + ) print(self.par_ensemble.shape) # par ensemble supplied but not dv_ensmeble, so check for any adjustable pars @@ -469,145 +516,180 @@ def initialize(self,obj_func_dict,num_par_reals=100,num_dv_reals=100, pset = set(par_ensemble.columns) diff = aset - pset if len(diff) > 0: - self.logger.lraise("the following par_ensemble names were not " + \ - "found in the pst par names: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following par_ensemble names were not " + + "found in the pst par names: {0}".format(",".join(diff)) + ) self.par_ensemble = par_ensemble if dv_names is None: - self.logger.lraise("dv_names must be passed if dv_ensemble is None and par_ensmeble is not None") + self.logger.lraise( + "dv_names must be passed if dv_ensemble is None and par_ensmeble is not None" + ) dvset = set(dv_names) diff = dvset - aset if len(diff) > 0: - self.logger.lraise("the following dv_names were not " + \ - "found in the adjustable parameters: {0}". \ - format(",".join(diff))) + self.logger.lraise( + "the following dv_names were not " + + "found in the adjustable parameters: {0}".format(",".join(diff)) + ) how = {p: "uniform" for p in dv_names} - self.dv_ensemble = pyemu.ParameterEnsemble.from_mixed_draws(self.pst, how_dict=how, - num_reals=num_dv_reals, - cov=self.parcov,partial=True) - + self.dv_ensemble = pyemu.ParameterEnsemble.from_mixed_draws( + self.pst, + how_dict=how, + num_reals=num_dv_reals, + cov=self.parcov, + partial=True, + ) self.last_stack = None - self.logger.log("evaluate initial dv ensemble of size {0}".format(self.dv_ensemble.shape[0])) + self.logger.log( + "evaluate initial dv ensemble of size {0}".format(self.dv_ensemble.shape[0]) + ) self.obs_ensemble = self._calc_obs(self.dv_ensemble) - self.logger.log("evaluate initial dv ensemble of size {0}".format(self.dv_ensemble.shape[0])) - + self.logger.log( + "evaluate initial dv ensemble of size {0}".format(self.dv_ensemble.shape[0]) + ) - isfeas = self.obj_func.is_feasible(self.obs_ensemble,risk=self.risk) + isfeas = self.obj_func.is_feasible(self.obs_ensemble, risk=self.risk) isnondom = self.obj_func.is_nondominated(self.obs_ensemble) vc = isfeas.value_counts() if True not in vc: self.logger.lraise("no feasible solutions in initial population") - self.logger.statement("{0} feasible individuals in initial population".format(vc[True])) - self.dv_ensemble = self.dv_ensemble.loc[isfeas,:] - self.obs_ensemble = self.obs_ensemble.loc[isfeas,:] + self.logger.statement( + "{0} feasible individuals in initial population".format(vc[True]) + ) + self.dv_ensemble = self.dv_ensemble.loc[isfeas, :] + self.obs_ensemble = self.obs_ensemble.loc[isfeas, :] vc = isnondom.value_counts() if True in vc: - self.logger.statement("{0} nondominated solutions in initial population".format(vc[True])) + self.logger.statement( + "{0} nondominated solutions in initial population".format(vc[True]) + ) else: self.logger.statement("no nondominated solutions in initial population") - self.dv_ensemble = self.dv_ensemble.loc[isfeas,:] - self.obs_ensemble = self.obs_ensemble.loc[isfeas,:] - + self.dv_ensemble = self.dv_ensemble.loc[isfeas, :] + self.obs_ensemble = self.obs_ensemble.loc[isfeas, :] self.pst.add_transform_columns() - - self._initialized = True - @staticmethod - def _drop_failed(failed_runs, dv_ensemble,obs_ensemble): + def _drop_failed(failed_runs, dv_ensemble, obs_ensemble): if failed_runs is None: return - dv_ensemble.loc[failed_runs,:] = np.NaN + dv_ensemble.loc[failed_runs, :] = np.NaN dv_ensemble = dv_ensemble.dropna(axis=1) - obs_ensemble.loc[failed_runs,:] = np.NaN + obs_ensemble.loc[failed_runs, :] = np.NaN obs_ensemble = obs_ensemble.dropna(axis=1) - self.logger.statement("dropped {0} failed runs, {1} remaining".\ - format(len(failed_runs),dv_ensemble.shape[0])) + self.logger.statement( + "dropped {0} failed runs, {1} remaining".format( + len(failed_runs), dv_ensemble.shape[0] + ) + ) - def _archive(self,dv_ensemble,obs_ensemble): + def _archive(self, dv_ensemble, obs_ensemble): self.logger.log("archiving {0} solutions".format(dv_ensemble.shape[0])) if dv_ensemble.shape[0] != obs_ensemble.shape[0]: - self.logger.lraise("EvolAlg._archive() error: shape mismatch: {0} : {1}".\ - format(dv_ensemble.shape[0],obs_ensemble.shape[0])) + self.logger.lraise( + "EvolAlg._archive() error: shape mismatch: {0} : {1}".format( + dv_ensemble.shape[0], obs_ensemble.shape[0] + ) + ) obs_ensemble = obs_ensemble.copy() dv_ensemble = dv_ensemble.copy() isfeas = self.obj_func.is_feasible(obs_ensemble) isnondom = self.obj_func.is_nondominated(obs_ensemble) cd = self.obj_func.crowd_distance(obs_ensemble) - obs_ensemble.loc[isfeas.index,"feasible"] = isfeas - obs_ensemble.loc[isnondom.index,"nondominated"] = isnondom - dv_ensemble.loc[isfeas.index,"feasible"] = isfeas - dv_ensemble.loc[isnondom.index,"nondominated"] = isnondom - obs_ensemble.loc[:,"iteration"] = self.iter_num - dv_ensemble.loc[:,"iteration"] = self.iter_num - obs_ensemble.loc[cd.index,"crowd_distance"] = cd - dv_ensemble.loc[cd.index,"crowd_distance"] = cd + obs_ensemble.loc[isfeas.index, "feasible"] = isfeas + obs_ensemble.loc[isnondom.index, "nondominated"] = isnondom + dv_ensemble.loc[isfeas.index, "feasible"] = isfeas + dv_ensemble.loc[isnondom.index, "nondominated"] = isnondom + obs_ensemble.loc[:, "iteration"] = self.iter_num + dv_ensemble.loc[:, "iteration"] = self.iter_num + obs_ensemble.loc[cd.index, "crowd_distance"] = cd + dv_ensemble.loc[cd.index, "crowd_distance"] = cd if self.obs_ensemble_archive is None: - self.obs_ensemble_archive = obs_ensemble._df.loc[:,:] - self.dv_ensemble_archive = dv_ensemble._df.loc[:,:] + self.obs_ensemble_archive = obs_ensemble._df.loc[:, :] + self.dv_ensemble_archive = dv_ensemble._df.loc[:, :] else: - self.obs_ensemble_archive = self.obs_ensemble_archive.append(obs_ensemble._df.loc[:,:]) - self.dv_ensemble_archive = self.dv_ensemble_archive.append(dv_ensemble.loc[:,:]) + self.obs_ensemble_archive = self.obs_ensemble_archive.append( + obs_ensemble._df.loc[:, :] + ) + self.dv_ensemble_archive = self.dv_ensemble_archive.append( + dv_ensemble.loc[:, :] + ) - def _calc_obs(self,dv_ensemble): + def _calc_obs(self, dv_ensemble): if self.par_ensemble is None: - failed_runs, oe = super(EvolAlg,self)._calc_obs(dv_ensemble) + failed_runs, oe = super(EvolAlg, self)._calc_obs(dv_ensemble) else: # make a copy of the org par ensemble but as a df instance - df_base = self.par_ensemble._df.loc[:,:] + df_base = self.par_ensemble._df.loc[:, :] # stack up the par ensembles for each solution dfs = [] for i in range(dv_ensemble.shape[0]): - solution = dv_ensemble.iloc[i,:] + solution = dv_ensemble.iloc[i, :] df = df_base.copy() - df.loc[:,solution.index] = solution.values + df.loc[:, solution.index] = solution.values dfs.append(df) df = pd.concat(dfs) # reset with a range index org_index = df.index.copy() df.index = np.arange(df.shape[0]) - failed_runs, oe = super(EvolAlg,self)._calc_obs(df) + failed_runs, oe = super(EvolAlg, self)._calc_obs(df) if oe.shape[0] != dv_ensemble.shape[0] * self.par_ensemble.shape[0]: self.logger.lraise("wrong number of runs back from stack eval") EvolAlg._drop_failed(failed_runs, dv_ensemble, oe) self.last_stack = oe.copy() - self.logger.log("reducing initial stack evaluation") - df = self.obj_func.reduce_stack_with_risk_shift(oe,self.par_ensemble.shape[0], - self.risk) + df = self.obj_func.reduce_stack_with_risk_shift( + oe, self.par_ensemble.shape[0], self.risk + ) self.logger.log("reducing initial stack evaluation") # big assumption the run results are in the same order df.index = dv_ensemble.index - oe = pyemu.ObservationEnsemble.from_dataframe(df=df,pst=self.pst) + oe = pyemu.ObservationEnsemble.from_dataframe(df=df, pst=self.pst) self._archive(dv_ensemble, oe) return oe - - def update(self,*args,**kwargs): + def update(self, *args, **kwargs): self.logger.lraise("EvolAlg.update() must be implemented by derived types") class EliteDiffEvol(EvolAlg): - def __init__(self, pst, parcov = None, obscov = None, num_workers = 0, use_approx_prior = True, - submit_file = None, verbose = False, port = 4004, worker_dir = "template"): - super(EliteDiffEvol, self).__init__(pst=pst, parcov=parcov, obscov=obscov, num_workers=num_workers, - submit_file=submit_file, verbose=verbose, port=port, - worker_dir=worker_dir) - - - def update(self,mut_base = 0.8,cross_over_base=0.7,num_dv_reals=None): + def __init__( + self, + pst, + parcov=None, + obscov=None, + num_workers=0, + use_approx_prior=True, + submit_file=None, + verbose=False, + port=4004, + worker_dir="template", + ): + super(EliteDiffEvol, self).__init__( + pst=pst, + parcov=parcov, + obscov=obscov, + num_workers=num_workers, + submit_file=submit_file, + verbose=verbose, + port=port, + worker_dir=worker_dir, + ) + + def update(self, mut_base=0.8, cross_over_base=0.7, num_dv_reals=None): if not self._initialized: self.logger.lraise("not initialized") if num_dv_reals is None: @@ -634,14 +716,16 @@ def next_name(): num_dv = self.dv_ensemble.shape[1] dv_names = self.dv_ensemble.columns dv_log = self.pst.parameter_data.loc[dv_names, "partrans"] == "log" - lb = self.pst.parameter_data.loc[dv_names,"parlbnd"].copy() - ub = self.pst.parameter_data.loc[dv_names,"parubnd"].copy() + lb = self.pst.parameter_data.loc[dv_names, "parlbnd"].copy() + ub = self.pst.parameter_data.loc[dv_names, "parubnd"].copy() lb.loc[dv_log] = lb.loc[dv_log].apply(np.log10) ub.loc[dv_log] = ub.loc[dv_log].apply(np.log10) dv_ensemble_trans = self.dv_ensemble.copy() for idx in dv_ensemble_trans.index: - dv_ensemble_trans.loc[idx,dv_log] = dv_ensemble_trans.loc[idx,dv_log].apply(lambda x: np.log10(x)) + dv_ensemble_trans.loc[idx, dv_log] = dv_ensemble_trans.loc[ + idx, dv_log + ].apply(lambda x: np.log10(x)) for i in range(num_dv_reals): # every parent gets an offspring @@ -650,56 +734,58 @@ def next_name(): mut = mut_base cross_over = cross_over_base else: - #otherwise, some parents get more than one offspring + # otherwise, some parents get more than one offspring # could do something better here - like pick a good parent # make a wild child - parent_idx = np.random.randint(0,dv_ensemble_trans.shape[0]) + parent_idx = np.random.randint(0, dv_ensemble_trans.shape[0]) mut = 0.9 cross_over = 0.9 - parent = dv_ensemble_trans.iloc[parent_idx,:] - + parent = dv_ensemble_trans.iloc[parent_idx, :] # select the three other members in the population - abc_idxs = np.random.choice(dv_ensemble_trans.index,3,replace=False) + abc_idxs = np.random.choice(dv_ensemble_trans.index, 3, replace=False) - abc = dv_ensemble_trans.loc[abc_idxs,:].copy() + abc = dv_ensemble_trans.loc[abc_idxs, :].copy() - mutant= abc.iloc[0] + (mut * (abc.iloc[1] - abc.iloc[2])) + mutant = abc.iloc[0] + (mut * (abc.iloc[1] - abc.iloc[2])) # select cross over genes (dec var values) cross_points = np.random.rand(num_dv) < cross_over if not np.any(cross_points): - cross_points[np.random.randint(0,num_dv)] = True + cross_points[np.random.randint(0, num_dv)] = True - #create an offspring + # create an offspring offspring = parent._df.copy() offspring.loc[cross_points] = mutant.loc[cross_points] - #enforce bounds + # enforce bounds out = offspring > ub offspring.loc[out] = ub.loc[out] out = offspring < lb offspring.loc[out] = lb.loc[out] # back transform - offspring.loc[dv_log] = 10.0**offspring.loc[dv_log] + offspring.loc[dv_log] = 10.0 ** offspring.loc[dv_log] offspring = offspring.loc[self.dv_ensemble.columns] - sol_name = "c_{0}".format(i) dv_offspring.append(offspring) offspring_idx.append(sol_name) child2parent[sol_name] = dv_ensemble_trans.index[parent_idx] - dv_offspring = pd.DataFrame(dv_offspring,columns=self.dv_ensemble.columns,index=offspring_idx) + dv_offspring = pd.DataFrame( + dv_offspring, columns=self.dv_ensemble.columns, index=offspring_idx + ) # run the model with offspring candidates - self.logger.log("running {0} canditiate solutions for iteration {1}".\ - format(dv_offspring.shape[0],self.iter_num)) + self.logger.log( + "running {0} canditiate solutions for iteration {1}".format( + dv_offspring.shape[0], self.iter_num + ) + ) obs_offspring = self._calc_obs(dv_offspring) - # evaluate offspring fitness WRT feasibility and nondomination (elitist) - # if offspring dominates parent, replace in # self.dv_ensemble and self.obs_ensemble. if not, drop candidate. @@ -712,48 +798,73 @@ def next_name(): self.logger.statement("child {0} is not feasible".format(child_idx)) continue - child_sol = obs_offspring.loc[child_idx,:] + child_sol = obs_offspring.loc[child_idx, :] parent_idx = child2parent[child_idx] if parent_idx is None: # the parent was already removed by another child, so if this child is # feasible and nondominated, keep it if isnondom(child_idx): - self.logger.statement("orphaned child {0} retained".format(child_idx)) + self.logger.statement( + "orphaned child {0} retained".format(child_idx) + ) sol_name = next_name() self.dv_ensemble.loc[sol_name, child_sol.index] = child_sol - self.obs_ensemble.loc[sol_name, obs_offspring.columns] = obs_offspring.loc[child_idx, :] + self.obs_ensemble.loc[ + sol_name, obs_offspring.columns + ] = obs_offspring.loc[child_idx, :] else: - parent_sol = self.obs_ensemble.loc[parent_idx,:] - if self.obj_func.dominates(parent_sol.loc[self.obj_func.obs_obj_names],\ - child_sol.loc[self.obj_func.obs_obj_names]): - self.logger.statement("child {0} dominated by parent {1}".format(child_idx,parent_idx)) + parent_sol = self.obs_ensemble.loc[parent_idx, :] + if self.obj_func.dominates( + parent_sol.loc[self.obj_func.obs_obj_names], + child_sol.loc[self.obj_func.obs_obj_names], + ): + self.logger.statement( + "child {0} dominated by parent {1}".format( + child_idx, parent_idx + ) + ) # your dead to me! pass - elif self.obj_func.dominates(child_sol.loc[self.obj_func.obs_obj_names],\ - parent_sol.loc[self.obj_func.obs_obj_names]): + elif self.obj_func.dominates( + child_sol.loc[self.obj_func.obs_obj_names], + parent_sol.loc[self.obj_func.obs_obj_names], + ): # hey dad, what do you think about your son now! - self.logger.statement("child {0} dominates parent {1}".format(child_idx,parent_idx)) - self.dv_ensemble.loc[parent_idx,dv_offspring.columns] = dv_offspring.loc[child_idx,:] - self.obs_ensemble._df.loc[parent_idx,obs_offspring.columns] = obs_offspring._df.loc[child_idx,:] + self.logger.statement( + "child {0} dominates parent {1}".format(child_idx, parent_idx) + ) + self.dv_ensemble.loc[ + parent_idx, dv_offspring.columns + ] = dv_offspring.loc[child_idx, :] + self.obs_ensemble._df.loc[ + parent_idx, obs_offspring.columns + ] = obs_offspring._df.loc[child_idx, :] child2parent[idx] = None else: - self.logger.statement("child {0} and parent {1} kept".format(child_idx,parent_idx)) + self.logger.statement( + "child {0} and parent {1} kept".format(child_idx, parent_idx) + ) sol_name = next_name() - self.dv_ensemble.loc[sol_name,dv_offspring.columns] = dv_offspring.loc[child_idx,:] - self.obs_ensemble._df.loc[sol_name,obs_offspring.columns] = obs_offspring._df.loc[child_idx,:] - - - #if there are too many individuals in self.dv_ensemble, + self.dv_ensemble.loc[ + sol_name, dv_offspring.columns + ] = dv_offspring.loc[child_idx, :] + self.obs_ensemble._df.loc[ + sol_name, obs_offspring.columns + ] = obs_offspring._df.loc[child_idx, :] + + # if there are too many individuals in self.dv_ensemble, # first drop dominated,then reduce by using crowding distance. # self.logger.statement("number of solutions:{0}".format(self.dv_ensemble.shape[0])) isnondom = self.obj_func.is_nondominated(self.obs_ensemble) dom_idx = isnondom.loc[isnondom == False].index - nondom_idx = isnondom.loc[isnondom==True].index - self.logger.statement("number of dominated solutions:{0}".format(dom_idx.shape[0])) + nondom_idx = isnondom.loc[isnondom == True].index + self.logger.statement( + "number of dominated solutions:{0}".format(dom_idx.shape[0]) + ) # self.logger.statement("nondominated solutions: {0}".format(','.join(nondom_idx))) - self.logger.statement("dominated solutions: {0}".format(','.join(str(dom_idx)))) + self.logger.statement("dominated solutions: {0}".format(",".join(str(dom_idx)))) ndrop = self.dv_ensemble.shape[0] - num_dv_reals if ndrop > 0: isnondom = self.obj_func.is_nondominated(self.obs_ensemble) @@ -762,23 +873,29 @@ def next_name(): # crowding distance as the order if False in vc.index: # get dfs for the dominated solutions - dv_dom = self.dv_ensemble.loc[dom_idx,:].copy() - obs_dom = self.obs_ensemble.loc[dom_idx,:].copy() - - self.dv_ensemble.drop(dom_idx,inplace=True) - self.obs_ensemble.drop(dom_idx,inplace=True) - self.logger.statement("dropping {0} dominated individuals based on crowd distance".\ - format(min(ndrop,dv_dom.shape[0]))) - - self._drop_by_crowd(dv_dom,obs_dom,min(ndrop,dv_dom.shape[0])) - #add any remaining dominated solutions back + dv_dom = self.dv_ensemble.loc[dom_idx, :].copy() + obs_dom = self.obs_ensemble.loc[dom_idx, :].copy() + + self.dv_ensemble.drop(dom_idx, inplace=True) + self.obs_ensemble.drop(dom_idx, inplace=True) + self.logger.statement( + "dropping {0} dominated individuals based on crowd distance".format( + min(ndrop, dv_dom.shape[0]) + ) + ) + + self._drop_by_crowd(dv_dom, obs_dom, min(ndrop, dv_dom.shape[0])) + # add any remaining dominated solutions back self.dv_ensemble = self.dv_ensemble.append(dv_dom._df) self.obs_ensemble = self.obs_ensemble.append(obs_dom._df) - # drop remaining nondom solutions as needed if self.dv_ensemble.shape[0] > num_dv_reals: - self._drop_by_crowd(self.dv_ensemble,self.obs_ensemble,self.dv_ensemble.shape[0] - num_dv_reals) + self._drop_by_crowd( + self.dv_ensemble, + self.obs_ensemble, + self.dv_ensemble.shape[0] - num_dv_reals, + ) self.iter_report() self.iter_num += 1 @@ -792,28 +909,31 @@ def iter_report(self): isfeas = self.obj_func.is_feasible(oe) isnondom = self.obj_func.is_nondominated(oe) cd = self.obj_func.crowd_distance(oe) - for df in [oe,dv]: - df.loc[isfeas.index,"feasible"] = isfeas + for df in [oe, dv]: + df.loc[isfeas.index, "feasible"] = isfeas df.loc[isnondom.index, "nondominated"] = isnondom - df.loc[cd.index,"crowd_distance"] = cd + df.loc[cd.index, "crowd_distance"] = cd dv.to_csv("dv_ensemble.{0}.csv".format(self.iter_num + 1)) oe.to_csv("obs_ensemble.{0}.csv".format(self.iter_num + 1)) - self.logger.statement("*** iteration {0} report".format(self.iter_num+1)) + self.logger.statement("*** iteration {0} report".format(self.iter_num + 1)) self.logger.statement("{0} current solutions".format(dv.shape[0])) - self.logger.statement("{0} infeasible".format(isfeas[isfeas==False].shape[0])) - self.logger.statement("{0} nondomiated".format(isnondom[isnondom==True].shape[0])) + self.logger.statement("{0} infeasible".format(isfeas[isfeas == False].shape[0])) + self.logger.statement( + "{0} nondomiated".format(isnondom[isnondom == True].shape[0]) + ) - - - - def _drop_by_crowd(self,dv_ensemble, obs_ensemble, ndrop,min_dist=0.1): + def _drop_by_crowd(self, dv_ensemble, obs_ensemble, ndrop, min_dist=0.1): if ndrop > dv_ensemble.shape[0]: - self.logger.lraise("EliteDiffEvol.drop_by_crowd() error: ndrop"+ - "{0} > dv_ensemble.shape[0] {1}".\ - format(ndrop,dv_ensemble.shape[0])) - self.logger.statement("dropping {0} of {1} individuals based on crowd distance".\ - format(ndrop,dv_ensemble.shape[0])) + self.logger.lraise( + "EliteDiffEvol.drop_by_crowd() error: ndrop" + + "{0} > dv_ensemble.shape[0] {1}".format(ndrop, dv_ensemble.shape[0]) + ) + self.logger.statement( + "dropping {0} of {1} individuals based on crowd distance".format( + ndrop, dv_ensemble.shape[0] + ) + ) # if min_dist is not None: # while True: # cd = self.obj_func.crowd_distance(obs_ensemble) @@ -829,20 +949,15 @@ def _drop_by_crowd(self,dv_ensemble, obs_ensemble, ndrop,min_dist=0.1): # obs_ensemble.drop(drop_idx,inplace=True) # ndrop -= 1% - for idrop in range(ndrop): cd = self.obj_func.crowd_distance(obs_ensemble) - cd.sort_values(inplace=True,ascending=False) - #drop the first element in cd from both dv_ensemble and obs_ensemble + cd.sort_values(inplace=True, ascending=False) + # drop the first element in cd from both dv_ensemble and obs_ensemble drop_idx = cd.index[-1] - self.logger.statement("solution {0} removed based on crowding distance {1}".\ - format(drop_idx,cd[drop_idx])) - dv_ensemble.drop(drop_idx,inplace=True) - obs_ensemble.drop(drop_idx,inplace=True) - - - - - - - + self.logger.statement( + "solution {0} removed based on crowding distance {1}".format( + drop_idx, cd[drop_idx] + ) + ) + dv_ensemble.drop(drop_idx, inplace=True) + obs_ensemble.drop(drop_idx, inplace=True) diff --git a/pyemu/pst/__init__.py b/pyemu/pst/__init__.py index 168fa4d8e..d4c5f487a 100644 --- a/pyemu/pst/__init__.py +++ b/pyemu/pst/__init__.py @@ -6,4 +6,4 @@ from .pst_controldata import ControlData from .pst_handler import Pst -from . import pst_utils \ No newline at end of file +from . import pst_utils diff --git a/pyemu/pst/pst_controldata.py b/pyemu/pst/pst_controldata.py index 76adb2990..caa390344 100644 --- a/pyemu/pst/pst_controldata.py +++ b/pyemu/pst/pst_controldata.py @@ -11,10 +11,11 @@ import numpy as np import pandas from ..pyemu_warnings import PyemuWarning + pandas.options.display.max_colwidth = 100 -#from pyemu.pst.pst_handler import SFMT,SFMT_LONG,FFMT,IFMT +# from pyemu.pst.pst_handler import SFMT,SFMT_LONG,FFMT,IFMT -#formatters +# formatters SFMT = lambda x: "{0:>20s}".format(str(x)) """lambda function for string formatting `str` types into 20-char widths""" SFMT_LONG = lambda x: "{0:>50s}".format(str(x)) @@ -31,8 +32,9 @@ 1.000000e+001 1.000000e+001 1.000000e-003 0 0 1.000000e-001 1 1.1 noaui nosenreuse noboundscale 30 1.000000e-002 3 3 1.000000e-002 3 0.0 1 -1.0 -0 0 0 0 jcosave verboserec jcosaveitn reisaveitn parsaveitn noparsaverun"""\ - .lower().split('\n') +0 0 0 0 jcosave verboserec jcosaveitn reisaveitn parsaveitn noparsaverun""".lower().split( + "\n" +) CONTROL_VARIABLE_LINES = """RSTFLE PESTMODE NPAR NOBS NPARGP NPRIOR NOBSGP [MAXCOMPDIM] @@ -41,37 +43,44 @@ RELPARMAX FACPARMAX FACORIG [IBOUNDSTICK] [UPVECBEND] PHIREDSWH [NOPTSWITCH] [SPLITSWH] [DOAUI] [DOSENREUSE] [BOUNDSCALE] NOPTMAX PHIREDSTP NPHISTP NPHINORED RELPARSTP NRELPAR [PHISTOPTHRESH] [LASTRUN] [PHIABANDON] -ICOV ICOR IEIG [IRES] [JCOSAVE] [VERBOSEREC] [JCOSAVEITN] [REISAVEITN] [PARSAVEITN] [PARSAVERUN]"""\ - .lower().split('\n') +ICOV ICOR IEIG [IRES] [JCOSAVE] [VERBOSEREC] [JCOSAVEITN] [REISAVEITN] [PARSAVEITN] [PARSAVERUN]""".lower().split( + "\n" +) REG_VARIABLE_LINES = """PHIMLIM PHIMACCEPT [FRACPHIM] [MEMSAVE] WFINIT WFMIN WFMAX [LINREG] [REGCONTINUE] -WFFAC WFTOL IREGADJ [NOPTREGADJ REGWEIGHTRAT [REGSINGTHRESH]]""".lower().split('\n') +WFFAC WFTOL IREGADJ [NOPTREGADJ REGWEIGHTRAT [REGSINGTHRESH]]""".lower().split( + "\n" +) REG_DEFAULT_LINES = """ 1.0e-10 1.05e-10 0.1 nomemsave 1.0 1.0e-10 1.0e10 linreg continue -1.3 1.0e-2 1 1.5 1.5 0.5""".lower().split('\n') +1.3 1.0e-2 1 1.5 1.5 0.5""".lower().split( + "\n" +) + class RegData(object): """ an object that encapsulates the regularization section of the PEST control file """ + def __init__(self): self.optional_dict = {} - for vline,dline in zip(REG_VARIABLE_LINES,REG_DEFAULT_LINES): + for vline, dline in zip(REG_VARIABLE_LINES, REG_DEFAULT_LINES): vraw = vline.split() draw = dline.split() - for v,d in zip(vraw,draw): + for v, d in zip(vraw, draw): o = False - if '[' in v: + if "[" in v: o = True - v = v.replace('[','').replace(']','') - super(RegData,self).__setattr__(v,d) + v = v.replace("[", "").replace("]", "") + super(RegData, self).__setattr__(v, d) self.optional_dict[v] = o - self.should_write = ["phimlim","phimaccept","fracphim","wfinit"] + self.should_write = ["phimlim", "phimaccept", "fracphim", "wfinit"] - def write(self,f): + def write(self, f): """ write the regularization section to an open file handle @@ -83,14 +92,13 @@ def write(self,f): for vline in REG_VARIABLE_LINES: vraw = vline.strip().split() for v in vraw: - v = v.replace("[",'').replace("]",'') + v = v.replace("[", "").replace("]", "") if v not in self.optional_dict.keys(): raise Exception("RegData missing attribute {0}".format(v)) f.write("{0} ".format(self.__getattribute__(v))) f.write("\n") - - def write_keyword(self,f): + def write_keyword(self, f): """write the regularization section to an open file handle using the keyword-style format @@ -101,12 +109,12 @@ def write_keyword(self,f): for vline in REG_VARIABLE_LINES: vraw = vline.strip().split() for v in vraw: - v = v.replace("[",'').replace("]",'') + v = v.replace("[", "").replace("]", "") if v not in self.should_write: continue if v not in self.optional_dict.keys(): raise Exception("RegData missing attribute {0}".format(v)) - f.write("{0:30} {1:>10}\n".format(v,self.__getattribute__(v))) + f.write("{0:30} {1:>10}\n".format(v, self.__getattribute__(v))) class SvdData(object): @@ -118,14 +126,14 @@ class SvdData(object): """ - def __init__(self,**kwargs): - self.svdmode = kwargs.pop("svdmode",1) - self.maxsing = kwargs.pop("maxsing",10000000) - self.eigthresh = kwargs.pop("eigthresh",1.0e-6) - self.eigwrite = kwargs.pop("eigwrite",1) + def __init__(self, **kwargs): + self.svdmode = kwargs.pop("svdmode", 1) + self.maxsing = kwargs.pop("maxsing", 10000000) + self.eigthresh = kwargs.pop("eigthresh", 1.0e-6) + self.eigwrite = kwargs.pop("eigwrite", 1) - def write_keyword(self,f): + def write_keyword(self, f): """ write an SVD section to a file handle using keyword-style format @@ -133,12 +141,12 @@ def write_keyword(self,f): f (`file handle`): open file handle for writing """ - f.write("{0:30} {1:>10}\n".format("svdmode",self.svdmode)) - f.write("{0:30} {1:>10}\n".format("maxsing",self.maxsing)) - f.write("{0:30} {1:>10}\n".format("eigthresh",self.eigthresh)) - f.write("{0:30} {1:>10}\n".format("eigwrite",self.eigwrite)) + f.write("{0:30} {1:>10}\n".format("svdmode", self.svdmode)) + f.write("{0:30} {1:>10}\n".format("maxsing", self.maxsing)) + f.write("{0:30} {1:>10}\n".format("eigthresh", self.eigthresh)) + f.write("{0:30} {1:>10}\n".format("eigwrite", self.eigwrite)) - def write(self,f): + def write(self, f): """ write an SVD section to a file handle Args: @@ -146,11 +154,11 @@ def write(self,f): """ f.write("* singular value decomposition\n") - f.write(IFMT(self.svdmode)+'\n') - f.write(IFMT(self.maxsing)+' '+FFMT(self.eigthresh)+"\n") - f.write('{0}\n'.format(self.eigwrite)) + f.write(IFMT(self.svdmode) + "\n") + f.write(IFMT(self.maxsing) + " " + FFMT(self.eigthresh) + "\n") + f.write("{0}\n".format(self.eigwrite)) - def parse_values_from_lines(self,lines): + def parse_values_from_lines(self, lines): """ parse values from lines of the SVD section of a PEST control file @@ -159,21 +167,28 @@ def parse_values_from_lines(self,lines): """ - assert len(lines) == 3,"SvdData.parse_values_from_lines: expected " + \ - "3 lines, not {0}".format(len(lines)) + assert len(lines) == 3, ( + "SvdData.parse_values_from_lines: expected " + + "3 lines, not {0}".format(len(lines)) + ) try: self.svdmode = int(lines[0].strip().split()[0]) except Exception as e: - raise Exception("SvdData.parse_values_from_lines: error parsing" + \ - " svdmode from line {0}: {1} \n".format(lines[0],str(e))) + raise Exception( + "SvdData.parse_values_from_lines: error parsing" + + " svdmode from line {0}: {1} \n".format(lines[0], str(e)) + ) try: raw = lines[1].strip().split() self.maxsing = int(raw[0]) self.eigthresh = float(raw[1]) except Exception as e: - raise Exception("SvdData.parse_values_from_lines: error parsing" + \ - " maxsing and eigthresh from line {0}: {1} \n"\ - .format(lines[1],str(e))) + raise Exception( + "SvdData.parse_values_from_lines: error parsing" + + " maxsing and eigthresh from line {0}: {1} \n".format( + lines[1], str(e) + ) + ) # try: # self.eigwrite = int(lines[2].strip()) # except Exception as e: @@ -192,44 +207,55 @@ class ControlData(object): matches the expected type of the attribute. """ + def __init__(self): - super(ControlData,self).__setattr__("formatters",{np.int32:IFMT,np.float64:FFMT,str:SFMT}) - super(ControlData,self).__setattr__("_df",self.get_dataframe()) + super(ControlData, self).__setattr__( + "formatters", {np.int32: IFMT, np.float64: FFMT, str: SFMT} + ) + super(ControlData, self).__setattr__("_df", self.get_dataframe()) # acceptable values for most optional string inputs - super(ControlData,self).__setattr__("accept_values",{'doaui':['aui','noaui'], - 'dosenreuse':['senreuse','nosenreuse'], - 'boundscale':['boundscale','noboundscale'], - 'jcosave':['jcosave','nojcosave'], - 'verboserec':['verboserec','noverboserec'], - 'jcosaveitn':['jcosaveitn','nojcosvaeitn'], - 'reisaveitn':['reisaveitn','noreisaveitn'], - 'parsaveitn':['parsaveitn','noparsaveitn'], - 'parsaverun':['parsaverun','noparsaverun']}) - - self._df.index = self._df.name.apply(lambda x:x.replace('[',''))\ - .apply(lambda x: x.replace(']','')) - super(ControlData, self).__setattr__("keyword_accessed", ["pestmode","noptmax"]) - counters = ["npar","nobs","npargp","nobsgp","nprior","ntplfle","ninsfle"] + super(ControlData, self).__setattr__( + "accept_values", + { + "doaui": ["aui", "noaui"], + "dosenreuse": ["senreuse", "nosenreuse"], + "boundscale": ["boundscale", "noboundscale"], + "jcosave": ["jcosave", "nojcosave"], + "verboserec": ["verboserec", "noverboserec"], + "jcosaveitn": ["jcosaveitn", "nojcosvaeitn"], + "reisaveitn": ["reisaveitn", "noreisaveitn"], + "parsaveitn": ["parsaveitn", "noparsaveitn"], + "parsaverun": ["parsaverun", "noparsaverun"], + }, + ) + + self._df.index = self._df.name.apply(lambda x: x.replace("[", "")).apply( + lambda x: x.replace("]", "") + ) + super(ControlData, self).__setattr__( + "keyword_accessed", ["pestmode", "noptmax"] + ) + counters = ["npar", "nobs", "npargp", "nobsgp", "nprior", "ntplfle", "ninsfle"] super(ControlData, self).__setattr__("counters", counters) - #self.keyword_accessed = ["pestmode","noptmax"] + # self.keyword_accessed = ["pestmode","noptmax"] def __setattr__(self, key, value): if key == "_df": - super(ControlData,self).__setattr__("_df",value) + super(ControlData, self).__setattr__("_df", value) return - assert key in self._df.index, str(key)+" not found in attributes" - self._df.loc[key,"value"] = self._df.loc[key,"type"](value) - #super(ControlData, self).__getattr__("keyword_accessed").append(key) + assert key in self._df.index, str(key) + " not found in attributes" + self._df.loc[key, "value"] = self._df.loc[key, "type"](value) + # super(ControlData, self).__getattr__("keyword_accessed").append(key) if key not in self.counters: self.keyword_accessed.append(key) def __getattr__(self, item): if item == "_df": return self._df.copy() - assert item in self._df.index, str(item)+" not found in attributes" - return self._df.loc[item,"value"] + assert item in self._df.index, str(item) + " not found in attributes" + return self._df.loc[item, "value"] @staticmethod def get_dataframe(): @@ -246,20 +272,25 @@ def get_dataframe(): defaults = [] [defaults.extend(line.split()) for line in CONTROL_DEFAULT_LINES] - types, required,cast_defaults,formats = [],[],[],[] - for name,default in zip(names,defaults): - if '[' in name or ']' in name: + types, required, cast_defaults, formats = [], [], [], [] + for name, default in zip(names, defaults): + if "[" in name or "]" in name: required.append(False) else: required.append(True) - v,t,f = ControlData._parse_value(default) + v, t, f = ControlData._parse_value(default) types.append(t) formats.append(f) cast_defaults.append(v) - return pandas.DataFrame({"name":names,"type":types, - "value":cast_defaults,"required":required, - "format":formats}) - + return pandas.DataFrame( + { + "name": names, + "type": types, + "value": cast_defaults, + "required": required, + "format": formats, + } + ) @staticmethod def _parse_value(value): @@ -276,10 +307,9 @@ def _parse_value(value): v = value.lower() t = str f = SFMT - return v,t,f - + return v, t, f - def parse_values_from_lines(self,lines,iskeyword=False): + def parse_values_from_lines(self, lines, iskeyword=False): """ cast the string lines for a pest control file into actual inputs Args: @@ -296,7 +326,7 @@ def parse_values_from_lines(self,lines,iskeyword=False): name = raw[0].strip().lower() value = raw[1].strip() - v,t,f = self._parse_value(value) + v, t, f = self._parse_value(value) if name not in self._df.index: extra[name] = v else: @@ -307,10 +337,11 @@ def parse_values_from_lines(self,lines,iskeyword=False): if t == np.int32 and self._df.loc[name, "type"] == np.float64: self._df.loc[name, "value"] = np.float64(v) - # if this is a required input, throw elif self._df.loc[name, "required"]: - raise Exception("wrong type found for variable " + name + ":" + str(t)) + raise Exception( + "wrong type found for variable " + name + ":" + str(t) + ) else: # else, since this problem is usually a string, check for acceptable values @@ -322,53 +353,65 @@ def parse_values_from_lines(self,lines,iskeyword=False): found = True break if not found: - warnings.warn("non-conforming value found for " + \ - name + ":" + str(v) + "...ignoring", PyemuWarning) - + warnings.warn( + "non-conforming value found for " + + name + + ":" + + str(v) + + "...ignoring", + PyemuWarning, + ) else: self._df.loc[name, "value"] = v return extra - assert len(lines) == len(CONTROL_VARIABLE_LINES),\ - "ControlData error: len of lines not equal to " +\ - str(len(CONTROL_VARIABLE_LINES)) + assert len(lines) == len(CONTROL_VARIABLE_LINES), ( + "ControlData error: len of lines not equal to " + + str(len(CONTROL_VARIABLE_LINES)) + ) - for iline,line in enumerate(lines): + for iline, line in enumerate(lines): vals = line.strip().split() names = CONTROL_VARIABLE_LINES[iline].strip().split() - for name,val in zip(names,vals): - v,t,f = self._parse_value(val) - name = name.replace('[','').replace(']','') - - #if the parsed values type isn't right - if t != self._df.loc[name,"type"]: + for name, val in zip(names, vals): + v, t, f = self._parse_value(val) + name = name.replace("[", "").replace("]", "") - # if a float was expected and int return, not a problem - if t == np.int32 and self._df.loc[name,"type"] == np.float64: - self._df.loc[name,"value"] = np.float64(v) + # if the parsed values type isn't right + if t != self._df.loc[name, "type"]: + # if a float was expected and int return, not a problem + if t == np.int32 and self._df.loc[name, "type"] == np.float64: + self._df.loc[name, "value"] = np.float64(v) # if this is a required input, throw - elif self._df.loc[name,"required"]: - raise Exception("wrong type found for variable " + name + ":" + str(t)) + elif self._df.loc[name, "required"]: + raise Exception( + "wrong type found for variable " + name + ":" + str(t) + ) else: - - #else, since this problem is usually a string, check for acceptable values + + # else, since this problem is usually a string, check for acceptable values found = False - for nname,avalues in self.accept_values.items(): + for nname, avalues in self.accept_values.items(): if v in avalues: - if t == self._df.loc[nname,"type"]: - self._df.loc[nname,"value"] = v + if t == self._df.loc[nname, "type"]: + self._df.loc[nname, "value"] = v found = True break if not found: - warnings.warn("non-conforming value found for " +\ - name + ":" + str(v) + "...ignoring",PyemuWarning) - + warnings.warn( + "non-conforming value found for " + + name + + ":" + + str(v) + + "...ignoring", + PyemuWarning, + ) else: - self._df.loc[name,"value"] = v + self._df.loc[name, "value"] = v return {} def copy(self): @@ -376,7 +419,6 @@ def copy(self): cd._df = self._df return cd - @property def formatted_values(self): """ list the entries and current values in the control data section @@ -385,10 +427,9 @@ def formatted_values(self): pandas.Series: formatted_values for the control data entries """ - return self._df.apply(lambda x: self.formatters[x["type"]](x["value"]),axis=1) - + return self._df.apply(lambda x: self.formatters[x["type"]](x["value"]), axis=1) - def write_keyword(self,f): + def write_keyword(self, f): """ write the control data entries to an open file handle using keyword-style format. @@ -401,25 +442,25 @@ def write_keyword(self,f): """ kw = super(ControlData, self).__getattribute__("keyword_accessed") f.write("* control data keyword\n") - for n,v in zip(self._df.name,self.formatted_values): + for n, v in zip(self._df.name, self.formatted_values): if n not in kw: continue - f.write("{0:30} {1}\n".format(n,v)) + f.write("{0:30} {1}\n".format(n, v)) - - def write(self,f): + def write(self, f): """ write control data section to a file Args: f (file handle): open file handle to write to """ - if isinstance(f,str): - f = open(f,'w') + if isinstance(f, str): + f = open(f, "w") f.write("pcf\n") f.write("* control data\n") for line in CONTROL_VARIABLE_LINES: - [f.write(self.formatted_values[name.replace('[','').replace(']','')]) for name in line.split()] - f.write('\n') - - + [ + f.write(self.formatted_values[name.replace("[", "").replace("]", "")]) + for name in line.split() + ] + f.write("\n") diff --git a/pyemu/pst/pst_handler.py b/pyemu/pst/pst_handler.py index 7065ec39d..1eabb2b2c 100644 --- a/pyemu/pst/pst_handler.py +++ b/pyemu/pst/pst_handler.py @@ -1,4 +1,3 @@ - from __future__ import print_function, division import os import glob @@ -7,13 +6,16 @@ import warnings import numpy as np import pandas as pd + pd.options.display.max_colwidth = 100 import pyemu from ..pyemu_warnings import PyemuWarning from pyemu.pst.pst_controldata import ControlData, SvdData, RegData from pyemu.pst import pst_utils from pyemu.plot import plot_utils -#from pyemu.utils.os_utils import run + +# from pyemu.utils.os_utils import run + class Pst(object): """All things PEST(++) control file @@ -35,6 +37,7 @@ class Pst(object): pst.write("my_new.pst") """ + def __init__(self, filename, load=True, resfile=None): self.parameter_data = None @@ -69,9 +72,9 @@ def __init__(self, filename, load=True, resfile=None): self.comments = {} self.other_sections = {} self.new_filename = None - for key,value in pst_utils.pst_config.items(): - self.__setattr__(key,copy.copy(value)) - #self.tied = None + for key, value in pst_utils.pst_config.items(): + self.__setattr__(key, copy.copy(value)) + # self.tied = None self.control_data = ControlData() """pyemu.pst.pst_controldata.ControlData: '* control data' information. Access with standard PEST variable names @@ -113,11 +116,10 @@ def __setattr__(self, key, value): if key == "model_command": if isinstance(value, str): value = [value] - super(Pst,self).__setattr__(key,value) - + super(Pst, self).__setattr__(key, value) @classmethod - def from_par_obs_names(cls,par_names=["par1"],obs_names=["obs1"]): + def from_par_obs_names(cls, par_names=["par1"], obs_names=["obs1"]): """construct a shell `Pst` instance from parameter and observation names Args: @@ -135,7 +137,7 @@ def from_par_obs_names(cls,par_names=["par1"],obs_names=["obs1"]): pst = pyemu.Pst.from_par_obs_names(par_names,obs_names) """ - return pst_utils.generic_pst(par_names=par_names,obs_names=obs_names) + return pst_utils.generic_pst(par_names=par_names, obs_names=obs_names) @property def phi(self): @@ -171,46 +173,62 @@ def phi_components(self): ogroups = self.observation_data.groupby("obgnme").groups rgroups = self.res.groupby("group").groups self.res.index = self.res.name - for og,onames in ogroups.items(): - #assert og in rgroups.keys(),"Pst.phi_componentw obs group " +\ + for og, onames in ogroups.items(): + # assert og in rgroups.keys(),"Pst.phi_componentw obs group " +\ # "not found: " + str(og) - #og_res_df = self.res.ix[rgroups[og]] - og_res_df = self.res.loc[onames,:].dropna(axis=1) - #og_res_df.index = og_res_df.name - og_df = self.observation_data.loc[ogroups[og],:] + # og_res_df = self.res.ix[rgroups[og]] + og_res_df = self.res.loc[onames, :].dropna(axis=1) + # og_res_df.index = og_res_df.name + og_df = self.observation_data.loc[ogroups[og], :] og_df.index = og_df.obsnme - #og_res_df = og_res_df.loc[og_df.index,:] - assert og_df.shape[0] == og_res_df.shape[0],\ - " Pst.phi_components error: group residual dataframe row length" +\ - "doesn't match observation data group dataframe row length" + \ - str(og_df.shape) + " vs. " + str(og_res_df.shape) + "," + og + # og_res_df = og_res_df.loc[og_df.index,:] + assert og_df.shape[0] == og_res_df.shape[0], ( + " Pst.phi_components error: group residual dataframe row length" + + "doesn't match observation data group dataframe row length" + + str(og_df.shape) + + " vs. " + + str(og_res_df.shape) + + "," + + og + ) if "modelled" not in og_res_df.columns: print(og_res_df) - m = self.res.loc[onames,"modelled"] + m = self.res.loc[onames, "modelled"] print(m.loc[m.isna()]) - raise Exception("'modelled' not in res df columns for group "+og) + raise Exception("'modelled' not in res df columns for group " + og) # components[og] = np.sum((og_res_df["residual"] * # og_df["weight"]) ** 2) - components[og] = np.sum(((og_df.loc[:,"obsval"] - og_res_df.loc[og_df.obsnme,"modelled"]) * - og_df.loc[:,"weight"]) ** 2) - if not self.control_data.pestmode.startswith("reg") and \ - self.prior_information.shape[0] > 0: + components[og] = np.sum( + ( + (og_df.loc[:, "obsval"] - og_res_df.loc[og_df.obsnme, "modelled"]) + * og_df.loc[:, "weight"] + ) + ** 2 + ) + if ( + not self.control_data.pestmode.startswith("reg") + and self.prior_information.shape[0] > 0 + ): ogroups = self.prior_information.groupby("obgnme").groups for og in ogroups.keys(): if og not in rgroups.keys(): - raise Exception("Pst.adjust_weights_res() obs group " +\ - "not found: " + str(og)) - og_res_df = self.res.loc[rgroups[og],:] + raise Exception( + "Pst.adjust_weights_res() obs group " + "not found: " + str(og) + ) + og_res_df = self.res.loc[rgroups[og], :] og_res_df.index = og_res_df.name - og_df = self.prior_information.loc[ogroups[og],:] + og_df = self.prior_information.loc[ogroups[og], :] og_df.index = og_df.pilbl - og_res_df = og_res_df.loc[og_df.index,:] + og_res_df = og_res_df.loc[og_df.index, :] if og_df.shape[0] != og_res_df.shape[0]: - raise Exception(" Pst.phi_components error: group residual dataframe row length" +\ - "doesn't match observation data group dataframe row length" + \ - str(og_df.shape) + " vs. " + str(og_res_df.shape)) - components[og] = np.sum((og_res_df["residual"] * - og_df["weight"]) ** 2) + raise Exception( + " Pst.phi_components error: group residual dataframe row length" + + "doesn't match observation data group dataframe row length" + + str(og_df.shape) + + " vs. " + + str(og_res_df.shape) + ) + components[og] = np.sum((og_res_df["residual"] * og_df["weight"]) ** 2) return components @@ -231,12 +249,12 @@ def phi_components_normalized(self): # use a dictionary comprehension to go through and normalize each component of phi to the total phi = self.phi comps = self.phi_components - norm = {i: c/phi for i,c in comps.items()} - print(phi,comps,norm) + norm = {i: c / phi for i, c in comps.items()} + print(phi, comps, norm) return norm - def set_res(self,res): + def set_res(self, res): """ reset the private `Pst.res` attribute. Args: @@ -245,7 +263,7 @@ def set_res(self,res): """ - if isinstance(res,str): + if isinstance(res, str): res = pst_utils.read_resfile(res) self.__res = res @@ -267,8 +285,9 @@ def res(self): else: if self.resfile is not None: if not os.path.exists(self.resfile): - raise Exception("Pst.res: self.resfile " +\ - str(self.resfile) + " does not exist") + raise Exception( + "Pst.res: self.resfile " + str(self.resfile) + " does not exist" + ) else: self.resfile = self.filename.replace(".pst", ".res") if not os.path.exists(self.resfile): @@ -277,24 +296,29 @@ def res(self): self.resfile = self.resfile.replace(".rei", ".base.rei") if not os.path.exists(self.resfile): if self.new_filename is not None: - self.resfile = self.new_filename.replace(".pst",".res") + self.resfile = self.new_filename.replace(".pst", ".res") if not os.path.exists(self.resfile): - self.resfile = self.resfile.replace(".res","rei") + self.resfile = self.resfile.replace(".res", "rei") if not os.path.exists(self.resfile): - raise Exception("Pst.res: " + - "could not residual file case.res" + - " or case.rei" + " or case.base.rei" + - " or case.obs.csv") - + raise Exception( + "Pst.res: " + + "could not residual file case.res" + + " or case.rei" + + " or case.base.rei" + + " or case.obs.csv" + ) res = pst_utils.read_resfile(self.resfile) - missing_bool = self.observation_data.obsnme.apply\ - (lambda x: x not in res.name) + missing_bool = self.observation_data.obsnme.apply( + lambda x: x not in res.name + ) missing = self.observation_data.obsnme[missing_bool] if missing.shape[0] > 0: - raise Exception("Pst.res: the following observations " + - "were not found in " + - "{0}:{1}".format(self.resfile,','.join(missing))) + raise Exception( + "Pst.res: the following observations " + + "were not found in " + + "{0}:{1}".format(self.resfile, ",".join(missing)) + ) self.__res = res return self.__res @@ -323,7 +347,6 @@ def nnz_obs(self): nnz += 1 return nnz - @property def nobs(self): """get the number of observations @@ -335,7 +358,6 @@ def nobs(self): self.control_data.nobs = self.observation_data.shape[0] return self.control_data.nobs - @property def npar_adj(self): """get the number of adjustable parameters (not fixed or tied) @@ -351,7 +373,6 @@ def npar_adj(self): np += 1 return np - @property def npar(self): """get number of parameters @@ -373,13 +394,13 @@ def forecast_names(self): """ if "forecasts" in self.pestpp_options.keys(): - if isinstance(self.pestpp_options["forecasts"],str): - return self.pestpp_options["forecasts"].lower().split(',') + if isinstance(self.pestpp_options["forecasts"], str): + return self.pestpp_options["forecasts"].lower().split(",") else: return [f.lower() for f in self.pestpp_options["forecasts"]] elif "predictions" in self.pestpp_options.keys(): if isinstance(self.pestpp_options["predictions"], str): - return self.pestpp_options["predictions"].lower().split(',') + return self.pestpp_options["predictions"].lower().split(",") else: return [f.lower() for f in self.pestpp_options["predictions"]] else: @@ -407,7 +428,7 @@ def nnz_obs_groups(self): """ obs = self.observation_data - og = obs.loc[obs.weight>0.0,"obgnme"].unique().tolist() + og = obs.loc[obs.weight > 0.0, "obgnme"].unique().tolist() return og @property @@ -424,7 +445,6 @@ def adj_par_groups(self): adj_pargp = par.loc[par.partrans.apply(lambda x: x not in tf), "pargp"].unique() return adj_pargp.tolist() - @property def par_groups(self): """get the parameter groups @@ -435,7 +455,6 @@ def par_groups(self): """ return self.parameter_data.pargp.unique().tolist() - @property def prior_groups(self): """get the prior info groups @@ -476,8 +495,8 @@ def adj_par_names(self): """ par = self.parameter_data - tf = set(["tied","fixed"]) - adj_names = par.loc[par.partrans.apply(lambda x: x not in tf),"parnme"] + tf = set(["tied", "fixed"]) + adj_names = par.loc[par.partrans.apply(lambda x: x not in tf), "parnme"] return adj_names.tolist() @property @@ -499,7 +518,7 @@ def nnz_obs_names(self): """ obs = self.observation_data - nz_names = obs.loc[obs.weight>0.0,"obsnme"] + nz_names = obs.loc[obs.weight > 0.0, "obsnme"] return nz_names.tolist() @property @@ -511,8 +530,7 @@ def zero_weight_obs_names(self): """ obs = self.observation_data - return obs.loc[obs.weight==0.0,"obsnme"].tolist() - + return obs.loc[obs.weight == 0.0, "obsnme"].tolist() @property def estimation(self): @@ -535,16 +553,16 @@ def tied(self): """ par = self.parameter_data - tied_pars = par.loc[par.partrans=="tied","parnme"] + tied_pars = par.loc[par.partrans == "tied", "parnme"] if tied_pars.shape[0] == 0: return None if "partied" not in par.columns: - par.loc[:,"partied"] = np.NaN - tied = par.loc[tied_pars,["parnme","partied"]] + par.loc[:, "partied"] = np.NaN + tied = par.loc[tied_pars, ["parnme", "partied"]] return tied @staticmethod - def _read_df(f,nrows,names,converters,defaults=None): + def _read_df(f, nrows, names, converters, defaults=None): """ a private method to read part of an open file into a pandas.DataFrame. Args: @@ -566,35 +584,51 @@ def _read_df(f,nrows,names,converters,defaults=None): if raw[0].lower() == "external": filename = raw[1] if not os.path.exists(filename): - raise Exception("Pst._read_df() error: external file '{0}' not found".format(filename)) - df = pd.read_csv(filename,index_col=False,comment='#') + raise Exception( + "Pst._read_df() error: external file '{0}' not found".format( + filename + ) + ) + df = pd.read_csv(filename, index_col=False, comment="#") df.columns = df.columns.str.lower() for name in names: if name not in df.columns: - raise Exception("Pst._read_df() error: name" +\ - "'{0}' not in external file '{1}' columns".format(name,filename)) + raise Exception( + "Pst._read_df() error: name" + + "'{0}' not in external file '{1}' columns".format( + name, filename + ) + ) if name in converters: - df.loc[:,name] = df.loc[:,name].apply(converters[name]) + df.loc[:, name] = df.loc[:, name].apply(converters[name]) if defaults is not None: for name in names: df.loc[:, name] = df.loc[:, name].fillna(defaults[name]) else: if nrows is None: - raise Exception("Pst._read_df() error: non-external sections require nrows") + raise Exception( + "Pst._read_df() error: non-external sections require nrows" + ) f.seek(seek_point) - df = pd.read_csv(f, header=None,names=names, - nrows=nrows,delim_whitespace=True, - converters=converters, index_col=False, - comment='#') + df = pd.read_csv( + f, + header=None, + names=names, + nrows=nrows, + delim_whitespace=True, + converters=converters, + index_col=False, + comment="#", + ) # in case there was some extra junk at the end of the lines if df.shape[1] > len(names): - df = df.iloc[:,len(names)] + df = df.iloc[:, len(names)] df.columns = names if defaults is not None: for name in names: - df.loc[:,name] = df.loc[:,name].fillna(defaults[name]) + df.loc[:, name] = df.loc[:, name].fillna(defaults[name]) elif np.any(pd.isnull(df).values.flatten()): raise Exception("NANs found") @@ -603,44 +637,42 @@ def _read_df(f,nrows,names,converters,defaults=None): for _ in range(nrows): line = f.readline() extra = np.NaN - if '#' in line: - raw = line.strip().split('#') - extra = ' # '.join(raw[1:]) + if "#" in line: + raw = line.strip().split("#") + extra = " # ".join(raw[1:]) extras.append(extra) - df.loc[:,"extra"] = extras + df.loc[:, "extra"] = extras return df - - def _read_line_comments(self,f,forgive): + def _read_line_comments(self, f, forgive): comments = [] while True: org_line = f.readline() line = org_line.lower().strip() self.lcount += 1 - if org_line == '': + if org_line == "": if forgive: - return None,comments + return None, comments else: raise Exception("unexpected EOF") - if line.startswith("++") and line.split('++')[1].strip()[0] != "#": + if line.startswith("++") and line.split("++")[1].strip()[0] != "#": self._parse_pestpp_line(line) elif "++" in line: comments.append(line.strip()) - elif line.startswith('#'): + elif line.startswith("#"): comments.append(line.strip()) else: break return org_line.strip(), comments - - def _read_section_comments(self,f,forgive): + def _read_section_comments(self, f, forgive): lines = [] section_comments = [] while True: - line,comments = self._read_line_comments(f,forgive) + line, comments = self._read_line_comments(f, forgive) section_comments.extend(comments) if line is None or line.startswith("*"): break @@ -648,18 +680,17 @@ def _read_section_comments(self,f,forgive): continue lines.append(line) - return line,lines,section_comments - + return line, lines, section_comments @staticmethod - def _parse_external_line(line,pst_path="."): + def _parse_external_line(line, pst_path="."): raw = line.strip().split() - existing_path,filename = Pst._parse_path_agnostic(raw[0]) + existing_path, filename = Pst._parse_path_agnostic(raw[0]) if pst_path is not None: if pst_path != ".": - filename = os.path.join(pst_path,filename) + filename = os.path.join(pst_path, filename) else: - filename = os.path.join(existing_path,filename) + filename = os.path.join(existing_path, filename) raw = line.lower().strip().split() options = {} @@ -671,78 +702,83 @@ def _parse_external_line(line,pst_path="."): options = {k.lower(): v.lower() for k, v in zip(raw[1:-1], raw[2:])} return filename, options - @staticmethod def _parse_path_agnostic(filename): - filename = filename.replace("\\",os.sep).replace("/",os.sep) + filename = filename.replace("\\", os.sep).replace("/", os.sep) return os.path.split(filename) - - - @staticmethod - def _cast_df_from_lines(section,lines, fieldnames, converters, defaults,alias_map={},pst_path="."): - #raw = lines[0].strip().split() - #if raw[0].lower() == "external": + def _cast_df_from_lines( + section, lines, fieldnames, converters, defaults, alias_map={}, pst_path="." + ): + # raw = lines[0].strip().split() + # if raw[0].lower() == "external": if section.lower().strip().split()[-1] == "external": dfs = [] for line in lines: - filename,options = Pst._parse_external_line(line, pst_path) + filename, options = Pst._parse_external_line(line, pst_path) if not os.path.exists(filename): - raise Exception("Pst._cast_df_from_lines() error: external file '{0}' not found".format(filename)) - sep = options.get("sep",',') + raise Exception( + "Pst._cast_df_from_lines() error: external file '{0}' not found".format( + filename + ) + ) + sep = options.get("sep", ",") missing_vals = options.get("missing_values", None) - if sep.lower() == 'w': - df = pd.read_csv(filename,delim_whitespace=True,na_values=missing_vals) + if sep.lower() == "w": + df = pd.read_csv( + filename, delim_whitespace=True, na_values=missing_vals + ) else: df = pd.read_csv(filename, sep=sep, na_values=missing_vals) df.columns = df.columns.str.lower() for easy, hard in alias_map.items(): if easy in df.columns and hard in df.columns: - raise Exception("fieldname '{0}' and its alias '{1}' both in '{2}'".format(hard, easy, filename)) + raise Exception( + "fieldname '{0}' and its alias '{1}' both in '{2}'".format( + hard, easy, filename + ) + ) if easy in df.columns: df.loc[:, hard] = df.pop(easy) dfs.append(df) - - df = pd.concat(dfs,axis=0,ignore_index=True) + df = pd.concat(dfs, axis=0, ignore_index=True) else: extra = [] raw = [] - for iline,line in enumerate(lines): + for iline, line in enumerate(lines): line = line.lower() - if '#' in line: - er = line.strip().split('#') - extra.append('#'.join(er[1:])) + if "#" in line: + er = line.strip().split("#") + extra.append("#".join(er[1:])) r = er[0].split() else: r = line.strip().split() extra.append(np.NaN) - raw.append(r[:len(defaults)]) + raw.append(r[: len(defaults)]) - found_fieldnames = fieldnames[:len(raw[0])] - df = pd.DataFrame(raw,columns=found_fieldnames) + found_fieldnames = fieldnames[: len(raw[0])] + df = pd.DataFrame(raw, columns=found_fieldnames) df.loc[:, "extra"] = extra - for col in fieldnames: if col not in df.columns: - df.loc[:,col] = np.NaN + df.loc[:, col] = np.NaN if col in defaults: df.loc[:, col] = df.loc[:, col].fillna(defaults[col]) if col in converters: - df.loc[:,col] = df.loc[:,col].apply(converters[col]) + df.loc[:, col] = df.loc[:, col].apply(converters[col]) return df - - def _cast_prior_df_from_lines(self,section, lines,pst_path="."): + def _cast_prior_df_from_lines(self, section, lines, pst_path="."): if pst_path == ".": pst_path = "" @@ -751,63 +787,81 @@ def _cast_prior_df_from_lines(self,section, lines,pst_path="."): for line in lines: filename, options = Pst._parse_external_line(line, pst_path) if not os.path.exists(filename): - raise Exception("Pst._cast_prior_df_from_lines() error: external file '{0}' not found".format(filename)) - sep = options.get("sep", ',') + raise Exception( + "Pst._cast_prior_df_from_lines() error: external file '{0}' not found".format( + filename + ) + ) + sep = options.get("sep", ",") missing_vals = options.get("missing_values", None) - if sep.lower() == 'w': - df = pd.read_csv(filename, delim_whitespace=True, na_values=missing_vals) + if sep.lower() == "w": + df = pd.read_csv( + filename, delim_whitespace=True, na_values=missing_vals + ) else: df = pd.read_csv(filename, sep=sep, na_values=missing_vals) df.columns = df.columns.str.lower() for field in pst_utils.pst_config["prior_fieldnames"]: if field not in df.columns: - raise Exception("Pst._cast_prior_df_from_lines() error: external file" +\ - "'{0}' missing required field '{1}'".format(filename,field)) + raise Exception( + "Pst._cast_prior_df_from_lines() error: external file" + + "'{0}' missing required field '{1}'".format( + filename, field + ) + ) dfs.append(df) - df = pd.concat(dfs,axis=0,ignore_index=True) + df = pd.concat(dfs, axis=0, ignore_index=True) self.prior_information = df self.prior_information.index = self.prior_information.pilbl - - else: pilbl, obgnme, weight, equation = [], [], [], [] extra = [] for line in lines: - if '#' in line: - er = line.split('#') + if "#" in line: + er = line.split("#") raw = er[0].split() - extra.append('#'.join(er[1:])) + extra.append("#".join(er[1:])) else: extra.append(np.NaN) raw = line.split() pilbl.append(raw[0].lower()) obgnme.append(raw[-1].lower()) weight.append(float(raw[-2])) - eq = ' '.join(raw[1:-2]) + eq = " ".join(raw[1:-2]) equation.append(eq) - self.prior_information = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "weight": weight, - "obgnme": obgnme}) + self.prior_information = pd.DataFrame( + { + "pilbl": pilbl, + "equation": equation, + "weight": weight, + "obgnme": obgnme, + } + ) self.prior_information.index = self.prior_information.pilbl - self.prior_information.loc[:,"extra"] = extra + self.prior_information.loc[:, "extra"] = extra - - def _load_version2(self,filename): + def _load_version2(self, filename): """load a version 2 control file """ - self.lcount = 0 + self.lcount = 0 self.comments = {} self.prior_information = self.null_prior - assert os.path.exists(filename), "couldn't find control file {0}".format(filename) - f = open(filename, 'r') + assert os.path.exists(filename), "couldn't find control file {0}".format( + filename + ) + f = open(filename, "r") pst_path, _ = Pst._parse_path_agnostic(filename) last_section = "" - req_sections = {"* parameter data", "* observation data","* model command line","* control data"} + req_sections = { + "* parameter data", + "* observation data", + "* model command line", + "* control data", + } sections_found = set() while True: @@ -817,10 +871,12 @@ def _load_version2(self,filename): iskeyword = False if "keyword" in last_section.lower(): iskeyword = True - self.pestpp_options = self.control_data.parse_values_from_lines(section_lines, iskeyword=iskeyword) + self.pestpp_options = self.control_data.parse_values_from_lines( + section_lines, iskeyword=iskeyword + ) if len(self.pestpp_options) > 0: ppo = self.pestpp_options - svd_opts = ["svdmode","eigthresh","maxsing","eigwrite"] + svd_opts = ["svdmode", "eigthresh", "maxsing", "eigwrite"] for svd_opt in svd_opts: if svd_opt in ppo: self.svd_data.__setattr__(svd_opt, ppo.pop(svd_opt)) @@ -829,15 +885,20 @@ def _load_version2(self,filename): self.reg_data.__setattr__(reg_opt, ppo.pop(reg_opt)) elif "* singular value decomposition" in last_section.lower(): - self.svd_data.parse_values_from_lines(section_lines) + self.svd_data.parse_values_from_lines(section_lines) elif "* observation groups" in last_section.lower(): pass elif "* parameter groups" in last_section.lower(): - self.parameter_groups = self._cast_df_from_lines(last_section, section_lines, self.pargp_fieldnames, - self.pargp_converters, self.pargp_defaults, - pst_path=pst_path) + self.parameter_groups = self._cast_df_from_lines( + last_section, + section_lines, + self.pargp_fieldnames, + self.pargp_converters, + self.pargp_defaults, + pst_path=pst_path, + ) self.parameter_groups.index = self.parameter_groups.pargpnme elif "* parameter data" in last_section.lower(): @@ -850,24 +911,35 @@ def _load_version2(self,filename): slines = section_lines[:-ntied] else: slines = section_lines - self.parameter_data = self._cast_df_from_lines(last_section, slines, self.par_fieldnames, - self.par_converters, self.par_defaults, - self.par_alias_map,pst_path=pst_path) + self.parameter_data = self._cast_df_from_lines( + last_section, + slines, + self.par_fieldnames, + self.par_converters, + self.par_defaults, + self.par_alias_map, + pst_path=pst_path, + ) self.parameter_data.index = self.parameter_data.parnme if ntied > 0: - tied_pars,partied = [],[] + tied_pars, partied = [], [] for line in section_lines[-ntied:]: raw = line.strip().split() tied_pars.append(raw[0].strip().lower()) partied.append(raw[1].strip().lower()) - self.parameter_data.loc[:,"partied"] = np.NaN - self.parameter_data.loc[tied_pars,"partied"] = partied + self.parameter_data.loc[:, "partied"] = np.NaN + self.parameter_data.loc[tied_pars, "partied"] = partied elif "* observation data" in last_section.lower(): - self.observation_data = self._cast_df_from_lines(last_section, section_lines, self.obs_fieldnames, - self.obs_converters, self.obs_defaults, - pst_path=pst_path) + self.observation_data = self._cast_df_from_lines( + last_section, + section_lines, + self.obs_fieldnames, + self.obs_converters, + self.obs_defaults, + pst_path=pst_path, + ) self.observation_data.index = self.observation_data.obsnme elif "* model command line" in last_section.lower(): @@ -876,26 +948,40 @@ def _load_version2(self,filename): elif "* model input/output" in last_section.lower(): if "* control data" not in sections_found: - raise Exception("attempting to read '* model input/output' before reading "+\ - "'* control data' - need NTPLFLE counter for this...") - if len(section_lines) != self.control_data.ntplfle + self.control_data.ninsfle: - raise Exception("didnt find the right number of '* model input/output' lines,"+\ - "expecting {0} template files and {1} instruction files".\ - format(self.control_data.ntplfle,self.control_data.ninsfle)) + raise Exception( + "attempting to read '* model input/output' before reading " + + "'* control data' - need NTPLFLE counter for this..." + ) + if ( + len(section_lines) + != self.control_data.ntplfle + self.control_data.ninsfle + ): + raise Exception( + "didnt find the right number of '* model input/output' lines," + + "expecting {0} template files and {1} instruction files".format( + self.control_data.ntplfle, self.control_data.ninsfle + ) + ) for i in range(self.control_data.ntplfle): raw = section_lines[i].strip().split() self.template_files.append(raw[0]) self.input_files.append(raw[1]) for j in range(self.control_data.ninsfle): - raw = section_lines[i+j+1].strip().split() + raw = section_lines[i + j + 1].strip().split() self.instruction_files.append(raw[0]) self.output_files.append(raw[1]) elif "* model input" in last_section.lower(): if last_section.strip().split()[-1].lower() == "external": - io_df = self._cast_df_from_lines(last_section, section_lines, ["pest_file","model_file"], - [], [], pst_path=pst_path) + io_df = self._cast_df_from_lines( + last_section, + section_lines, + ["pest_file", "model_file"], + [], + [], + pst_path=pst_path, + ) self.template_files.extend(io_df.pest_file.tolist()) self.input_files.extend(io_df.model_file.tolist()) @@ -907,8 +993,14 @@ def _load_version2(self,filename): elif "* model output" in last_section.lower(): if last_section.strip().split()[-1].lower() == "external": - io_df = self._cast_df_from_lines(last_section, section_lines, ["pest_file", "model_file"], - [], [], pst_path=pst_path) + io_df = self._cast_df_from_lines( + last_section, + section_lines, + ["pest_file", "model_file"], + [], + [], + pst_path=pst_path, + ) self.instruction_files.extend(io_df.pest_file.tolist()) self.output_files.extend(io_df.model_file.tolist()) @@ -919,11 +1011,16 @@ def _load_version2(self,filename): self.output_files.append(raw[1]) elif "* prior information" in last_section.lower(): - self._cast_prior_df_from_lines(last_section, section_lines,pst_path=pst_path) - #self.prior_information = Pst._cast_df_from_lines(last_section,section_lines,self.prior_fieldnames, + self._cast_prior_df_from_lines( + last_section, section_lines, pst_path=pst_path + ) + # self.prior_information = Pst._cast_df_from_lines(last_section,section_lines,self.prior_fieldnames, # self.prior_format,{},pst_path=pst_path) - elif last_section.lower() == "* regularization" or last_section.lower() == "* regularisation": + elif ( + last_section.lower() == "* regularization" + or last_section.lower() == "* regularisation" + ): raw = section_lines[0].strip().split() self.reg_data.phimlim = float(raw[0]) self.reg_data.phimaccept = float(raw[1]) @@ -931,14 +1028,25 @@ def _load_version2(self,filename): self.reg_data.wfinit = float(raw[0]) elif len(last_section) > 0: - print("Pst._load_version2() warning: unrecognized section: ", last_section) + print( + "Pst._load_version2() warning: unrecognized section: ", last_section + ) self.comments[last_section] = section_lines if next_section is None or len(section_lines) == 0: break - next_section_generic = next_section.replace("external","").replace("keyword","").strip().lower() + next_section_generic = ( + next_section.replace("external", "") + .replace("keyword", "") + .strip() + .lower() + ) if next_section_generic in sections_found: - raise Exception("duplicate control file sections for '{0}'".format(next_section_generic)) + raise Exception( + "duplicate control file sections for '{0}'".format( + next_section_generic + ) + ) sections_found.add(next_section_generic) last_section = next_section @@ -948,15 +1056,18 @@ def _load_version2(self,filename): if section not in sections_found: not_found.append(section) if len(not_found) > 0: - raise Exception("Pst._load_version2() error: the following required sections were "+\ - "not found:{0}".format(",".join(not_found))) - if "* model input/output" in sections_found and \ - ("* model input" in sections_found or "* model output" in sections_found): - raise Exception("'* model input/output cant be used with '* model input' or '* model output'") - - - - def load(self,filename): + raise Exception( + "Pst._load_version2() error: the following required sections were " + + "not found:{0}".format(",".join(not_found)) + ) + if "* model input/output" in sections_found and ( + "* model input" in sections_found or "* model output" in sections_found + ): + raise Exception( + "'* model input/output cant be used with '* model input' or '* model output'" + ) + + def load(self, filename): """ entry point load the pest control file. Args: @@ -968,36 +1079,42 @@ def load(self,filename): """ if not os.path.exists(filename): raise Exception("couldn't find control file {0}".format(filename)) - f = open(filename, 'r') + f = open(filename, "r") while True: line = f.readline() if line == "": - raise Exception("Pst.load() error: EOF when trying to find first line - #sad") + raise Exception( + "Pst.load() error: EOF when trying to find first line - #sad" + ) if line.strip().split()[0].lower() == "pcf": break if not line.startswith("pcf"): - raise Exception("Pst.load() error: first non-comment line must start with 'pcf', not '{0}'".format(line)) + raise Exception( + "Pst.load() error: first non-comment line must start with 'pcf', not '{0}'".format( + line + ) + ) self._load_version2(filename) self.try_parse_name_metadata() - - - - def _parse_pestpp_line(self,line): + def _parse_pestpp_line(self, line): # args = line.replace('++','').strip().split() - args = line.replace("++", '').strip().split(')') - args = [a for a in args if a != ''] + args = line.replace("++", "").strip().split(")") + args = [a for a in args if a != ""] # args = ['++'+arg.strip() for arg in args] # self.pestpp_lines.extend(args) - keys = [arg.split('(')[0] for arg in args] - values = [arg.split('(')[1].replace(')', '') for arg in args if '(' in arg] - for _ in range(len(values)-1,len(keys)): - values.append('') + keys = [arg.split("(")[0] for arg in args] + values = [arg.split("(")[1].replace(")", "") for arg in args if "(" in arg] + for _ in range(len(values) - 1, len(keys)): + values.append("") for key, value in zip(keys, values): if key in self.pestpp_options: - print("Pst.load() warning: duplicate pest++ option found:" + str(key),PyemuWarning) + print( + "Pst.load() warning: duplicate pest++ option found:" + str(key), + PyemuWarning, + ) self.pestpp_options[key.lower()] = value def _update_control_section(self): @@ -1009,9 +1126,10 @@ def _update_control_section(self): self.control_data.npar = self.npar self.control_data.nobs = self.nobs self.control_data.npargp = self.parameter_groups.shape[0] - self.control_data.nobsgp = self.observation_data.obgnme.\ - value_counts().shape[0] + self.prior_information.obgnme.\ - value_counts().shape[0] + self.control_data.nobsgp = ( + self.observation_data.obgnme.value_counts().shape[0] + + self.prior_information.obgnme.value_counts().shape[0] + ) self.control_data.nprior = self.prior_information.shape[0] self.control_data.ntplfle = len(self.template_files) @@ -1039,13 +1157,11 @@ def rectify_pgroups(self): """ # add any parameters groups - pdata_groups = list(self.parameter_data.loc[:,"pargp"].\ - value_counts().keys()) - #print(pdata_groups) + pdata_groups = list(self.parameter_data.loc[:, "pargp"].value_counts().keys()) + # print(pdata_groups) need_groups = [] - - if hasattr(self,"parameter_groups"): + if hasattr(self, "parameter_groups"): existing_groups = list(self.parameter_groups.pargpnme) else: existing_groups = [] @@ -1055,24 +1171,26 @@ def rectify_pgroups(self): if pg not in existing_groups: need_groups.append(pg) if len(need_groups) > 0: - #print(need_groups) + # print(need_groups) defaults = copy.copy(pst_utils.pst_config["pargp_defaults"]) for grp in need_groups: defaults["pargpnme"] = grp - self.parameter_groups = \ - self.parameter_groups.append(defaults,ignore_index=True) + self.parameter_groups = self.parameter_groups.append( + defaults, ignore_index=True + ) # now drop any left over groups that aren't needed - for gp in self.parameter_groups.loc[:,"pargpnme"]: + for gp in self.parameter_groups.loc[:, "pargpnme"]: if gp in pdata_groups and gp not in need_groups: need_groups.append(gp) self.parameter_groups.index = self.parameter_groups.pargpnme - self.parameter_groups = self.parameter_groups.loc[need_groups,:] + self.parameter_groups = self.parameter_groups.loc[need_groups, :] idx = self.parameter_groups.index.drop_duplicates() if idx.shape[0] != self.parameter_groups.shape[0]: - warnings.warn("dropping duplicate parameter groups",PyemuWarning) - self.parameter_groups = self.parameter_groups.loc[~self.parameter_groups.\ - index.duplicated(keep='first'),:] + warnings.warn("dropping duplicate parameter groups", PyemuWarning) + self.parameter_groups = self.parameter_groups.loc[ + ~self.parameter_groups.index.duplicated(keep="first"), : + ] def _parse_pi_par_names(self): """ private method to get the parameter names from prior information @@ -1087,11 +1205,18 @@ def _parse_pi_par_names(self): self.prior_information.pop("names") if "rhs" in self.prior_information.columns: self.prior_information.pop("rhs") + def parse(eqs): - raw = eqs.split('=') - #rhs = float(raw[1]) - raw = [i for i in re.split('[###]', - raw[0].lower().strip().replace(' + ','###').replace(' - ','###')) if i != ''] + raw = eqs.split("=") + # rhs = float(raw[1]) + raw = [ + i + for i in re.split( + "[###]", + raw[0].lower().strip().replace(" + ", "###").replace(" - ", "###"), + ) + if i != "" + ] # in case of a leading '-' or '+' if len(raw[0]) == 0: raw = raw[1:] @@ -1102,14 +1227,25 @@ def parse(eqs): # pname = r.split('*')[1].replace("log(", '').replace(')', '').strip() # pnames.append(pname) # return pnames - return [r.split('*')[1].replace("log(",'').replace(')','').strip() for r in raw if '*' in r] - - self.prior_information.loc[:,"names"] =\ - self.prior_information.equation.apply(lambda x: parse(x)) - - - def add_pi_equation(self,par_names,pilbl=None,rhs=0.0,weight=1.0, - obs_group="pi_obgnme",coef_dict={}): + return [ + r.split("*")[1].replace("log(", "").replace(")", "").strip() + for r in raw + if "*" in r + ] + + self.prior_information.loc[:, "names"] = self.prior_information.equation.apply( + lambda x: parse(x) + ) + + def add_pi_equation( + self, + par_names, + pilbl=None, + rhs=0.0, + weight=1.0, + obs_group="pi_obgnme", + coef_dict={}, + ): """ a helper to construct a new prior information equation. Args: @@ -1128,35 +1264,40 @@ def add_pi_equation(self,par_names,pilbl=None,rhs=0.0,weight=1.0, if pilbl is None: pilbl = "pilbl_{0}".format(self.__pi_count) self.__pi_count += 1 - missing,fixed = [],[] + missing, fixed = [], [] for par_name in par_names: if par_name not in self.parameter_data.parnme: missing.append(par_name) - elif self.parameter_data.loc[par_name,"partrans"] in ["fixed","tied"]: + elif self.parameter_data.loc[par_name, "partrans"] in ["fixed", "tied"]: fixed.append(par_name) if len(missing) > 0: - raise Exception("Pst.add_pi_equation(): the following pars "+\ - " were not found: {0}".format(','.join(missing))) + raise Exception( + "Pst.add_pi_equation(): the following pars " + + " were not found: {0}".format(",".join(missing)) + ) if len(fixed) > 0: - raise Exception("Pst.add_pi_equation(): the following pars "+\ - " were are fixed/tied: {0}".format(','.join(missing))) - eqs_str = '' - sign = '' - for i,par_name in enumerate(par_names): - coef = coef_dict.get(par_name,1.0) + raise Exception( + "Pst.add_pi_equation(): the following pars " + + " were are fixed/tied: {0}".format(",".join(missing)) + ) + eqs_str = "" + sign = "" + for i, par_name in enumerate(par_names): + coef = coef_dict.get(par_name, 1.0) if coef < 0.0: - sign = '-' + sign = "-" coef = np.abs(coef) - elif i > 0: sign = '+' - if self.parameter_data.loc[par_name,"partrans"] == "log": + elif i > 0: + sign = "+" + if self.parameter_data.loc[par_name, "partrans"] == "log": par_name = "log({})".format(par_name) - eqs_str += " {0} {1} * {2} ".format(sign,coef,par_name) + eqs_str += " {0} {1} * {2} ".format(sign, coef, par_name) eqs_str += " = {0}".format(rhs) - self.prior_information.loc[pilbl,"pilbl"] = pilbl - self.prior_information.loc[pilbl,"equation"] = eqs_str - self.prior_information.loc[pilbl,"weight"] = weight - self.prior_information.loc[pilbl,"obgnme"] = obs_group + self.prior_information.loc[pilbl, "pilbl"] = pilbl + self.prior_information.loc[pilbl, "equation"] = eqs_str + self.prior_information.loc[pilbl, "weight"] = weight + self.prior_information.loc[pilbl, "obgnme"] = obs_group def rectify_pi(self): """ rectify the prior information equation with the current state of the @@ -1173,43 +1314,56 @@ def rectify_pi(self): return self._parse_pi_par_names() adj_names = self.adj_par_names + def is_good(names): for n in names: if n not in adj_names: return False return True - keep_idx = self.prior_information.names.\ - apply(lambda x: is_good(x)) - self.prior_information = self.prior_information.loc[keep_idx,:] - def _write_df(self,name,f,df,formatters,columns): - if name.startswith('*'): - f.write(name+'\n') + keep_idx = self.prior_information.names.apply(lambda x: is_good(x)) + self.prior_information = self.prior_information.loc[keep_idx, :] + + def _write_df(self, name, f, df, formatters, columns): + if name.startswith("*"): + f.write(name + "\n") if self.with_comments: for line in self.comments.get(name, []): - f.write(line+'\n') - if df.loc[:,columns].isnull().values.any(): - #warnings.warn("WARNING: NaNs in {0} dataframe".format(name)) - csv_name = "pst.{0}.nans.csv".format(name.replace(" ",'_').replace('*','')) + f.write(line + "\n") + if df.loc[:, columns].isnull().values.any(): + # warnings.warn("WARNING: NaNs in {0} dataframe".format(name)) + csv_name = "pst.{0}.nans.csv".format( + name.replace(" ", "_").replace("*", "") + ) df.to_csv(csv_name) - raise Exception("NaNs in {0} dataframe, csv written to {1}".format(name, csv_name)) + raise Exception( + "NaNs in {0} dataframe, csv written to {1}".format(name, csv_name) + ) + def ext_fmt(x): if pd.notnull(x): return " # {0}".format(x) - return '' - if self.with_comments and 'extra' in df.columns: - df.loc[:,"extra_str"] = df.extra.apply(ext_fmt) + return "" + + if self.with_comments and "extra" in df.columns: + df.loc[:, "extra_str"] = df.extra.apply(ext_fmt) columns.append("extra_str") - #formatters["extra"] = lambda x: " # {0}".format(x) if pd.notnull(x) else 'test' - #formatters["extra"] = lambda x: ext_fmt(x) + # formatters["extra"] = lambda x: " # {0}".format(x) if pd.notnull(x) else 'test' + # formatters["extra"] = lambda x: ext_fmt(x) # only write out the dataframe if it contains data - could be empty if len(df) > 0: - f.write(df.to_string(col_space=0,formatters=formatters, - columns=columns, - justify="right", - header=False, - index=False) + '\n') + f.write( + df.to_string( + col_space=0, + formatters=formatters, + columns=columns, + justify="right", + header=False, + index=False, + ) + + "\n" + ) def sanity_checks(self): """some basic check for strangeness @@ -1221,25 +1375,29 @@ def sanity_checks(self): """ dups = self.parameter_data.parnme.value_counts() - dups = dups.loc[dups>1] + dups = dups.loc[dups > 1] if dups.shape[0] > 0: - warnings.warn("duplicate parameter names: {0}".format(','.join(list(dups.index))),PyemuWarning) + warnings.warn( + "duplicate parameter names: {0}".format(",".join(list(dups.index))), + PyemuWarning, + ) dups = self.observation_data.obsnme.value_counts() - dups = dups.loc[dups>1] + dups = dups.loc[dups > 1] if dups.shape[0] > 0: - warnings.warn("duplicate observation names: {0}".format(','.join(list(dups.index))),PyemuWarning) + warnings.warn( + "duplicate observation names: {0}".format(",".join(list(dups.index))), + PyemuWarning, + ) if self.npar_adj == 0: - warnings.warn("no adjustable pars",PyemuWarning) + warnings.warn("no adjustable pars", PyemuWarning) if self.nnz_obs == 0: - warnings.warn("no non-zero weight obs",PyemuWarning) - - #print("noptmax: {0}".format(self.control_data.noptmax)) - + warnings.warn("no non-zero weight obs", PyemuWarning) + # print("noptmax: {0}".format(self.control_data.noptmax)) - def _write_version2(self,new_filename,use_pst_path=True,pst_rel_path="."): + def _write_version2(self, new_filename, use_pst_path=True, pst_rel_path="."): pst_path = None if use_pst_path: pst_path, _ = Pst._parse_path_agnostic(new_filename) @@ -1252,47 +1410,47 @@ def _write_version2(self,new_filename,use_pst_path=True,pst_rel_path="."): self._update_control_section() self.sanity_checks() - f_out = open(new_filename, 'w') + f_out = open(new_filename, "w") if self.with_comments: for line in self.comments.get("initial", []): - f_out.write(line + '\n') + f_out.write(line + "\n") f_out.write("pcf version=2\n") self.control_data.write_keyword(f_out) if self.with_comments: - for line in self.comments.get("* singular value decomposition",[]): + for line in self.comments.get("* singular value decomposition", []): f_out.write(line) self.svd_data.write_keyword(f_out) if self.control_data.pestmode.lower().startswith("r"): self.reg_data.write_keyword(f_out) - for k,v in self.pestpp_options.items(): - if isinstance(v,list) or isinstance(v, tuple): - v = ','.join([str(vv) for vv in list(v)]) - f_out.write("{0:30} {1}\n".format(k,v)) + for k, v in self.pestpp_options.items(): + if isinstance(v, list) or isinstance(v, tuple): + v = ",".join([str(vv) for vv in list(v)]) + f_out.write("{0:30} {1}\n".format(k, v)) f_out.write("* parameter groups external\n") - pargp_filename = new_filename.lower().replace(".pst",".pargrp_data.csv") + pargp_filename = new_filename.lower().replace(".pst", ".pargrp_data.csv") if pst_path is not None: - pargp_filename = os.path.join(pst_path,os.path.split(pargp_filename)[-1]) - self.parameter_groups.to_csv(pargp_filename,index=False) - pargp_filename = os.path.join(pst_rel_path,os.path.split(pargp_filename)[-1]) + pargp_filename = os.path.join(pst_path, os.path.split(pargp_filename)[-1]) + self.parameter_groups.to_csv(pargp_filename, index=False) + pargp_filename = os.path.join(pst_rel_path, os.path.split(pargp_filename)[-1]) f_out.write("{0}\n".format(pargp_filename)) f_out.write("* parameter data external\n") par_filename = new_filename.lower().replace(".pst", ".par_data.csv") if pst_path is not None: - par_filename = os.path.join(pst_path,os.path.split(par_filename)[-1]) - self.parameter_data.to_csv(par_filename,index=False) + par_filename = os.path.join(pst_path, os.path.split(par_filename)[-1]) + self.parameter_data.to_csv(par_filename, index=False) par_filename = os.path.join(pst_rel_path, os.path.split(par_filename)[-1]) f_out.write("{0}\n".format(par_filename)) f_out.write("* observation data external\n") obs_filename = new_filename.lower().replace(".pst", ".obs_data.csv") if pst_path is not None: - obs_filename = os.path.join(pst_path,os.path.split(obs_filename)[-1]) - self.observation_data.to_csv(obs_filename,index=False) + obs_filename = os.path.join(pst_path, os.path.split(obs_filename)[-1]) + self.observation_data.to_csv(obs_filename, index=False) obs_filename = os.path.join(pst_rel_path, os.path.split(obs_filename)[-1]) f_out.write("{0}\n".format(obs_filename)) @@ -1301,26 +1459,26 @@ def _write_version2(self,new_filename,use_pst_path=True,pst_rel_path="."): f_out.write("{0}\n".format(mc)) f_out.write("* model input external\n") - io_filename = new_filename.lower().replace(".pst",".tplfile_data.csv") + io_filename = new_filename.lower().replace(".pst", ".tplfile_data.csv") if pst_path is not None: - io_filename = os.path.join(pst_path,os.path.split(io_filename)[-1]) + io_filename = os.path.join(pst_path, os.path.split(io_filename)[-1]) pfiles = self.template_files - #pfiles.extend(self.instruction_files) + # pfiles.extend(self.instruction_files) mfiles = self.input_files - #mfiles.extend(self.output_files) - io_df = pd.DataFrame({"pest_file":pfiles,"model_file":mfiles}) - io_df.to_csv(io_filename,index=False) + # mfiles.extend(self.output_files) + io_df = pd.DataFrame({"pest_file": pfiles, "model_file": mfiles}) + io_df.to_csv(io_filename, index=False) io_filename = os.path.join(pst_rel_path, os.path.split(io_filename)[-1]) f_out.write("{0}\n".format(io_filename)) f_out.write("* model output external\n") io_filename = new_filename.lower().replace(".pst", ".insfile_data.csv") if pst_path is not None: - io_filename = os.path.join(pst_path,os.path.split(io_filename)[-1]) + io_filename = os.path.join(pst_path, os.path.split(io_filename)[-1]) pfiles = self.instruction_files mfiles = self.output_files io_df = pd.DataFrame({"pest_file": pfiles, "model_file": mfiles}) - io_df.to_csv(io_filename,index=False) + io_df.to_csv(io_filename, index=False) io_filename = os.path.join(pst_rel_path, os.path.split(io_filename)[-1]) f_out.write("{0}\n".format(io_filename)) @@ -1329,13 +1487,13 @@ def _write_version2(self,new_filename,use_pst_path=True,pst_rel_path="."): pi_filename = new_filename.lower().replace(".pst", ".pi_data.csv") if pst_path is not None: pi_filename = os.path.join(pst_path, os.path.split(pi_filename)[-1]) - self.prior_information.to_csv(pi_filename,index=False) + self.prior_information.to_csv(pi_filename, index=False) pi_filename = os.path.join(pst_rel_path, os.path.split(pi_filename)[-1]) f_out.write("{0}\n".format(pi_filename)) f_out.close() - def write(self,new_filename,version=1): + def write(self, new_filename, version=1): """main entry point to write a pest control file. Args: @@ -1353,9 +1511,9 @@ def write(self,new_filename,version=1): """ - - vstring = "noptmax:{0}, npar_adj:{1}, nnz_obs:{2}".format(self.control_data.noptmax, - self.npar_adj,self.nnz_obs) + vstring = "noptmax:{0}, npar_adj:{1}, nnz_obs:{2}".format( + self.control_data.noptmax, self.npar_adj, self.nnz_obs + ) print(vstring) if version == 1: @@ -1363,9 +1521,11 @@ def write(self,new_filename,version=1): elif version == 2: return self._write_version2(new_filename=new_filename) else: - raise Exception("Pst.write() error: version must be 1 or 2, not '{0}'".format(version)) + raise Exception( + "Pst.write() error: version must be 1 or 2, not '{0}'".format(version) + ) - def _write_version1(self,new_filename): + def _write_version1(self, new_filename): """write a version 1 pest control file @@ -1377,37 +1537,53 @@ def _write_version1(self,new_filename): self._update_control_section() self.sanity_checks() - f_out = open(new_filename, 'w') + f_out = open(new_filename, "w") if self.with_comments: - for line in self.comments.get("initial",[]): - f_out.write(line+'\n') + for line in self.comments.get("initial", []): + f_out.write(line + "\n") f_out.write("pcf\n* control data\n") self.control_data.write(f_out) # for line in self.other_lines: # f_out.write(line) if self.with_comments: - for line in self.comments.get("* singular value decompisition",[]): + for line in self.comments.get("* singular value decompisition", []): f_out.write(line) self.svd_data.write(f_out) - #f_out.write("* parameter groups\n") + # f_out.write("* parameter groups\n") # to catch the byte code ugliness in python 3 - pargpnme = self.parameter_groups.loc[:,"pargpnme"].copy() - self.parameter_groups.loc[:,"pargpnme"] = \ - self.parameter_groups.pargpnme.apply(self.pargp_format["pargpnme"]) - - self._write_df("* parameter groups", f_out, self.parameter_groups, - self.pargp_format, self.pargp_fieldnames) - self.parameter_groups.loc[:,"pargpnme"] = pargpnme - - self._write_df("* parameter data",f_out, self.parameter_data, - self.par_format, self.par_fieldnames) + pargpnme = self.parameter_groups.loc[:, "pargpnme"].copy() + self.parameter_groups.loc[:, "pargpnme"] = self.parameter_groups.pargpnme.apply( + self.pargp_format["pargpnme"] + ) + + self._write_df( + "* parameter groups", + f_out, + self.parameter_groups, + self.pargp_format, + self.pargp_fieldnames, + ) + self.parameter_groups.loc[:, "pargpnme"] = pargpnme + + self._write_df( + "* parameter data", + f_out, + self.parameter_data, + self.par_format, + self.par_fieldnames, + ) if self.tied is not None: - self._write_df("tied parameter data", f_out, self.tied, - self.tied_format, self.tied_fieldnames) + self._write_df( + "tied parameter data", + f_out, + self.tied, + self.tied_format, + self.tied_fieldnames, + ) f_out.write("* observation groups\n") for group in self.obs_groups: @@ -1415,44 +1591,49 @@ def _write_version1(self,new_filename): group = group.decode() except Exception as e: pass - f_out.write(pst_utils.SFMT(str(group))+'\n') + f_out.write(pst_utils.SFMT(str(group)) + "\n") for group in self.prior_groups: try: group = group.decode() except Exception as e: pass - f_out.write(pst_utils.SFMT(str(group))+'\n') - self._write_df("* observation data", f_out, self.observation_data, - self.obs_format, self.obs_fieldnames) + f_out.write(pst_utils.SFMT(str(group)) + "\n") + self._write_df( + "* observation data", + f_out, + self.observation_data, + self.obs_format, + self.obs_fieldnames, + ) f_out.write("* model command line\n") for cline in self.model_command: - f_out.write(cline+'\n') + f_out.write(cline + "\n") f_out.write("* model input/output\n") - for tplfle,infle in zip(self.template_files,self.input_files): - f_out.write('{0} {1}\n'.format(tplfle,infle)) - for insfle,outfle in zip(self.instruction_files,self.output_files): - f_out.write("{0} {1}\n".format(insfle,outfle)) + for tplfle, infle in zip(self.template_files, self.input_files): + f_out.write("{0} {1}\n".format(tplfle, infle)) + for insfle, outfle in zip(self.instruction_files, self.output_files): + f_out.write("{0} {1}\n".format(insfle, outfle)) if self.nprior > 0: if self.prior_information.isnull().values.any(): - #print("WARNING: NaNs in prior_information dataframe") - warnings.warn("NaNs in prior_information dataframe",PyemuWarning) + # print("WARNING: NaNs in prior_information dataframe") + warnings.warn("NaNs in prior_information dataframe", PyemuWarning) f_out.write("* prior information\n") - #self.prior_information.index = self.prior_information.pop("pilbl") - max_eq_len = self.prior_information.equation.apply(lambda x:len(x)).max() - eq_fmt_str = " {0:<" + str(max_eq_len) + "s} " - eq_fmt_func = lambda x:eq_fmt_str.format(x) + # self.prior_information.index = self.prior_information.pop("pilbl") + max_eq_len = self.prior_information.equation.apply(lambda x: len(x)).max() + eq_fmt_str = " {0:<" + str(max_eq_len) + "s} " + eq_fmt_func = lambda x: eq_fmt_str.format(x) # 17/9/2016 - had to go with a custom writer loop b/c pandas doesn't want to # output strings longer than 100, even with display.max_colwidth - #f_out.write(self.prior_information.to_string(col_space=0, + # f_out.write(self.prior_information.to_string(col_space=0, # columns=self.prior_fieldnames, # formatters=pi_formatters, # justify="right", # header=False, # index=False) + '\n') - #self.prior_information["pilbl"] = self.prior_information.index + # self.prior_information["pilbl"] = self.prior_information.index # for idx,row in self.prior_information.iterrows(): # f_out.write(pst_utils.SFMT(row["pilbl"])) # f_out.write(eq_fmt_func(row["equation"])) @@ -1463,29 +1644,29 @@ def _write_version1(self,new_filename): f_out.write(eq_fmt_func(row["equation"])) f_out.write(pst_utils.FFMT(row["weight"])) f_out.write(pst_utils.SFMT(row["obgnme"])) - if self.with_comments and 'extra' in row: - f_out.write(" # {0}".format(row['extra'])) - f_out.write('\n') + if self.with_comments and "extra" in row: + f_out.write(" # {0}".format(row["extra"])) + f_out.write("\n") if self.control_data.pestmode.startswith("regul"): - #f_out.write("* regularisation\n") - #if update_regul or len(self.regul_lines) == 0: + # f_out.write("* regularisation\n") + # if update_regul or len(self.regul_lines) == 0: # f_out.write(self.regul_section) - #else: + # else: # [f_out.write(line) for line in self.regul_lines] self.reg_data.write(f_out) for line in self.other_lines: - f_out.write(line+'\n') + f_out.write(line + "\n") - for key,value in self.pestpp_options.items(): - if isinstance(value,list) or isinstance(value, tuple): - value = ','.join([str(v) for v in list(value)]) - f_out.write("++{0}({1})\n".format(str(key),str(value))) + for key, value in self.pestpp_options.items(): + if isinstance(value, list) or isinstance(value, tuple): + value = ",".join([str(v) for v in list(value)]) + f_out.write("++{0}({1})\n".format(str(key), str(value))) if self.with_comments: - for line in self.comments.get("final",[]): - f_out.write(line+'\n') + for line in self.comments.get("final", []): + f_out.write(line + "\n") f_out.close() @@ -1506,82 +1687,115 @@ def bounds_report(self, iterations=None): pst = pyemu.Pst("my.pst") df = pst.bound_report(iterations=[0,2,3]) - """ + """ # sort out which files are parameter files and parse pstroot from pst directory pstroot = self.filename - if pstroot.lower().endswith('.pst'): + if pstroot.lower().endswith(".pst"): pstroot = pstroot[:-4] pstdir = os.path.dirname(pstroot) if len(pstdir) == 0: - pstdir = '.' + pstdir = "." pstroot = os.path.basename(pstroot) # find all the par files - parfiles = glob.glob(os.path.join(pstdir,'{}*.par'.format(pstroot))) - + parfiles = glob.glob(os.path.join(pstdir, "{}*.par".format(pstroot))) + # exception if no par files found if len(parfiles) == 0: - raise Exception("no par files with root {} in directory {}".format(pstdir,pstroot)) - - is_ies = any(['base' in i.lower() for i in parfiles]) + raise Exception( + "no par files with root {} in directory {}".format(pstdir, pstroot) + ) + + is_ies = any(["base" in i.lower() for i in parfiles]) # decide which iterations we care about if is_ies: - iters = [os.path.basename(cf).replace(pstroot,'').split('.')[1] for cf in parfiles if 'base' in cf.lower()] - iters = [int(i) for i in iters if i != 'base'] - parfiles = [i for i in parfiles if 'base' in i] + iters = [ + os.path.basename(cf).replace(pstroot, "").split(".")[1] + for cf in parfiles + if "base" in cf.lower() + ] + iters = [int(i) for i in iters if i != "base"] + parfiles = [i for i in parfiles if "base" in i] else: - iters = [os.path.basename(cf).replace(pstroot,'').split('.')[1] for cf in parfiles if 'base' not in cf.lower()] - iters = [int(i) for i in iters if i != 'par'] - parfiles = [i for i in parfiles if 'base' not in i] - + iters = [ + os.path.basename(cf).replace(pstroot, "").split(".")[1] + for cf in parfiles + if "base" not in cf.lower() + ] + iters = [int(i) for i in iters if i != "par"] + parfiles = [i for i in parfiles if "base" not in i] + if iterations is None: iterations = iters - - + if isinstance(iterations, tuple): iterations = list(iterations) - + if not isinstance(iterations, list): iterations = [iterations] - + # sort the iterations to go through them in order iterations.sort() - + # set up a DataFrame with bounds and into which to put the par values - allpars = self.parameter_data[['parlbnd','parubnd', 'pargp']].copy() - + allpars = self.parameter_data[["parlbnd", "parubnd", "pargp"]].copy() + # loop over iterations and calculate which are at upper and lower bounds for citer in iterations: try: - tmp = pd.read_csv(os.path.join(pstdir,'{}.{}.base.par'.format(pstroot,citer)), skiprows=1, index_col=0, - usecols =[0,1], delim_whitespace=True, header=None) + tmp = pd.read_csv( + os.path.join(pstdir, "{}.{}.base.par".format(pstroot, citer)), + skiprows=1, + index_col=0, + usecols=[0, 1], + delim_whitespace=True, + header=None, + ) except FileNotFoundError: - raise Exception("iteration {} does not have a paramter file associated with it in {}".format(citer,pstdir)) - tmp.columns = ['pars_iter_{}'.format(citer)] - allpars=allpars.merge(tmp, left_index =True, right_index=True) - allpars['at_upper_bound_{}'.format(citer)] = allpars['pars_iter_{}'.format(citer)] >= allpars['parubnd'] - allpars['at_lower_bound_{}'.format(citer)] = allpars['pars_iter_{}'.format(citer)] <= allpars['parlbnd'] + raise Exception( + "iteration {} does not have a paramter file associated with it in {}".format( + citer, pstdir + ) + ) + tmp.columns = ["pars_iter_{}".format(citer)] + allpars = allpars.merge(tmp, left_index=True, right_index=True) + allpars["at_upper_bound_{}".format(citer)] = ( + allpars["pars_iter_{}".format(citer)] >= allpars["parubnd"] + ) + allpars["at_lower_bound_{}".format(citer)] = ( + allpars["pars_iter_{}".format(citer)] <= allpars["parlbnd"] + ) # sum up by groups - df = allpars.groupby('pargp').sum()[[i for i in allpars.columns if i.startswith('at_')]].astype(int) - - + df = ( + allpars.groupby("pargp") + .sum()[[i for i in allpars.columns if i.startswith("at_")]] + .astype(int) + ) + # add the total - df.loc['total'] = df.sum() - + df.loc["total"] = df.sum() + # sum up upper and lower bounds cols = [] for citer in iterations: - df['at_either_bound_{}'.format(citer)] = df['at_upper_bound_{}'.format(citer)] + \ - df['at_lower_bound_{}'.format(citer)] - cols.extend(['at_either_bound_{}'.format(citer), - 'at_lower_bound_{}'.format(citer), - 'at_upper_bound_{}'.format(citer)]) - + df["at_either_bound_{}".format(citer)] = ( + df["at_upper_bound_{}".format(citer)] + + df["at_lower_bound_{}".format(citer)] + ) + cols.extend( + [ + "at_either_bound_{}".format(citer), + "at_lower_bound_{}".format(citer), + "at_upper_bound_{}".format(citer), + ] + ) + # reorder by iterations and return return df[cols] - + # loop over the iterations and count the pars at bounds + def get(self, par_names=None, obs_names=None): """get a new pst object with subset of parameters and/or observations @@ -1602,7 +1816,7 @@ def get(self, par_names=None, obs_names=None): """ - #if par_names is None and obs_names is None: + # if par_names is None and obs_names is None: # return copy.deepcopy(self) if par_names is None: par_names = self.parameter_data.parnme @@ -1646,17 +1860,19 @@ def get(self, par_names=None, obs_names=None): new_pst.output_files = self.output_files if self.tied is not None: - warnings.warn("Pst.get() not checking for tied parameter " + - "compatibility in new Pst instance",PyemuWarning) - #new_pst.tied = self.tied.copy() + warnings.warn( + "Pst.get() not checking for tied parameter " + + "compatibility in new Pst instance", + PyemuWarning, + ) + # new_pst.tied = self.tied.copy() new_pst.other_lines = self.other_lines new_pst.pestpp_options = self.pestpp_options new_pst.regul_lines = self.regul_lines return new_pst - - def parrep(self, parfile=None,enforce_bounds=True): + def parrep(self, parfile=None, enforce_bounds=True): """replicates the pest parrep util. replaces the parval1 field in the parameter data section dataframe with values in a PEST parameter file @@ -1680,13 +1896,11 @@ def parrep(self, parfile=None,enforce_bounds=True): if enforce_bounds: par = self.parameter_data - idx = par.loc[par.parval1 > par.parubnd,"parnme"] - par.loc[idx,"parval1"] = par.loc[idx,"parubnd"] - idx = par.loc[par.parval1 < par.parlbnd,"parnme"] + idx = par.loc[par.parval1 > par.parubnd, "parnme"] + par.loc[idx, "parval1"] = par.loc[idx, "parubnd"] + idx = par.loc[par.parval1 < par.parlbnd, "parnme"] par.loc[idx, "parval1"] = par.loc[idx, "parlbnd"] - - # jwhite - 13 Aug 2019 - gonna remove this because the rec file format is changing a lot # and that makes this method dangerous # def adjust_weights_recfile(self, recfile=None,original_ceiling=True): @@ -1730,7 +1944,6 @@ def parrep(self, parfile=None,enforce_bounds=True): # self._adjust_weights_by_phi_components( # iter_components[last_complete_iter],original_ceiling) - # jwhite - 13 Aug 2019 - removing this one because it has been replaced # by adjust_weights_discrepancy (and there are too many adjust_weights methods) # def adjust_weights_resfile(self, resfile=None,original_ceiling=True): @@ -1759,7 +1972,9 @@ def parrep(self, parfile=None,enforce_bounds=True): # phi_comps = self.phi_components # self._adjust_weights_by_phi_components(phi_comps,original_ceiling) - def adjust_weights_discrepancy(self, resfile=None,original_ceiling=True, bygroups=False): + def adjust_weights_discrepancy( + self, resfile=None, original_ceiling=True, bygroups=False + ): """adjusts the weights of each non-zero weight observation based on the residual in the pest residual file so each observations contribution to phi is 1.0 (e.g. Mozorov's discrepancy principal) @@ -1788,18 +2003,16 @@ def adjust_weights_discrepancy(self, resfile=None,original_ceiling=True, bygroup self.__res = None if bygroups: phi_comps = self.phi_components - self._adjust_weights_by_phi_components(phi_comps,original_ceiling) + self._adjust_weights_by_phi_components(phi_comps, original_ceiling) else: - obs = self.observation_data.loc[self.nnz_obs_names,:] - swr = (self.res.loc[self.nnz_obs_names,:].residual * obs.weight)**2 - factors = (1.0/swr).apply(np.sqrt) + obs = self.observation_data.loc[self.nnz_obs_names, :] + swr = (self.res.loc[self.nnz_obs_names, :].residual * obs.weight) ** 2 + factors = (1.0 / swr).apply(np.sqrt) if original_ceiling: factors = factors.apply(lambda x: 1.0 if x > 1.0 else x) - self.observation_data.loc[self.nnz_obs_names,"weight"] *= factors - + self.observation_data.loc[self.nnz_obs_names, "weight"] *= factors - - def _adjust_weights_by_phi_components(self, components,original_ceiling): + def _adjust_weights_by_phi_components(self, components, original_ceiling): """private method that resets the weights of observations by group to account for residual phi components. @@ -1812,24 +2025,32 @@ def _adjust_weights_by_phi_components(self, components,original_ceiling): nz_groups = obs.groupby(obs["weight"].map(lambda x: x == 0)).groups ogroups = obs.groupby("obgnme").groups for ogroup, idxs in ogroups.items(): - if self.control_data.pestmode.startswith("regul") \ - and "regul" in ogroup.lower(): + if ( + self.control_data.pestmode.startswith("regul") + and "regul" in ogroup.lower() + ): continue og_phi = components[ogroup] - nz_groups = obs.loc[idxs,:].groupby(obs.loc[idxs,"weight"].\ - map(lambda x: x == 0)).groups + nz_groups = ( + obs.loc[idxs, :] + .groupby(obs.loc[idxs, "weight"].map(lambda x: x == 0)) + .groups + ) og_nzobs = 0 if False in nz_groups.keys(): og_nzobs = len(nz_groups[False]) if og_nzobs == 0 and og_phi > 0: - raise Exception("Pst.adjust_weights_by_phi_components():" - " no obs with nonzero weight," + - " but phi > 0 for group:" + str(ogroup)) + raise Exception( + "Pst.adjust_weights_by_phi_components():" + " no obs with nonzero weight," + + " but phi > 0 for group:" + + str(ogroup) + ) if og_phi > 0: factor = np.sqrt(float(og_nzobs) / float(og_phi)) if original_ceiling: - factor = min(factor,1.0) - obs.loc[idxs,"weight"] = obs.weight[idxs] * factor + factor = min(factor, 1.0) + obs.loc[idxs, "weight"] = obs.weight[idxs] * factor self.observation_data = obs def __reset_weights(self, target_phis, res_idxs, obs_idxs): @@ -1849,22 +2070,39 @@ def __reset_weights(self, target_phis, res_idxs, obs_idxs): res = self.res for item in target_phis.keys(): if item not in res_idxs.keys(): - raise Exception("Pst.__reset_weights(): " + str(item) +\ - " not in residual group indices") + raise Exception( + "Pst.__reset_weights(): " + + str(item) + + " not in residual group indices" + ) if item not in obs_idxs.keys(): - raise Exception("Pst.__reset_weights(): " + str(item) +\ - " not in observation group indices") - #actual_phi = ((self.res.loc[res_idxs[item], "residual"] * + raise Exception( + "Pst.__reset_weights(): " + + str(item) + + " not in observation group indices" + ) + # actual_phi = ((self.res.loc[res_idxs[item], "residual"] * # self.observation_data.loc # [obs_idxs[item], "weight"])**2).sum() - actual_phi = (((obs.loc[obs_idxs[item],"obsval"] - res.loc[res_idxs[item], "modelled"]) * - self.observation_data.loc[obs_idxs[item], "weight"])**2).sum() + actual_phi = ( + ( + ( + obs.loc[obs_idxs[item], "obsval"] + - res.loc[res_idxs[item], "modelled"] + ) + * self.observation_data.loc[obs_idxs[item], "weight"] + ) + ** 2 + ).sum() if actual_phi > 0.0: weight_mult = np.sqrt(target_phis[item] / actual_phi) self.observation_data.loc[obs_idxs[item], "weight"] *= weight_mult else: - ("Pst.__reset_weights() warning: phi group {0} has zero phi, skipping...".format(item)) - + ( + "Pst.__reset_weights() warning: phi group {0} has zero phi, skipping...".format( + item + ) + ) def _adjust_weights_by_list(self, obslist, weight): """a private method to reset the weight for a list of observation names. Supports the @@ -1884,11 +2122,13 @@ def _adjust_weights_by_list(self, obslist, weight): # obs.weight.apply(lambda x:x==0.0)]).groups # if (True,True) in groups: # obs.loc[groups[True,True],"weight"] = weight - reset_names = obs.loc[obs.apply(lambda x: x.obsnme in obslist and x.weight == 0, axis=1), "obsnme"] + reset_names = obs.loc[ + obs.apply(lambda x: x.obsnme in obslist and x.weight == 0, axis=1), "obsnme" + ] if len(reset_names) > 0: obs.loc[reset_names, "weight"] = weight - def adjust_weights(self,obs_dict=None, obsgrp_dict=None): + def adjust_weights(self, obs_dict=None, obsgrp_dict=None): """reset the weights of observations or observation groups to contribute a specified amount to the composite objective function @@ -1925,8 +2165,8 @@ def adjust_weights(self,obs_dict=None, obsgrp_dict=None): # reset groups with all zero weights obs = self.observation_data for grp in obsgrp_dict.keys(): - if obs.loc[obs.obgnme==grp,"weight"].sum() == 0.0: - obs.loc[obs.obgnme==grp,"weight"] = 1.0 + if obs.loc[obs.obgnme == grp, "weight"].sum() == 0.0: + obs.loc[obs.obgnme == grp, "weight"] = 1.0 res_groups = self.res.groupby("group").groups obs_groups = self.observation_data.groupby("obgnme").groups self.__reset_weights(obsgrp_dict, res_groups, obs_groups) @@ -1934,18 +2174,18 @@ def adjust_weights(self,obs_dict=None, obsgrp_dict=None): # reset obs with zero weight obs = self.observation_data for oname in obs_dict.keys(): - if obs.loc[oname,"weight"] == 0.0: - obs.loc[oname,"weight"] = 1.0 + if obs.loc[oname, "weight"] == 0.0: + obs.loc[oname, "weight"] = 1.0 - #res_groups = self.res.groupby("name").groups + # res_groups = self.res.groupby("name").groups res_groups = self.res.groupby(self.res.index).groups - #obs_groups = self.observation_data.groupby("obsnme").groups - obs_groups = self.observation_data.groupby(self.observation_data.index).groups + # obs_groups = self.observation_data.groupby("obsnme").groups + obs_groups = self.observation_data.groupby( + self.observation_data.index + ).groups self.__reset_weights(obs_dict, res_groups, obs_groups) - - def proportional_weights(self, fraction_stdev=1.0, wmax=100.0, - leave_zero=True): + def proportional_weights(self, fraction_stdev=1.0, wmax=100.0, leave_zero=True): """setup weights inversely proportional to the observation value Args: @@ -1959,8 +2199,7 @@ def proportional_weights(self, fraction_stdev=1.0, wmax=100.0, """ new_weights = [] - for oval, ow in zip(self.observation_data.obsval, - self.observation_data.weight): + for oval, ow in zip(self.observation_data.obsval, self.observation_data.weight): if leave_zero and ow == 0.0: ow = 0.0 elif oval == 0.0: @@ -1982,31 +2221,36 @@ def calculate_pertubations(self): """ self.build_increments() - self.parameter_data.loc[:,"pertubation"] = \ - self.parameter_data.parval1 + \ - self.parameter_data.increment + self.parameter_data.loc[:, "pertubation"] = ( + self.parameter_data.parval1 + self.parameter_data.increment + ) - self.parameter_data.loc[:,"out_forward"] = \ - self.parameter_data.loc[:,"pertubation"] > \ - self.parameter_data.loc[:,"parubnd"] + self.parameter_data.loc[:, "out_forward"] = ( + self.parameter_data.loc[:, "pertubation"] + > self.parameter_data.loc[:, "parubnd"] + ) out_forward = self.parameter_data.groupby("out_forward").groups if True in out_forward: - self.parameter_data.loc[out_forward[True],"pertubation"] = \ - self.parameter_data.loc[out_forward[True],"parval1"] - \ - self.parameter_data.loc[out_forward[True],"increment"] - - self.parameter_data.loc[:,"out_back"] = \ - self.parameter_data.loc[:,"pertubation"] < \ - self.parameter_data.loc[:,"parlbnd"] + self.parameter_data.loc[out_forward[True], "pertubation"] = ( + self.parameter_data.loc[out_forward[True], "parval1"] + - self.parameter_data.loc[out_forward[True], "increment"] + ) + + self.parameter_data.loc[:, "out_back"] = ( + self.parameter_data.loc[:, "pertubation"] + < self.parameter_data.loc[:, "parlbnd"] + ) out_back = self.parameter_data.groupby("out_back").groups if True in out_back: still_out = out_back[True] - print(self.parameter_data.loc[still_out,:],flush=True) + print(self.parameter_data.loc[still_out, :], flush=True) - raise Exception("Pst.calculate_pertubations(): " +\ - "can't calc pertubations for the following "+\ - "Parameters {0}".format(','.join(still_out))) + raise Exception( + "Pst.calculate_pertubations(): " + + "can't calc pertubations for the following " + + "Parameters {0}".format(",".join(still_out)) + ) def build_increments(self): """ experimental method to calculate parameter increments for use @@ -2020,31 +2264,37 @@ def build_increments(self): self.add_transform_columns() par_groups = self.parameter_data.groupby("pargp").groups inctype = self.parameter_groups.groupby("inctyp").groups - for itype,inc_groups in inctype.items(): + for itype, inc_groups in inctype.items(): pnames = [] for group in inc_groups: pnames.extend(par_groups[group]) - derinc = self.parameter_groups.loc[group,"derinc"] - self.parameter_data.loc[par_groups[group],"derinc"] = derinc + derinc = self.parameter_groups.loc[group, "derinc"] + self.parameter_data.loc[par_groups[group], "derinc"] = derinc if itype == "absolute": - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] + self.parameter_data.loc[pnames, "increment"] = self.parameter_data.loc[ + pnames, "derinc" + ] elif itype == "relative": - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] * \ - self.parameter_data.loc[pnames,"parval1"] + self.parameter_data.loc[pnames, "increment"] = ( + self.parameter_data.loc[pnames, "derinc"] + * self.parameter_data.loc[pnames, "parval1"] + ) elif itype == "rel_to_max": - mx = self.parameter_data.loc[pnames,"parval1"].max() - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] * mx + mx = self.parameter_data.loc[pnames, "parval1"].max() + self.parameter_data.loc[pnames, "increment"] = ( + self.parameter_data.loc[pnames, "derinc"] * mx + ) else: - raise Exception('Pst.get_derivative_increments(): '+\ - 'unrecognized increment type:{0}'.format(itype)) + raise Exception( + "Pst.get_derivative_increments(): " + + "unrecognized increment type:{0}".format(itype) + ) - #account for fixed pars - isfixed = self.parameter_data.partrans=="fixed" - self.parameter_data.loc[isfixed,"increment"] = \ - self.parameter_data.loc[isfixed,"parval1"] + # account for fixed pars + isfixed = self.parameter_data.partrans == "fixed" + self.parameter_data.loc[isfixed, "increment"] = self.parameter_data.loc[ + isfixed, "parval1" + ] def add_transform_columns(self): """ add transformed values to the `Pst.parameter_data` attribute @@ -2055,17 +2305,17 @@ def add_transform_columns(self): """ - for col in ["parval1","parlbnd","parubnd","increment"]: + for col in ["parval1", "parlbnd", "parubnd", "increment"]: if col not in self.parameter_data.columns: continue - self.parameter_data.loc[:,col+"_trans"] = (self.parameter_data.loc[:,col] * - self.parameter_data.scale) +\ - self.parameter_data.offset - #isnotfixed = self.parameter_data.partrans != "fixed" + self.parameter_data.loc[:, col + "_trans"] = ( + self.parameter_data.loc[:, col] * self.parameter_data.scale + ) + self.parameter_data.offset + # isnotfixed = self.parameter_data.partrans != "fixed" islog = self.parameter_data.partrans == "log" - self.parameter_data.loc[islog,col+"_trans"] = \ - self.parameter_data.loc[islog,col+"_trans"].\ - apply(lambda x:np.log10(x)) + self.parameter_data.loc[islog, col + "_trans"] = self.parameter_data.loc[ + islog, col + "_trans" + ].apply(lambda x: np.log10(x)) def enforce_bounds(self): """ enforce bounds violation @@ -2074,20 +2324,26 @@ def enforce_bounds(self): cheap enforcement of simply bringing violators back in bounds """ - too_big = self.parameter_data.loc[:,"parval1"] > \ - self.parameter_data.loc[:,"parubnd"] - self.parameter_data.loc[too_big,"parval1"] = \ - self.parameter_data.loc[too_big,"parubnd"] - - too_small = self.parameter_data.loc[:,"parval1"] < \ - self.parameter_data.loc[:,"parlbnd"] - self.parameter_data.loc[too_small,"parval1"] = \ - self.parameter_data.loc[too_small,"parlbnd"] + too_big = ( + self.parameter_data.loc[:, "parval1"] + > self.parameter_data.loc[:, "parubnd"] + ) + self.parameter_data.loc[too_big, "parval1"] = self.parameter_data.loc[ + too_big, "parubnd" + ] + too_small = ( + self.parameter_data.loc[:, "parval1"] + < self.parameter_data.loc[:, "parlbnd"] + ) + self.parameter_data.loc[too_small, "parval1"] = self.parameter_data.loc[ + too_small, "parlbnd" + ] @classmethod - def from_io_files(cls, tpl_files, in_files, ins_files, out_files, - pst_filename=None, pst_path=None): + def from_io_files( + cls, tpl_files, in_files, ins_files, out_files, pst_filename=None, pst_path=None + ): """ create a Pst instance from model interface files. Args: @@ -2132,12 +2388,17 @@ def from_io_files(cls, tpl_files, in_files, ins_files, out_files, """ from pyemu import helpers - return helpers.pst_from_io_files(tpl_files=tpl_files,in_files=in_files, - ins_files=ins_files,out_files=out_files, - pst_filename=pst_filename, pst_path=pst_path) + return helpers.pst_from_io_files( + tpl_files=tpl_files, + in_files=in_files, + ins_files=ins_files, + out_files=out_files, + pst_filename=pst_filename, + pst_path=pst_path, + ) - def add_parameters(self,template_file,in_file=None,pst_path=None): + def add_parameters(self, template_file, in_file=None, pst_path=None): """ add new parameters to an existing control file Args: @@ -2174,28 +2435,33 @@ def add_parameters(self,template_file,in_file=None,pst_path=None): new_parnme = [p for p in parnme if p not in self.parameter_data.parnme] if len(new_parnme) == 0: - warnings.warn("no new parameters found in template file {0}".format(template_file),PyemuWarning) + warnings.warn( + "no new parameters found in template file {0}".format(template_file), + PyemuWarning, + ) new_par_data = None else: # extend pa # rameter_data - new_par_data = pst_utils._populate_dataframe(new_parnme, pst_utils.pst_config["par_fieldnames"], - pst_utils.pst_config["par_defaults"], - pst_utils.pst_config["par_dtype"]) - new_par_data.loc[new_parnme,"parnme"] = new_parnme + new_par_data = pst_utils._populate_dataframe( + new_parnme, + pst_utils.pst_config["par_fieldnames"], + pst_utils.pst_config["par_defaults"], + pst_utils.pst_config["par_dtype"], + ) + new_par_data.loc[new_parnme, "parnme"] = new_parnme self.parameter_data = self.parameter_data.append(new_par_data) if in_file is None: - in_file = template_file.replace(".tpl",'') + in_file = template_file.replace(".tpl", "") if pst_path is not None: - template_file = os.path.join(pst_path,os.path.split(template_file)[-1]) + template_file = os.path.join(pst_path, os.path.split(template_file)[-1]) in_file = os.path.join(pst_path, os.path.split(in_file)[-1]) self.template_files.append(template_file) self.input_files.append(in_file) return new_par_data - - def add_observations(self,ins_file,out_file=None,pst_path=None,inschek=True): + def add_observations(self, ins_file, out_file=None, pst_path=None, inschek=True): """ add new observations to a control file Args: @@ -2223,9 +2489,11 @@ def add_observations(self,ins_file,out_file=None,pst_path=None,inschek=True): """ if not os.path.exists(ins_file): - raise Exception("ins file not found: {0}, {1}".format(os.getcwd(),ins_file)) + raise Exception( + "ins file not found: {0}, {1}".format(os.getcwd(), ins_file) + ) if out_file is None: - out_file = ins_file.replace(".ins","") + out_file = ins_file.replace(".ins", "") if ins_file == out_file: raise Exception("ins_file == out_file, doh!") @@ -2236,36 +2504,44 @@ def add_observations(self,ins_file,out_file=None,pst_path=None,inschek=True): sexist = set(self.obs_names) sint = sobsnme.intersection(sexist) if len(sint) > 0: - raise Exception("the following obs in instruction file {0} are already in the control file:{1}". - format(ins_file,','.join(sint))) + raise Exception( + "the following obs in instruction file {0} are already in the control file:{1}".format( + ins_file, ",".join(sint) + ) + ) # extend observation_data - new_obs_data = pst_utils._populate_dataframe(obsnme, pst_utils.pst_config["obs_fieldnames"], - pst_utils.pst_config["obs_defaults"], - pst_utils.pst_config["obs_dtype"]) - new_obs_data.loc[obsnme,"obsnme"] = obsnme + new_obs_data = pst_utils._populate_dataframe( + obsnme, + pst_utils.pst_config["obs_fieldnames"], + pst_utils.pst_config["obs_defaults"], + pst_utils.pst_config["obs_dtype"], + ) + new_obs_data.loc[obsnme, "obsnme"] = obsnme new_obs_data.index = obsnme self.observation_data = self.observation_data.append(new_obs_data) - cwd = '.' + cwd = "." if pst_path is not None: cwd = os.path.join(*os.path.split(ins_file)[:-1]) - ins_file = os.path.join(pst_path,os.path.split(ins_file)[-1]) + ins_file = os.path.join(pst_path, os.path.split(ins_file)[-1]) out_file = os.path.join(pst_path, os.path.split(out_file)[-1]) self.instruction_files.append(ins_file) self.output_files.append(out_file) df = None if inschek: - #df = pst_utils._try_run_inschek(ins_file,out_file,cwd=cwd) - ins_file = os.path.join(cwd,ins_file) - out_file = os.path.join(cwd,out_file) - df = pst_utils.try_process_output_file(ins_file=ins_file,output_file=out_file) + # df = pst_utils._try_run_inschek(ins_file,out_file,cwd=cwd) + ins_file = os.path.join(cwd, ins_file) + out_file = os.path.join(cwd, out_file) + df = pst_utils.try_process_output_file( + ins_file=ins_file, output_file=out_file + ) if df is not None: - #print(self.observation_data.index,df.index) - self.observation_data.loc[df.index,"obsval"] = df.obsval - new_obs_data.loc[df.index,"obsval"] = df.obsval + # print(self.observation_data.index,df.index) + self.observation_data.loc[df.index, "obsval"] = df.obsval + new_obs_data.loc[df.index, "obsval"] = df.obsval return new_obs_data - def write_input_files(self,pst_path='.'): + def write_input_files(self, pst_path="."): """writes model input files using template files and current `parval1` values. Args: @@ -2287,10 +2563,9 @@ def write_input_files(self,pst_path='.'): pst.write_input_files() """ - pst_utils.write_input_files(self,pst_path=pst_path) - + pst_utils.write_input_files(self, pst_path=pst_path) - def process_output_files(self,pst_path='.'): + def process_output_files(self, pst_path="."): """processing the model output files using the instruction files and existing model output files. @@ -2307,10 +2582,9 @@ def process_output_files(self,pst_path='.'): from where python is running to `pst_path` """ - return pst_utils.process_output_files(self,pst_path) + return pst_utils.process_output_files(self, pst_path) - - def get_res_stats(self,nonzero=True): + def get_res_stats(self, nonzero=True): """ get some common residual stats by observation group. Args: @@ -2331,30 +2605,37 @@ def get_res_stats(self,nonzero=True): """ res = self.res.copy() - res.loc[:,"obsnme"] = res.pop("name") + res.loc[:, "obsnme"] = res.pop("name") res.index = res.obsnme if nonzero: - obs = self.observation_data.loc[self.nnz_obs_names,:] - #print(obs.shape,res.shape) - res = res.loc[obs.obsnme,:] - #print(obs.shape, res.shape) - - #reset the res parts to current obs values and remove - #duplicate attributes - res.loc[:,"weight"] = obs.weight - res.loc[:,"obsval"] = obs.obsval - res.loc[:,"obgnme"] = obs.obgnme + obs = self.observation_data.loc[self.nnz_obs_names, :] + # print(obs.shape,res.shape) + res = res.loc[obs.obsnme, :] + # print(obs.shape, res.shape) + + # reset the res parts to current obs values and remove + # duplicate attributes + res.loc[:, "weight"] = obs.weight + res.loc[:, "obsval"] = obs.obsval + res.loc[:, "obgnme"] = obs.obgnme res.pop("group") res.pop("measured") - #build these attribute lists for faster lookup later - og_dict = {og:res.loc[res.obgnme==og,"obsnme"] for og in res.obgnme.unique()} + # build these attribute lists for faster lookup later + og_dict = { + og: res.loc[res.obgnme == og, "obsnme"] for og in res.obgnme.unique() + } og_names = list(og_dict.keys()) # the list of functions and names - sfuncs = [self._stats_rss, self._stats_mean,self._stats_mae, - self._stats_rmse,self._stats_nrmse] - snames = ["rss","mean","mae","rmse","nrmse"] + sfuncs = [ + self._stats_rss, + self._stats_mean, + self._stats_mae, + self._stats_rmse, + self._stats_nrmse, + ] + snames = ["rss", "mean", "mae", "rmse", "nrmse"] data = [] for sfunc in sfuncs: @@ -2362,17 +2643,17 @@ def get_res_stats(self,nonzero=True): groups = [full] for og in og_names: onames = og_dict[og] - res_og = res.loc[onames,:] + res_og = res.loc[onames, :] groups.append(sfunc(res_og)) data.append(groups) - og_names.insert(0,"all") - stats = pd.DataFrame(data,columns=og_names,index=snames) + og_names.insert(0, "all") + stats = pd.DataFrame(data, columns=og_names, index=snames) return stats @staticmethod def _stats_rss(df): - return (((df.modelled - df.obsval) * df.weight)**2).sum() + return (((df.modelled - df.obsval) * df.weight) ** 2).sum() @staticmethod def _stats_mean(df): @@ -2384,14 +2665,13 @@ def _stats_mae(df): @staticmethod def _stats_rmse(df): - return np.sqrt(((df.modelled - df.obsval)**2).sum() / df.shape[0]) + return np.sqrt(((df.modelled - df.obsval) ** 2).sum() / df.shape[0]) @staticmethod def _stats_nrmse(df): return Pst._stats_rmse(df) / (df.obsval.max() - df.obsval.min()) - - def plot(self,kind=None,**kwargs): + def plot(self, kind=None, **kwargs): """method to plot various parts of the control. This is sweet as! Args: @@ -2413,13 +2693,9 @@ def plot(self,kind=None,**kwargs): """ - return plot_utils.pst_helper(self,kind,**kwargs) - - + return plot_utils.pst_helper(self, kind, **kwargs) - - def write_par_summary_table(self,filename=None,group_names=None, - sigma_range = 4.0): + def write_par_summary_table(self, filename=None, group_names=None, sigma_range=4.0): """write a stand alone parameter summary latex table @@ -2444,36 +2720,42 @@ def write_par_summary_table(self,filename=None,group_names=None, ffmt = lambda x: "{0:5G}".format(x) par = self.parameter_data.copy() pargp = par.groupby(par.pargp).groups - #cols = ["parval1","parubnd","parlbnd","stdev","partrans","pargp"] - cols = ["pargp","partrans","count","parval1","parubnd","parlbnd","stdev"] - - labels = {"parval1":"initial value","parubnd":"upper bound", - "parlbnd":"lower bound","partrans":"transform", - "stdev":"standard deviation","pargp":"type","count":"count"} + # cols = ["parval1","parubnd","parlbnd","stdev","partrans","pargp"] + cols = ["pargp", "partrans", "count", "parval1", "parubnd", "parlbnd", "stdev"] + + labels = { + "parval1": "initial value", + "parubnd": "upper bound", + "parlbnd": "lower bound", + "partrans": "transform", + "stdev": "standard deviation", + "pargp": "type", + "count": "count", + } li = par.partrans == "log" - par.loc[li,"parval1"] = par.parval1.loc[li].apply(np.log10) + par.loc[li, "parval1"] = par.parval1.loc[li].apply(np.log10) par.loc[li, "parubnd"] = par.parubnd.loc[li].apply(np.log10) par.loc[li, "parlbnd"] = par.parlbnd.loc[li].apply(np.log10) - par.loc[:,"stdev"] = (par.parubnd - par.parlbnd) / sigma_range + par.loc[:, "stdev"] = (par.parubnd - par.parlbnd) / sigma_range - data = {c:[] for c in cols} - for pg,pnames in pargp.items(): - par_pg = par.loc[pnames,:] + data = {c: [] for c in cols} + for pg, pnames in pargp.items(): + par_pg = par.loc[pnames, :] data["pargp"].append(pg) for col in cols: - if col in ["pargp","partrans"]: + if col in ["pargp", "partrans"]: continue if col == "count": data["count"].append(par_pg.shape[0]) continue - #print(col) - mn = par_pg.loc[:,col].min() - mx = par_pg.loc[:,col].max() + # print(col) + mn = par_pg.loc[:, col].min() + mx = par_pg.loc[:, col].max() if mn == mx: data[col].append(ffmt(mn)) else: - data[col].append("{0} to {1}".format(ffmt(mn),ffmt(mx))) + data[col].append("{0} to {1}".format(ffmt(mn), ffmt(mx))) pts = par_pg.partrans.unique() if len(pts) == 1: @@ -2481,22 +2763,26 @@ def write_par_summary_table(self,filename=None,group_names=None, else: data["partrans"].append("mixed") - pargp_df = pd.DataFrame(data=data,index=list(pargp.keys())) + pargp_df = pd.DataFrame(data=data, index=list(pargp.keys())) pargp_df = pargp_df.loc[:, cols] if group_names is not None: - pargp_df.loc[:, "pargp"] = pargp_df.pargp.apply(lambda x: group_names.pop(x, x)) + pargp_df.loc[:, "pargp"] = pargp_df.pargp.apply( + lambda x: group_names.pop(x, x) + ) pargp_df.columns = pargp_df.columns.map(lambda x: labels[x]) - preamble = '\\documentclass{article}\n\\usepackage{booktabs}\n'+ \ - '\\usepackage{pdflscape}\n\\usepackage{longtable}\n' + \ - '\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n' + preamble = ( + "\\documentclass{article}\n\\usepackage{booktabs}\n" + + "\\usepackage{pdflscape}\n\\usepackage{longtable}\n" + + "\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n" + ) if filename == "none": return pargp_df if filename is None: - filename = self.filename.replace(".pst",".par.tex") + filename = self.filename.replace(".pst", ".par.tex") - with open(filename,'w') as f: + with open(filename, "w") as f: f.write(preamble) f.write("\\begin{center}\nParameter Summary\n\\end{center}\n") f.write("\\begin{center}\n\\begin{landscape}\n") @@ -2506,7 +2792,7 @@ def write_par_summary_table(self,filename=None,group_names=None, f.write("\\end{document}\n") return pargp_df - def write_obs_summary_table(self,filename=None,group_names=None): + def write_obs_summary_table(self, filename=None, group_names=None): """write a stand alone observation summary latex table @@ -2528,27 +2814,33 @@ def write_obs_summary_table(self,filename=None,group_names=None): ffmt = lambda x: "{0:5G}".format(x) obs = self.observation_data.copy() obsgp = obs.groupby(obs.obgnme).groups - cols = ["obgnme","obsval","nzcount","zcount","weight","stdev","pe"] - - labels = {"obgnme":"group","obsval":"value","nzcount":"non-zero weight", - "zcount":"zero weight","weight":"weight","stdev":"standard deviation", - "pe":"percent error"} - - obs.loc[:,"stdev"] = 1.0 / obs.weight - obs.loc[:,"pe"] = 100.0 * (obs.stdev / obs.obsval.apply(np.abs)) - obs = obs.replace([np.inf,-np.inf],np.NaN) + cols = ["obgnme", "obsval", "nzcount", "zcount", "weight", "stdev", "pe"] + + labels = { + "obgnme": "group", + "obsval": "value", + "nzcount": "non-zero weight", + "zcount": "zero weight", + "weight": "weight", + "stdev": "standard deviation", + "pe": "percent error", + } + + obs.loc[:, "stdev"] = 1.0 / obs.weight + obs.loc[:, "pe"] = 100.0 * (obs.stdev / obs.obsval.apply(np.abs)) + obs = obs.replace([np.inf, -np.inf], np.NaN) data = {c: [] for c in cols} for og, onames in obsgp.items(): obs_g = obs.loc[onames, :] data["obgnme"].append(og) - data["nzcount"].append(obs_g.loc[obs_g.weight > 0.0,:].shape[0]) - data["zcount"].append(obs_g.loc[obs_g.weight == 0.0,:].shape[0]) + data["nzcount"].append(obs_g.loc[obs_g.weight > 0.0, :].shape[0]) + data["zcount"].append(obs_g.loc[obs_g.weight == 0.0, :].shape[0]) for col in cols: - if col in ["obgnme","nzcount","zcount"]: + if col in ["obgnme", "nzcount", "zcount"]: continue - #print(col) + # print(col) mn = obs_g.loc[:, col].min() mx = obs_g.loc[:, col].max() if np.isnan(mn) or np.isnan(mx): @@ -2558,18 +2850,20 @@ def write_obs_summary_table(self,filename=None,group_names=None): else: data[col].append("{0} to {1}".format(ffmt(mn), ffmt(mx))) - obsg_df = pd.DataFrame(data=data, index=list(obsgp.keys())) obsg_df = obsg_df.loc[:, cols] if group_names is not None: - obsg_df.loc[:, "obgnme"] = obsg_df.obgnme.apply(lambda x: group_names.pop(x, x)) - obsg_df.sort_values(by="obgnme",inplace=True,ascending=True) + obsg_df.loc[:, "obgnme"] = obsg_df.obgnme.apply( + lambda x: group_names.pop(x, x) + ) + obsg_df.sort_values(by="obgnme", inplace=True, ascending=True) obsg_df.columns = obsg_df.columns.map(lambda x: labels[x]) - preamble = '\\documentclass{article}\n\\usepackage{booktabs}\n' + \ - '\\usepackage{pdflscape}\n\\usepackage{longtable}\n' + \ - '\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n' - + preamble = ( + "\\documentclass{article}\n\\usepackage{booktabs}\n" + + "\\usepackage{pdflscape}\n\\usepackage{longtable}\n" + + "\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n" + ) if filename == "none": return obsg_df @@ -2577,7 +2871,7 @@ def write_obs_summary_table(self,filename=None,group_names=None): if filename is None: filename = self.filename.replace(".pst", ".obs.tex") - with open(filename, 'w') as f: + with open(filename, "w") as f: f.write(preamble) @@ -2591,7 +2885,6 @@ def write_obs_summary_table(self,filename=None,group_names=None): return obsg_df - # jwhite - 13 Aug 2019 - no one is using this write? # def run(self,exe_name="pestpp",cwd=None): # """run a command related to the pst instance. If @@ -2619,7 +2912,6 @@ def write_obs_summary_table(self,filename=None,group_names=None): # print("executing {0} in dir {1}".format(cmd_line, cwd)) # pyemu.utils.os_utils.run(cmd_line,cwd=cwd) - @staticmethod def _is_less_const(name): constraint_tags = ["l_", "less"] @@ -2639,10 +2931,13 @@ def less_than_obs_constraints(self): """ - obs = self.observation_data - lt_obs = obs.loc[obs.apply(lambda x: self._is_less_const(x.obgnme) \ - and x.weight != 0.0,axis=1),"obsnme"] + lt_obs = obs.loc[ + obs.apply( + lambda x: self._is_less_const(x.obgnme) and x.weight != 0.0, axis=1 + ), + "obsnme", + ] return lt_obs @property @@ -2660,8 +2955,12 @@ def less_than_pi_constraints(self): """ pi = self.prior_information - lt_pi = pi.loc[pi.apply(lambda x: self._is_less_const(x.obgnme) \ - and x.weight != 0.0, axis=1), "pilbl"] + lt_pi = pi.loc[ + pi.apply( + lambda x: self._is_less_const(x.obgnme) and x.weight != 0.0, axis=1 + ), + "pilbl", + ] return lt_pi @staticmethod @@ -2683,11 +2982,13 @@ def greater_than_obs_constraints(self): """ - - obs = self.observation_data - gt_obs = obs.loc[obs.apply(lambda x: self._is_greater_const(x.obgnme) \ - and x.weight != 0.0,axis=1),"obsnme"] + gt_obs = obs.loc[ + obs.apply( + lambda x: self._is_greater_const(x.obgnme) and x.weight != 0.0, axis=1 + ), + "obsnme", + ] return gt_obs @property @@ -2706,12 +3007,14 @@ def greater_than_pi_constraints(self): """ pi = self.prior_information - gt_pi = pi.loc[pi.apply(lambda x: self._is_greater_const(x.obgnme) \ - and x.weight != 0.0, axis=1), "pilbl"] + gt_pi = pi.loc[ + pi.apply( + lambda x: self._is_greater_const(x.obgnme) and x.weight != 0.0, axis=1 + ), + "pilbl", + ] return gt_pi - - def get_par_change_limits(self): """ calculate the various parameter change limits used in pest. @@ -2729,9 +3032,9 @@ def get_par_change_limits(self): """ par = self.parameter_data - fpars = par.loc[par.parchglim=="factor","parnme"] + fpars = par.loc[par.parchglim == "factor", "parnme"] rpars = par.loc[par.parchglim == "relative", "parnme"] - #apars = par.loc[par.parchglim == "absolute", "parnme"] + # apars = par.loc[par.parchglim == "absolute", "parnme"] change_df = par.copy() @@ -2741,11 +3044,14 @@ def get_par_change_limits(self): base_vals = par.parval1.copy() # apply zero value correction - base_vals[base_vals==0] = par.loc[base_vals==0,"parubnd"] / 4.0 + base_vals[base_vals == 0] = par.loc[base_vals == 0, "parubnd"] / 4.0 # apply facorig - replace_pars = base_vals.index.map(lambda x: par.loc[x,"partrans"]!="log" and np.abs(base_vals.loc[x]) < facorig*np.abs(base_vals.loc[x])) - #print(facorig,replace_pars) + replace_pars = base_vals.index.map( + lambda x: par.loc[x, "partrans"] != "log" + and np.abs(base_vals.loc[x]) < facorig * np.abs(base_vals.loc[x]) + ) + # print(facorig,replace_pars) base_vals.loc[replace_pars] = base_vals.loc[replace_pars] * facorig # negative fac pars @@ -2761,19 +3067,23 @@ def get_par_change_limits(self): # relative rdelta = base_vals.apply(np.abs) * rpm - change_df.loc[:,"rel_upper"] = base_vals + rdelta - change_df.loc[:,"rel_lower"] = base_vals - rdelta + change_df.loc[:, "rel_upper"] = base_vals + rdelta + change_df.loc[:, "rel_lower"] = base_vals - rdelta - change_df.loc[:,"chg_upper"] = np.NaN - change_df.loc[fpars,"chg_upper"] = change_df.fac_upper[fpars] + change_df.loc[:, "chg_upper"] = np.NaN + change_df.loc[fpars, "chg_upper"] = change_df.fac_upper[fpars] change_df.loc[rpars, "chg_upper"] = change_df.rel_upper[rpars] change_df.loc[:, "chg_lower"] = np.NaN change_df.loc[fpars, "chg_lower"] = change_df.fac_lower[fpars] change_df.loc[rpars, "chg_lower"] = change_df.rel_lower[rpars] # effective limits - change_df.loc[:,"eff_upper"] = change_df.loc[:,["parubnd","chg_upper"]].min(axis=1) - change_df.loc[:,"eff_lower"] = change_df.loc[:, ["parlbnd", "chg_lower"]].max(axis=1) + change_df.loc[:, "eff_upper"] = change_df.loc[:, ["parubnd", "chg_upper"]].min( + axis=1 + ) + change_df.loc[:, "eff_lower"] = change_df.loc[:, ["parlbnd", "chg_lower"]].max( + axis=1 + ) return change_df @@ -2792,11 +3102,17 @@ def get_adj_pars_at_bounds(self, frac_tol=0.01): """ - par = self.parameter_data.loc[self.adj_par_names,:].copy() - over_ub = par.loc[par.apply(lambda x: x.parval1 >= (1.-frac_tol) * x.parubnd, axis=1),"parnme"].tolist() - under_lb = par.loc[par.apply(lambda x: x.parval1 <= (1.+frac_tol) * x.parlbnd, axis=1),"parnme"].tolist() + par = self.parameter_data.loc[self.adj_par_names, :].copy() + over_ub = par.loc[ + par.apply(lambda x: x.parval1 >= (1.0 - frac_tol) * x.parubnd, axis=1), + "parnme", + ].tolist() + under_lb = par.loc[ + par.apply(lambda x: x.parval1 <= (1.0 + frac_tol) * x.parlbnd, axis=1), + "parnme", + ].tolist() - return under_lb,over_ub + return under_lb, over_ub def try_parse_name_metadata(self): """try to add meta data columns to parameter and observation data based on @@ -2811,19 +3127,23 @@ def try_parse_name_metadata(self): par_cols = pst_utils.pst_config["par_fieldnames"] obs_cols = pst_utils.pst_config["obs_fieldnames"] - for df,name,fieldnames in zip([par,obs],["parnme","obsnme"],[par_cols,obs_cols]): + for df, name, fieldnames in zip( + [par, obs], ["parnme", "obsnme"], [par_cols, obs_cols] + ): try: - meta_dict = df.loc[:,name].apply(lambda x: dict([item.split(':') for item in x.split('_') if ':' in item])) + meta_dict = df.loc[:, name].apply( + lambda x: dict( + [item.split(":") for item in x.split("_") if ":" in item] + ) + ) unique_keys = [] - for k,v in meta_dict.items(): - for kk,vv in v.items(): + for k, v in meta_dict.items(): + for kk, vv in v.items(): if kk not in fieldnames and kk not in unique_keys: unique_keys.append(kk) for uk in unique_keys: if uk not in df.columns: - df.loc[:,uk] = np.NaN - df.loc[:,uk] = meta_dict.apply(lambda x: x.get(uk,np.NaN)) + df.loc[:, uk] = np.NaN + df.loc[:, uk] = meta_dict.apply(lambda x: x.get(uk, np.NaN)) except Exception as e: print("error parsing metadata from '{0}', continuing".format(name)) - - diff --git a/pyemu/pst/pst_utils.py b/pyemu/pst/pst_utils.py index 1c90a6b1c..f8d658648 100644 --- a/pyemu/pst/pst_utils.py +++ b/pyemu/pst/pst_utils.py @@ -6,12 +6,14 @@ import re import numpy as np import pandas as pd + pd.options.display.max_colwidth = 100 import pyemu from ..pyemu_warnings import PyemuWarning -#formatters -#SFMT = lambda x: "{0:>20s}".format(str(x.decode())) + +# formatters +# SFMT = lambda x: "{0:>20s}".format(str(x.decode())) def SFMT(item): try: s = "{0:<20s} ".format(item.decode()) @@ -19,91 +21,184 @@ def SFMT(item): s = "{0:<20s} ".format(str(item)) return s + SFMT_LONG = lambda x: "{0:<50s} ".format(str(x)) IFMT = lambda x: "{0:<10d} ".format(int(x)) FFMT = lambda x: "{0:<20.10E} ".format(float(x)) + def str_con(item): if len(item) == 0: return np.NaN return item.lower().strip() + pst_config = {} # parameter stuff -pst_config["tied_dtype"] = np.dtype([("parnme", "U20"), ("partied","U20")]) -pst_config["tied_fieldnames"] = ["parnme","partied"] -pst_config["tied_format"] = {"parnme":SFMT,"partied":SFMT} -pst_config["tied_converters"] = {"parnme":str_con,"partied":str_con} -pst_config["tied_defaults"] = {"parnme":"dum","partied":"dum"} - -pst_config["par_dtype"] = np.dtype([("parnme", "U20"), ("partrans","U20"), - ("parchglim","U20"),("parval1", np.float64), - ("parlbnd",np.float64),("parubnd",np.float64), - ("pargp","U20"),("scale", np.float64), - ("offset", np.float64),("dercom",np.int)]) -pst_config["par_fieldnames"] = "PARNME PARTRANS PARCHGLIM PARVAL1 PARLBND PARUBND " +\ - "PARGP SCALE OFFSET DERCOM" +pst_config["tied_dtype"] = np.dtype([("parnme", "U20"), ("partied", "U20")]) +pst_config["tied_fieldnames"] = ["parnme", "partied"] +pst_config["tied_format"] = {"parnme": SFMT, "partied": SFMT} +pst_config["tied_converters"] = {"parnme": str_con, "partied": str_con} +pst_config["tied_defaults"] = {"parnme": "dum", "partied": "dum"} + +pst_config["par_dtype"] = np.dtype( + [ + ("parnme", "U20"), + ("partrans", "U20"), + ("parchglim", "U20"), + ("parval1", np.float64), + ("parlbnd", np.float64), + ("parubnd", np.float64), + ("pargp", "U20"), + ("scale", np.float64), + ("offset", np.float64), + ("dercom", np.int), + ] +) +pst_config["par_fieldnames"] = ( + "PARNME PARTRANS PARCHGLIM PARVAL1 PARLBND PARUBND " + "PARGP SCALE OFFSET DERCOM" +) pst_config["par_fieldnames"] = pst_config["par_fieldnames"].lower().strip().split() -pst_config["par_format"] = {"parnme": SFMT, "partrans": SFMT, - "parchglim": SFMT, "parval1": FFMT, - "parlbnd": FFMT, "parubnd": FFMT, - "pargp": SFMT, "scale": FFMT, - "offset": FFMT, "dercom": IFMT} -pst_config["par_alias_map"] = {"name":"parnme","transform":"partrans","value":"parval1", - "upper_bound":"parubnd","lower_bound":"parlbnd", - "group":"pargp"} -pst_config["par_converters"] = {"parnme": str_con, "pargp": str_con, - "parval1":np.float,"parubnd":np.float, - "parlbnd":np.float,"scale":np.float, - "offset":np.float} -pst_config["par_defaults"] = {"parnme":"dum","partrans":"log","parchglim":"factor", - "parval1":1.0,"parlbnd":1.1e-10,"parubnd":1.1e+10, - "pargp":"pargp","scale":1.0,"offset":0.0,"dercom":1} +pst_config["par_format"] = { + "parnme": SFMT, + "partrans": SFMT, + "parchglim": SFMT, + "parval1": FFMT, + "parlbnd": FFMT, + "parubnd": FFMT, + "pargp": SFMT, + "scale": FFMT, + "offset": FFMT, + "dercom": IFMT, +} +pst_config["par_alias_map"] = { + "name": "parnme", + "transform": "partrans", + "value": "parval1", + "upper_bound": "parubnd", + "lower_bound": "parlbnd", + "group": "pargp", +} +pst_config["par_converters"] = { + "parnme": str_con, + "pargp": str_con, + "parval1": np.float, + "parubnd": np.float, + "parlbnd": np.float, + "scale": np.float, + "offset": np.float, +} +pst_config["par_defaults"] = { + "parnme": "dum", + "partrans": "log", + "parchglim": "factor", + "parval1": 1.0, + "parlbnd": 1.1e-10, + "parubnd": 1.1e10, + "pargp": "pargp", + "scale": 1.0, + "offset": 0.0, + "dercom": 1, +} # parameter group stuff -pst_config["pargp_dtype"] = np.dtype([("pargpnme", "U20"), ("inctyp","U20"), - ("derinc", np.float64), - ("derinclb",np.float64),("forcen","U20"), - ("derincmul",np.float64),("dermthd", "U20"), - ("splitthresh", np.float64),("splitreldiff",np.float64), - ("splitaction","U20")]) -pst_config["pargp_fieldnames"] = "PARGPNME INCTYP DERINC DERINCLB FORCEN DERINCMUL " +\ - "DERMTHD SPLITTHRESH SPLITRELDIFF SPLITACTION" +pst_config["pargp_dtype"] = np.dtype( + [ + ("pargpnme", "U20"), + ("inctyp", "U20"), + ("derinc", np.float64), + ("derinclb", np.float64), + ("forcen", "U20"), + ("derincmul", np.float64), + ("dermthd", "U20"), + ("splitthresh", np.float64), + ("splitreldiff", np.float64), + ("splitaction", "U20"), + ] +) +pst_config["pargp_fieldnames"] = ( + "PARGPNME INCTYP DERINC DERINCLB FORCEN DERINCMUL " + + "DERMTHD SPLITTHRESH SPLITRELDIFF SPLITACTION" +) pst_config["pargp_fieldnames"] = pst_config["pargp_fieldnames"].lower().strip().split() -pst_config["pargp_format"] = {"pargpnme":SFMT,"inctyp":SFMT,"derinc":FFMT,"forcen":SFMT, - "derincmul":FFMT,"dermthd":SFMT,"splitthresh":FFMT, - "splitreldiff":FFMT,"splitaction":SFMT} - -pst_config["pargp_converters"] = {"pargpnme":str_con,"inctyp":str_con, - "dermethd":str_con,"derinc":np.float,"derinclb":np.float, - "splitaction":str_con,"forcen":str_con,"derincmul":np.float} -pst_config["pargp_defaults"] = {"pargpnme":"pargp","inctyp":"relative","derinc":0.01, - "derinclb":0.0,"forcen":"switch","derincmul":2.0, - "dermthd":"parabolic","splitthresh":1.0e-5, - "splitreldiff":0.5,"splitaction":"smaller"} +pst_config["pargp_format"] = { + "pargpnme": SFMT, + "inctyp": SFMT, + "derinc": FFMT, + "forcen": SFMT, + "derincmul": FFMT, + "dermthd": SFMT, + "splitthresh": FFMT, + "splitreldiff": FFMT, + "splitaction": SFMT, +} + +pst_config["pargp_converters"] = { + "pargpnme": str_con, + "inctyp": str_con, + "dermethd": str_con, + "derinc": np.float, + "derinclb": np.float, + "splitaction": str_con, + "forcen": str_con, + "derincmul": np.float, +} +pst_config["pargp_defaults"] = { + "pargpnme": "pargp", + "inctyp": "relative", + "derinc": 0.01, + "derinclb": 0.0, + "forcen": "switch", + "derincmul": 2.0, + "dermthd": "parabolic", + "splitthresh": 1.0e-5, + "splitreldiff": 0.5, + "splitaction": "smaller", +} # observation stuff pst_config["obs_fieldnames"] = "OBSNME OBSVAL WEIGHT OBGNME".lower().split() -pst_config["obs_dtype"] = np.dtype([("obsnme","U20"),("obsval",np.float64), - ("weight",np.float64),("obgnme","U20")]) -pst_config["obs_format"] = {"obsnme": SFMT, "obsval": FFMT, - "weight": FFMT, "obgnme": SFMT} -pst_config["obs_converters"] = {"obsnme": str_con, "obgnme": str_con, - "weight":np.float,"obsval":np.float} -pst_config["obs_defaults"] = {"obsnme":"dum","obsval":1.0e+10, - "weight":1.0,"obgnme":"obgnme"} -pst_config["obs_alias_map"] = {"name":"obsnme","value":"obsval","group":"obgnme"} +pst_config["obs_dtype"] = np.dtype( + [ + ("obsnme", "U20"), + ("obsval", np.float64), + ("weight", np.float64), + ("obgnme", "U20"), + ] +) +pst_config["obs_format"] = { + "obsnme": SFMT, + "obsval": FFMT, + "weight": FFMT, + "obgnme": SFMT, +} +pst_config["obs_converters"] = { + "obsnme": str_con, + "obgnme": str_con, + "weight": np.float, + "obsval": np.float, +} +pst_config["obs_defaults"] = { + "obsnme": "dum", + "obsval": 1.0e10, + "weight": 1.0, + "obgnme": "obgnme", +} +pst_config["obs_alias_map"] = {"name": "obsnme", "value": "obsval", "group": "obgnme"} # prior info stuff -pst_config["null_prior"] = pd.DataFrame({"pilbl": None, - "obgnme": None}, index=[]) -pst_config["prior_format"] = {"pilbl": SFMT, "equation": SFMT_LONG, - "weight": FFMT, "obgnme": SFMT} -pst_config["prior_fieldnames"] = ["pilbl","equation", "weight", "obgnme"] +pst_config["null_prior"] = pd.DataFrame({"pilbl": None, "obgnme": None}, index=[]) +pst_config["prior_format"] = { + "pilbl": SFMT, + "equation": SFMT_LONG, + "weight": FFMT, + "obgnme": SFMT, +} +pst_config["prior_fieldnames"] = ["pilbl", "equation", "weight", "obgnme"] # other containers @@ -135,25 +230,29 @@ def read_resfile(resfile): df.residual.plot(kind="hist") """ - assert os.path.exists(resfile),"read_resfile() error: resfile " +\ - "{0} not found".format(resfile) + assert os.path.exists( + resfile + ), "read_resfile() error: resfile " + "{0} not found".format(resfile) converters = {"name": str_con, "group": str_con} - f = open(resfile, 'r') + f = open(resfile, "r") while True: line = f.readline() - if line == '': - raise Exception("Pst.get_residuals: EOF before finding "+ - "header in resfile: " + resfile) + if line == "": + raise Exception( + "Pst.get_residuals: EOF before finding " + + "header in resfile: " + + resfile + ) if "name" in line.lower(): header = line.lower().strip().split() break - res_df = pd.read_csv(f, header=None, names=header, sep="\s+", - converters=converters) + res_df = pd.read_csv(f, header=None, names=header, sep="\s+", converters=converters) res_df.index = res_df.name f.close() return res_df -def res_from_en(pst,enfile): + +def res_from_en(pst, enfile): """load ensemble results from PESTPP-IES into a PEST-style residuals `pandas.DataFrame` @@ -176,27 +275,28 @@ def res_from_en(pst,enfile): """ converters = {"name": str_con, "group": str_con} - obs=pst.observation_data - if isinstance(enfile,str): - df=pd.read_csv(enfile,converters=converters) - df.columns=df.columns.str.lower() - df = df.set_index('real_name').T.rename_axis('name').rename_axis(None, 1) + obs = pst.observation_data + if isinstance(enfile, str): + df = pd.read_csv(enfile, converters=converters) + df.columns = df.columns.str.lower() + df = df.set_index("real_name").T.rename_axis("name").rename_axis(None, 1) else: df = enfile.T - if 'base' in df.columns: - modelled = df['base'] + if "base" in df.columns: + modelled = df["base"] std = df.std(axis=1) else: modelled = df.mean(axis=1) std = df.std(axis=1) - #probably a more pandastic way to do this - res_df = pd.DataFrame({"modelled":modelled,"std":std},index=obs.obsnme.values) - res_df['group']=obs['obgnme'].copy() - res_df['measured']=obs['obsval'].copy() - res_df['weight']=obs['weight'].copy() - res_df['residual']=res_df['measured']-res_df['modelled'] + # probably a more pandastic way to do this + res_df = pd.DataFrame({"modelled": modelled, "std": std}, index=obs.obsnme.values) + res_df["group"] = obs["obgnme"].copy() + res_df["measured"] = obs["obsval"].copy() + res_df["weight"] = obs["weight"].copy() + res_df["residual"] = res_df["measured"] - res_df["modelled"] return res_df + def read_parfile(parfile): """load a PEST-style parameter value file into a pandas.DataFrame @@ -213,17 +313,19 @@ def read_parfile(parfile): """ if not os.path.exists(parfile): - raise Exception("pst_utils.read_parfile: parfile not found: {0}".\ - format(parfile)) - f = open(parfile, 'r') + raise Exception( + "pst_utils.read_parfile: parfile not found: {0}".format(parfile) + ) + f = open(parfile, "r") header = f.readline() - par_df = pd.read_csv(f, header=None, - names=["parnme", "parval1", "scale", "offset"], - sep="\s+") + par_df = pd.read_csv( + f, header=None, names=["parnme", "parval1", "scale", "offset"], sep="\s+" + ) par_df.index = par_df.parnme return par_df -def write_parfile(df,parfile): + +def write_parfile(df, parfile): """ write a PEST-style parameter file from a dataframe Args: @@ -237,24 +339,32 @@ def write_parfile(df,parfile): pyemu.pst_utils.write_parfile(pst.parameter_data,"my.par") """ - columns = ["parnme","parval1","scale","offset"] - formatters = {"parnme":lambda x:"{0:20s}".format(x), - "parval1":lambda x:"{0:20.7E}".format(x), - "scale":lambda x:"{0:20.7E}".format(x), - "offset":lambda x:"{0:20.7E}".format(x)} + columns = ["parnme", "parval1", "scale", "offset"] + formatters = { + "parnme": lambda x: "{0:20s}".format(x), + "parval1": lambda x: "{0:20.7E}".format(x), + "scale": lambda x: "{0:20.7E}".format(x), + "offset": lambda x: "{0:20.7E}".format(x), + } for col in columns: - assert col in df.columns,"write_parfile() error: " +\ - "{0} not found in df".format(col) - with open(parfile,'w') as f: + assert ( + col in df.columns + ), "write_parfile() error: " + "{0} not found in df".format(col) + with open(parfile, "w") as f: f.write("single point\n") - f.write(df.to_string(col_space=0, - columns=columns, - formatters=formatters, - justify="right", - header=False, - index=False, - index_names=False) + '\n') + f.write( + df.to_string( + col_space=0, + columns=columns, + formatters=formatters, + justify="right", + header=False, + index=False, + index_names=False, + ) + + "\n" + ) def parse_tpl_file(tpl_file): @@ -272,38 +382,41 @@ def parse_tpl_file(tpl_file): """ par_names = set() - with open(tpl_file,'r') as f: + with open(tpl_file, "r") as f: try: header = f.readline().strip().split() - assert header[0].lower() in ["ptf","jtf"],\ - "template file error: must start with [ptf,jtf], not:" +\ - str(header[0]) - assert len(header) == 2,\ - "template file error: header line must have two entries: " +\ - str(header) + assert header[0].lower() in [ + "ptf", + "jtf", + ], "template file error: must start with [ptf,jtf], not:" + str(header[0]) + assert ( + len(header) == 2 + ), "template file error: header line must have two entries: " + str(header) marker = header[1] - assert len(marker) == 1,\ - "template file error: marker must be a single character, not:" +\ - str(marker) + assert len(marker) == 1, ( + "template file error: marker must be a single character, not:" + + str(marker) + ) for line in f: par_line = set(line.lower().strip().split(marker)[1::2]) par_names.update(par_line) - #par_names.extend(par_line) - #for p in par_line: + # par_names.extend(par_line) + # for p in par_line: # if p not in par_names: # par_names.append(p) except Exception as e: - raise Exception("error processing template file " +\ - tpl_file+" :\n" + str(e)) - #par_names = [pn.strip().lower() for pn in par_names] - #seen = set() - #seen_add = seen.add - #return [x for x in par_names if not (x in seen or seen_add(x))] + raise Exception( + "error processing template file " + tpl_file + " :\n" + str(e) + ) + # par_names = [pn.strip().lower() for pn in par_names] + # seen = set() + # seen_add = seen.add + # return [x for x in par_names if not (x in seen or seen_add(x))] return [p.strip() for p in list(par_names)] -def write_input_files(pst,pst_path='.'): +def write_input_files(pst, pst_path="."): """write parameter values to model input files Args: @@ -328,17 +441,19 @@ def write_input_files(pst,pst_path='.'): num_tpl = len(pairs) chunk_len = 50 num_chunk_floor = num_tpl // chunk_len - main_chunks = pairs[:num_chunk_floor * chunk_len].reshape( - [-1, chunk_len, 2]).tolist() # the list of files broken down into chunks - remainder = pairs[num_chunk_floor * chunk_len:].tolist() # remaining files + main_chunks = ( + pairs[: num_chunk_floor * chunk_len].reshape([-1, chunk_len, 2]).tolist() + ) # the list of files broken down into chunks + remainder = pairs[num_chunk_floor * chunk_len :].tolist() # remaining files chunks = main_chunks + [remainder] procs = [] for chunk in chunks: - #write_to_template(pst.parameter_data.parval1_trans,os.path.join(pst_path,tpl_file), + # write_to_template(pst.parameter_data.parval1_trans,os.path.join(pst_path,tpl_file), # os.path.join(pst_path,in_file)) - p = mp.Process(target=_write_chunk_to_template, - args=[chunk, pst.parameter_data.parval1_trans, - pst_path]) + p = mp.Process( + target=_write_chunk_to_template, + args=[chunk, pst.parameter_data.parval1_trans, pst_path], + ) p.start() procs.append(p) for p in procs: @@ -352,7 +467,7 @@ def _write_chunk_to_template(chunk, parvals, pst_path): write_to_template(parvals, tpl_file, in_file) -def write_to_template(parvals,tpl_file,in_file): +def write_to_template(parvals, tpl_file, in_file): """ write parameter values to a model input file using the corresponding template file @@ -368,20 +483,23 @@ def write_to_template(parvals,tpl_file,in_file): "my.tpl","my.input") """ - f_in = open(in_file,'w') - f_tpl = open(tpl_file,'r') + f_in = open(in_file, "w") + f_tpl = open(tpl_file, "r") header = f_tpl.readline().strip().split() if header[0].lower() not in ["ptf", "jtf"]: - raise Exception("template file error: must start with [ptf,jtf], not:" + \ - str(header[0])) + raise Exception( + "template file error: must start with [ptf,jtf], not:" + str(header[0]) + ) if len(header) != 2: - raise Exception("template file error: header line must have two entries: " + \ - str(header)) + raise Exception( + "template file error: header line must have two entries: " + str(header) + ) marker = header[1] if len(marker) != 1: - raise Exception("template file error: marker must be a single character, not:" + \ - str(marker)) + raise Exception( + "template file error: marker must be a single character, not:" + str(marker) + ) for line in f_tpl: if marker not in line: f_in.write(line) @@ -389,25 +507,25 @@ def write_to_template(parvals,tpl_file,in_file): line = line.rstrip() par_names = line.lower().split(marker)[1::2] par_names = [name.strip() for name in par_names] - start,end = _get_marker_indices(marker, line) + start, end = _get_marker_indices(marker, line) if len(par_names) != len(start): raise Exception("par_names != start") - new_line = line[:start[0]] - between = [line[e:s] for s,e in zip(start[1:],end[:-1])] - for i,name in enumerate(par_names): - s,e = start[i],end[i] + new_line = line[: start[0]] + between = [line[e:s] for s, e in zip(start[1:], end[:-1])] + for i, name in enumerate(par_names): + s, e = start[i], end[i] w = e - s if w > 15: d = 6 else: d = 3 - fmt = "{0:" + str(w)+"."+str(d)+"E}" + fmt = "{0:" + str(w) + "." + str(d) + "E}" val_str = fmt.format(parvals[name]) new_line += val_str if i != len(par_names) - 1: new_line += between[i] - new_line += line[end[-1]:] - f_in.write(new_line+'\n') + new_line += line[end[-1] :] + f_in.write(new_line + "\n") f_tpl.close() f_in.close() @@ -419,9 +537,9 @@ def _get_marker_indices(marker, line): """ indices = [i for i, ltr in enumerate(line) if ltr == marker] start = indices[0:-1:2] - end = [i+1 for i in indices[1::2]] + end = [i + 1 for i in indices[1::2]] assert len(start) == len(end) - return start,end + return start, end def parse_ins_file(ins_file): @@ -444,15 +562,17 @@ def parse_ins_file(ins_file): """ obs_names = [] - with open(ins_file,'r') as f: + with open(ins_file, "r") as f: header = f.readline().strip().split() - assert header[0].lower() in ["pif","jif"],\ - "instruction file error: must start with [pif,jif], not:" +\ - str(header[0]) + assert header[0].lower() in [ + "pif", + "jif", + ], "instruction file error: must start with [pif,jif], not:" + str(header[0]) marker = header[1] - assert len(marker) == 1,\ - "instruction file error: marker must be a single character, not:" +\ - str(marker) + assert len(marker) == 1, ( + "instruction file error: marker must be a single character, not:" + + str(marker) + ) for line in f: line = line.lower() if marker in line: @@ -461,16 +581,16 @@ def parse_ins_file(ins_file): obs_names.extend(_parse_ins_string(item)) else: obs_names.extend(_parse_ins_string(line.strip())) - #obs_names = [on.strip().lower() for on in obs_names] + # obs_names = [on.strip().lower() for on in obs_names] return obs_names def _parse_ins_string(string): """ split up an instruction file line to get the observation names """ - istart_markers = set(["[","(","!"]) - marker_dict = {"[":"]","(":")","!":"!"} - #iend_markers = set(["]",")","!"]) + istart_markers = set(["[", "(", "!"]) + marker_dict = {"[": "]", "(": ")", "!": "!"} + # iend_markers = set(["]",")","!"]) obs_names = [] slen = len(string) @@ -480,15 +600,15 @@ def _parse_ins_string(string): break char = string[idx] if char in istart_markers: - #em = iend_markers[istart_markers.index(char)] + # em = iend_markers[istart_markers.index(char)] em = marker_dict[char] # print("\n",idx) # print(string) # print(string[idx+1:]) # print(string[idx+1:].index(em)) # print(string[idx+1:].index(em)+idx+1) - eidx = min(slen,string[idx+1:].index(em)+idx+1) - obs_name = string[idx+1:eidx] + eidx = min(slen, string[idx + 1 :].index(em) + idx + 1) + obs_name = string[idx + 1 : eidx] if obs_name.lower() != "dum": obs_names.append(obs_name) idx = eidx + 1 @@ -504,15 +624,15 @@ def _populate_dataframe(index, columns, default_dict, dtype): This function is called as part of constructing a generic Pst instance """ - new_df = pd.DataFrame(index=index,columns=columns) - for fieldname,dt in zip(columns,dtype.descr): + new_df = pd.DataFrame(index=index, columns=columns) + for fieldname, dt in zip(columns, dtype.descr): default = default_dict[fieldname] - new_df.loc[:,fieldname] = default - new_df.loc[:,fieldname] = new_df.loc[:,fieldname].astype(dt[1]) + new_df.loc[:, fieldname] = default + new_df.loc[:, fieldname] = new_df.loc[:, fieldname].astype(dt[1]) return new_df -def generic_pst(par_names=["par1"],obs_names=["obs1"],addreg=False): +def generic_pst(par_names=["par1"], obs_names=["obs1"], addreg=False): """generate a generic pst instance. Args: @@ -535,24 +655,27 @@ def generic_pst(par_names=["par1"],obs_names=["obs1"],addreg=False): pst = pyemu.pst_utils.generic_pst(par_names,obs_names] """ - if not isinstance(par_names,list): + if not isinstance(par_names, list): par_names = list(par_names) - if not isinstance(obs_names,list): + if not isinstance(obs_names, list): obs_names = list(obs_names) - new_pst = pyemu.Pst("pest.pst",load=False) - pargp_data = _populate_dataframe(["pargp"], new_pst.pargp_fieldnames, - new_pst.pargp_defaults, new_pst.pargp_dtype) + new_pst = pyemu.Pst("pest.pst", load=False) + pargp_data = _populate_dataframe( + ["pargp"], new_pst.pargp_fieldnames, new_pst.pargp_defaults, new_pst.pargp_dtype + ) new_pst.parameter_groups = pargp_data - par_data = _populate_dataframe(par_names, new_pst.par_fieldnames, - new_pst.par_defaults, new_pst.par_dtype) - par_data.loc[:,"parnme"] = par_names + par_data = _populate_dataframe( + par_names, new_pst.par_fieldnames, new_pst.par_defaults, new_pst.par_dtype + ) + par_data.loc[:, "parnme"] = par_names par_data.index = par_names par_data.sort_index(inplace=True) new_pst.parameter_data = par_data - obs_data = _populate_dataframe(obs_names, new_pst.obs_fieldnames, - new_pst.obs_defaults, new_pst.obs_dtype) - obs_data.loc[:,"obsnme"] = obs_names + obs_data = _populate_dataframe( + obs_names, new_pst.obs_fieldnames, new_pst.obs_defaults, new_pst.obs_dtype + ) + obs_data.loc[:, "obsnme"] = obs_names obs_data.index = obs_names obs_data.sort_index(inplace=True) new_pst.observation_data = obs_data @@ -565,7 +688,7 @@ def generic_pst(par_names=["par1"],obs_names=["obs1"],addreg=False): new_pst.prior_information = new_pst.null_prior - #new_pst.other_lines = ["* singular value decomposition\n","1\n", + # new_pst.other_lines = ["* singular value decomposition\n","1\n", # "{0:d} {1:15.6E}\n".format(new_pst.npar_adj,1.0E-6), # "1 1 1\n"] if addreg: @@ -574,7 +697,7 @@ def generic_pst(par_names=["par1"],obs_names=["obs1"],addreg=False): return new_pst -def try_process_output_file(ins_file,output_file=None): +def try_process_output_file(ins_file, output_file=None): """attempt to process a model output file using a PEST-style instruction file Args: @@ -597,7 +720,7 @@ def try_process_output_file(ins_file,output_file=None): """ if output_file is None: - output_file = ins_file.replace(".ins","") + output_file = ins_file.replace(".ins", "") df = None try: i = InstructionFile(ins_file) @@ -627,33 +750,38 @@ def try_process_output_pst(pst): """ - for ins_file,out_file in zip(pst.instruction_files,pst.output_files): + for ins_file, out_file in zip(pst.instruction_files, pst.output_files): df = None try: - i = InstructionFile(ins_file,pst=pst) + i = InstructionFile(ins_file, pst=pst) df = i.read_output_file(out_file) except Exception as e: - warnings.warn("error processing instruction file {0}, trying inschek: {1}".format(ins_file,str(e))) - df = _try_run_inschek(ins_file,out_file) + warnings.warn( + "error processing instruction file {0}, trying inschek: {1}".format( + ins_file, str(e) + ) + ) + df = _try_run_inschek(ins_file, out_file) if df is not None: pst.observation_data.loc[df.index, "obsval"] = df.obsval -def _try_run_inschek(ins_file,out_file,cwd='.'): +def _try_run_inschek(ins_file, out_file, cwd="."): """try to run inschek and load the resulting obf file """ try: - pyemu.os_utils.run("inschek {0} {1}".format(ins_file, out_file),cwd=cwd) - obf_file = os.path.join(cwd,ins_file.replace(".ins", ".obf")) - df = pd.read_csv(obf_file, delim_whitespace=True, - skiprows=0, index_col=0, names=["obsval"]) + pyemu.os_utils.run("inschek {0} {1}".format(ins_file, out_file), cwd=cwd) + obf_file = os.path.join(cwd, ins_file.replace(".ins", ".obf")) + df = pd.read_csv( + obf_file, delim_whitespace=True, skiprows=0, index_col=0, names=["obsval"] + ) df.index = df.index.map(str.lower) return df except Exception as e: - print("error using inschek for instruction file {0}:{1}". - format(ins_file, str(e))) - print("observations in this instruction file will have" + - "generic values.") + print( + "error using inschek for instruction file {0}:{1}".format(ins_file, str(e)) + ) + print("observations in this instruction file will have" + "generic values.") return None @@ -673,17 +801,19 @@ def get_phi_comps_from_recfile(recfile): """ iiter = 1 iters = {} - f = open(recfile,'r') + f = open(recfile, "r") while True: line = f.readline() - if line == '': + if line == "": break - if "starting phi for this iteration" in line.lower() or \ - "final phi" in line.lower(): + if ( + "starting phi for this iteration" in line.lower() + or "final phi" in line.lower() + ): contributions = {} while True: line = f.readline() - if line == '': + if line == "": break if "contribution to phi" not in line.lower(): iters[iiter] = contributions @@ -691,7 +821,7 @@ def get_phi_comps_from_recfile(recfile): break raw = line.strip().split() val = float(raw[-1]) - group = raw[-3].lower().replace('\"', '') + group = raw[-3].lower().replace('"', "") contributions[group] = val return iters @@ -720,7 +850,8 @@ def res_from_obseravtion_data(observation_data): res_df.loc[:, "residual"] = np.NaN return res_df -def clean_missing_exponent(pst_filename,clean_filename="clean.pst"): + +def clean_missing_exponent(pst_filename, clean_filename="clean.pst"): """fixes the issue where some terrible fortran program may have written a floating point format without the 'e' - like 1.0-3, really?! @@ -731,26 +862,37 @@ def clean_missing_exponent(pst_filename,clean_filename="clean.pst"): """ lines = [] - with open(pst_filename,'r') as f: + with open(pst_filename, "r") as f: for line in f: line = line.lower().strip() - if '+' in line: - raw = line.split('+') - for i,r in enumerate(raw[:-1]): - if r[-1] != 'e': - r = r + 'e' + if "+" in line: + raw = line.split("+") + for i, r in enumerate(raw[:-1]): + if r[-1] != "e": + r = r + "e" raw[i] = r - lines.append('+'.join(raw)) + lines.append("+".join(raw)) else: lines.append(line) - with open(clean_filename,'w') as f: + with open(clean_filename, "w") as f: for line in lines: - f.write(line+'\n') - - -def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None, - marker='~',includes_header=True,includes_index=True,prefix='', - longnames=False, head_lines_len=0, sep=',', gpname=False): + f.write(line + "\n") + + +def csv_to_ins_file( + csv_filename, + ins_filename=None, + only_cols=None, + only_rows=None, + marker="~", + includes_header=True, + includes_index=True, + prefix="", + longnames=False, + head_lines_len=0, + sep=",", + gpname=False, +): """write a PEST-style instruction file from an existing CSV file Args: @@ -782,8 +924,8 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None """ # process the csv_filename in case it is a dataframe - if isinstance(csv_filename,str): - df = pd.read_csv(csv_filename,index_col=0) + if isinstance(csv_filename, str): + df = pd.read_csv(csv_filename, index_col=0) df.columns = df.columns.map(str.lower) df.index = df.index.map(lambda x: str(x).lower()) else: @@ -793,7 +935,7 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None if only_cols is None: only_cols = set(df.columns.map(lambda x: x.lower().strip()).tolist()) else: - if isinstance(only_cols, str): # incase it is a single name + if isinstance(only_cols, str): # incase it is a single name only_cols = [only_cols] only_cols = set(only_cols) only_cols = {c.lower() if isinstance(c, str) else c for c in only_cols} @@ -801,7 +943,7 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None if only_rows is None: only_rows = set(df.index.map(lambda x: x.lower().strip()).tolist()) else: - if isinstance(only_rows, str): # incase it is a single name + if isinstance(only_rows, str): # incase it is a single name only_rows = [only_rows] only_rows = set(only_rows) only_cols = {c.lower() if isinstance(c, str) else c for c in only_cols} @@ -818,25 +960,25 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None row_visit[rname] += 1 else: row_visit[rname] = 1 - rsuffix = '' + rsuffix = "" rlabel = rname + rsuffix rlabels.append(rlabel) if rname in only_rows: only_rlabels.append(rlabel) only_rlabels = set(only_rlabels) - #process the col labels, handling duplicates + # process the col labels, handling duplicates clabels = [] col_visit = {} only_clabels = [] for cname in df.columns: cname = str(cname).strip().lower() if cname in col_visit: - csuffix = str(int(col_visit[cname]+1)) + csuffix = str(int(col_visit[cname] + 1)) col_visit[cname] += 1 else: col_visit[cname] = 1 - csuffix = '' + csuffix = "" clabel = cname + csuffix clabels.append(clabel) if cname in only_cols: @@ -844,27 +986,27 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None only_clabels = set(only_clabels) if ins_filename is None: - if not isinstance(csv_filename,str): + if not isinstance(csv_filename, str): raise Exception("ins_filename is None but csv_filename is not string") ins_filename = csv_filename + ".ins" - row_visit, col_visit = {},{} + row_visit, col_visit = {}, {} onames = [] ovals = [] ognames = [] only_clabels_len = len(only_clabels) clabels_len = len(clabels) prefix_is_str = isinstance(prefix, str) - vals = df.values.copy() # wasteful but way faster - with open(ins_filename,'w') as f: + vals = df.values.copy() # wasteful but way faster + with open(ins_filename, "w") as f: f.write("pif {0}\n".format(marker)) [f.write("l1\n") for _ in range(head_lines_len)] if includes_header: f.write("l1\n") # skip the row (index) label - for i,rlabel in enumerate(rlabels): # loop over rows + for i, rlabel in enumerate(rlabels): # loop over rows f.write("l1") c_count = 0 - for j,clabel in enumerate(clabels): # loop over columns - oname = '' + for j, clabel in enumerate(clabels): # loop over columns + oname = "" if c_count < only_clabels_len: # if we haven't yet set up all obs if rlabel in only_rlabels and clabel in only_clabels: # define obs names @@ -876,8 +1018,8 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None nname = "{0}_usecol:{1}".format(nprefix, clabel) oname = "{0}_{1}".format(nname, rlabel) else: - nname = nprefix+clabel - oname = nprefix+rlabel+"_"+clabel + nname = nprefix + clabel + oname = nprefix + rlabel + "_" + clabel onames.append(oname) # append list of obs ovals.append(vals[i, j]) # store current obs val # defin group name @@ -897,26 +1039,27 @@ def csv_to_ins_file(csv_filename,ins_filename=None,only_cols=None,only_rows=None c_count += 1 # else: # not a requested observation; add spacer if j < clabels_len - 1: - if sep == ',': + if sep == ",": oname = "{0} {1},{1}".format(oname, marker) else: oname = "{0} w".format(oname) if j == 0: # if first col and input file has an index need additional spacer if includes_index: - if sep == ',': + if sep == ",": f.write(" {0},{0}".format(marker)) else: f.write(" w") f.write(oname) - f.write('\n') - odf = pd.DataFrame({"obsnme": onames, "obsval": ovals, 'obgnme': ognames}, - index=onames).dropna(axis=1) # dropna to keep consistent after adding obgnme + f.write("\n") + odf = pd.DataFrame( + {"obsnme": onames, "obsval": ovals, "obgnme": ognames}, index=onames + ).dropna( + axis=1 + ) # dropna to keep consistent after adding obgnme return odf - - class InstructionFile(object): """class for handling instruction files. @@ -931,15 +1074,16 @@ class InstructionFile(object): df = i.read_output_file("my.output") """ - def __init__(self,ins_filename,pst=None): + + def __init__(self, ins_filename, pst=None): self._ins_linecount = 0 self._out_linecount = 0 self._ins_filename = ins_filename - #self._pst = pst + # self._pst = pst self._marker = None self._ins_filehandle = None self._out_filehandle = None - self._last_line = '' + self._last_line = "" self._full_oname_set = None if pst is not None: self._full_oname_set = set(pst.obs_names) @@ -954,7 +1098,6 @@ def __init__(self,ins_filename,pst=None): def obs_name_set(self): return self._found_oname_set - def read_ins_file(self): """read the instruction and do some minimal error checking. @@ -967,10 +1110,17 @@ def read_ins_file(self): self._instruction_lcount = [] first_line = self._readline_ins() if len(first_line) < 2: - raise Exception("first line of ins file must have atleast two entries, not '{0}'".format(','.join(first_line))) + raise Exception( + "first line of ins file must have atleast two entries, not '{0}'".format( + ",".join(first_line) + ) + ) if first_line[0] != "pif": - raise Exception("first line of ins file '{0}' must start with 'pif', not '{1}'". \ - format(self._ins_filename, first_line[0])) + raise Exception( + "first line of ins file '{0}' must start with 'pif', not '{1}'".format( + self._ins_filename, first_line[0] + ) + ) self._marker = first_line[1] while True: line = self._readline_ins() @@ -980,48 +1130,62 @@ def read_ins_file(self): elif len(line) == 0: self.throw_ins_warning("empty line, breaking") break - elif line[0].startswith('l'): + elif line[0].startswith("l"): pass elif line[0].startswith(self._marker): pass - elif line[0].startswith('&'): + elif line[0].startswith("&"): self.throw_ins_error("line continuation not supported") else: - self.throw_ins_error("first token must be line advance ('l'), primary marker, or continuation ('&'),"+ - "not: {0}".format(line[0])) + self.throw_ins_error( + "first token must be line advance ('l'), primary marker, or continuation ('&')," + + "not: {0}".format(line[0]) + ) for token in line[1:]: if token.startswith("t"): self.throw_ins_error("tab instruction not supported") elif token.startswith(self._marker): if not token.endswith(self._marker): - self.throw_ins_error("unbalanced secondary marker in token '{0}'".format(token)) - + self.throw_ins_error( + "unbalanced secondary marker in token '{0}'".format(token) + ) - for somarker,eomarker in zip(['!','[','('],['!',']',')']): + for somarker, eomarker in zip(["!", "[", "("], ["!", "]", ")"]): # if token[0] == somarker: ofound = True if eomarker not in token[1:]: - self.throw_ins_error("unmatched observation marker '{0}', looking for '{1}' in token '{2}'".\ - format(somarker,eomarker,token)) - raw = token[1:].split(eomarker)[0].replace(somarker,'') - if raw == 'dum': + self.throw_ins_error( + "unmatched observation marker '{0}', looking for '{1}' in token '{2}'".format( + somarker, eomarker, token + ) + ) + raw = token[1:].split(eomarker)[0].replace(somarker, "") + if raw == "dum": pass else: - if self._full_oname_set is not None and raw not in self._full_oname_set: - self.throw_ins_error("obs name '{0}' not in pst".format(raw)) + if ( + self._full_oname_set is not None + and raw not in self._full_oname_set + ): + self.throw_ins_error( + "obs name '{0}' not in pst".format(raw) + ) elif raw in self._found_oname_set: - self.throw_ins_error("obs name '{0}' is listed more than once".format(raw)) + self.throw_ins_error( + "obs name '{0}' is listed more than once".format( + raw + ) + ) self._found_oname_set.add(raw) break - #print(raw) + # print(raw) self._instruction_lines.append(line) self._instruction_lcount.append(self._ins_linecount) - - def throw_ins_warning(self,message,lcount=None): + def throw_ins_warning(self, message, lcount=None): """throw a verbose PyemuWarning Args: @@ -1031,10 +1195,14 @@ def throw_ins_warning(self,message,lcount=None): """ if lcount is None: lcount = self._ins_linecount - warnings.warn("InstructionFile error processing instruction file {0} on line number {1}: {2}".\ - format(self._ins_filename,lcount,message),PyemuWarning) - - def throw_ins_error(self,message,lcount=None): + warnings.warn( + "InstructionFile error processing instruction file {0} on line number {1}: {2}".format( + self._ins_filename, lcount, message + ), + PyemuWarning, + ) + + def throw_ins_error(self, message, lcount=None): """throw a verbose instruction file error Args: @@ -1043,10 +1211,13 @@ def throw_ins_error(self,message,lcount=None): """ if lcount is None: lcount = self._ins_linecount - raise Exception("InstructionFile error processing instruction file on line number {0}: {1}".\ - format(lcount,message)) + raise Exception( + "InstructionFile error processing instruction file on line number {0}: {1}".format( + lcount, message + ) + ) - def throw_out_error(self,message,lcount=None): + def throw_out_error(self, message, lcount=None): """throw a verbose output file error Args: @@ -1056,10 +1227,13 @@ def throw_out_error(self,message,lcount=None): """ if lcount is None: lcount = self._out_linecount - raise Exception("InstructionFile error processing output file on line number {0}: {1}".\ - format(lcount,message)) + raise Exception( + "InstructionFile error processing output file on line number {0}: {1}".format( + lcount, message + ) + ) - def read_output_file(self,output_file): + def read_output_file(self, output_file): """process a model output file using `InstructionFile.instruction_set` Args: @@ -1074,104 +1248,134 @@ def read_output_file(self,output_file): """ self._out_filename = output_file val_dict = {} - for ins_line,ins_lcount in zip(self._instruction_lines,self._instruction_lcount): - #try: - val_dict.update(self._execute_ins_line(ins_line,ins_lcount)) - #except Exception as e: + for ins_line, ins_lcount in zip( + self._instruction_lines, self._instruction_lcount + ): + # try: + val_dict.update(self._execute_ins_line(ins_line, ins_lcount)) + # except Exception as e: # raise Exception(str(e)) s = pd.Series(val_dict) s.sort_index(inplace=True) - return pd.DataFrame({"obsval":s},index=s.index) + return pd.DataFrame({"obsval": s}, index=s.index) - def _execute_ins_line(self,ins_line,ins_lcount): + def _execute_ins_line(self, ins_line, ins_lcount): """private method to process output file lines with an instruction line """ cursor_pos = 0 val_dict = {} - #for ii,ins in enumerate(ins_line): + # for ii,ins in enumerate(ins_line): ii = 0 all_markers = True - line_seps = set([","," ","\t"]) + line_seps = set([",", " ", "\t"]) while True: if ii >= len(ins_line): break ins = ins_line[ii] - #primary marker + # primary marker if ii == 0 and ins.startswith(self._marker): - mstr = ins.replace(self._marker,'') + mstr = ins.replace(self._marker, "") while True: line = self._readline_output() if line is None: self.throw_out_error( - "EOF when trying to find primary marker '{0}' from instruction file line {1}".format(mstr,ins_lcount)) + "EOF when trying to find primary marker '{0}' from instruction file line {1}".format( + mstr, ins_lcount + ) + ) if mstr in line: break cursor_pos = line.index(mstr) + len(mstr) # line advance - elif ins.startswith('l'): + elif ins.startswith("l"): try: nlines = int(ins[1:]) except Exception as e: - self.throw_ins_error("casting line advance to int for instruction '{0}'". \ - format(ins), ins_lcount) + self.throw_ins_error( + "casting line advance to int for instruction '{0}'".format(ins), + ins_lcount, + ) for i in range(nlines): line = self._readline_output() if line is None: - self.throw_out_error("EOF when trying to read {0} lines for line advance instruction '{1}', from instruction file line number {2}". \ - format(nlines, ins, ins_lcount)) - elif ins == 'w': - raw = line[cursor_pos:].replace(","," ").split() + self.throw_out_error( + "EOF when trying to read {0} lines for line advance instruction '{1}', from instruction file line number {2}".format( + nlines, ins, ins_lcount + ) + ) + elif ins == "w": + raw = line[cursor_pos:].replace(",", " ").split() if line[cursor_pos] in line_seps: - raw.insert(0,'') + raw.insert(0, "") if len(raw) == 1: - self.throw_out_error("no whitespaces found on output line {0} past {1}".format(line,cursor_pos)) + self.throw_out_error( + "no whitespaces found on output line {0} past {1}".format( + line, cursor_pos + ) + ) # step over current value - cursor_pos = (cursor_pos + - line[cursor_pos:].replace(',', ' ').index(' ')) + cursor_pos = cursor_pos + line[cursor_pos:].replace(",", " ").index(" ") # now find position of next entry - cursor_pos = (cursor_pos + - line[cursor_pos:].replace(',', ' '). - index(raw[1])) + cursor_pos = cursor_pos + line[cursor_pos:].replace(",", " ").index( + raw[1] + ) - elif ins.startswith('!'): - oname = ins.replace('!','') + elif ins.startswith("!"): + oname = ins.replace("!", "") # look a head for a sec marker - if ii < len(ins_line) - 1 and ins_line[ii+1].startswith(self._marker): - m = ins_line[ii+1].replace(self._marker,'') + if ii < len(ins_line) - 1 and ins_line[ii + 1].startswith(self._marker): + m = ins_line[ii + 1].replace(self._marker, "") if m not in line[cursor_pos:]: - self.throw_out_error("secondary marker '{0}' not found from cursor_pos {2}".format(m,cursor_pos)) + self.throw_out_error( + "secondary marker '{0}' not found from cursor_pos {2}".format( + m, cursor_pos + ) + ) val_str = line[cursor_pos:].split(m)[0] else: - val_str = line[cursor_pos:].replace(","," ").split()[0] + val_str = line[cursor_pos:].replace(",", " ").split()[0] try: val = float(val_str) except Exception as e: - self.throw_out_error("casting string '{0}' to float for instruction '{1}'".format(val_str,ins)) + self.throw_out_error( + "casting string '{0}' to float for instruction '{1}'".format( + val_str, ins + ) + ) if oname != "dum": val_dict[oname] = val ipos = line[cursor_pos:].index(val_str.strip()) - #val_len = len(val_str) - cursor_pos = cursor_pos + line[cursor_pos:].index(val_str.strip()) + len(val_str) + # val_len = len(val_str) + cursor_pos = ( + cursor_pos + line[cursor_pos:].index(val_str.strip()) + len(val_str) + ) all_markers = False elif ins.startswith(self._marker): - m = ins.replace(self._marker,'') + m = ins.replace(self._marker, "") if m not in line[cursor_pos:]: if all_markers: ii = 0 continue else: - self.throw_out_error("secondary marker '{0}' not found from cursor_pos {2}".\ - format(m,cursor_pos)) + self.throw_out_error( + "secondary marker '{0}' not found from cursor_pos {2}".format( + m, cursor_pos + ) + ) cursor_pos = cursor_pos + line[cursor_pos:].index(m) + len(m) else: - self.throw_out_error("unrecognized instruction '{0}' on ins file line {1}".format(ins,ins_lcount)) + self.throw_out_error( + "unrecognized instruction '{0}' on ins file line {1}".format( + ins, ins_lcount + ) + ) ii += 1 return val_dict @@ -1181,59 +1385,62 @@ def _readline_ins(self): """ if self._ins_filehandle is None: if not os.path.exists(self._ins_filename): - raise Exception("instruction file '{0}' not found".format(self._ins_filename)) - self._ins_filehandle = open(self._ins_filename,'r') + raise Exception( + "instruction file '{0}' not found".format(self._ins_filename) + ) + self._ins_filehandle = open(self._ins_filename, "r") line = self._ins_filehandle.readline() self._ins_linecount += 1 - if line == '': + if line == "": return None self._last_line = line # check for spaces in between the markers - this gets ugly line = line.lower() if self._marker is not None and self._marker in line: + def find_all(a_str, sub): start = 0 while True: start = a_str.find(sub, start) - if start == -1: return + if start == -1: + return yield start start += len(sub) - midx = list(find_all(line,self._marker)) + + midx = list(find_all(line, self._marker)) midx.append(len(line)) - first =line[:midx[0]].strip() + first = line[: midx[0]].strip() tokens = [] if len(first) > 0: tokens.append(first) - for idx in range(1,len(midx)-1,2): - mstr = line[midx[idx-1]:midx[idx]+1] - ostr = line[midx[idx]+1:midx[idx+1]] + for idx in range(1, len(midx) - 1, 2): + mstr = line[midx[idx - 1] : midx[idx] + 1] + ostr = line[midx[idx] + 1 : midx[idx + 1]] tokens.append(mstr) tokens.extend(ostr.split()) else: tokens = line.strip().split() return tokens - def _readline_output(self): """consolidate private method to read the next output file line. Casts to lower """ if self._out_filehandle is None: if not os.path.exists(self._out_filename): - raise Exception("output file '{0}' not found".format(self._out_filename)) - self._out_filehandle = open(self._out_filename,'r') + raise Exception( + "output file '{0}' not found".format(self._out_filename) + ) + self._out_filehandle = open(self._out_filename, "r") line = self._out_filehandle.readline() self._out_linecount += 1 - if line == '': + if line == "": return None self._last_line = line return line.lower() - - - -def process_output_files(pst,pst_path='.'): +def process_output_files(pst, pst_path="."): """helper function to process output files using the InstructionFile class @@ -1254,24 +1461,25 @@ def process_output_files(pst,pst_path='.'): """ - if not isinstance(pst,pyemu.Pst): - raise Exception("process_output_files error: 'pst' arg must be pyemu.Pst instance") + if not isinstance(pst, pyemu.Pst): + raise Exception( + "process_output_files error: 'pst' arg must be pyemu.Pst instance" + ) series = [] - for ins,out in zip(pst.instruction_files,pst.output_files): - ins = os.path.join(pst_path,ins) - out = os.path.join(pst_path,out) + for ins, out in zip(pst.instruction_files, pst.output_files): + ins = os.path.join(pst_path, ins) + out = os.path.join(pst_path, out) if not os.path.exists(out): - warnings.warn("out file '{0}' not found".format(out),PyemuWarning) - f = os.path.join(pst_path,ins) - i = InstructionFile(ins,pst=pst) + warnings.warn("out file '{0}' not found".format(out), PyemuWarning) + f = os.path.join(pst_path, ins) + i = InstructionFile(ins, pst=pst) try: s = i.read_output_file(out) series.append(s) except Exception as e: - warnings.warn("error processing output file '{0}': {1}".format(out,str(e))) + warnings.warn("error processing output file '{0}': {1}".format(out, str(e))) if len(series) == 0: return None series = pd.concat(series) - #print(series) + # print(series) return series - diff --git a/pyemu/pyemu_warnings.py b/pyemu/pyemu_warnings.py index 0eb24b4da..7876baf44 100644 --- a/pyemu/pyemu_warnings.py +++ b/pyemu/pyemu_warnings.py @@ -1,4 +1,5 @@ import warnings + class PyemuWarning(RuntimeWarning): - pass \ No newline at end of file + pass diff --git a/pyemu/sc.py b/pyemu/sc.py index 85b324ab5..5c60688ac 100644 --- a/pyemu/sc.py +++ b/pyemu/sc.py @@ -8,6 +8,7 @@ from pyemu.la import LinearAnalysis from pyemu.mat import Cov, Matrix + class Schur(LinearAnalysis): """FOSM-based uncertainty and data-worth analysis @@ -59,11 +60,11 @@ class Schur(LinearAnalysis): print(sc.get_parameter_contribution()) """ - def __init__(self,jco,**kwargs): + + def __init__(self, jco, **kwargs): self.__posterior_prediction = None self.__posterior_parameter = None - super(Schur,self).__init__(jco,**kwargs) - + super(Schur, self).__init__(jco, **kwargs) # @property # def pandas(self): @@ -114,16 +115,17 @@ def posterior_parameter(self): except Exception as e: pinv.to_ascii("parcov_inv.err.cov") - self.logger.warn("error forming schur's complement: {0}". - format(str(e))) + self.logger.warn("error forming schur's complement: {0}".format(str(e))) self.xtqx.to_binary("xtqx.err.jcb") self.logger.warn("problemtic xtqx saved to xtqx.err.jcb") - self.logger.warn("problematic inverse parcov saved to parcov_inv.err.cov") - raise Exception("error forming schur's complement: {0}". - format(str(e))) + self.logger.warn( + "problematic inverse parcov saved to parcov_inv.err.cov" + ) + raise Exception("error forming schur's complement: {0}".format(str(e))) assert r.row_names == r.col_names - self.__posterior_parameter = Cov(r.x, row_names=r.row_names, - col_names=r.col_names) + self.__posterior_parameter = Cov( + r.x, row_names=r.row_names, col_names=r.col_names + ) self.log("Schur's complement") return self.__posterior_parameter @@ -236,11 +238,12 @@ def posterior_prediction(self): self.log("propagating posterior to predictions") except: pass - post_cov = self.predictions.T *\ - self.posterior_parameter * self.predictions - self.__posterior_prediction = {n:v for n,v in - zip(post_cov.row_names, - np.diag(post_cov.x))} + post_cov = ( + self.predictions.T * self.posterior_parameter * self.predictions + ) + self.__posterior_prediction = { + n: v for n, v in zip(post_cov.row_names, np.diag(post_cov.x)) + } self.log("propagating posterior to predictions") else: self.__posterior_prediction = {} @@ -279,9 +282,10 @@ def get_parameter_summary(self): ureduce = 100.0 * (1.0 - (post / prior)) - return pd.DataFrame({"prior_var":prior,"post_var":post, - "percent_reduction":ureduce}, - index=self.posterior_parameter.col_names) + return pd.DataFrame( + {"prior_var": prior, "post_var": post, "percent_reduction": ureduce}, + index=self.posterior_parameter.col_names, + ) def get_forecast_summary(self): """summary of the FOSM-based forecast uncertainty (variance) estimate(s) @@ -305,15 +309,15 @@ def get_forecast_summary(self): plt.show() """ - sum = {"prior_var":[], "post_var":[], "percent_reduction":[]} + sum = {"prior_var": [], "post_var": [], "percent_reduction": []} for forecast in self.prior_forecast.keys(): pr = self.prior_forecast[forecast] pt = self.posterior_forecast[forecast] - ur = 100.0 * (1.0 - (pt/pr)) + ur = 100.0 * (1.0 - (pt / pr)) sum["prior_var"].append(pr) sum["post_var"].append(pt) sum["percent_reduction"].append(ur) - return pd.DataFrame(sum,index=self.prior_forecast.keys()) + return pd.DataFrame(sum, index=self.prior_forecast.keys()) def __contribution_from_parameters(self, parameter_names): """private method get the prior and posterior uncertainty reduction as a result of @@ -321,12 +325,12 @@ def __contribution_from_parameters(self, parameter_names): """ - #get the prior and posterior for the base case - bprior,bpost = self.prior_prediction, self.posterior_prediction - #get the prior and posterior for the conditioned case + # get the prior and posterior for the base case + bprior, bpost = self.prior_prediction, self.posterior_prediction + # get the prior and posterior for the conditioned case la_cond = self.get_conditional_instance(parameter_names) - cprior,cpost = la_cond.prior_prediction, la_cond.posterior_prediction - return cprior,cpost + cprior, cpost = la_cond.prior_prediction, la_cond.posterior_prediction + return cprior, cpost def get_conditional_instance(self, parameter_names): """ get a new `pyemu.Schur` instance that includes conditional update from @@ -352,19 +356,23 @@ def get_conditional_instance(self, parameter_names): for iname, name in enumerate(parameter_names): name = str(name).lower() parameter_names[iname] = name - assert name in self.jco.col_names,\ + assert name in self.jco.col_names, ( "contribution parameter " + name + " not found jco" + ) keep_names = [] for name in self.jco.col_names: if name not in parameter_names: keep_names.append(name) if len(keep_names) == 0: - raise Exception("Schur.contribution_from_Parameters " + - "atleast one parameter must remain uncertain") - #get the reduced predictions + raise Exception( + "Schur.contribution_from_Parameters " + + "atleast one parameter must remain uncertain" + ) + # get the reduced predictions if self.predictions is None: - raise Exception("Schur.contribution_from_Parameters " + - "no predictions have been set") + raise Exception( + "Schur.contribution_from_Parameters " + "no predictions have been set" + ) # cond_preds = [] # for pred in self.predictions: # cond_preds.append(pred.get(keep_names, pred.col_names)) @@ -373,12 +381,17 @@ def get_conditional_instance(self, parameter_names): pst = self.pst except: pst = None - la_cond = Schur(jco=self.jco.get(self.jco.row_names, keep_names),pst=pst, - parcov=self.parcov.condition_on(parameter_names), - obscov=self.obscov, predictions=cond_preds,verbose=False) + la_cond = Schur( + jco=self.jco.get(self.jco.row_names, keep_names), + pst=pst, + parcov=self.parcov.condition_on(parameter_names), + obscov=self.obscov, + predictions=cond_preds, + verbose=False, + ) return la_cond - def get_par_contribution(self,parlist_dict=None,include_prior_results=False): + def get_par_contribution(self, parlist_dict=None, include_prior_results=False): """A dataworth method to get a dataframe the prior and posterior uncertainty reduction as a result of some parameter becoming perfectly known @@ -419,42 +432,44 @@ def get_par_contribution(self,parlist_dict=None,include_prior_results=False): """ self.log("calculating contribution from parameters") if parlist_dict is None: - parlist_dict = {}#dict(zip(self.pst.adj_par_names,self.pst.adj_par_names)) + parlist_dict = ( + {} + ) # dict(zip(self.pst.adj_par_names,self.pst.adj_par_names)) # make sure all of the adjustable pars are in the jco for pname in self.pst.adj_par_names: if pname in self.jco.col_names: parlist_dict[pname] = pname else: if type(parlist_dict) == list: - parlist_dict = dict(zip(parlist_dict,parlist_dict)) + parlist_dict = dict(zip(parlist_dict, parlist_dict)) results = {} names = ["base"] for forecast in self.prior_forecast.keys(): pr = self.prior_forecast[forecast] pt = self.posterior_forecast[forecast] - #reduce = 100.0 * ((pr - pt) / pr) - results[(forecast,"prior")] = [pr] - results[(forecast,"post")] = [pt] - #results[(forecast,"percent_reduce")] = [reduce] - for case_name,par_list in parlist_dict.items(): + # reduce = 100.0 * ((pr - pt) / pr) + results[(forecast, "prior")] = [pr] + results[(forecast, "post")] = [pt] + # results[(forecast,"percent_reduce")] = [reduce] + for case_name, par_list in parlist_dict.items(): if len(par_list) == 0: continue names.append(case_name) self.log("calculating contribution from: " + str(par_list)) - case_prior,case_post = self.__contribution_from_parameters(par_list) + case_prior, case_post = self.__contribution_from_parameters(par_list) self.log("calculating contribution from: " + str(par_list)) for forecast in case_prior.keys(): pr = case_prior[forecast] pt = case_post[forecast] - #reduce = 100.0 * ((pr - pt) / pr) + # reduce = 100.0 * ((pr - pt) / pr) results[(forecast, "prior")].append(pr) results[(forecast, "post")].append(pt) - #results[(forecast, "percent_reduce")].append(reduce) + # results[(forecast, "percent_reduce")].append(reduce) - df = pd.DataFrame(results,index=names) - #base = df.loc["base",df.columns.get_level_values(1)=="post"] - #df = 1.0 - (df.loc[:,df.columns.get_level_values(1)=="post"] / base) + df = pd.DataFrame(results, index=names) + # base = df.loc["base",df.columns.get_level_values(1)=="post"] + # df = 1.0 - (df.loc[:,df.columns.get_level_values(1)=="post"] / base) self.log("calculating contribution from parameters") if include_prior_results: @@ -501,16 +516,20 @@ def get_par_group_contribution(self, include_prior_results=False): pargrp_dict = {} par = self.pst.parameter_data groups = par.groupby("pargp").groups - for grp,idxs in groups.items(): - #pargrp_dict[grp] = list(par.loc[idxs,"parnme"]) - pargrp_dict[grp] = [pname for pname in list(par.loc[idxs,"parnme"]) - if pname in self.jco.col_names and pname in self.parcov.row_names] - return self.get_par_contribution(pargrp_dict,include_prior_results=include_prior_results) - - - - def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, - reset_zero_weight=False): + for grp, idxs in groups.items(): + # pargrp_dict[grp] = list(par.loc[idxs,"parnme"]) + pargrp_dict[grp] = [ + pname + for pname in list(par.loc[idxs, "parnme"]) + if pname in self.jco.col_names and pname in self.parcov.row_names + ] + return self.get_par_contribution( + pargrp_dict, include_prior_results=include_prior_results + ) + + def get_added_obs_importance( + self, obslist_dict=None, base_obslist=None, reset_zero_weight=False + ): """A dataworth method to analyze the posterior uncertainty as a result of gathering some additional observations @@ -564,13 +583,14 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, if obslist_dict is not None: if type(obslist_dict) == list: - obslist_dict = dict(zip(obslist_dict,obslist_dict)) + obslist_dict = dict(zip(obslist_dict, obslist_dict)) reset = False if reset_zero_weight is not False: if not self.obscov.isdiagonal: - raise NotImplementedError("cannot reset weights for non-"+\ - "diagonal obscov") + raise NotImplementedError( + "cannot reset weights for non-" + "diagonal obscov" + ) reset = True try: weight = float(reset_zero_weight) @@ -578,7 +598,7 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, weight = 1.0 self.logger.statement("resetting zero weights to {0}".format(weight)) # make copies of the original obscov and pst - #org_obscov = self.obscov.get(self.obscov.row_names) + # org_obscov = self.obscov.get(self.obscov.row_names) org_obscov = self.obscov.copy() org_pst = self.pst.get() @@ -587,9 +607,12 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, # if we don't care about grouping obs, then just reset all weights at once if base_obslist is None and obslist_dict is None and reset: - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.row_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight + onames = [ + name + for name in self.pst.zero_weight_obs_names + if name in self.jco.row_names and name in self.obscov.row_names + ] + obs.loc[onames, "weight"] = weight # if needed reset the zero-weight obs in base_obslist if base_obslist is not None and reset: @@ -602,20 +625,24 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, base_obslist = [] else: if type(base_obslist) != list: - self.logger.lraise("Schur.get_added_obs)_importance: base_obslist must be" + - " type 'list', not {0}".format(str(type(base_obslist)))) + self.logger.lraise( + "Schur.get_added_obs)_importance: base_obslist must be" + + " type 'list', not {0}".format(str(type(base_obslist))) + ) # if needed reset the zero-weight obs in obslist_dict if obslist_dict is not None and reset: z_obs = [] - for case,obslist in obslist_dict.items(): - if not isinstance(obslist,list): + for case, obslist in obslist_dict.items(): + if not isinstance(obslist, list): obslist_dict[case] = [obslist] obslist = [obslist] inboth = set(base_obslist).intersection(set(obslist)) if len(inboth) > 0: - raise Exception("observation(s) listed in both "+\ - "base_obslist and obslist_dict: "+\ - ','.join(inboth)) + raise Exception( + "observation(s) listed in both " + + "base_obslist and obslist_dict: " + + ",".join(inboth) + ) z_obs.extend(obslist) self.log("resetting zero weight obs in obslist_dict") self.pst._adjust_weights_by_list(z_obs, weight) @@ -625,13 +652,19 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, if obslist_dict is None and reset: obs = self.pst.observation_data obs.index = obs.obsnme - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.row_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight + onames = [ + name + for name in self.pst.zero_weight_obs_names + if name in self.jco.row_names and name in self.obscov.row_names + ] + obs.loc[onames, "weight"] = weight if obslist_dict is None: - obslist_dict = {name:name for name in self.pst.nnz_obs_names if name\ - in self.jco.row_names and name in self.obscov.row_names} + obslist_dict = { + name: name + for name in self.pst.nnz_obs_names + if name in self.jco.row_names and name in self.obscov.row_names + } # reset the obs cov from the newly adjusted weights if reset: @@ -643,39 +676,48 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, names = ["base"] if base_obslist is None or len(base_obslist) == 0: - self.logger.statement("no base observation passed, 'base' case"+ - " is just the prior of the forecasts") - for forecast,pr in self.prior_forecast.items(): + self.logger.statement( + "no base observation passed, 'base' case" + + " is just the prior of the forecasts" + ) + for forecast, pr in self.prior_forecast.items(): results[forecast] = [pr] # reset base obslist for use later base_obslist = [] else: - base_posterior = self.get(par_names=self.jco.par_names, - obs_names=base_obslist).posterior_forecast - for forecast,pt in base_posterior.items(): + base_posterior = self.get( + par_names=self.jco.par_names, obs_names=base_obslist + ).posterior_forecast + for forecast, pt in base_posterior.items(): results[forecast] = [pt] - for case_name,obslist in obslist_dict.items(): + for case_name, obslist in obslist_dict.items(): names.append(case_name) - if not isinstance(obslist,list): + if not isinstance(obslist, list): obslist = [obslist] - self.log("calculating importance of observations by adding: " + - str(obslist) + '\n') + self.log( + "calculating importance of observations by adding: " + + str(obslist) + + "\n" + ) # this case is the combination of the base obs plus whatever unique # obs names in obslist case_obslist = list(base_obslist) dedup_obslist = [oname for oname in obslist if oname not in case_obslist] case_obslist.extend(dedup_obslist) - #print(self.pst.observation_data.loc[case_obslist,:]) - case_post = self.get(par_names=self.jco.col_names, - obs_names=case_obslist).posterior_forecast - for forecast,pt in case_post.items(): + # print(self.pst.observation_data.loc[case_obslist,:]) + case_post = self.get( + par_names=self.jco.col_names, obs_names=case_obslist + ).posterior_forecast + for forecast, pt in case_post.items(): results[forecast].append(pt) - self.log("calculating importance of observations by adding: " + - str(obslist) + '\n') - df = pd.DataFrame(results,index=names) - + self.log( + "calculating importance of observations by adding: " + + str(obslist) + + "\n" + ) + df = pd.DataFrame(results, index=names) if reset: self.reset_obscov(org_obscov) @@ -683,8 +725,7 @@ def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, return df - def get_removed_obs_importance(self,obslist_dict=None, - reset_zero_weight=False): + def get_removed_obs_importance(self, obslist_dict=None, reset_zero_weight=False): """A dataworth method to analyze the posterior uncertainty as a result of losing some existing observations @@ -723,19 +764,21 @@ def get_removed_obs_importance(self,obslist_dict=None, """ - if obslist_dict is not None: if type(obslist_dict) == list: - obslist_dict = dict(zip(obslist_dict,obslist_dict)) + obslist_dict = dict(zip(obslist_dict, obslist_dict)) elif reset_zero_weight is False and self.pst.nnz_obs == 0: - raise Exception("not resetting weights and there are no non-zero weight obs to remove") + raise Exception( + "not resetting weights and there are no non-zero weight obs to remove" + ) reset = False if reset_zero_weight is not False: if not self.obscov.isdiagonal: - raise NotImplementedError("cannot reset weights for non-"+\ - "diagonal obscov") + raise NotImplementedError( + "cannot reset weights for non-" + "diagonal obscov" + ) reset = True try: weight = float(reset_zero_weight) @@ -749,28 +792,28 @@ def get_removed_obs_importance(self,obslist_dict=None, self.log("calculating importance of observations") if reset and obslist_dict is None: obs = self.pst.observation_data - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.row_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight + onames = [ + name + for name in self.pst.zero_weight_obs_names + if name in self.jco.row_names and name in self.obscov.row_names + ] + obs.loc[onames, "weight"] = weight if obslist_dict is None: - obslist_dict = dict(zip(self.pst.nnz_obs_names, - self.pst.nnz_obs_names)) - + obslist_dict = dict(zip(self.pst.nnz_obs_names, self.pst.nnz_obs_names)) elif reset: self.pst.observation_data.index = self.pst.observation_data.obsnme - for name,obslist in obslist_dict.items(): + for name, obslist in obslist_dict.items(): self.log("resetting weights in obs in group {0}".format(name)) - self.pst._adjust_weights_by_list(obslist,weight) + self.pst._adjust_weights_by_list(obslist, weight) self.log("resetting weights in obs in group {0}".format(name)) - for case,obslist in obslist_dict.items(): - if not isinstance(obslist,list): + for case, obslist in obslist_dict.items(): + if not isinstance(obslist, list): obslist = [obslist] obslist_dict[case] = obslist - if reset: self.log("resetting self.obscov") self.reset_obscov(self.pst) @@ -778,34 +821,47 @@ def get_removed_obs_importance(self,obslist_dict=None, results = {} names = ["base"] - for forecast,pt in self.posterior_forecast.items(): + for forecast, pt in self.posterior_forecast.items(): results[forecast] = [pt] - for case_name,obslist in obslist_dict.items(): - if not isinstance(obslist,list): + for case_name, obslist in obslist_dict.items(): + if not isinstance(obslist, list): obslist = [obslist] names.append(case_name) - self.log("calculating importance of observations by removing: " + - str(obslist) + '\n') + self.log( + "calculating importance of observations by removing: " + + str(obslist) + + "\n" + ) # check for missing names - missing_onames = [oname for oname in obslist if oname not in self.jco.row_names] + missing_onames = [ + oname for oname in obslist if oname not in self.jco.row_names + ] if len(missing_onames) > 0: - raise Exception("case {0} has observation names ".format(case_name) + \ - "not found: " + ','.join(missing_onames)) + raise Exception( + "case {0} has observation names ".format(case_name) + + "not found: " + + ",".join(missing_onames) + ) # find the set difference between obslist and jco obs names - #diff_onames = [oname for oname in self.jco.obs_names if oname not in obslist] - diff_onames = [oname for oname in self.nnz_obs_names if oname not in obslist and oname not in self.forecast_names] - + # diff_onames = [oname for oname in self.jco.obs_names if oname not in obslist] + diff_onames = [ + oname + for oname in self.nnz_obs_names + if oname not in obslist and oname not in self.forecast_names + ] # calculate the increase in forecast variance by not using the obs # in obslist - case_post = self.get(par_names=self.jco.col_names, - obs_names=diff_onames).posterior_forecast + case_post = self.get( + par_names=self.jco.col_names, obs_names=diff_onames + ).posterior_forecast - for forecast,pt in case_post.items(): + for forecast, pt in case_post.items(): results[forecast].append(pt) - df = pd.DataFrame(results,index=names) - self.log("calculating importance of observations by removing: " + - str(obslist) + '\n') + df = pd.DataFrame(results, index=names) + self.log( + "calculating importance of observations by removing: " + str(obslist) + "\n" + ) if reset: self.reset_obscov(org_obscov) @@ -832,15 +888,13 @@ def get_obs_group_dict(self): obsgrp_dict = {} obs = self.pst.observation_data obs.index = obs.obsnme - obs = obs.loc[self.jco.row_names,:] + obs = obs.loc[self.jco.row_names, :] groups = obs.groupby("obgnme").groups for grp, idxs in groups.items(): - obsgrp_dict[grp] = list(obs.loc[idxs,"obsnme"]) + obsgrp_dict[grp] = list(obs.loc[idxs, "obsnme"]) return obsgrp_dict - - - def get_removed_obs_group_importance(self,reset_zero_weight=False): + def get_removed_obs_group_importance(self, reset_zero_weight=False): """A dataworth method to analyze the posterior uncertainty as a result of losing existing observations, tested by observation groups @@ -873,8 +927,9 @@ def get_removed_obs_group_importance(self,reset_zero_weight=False): df = sc.get_removed_obs_group_importance(reset_zero_weight=True) """ - return self.get_removed_obs_importance(self.get_obs_group_dict(), reset_zero_weight=reset_zero_weight) - + return self.get_removed_obs_importance( + self.get_obs_group_dict(), reset_zero_weight=reset_zero_weight + ) def get_added_obs_group_importance(self, reset_zero_weight=False): """A dataworth method to analyze the posterior uncertainty as a result of gaining @@ -910,11 +965,18 @@ def get_added_obs_group_importance(self, reset_zero_weight=False): df = sc.get_added_obs_group_importance(reset_zero_weight=True) """ - return self.get_added_obs_importance(self.get_obs_group_dict(), reset_zero_weight=reset_zero_weight) - - def next_most_important_added_obs(self,forecast=None,niter=3, obslist_dict=None, - base_obslist=None, - reset_zero_weight=False): + return self.get_added_obs_importance( + self.get_obs_group_dict(), reset_zero_weight=reset_zero_weight + ) + + def next_most_important_added_obs( + self, + forecast=None, + niter=3, + obslist_dict=None, + base_obslist=None, + reset_zero_weight=False, + ): """find the most important observation(s) by sequentially evaluating the importance of the observations in `obslist_dict`. @@ -964,12 +1026,12 @@ def next_most_important_added_obs(self,forecast=None,niter=3, obslist_dict=None, """ - if forecast is None: - assert self.forecasts.shape[1] == 1,"forecast arg list one and only one" +\ - " forecast" + assert self.forecasts.shape[1] == 1, ( + "forecast arg list one and only one" + " forecast" + ) forecast = self.forecasts[0].col_names[0] - #elif forecast not in self.prediction_arg: + # elif forecast not in self.prediction_arg: # raise Exception("forecast {0} not found".format(forecast)) else: @@ -987,30 +1049,32 @@ def next_most_important_added_obs(self,forecast=None,niter=3, obslist_dict=None, else: obs_being_used = [] - best_case, best_results = [],[] + best_case, best_results = [], [] for iiter in range(niter): - self.log("next most important added obs iteration {0}".format(iiter+1)) - df = self.get_added_obs_importance(obslist_dict=obslist_dict, - base_obslist=obs_being_used, - reset_zero_weight=reset_zero_weight) + self.log("next most important added obs iteration {0}".format(iiter + 1)) + df = self.get_added_obs_importance( + obslist_dict=obslist_dict, + base_obslist=obs_being_used, + reset_zero_weight=reset_zero_weight, + ) if iiter == 0: - init_base = df.loc["base",forecast].copy() - fore_df = df.loc[:,forecast] + init_base = df.loc["base", forecast].copy() + fore_df = df.loc[:, forecast] fore_diff_df = fore_df - fore_df.loc["base"] fore_diff_df.sort_values(inplace=True) iter_best_name = fore_diff_df.index[0] - iter_best_result = df.loc[iter_best_name,forecast] - iter_base_result = df.loc["base",forecast] - diff_percent_init = 100.0 * (init_base - - iter_best_result) / init_base - diff_percent_iter = 100.0 * (iter_base_result - - iter_best_result) / iter_base_result - self.log("next most important added obs iteration {0}".format(iiter+1)) - - - best_results.append([iter_best_name,iter_best_result, - diff_percent_iter,diff_percent_init]) + iter_best_result = df.loc[iter_best_name, forecast] + iter_base_result = df.loc["base", forecast] + diff_percent_init = 100.0 * (init_base - iter_best_result) / init_base + diff_percent_iter = ( + 100.0 * (iter_base_result - iter_best_result) / iter_base_result + ) + self.log("next most important added obs iteration {0}".format(iiter + 1)) + + best_results.append( + [iter_best_name, iter_best_result, diff_percent_iter, diff_percent_init] + ) best_case.append(iter_best_name) if iter_best_name.lower() == "base": @@ -1020,15 +1084,18 @@ def next_most_important_added_obs(self,forecast=None,niter=3, obslist_dict=None, onames = [iter_best_name] else: onames = obslist_dict.pop(iter_best_name) - if not isinstance(onames,list): + if not isinstance(onames, list): onames = [onames] obs_being_used.extend(onames) - columns = ["best_obs",forecast+"_variance", - "unc_reduce_iter_base","unc_reduce_initial_base"] - return pd.DataFrame(best_results,index=best_case,columns=columns) - - - def next_most_par_contribution(self,niter=3,forecast=None,parlist_dict=None): + columns = [ + "best_obs", + forecast + "_variance", + "unc_reduce_iter_base", + "unc_reduce_initial_base", + ] + return pd.DataFrame(best_results, index=best_case, columns=columns) + + def next_most_par_contribution(self, niter=3, forecast=None, parlist_dict=None): """find the parameter(s) contributing most to posterior forecast by sequentially evaluating the contribution of parameters in `parlist_dict`. @@ -1063,39 +1130,39 @@ def next_most_par_contribution(self,niter=3,forecast=None,parlist_dict=None): """ if forecast is None: - assert len(self.forecasts) == 1,"forecast arg list one and only one" +\ - " forecast" + assert len(self.forecasts) == 1, ( + "forecast arg list one and only one" + " forecast" + ) elif forecast not in self.prediction_arg: raise Exception("forecast {0} not found".format(forecast)) org_parcov = self.parcov.get(row_names=self.parcov.row_names) if parlist_dict is None: - parlist_dict = dict(zip(self.pst.adj_par_names,self.pst.adj_par_names)) + parlist_dict = dict(zip(self.pst.adj_par_names, self.pst.adj_par_names)) - base_prior,base_post = self.prior_forecast,self.posterior_forecast + base_prior, base_post = self.prior_forecast, self.posterior_forecast iter_results = [base_post[forecast].copy()] iter_names = ["base"] for iiter in range(niter): - iter_contrib = {forecast:[base_post[forecast]]} + iter_contrib = {forecast: [base_post[forecast]]} iter_case_names = ["base"] - self.log("next most par iteration {0}".format(iiter+1)) + self.log("next most par iteration {0}".format(iiter + 1)) - for case,parlist in parlist_dict.items(): + for case, parlist in parlist_dict.items(): iter_case_names.append(case) la_cond = self.get_conditional_instance(parlist) iter_contrib[forecast].append(la_cond.posterior_forecast[forecast]) - df = pd.DataFrame(iter_contrib,index=iter_case_names) - df.sort_values(by=forecast,inplace=True) + df = pd.DataFrame(iter_contrib, index=iter_case_names) + df.sort_values(by=forecast, inplace=True) iter_best = df.index[0] - self.logger.statement("next best iter {0}: {1}".format(iiter+1,iter_best)) - self.log("next most par iteration {0}".format(iiter+1)) + self.logger.statement( + "next best iter {0}: {1}".format(iiter + 1, iter_best) + ) + self.log("next most par iteration {0}".format(iiter + 1)) if iter_best.lower() == "base": break - iter_results.append(df.loc[iter_best,forecast]) + iter_results.append(df.loc[iter_best, forecast]) iter_names.append(iter_best) self.reset_parcov(self.parcov.condition_on(parlist_dict.pop(iter_best))) self.reset_parcov(org_parcov) - return pd.DataFrame(iter_results,index=iter_names) - - - + return pd.DataFrame(iter_results, index=iter_names) diff --git a/pyemu/utils/__init__.py b/pyemu/utils/__init__.py index 6dfb5102c..debef1b68 100644 --- a/pyemu/utils/__init__.py +++ b/pyemu/utils/__init__.py @@ -11,4 +11,3 @@ from .os_utils import * from .smp_utils import * from .pst_from import * - diff --git a/pyemu/utils/geostats.py b/pyemu/utils/geostats.py index 37e1223c9..709036d8b 100644 --- a/pyemu/utils/geostats.py +++ b/pyemu/utils/geostats.py @@ -33,9 +33,6 @@ # raise NotImplementedError() - - - class GeoStruct(object): """a geostatistical structure object that mimics the behavior of a PEST geostatistical structure. The object contains variogram instances and @@ -58,36 +55,34 @@ class GeoStruct(object): gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5) """ - def __init__(self,nugget=0.0,variograms=[],name="struct1", - transform="none"): + + def __init__(self, nugget=0.0, variograms=[], name="struct1", transform="none"): self.name = name self.nugget = float(nugget) """`float`: the nugget effect contribution""" - if not isinstance(variograms,list): + if not isinstance(variograms, list): variograms = [variograms] for vario in variograms: - assert isinstance(vario,Vario2d) + assert isinstance(vario, Vario2d) self.variograms = variograms """[`pyemu.utils.geostats.Vario2d`]: a list of variogram instances""" transform = transform.lower() - assert transform in ["none","log"] + assert transform in ["none", "log"] self.transform = transform """`str`: the transformation of the `GeoStruct`. Can be 'log' or 'none'""" - - def __lt__(self,other): + def __lt__(self, other): return self.name < other.name - - def __gt__(self,other): + def __gt__(self, other): return self.name > other.name - def same_as_other(self,other): + def same_as_other(self, other): if self.nugget != other.nugget: return False if len(self.variograms) != len(other.variograms): return False - for sv,ov in zip(self.variograms,other.variograms): + for sv, ov in zip(self.variograms, other.variograms): if not sv.same_as_other(ov): return False return True @@ -101,18 +96,18 @@ def to_struct_file(self, f): """ if isinstance(f, str): - f = open(f,'w') + f = open(f, "w") f.write("STRUCTURE {0}\n".format(self.name)) f.write(" NUGGET {0}\n".format(self.nugget)) f.write(" NUMVARIOGRAM {0}\n".format(len(self.variograms))) for v in self.variograms: - f.write(" VARIOGRAM {0} {1}\n".format(v.name,v.contribution)) + f.write(" VARIOGRAM {0} {1}\n".format(v.name, v.contribution)) f.write(" TRANSFORM {0}\n".format(self.transform)) f.write("END STRUCTURE\n\n") for v in self.variograms: v.to_struct_file(f) - def covariance_matrix(self,x,y,names=None,cov=None): + def covariance_matrix(self, x, y, names=None, cov=None): """build a `pyemu.Cov` instance from `GeoStruct` Args: @@ -143,33 +138,34 @@ def covariance_matrix(self,x,y,names=None,cov=None): """ - if not isinstance(x,np.ndarray): + if not isinstance(x, np.ndarray): x = np.array(x) - if not isinstance(y,np.ndarray): + if not isinstance(y, np.ndarray): y = np.array(y) assert x.shape[0] == y.shape[0] if names is not None: assert x.shape[0] == len(names) - c = np.zeros((len(names),len(names))) - np.fill_diagonal(c,self.nugget) - cov = Cov(x=c,names=names) + c = np.zeros((len(names), len(names))) + np.fill_diagonal(c, self.nugget) + cov = Cov(x=c, names=names) elif cov is not None: assert cov.shape[0] == x.shape[0] names = cov.row_names - c = np.zeros((len(names),1)) + c = np.zeros((len(names), 1)) c += self.nugget - cont = Cov(x=c,names=names,isdiagonal=True) + cont = Cov(x=c, names=names, isdiagonal=True) cov += cont else: - raise Exception("GeoStruct.covariance_matrix() requires either " + - "names or cov arg") + raise Exception( + "GeoStruct.covariance_matrix() requires either " + "names or cov arg" + ) for v in self.variograms: - v.covariance_matrix(x,y,cov=cov) + v.covariance_matrix(x, y, cov=cov) return cov - def covariance(self,pt0,pt1): + def covariance(self, pt0, pt1): """get the covariance between two points implied by the `GeoStruct`. This is used during the ordinary kriging process to get the RHS @@ -182,13 +178,13 @@ def covariance(self,pt0,pt1): by the GeoStruct """ - #raise Exception() + # raise Exception() cov = self.nugget for vario in self.variograms: - cov += vario.covariance(pt0,pt1) + cov += vario.covariance(pt0, pt1) return cov - def covariance_points(self,x0,y0,xother,yother): + def covariance_points(self, x0, y0, xother, yother): """ Get the covariance between point (x0,y0) and the points contained in xother, yother. @@ -207,7 +203,7 @@ def covariance_points(self,x0,y0,xother,yother): cov = np.zeros((len(xother))) + self.nugget for v in self.variograms: - cov += v.covariance_points(x0,y0,xother,yother) + cov += v.covariance_points(x0, y0, xother, yother) return cov @property @@ -224,8 +220,7 @@ def sill(self): sill += v.contribution return sill - - def plot(self,**kwargs): + def plot(self, **kwargs): """ make a cheap plot of the `GeoStruct` Args: @@ -251,18 +246,18 @@ def plot(self,**kwargs): raise Exception("error importing matplotlib: {0}".format(str(e))) ax = plt.subplot(111) - legend = kwargs.pop("legend",False) - individuals = kwargs.pop("individuals",False) - xmx = max([v.a*3.0 for v in self.variograms]) - x = np.linspace(0,xmx,100) + legend = kwargs.pop("legend", False) + individuals = kwargs.pop("individuals", False) + xmx = max([v.a * 3.0 for v in self.variograms]) + x = np.linspace(0, xmx, 100) y = np.zeros_like(x) for v in self.variograms: yv = v.inv_h(x) if individuals: - ax.plot(x,yv,label=v.name,**kwargs) + ax.plot(x, yv, label=v.name, **kwargs) y += yv y += self.nugget - ax.plot(x,y,label=self.name,**kwargs) + ax.plot(x, y, label=self.name, **kwargs) if legend: ax.legend() ax.set_xlabel("distance") @@ -275,8 +270,8 @@ def __str__(self): Returns: `str`: the string representation of the GeoStruct """ - s = '' - s += 'name:{0},nugget:{1},structures:\n'.format(self.name,self.nugget) + s = "" + s += "name:{0},nugget:{1},structures:\n".format(self.name, self.nugget) for v in self.variograms: s += str(v) return s @@ -303,7 +298,8 @@ class SpecSim2d(object): arrays = ss.draw(num_reals=100) """ - def __init__(self,delx,dely,geostruct): + + def __init__(self, delx, dely, geostruct): self.geostruct = geostruct self.delx = delx @@ -331,11 +327,11 @@ def grid_is_regular(delx, dely, tol=1.0e-4): `bool`: flag indicating if the grid defined by `delx` and `dely` is regular """ - if np.abs((delx.mean() - delx.min())/delx.mean()) > tol: + if np.abs((delx.mean() - delx.min()) / delx.mean()) > tol: return False - if (np.abs(dely.mean() - dely.min())/dely.mean()) > tol: + if (np.abs(dely.mean() - dely.min()) / dely.mean()) > tol: return False - if (np.abs(delx.mean() - dely.mean())/delx.mean()) > tol: + if (np.abs(delx.mean() - dely.mean()) / delx.mean()) > tol: return False return True @@ -363,20 +359,28 @@ def initialize(self): self.effective_variograms = [] dist = self.delx[0] for v in self.geostruct.variograms: - eff_v = type(v)(contribution=v.contribution,a=v.a/dist,bearing=v.bearing,anisotropy=v.anisotropy) + eff_v = type(v)( + contribution=v.contribution, + a=v.a / dist, + bearing=v.bearing, + anisotropy=v.anisotropy, + ) self.effective_variograms.append(eff_v) # pad the grid with 3X max range mx_a = -1.0e10 for v in self.effective_variograms: mx_a = max(mx_a, v.a) - mx_dim = max(self.delx.shape[0],self.dely.shape[0]) + mx_dim = max(self.delx.shape[0], self.dely.shape[0]) freq_pad = int(np.ceil(mx_a * 3)) - freq_pad = int(np.ceil(freq_pad / 8.) * 8.) + freq_pad = int(np.ceil(freq_pad / 8.0) * 8.0) # use the max dimension so that simulation grid is square full_delx = np.ones((mx_dim + (2 * freq_pad))) full_dely = np.ones_like(full_delx) - print("SpecSim.initialize() summary: full_delx X full_dely: {0} X {1}".\ - format(full_delx.shape[0],full_dely.shape[0])) + print( + "SpecSim.initialize() summary: full_delx X full_dely: {0} X {1}".format( + full_delx.shape[0], full_dely.shape[0] + ) + ) xdist = np.cumsum(full_delx) ydist = np.cumsum(full_dely) @@ -405,7 +409,7 @@ def initialize(self): self.num_pts = np.prod(xgrid.shape) self.sqrt_fftc = np.sqrt(fftc / self.num_pts) - def draw_arrays(self,num_reals=1,mean_value=1.0): + def draw_arrays(self, num_reals=1, mean_value=1.0): """draw realizations Args: @@ -428,18 +432,20 @@ def draw_arrays(self,num_reals=1,mean_value=1.0): epsilon = real + 1j * imag rand = epsilon * self.sqrt_fftc real = np.real(np.fft.ifftn(rand)) * self.num_pts - real = real[:self.dely.shape[0], :self.delx.shape[0]] + real = real[: self.dely.shape[0], : self.delx.shape[0]] reals.append(real) reals = np.array(reals) if self.geostruct.transform == "log": reals += np.log10(mean_value) - reals = 10**reals + reals = 10 ** reals else: reals += mean_value return reals - def grid_par_ensemble_helper(self,pst,gr_df,num_reals,sigma_range=6,logger=None): + def grid_par_ensemble_helper( + self, pst, gr_df, num_reals, sigma_range=6, logger=None + ): """wrapper around `SpecSim2d.draw()` designed to support `pyemu.PstFromFlopy` grid-based parameters @@ -465,12 +471,18 @@ def grid_par_ensemble_helper(self,pst,gr_df,num_reals,sigma_range=6,logger=None) if "i" not in gr_df.columns: print(gr_df.columns) - raise Exception("SpecSim2d.grid_par_ensmeble_helper() error: 'i' not in gr_df") + raise Exception( + "SpecSim2d.grid_par_ensmeble_helper() error: 'i' not in gr_df" + ) if "j" not in gr_df.columns: print(gr_df.columns) - raise Exception("SpecSim2d.grid_par_ensmeble_helper() error: 'j' not in gr_df") + raise Exception( + "SpecSim2d.grid_par_ensmeble_helper() error: 'j' not in gr_df" + ) if len(self.geostruct.variograms) > 1: - raise Exception("SpecSim2D grid_par_ensemble_helper() error: only a single variogram can be used...") + raise Exception( + "SpecSim2D grid_par_ensemble_helper() error: only a single variogram can be used..." + ) # scale the total contrib org_var = self.geostruct.variograms[0].contribution @@ -478,7 +490,9 @@ def grid_par_ensemble_helper(self,pst,gr_df,num_reals,sigma_range=6,logger=None) new_var = org_var new_nug = org_nug if self.geostruct.sill != 1.0: - print("SpecSim2d.grid_par_ensemble_helper() warning: scaling contribution and nugget to unity") + print( + "SpecSim2d.grid_par_ensemble_helper() warning: scaling contribution and nugget to unity" + ) tot = org_var + org_nug new_var = org_var / tot new_nug = org_nug / tot @@ -490,47 +504,52 @@ def grid_par_ensemble_helper(self,pst,gr_df,num_reals,sigma_range=6,logger=None) par = pst.parameter_data # real and name containers - real_arrs,names = [],[] + real_arrs, names = [], [] for gr_grp in gr_grps: - gp_df = gr_df.loc[gr_df.pargp==gr_grp,:] + gp_df = gr_df.loc[gr_df.pargp == gr_grp, :] - gp_par = par.loc[gp_df.parnme,:] + gp_par = par.loc[gp_df.parnme, :] # use the parval1 as the mean - mean_arr = np.zeros((self.dely.shape[0],self.delx.shape[0])) + np.NaN - mean_arr[gp_df.i,gp_df.j] = gp_par.parval1 + mean_arr = np.zeros((self.dely.shape[0], self.delx.shape[0])) + np.NaN + mean_arr[gp_df.i, gp_df.j] = gp_par.parval1 # fill missing mean values mean_arr[np.isnan(mean_arr)] = gp_par.parval1.mean() # use the max upper and min lower (transformed) bounds for the variance mx_ubnd = gp_par.parubnd_trans.max() mn_lbnd = gp_par.parlbnd_trans.min() - var = ((mx_ubnd - mn_lbnd)/sigma_range)**2 + var = ((mx_ubnd - mn_lbnd) / sigma_range) ** 2 # update the geostruct self.geostruct.variograms[0].contribution = var * new_var self.geostruct.nugget = var * new_nug - #print(gr_grp, var,new_var,mx_ubnd,mn_lbnd) + # print(gr_grp, var,new_var,mx_ubnd,mn_lbnd) # reinitialize and draw if logger is not None: - logger.log("SpecSim: drawing {0} realization for group {1} with {4} pars, (log) variance {2} (sill {3})".\ - format(num_reals, gr_grp, var,self.geostruct.sill,gp_df.shape[0])) + logger.log( + "SpecSim: drawing {0} realization for group {1} with {4} pars, (log) variance {2} (sill {3})".format( + num_reals, gr_grp, var, self.geostruct.sill, gp_df.shape[0] + ) + ) self.initialize() - reals = self.draw_arrays(num_reals=num_reals,mean_value=mean_arr) + reals = self.draw_arrays(num_reals=num_reals, mean_value=mean_arr) # put the pieces into the par en - reals = reals[:,gp_df.i,gp_df.j].reshape(num_reals,gp_df.shape[0]) + reals = reals[:, gp_df.i, gp_df.j].reshape(num_reals, gp_df.shape[0]) real_arrs.append(reals) names.extend(list(gp_df.parnme.values)) if logger is not None: logger.log( - "SpecSim: drawing {0} realization for group {1} with {4} pars, (log) variance {2} (sill {3})". \ - format(num_reals, gr_grp, var, self.geostruct.sill, gp_df.shape[0])) + "SpecSim: drawing {0} realization for group {1} with {4} pars, (log) variance {2} (sill {3})".format( + num_reals, gr_grp, var, self.geostruct.sill, gp_df.shape[0] + ) + ) # get into a dataframe reals = real_arrs[0] for r in real_arrs[1:]: - reals = np.append(reals,r,axis=1) - pe = pd.DataFrame(data=reals,columns=names) + reals = np.append(reals, r, axis=1) + pe = pd.DataFrame(data=reals, columns=names) # reset to org conditions self.geostruct.nugget = org_nug self.geostruct.variograms[0].contribution = org_var @@ -538,8 +557,18 @@ def grid_par_ensemble_helper(self,pst,gr_df,num_reals,sigma_range=6,logger=None) return pe - def draw_conditional(self, seed, obs_points, sg, base_values_file, local=True, factors_file=None, - num_reals=1, mean_value=1.0, R_factor=1.0): + def draw_conditional( + self, + seed, + obs_points, + sg, + base_values_file, + local=True, + factors_file=None, + num_reals=1, + mean_value=1.0, + R_factor=1.0, + ): """ Generate a conditional, correlated random field using the Spec2dSim object, a set of observation points, and a factors file. @@ -596,23 +625,23 @@ def draw_conditional(self, seed, obs_points, sg, base_values_file, local=True, f in arithmetic space """ - # get a dataframe for the observation points, from file unless passed if isinstance(obs_points, str): obs_points = pp_file_to_dataframe(obs_points) - assert isinstance(obs_points, pd.DataFrame),"need a DataFrame, not {0}".\ - format(type(obs_points)) + assert isinstance(obs_points, pd.DataFrame), "need a DataFrame, not {0}".format( + type(obs_points) + ) # if factors_file is not passed, generate one from the geostruct associated # with the calling object and the observation points dataframe if factors_file is None: ok = OrdinaryKrige(self.geostruct, obs_points) ok.calc_factors_grid(sg, zone_array=None, var_filename=None) - ok.to_grid_factors_file('conditional_factors.dat') - factors_file = 'conditional_factors.dat' + ok.to_grid_factors_file("conditional_factors.dat") + factors_file = "conditional_factors.dat" # read in the base values, Z(x), assume these are not log-transformed - values_krige = np.loadtxt(base_values_file) + values_krige = np.loadtxt(base_values_file) np.random.seed(int(seed)) @@ -620,7 +649,7 @@ def draw_conditional(self, seed, obs_points, sg, base_values_file, local=True, f unconditioned = self.draw_arrays(num_reals=num_reals, mean_value=mean_value) # If geostruct is log transformed, then work with log10 of field - if self.geostruct.transform == 'log': + if self.geostruct.transform == "log": unconditioned = np.log10(unconditioned) # now do the conditioning by making another kriged surface with the @@ -629,30 +658,39 @@ def draw_conditional(self, seed, obs_points, sg, base_values_file, local=True, f # need to row and column for the x and y values in the observation # dataframe, regular grid is tested when object is instantiated # so grid spacing can be used. - conditioning_df['row'] = conditioning_df.apply(lambda row: sg.intersect(row['x'], - row['y'], - local=local)[0], axis=1) - conditioning_df['col'] = conditioning_df.apply(lambda row: sg.intersect(row['x'], - row['y'], - local=local)[1], axis=1) + conditioning_df["row"] = conditioning_df.apply( + lambda row: sg.intersect(row["x"], row["y"], local=local)[0], axis=1 + ) + conditioning_df["col"] = conditioning_df.apply( + lambda row: sg.intersect(row["x"], row["y"], local=local)[1], axis=1 + ) reals = [] for layer in range(0, num_reals): - unconditioned[layer] = unconditioned[layer] * R_factor # scale the unconditioned values - conditioning_df['unconditioned'] = conditioning_df.apply( - lambda row: unconditioned[layer][row['row'], row['col']], axis=1) - conditioning_df.to_csv('unconditioned.dat', - columns=['name', 'x', 'y', 'zone', 'unconditioned'], - sep=' ', - header=False, - index=False) + unconditioned[layer] = ( + unconditioned[layer] * R_factor + ) # scale the unconditioned values + conditioning_df["unconditioned"] = conditioning_df.apply( + lambda row: unconditioned[layer][row["row"], row["col"]], axis=1 + ) + conditioning_df.to_csv( + "unconditioned.dat", + columns=["name", "x", "y", "zone", "unconditioned"], + sep=" ", + header=False, + index=False, + ) # krige a surface using unconditioned observations to make the conditioning surface - fac2real(pp_file='unconditioned.dat', - factors_file=factors_file, - out_file='conditioning.dat') - conditioning = np.loadtxt('conditioning.dat') + fac2real( + pp_file="unconditioned.dat", + factors_file=factors_file, + out_file="conditioning.dat", + ) + conditioning = np.loadtxt("conditioning.dat") if self.geostruct.transform == "log": - conditioned = np.log10(values_krige) + (unconditioned[layer] - conditioning) + conditioned = np.log10(values_krige) + ( + unconditioned[layer] - conditioning + ) conditioned = np.power(10, conditioned) else: conditioned = values_krige + (unconditioned[layer] - conditioning) @@ -663,7 +701,6 @@ def draw_conditional(self, seed, obs_points, sg, base_values_file, local=True, f return reals - class OrdinaryKrige(object): """ Ordinary Kriging using Pandas and Numpy. @@ -690,30 +727,39 @@ class OrdinaryKrige(object): """ - def __init__(self,geostruct,point_data): - if isinstance(geostruct,str): + def __init__(self, geostruct, point_data): + if isinstance(geostruct, str): geostruct = read_struct_file(geostruct) - assert isinstance(geostruct,GeoStruct),"need a GeoStruct, not {0}".\ - format(type(geostruct)) + assert isinstance(geostruct, GeoStruct), "need a GeoStruct, not {0}".format( + type(geostruct) + ) self.geostruct = geostruct - if isinstance(point_data,str): + if isinstance(point_data, str): point_data = pp_file_to_dataframe(point_data) - assert isinstance(point_data,pd.DataFrame) - assert 'name' in point_data.columns,"point_data missing 'name'" - assert 'x' in point_data.columns, "point_data missing 'x'" - assert 'y' in point_data.columns, "point_data missing 'y'" - #check for duplicates in point data + assert isinstance(point_data, pd.DataFrame) + assert "name" in point_data.columns, "point_data missing 'name'" + assert "x" in point_data.columns, "point_data missing 'x'" + assert "y" in point_data.columns, "point_data missing 'y'" + # check for duplicates in point data unique_name = point_data.name.unique() if len(unique_name) != point_data.shape[0]: - warnings.warn("duplicates detected in point_data..attempting to rectify",PyemuWarning) - ux_std = point_data.groupby(point_data.name).std()['x'] + warnings.warn( + "duplicates detected in point_data..attempting to rectify", PyemuWarning + ) + ux_std = point_data.groupby(point_data.name).std()["x"] if ux_std.max() > 0.0: - raise Exception("duplicate point_info entries with name {0} have different x values" - .format(uname)) - uy_std = point_data.groupby(point_data.name).std()['y'] + raise Exception( + "duplicate point_info entries with name {0} have different x values".format( + uname + ) + ) + uy_std = point_data.groupby(point_data.name).std()["y"] if uy_std.max() > 0.0: - raise Exception("duplicate point_info entries with name {0} have different y values" - .format(uname)) + raise Exception( + "duplicate point_info entries with name {0} have different y values".format( + uname + ) + ) self.point_data = point_data.drop_duplicates(subset=["name"]) else: @@ -722,13 +768,13 @@ def __init__(self,geostruct,point_data): self.check_point_data_dist() self.interp_data = None self.spatial_reference = None - #X, Y = np.meshgrid(point_data.x,point_data.y) - #self.point_data_dist = pd.DataFrame(data=np.sqrt((X - X.T) ** 2 + (Y - Y.T) ** 2), + # X, Y = np.meshgrid(point_data.x,point_data.y) + # self.point_data_dist = pd.DataFrame(data=np.sqrt((X - X.T) ** 2 + (Y - Y.T) ** 2), # index=point_data.name,columns=point_data.name) - self.point_cov_df = self.geostruct.covariance_matrix(self.point_data.x, - self.point_data.y, - self.point_data.name).to_dataframe() - #for name in self.point_cov_df.index: + self.point_cov_df = self.geostruct.covariance_matrix( + self.point_data.x, self.point_data.y, self.point_data.name + ).to_dataframe() + # for name in self.point_cov_df.index: # self.point_cov_df.loc[name,name] -= self.geostruct.nugget def check_point_data_dist(self, rectify=False): @@ -751,28 +797,48 @@ def check_point_data_dist(self, rectify=False): ptnames = self.point_data.name.values drop = [] for i in range(self.point_data.shape[0]): - ix,iy,iname = ptx_array[i],pty_array[i],ptnames[i] - dist = pd.Series((ptx_array[i+1:] - ix) ** 2 + (pty_array[i+1:] - iy) ** 2, ptnames[i+1:]) - if dist.min() < EPSILON**2: - print(iname,ix,iy) - warnings.warn("points {0} and {1} are too close. This will cause a singular kriging matrix ".\ - format(iname,dist.idxmin()),PyemuWarning) - drop_idxs = dist.loc[dist<=EPSILON**2] + ix, iy, iname = ptx_array[i], pty_array[i], ptnames[i] + dist = pd.Series( + (ptx_array[i + 1 :] - ix) ** 2 + (pty_array[i + 1 :] - iy) ** 2, + ptnames[i + 1 :], + ) + if dist.min() < EPSILON ** 2: + print(iname, ix, iy) + warnings.warn( + "points {0} and {1} are too close. This will cause a singular kriging matrix ".format( + iname, dist.idxmin() + ), + PyemuWarning, + ) + drop_idxs = dist.loc[dist <= EPSILON ** 2] drop.extend([pt for pt in list(drop_idxs.index) if pt not in drop]) if rectify and len(drop) > 0: - print("rectifying point data by removing the following points: {0}".format(','.join(drop))) + print( + "rectifying point data by removing the following points: {0}".format( + ",".join(drop) + ) + ) print(self.point_data.shape) - self.point_data = self.point_data.loc[self.point_data.index.map(lambda x: x not in drop),:] + self.point_data = self.point_data.loc[ + self.point_data.index.map(lambda x: x not in drop), : + ] print(self.point_data.shape) - #def prep_for_ppk2fac(self,struct_file="structure.dat",pp_file="points.dat",): + # def prep_for_ppk2fac(self,struct_file="structure.dat",pp_file="points.dat",): # pass - - - def calc_factors_grid(self,spatial_reference,zone_array=None,minpts_interp=1, - maxpts_interp=20,search_radius=1.0e+10,verbose=False, - var_filename=None, forgive=False,num_threads=1): + def calc_factors_grid( + self, + spatial_reference, + zone_array=None, + minpts_interp=1, + maxpts_interp=20, + search_radius=1.0e10, + verbose=False, + var_filename=None, + forgive=False, + num_threads=1, + ): """ calculate kriging factors (weights) for a structured grid. Args: @@ -834,54 +900,73 @@ def calc_factors_grid(self,spatial_reference,zone_array=None,minpts_interp=1, self.spatial_reference = spatial_reference self.interp_data = None - #assert isinstance(spatial_reference,SpatialReference) + # assert isinstance(spatial_reference,SpatialReference) try: x = self.spatial_reference.xcentergrid.copy() y = self.spatial_reference.ycentergrid.copy() except Exception as e: - raise Exception("spatial_reference does not have proper attributes:{0}"\ - .format(str(e))) + raise Exception( + "spatial_reference does not have proper attributes:{0}".format(str(e)) + ) if var_filename is not None: - arr = np.zeros((self.spatial_reference.nrow, - self.spatial_reference.ncol)) - 1.0e+30 + arr = ( + np.zeros((self.spatial_reference.nrow, self.spatial_reference.ncol)) + - 1.0e30 + ) # the simple case of no zone array: ignore point_data zones if zone_array is None: - df = self.calc_factors(x.ravel(),y.ravel(), - minpts_interp=minpts_interp, - maxpts_interp=maxpts_interp, - search_radius=search_radius, - verbose=verbose, forgive=forgive, - num_threads=num_threads) + df = self.calc_factors( + x.ravel(), + y.ravel(), + minpts_interp=minpts_interp, + maxpts_interp=maxpts_interp, + search_radius=search_radius, + verbose=verbose, + forgive=forgive, + num_threads=num_threads, + ) if var_filename is not None: arr = df.err_var.values.reshape(x.shape) - np.savetxt(var_filename,arr,fmt="%15.6E") + np.savetxt(var_filename, arr, fmt="%15.6E") if zone_array is not None: assert zone_array.shape == x.shape if "zone" not in self.point_data.columns: - warnings.warn("'zone' columns not in point_data, assigning generic zone",PyemuWarning) - self.point_data.loc[:,"zone"] = 1 + warnings.warn( + "'zone' columns not in point_data, assigning generic zone", + PyemuWarning, + ) + self.point_data.loc[:, "zone"] = 1 pt_data_zones = self.point_data.zone.unique() dfs = [] for pt_data_zone in pt_data_zones: if pt_data_zone not in zone_array: - warnings.warn("pt zone {0} not in zone array {1}, skipping".\ - format(pt_data_zone,np.unique(zone_array)),PyemuWarning) + warnings.warn( + "pt zone {0} not in zone array {1}, skipping".format( + pt_data_zone, np.unique(zone_array) + ), + PyemuWarning, + ) continue - xzone,yzone = x.copy(),y.copy() - xzone[zone_array!=pt_data_zone] = np.NaN - yzone[zone_array!=pt_data_zone] = np.NaN - - df = self.calc_factors(xzone.ravel(),yzone.ravel(), - minpts_interp=minpts_interp, - maxpts_interp=maxpts_interp, - search_radius=search_radius, - verbose=verbose,pt_zone=pt_data_zone, - forgive=forgive,num_threads=num_threads) + xzone, yzone = x.copy(), y.copy() + xzone[zone_array != pt_data_zone] = np.NaN + yzone[zone_array != pt_data_zone] = np.NaN + + df = self.calc_factors( + xzone.ravel(), + yzone.ravel(), + minpts_interp=minpts_interp, + maxpts_interp=maxpts_interp, + search_radius=search_radius, + verbose=verbose, + pt_zone=pt_data_zone, + forgive=forgive, + num_threads=num_threads, + ) dfs.append(df) if var_filename is not None: @@ -892,10 +977,10 @@ def calc_factors_grid(self,spatial_reference,zone_array=None,minpts_interp=1, raise Exception("no interpolation took place...something is wrong") df = pd.concat(dfs) if var_filename is not None: - np.savetxt(var_filename,arr,fmt="%15.6E") + np.savetxt(var_filename, arr, fmt="%15.6E") return df - def _dist_calcs(self,ix, iy, ptx_array, pty_array, ptnames, sqradius): + def _dist_calcs(self, ix, iy, ptx_array, pty_array, ptnames, sqradius): """private: find nearby points""" # calc dist from this interp point to all point data...slow dist = pd.Series((ptx_array - ix) ** 2 + (pty_array - iy) ** 2, ptnames) @@ -903,13 +988,17 @@ def _dist_calcs(self,ix, iy, ptx_array, pty_array, ptnames, sqradius): dist = dist.loc[dist <= sqradius] return dist - def _cov_points(self,ix, iy, pt_names): + def _cov_points(self, ix, iy, pt_names): """private: get covariance between points""" - interp_cov = self.geostruct.covariance_points(ix, iy, self.point_data.loc[pt_names, "x"], - self.point_data.loc[pt_names, "y"]) + interp_cov = self.geostruct.covariance_points( + ix, + iy, + self.point_data.loc[pt_names, "x"], + self.point_data.loc[pt_names, "y"], + ) return interp_cov - def _form(self,pt_names, point_cov, interp_cov): + def _form(self, pt_names, point_cov, interp_cov): """private: form the kriging equations""" d = len(pt_names) + 1 # +1 for lagrange mult @@ -920,14 +1009,21 @@ def _form(self,pt_names, point_cov, interp_cov): rhs[:-1, 0] = interp_cov return A, rhs - def _solve(self,A, rhs): + def _solve(self, A, rhs): return np.linalg.solve(A, rhs) - - - def calc_factors(self,x,y,minpts_interp=1,maxpts_interp=20, - search_radius=1.0e+10,verbose=False, - pt_zone=None,forgive=False,num_threads=1): + def calc_factors( + self, + x, + y, + minpts_interp=1, + maxpts_interp=20, + search_radius=1.0e10, + verbose=False, + pt_zone=None, + forgive=False, + num_threads=1, + ): """ calculate ordinary kriging factors (weights) for the points represented by arguments x and y @@ -964,23 +1060,46 @@ def calc_factors(self,x,y,minpts_interp=1,maxpts_interp=20, `OrdinaryKrige.calc_factors_mp()` depending on the value of `num_threads` """ if num_threads == 1: - return self._calc_factors_org(x,y,minpts_interp,maxpts_interp, - search_radius,verbose,pt_zone, - forgive) + return self._calc_factors_org( + x, + y, + minpts_interp, + maxpts_interp, + search_radius, + verbose, + pt_zone, + forgive, + ) else: - return self._calc_factors_mp(x,y,minpts_interp,maxpts_interp, - search_radius,verbose,pt_zone, - forgive, num_threads) - - def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, - search_radius=1.0e+10,verbose=False, - pt_zone=None,forgive=False): + return self._calc_factors_mp( + x, + y, + minpts_interp, + maxpts_interp, + search_radius, + verbose, + pt_zone, + forgive, + num_threads, + ) + + def _calc_factors_org( + self, + x, + y, + minpts_interp=1, + maxpts_interp=20, + search_radius=1.0e10, + verbose=False, + pt_zone=None, + forgive=False, + ): assert len(x) == len(y) # find the point data to use for each interp point - sqradius = search_radius**2 - df = pd.DataFrame(data={'x':x,'y':y}) - inames,idist,ifacts,err_var = [],[],[],[] + sqradius = search_radius ** 2 + df = pd.DataFrame(data={"x": x, "y": y}) + inames, idist, ifacts, err_var = [], [], [], [] sill = self.geostruct.sill if pt_zone is None: ptx_array = self.point_data.x.values @@ -988,17 +1107,15 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, ptnames = self.point_data.name.values else: pt_data = self.point_data - ptx_array = pt_data.loc[pt_data.zone==pt_zone,"x"].values - pty_array = pt_data.loc[pt_data.zone==pt_zone,"y"].values - ptnames = pt_data.loc[pt_data.zone==pt_zone,"name"].values - #if verbose: - - + ptx_array = pt_data.loc[pt_data.zone == pt_zone, "x"].values + pty_array = pt_data.loc[pt_data.zone == pt_zone, "y"].values + ptnames = pt_data.loc[pt_data.zone == pt_zone, "name"].values + # if verbose: print("starting interp point loop for {0} points".format(df.shape[0])) start_loop = datetime.now() - for idx,(ix,iy) in enumerate(zip(df.x,df.y)): - if np.isnan(ix) or np.isnan(iy): #if nans, skip + for idx, (ix, iy) in enumerate(zip(df.x, df.y)): + if np.isnan(ix) or np.isnan(iy): # if nans, skip inames.append([]) idist.append([]) ifacts.append([]) @@ -1006,17 +1123,17 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, continue if verbose: istart = datetime.now() - print("processing interp point:{0} of {1}".format(idx,df.shape[0])) + print("processing interp point:{0} of {1}".format(idx, df.shape[0])) # if verbose == 2: # start = datetime.now() # print("calc ipoint dist...",end='') # calc dist from this interp point to all point data...slow - #dist = pd.Series((ptx_array-ix)**2 + (pty_array-iy)**2,ptnames) - #dist.sort_values(inplace=True) - #dist = dist.loc[dist <= sqradius] - #def _dist_calcs(self, ix, iy, ptx_array, pty_array, ptnames, sqradius): - dist = self._dist_calcs(ix,iy, ptx_array, pty_array, ptnames, sqradius) + # dist = pd.Series((ptx_array-ix)**2 + (pty_array-iy)**2,ptnames) + # dist.sort_values(inplace=True) + # dist = dist.loc[dist <= sqradius] + # def _dist_calcs(self, ix, iy, ptx_array, pty_array, ptnames, sqradius): + dist = self._dist_calcs(ix, iy, ptx_array, pty_array, ptnames, sqradius) # if too few points were found, skip if len(dist) < minpts_interp: @@ -1042,8 +1159,8 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, # start = datetime.now() # print("extracting pt cov...",end='') - #vextract the point-to-point covariance matrix - point_cov = self.point_cov_df.loc[pt_names,pt_names] + # vextract the point-to-point covariance matrix + point_cov = self.point_cov_df.loc[pt_names, pt_names] # if verbose == 2: # td = (datetime.now()-start).total_seconds() # print("...took {0}".format(td)) @@ -1052,11 +1169,11 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, # calc the interp point to points covariance # interp_cov = self.geostruct.covariance_points(ix,iy,self.point_data.loc[pt_names,"x"], # self.point_data.loc[pt_names,"y"]) - interp_cov = self._cov_points(ix,iy,pt_names) + interp_cov = self._cov_points(ix, iy, pt_names) if verbose == 2: - td = (datetime.now()-start).total_seconds() + td = (datetime.now() - start).total_seconds() print("...took {0} seconds".format(td)) - print("forming lin alg components...",end='') + print("forming lin alg components...", end="") # form the linear algebra parts and solve # d = len(pt_names) + 1 # +1 for lagrange mult @@ -1065,7 +1182,7 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, # A[-1,-1] = 0.0 #unbiaised constraint # rhs = np.ones((d,1)) # rhs[:-1,0] = interp_cov - A, rhs = self._form(pt_names,point_cov,interp_cov) + A, rhs = self._form(pt_names, point_cov, interp_cov) # if verbose == 2: # td = (datetime.now()-start).total_seconds() @@ -1076,8 +1193,8 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, facs = self._solve(A, rhs) except Exception as e: print("error solving for factors: {0}".format(str(e))) - print("point:",ix,iy) - print("dist:",dist) + print("point:", ix, iy) + print("dist:", dist) print("A:", A) print("rhs:", rhs) if forgive: @@ -1090,16 +1207,22 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, raise Exception("error solving for factors:{0}".format(str(e))) assert len(facs) - 1 == len(dist) - err_var.append(float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)]))) + err_var.append( + float( + sill + + facs[-1] + - sum([f * c for f, c in zip(facs[:-1], interp_cov)]) + ) + ) inames.append(pt_names) idist.append(dist.values) - ifacts.append(facs[:-1,0]) + ifacts.append(facs[:-1, 0]) # if verbose == 2: # td = (datetime.now()-start).total_seconds() # print("...took {0}".format(td)) if verbose: - td = (datetime.now()-istart).total_seconds() + td = (datetime.now() - istart).total_seconds() print("point took {0}".format(td)) df["idist"] = idist df["inames"] = inames @@ -1116,14 +1239,22 @@ def _calc_factors_org(self,x,y,minpts_interp=1,maxpts_interp=20, print("took {0} seconds".format(td)) return df - - def _calc_factors_mp(self,x,y,minpts_interp=1,maxpts_interp=20, - search_radius=1.0e+10,verbose=False, - pt_zone=None,forgive=False,num_threads=1): + def _calc_factors_mp( + self, + x, + y, + minpts_interp=1, + maxpts_interp=20, + search_radius=1.0e10, + verbose=False, + pt_zone=None, + forgive=False, + num_threads=1, + ): assert len(x) == len(y) start_loop = datetime.now() - df = pd.DataFrame(data={'x': x, 'y': y}) + df = pd.DataFrame(data={"x": x, "y": y}) print("starting interp point loop for {0} points".format(df.shape[0])) with mp.Manager() as manager: @@ -1132,8 +1263,8 @@ def _calc_factors_mp(self,x,y,minpts_interp=1,maxpts_interp=20, inames = manager.list() ifacts = manager.list() err_var = manager.list() - #start = mp.Value('d',0) - for i,(xx, yy) in enumerate(zip(x, y)): + # start = mp.Value('d',0) + for i, (xx, yy) in enumerate(zip(x, y)): point_pairs.append((i, xx, yy)) idist.append([]) inames.append([]) @@ -1142,10 +1273,27 @@ def _calc_factors_mp(self,x,y,minpts_interp=1,maxpts_interp=20, lock = mp.Lock() procs = [] for i in range(num_threads): - print("starting",i) - p = mp.Process(target=OrdinaryKrige._worker,args=(i,self.point_data,point_pairs,inames,idist,ifacts,err_var, - self.point_cov_df,self.geostruct,EPSILON,search_radius, - pt_zone,minpts_interp,maxpts_interp,lock)) + print("starting", i) + p = mp.Process( + target=OrdinaryKrige._worker, + args=( + i, + self.point_data, + point_pairs, + inames, + idist, + ifacts, + err_var, + self.point_cov_df, + self.geostruct, + EPSILON, + search_radius, + pt_zone, + minpts_interp, + maxpts_interp, + lock, + ), + ) p.start() procs.append(p) for p in procs: @@ -1166,10 +1314,24 @@ def _calc_factors_mp(self,x,y,minpts_interp=1,maxpts_interp=20, print("took {0} seconds".format(td)) return df - @staticmethod - def _worker(ithread,point_data,point_pairs,inames,idist,ifacts,err_var,point_cov_df, - geostruct,epsilon,search_radius,pt_zone,minpts_interp,maxpts_interp,lock): + def _worker( + ithread, + point_data, + point_pairs, + inames, + idist, + ifacts, + err_var, + point_cov_df, + geostruct, + epsilon, + search_radius, + pt_zone, + minpts_interp, + maxpts_interp, + lock, + ): # find the point data to use for each interp point sqradius = search_radius ** 2 @@ -1188,31 +1350,31 @@ def _worker(ithread,point_data,point_pairs,inames,idist,ifacts,err_var,point_cov return else: try: - idx, ix,iy = point_pairs.pop(0) + idx, ix, iy = point_pairs.pop(0) except IndexError: return - #if idx % 1000 == 0 and idx != 0: + # if idx % 1000 == 0 and idx != 0: # print (ithread, idx,"done",datetime.now()) - if np.isnan(ix) or np.isnan(iy): #if nans, skip - #inames.append([]) - #idist.append([]) - #ifacts.append([]) - #err_var.append(np.NaN) - #err_var.insert(idx,np.NaN) + if np.isnan(ix) or np.isnan(iy): # if nans, skip + # inames.append([]) + # idist.append([]) + # ifacts.append([]) + # err_var.append(np.NaN) + # err_var.insert(idx,np.NaN) continue # calc dist from this interp point to all point data...slow - dist = pd.Series((ptx_array-ix)**2 + (pty_array-iy)**2,ptnames) + dist = pd.Series((ptx_array - ix) ** 2 + (pty_array - iy) ** 2, ptnames) dist.sort_values(inplace=True) dist = dist.loc[dist <= sqradius] # if too few points were found, skip if len(dist) < minpts_interp: - #inames.append([]) - #idist.append([]) - #ifacts.append([]) - #err_var.append(sill) + # inames.append([]) + # idist.append([]) + # ifacts.append([]) + # err_var.append(sill) err_var[idx] = sill continue @@ -1221,68 +1383,69 @@ def _worker(ithread,point_data,point_pairs,inames,idist,ifacts,err_var,point_cov pt_names = dist.index.values # if one of the points is super close, just use it and skip if dist.min() <= epsilon: - #ifacts.append([1.0]) + # ifacts.append([1.0]) ifacts[idx] = [1.0] - #idist.append([epsilon]) + # idist.append([epsilon]) idist[idx] = [epsilon] - #inames.append([dist.idxmin()]) + # inames.append([dist.idxmin()]) inames[idx] = [dist.idxmin()] - #err_var.append(geostruct.nugget) + # err_var.append(geostruct.nugget) err_var[idx] = geostruct.nugget continue - #vextract the point-to-point covariance matrix - point_cov = point_cov_df.loc[pt_names,pt_names] + # vextract the point-to-point covariance matrix + point_cov = point_cov_df.loc[pt_names, pt_names] # calc the interp point to points covariance - interp_cov = geostruct.covariance_points(ix,iy,point_data.loc[pt_names,"x"], - point_data.loc[pt_names,"y"]) + interp_cov = geostruct.covariance_points( + ix, iy, point_data.loc[pt_names, "x"], point_data.loc[pt_names, "y"] + ) # form the linear algebra parts and solve - d = len(pt_names) + 1 # +1 for lagrange mult - A = np.ones((d,d)) - A[:-1,:-1] = point_cov.values - A[-1,-1] = 0.0 #unbiaised constraint - rhs = np.ones((d,1)) - rhs[:-1,0] = interp_cov + d = len(pt_names) + 1 # +1 for lagrange mult + A = np.ones((d, d)) + A[:-1, :-1] = point_cov.values + A[-1, -1] = 0.0 # unbiaised constraint + rhs = np.ones((d, 1)) + rhs[:-1, 0] = interp_cov try: facs = np.linalg.solve(A, rhs) except Exception as e: print("error solving for factors: {0}".format(str(e))) - print("point:",ix,iy) - print("dist:",dist) + print("point:", ix, iy) + print("dist:", dist) print("A:", A) print("rhs:", rhs) - #inames.append([]) - #idist.append([]) - #ifacts.append([]) - #err_var.append(np.NaN) - #err_var.insert(np.NaN) + # inames.append([]) + # idist.append([]) + # ifacts.append([]) + # err_var.append(np.NaN) + # err_var.insert(np.NaN) continue assert len(facs) - 1 == len(dist) - #err_var.append(float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)]))) - err_var[idx] = float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)])) - #inames.append(pt_names) + # err_var.append(float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)]))) + err_var[idx] = float( + sill + facs[-1] - sum([f * c for f, c in zip(facs[:-1], interp_cov)]) + ) + # inames.append(pt_names) inames[idx] = pt_names - #idist.append(dist.values) + # idist.append(dist.values) idist[idx] = dist.values - #ifacts.append(facs[:-1,0]) - ifacts[idx] = facs[:-1,0] + # ifacts.append(facs[:-1,0]) + ifacts[idx] = facs[:-1, 0] # if verbose == 2: # td = (datetime.now()-start).total_seconds() # print("...took {0}".format(td)) - - - - def to_grid_factors_file(self, filename,points_file="points.junk", - zone_file="zone.junk"): + def to_grid_factors_file( + self, filename, points_file="points.junk", zone_file="zone.junk" + ): """ write a grid-based PEST-style factors file. This file can be used with the fac2real() method to write an interpolated structured array @@ -1298,25 +1461,38 @@ def to_grid_factors_file(self, filename,points_file="points.junk", """ if self.interp_data is None: - raise Exception("ok.interp_data is None, must call calc_factors_grid() first") + raise Exception( + "ok.interp_data is None, must call calc_factors_grid() first" + ) if self.spatial_reference is None: - raise Exception("ok.spatial_reference is None, must call calc_factors_grid() first") - with open(filename, 'w') as f: - f.write(points_file + '\n') - f.write(zone_file + '\n') - f.write("{0} {1}\n".format(self.spatial_reference.ncol, self.spatial_reference.nrow)) + raise Exception( + "ok.spatial_reference is None, must call calc_factors_grid() first" + ) + with open(filename, "w") as f: + f.write(points_file + "\n") + f.write(zone_file + "\n") + f.write( + "{0} {1}\n".format( + self.spatial_reference.ncol, self.spatial_reference.nrow + ) + ) f.write("{0}\n".format(self.point_data.shape[0])) [f.write("{0}\n".format(name)) for name in self.point_data.name] t = 0 if self.geostruct.transform == "log": t = 1 pt_names = list(self.point_data.name) - for idx,names,facts in zip(self.interp_data.index,self.interp_data.inames,self.interp_data.ifacts): + for idx, names, facts in zip( + self.interp_data.index, self.interp_data.inames, self.interp_data.ifacts + ): if len(facts) == 0: continue n_idxs = [pt_names.index(name) for name in names] - f.write("{0} {1} {2} {3:8.5e} ".format(idx+1, t, len(names), 0.0)) - [f.write("{0} {1:12.8g} ".format(i+1, w)) for i, w in zip(n_idxs, facts)] + f.write("{0} {1} {2} {3:8.5e} ".format(idx + 1, t, len(names), 0.0)) + [ + f.write("{0} {1:12.8g} ".format(i + 1, w)) + for i, w in zip(n_idxs, facts) + ] f.write("\n") @@ -1337,7 +1513,7 @@ class Vario2d(object): """ - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): + def __init__(self, contribution, a, anisotropy=1.0, bearing=0.0, name="var1"): self.name = name self.epsilon = EPSILON self.contribution = float(contribution) @@ -1348,8 +1524,7 @@ def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): assert self.anisotropy > 0.0 self.bearing = float(bearing) - - def same_as_other(self,other): + def same_as_other(self, other): if type(self) != type(other): return False if self.contribution != other.contribution: @@ -1371,7 +1546,7 @@ def to_struct_file(self, f): """ if isinstance(f, str): - f = open(f,'w') + f = open(f, "w") f.write("VARIOGRAM {0}\n".format(self.name)) f.write(" VARTYPE {0}\n".format(self.vartype)) f.write(" A {0}\n".format(self.a)) @@ -1386,7 +1561,7 @@ def bearing_rads(self): Returns: `float`: the Vario2d bearing in radians """ - return (np.pi / 180.0 ) * (90.0 - self.bearing) + return (np.pi / 180.0) * (90.0 - self.bearing) @property def rotation_coefs(self): @@ -1397,12 +1572,14 @@ def rotation_coefs(self): """ - return [np.cos(self.bearing_rads), - np.sin(self.bearing_rads), - -1.0*np.sin(self.bearing_rads), - np.cos(self.bearing_rads)] - - def inv_h(self,h): + return [ + np.cos(self.bearing_rads), + np.sin(self.bearing_rads), + -1.0 * np.sin(self.bearing_rads), + np.cos(self.bearing_rads), + ] + + def inv_h(self, h): """ the inverse of the h_function. Used for plotting Args: @@ -1414,7 +1591,7 @@ def inv_h(self,h): """ return self.contribution - self._h_function(h) - def plot(self,**kwargs): + def plot(self, **kwargs): """ get a cheap plot of the Vario2d Args: @@ -1434,15 +1611,15 @@ def plot(self,**kwargs): except Exception as e: raise Exception("error importing matplotlib: {0}".format(str(e))) - ax = kwargs.pop("ax",plt.subplot(111)) - x = np.linspace(0,self.a*3,100) + ax = kwargs.pop("ax", plt.subplot(111)) + x = np.linspace(0, self.a * 3, 100) y = self.inv_h(x) ax.set_xlabel("distance") ax.set_ylabel("$\gamma$") - ax.plot(x,y,**kwargs) + ax.plot(x, y, **kwargs) return ax - def covariance_matrix(self,x,y,names=None,cov=None): + def covariance_matrix(self, x, y, names=None, cov=None): """build a pyemu.Cov instance implied by Vario2d Args: @@ -1459,69 +1636,67 @@ def covariance_matrix(self,x,y,names=None,cov=None): either `names` or `cov` must not be None. """ - if not isinstance(x,np.ndarray): + if not isinstance(x, np.ndarray): x = np.array(x) - if not isinstance(y,np.ndarray): + if not isinstance(y, np.ndarray): y = np.array(y) assert x.shape[0] == y.shape[0] if names is not None: assert x.shape[0] == len(names) - c = np.zeros((len(names),len(names))) - np.fill_diagonal(c,self.contribution) - cov = Cov(x=c,names=names) + c = np.zeros((len(names), len(names))) + np.fill_diagonal(c, self.contribution) + cov = Cov(x=c, names=names) elif cov is not None: assert cov.shape[0] == x.shape[0] names = cov.row_names - c = np.zeros((len(names),1)) + self.contribution - cont = Cov(x=c,names=names,isdiagonal=True) + c = np.zeros((len(names), 1)) + self.contribution + cont = Cov(x=c, names=names, isdiagonal=True) cov += cont else: - raise Exception("Vario2d.covariance_matrix() requires either" + - "names or cov arg") + raise Exception( + "Vario2d.covariance_matrix() requires either" + "names or cov arg" + ) rc = self.rotation_coefs - for i1,(n1,x1,y1) in enumerate(zip(names,x,y)): - dx = x1 - x[i1+1:] - dy = y1 - y[i1+1:] - dxx,dyy = self._apply_rotation(dx,dy) - h = np.sqrt(dxx*dxx + dyy*dyy) + for i1, (n1, x1, y1) in enumerate(zip(names, x, y)): + dx = x1 - x[i1 + 1 :] + dy = y1 - y[i1 + 1 :] + dxx, dyy = self._apply_rotation(dx, dy) + h = np.sqrt(dxx * dxx + dyy * dyy) - h[h<0.0] = 0.0 + h[h < 0.0] = 0.0 h = self._h_function(h) if np.any(np.isnan(h)): raise Exception("nans in h for i1 {0}".format(i1)) - cov.x[i1,i1+1:] += h + cov.x[i1, i1 + 1 :] += h for i in range(len(names)): - cov.x[i+1:,i] = cov.x[i,i+1:] + cov.x[i + 1 :, i] = cov.x[i, i + 1 :] return cov - def _specsim_grid_contrib(self,grid): + def _specsim_grid_contrib(self, grid): rot_grid = grid - if self.bearing % 90. != 0: - dx,dy = self._apply_rotation(grid[0,:,:],grid[1,:,:]) - rot_grid = np.array((dx,dy)) - h = ((rot_grid**2).sum(axis=0))**0.5 + if self.bearing % 90.0 != 0: + dx, dy = self._apply_rotation(grid[0, :, :], grid[1, :, :]) + rot_grid = np.array((dx, dy)) + h = ((rot_grid ** 2).sum(axis=0)) ** 0.5 c = self._h_function(h) return c - def _apply_rotation(self,dx,dy): + def _apply_rotation(self, dx, dy): """ private method to rotate points according to Vario2d.bearing and Vario2d.anisotropy """ if self.anisotropy == 1.0: - return dx,dy + return dx, dy rcoefs = self.rotation_coefs - dxx = (dx * rcoefs[0]) +\ - (dy * rcoefs[1]) - dyy = ((dx * rcoefs[2]) +\ - (dy * rcoefs[3])) *\ - self.anisotropy - return dxx,dyy - - def covariance_points(self,x0,y0,xother,yother): + dxx = (dx * rcoefs[0]) + (dy * rcoefs[1]) + dyy = ((dx * rcoefs[2]) + (dy * rcoefs[3])) * self.anisotropy + return dxx, dyy + + def covariance_points(self, x0, y0, xother, yother): """ get the covariance between base point (x0,y0) and other points xother,yother implied by `Vario2d` @@ -1539,11 +1714,11 @@ def covariance_points(self,x0,y0,xother,yother): """ dxx = x0 - xother dyy = y0 - yother - dxx,dyy = self._apply_rotation(dxx,dyy) - h = np.sqrt(dxx*dxx + dyy*dyy) + dxx, dyy = self._apply_rotation(dxx, dyy) + h = np.sqrt(dxx * dxx + dyy * dyy) return self._h_function(h) - def covariance(self,pt0,pt1): + def covariance(self, pt0, pt1): """ get the covarince between two points implied by Vario2d Args: @@ -1555,11 +1730,10 @@ def covariance(self,pt0,pt1): """ - x = np.array([pt0[0],pt1[0]]) - y = np.array([pt0[1],pt1[1]]) - names = ["n1","n2"] - return self.covariance_matrix(x,y,names=names).x[0,1] - + x = np.array([pt0[0], pt1[0]]) + y = np.array([pt0[1], pt1[1]]) + names = ["n1", "n2"] + return self.covariance_matrix(x, y, names=names).x[0, 1] def __str__(self): """ get the str representation of Vario2d @@ -1567,11 +1741,12 @@ def __str__(self): Returns: `str`: string rep """ - s = "name:{0},contribution:{1},a:{2},anisotropy:{3},bearing:{4}\n".\ - format(self.name,self.contribution,self.a,\ - self.anisotropy,self.bearing) + s = "name:{0},contribution:{1},a:{2},anisotropy:{3},bearing:{4}\n".format( + self.name, self.contribution, self.a, self.anisotropy, self.bearing + ) return s + class ExpVario(Vario2d): """Gaussian variogram derived type @@ -1590,16 +1765,18 @@ class ExpVario(Vario2d): """ - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(ExpVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) + def __init__(self, contribution, a, anisotropy=1.0, bearing=0.0, name="var1"): + super(ExpVario, self).__init__( + contribution, a, anisotropy=anisotropy, bearing=bearing, name=name + ) self.vartype = 2 - def _h_function(self,h): + def _h_function(self, h): """ private method exponential variogram "h" function """ return self.contribution * np.exp(-1.0 * h / self.a) + class GauVario(Vario2d): """Gaussian variogram derived type @@ -1620,12 +1797,13 @@ class GauVario(Vario2d): """ - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(GauVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) + def __init__(self, contribution, a, anisotropy=1.0, bearing=0.0, name="var1"): + super(GauVario, self).__init__( + contribution, a, anisotropy=anisotropy, bearing=bearing, name=name + ) self.vartype = 3 - def _h_function(self,h): + def _h_function(self, h): """ private method for the gaussian variogram "h" function """ @@ -1633,6 +1811,7 @@ def _h_function(self,h): hh = -1.0 * (h * h) / (self.a * self.a) return self.contribution * np.exp(hh) + class SphVario(Vario2d): """Spherical variogram derived type @@ -1650,12 +1829,13 @@ class SphVario(Vario2d): """ - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(SphVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) + def __init__(self, contribution, a, anisotropy=1.0, bearing=0.0, name="var1"): + super(SphVario, self).__init__( + contribution, a, anisotropy=anisotropy, bearing=bearing, name=name + ) self.vartype = 1 - def _h_function(self,h): + def _h_function(self, h): """ private method for the spherical variogram "h" function """ @@ -1670,17 +1850,14 @@ def _h_function(self,h): # except TypeError: # if hh > 0.0: # h = 0.0 - #return h + # return h # if hh < 1.0: # return self.contribution * (1.0 - (hh * (1.5 - (0.5 * hh * hh)))) # else: # return 0.0 - - - -def read_struct_file(struct_file,return_type=GeoStruct): +def read_struct_file(struct_file, return_type=GeoStruct): """read an existing PEST-type structure file into a GeoStruct instance Args: @@ -1699,33 +1876,38 @@ def read_struct_file(struct_file,return_type=GeoStruct): """ - VARTYPE = {1:SphVario,2:ExpVario,3:GauVario,4:None} + VARTYPE = {1: SphVario, 2: ExpVario, 3: GauVario, 4: None} assert os.path.exists(struct_file) structures = [] variograms = [] - with open(struct_file,'r') as f: + with open(struct_file, "r") as f: while True: line = f.readline() - if line == '': + if line == "": break line = line.strip().lower() if line.startswith("structure"): name = line.strip().split()[1] - nugget,transform,variogram_info = _read_structure_attributes(f) - s = return_type(nugget=nugget,transform=transform,name=name) + nugget, transform, variogram_info = _read_structure_attributes(f) + s = return_type(nugget=nugget, transform=transform, name=name) s.variogram_info = variogram_info # not sure what is going on, but if I don't copy s here, # all the structures end up sharing all the variograms later structures.append(copy.deepcopy(s)) elif line.startswith("variogram"): name = line.strip().split()[1].lower() - vartype,bearing,a,anisotropy = _read_variogram(f) + vartype, bearing, a, anisotropy = _read_variogram(f) if name in variogram_info: - v = VARTYPE[vartype](variogram_info[name],a,anisotropy=anisotropy, - bearing=bearing,name=name) + v = VARTYPE[vartype]( + variogram_info[name], + a, + anisotropy=anisotropy, + bearing=bearing, + name=name, + ) variograms.append(v) - for i,st in enumerate(structures): + for i, st in enumerate(structures): for vname in st.variogram_info: vfound = None for v in variograms: @@ -1733,8 +1915,9 @@ def read_struct_file(struct_file,return_type=GeoStruct): vfound = v break if vfound is None: - raise Exception("variogram {0} not found for structure {1}".\ - format(vname,s.name)) + raise Exception( + "variogram {0} not found for structure {1}".format(vname, s.name) + ) st.variograms.append(vfound) if len(structures) == 1: @@ -1742,24 +1925,23 @@ def read_struct_file(struct_file,return_type=GeoStruct): return structures - def _read_variogram(f): """Function to instantiate a Vario2d from a PEST-style structure file """ - line = '' + line = "" vartype = None bearing = 0.0 a = None anisotropy = 1.0 while "end variogram" not in line: line = f.readline() - if line == '': + if line == "": raise Exception("EOF while read variogram") line = line.strip().lower().split() - if line[0].startswith('#'): + if line[0].startswith("#"): continue if line[0] == "vartype": vartype = int(line[1]) @@ -1773,7 +1955,7 @@ def _read_variogram(f): break else: raise Exception("unrecognized arg in variogram:{0}".format(line[0])) - return vartype,bearing,a,anisotropy + return vartype, bearing, a, anisotropy def _read_structure_attributes(f): @@ -1782,14 +1964,14 @@ def _read_structure_attributes(f): """ - line = '' + line = "" variogram_info = {} while "end structure" not in line: line = f.readline() - if line == '': + if line == "": raise Exception("EOF while reading structure") line = line.strip().lower().split() - if line[0].startswith('#'): + if line[0].startswith("#"): continue if line[0] == "nugget": nugget = float(line[1]) @@ -1802,15 +1984,16 @@ def _read_structure_attributes(f): elif line[0] == "end": break elif line[0] == "mean": - warnings.warn("'mean' attribute not supported, skipping",PyemuWarning) + warnings.warn("'mean' attribute not supported, skipping", PyemuWarning) else: - raise Exception("unrecognized line in structure definition:{0}".\ - format(line[0])) + raise Exception( + "unrecognized line in structure definition:{0}".format(line[0]) + ) assert numvariograms == len(variogram_info) - return nugget,transform,variogram_info + return nugget, transform, variogram_info -def read_sgems_variogram_xml(xml_file,return_type=GeoStruct): +def read_sgems_variogram_xml(xml_file, return_type=GeoStruct): """ function to read an SGEMS-type variogram XML file into a `GeoStruct` @@ -1840,8 +2023,8 @@ def read_sgems_variogram_xml(xml_file,return_type=GeoStruct): variograms = [] nugget = 0.0 num_struct = 0 - for key,val in gs_model.items(): - #print(key,val) + for key, val in gs_model.items(): + # print(key,val) if str(key).lower() == "nugget": if len(val) > 0: nugget = float(val) @@ -1853,10 +2036,10 @@ def read_sgems_variogram_xml(xml_file,return_type=GeoStruct): raise NotImplementedError() for structure in gs_model: vtype, contribution = None, None - mx_range,mn_range = None, None - x_angle,y_angle = None,None - #struct_name = structure.tag - for key,val in structure.items(): + mx_range, mn_range = None, None + x_angle, y_angle = None, None + # struct_name = structure.tag + for key, val in structure.items(): key = str(key).lower() if key == "type": vtype = str(val).lower() @@ -1885,13 +2068,17 @@ def read_sgems_variogram_xml(xml_file,return_type=GeoStruct): assert x_angle is not None assert y_angle is not None assert vtype is not None - v = vtype(contribution=contribution,a=mx_range, - anisotropy=mx_range/mn_range,bearing=(180.0/np.pi)*np.arctan2(x_angle,y_angle), - name=structure.tag) - return GeoStruct(nugget=nugget,variograms=[v]) + v = vtype( + contribution=contribution, + a=mx_range, + anisotropy=mx_range / mn_range, + bearing=(180.0 / np.pi) * np.arctan2(x_angle, y_angle), + name=structure.tag, + ) + return GeoStruct(nugget=nugget, variograms=[v]) -def gslib_2_dataframe(filename,attr_name=None,x_idx=0,y_idx=1): +def gslib_2_dataframe(filename, attr_name=None, x_idx=0, y_idx=1): """ function to read a GSLIB point data file into a pandas.DataFrame Args: @@ -1916,22 +2103,26 @@ def gslib_2_dataframe(filename,attr_name=None,x_idx=0,y_idx=1): """ - with open(filename,'r') as f: + with open(filename, "r") as f: title = f.readline().strip() num_attrs = int(f.readline().strip()) attrs = [f.readline().strip() for _ in range(num_attrs)] if attr_name is not None: - assert attr_name in attrs,"{0} not in attrs:{1}".format(attr_name,','.join(attrs)) + assert attr_name in attrs, "{0} not in attrs:{1}".format( + attr_name, ",".join(attrs) + ) else: - assert len(attrs) == 3,"propname is None but more than 3 attrs in gslib file" + assert ( + len(attrs) == 3 + ), "propname is None but more than 3 attrs in gslib file" attr_name = attrs[2] assert len(attrs) > x_idx assert len(attrs) > y_idx a_idx = attrs.index(attr_name) - x,y,a = [],[],[] + x, y, a = [], [], [] while True: line = f.readline() - if line == '': + if line == "": break raw = line.strip().split() try: @@ -1939,16 +2130,17 @@ def gslib_2_dataframe(filename,attr_name=None,x_idx=0,y_idx=1): y.append(float(raw[y_idx])) a.append(float(raw[a_idx])) except Exception as e: - raise Exception("error paring line {0}: {1}".format(line,str(e))) - df = pd.DataFrame({"x":x,"y":y,"value":a}) - df.loc[:,"name"] = ["pt{0}".format(i) for i in range(df.shape[0])] + raise Exception("error paring line {0}: {1}".format(line, str(e))) + df = pd.DataFrame({"x": x, "y": y, "value": a}) + df.loc[:, "name"] = ["pt{0}".format(i) for i in range(df.shape[0])] df.index = df.name return df -#class ExperimentalVariogram(object): +# class ExperimentalVariogram(object): # def __init__(self,na) + def load_sgems_exp_var(filename): """ read an SGEM experimental variogram into a sequence of pandas.DataFrames @@ -1964,16 +2156,17 @@ def load_sgems_exp_var(filename): assert os.path.exists(filename) import xml.etree.ElementTree as etree + tree = etree.parse(filename) root = tree.getroot() dfs = {} for variogram in root: - #print(variogram.tag) + # print(variogram.tag) for attrib in variogram: - #print(attrib.tag,attrib.text) + # print(attrib.tag,attrib.text) if attrib.tag == "title": - title = attrib.text.split(',')[0].split('=')[-1] + title = attrib.text.split(",")[0].split("=")[-1] elif attrib.tag == "x": x = [float(i) for i in attrib.text.split()] elif attrib.tag == "y": @@ -1982,16 +2175,21 @@ def load_sgems_exp_var(filename): pairs = [int(i) for i in attrib.text.split()] for item in attrib: - print(item,item.tag) - df = pd.DataFrame({"x":x,"y":y,"pairs":pairs}) - df.loc[df.y<0.0,"y"] = np.NaN + print(item, item.tag) + df = pd.DataFrame({"x": x, "y": y, "pairs": pairs}) + df.loc[df.y < 0.0, "y"] = np.NaN dfs[title] = df return dfs - -def fac2real(pp_file=None,factors_file="factors.dat",out_file="test.ref", - upper_lim=1.0e+30,lower_lim=-1.0e+30,fill_value=1.0e+30): +def fac2real( + pp_file=None, + factors_file="factors.dat", + out_file="test.ref", + upper_lim=1.0e30, + lower_lim=-1.0e30, + fill_value=1.0e30, +): """A python replication of the PEST fac2real utility for creating a structure grid array from previously calculated kriging factors (weights) @@ -2018,88 +2216,98 @@ def fac2real(pp_file=None,factors_file="factors.dat",out_file="test.ref", """ - if pp_file is not None and isinstance(pp_file,str): + if pp_file is not None and isinstance(pp_file, str): assert os.path.exists(pp_file) # pp_data = pd.read_csv(pp_file,delim_whitespace=True,header=None, # names=["name","parval1"],usecols=[0,4]) pp_data = pp_file_to_dataframe(pp_file) - pp_data.loc[:,"name"] = pp_data.name.apply(lambda x: x.lower()) - elif pp_file is not None and isinstance(pp_file,pd.DataFrame): + pp_data.loc[:, "name"] = pp_data.name.apply(lambda x: x.lower()) + elif pp_file is not None and isinstance(pp_file, pd.DataFrame): assert "name" in pp_file.columns assert "parval1" in pp_file.columns pp_data = pp_file else: - raise Exception("unrecognized pp_file arg: must be str or pandas.DataFrame, not {0}"\ - .format(type(pp_file))) + raise Exception( + "unrecognized pp_file arg: must be str or pandas.DataFrame, not {0}".format( + type(pp_file) + ) + ) assert os.path.exists(factors_file), "factors file not found" - f_fac = open(factors_file,'r') + f_fac = open(factors_file, "r") fpp_file = f_fac.readline() if pp_file is None and pp_data is None: pp_data = pp_file_to_dataframe(fpp_file) pp_data.loc[:, "name"] = pp_data.name.apply(lambda x: x.lower()) fzone_file = f_fac.readline() - ncol,nrow = [int(i) for i in f_fac.readline().strip().split()] + ncol, nrow = [int(i) for i in f_fac.readline().strip().split()] npp = int(f_fac.readline().strip()) pp_names = [f_fac.readline().strip().lower() for _ in range(npp)] # check that pp_names is sync'd with pp_data diff = set(list(pp_data.name)).symmetric_difference(set(pp_names)) if len(diff) > 0: - raise Exception("the following pilot point names are not common " +\ - "between the factors file and the pilot points file " +\ - ','.join(list(diff))) - - arr = np.zeros((nrow,ncol),dtype=np.float) + fill_value - pp_dict = {int(name):val for name,val in zip(pp_data.index,pp_data.parval1)} + raise Exception( + "the following pilot point names are not common " + + "between the factors file and the pilot points file " + + ",".join(list(diff)) + ) + + arr = np.zeros((nrow, ncol), dtype=np.float) + fill_value + pp_dict = {int(name): val for name, val in zip(pp_data.index, pp_data.parval1)} try: - pp_dict_log = {name:np.log10(val) for name,val in zip(pp_data.index,pp_data.parval1)} + pp_dict_log = { + name: np.log10(val) for name, val in zip(pp_data.index, pp_data.parval1) + } except: pp_dict_log = {} - #for i in range(nrow): + # for i in range(nrow): # for j in range(ncol): while True: line = f_fac.readline() if len(line) == 0: - #raise Exception("unexpected EOF in factors file") + # raise Exception("unexpected EOF in factors file") break try: - inode,itrans,fac_data = _parse_factor_line(line) + inode, itrans, fac_data = _parse_factor_line(line) except Exception as e: - raise Exception("error parsing factor line {0}:{1}".format(line,str(e))) - #fac_prods = [pp_data.loc[pp,"value"]*fac_data[pp] for pp in fac_data] + raise Exception("error parsing factor line {0}:{1}".format(line, str(e))) + # fac_prods = [pp_data.loc[pp,"value"]*fac_data[pp] for pp in fac_data] if itrans == 0: fac_sum = sum([pp_dict[pp] * fac_data[pp] for pp in fac_data]) else: fac_sum = sum([pp_dict_log[pp] * fac_data[pp] for pp in fac_data]) if itrans != 0: - fac_sum = 10**fac_sum - #col = ((inode - 1) // nrow) + 1 - #row = inode - ((col - 1) * nrow) - row = ((inode-1) // ncol) + 1 + fac_sum = 10 ** fac_sum + # col = ((inode - 1) // nrow) + 1 + # row = inode - ((col - 1) * nrow) + row = ((inode - 1) // ncol) + 1 col = inode - ((row - 1) * ncol) - #arr[row-1,col-1] = np.sum(np.array(fac_prods)) + # arr[row-1,col-1] = np.sum(np.array(fac_prods)) arr[row - 1, col - 1] = fac_sum - arr[arrupper_lim] = upper_lim + arr[arr < lower_lim] = lower_lim + arr[arr > upper_lim] = upper_lim - #print(out_file,arr.min(),pp_data.parval1.min(),lower_lim) + # print(out_file,arr.min(),pp_data.parval1.min(),lower_lim) if out_file is not None: - np.savetxt(out_file,arr,fmt="%15.6E",delimiter='') + np.savetxt(out_file, arr, fmt="%15.6E", delimiter="") return out_file return arr + def _parse_factor_line(line): """ function to parse a factor file line. Used by fac2real() """ raw = line.strip().split() - inode,itrans,nfac = [int(i) for i in raw[:3]] - fac_data = {int(raw[ifac])-1:float(raw[ifac+1]) for ifac in range(4,4+nfac*2,2)} + inode, itrans, nfac = [int(i) for i in raw[:3]] + fac_data = { + int(raw[ifac]) - 1: float(raw[ifac + 1]) for ifac in range(4, 4 + nfac * 2, 2) + } # fac_data = {} # for ifac in range(4,4+nfac*2,2): # pnum = int(raw[ifac]) - 1 #zero based to sync with pandas # fac = float(raw[ifac+1]) # fac_data[pnum] = fac - return inode,itrans,fac_data \ No newline at end of file + return inode, itrans, fac_data diff --git a/pyemu/utils/gw_utils.py b/pyemu/utils/gw_utils.py index 96ae9d58a..4c6ee8b7a 100644 --- a/pyemu/utils/gw_utils.py +++ b/pyemu/utils/gw_utils.py @@ -6,18 +6,32 @@ import numpy as np import pandas as pd import re + pd.options.display.max_colwidth = 100 -from pyemu.pst.pst_utils import SFMT,IFMT,FFMT,pst_config,\ - parse_tpl_file,try_process_output_file +from pyemu.pst.pst_utils import ( + SFMT, + IFMT, + FFMT, + pst_config, + parse_tpl_file, + try_process_output_file, +) from pyemu.utils.os_utils import run from pyemu.utils.helpers import _write_df_tpl from ..pyemu_warnings import PyemuWarning -PP_FMT = {"name": SFMT, "x": FFMT, "y": FFMT, "zone": IFMT, "tpl": SFMT, - "parval1": FFMT} -PP_NAMES = ["name","x","y","zone","parval1"] + +PP_FMT = { + "name": SFMT, + "x": FFMT, + "y": FFMT, + "zone": IFMT, + "tpl": SFMT, + "parval1": FFMT, +} +PP_NAMES = ["name", "x", "y", "zone", "parval1"] -def modflow_pval_to_template_file(pval_file,tpl_file=None): +def modflow_pval_to_template_file(pval_file, tpl_file=None): """write a template file for a modflow parameter value file. Args: @@ -33,21 +47,30 @@ def modflow_pval_to_template_file(pval_file,tpl_file=None): if tpl_file is None: tpl_file = pval_file + ".tpl" - pval_df = pd.read_csv(pval_file,delim_whitespace=True, - header=None,skiprows=2, - names=["parnme","parval1"]) + pval_df = pd.read_csv( + pval_file, + delim_whitespace=True, + header=None, + skiprows=2, + names=["parnme", "parval1"], + ) pval_df.index = pval_df.parnme - pval_df.loc[:,"tpl"] = pval_df.parnme.apply(lambda x: " ~ {0:15s} ~".format(x)) - with open(tpl_file,'w') as f: + pval_df.loc[:, "tpl"] = pval_df.parnme.apply(lambda x: " ~ {0:15s} ~".format(x)) + with open(tpl_file, "w") as f: f.write("ptf ~\n#pval template file from pyemu\n") f.write("{0:10d} #NP\n".format(pval_df.shape[0])) - f.write(pval_df.loc[:,["parnme","tpl"]].to_string(col_space=0, - formatters=[SFMT,SFMT], - index=False, - header=False, - justify="left")) + f.write( + pval_df.loc[:, ["parnme", "tpl"]].to_string( + col_space=0, + formatters=[SFMT, SFMT], + index=False, + header=False, + justify="left", + ) + ) return pval_df + def modflow_hob_to_instruction_file(hob_file, ins_file=None): """write an instruction file for a modflow head observation file @@ -61,27 +84,38 @@ def modflow_hob_to_instruction_file(hob_file, ins_file=None): """ - hob_df = pd.read_csv(hob_file,delim_whitespace=True,skiprows=1, - header=None,names=["simval","obsval","obsnme"]) + hob_df = pd.read_csv( + hob_file, + delim_whitespace=True, + skiprows=1, + header=None, + names=["simval", "obsval", "obsnme"], + ) - hob_df.loc[:,"obsnme"] = hob_df.obsnme.apply(str.lower) - hob_df.loc[:,"ins_line"] = hob_df.obsnme.apply(lambda x:"l1 !{0:s}!".format(x)) - hob_df.loc[0,"ins_line"] = hob_df.loc[0,"ins_line"].replace('l1','l2') + hob_df.loc[:, "obsnme"] = hob_df.obsnme.apply(str.lower) + hob_df.loc[:, "ins_line"] = hob_df.obsnme.apply(lambda x: "l1 !{0:s}!".format(x)) + hob_df.loc[0, "ins_line"] = hob_df.loc[0, "ins_line"].replace("l1", "l2") if ins_file is None: ins_file = hob_file + ".ins" - f_ins = open(ins_file, 'w') + f_ins = open(ins_file, "w") f_ins.write("pif ~\n") - f_ins.write(hob_df.loc[:,["ins_line"]].to_string(col_space=0, - columns=["ins_line"], - header=False, - index=False, - formatters=[SFMT]) + '\n') - hob_df.loc[:,"weight"] = 1.0 - hob_df.loc[:,"obgnme"] = "obgnme" + f_ins.write( + hob_df.loc[:, ["ins_line"]].to_string( + col_space=0, + columns=["ins_line"], + header=False, + index=False, + formatters=[SFMT], + ) + + "\n" + ) + hob_df.loc[:, "weight"] = 1.0 + hob_df.loc[:, "obgnme"] = "obgnme" f_ins.close() return hob_df + def modflow_hydmod_to_instruction_file(hydmod_file, ins_file=None): """write an instruction file for a modflow hydmod file @@ -98,30 +132,38 @@ def modflow_hydmod_to_instruction_file(hydmod_file, ins_file=None): """ hydmod_df, hydmod_outfile = modflow_read_hydmod_file(hydmod_file) - hydmod_df.loc[:,"ins_line"] = hydmod_df.obsnme.apply(lambda x:"l1 w !{0:s}!".format(x)) + hydmod_df.loc[:, "ins_line"] = hydmod_df.obsnme.apply( + lambda x: "l1 w !{0:s}!".format(x) + ) if ins_file is None: ins_file = hydmod_outfile + ".ins" - with open(ins_file, 'w') as f_ins: + with open(ins_file, "w") as f_ins: f_ins.write("pif ~\nl1\n") - f_ins.write(hydmod_df.loc[:,["ins_line"]].to_string(col_space=0, - columns=["ins_line"], - header=False, - index=False, - formatters=[SFMT]) + '\n') - hydmod_df.loc[:,"weight"] = 1.0 - hydmod_df.loc[:,"obgnme"] = "obgnme" - - df = try_process_output_file(hydmod_outfile+".ins") + f_ins.write( + hydmod_df.loc[:, ["ins_line"]].to_string( + col_space=0, + columns=["ins_line"], + header=False, + index=False, + formatters=[SFMT], + ) + + "\n" + ) + hydmod_df.loc[:, "weight"] = 1.0 + hydmod_df.loc[:, "obgnme"] = "obgnme" + + df = try_process_output_file(hydmod_outfile + ".ins") if df is not None: - df.loc[:,"obsnme"] = df.index.values - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: x[:-9]) - df.to_csv("_setup_"+os.path.split(hydmod_outfile)[-1]+'.csv',index=False) + df.loc[:, "obsnme"] = df.index.values + df.loc[:, "obgnme"] = df.obsnme.apply(lambda x: x[:-9]) + df.to_csv("_setup_" + os.path.split(hydmod_outfile)[-1] + ".csv", index=False) return df return hydmod_df + def modflow_read_hydmod_file(hydmod_file, hydmod_outfile=None): """ read a binary hydmod file and return a dataframe of the results @@ -136,44 +178,51 @@ def modflow_read_hydmod_file(hydmod_file, hydmod_outfile=None): try: import flopy.utils as fu except Exception as e: - print('flopy is not installed - cannot read {0}\n{1}'.format(hydmod_file, e)) + print("flopy is not installed - cannot read {0}\n{1}".format(hydmod_file, e)) return obs = fu.HydmodObs(hydmod_file) hyd_df = obs.get_dataframe() - hyd_df.columns = [i[2:] if i.lower() != 'totim' else i for i in hyd_df.columns] - #hyd_df.loc[:,"datetime"] = hyd_df.index - hyd_df['totim'] = hyd_df.index.map(lambda x: x.strftime("%Y%m%d")) - - hyd_df.rename(columns={'totim': 'datestamp'}, inplace=True) + hyd_df.columns = [i[2:] if i.lower() != "totim" else i for i in hyd_df.columns] + # hyd_df.loc[:,"datetime"] = hyd_df.index + hyd_df["totim"] = hyd_df.index.map(lambda x: x.strftime("%Y%m%d")) + hyd_df.rename(columns={"totim": "datestamp"}, inplace=True) # reshape into a single column - hyd_df = pd.melt(hyd_df, id_vars='datestamp') + hyd_df = pd.melt(hyd_df, id_vars="datestamp") - hyd_df.rename(columns={'value': 'obsval'}, inplace=True) + hyd_df.rename(columns={"value": "obsval"}, inplace=True) - hyd_df['obsnme'] = [i.lower() + '_' + j.lower() for i, j in zip(hyd_df.variable, hyd_df.datestamp)] + hyd_df["obsnme"] = [ + i.lower() + "_" + j.lower() for i, j in zip(hyd_df.variable, hyd_df.datestamp) + ] vc = hyd_df.obsnme.value_counts().sort_values() - vc = list(vc.loc[vc>1].index.values) + vc = list(vc.loc[vc > 1].index.values) if len(vc) > 0: hyd_df.to_csv("hyd_df.duplciates.csv") obs.get_dataframe().to_csv("hyd_org.duplicates.csv") raise Exception("duplicates in obsnme:{0}".format(vc)) - #assert hyd_df.obsnme.value_counts().max() == 1,"duplicates in obsnme" + # assert hyd_df.obsnme.value_counts().max() == 1,"duplicates in obsnme" if not hydmod_outfile: - hydmod_outfile = hydmod_file + '.dat' - hyd_df.to_csv(hydmod_outfile, columns=['obsnme','obsval'], sep=' ',index=False) - #hyd_df = hyd_df[['obsnme','obsval']] - return hyd_df[['obsnme','obsval']], hydmod_outfile - - -def setup_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat",sw_filename="mtlist_sw.dat", - start_datetime="1-1-1970",gw_prefix='gw',sw_prefix="sw", - save_setup_file=False): + hydmod_outfile = hydmod_file + ".dat" + hyd_df.to_csv(hydmod_outfile, columns=["obsnme", "obsval"], sep=" ", index=False) + # hyd_df = hyd_df[['obsnme','obsval']] + return hyd_df[["obsnme", "obsval"]], hydmod_outfile + + +def setup_mtlist_budget_obs( + list_filename, + gw_filename="mtlist_gw.dat", + sw_filename="mtlist_sw.dat", + start_datetime="1-1-1970", + gw_prefix="gw", + sw_prefix="sw", + save_setup_file=False, +): """ setup observations of gw (and optionally sw) mass budgets from mt3dusgs list file. Args: @@ -212,12 +261,14 @@ def setup_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat",sw_filenam This is the companion function of `gw_utils.apply_mtlist_budget_obs()`. """ - gw,sw = apply_mtlist_budget_obs(list_filename, gw_filename, sw_filename, start_datetime) + gw, sw = apply_mtlist_budget_obs( + list_filename, gw_filename, sw_filename, start_datetime + ) gw_ins = gw_filename + ".ins" _write_mtlist_ins(gw_ins, gw, gw_prefix) ins_files = [gw_ins] - df_gw = try_process_output_file(gw_ins,gw_filename) + df_gw = try_process_output_file(gw_ins, gw_filename) if df_gw is None: raise Exception("error processing groundwater instruction file") if sw is not None: @@ -225,18 +276,19 @@ def setup_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat",sw_filenam _write_mtlist_ins(sw_ins, sw, sw_prefix) ins_files.append(sw_ins) - df_sw = try_process_output_file(sw_ins,sw_filename) + df_sw = try_process_output_file(sw_ins, sw_filename) if df_sw is None: raise Exception("error processing surface water instruction file") df_gw = df_gw.append(df_sw) df_gw.loc[:, "obsnme"] = df_gw.index.values if save_setup_file: - df_gw.to_csv("_setup_" + os.path.split(list_filename)[-1] + '.csv', index=False) + df_gw.to_csv("_setup_" + os.path.split(list_filename)[-1] + ".csv", index=False) frun_line = "pyemu.gw_utils.apply_mtlist_budget_obs('{0}')".format(list_filename) - return frun_line,ins_files,df_gw + return frun_line, ins_files, df_gw + -def _write_mtlist_ins(ins_filename,df,prefix): +def _write_mtlist_ins(ins_filename, df, prefix): """ write an instruction file for a MT3D-USGS list file """ @@ -244,22 +296,27 @@ def _write_mtlist_ins(ins_filename,df,prefix): dt_str = df.index.map(lambda x: x.strftime("%Y%m%d")) except: dt_str = df.index.map(lambda x: "{0:08.1f}".format(x).strip()) - with open(ins_filename,'w') as f: - f.write('pif ~\nl1\n') + with open(ins_filename, "w") as f: + f.write("pif ~\nl1\n") for dt in dt_str: f.write("l1 ") for col in df.columns.str.translate( - {ord(s): None for s in ['(', ')', '/', '=']}): - if prefix == '': + {ord(s): None for s in ["(", ")", "/", "="]} + ): + if prefix == "": obsnme = "{0}_{1}".format(col, dt) else: obsnme = "{0}_{1}_{2}".format(prefix, col, dt) f.write(" w !{0}!".format(obsnme)) f.write("\n") -def apply_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat", - sw_filename="mtlist_sw.dat", - start_datetime="1-1-1970"): + +def apply_mtlist_budget_obs( + list_filename, + gw_filename="mtlist_gw.dat", + sw_filename="mtlist_sw.dat", + start_datetime="1-1-1970", +): """ process an MT3D-USGS list file to extract mass budget entries. Args: @@ -287,20 +344,38 @@ def apply_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat", raise Exception("error import flopy: {0}".format(str(e))) mt = flopy.utils.MtListBudget(list_filename) gw, sw = mt.parse(start_datetime=start_datetime, diff=True) - gw = gw.drop([col for col in gw.columns - for drop_col in ["kper", "kstp", "tkstp"] - if (col.lower().startswith(drop_col))], axis=1) - gw.to_csv(gw_filename, sep=' ', index_label="datetime", date_format="%Y%m%d") + gw = gw.drop( + [ + col + for col in gw.columns + for drop_col in ["kper", "kstp", "tkstp"] + if (col.lower().startswith(drop_col)) + ], + axis=1, + ) + gw.to_csv(gw_filename, sep=" ", index_label="datetime", date_format="%Y%m%d") if sw is not None: - sw = sw.drop([col for col in sw.columns - for drop_col in ["kper", "kstp", "tkstp"] - if (col.lower().startswith(drop_col))], axis=1) - sw.to_csv(sw_filename, sep=' ', index_label="datetime", date_format="%Y%m%d") + sw = sw.drop( + [ + col + for col in sw.columns + for drop_col in ["kper", "kstp", "tkstp"] + if (col.lower().startswith(drop_col)) + ], + axis=1, + ) + sw.to_csv(sw_filename, sep=" ", index_label="datetime", date_format="%Y%m%d") return gw, sw -def setup_mflist_budget_obs(list_filename,flx_filename="flux.dat", - vol_filename="vol.dat",start_datetime="1-1'1970",prefix='', - save_setup_file=False): + +def setup_mflist_budget_obs( + list_filename, + flx_filename="flux.dat", + vol_filename="vol.dat", + start_datetime="1-1'1970", + prefix="", + save_setup_file=False, +): """ setup observations of budget volume and flux from modflow list file. Args: @@ -329,29 +404,34 @@ def setup_mflist_budget_obs(list_filename,flx_filename="flux.dat", """ - flx,vol = apply_mflist_budget_obs(list_filename,flx_filename,vol_filename, - start_datetime) - _write_mflist_ins(flx_filename+".ins",flx,prefix+"flx") - _write_mflist_ins(vol_filename+".ins",vol, prefix+"vol") + flx, vol = apply_mflist_budget_obs( + list_filename, flx_filename, vol_filename, start_datetime + ) + _write_mflist_ins(flx_filename + ".ins", flx, prefix + "flx") + _write_mflist_ins(vol_filename + ".ins", vol, prefix + "vol") - df = try_process_output_file(flx_filename+".ins") + df = try_process_output_file(flx_filename + ".ins") if df is None: raise Exception("error processing flux instruction file") - df2 = try_process_output_file(vol_filename+".ins") + df2 = try_process_output_file(vol_filename + ".ins") if df2 is None: raise Exception("error processing volume instruction file") df = df.append(df2) - df.loc[:,"obsnme"] = df.index.values + df.loc[:, "obsnme"] = df.index.values if save_setup_file: - df.to_csv("_setup_" + os.path.split(list_filename)[-1] + '.csv', index=False) + df.to_csv("_setup_" + os.path.split(list_filename)[-1] + ".csv", index=False) return df -def apply_mflist_budget_obs(list_filename,flx_filename="flux.dat", - vol_filename="vol.dat", - start_datetime="1-1-1970"): + +def apply_mflist_budget_obs( + list_filename, + flx_filename="flux.dat", + vol_filename="vol.dat", + start_datetime="1-1-1970", +): """ process a MODFLOW list file to extract flux and volume water budget entries. Args: @@ -383,31 +463,39 @@ def apply_mflist_budget_obs(list_filename,flx_filename="flux.dat", except Exception as e: raise Exception("error import flopy: {0}".format(str(e))) mlf = flopy.utils.MfListBudget(list_filename) - flx,vol = mlf.get_dataframes(start_datetime=start_datetime,diff=True) - flx.to_csv(flx_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - vol.to_csv(vol_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - return flx,vol + flx, vol = mlf.get_dataframes(start_datetime=start_datetime, diff=True) + flx.to_csv(flx_filename, sep=" ", index_label="datetime", date_format="%Y%m%d") + vol.to_csv(vol_filename, sep=" ", index_label="datetime", date_format="%Y%m%d") + return flx, vol -def _write_mflist_ins(ins_filename,df,prefix): +def _write_mflist_ins(ins_filename, df, prefix): """ write an instruction file for a MODFLOW list file """ dt_str = df.index.map(lambda x: x.strftime("%Y%m%d")) - with open(ins_filename,'w') as f: - f.write('pif ~\nl1\n') + with open(ins_filename, "w") as f: + f.write("pif ~\nl1\n") for dt in dt_str: f.write("l1 ") for col in df.columns: - obsnme = "{0}_{1}_{2}".format(prefix,col,dt) + obsnme = "{0}_{1}_{2}".format(prefix, col, dt) f.write(" w !{0}!".format(obsnme)) f.write("\n") -def setup_hds_timeseries(bin_file, kij_dict, prefix=None, include_path=False, - model=None, postprocess_inact=None, text=None, - fill=None,precision="single"): +def setup_hds_timeseries( + bin_file, + kij_dict, + prefix=None, + include_path=False, + model=None, + postprocess_inact=None, + text=None, + fill=None, + precision="single", +): """a function to setup a forward process to extract time-series style values from a binary modflow binary file (or equivalent format - hds, ucn, sub, cbb, etc). @@ -466,24 +554,35 @@ def setup_hds_timeseries(bin_file, kij_dict, prefix=None, include_path=False, try: # hack: if model is passed and its None, it trips up CellBudgetFile... if model is not None: - bf = flopy.utils.CellBudgetFile(bin_file,precision=precision,model=model) - iscbc=True + bf = flopy.utils.CellBudgetFile( + bin_file, precision=precision, model=model + ) + iscbc = True else: bf = flopy.utils.CellBudgetFile(bin_file, precision=precision) - iscbc=True + iscbc = True except Exception as e: try: if model is not None: - bf = flopy.utils.HeadFile(bin_file, precision=precision, model=model,text=text) + bf = flopy.utils.HeadFile( + bin_file, precision=precision, model=model, text=text + ) else: - bf = flopy.utils.HeadFile(bin_file, precision=precision,text=text) + bf = flopy.utils.HeadFile(bin_file, precision=precision, text=text) except Exception as e1: - raise Exception("error instantiating binary file as either CellBudgetFile:{0} or as HeadFile with text arg: {1}".format(str(e),str(e1))) + raise Exception( + "error instantiating binary file as either CellBudgetFile:{0} or as HeadFile with text arg: {1}".format( + str(e), str(e1) + ) + ) if iscbc: tl = [t.decode().strip() for t in bf.textlist] if text not in tl: - raise Exception("'text' {0} not found in CellBudgetFile.textlist:{1}".\ - format(text,tl)) + raise Exception( + "'text' {0} not found in CellBudgetFile.textlist:{1}".format( + text, tl + ) + ) elif bin_file.lower().endswith(".ucn"): try: bf = flopy.utils.UcnFile(bin_file) @@ -498,53 +597,65 @@ def setup_hds_timeseries(bin_file, kij_dict, prefix=None, include_path=False, if text is None: text = "none" - nlay,nrow,ncol = bf.nlay,bf.nrow,bf.ncol + nlay, nrow, ncol = bf.nlay, bf.nrow, bf.ncol - #if include_path: + # if include_path: # pth = os.path.join(*[p for p in os.path.split(hds_file)[:-1]]) # config_file = os.path.join(pth,"{0}_timeseries.config".format(hds_file)) - #else: + # else: config_file = "{0}_timeseries.config".format(bin_file) print("writing config file to {0}".format(config_file)) if fill is None: fill = "none" - f_config = open(config_file,'w') + f_config = open(config_file, "w") if model is not None: if model.dis.itmuni != 4: - warnings.warn("setup_hds_timeseries only supports 'days' time units...",PyemuWarning) - f_config.write("{0},{1},d,{2},{3},{4},{5}\n". - format(os.path.split(bin_file)[-1], - model.start_datetime,text,fill,precision,iscbc)) + warnings.warn( + "setup_hds_timeseries only supports 'days' time units...", PyemuWarning + ) + f_config.write( + "{0},{1},d,{2},{3},{4},{5}\n".format( + os.path.split(bin_file)[-1], + model.start_datetime, + text, + fill, + precision, + iscbc, + ) + ) start = pd.to_datetime(model.start_datetime) else: - f_config.write("{0},none,none,{1},{2},{3},{4}\n".format(os.path.split(bin_file)[-1], - text, fill,precision,iscbc)) + f_config.write( + "{0},none,none,{1},{2},{3},{4}\n".format( + os.path.split(bin_file)[-1], text, fill, precision, iscbc + ) + ) f_config.write("site,k,i,j\n") dfs = [] - for site,(k,i,j) in kij_dict.items(): + for site, (k, i, j) in kij_dict.items(): assert k >= 0 and k < nlay, k assert i >= 0 and i < nrow, i assert j >= 0 and j < ncol, j - site = site.lower().replace(" ",'') + site = site.lower().replace(" ", "") if iscbc: - ts = bf.get_ts((k, i, j),text=text) - #print(ts) + ts = bf.get_ts((k, i, j), text=text) + # print(ts) df = pd.DataFrame(data=ts, columns=["totim", site]) else: - df = pd.DataFrame(data=bf.get_ts((k,i,j)),columns=["totim",site]) + df = pd.DataFrame(data=bf.get_ts((k, i, j)), columns=["totim", site]) if model is not None: - dts = start + pd.to_timedelta(df.totim,unit='d') - df.loc[:,"totim"] = dts - #print(df) - f_config.write("{0},{1},{2},{3}\n".format(site,k,i,j)) + dts = start + pd.to_timedelta(df.totim, unit="d") + df.loc[:, "totim"] = dts + # print(df) + f_config.write("{0},{1},{2},{3}\n".format(site, k, i, j)) df.index = df.pop("totim") dfs.append(df) f_config.close() - df = pd.concat(dfs,axis=1).T - df.to_csv(bin_file + "_timeseries.processed", sep=' ') + df = pd.concat(dfs, axis=1).T + df.to_csv(bin_file + "_timeseries.processed", sep=" ") if model is not None: t_str = df.columns.map(lambda x: x.strftime("%Y%m%d")) else: @@ -552,23 +663,25 @@ def setup_hds_timeseries(bin_file, kij_dict, prefix=None, include_path=False, ins_file = bin_file + "_timeseries.processed.ins" print("writing instruction file to {0}".format(ins_file)) - with open(ins_file,'w') as f: - f.write('pif ~\n') + with open(ins_file, "w") as f: + f.write("pif ~\n") f.write("l1 \n") for site in df.index: - #for t in t_str: + # for t in t_str: f.write("l1 w ") - #for site in df.columns: + # for site in df.columns: for t in t_str: if prefix is not None: - obsnme = "{0}_{1}_{2}".format(prefix,site,t) + obsnme = "{0}_{1}_{2}".format(prefix, site, t) else: obsnme = "{0}_{1}".format(site, t) f.write(" !{0}!".format(obsnme)) - f.write('\n') + f.write("\n") if postprocess_inact is not None: - _setup_postprocess_hds_timeseries(bin_file, df, config_file, prefix=prefix, model=model) - bd = '.' + _setup_postprocess_hds_timeseries( + bin_file, df, config_file, prefix=prefix, model=model + ) + bd = "." if include_path: bd = os.getcwd() pth = os.path.join(*[p for p in os.path.split(bin_file)[:-1]]) @@ -578,22 +691,23 @@ def setup_hds_timeseries(bin_file, kij_dict, prefix=None, include_path=False, df = apply_hds_timeseries(config_file, postprocess_inact=postprocess_inact) except Exception as e: - os.chdir(bd) - raise Exception("error in apply_hds_timeseries(): {0}".format(str(e))) + os.chdir(bd) + raise Exception("error in apply_hds_timeseries(): {0}".format(str(e))) os.chdir(bd) - df = try_process_output_file(ins_file) if df is None: raise Exception("error processing {0} instruction file".format(ins_file)) - df.loc[:,"weight"] = 0.0 + df.loc[:, "weight"] = 0.0 if prefix is not None: - df.loc[:,"obgnme"] = df.index.map(lambda x: '_'.join(x.split('_')[:2])) + df.loc[:, "obgnme"] = df.index.map(lambda x: "_".join(x.split("_")[:2])) else: - df.loc[:, "obgnme"] = df.index.map(lambda x: x.split('_')[0]) - frun_line = "pyemu.gw_utils.apply_hds_timeseries('{0}',{1})\n".format(config_file, postprocess_inact) - return frun_line,df + df.loc[:, "obgnme"] = df.index.map(lambda x: x.split("_")[0]) + frun_line = "pyemu.gw_utils.apply_hds_timeseries('{0}',{1})\n".format( + config_file, postprocess_inact + ) + return frun_line, df def apply_hds_timeseries(config_file=None, postprocess_inact=None): @@ -616,9 +730,17 @@ def apply_hds_timeseries(config_file=None, postprocess_inact=None): config_file = "hds_timeseries.config" assert os.path.exists(config_file), config_file - with open(config_file,'r') as f: + with open(config_file, "r") as f: line = f.readline() - bf_file,start_datetime,time_units, text, fill, precision,_iscbc = line.strip().split(',') + ( + bf_file, + start_datetime, + time_units, + text, + fill, + precision, + _iscbc, + ) = line.strip().split(",") site_df = pd.read_csv(f) text = text.upper() if _iscbc.lower().strip() == "false": @@ -626,22 +748,26 @@ def apply_hds_timeseries(config_file=None, postprocess_inact=None): elif _iscbc.lower().strip() == "true": iscbc = True else: - raise Exception("apply_hds_timeseries() error: unrecognized 'iscbc' string in config file: {0}".format(_iscbc)) + raise Exception( + "apply_hds_timeseries() error: unrecognized 'iscbc' string in config file: {0}".format( + _iscbc + ) + ) assert os.path.exists(bf_file), "head save file not found" if iscbc: try: - bf = flopy.utils.CellBudgetFile(bf_file,precision=precision) + bf = flopy.utils.CellBudgetFile(bf_file, precision=precision) except Exception as e: raise Exception("error instantiating CellBudgetFile:{0}".format(str(e))) elif bf_file.lower().endswith(".ucn"): try: - bf = flopy.utils.UcnFile(bf_file,precision=precision) + bf = flopy.utils.UcnFile(bf_file, precision=precision) except Exception as e: raise Exception("error instantiating UcnFile:{0}".format(str(e))) else: try: if text != "NONE": - bf = flopy.utils.HeadFile(bf_file,text=text, precision=precision) + bf = flopy.utils.HeadFile(bf_file, text=text, precision=precision) else: bf = flopy.utils.HeadFile(bf_file, precision=precision) except Exception as e: @@ -650,33 +776,40 @@ def apply_hds_timeseries(config_file=None, postprocess_inact=None): nlay, nrow, ncol = bf.nlay, bf.nrow, bf.ncol dfs = [] - for site,k,i,j in zip(site_df.site,site_df.k,site_df.i,site_df.j): + for site, k, i, j in zip(site_df.site, site_df.k, site_df.i, site_df.j): assert k >= 0 and k < nlay assert i >= 0 and i < nrow assert j >= 0 and j < ncol if iscbc: - df = pd.DataFrame(data=bf.get_ts((k, i, j), text=text), columns=["totim", site]) + df = pd.DataFrame( + data=bf.get_ts((k, i, j), text=text), columns=["totim", site] + ) else: - df = pd.DataFrame(data=bf.get_ts((k,i,j)),columns=["totim",site]) + df = pd.DataFrame(data=bf.get_ts((k, i, j)), columns=["totim", site]) df.index = df.pop("totim") dfs.append(df) - df = pd.concat(dfs,axis=1).T + df = pd.concat(dfs, axis=1).T if df.shape != df.dropna().shape: - warnings.warn("NANs in processed timeseries file",PyemuWarning) + warnings.warn("NANs in processed timeseries file", PyemuWarning) if fill.upper() != "NONE": fill = float(fill) - df.fillna(fill,inplace=True) - #print(df) - df.to_csv(bf_file+"_timeseries.processed",sep=' ') + df.fillna(fill, inplace=True) + # print(df) + df.to_csv(bf_file + "_timeseries.processed", sep=" ") if postprocess_inact is not None: _apply_postprocess_hds_timeseries(config_file, postprocess_inact) return df -def _setup_postprocess_hds_timeseries(hds_file, df, config_file, prefix=None, model=None): + +def _setup_postprocess_hds_timeseries( + hds_file, df, config_file, prefix=None, model=None +): """Dirty function to setup post processing concentrations in inactive/dry cells""" warnings.warn( "Setting up post processing of hds or ucn timeseries obs. " - "Prepending 'pp' to obs name may cause length to exceed 20 chars", PyemuWarning) + "Prepending 'pp' to obs name may cause length to exceed 20 chars", + PyemuWarning, + ) if model is not None: t_str = df.columns.map(lambda x: x.strftime("%Y%m%d")) else: @@ -685,19 +818,21 @@ def _setup_postprocess_hds_timeseries(hds_file, df, config_file, prefix=None, mo prefix = "pp{0}".format(prefix) else: prefix = "pp" - ins_file = hds_file+"_timeseries.post_processed.ins" + ins_file = hds_file + "_timeseries.post_processed.ins" print("writing instruction file to {0}".format(ins_file)) - with open(ins_file,'w') as f: - f.write('pif ~\n') + with open(ins_file, "w") as f: + f.write("pif ~\n") f.write("l1 \n") for site in df.index: f.write("l1 w ") - #for site in df.columns: + # for site in df.columns: for t in t_str: obsnme = "{0}{1}_{2}".format(prefix, site, t) f.write(" !{0}!".format(obsnme)) - f.write('\n') - frun_line = "pyemu.gw_utils._apply_postprocess_hds_timeseries('{0}')\n".format(config_file) + f.write("\n") + frun_line = "pyemu.gw_utils._apply_postprocess_hds_timeseries('{0}')\n".format( + config_file + ) return frun_line @@ -709,25 +844,33 @@ def _apply_postprocess_hds_timeseries(config_file=None, cinact=1e30): config_file = "hds_timeseries.config" assert os.path.exists(config_file), config_file - with open(config_file,'r') as f: + with open(config_file, "r") as f: line = f.readline() - hds_file,start_datetime,time_units,text,fill,precision,_iscbc = line.strip().split(',') + ( + hds_file, + start_datetime, + time_units, + text, + fill, + precision, + _iscbc, + ) = line.strip().split(",") site_df = pd.read_csv(f) - #print(site_df) + # print(site_df) text = text.upper() assert os.path.exists(hds_file), "head save file not found" if hds_file.lower().endswith(".ucn"): try: - hds = flopy.utils.UcnFile(hds_file,precision=precision) + hds = flopy.utils.UcnFile(hds_file, precision=precision) except Exception as e: raise Exception("error instantiating UcnFile:{0}".format(str(e))) else: try: if text != "NONE": - hds = flopy.utils.HeadFile(hds_file,text=text,precision=precision) + hds = flopy.utils.HeadFile(hds_file, text=text, precision=precision) else: - hds = flopy.utils.HeadFile(hds_file,precision=precision) + hds = flopy.utils.HeadFile(hds_file, precision=precision) except Exception as e: raise Exception("error instantiating HeadFile:{0}".format(str(e))) @@ -745,21 +888,33 @@ def _apply_postprocess_hds_timeseries(config_file=None, cinact=1e30): df.index = df.pop("totim") inact_obs = df[site].apply(lambda x: np.isclose(x, cinact)) if inact_obs.sum() > 0: - assert k+1 < nlay, "Inactive observation in lowest layer" - df_lower = pd.DataFrame(data=hds.get_ts((k+1, i, j)), columns=["totim", site]) + assert k + 1 < nlay, "Inactive observation in lowest layer" + df_lower = pd.DataFrame( + data=hds.get_ts((k + 1, i, j)), columns=["totim", site] + ) df_lower.index = df_lower.pop("totim") df.loc[inact_obs] = df_lower.loc[inact_obs] - print("{0} observation(s) post-processed for site {1} at kij ({2},{3},{4})". - format(inact_obs.sum(), site, k, i, j)) + print( + "{0} observation(s) post-processed for site {1} at kij ({2},{3},{4})".format( + inact_obs.sum(), site, k, i, j + ) + ) dfs.append(df) df = pd.concat(dfs, axis=1).T - #print(df) - df.to_csv(hds_file+"_timeseries.post_processed", sep=' ') + # print(df) + df.to_csv(hds_file + "_timeseries.post_processed", sep=" ") return df -def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds",text="head", precision="single", - include_path=False): +def setup_hds_obs( + hds_file, + kperk_pairs=None, + skip=None, + prefix="hds", + text="head", + precision="single", + include_path=False, +): """a function to setup using all values from a layer-stress period pair for observations. @@ -804,7 +959,7 @@ def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds",text="head", print("error importing flopy, returning {0}".format(str(e))) return - assert os.path.exists(hds_file),"head save file not found" + assert os.path.exists(hds_file), "head save file not found" if hds_file.lower().endswith(".ucn"): try: hds = flopy.utils.UcnFile(hds_file) @@ -812,14 +967,14 @@ def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds",text="head", raise Exception("error instantiating UcnFile:{0}".format(str(e))) else: try: - hds = flopy.utils.HeadFile(hds_file,text=text,precision=precision) + hds = flopy.utils.HeadFile(hds_file, text=text, precision=precision) except Exception as e: raise Exception("error instantiating HeadFile:{0}".format(str(e))) if kperk_pairs is None: kperk_pairs = [] - for kstp,kper in hds.kstpkper: - kperk_pairs.extend([(kper-1,k) for k in range(hds.nlay)]) + for kstp, kper in hds.kstpkper: + kperk_pairs.extend([(kper - 1, k) for k in range(hds.nlay)]) if len(kperk_pairs) == 2: try: if len(kperk_pairs[0]) == 2: @@ -827,68 +982,86 @@ def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds",text="head", except: kperk_pairs = [kperk_pairs] - #if start_datetime is not None: + # if start_datetime is not None: # start_datetime = pd.to_datetime(start_datetime) # dts = start_datetime + pd.to_timedelta(hds.times,unit='d') data = {} - kpers = [kper-1 for kstp,kper in hds.kstpkper] + kpers = [kper - 1 for kstp, kper in hds.kstpkper] for kperk_pair in kperk_pairs: - kper,k = kperk_pair + kper, k = kperk_pair assert kper in kpers, "kper not in hds:{0}".format(kper) assert k in range(hds.nlay), "k not in hds:{0}".format(k) - kstp = last_kstp_from_kper(hds,kper) - d = hds.get_data(kstpkper=(kstp,kper))[k,:,:] + kstp = last_kstp_from_kper(hds, kper) + d = hds.get_data(kstpkper=(kstp, kper))[k, :, :] - data["{0}_{1}".format(kper,k)] = d.flatten() - #data[(kper,k)] = d.flatten() - idx,iidx,jidx = [],[],[] + data["{0}_{1}".format(kper, k)] = d.flatten() + # data[(kper,k)] = d.flatten() + idx, iidx, jidx = [], [], [] for _ in range(len(data)): for i in range(hds.nrow): iidx.extend([i for _ in range(hds.ncol)]) jidx.extend([j for j in range(hds.ncol)]) - idx.extend(["i{0:04d}_j{1:04d}".format(i,j) for j in range(hds.ncol)]) - idx = idx[:hds.nrow*hds.ncol] + idx.extend(["i{0:04d}_j{1:04d}".format(i, j) for j in range(hds.ncol)]) + idx = idx[: hds.nrow * hds.ncol] - df = pd.DataFrame(data,index=idx) + df = pd.DataFrame(data, index=idx) data_cols = list(df.columns) data_cols.sort() - #df.loc[:,"iidx"] = iidx - #df.loc[:,"jidx"] = jidx + # df.loc[:,"iidx"] = iidx + # df.loc[:,"jidx"] = jidx if skip is not None: for col in data_cols: if np.isscalar(skip): - df.loc[df.loc[:,col]==skip,col] = np.NaN + df.loc[df.loc[:, col] == skip, col] = np.NaN elif isinstance(skip, np.ndarray): - assert skip.ndim >= 2, "skip passed as {}D array, At least 2D (<= 4D) array required".format(skip.ndim) - assert skip.shape[-2:] == (hds.nrow, hds.ncol), \ - "Array dimensions of arg. skip needs to match model dimensions ({0},{1}). ({2},{3}) passed".\ - format(hds.nrow, hds.ncol, skip.shape[-2], skip.shape[-1]) + assert ( + skip.ndim >= 2 + ), "skip passed as {}D array, At least 2D (<= 4D) array required".format( + skip.ndim + ) + assert skip.shape[-2:] == ( + hds.nrow, + hds.ncol, + ), "Array dimensions of arg. skip needs to match model dimensions ({0},{1}). ({2},{3}) passed".format( + hds.nrow, hds.ncol, skip.shape[-2], skip.shape[-1] + ) if skip.ndim == 2: - print("2D array passed for skip, assuming constant for all layers and kper") + print( + "2D array passed for skip, assuming constant for all layers and kper" + ) skip = np.tile(skip, (len(kpers), hds.nlay, 1, 1)) if skip.ndim == 3: print("3D array passed for skip, assuming constant for all kper") skip = np.tile(skip, (len(kpers), 1, 1, 1)) - kper, k = [int(c) for c in col.split('_')] - df.loc[df.index.map( - lambda x: skip[kper, k, int(x.split('_')[0].strip('i')), int(x.split('_')[1].strip('j'))] == 0), - col] = np.NaN + kper, k = [int(c) for c in col.split("_")] + df.loc[ + df.index.map( + lambda x: skip[ + kper, + k, + int(x.split("_")[0].strip("i")), + int(x.split("_")[1].strip("j")), + ] + == 0 + ), + col, + ] = np.NaN else: - df.loc[:,col] = df.loc[:,col].apply(skip) + df.loc[:, col] = df.loc[:, col].apply(skip) # melt to long form - df = df.melt(var_name="kperk",value_name="obsval") + df = df.melt(var_name="kperk", value_name="obsval") # set row and col identifies - df.loc[:,"iidx"] = iidx - df.loc[:,"jidx"] = jidx - #drop nans from skip + df.loc[:, "iidx"] = iidx + df.loc[:, "jidx"] = jidx + # drop nans from skip df = df.dropna() - #set some additional identifiers - df.loc[:,"kper"] = df.kperk.apply(lambda x: int(x.split('_')[0])) - df.loc[:,"kidx"] = df.pop("kperk").apply(lambda x: int(x.split('_')[1])) + # set some additional identifiers + df.loc[:, "kper"] = df.kperk.apply(lambda x: int(x.split("_")[0])) + df.loc[:, "kidx"] = df.pop("kperk").apply(lambda x: int(x.split("_")[1])) # form obs names - #def get_kper_str(kper): + # def get_kper_str(kper): # if start_datetime is not None: # return dts[int(kper)].strftime("%Y%m%d") # else: @@ -896,30 +1069,35 @@ def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds",text="head", fmt = prefix + "_{0:02.0f}_{1:03.0f}_{2:03.0f}_{3:03.0f}" # df.loc[:,"obsnme"] = df.apply(lambda x: fmt.format(x.kidx,x.iidx,x.jidx, # get_kper_str(x.kper)),axis=1) - df.loc[:,"obsnme"] = df.apply(lambda x: fmt.format(x.kidx,x.iidx,x.jidx, - x.kper),axis=1) + df.loc[:, "obsnme"] = df.apply( + lambda x: fmt.format(x.kidx, x.iidx, x.jidx, x.kper), axis=1 + ) - df.loc[:,"ins_str"] = df.obsnme.apply(lambda x: "l1 w !{0}!".format(x)) - df.loc[:,"obgnme"] = prefix - #write the instruction file - with open(hds_file+".dat.ins","w") as f: + df.loc[:, "ins_str"] = df.obsnme.apply(lambda x: "l1 w !{0}!".format(x)) + df.loc[:, "obgnme"] = prefix + # write the instruction file + with open(hds_file + ".dat.ins", "w") as f: f.write("pif ~\nl1\n") - df.ins_str.to_string(f,index=False,header=False) + df.ins_str.to_string(f, index=False, header=False) - #write the corresponding output file - df.loc[:,["obsnme","obsval"]].to_csv(hds_file+".dat",sep=' ',index=False) + # write the corresponding output file + df.loc[:, ["obsnme", "obsval"]].to_csv(hds_file + ".dat", sep=" ", index=False) hds_path = os.path.dirname(hds_file) - setup_file = os.path.join(hds_path,"_setup_{0}.csv".format(os.path.split(hds_file)[-1])) + setup_file = os.path.join( + hds_path, "_setup_{0}.csv".format(os.path.split(hds_file)[-1]) + ) df.to_csv(setup_file) if not include_path: hds_file = os.path.split(hds_file)[-1] - fwd_run_line = "pyemu.gw_utils.apply_hds_obs('{0}',precision='{1}',text='{2}')\n".format(hds_file,precision,text) + fwd_run_line = "pyemu.gw_utils.apply_hds_obs('{0}',precision='{1}',text='{2}')\n".format( + hds_file, precision, text + ) df.index = df.obsnme return fwd_run_line, df -def last_kstp_from_kper(hds,kper): +def last_kstp_from_kper(hds, kper): """ function to find the last time step (kstp) for a give stress period (kper) in a modflow head save file. @@ -933,10 +1111,10 @@ def last_kstp_from_kper(hds,kper): kper in the head save file """ - #find the last kstp with this kper + # find the last kstp with this kper kstp = -1 - for kkstp,kkper in hds.kstpkper: - if kkper == kper+1 and kkstp > kstp: + for kkstp, kkper in hds.kstpkper: + if kkper == kper + 1 and kkstp > kstp: kstp = kkstp if kstp == -1: raise Exception("kstp not found for kper {0}".format(kper)) @@ -944,7 +1122,7 @@ def last_kstp_from_kper(hds,kper): return kstp -def apply_hds_obs(hds_file, inact_abs_val=1.0e+20, precision="single",text="head"): +def apply_hds_obs(hds_file, inact_abs_val=1.0e20, precision="single", text="head"): """ process a modflow head save file. A companion function to `gw_utils.setup_hds_obs()` that is called during the forward run process @@ -964,43 +1142,43 @@ def apply_hds_obs(hds_file, inact_abs_val=1.0e+20, precision="single",text="head try: import flopy except Exception as e: - raise Exception("apply_hds_obs(): error importing flopy: {0}".\ - format(str(e))) + raise Exception("apply_hds_obs(): error importing flopy: {0}".format(str(e))) from .. import pst_utils + assert os.path.exists(hds_file) - out_file = hds_file+".dat" + out_file = hds_file + ".dat" ins_file = out_file + ".ins" assert os.path.exists(ins_file) - df = pd.DataFrame({"obsnme":pst_utils.parse_ins_file(ins_file)}) + df = pd.DataFrame({"obsnme": pst_utils.parse_ins_file(ins_file)}) df.index = df.obsnme # populate metdata - items = ["k","i","j","kper"] - for i,item in enumerate(items): - df.loc[:,item] = df.obsnme.apply(lambda x: int(x.split('_')[i+1])) + items = ["k", "i", "j", "kper"] + for i, item in enumerate(items): + df.loc[:, item] = df.obsnme.apply(lambda x: int(x.split("_")[i + 1])) - if hds_file.lower().endswith('ucn'): + if hds_file.lower().endswith("ucn"): hds = flopy.utils.UcnFile(hds_file) else: - hds = flopy.utils.HeadFile(hds_file, precision=precision,text=text) + hds = flopy.utils.HeadFile(hds_file, precision=precision, text=text) kpers = df.kper.unique() - df.loc[:,"obsval"] = np.NaN + df.loc[:, "obsval"] = np.NaN for kper in kpers: - kstp = last_kstp_from_kper(hds,kper) - data = hds.get_data(kstpkper=(kstp,kper)) - #jwhite 15jan2018 fix for really large values that are getting some - #trash added to them... + kstp = last_kstp_from_kper(hds, kper) + data = hds.get_data(kstpkper=(kstp, kper)) + # jwhite 15jan2018 fix for really large values that are getting some + # trash added to them... data[np.isnan(data)] = 0.0 - data[data>np.abs(inact_abs_val)] = np.abs(inact_abs_val) - data[data<-np.abs(inact_abs_val)] = -np.abs(inact_abs_val) - df_kper = df.loc[df.kper==kper,:] - df.loc[df_kper.index,"obsval"] = data[df_kper.k,df_kper.i,df_kper.j] + data[data > np.abs(inact_abs_val)] = np.abs(inact_abs_val) + data[data < -np.abs(inact_abs_val)] = -np.abs(inact_abs_val) + df_kper = df.loc[df.kper == kper, :] + df.loc[df_kper.index, "obsval"] = data[df_kper.k, df_kper.i, df_kper.j] assert df.dropna().shape[0] == df.shape[0] - df.loc[:,["obsnme","obsval"]].to_csv(out_file,index=False,sep=" ") + df.loc[:, ["obsnme", "obsval"]].to_csv(out_file, index=False, sep=" ") return df -def setup_sft_obs(sft_file,ins_file=None,start_datetime=None,times=None,ncomp=1): +def setup_sft_obs(sft_file, ins_file=None, start_datetime=None, times=None, ncomp=1): """writes a post-processor and instruction file for a mt3d-usgs sft output file Args: @@ -1024,8 +1202,8 @@ def setup_sft_obs(sft_file,ins_file=None,start_datetime=None,times=None,ncomp=1) """ - df = pd.read_csv(sft_file,skiprows=1,delim_whitespace=True) - df.columns = [c.lower().replace("-","_") for c in df.columns] + df = pd.read_csv(sft_file, skiprows=1, delim_whitespace=True) + df.columns = [c.lower().replace("-", "_") for c in df.columns] if times is None: times = df.time.unique() missing = [] @@ -1035,52 +1213,58 @@ def setup_sft_obs(sft_file,ins_file=None,start_datetime=None,times=None,ncomp=1) missing.append(str(t)) if len(missing) > 0: print(df.time) - raise Exception("the following times are missing:{0}".format(','.join(missing))) - with open("sft_obs.config",'w') as f: - f.write(sft_file+'\n') + raise Exception("the following times are missing:{0}".format(",".join(missing))) + with open("sft_obs.config", "w") as f: + f.write(sft_file + "\n") [f.write("{0:15.6E}\n".format(t)) for t in times] df = apply_sft_obs() utimes = df.time.unique() for t in times: - assert t in utimes,"time {0} missing in processed dataframe".format(t) + assert t in utimes, "time {0} missing in processed dataframe".format(t) idx = df.time.apply(lambda x: x in times) if start_datetime is not None: start_datetime = pd.to_datetime(start_datetime) - df.loc[:,"time_str"] = pd.to_timedelta(df.time,unit='d') + start_datetime - df.loc[:,"time_str"] = df.time_str.apply(lambda x: datetime.strftime(x,"%Y%m%d")) + df.loc[:, "time_str"] = pd.to_timedelta(df.time, unit="d") + start_datetime + df.loc[:, "time_str"] = df.time_str.apply( + lambda x: datetime.strftime(x, "%Y%m%d") + ) else: - df.loc[:,"time_str"] = df.time.apply(lambda x: "{0:08.2f}".format(x)) - df.loc[:,"ins_str"] = "l1\n" + df.loc[:, "time_str"] = df.time.apply(lambda x: "{0:08.2f}".format(x)) + df.loc[:, "ins_str"] = "l1\n" # check for multiple components - df_times = df.loc[idx,:] - df.loc[:,"icomp"] = 1 + df_times = df.loc[idx, :] + df.loc[:, "icomp"] = 1 icomp_idx = list(df.columns).index("icomp") for t in times: - df_time = df.loc[df.time==t,:] + df_time = df.loc[df.time == t, :] vc = df_time.sfr_node.value_counts() ncomp = vc.max() - assert np.all(vc.values==ncomp) + assert np.all(vc.values == ncomp) nstrm = df_time.shape[0] / ncomp for icomp in range(ncomp): - s = int(nstrm*(icomp)) - e = int(nstrm*(icomp+1)) - idxs = df_time.iloc[s:e,:].index - #df_time.iloc[nstrm*(icomp):nstrm*(icomp+1),icomp_idx.loc["icomp"] = int(icomp+1) - df_time.loc[idxs,"icomp"] = int(icomp+1) + s = int(nstrm * (icomp)) + e = int(nstrm * (icomp + 1)) + idxs = df_time.iloc[s:e, :].index + # df_time.iloc[nstrm*(icomp):nstrm*(icomp+1),icomp_idx.loc["icomp"] = int(icomp+1) + df_time.loc[idxs, "icomp"] = int(icomp + 1) - #df.loc[df_time.index,"ins_str"] = df_time.apply(lambda x: "l1 w w !sfrc{0}_{1}_{2}! !swgw{0}_{1}_{2}! !gwcn{0}_{1}_{2}!\n".\ + # df.loc[df_time.index,"ins_str"] = df_time.apply(lambda x: "l1 w w !sfrc{0}_{1}_{2}! !swgw{0}_{1}_{2}! !gwcn{0}_{1}_{2}!\n".\ # format(x.sfr_node,x.icomp,x.time_str),axis=1) df.loc[df_time.index, "ins_str"] = df_time.apply( - lambda x: "l1 w w !sfrc{0}_{1}_{2}!\n".format(x.sfr_node, x.icomp, x.time_str), axis=1) + lambda x: "l1 w w !sfrc{0}_{1}_{2}!\n".format( + x.sfr_node, x.icomp, x.time_str + ), + axis=1, + ) df.index = np.arange(df.shape[0]) if ins_file is None: - ins_file = sft_file+".processed.ins" + ins_file = sft_file + ".processed.ins" - with open(ins_file,'w') as f: + with open(ins_file, "w") as f: f.write("pif ~\nl1\n") [f.write(i) for i in df.ins_str] - #df = try_process_ins_file(ins_file,sft_file+".processed") - df = try_process_output_file(ins_file,sft_file+".processed") + # df = try_process_ins_file(ins_file,sft_file+".processed") + df = try_process_output_file(ins_file, sft_file + ".processed") return df @@ -1107,26 +1291,27 @@ def try_cast(x): sft_file = f.readline().strip() for line in f: times.append(float(line.strip())) - df = pd.read_csv(sft_file,skiprows=1,delim_whitespace=True)#,nrows=10000000) + df = pd.read_csv(sft_file, skiprows=1, delim_whitespace=True) # ,nrows=10000000) df.columns = [c.lower().replace("-", "_") for c in df.columns] df = df.loc[df.time.apply(lambda x: x in times), :] - #print(df.dtypes) - #normalize + # print(df.dtypes) + # normalize for c in df.columns: - #print(c) + # print(c) if not "node" in c: - df.loc[:,c] = df.loc[:,c].apply(try_cast) - #print(df.loc[df.loc[:,c].apply(lambda x : type(x) == str),:]) - df.loc[df.loc[:,c].apply(lambda x: x<1e-30),c] = 0.0 - df.loc[df.loc[:, c] > 1e+30, c] = 1.0e+30 - df.loc[:,"sfr_node"] = df.sfr_node.apply(np.int) + df.loc[:, c] = df.loc[:, c].apply(try_cast) + # print(df.loc[df.loc[:,c].apply(lambda x : type(x) == str),:]) + df.loc[df.loc[:, c].apply(lambda x: x < 1e-30), c] = 0.0 + df.loc[df.loc[:, c] > 1e30, c] = 1.0e30 + df.loc[:, "sfr_node"] = df.sfr_node.apply(np.int) - df.to_csv(sft_file+".processed",sep=' ',index=False) + df.to_csv(sft_file + ".processed", sep=" ", index=False) return df -def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, - tie_hcond=True, include_temporal_pars=None): +def setup_sfr_seg_parameters( + nam_file, model_ws=".", par_cols=None, tie_hcond=True, include_temporal_pars=None +): """Setup multiplier parameters for SFR segment data. Args: @@ -1167,22 +1352,23 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, if tie_hcond: if "hcond1" not in par_cols or "hcond2" not in par_cols: tie_hcond = False - - if isinstance(nam_file,flopy.modflow.mf.Modflow) and nam_file.sfr is not None: + + if isinstance(nam_file, flopy.modflow.mf.Modflow) and nam_file.sfr is not None: m = nam_file nam_file = m.namefile model_ws = m.model_ws else: # load MODFLOW model # is this needed? could we just pass the model if it has already been read in? - m = flopy.modflow.Modflow.load(nam_file,load_only=["sfr"],model_ws=model_ws,check=False,forgive=False) + m = flopy.modflow.Modflow.load( + nam_file, load_only=["sfr"], model_ws=model_ws, check=False, forgive=False + ) if include_temporal_pars: if include_temporal_pars is True: tmp_par_cols = {col: range(m.dis.nper) for col in par_cols} elif isinstance(include_temporal_pars, str): tmp_par_cols = {include_temporal_pars: range(m.dis.nper)} elif isinstance(include_temporal_pars, list): - tmp_par_cols = {col: range(m.dis.nper) - for col in include_temporal_pars} + tmp_par_cols = {col: range(m.dis.nper) for col in include_temporal_pars} elif isinstance(include_temporal_pars, dict): tmp_par_cols = include_temporal_pars include_temporal_pars = True @@ -1190,25 +1376,34 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, tmp_par_cols = {} include_temporal_pars = False - #make backup copy of sfr file - shutil.copy(os.path.join(model_ws,m.sfr.file_name[0]),os.path.join(model_ws,nam_file+"_backup_.sfr")) + # make backup copy of sfr file + shutil.copy( + os.path.join(model_ws, m.sfr.file_name[0]), + os.path.join(model_ws, nam_file + "_backup_.sfr"), + ) - #get the segment data (dict) + # get the segment data (dict) segment_data = m.sfr.segment_data shape = segment_data[list(segment_data.keys())[0]].shape # check - for kper,seg_data in m.sfr.segment_data.items(): - assert seg_data.shape == shape,"cannot use: seg data must have the same number of entires for all kpers" + for kper, seg_data in m.sfr.segment_data.items(): + assert ( + seg_data.shape == shape + ), "cannot use: seg data must have the same number of entires for all kpers" seg_data_col_order = list(seg_data.dtype.names) # convert segment_data dictionary to multi index df - this could get ugly - reform = {(k, c): segment_data[k][c] for k in segment_data.keys() for c in segment_data[k].dtype.names} + reform = { + (k, c): segment_data[k][c] + for k in segment_data.keys() + for c in segment_data[k].dtype.names + } seg_data_all_kper = pd.DataFrame.from_dict(reform) - seg_data_all_kper.columns.names = ['kper', 'col'] + seg_data_all_kper.columns.names = ["kper", "col"] # extract the first seg data kper to a dataframe - seg_data = seg_data_all_kper[0].copy() # pd.DataFrame.from_records(seg_data) + seg_data = seg_data_all_kper[0].copy() # pd.DataFrame.from_records(seg_data) - #make sure all par cols are found and search of any data in kpers + # make sure all par cols are found and search of any data in kpers missing = [] cols = par_cols.copy() for par_col in set(par_cols + list(tmp_par_cols.keys())): @@ -1219,37 +1414,52 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, _ = tmp_par_cols.pop(par_col) # look across all kper in multiindex df to check for values entry - fill with absmax should capture entries else: - seg_data.loc[:, par_col] = seg_data_all_kper.loc[:, (slice(None), par_col)].abs().max(level=1, axis=1) + seg_data.loc[:, par_col] = ( + seg_data_all_kper.loc[:, (slice(None), par_col)] + .abs() + .max(level=1, axis=1) + ) if len(missing) > 0: - warnings.warn("the following par_cols were not found in segment data: {0}".format(','.join(missing)), PyemuWarning) + warnings.warn( + "the following par_cols were not found in segment data: {0}".format( + ",".join(missing) + ), + PyemuWarning, + ) if len(missing) >= len(par_cols): - warnings.warn("None of the passed par_cols ({0}) were found in segment data.". - format(','.join(par_cols)), PyemuWarning) + warnings.warn( + "None of the passed par_cols ({0}) were found in segment data.".format( + ",".join(par_cols) + ), + PyemuWarning, + ) seg_data = seg_data[seg_data_col_order] # reset column orders to inital seg_data_org = seg_data.copy() - seg_data.to_csv(os.path.join(model_ws, "sfr_seg_pars.dat"), sep=',') + seg_data.to_csv(os.path.join(model_ws, "sfr_seg_pars.dat"), sep=",") - #the data cols not to parameterize + # the data cols not to parameterize # better than a column indexer as pandas can change column orders - idx_cols=['nseg', 'icalc', 'outseg', 'iupseg', 'iprior', 'nstrpts'] - notpar_cols = [c for c in seg_data.columns if c not in cols+idx_cols] + idx_cols = ["nseg", "icalc", "outseg", "iupseg", "iprior", "nstrpts"] + notpar_cols = [c for c in seg_data.columns if c not in cols + idx_cols] - #process par cols + # process par cols tpl_str, pvals = [], [] if include_temporal_pars: tmp_pnames, tmp_tpl_str = [], [] - tmp_df = pd.DataFrame(data={c: 1.0 for c in tmp_par_cols.keys()}, - index=list(m.sfr.segment_data.keys())) + tmp_df = pd.DataFrame( + data={c: 1.0 for c in tmp_par_cols.keys()}, + index=list(m.sfr.segment_data.keys()), + ) tmp_df.sort_index(inplace=True) tmp_df.to_csv(os.path.join(model_ws, "sfr_seg_temporal_pars.dat")) for par_col in set(cols + list(tmp_par_cols.keys())): print(par_col) prefix = par_col - if tie_hcond and par_col == 'hcond2': - prefix = 'hcond1' + if tie_hcond and par_col == "hcond2": + prefix = "hcond1" if seg_data.loc[:, par_col].sum() == 0.0: print("all zeros for {0}...skipping...".format(par_col)) - #seg_data.loc[:,par_col] = 1 + # seg_data.loc[:,par_col] = 1 # all zero so no need to set up if par_col in cols: # - add to notpar @@ -1257,9 +1467,12 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, if par_col in tmp_par_cols.keys(): _ = tmp_par_cols.pop(par_col) if par_col in cols: - seg_data.loc[:, par_col] = seg_data.apply(lambda x: "~ {0}_{1:04d} ~". - format(prefix, int(x.nseg)) if float(x[par_col]) != 0.0 - else "1.0", axis=1) + seg_data.loc[:, par_col] = seg_data.apply( + lambda x: "~ {0}_{1:04d} ~".format(prefix, int(x.nseg)) + if float(x[par_col]) != 0.0 + else "1.0", + axis=1, + ) org_vals = seg_data_org.loc[seg_data_org.loc[:, par_col] != 0.0, par_col] pnames = seg_data.loc[org_vals.index, par_col] pvals.extend(list(org_vals.values)) @@ -1267,51 +1480,64 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, if par_col in tmp_par_cols.keys(): parnme = tmp_df.index.map( lambda x: "{0}_{1:04d}_tmp".format(par_col, int(x)) - if x in tmp_par_cols[par_col] else 1.0) + if x in tmp_par_cols[par_col] + else 1.0 + ) sel = parnme != 1.0 - tmp_df.loc[sel, par_col] = parnme[sel].map( - lambda x: "~ {0} ~".format(x)) + tmp_df.loc[sel, par_col] = parnme[sel].map(lambda x: "~ {0} ~".format(x)) tmp_tpl_str.extend(list(tmp_df.loc[sel, par_col].values)) tmp_pnames.extend(list(parnme[sel].values)) - pnames = [t.replace('~','').strip() for t in tpl_str] - df = pd.DataFrame({"parnme":pnames,"org_value":pvals,"tpl_str":tpl_str},index=pnames) + pnames = [t.replace("~", "").strip() for t in tpl_str] + df = pd.DataFrame( + {"parnme": pnames, "org_value": pvals, "tpl_str": tpl_str}, index=pnames + ) df.drop_duplicates(inplace=True) if df.empty: - warnings.warn("No spatial sfr segment parameters have been set up, " - "either none of {0} were found or all were zero.". - format(','.join(par_cols)), PyemuWarning) + warnings.warn( + "No spatial sfr segment parameters have been set up, " + "either none of {0} were found or all were zero.".format( + ",".join(par_cols) + ), + PyemuWarning, + ) # return df # set not par cols to 1.0 seg_data.loc[:, notpar_cols] = "1.0" - #write the template file - _write_df_tpl(os.path.join(model_ws, "sfr_seg_pars.dat.tpl"), seg_data, sep=',') + # write the template file + _write_df_tpl(os.path.join(model_ws, "sfr_seg_pars.dat.tpl"), seg_data, sep=",") - #make sure the tpl file exists and has the same num of pars - parnme = parse_tpl_file(os.path.join(model_ws,"sfr_seg_pars.dat.tpl")) + # make sure the tpl file exists and has the same num of pars + parnme = parse_tpl_file(os.path.join(model_ws, "sfr_seg_pars.dat.tpl")) assert len(parnme) == df.shape[0] - #set some useful par info - df["pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) + # set some useful par info + df["pargp"] = df.parnme.apply(lambda x: x.split("_")[0]) if include_temporal_pars: - _write_df_tpl(filename=os.path.join(model_ws, "sfr_seg_temporal_pars.dat.tpl"), df=tmp_df) - pargp = [pname.split('_')[0]+"_tmp" for pname in tmp_pnames] - tmp_df = pd.DataFrame(data={"parnme":tmp_pnames,"pargp":pargp},index=tmp_pnames) + _write_df_tpl( + filename=os.path.join(model_ws, "sfr_seg_temporal_pars.dat.tpl"), df=tmp_df + ) + pargp = [pname.split("_")[0] + "_tmp" for pname in tmp_pnames] + tmp_df = pd.DataFrame( + data={"parnme": tmp_pnames, "pargp": pargp}, index=tmp_pnames + ) if not tmp_df.empty: - tmp_df.loc[:,"org_value"] = 1.0 - tmp_df.loc[:,"tpl_str"] = tmp_tpl_str + tmp_df.loc[:, "org_value"] = 1.0 + tmp_df.loc[:, "tpl_str"] = tmp_tpl_str df = df.append(tmp_df[df.columns]) if df.empty: - warnings.warn("No sfr segment parameters have been set up, " - "either none of {0} were found or all were zero.". - format(','.join(set(par_cols + - list(tmp_par_cols.keys())))), - PyemuWarning) + warnings.warn( + "No sfr segment parameters have been set up, " + "either none of {0} were found or all were zero.".format( + ",".join(set(par_cols + list(tmp_par_cols.keys()))) + ), + PyemuWarning, + ) return df # write the config file used by apply_sfr_pars() - with open(os.path.join(model_ws, "sfr_seg_pars.config"), 'w') as f: + with open(os.path.join(model_ws, "sfr_seg_pars.config"), "w") as f: f.write("nam_file {0}\n".format(nam_file)) f.write("model_ws {0}\n".format(model_ws)) f.write("mult_file sfr_seg_pars.dat\n") @@ -1329,7 +1555,7 @@ def setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=None, return df -def setup_sfr_reach_parameters(nam_file,model_ws='.', par_cols=['strhc1']): +def setup_sfr_reach_parameters(nam_file, model_ws=".", par_cols=["strhc1"]): """Setup multiplier paramters for reach data, when reachinput option is specififed in sfr. @@ -1363,28 +1589,30 @@ def setup_sfr_reach_parameters(nam_file,model_ws='.', par_cols=['strhc1']): except Exception as e: return if par_cols is None: - par_cols = ['strhc1'] - if isinstance(nam_file,flopy.modflow.mf.Modflow) and nam_file.sfr is not None: + par_cols = ["strhc1"] + if isinstance(nam_file, flopy.modflow.mf.Modflow) and nam_file.sfr is not None: # flopy MODFLOW model has been passed and has SFR loaded m = nam_file nam_file = m.namefile model_ws = m.model_ws else: # if model has not been passed or SFR not loaded # load MODFLOW model - m = flopy.modflow.Modflow.load(nam_file, load_only=["sfr"], model_ws=model_ws, check=False, forgive=False) + m = flopy.modflow.Modflow.load( + nam_file, load_only=["sfr"], model_ws=model_ws, check=False, forgive=False + ) # get reachdata as dataframe reach_data = pd.DataFrame.from_records(m.sfr.reach_data) # write inital reach_data as csv reach_data_orig = reach_data.copy() - reach_data.to_csv(os.path.join(m.model_ws, "sfr_reach_pars.dat"), sep=',') + reach_data.to_csv(os.path.join(m.model_ws, "sfr_reach_pars.dat"), sep=",") # generate template file with pars in par_cols - #process par cols - tpl_str,pvals = [],[] + # process par cols + tpl_str, pvals = [], [] # par_cols=["strhc1"] - idx_cols=["node", "k", "i", "j", "iseg", "ireach", "reachID", "outreach"] - #the data cols not to parameterize - notpar_cols = [c for c in reach_data.columns if c not in par_cols+idx_cols] + idx_cols = ["node", "k", "i", "j", "iseg", "ireach", "reachID", "outreach"] + # the data cols not to parameterize + notpar_cols = [c for c in reach_data.columns if c not in par_cols + idx_cols] # make sure all par cols are found and search of any data in kpers missing = [] cols = par_cols.copy() @@ -1393,38 +1621,57 @@ def setup_sfr_reach_parameters(nam_file,model_ws='.', par_cols=['strhc1']): missing.append(par_col) cols.remove(par_col) if len(missing) > 0: - warnings.warn("the following par_cols were not found in reach data: {0}".format(','.join(missing)), - PyemuWarning) + warnings.warn( + "the following par_cols were not found in reach data: {0}".format( + ",".join(missing) + ), + PyemuWarning, + ) if len(missing) >= len(par_cols): - warnings.warn("None of the passed par_cols ({0}) were found in reach data.". - format(','.join(par_cols)), PyemuWarning) + warnings.warn( + "None of the passed par_cols ({0}) were found in reach data.".format( + ",".join(par_cols) + ), + PyemuWarning, + ) for par_col in cols: if par_col == "strhc1": - prefix = 'strk' # shorten par + prefix = "strk" # shorten par else: prefix = par_col reach_data.loc[:, par_col] = reach_data.apply( lambda x: "~ {0}_{1:04d} ~".format(prefix, int(x.reachID)) - if float(x[par_col]) != 0.0 else "1.0", axis=1) + if float(x[par_col]) != 0.0 + else "1.0", + axis=1, + ) org_vals = reach_data_orig.loc[reach_data_orig.loc[:, par_col] != 0.0, par_col] pnames = reach_data.loc[org_vals.index, par_col] pvals.extend(list(org_vals.values)) tpl_str.extend(list(pnames.values)) - pnames = [t.replace('~','').strip() for t in tpl_str] - df = pd.DataFrame({"parnme":pnames,"org_value":pvals,"tpl_str":tpl_str},index=pnames) + pnames = [t.replace("~", "").strip() for t in tpl_str] + df = pd.DataFrame( + {"parnme": pnames, "org_value": pvals, "tpl_str": tpl_str}, index=pnames + ) df.drop_duplicates(inplace=True) if df.empty: - warnings.warn("No sfr reach parameters have been set up, either none of {0} were found or all were zero.". - format(','.join(par_cols)), PyemuWarning) + warnings.warn( + "No sfr reach parameters have been set up, either none of {0} were found or all were zero.".format( + ",".join(par_cols) + ), + PyemuWarning, + ) else: # set not par cols to 1.0 reach_data.loc[:, notpar_cols] = "1.0" # write the template file - _write_df_tpl(os.path.join(model_ws, "sfr_reach_pars.dat.tpl"), reach_data, sep=',') + _write_df_tpl( + os.path.join(model_ws, "sfr_reach_pars.dat.tpl"), reach_data, sep="," + ) # write the config file used by apply_sfr_pars() - with open(os.path.join(model_ws, "sfr_reach_pars.config"), 'w') as f: + with open(os.path.join(model_ws, "sfr_reach_pars.config"), "w") as f: f.write("nam_file {0}\n".format(nam_file)) f.write("model_ws {0}\n".format(model_ws)) f.write("mult_file sfr_reach_pars.dat\n") @@ -1435,7 +1682,7 @@ def setup_sfr_reach_parameters(nam_file,model_ws='.', par_cols=['strhc1']): assert len(parnme) == df.shape[0] # set some useful par info - df.loc[:, "pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) + df.loc[:, "pargp"] = df.parnme.apply(lambda x: x.split("_")[0]) df.loc[:, "parubnd"] = 1.25 df.loc[:, "parlbnd"] = 0.75 hpars = df.loc[df.pargp.apply(lambda x: x.startswith("strk")), "parnme"] @@ -1466,23 +1713,26 @@ def apply_sfr_seg_parameters(seg_pars=True, reach_pars=False): """ if not seg_pars and not reach_pars: - raise Exception("gw_utils.apply_sfr_pars() error: both seg_pars and reach_pars are False") - #if seg_pars and reach_pars: + raise Exception( + "gw_utils.apply_sfr_pars() error: both seg_pars and reach_pars are False" + ) + # if seg_pars and reach_pars: # raise Exception("gw_utils.apply_sfr_pars() error: both seg_pars and reach_pars are True") import flopy - bak_sfr_file,pars = None,None + + bak_sfr_file, pars = None, None if seg_pars: assert os.path.exists("sfr_seg_pars.config") - with open("sfr_seg_pars.config",'r') as f: + with open("sfr_seg_pars.config", "r") as f: pars = {} for line in f: line = line.strip().split() pars[line[0]] = line[1] - bak_sfr_file = pars["nam_file"]+"_backup_.sfr" - #m = flopy.modflow.Modflow.load(pars["nam_file"],model_ws=pars["model_ws"],load_only=["sfr"],check=False) + bak_sfr_file = pars["nam_file"] + "_backup_.sfr" + # m = flopy.modflow.Modflow.load(pars["nam_file"],model_ws=pars["model_ws"],load_only=["sfr"],check=False) m = flopy.modflow.Modflow.load(pars["nam_file"], load_only=[], check=False) sfr = flopy.modflow.ModflowSfr2.load(os.path.join(bak_sfr_file), m) sfrfile = pars["sfr_filename"] @@ -1492,7 +1742,7 @@ def apply_sfr_seg_parameters(seg_pars=True, reach_pars=False): # time_mult_file = pars["time_mult_file"] # time_mlt_df = pd.read_csv(pars["time_mult_file"], delim_whitespace=False,index_col=0) - idx_cols = ['nseg', 'icalc', 'outseg', 'iupseg', 'iprior', 'nstrpts'] + idx_cols = ["nseg", "icalc", "outseg", "iupseg", "iprior", "nstrpts"] present_cols = [c for c in idx_cols if c in mlt_df.columns] mlt_cols = mlt_df.columns.drop(present_cols) for key, val in m.sfr.segment_data.items(): @@ -1502,38 +1752,42 @@ def apply_sfr_seg_parameters(seg_pars=True, reach_pars=False): sfr.segment_data[key] = val if reach_pars: assert os.path.exists("sfr_reach_pars.config") - with open("sfr_reach_pars.config", 'r') as f: + with open("sfr_reach_pars.config", "r") as f: r_pars = {} for line in f: line = line.strip().split() r_pars[line[0]] = line[1] if bak_sfr_file is None: # will be the case is seg_pars is false - bak_sfr_file = r_pars["nam_file"]+"_backup_.sfr" - #m = flopy.modflow.Modflow.load(pars["nam_file"],model_ws=pars["model_ws"],load_only=["sfr"],check=False) - m = flopy.modflow.Modflow.load(r_pars["nam_file"], load_only=[], check=False) + bak_sfr_file = r_pars["nam_file"] + "_backup_.sfr" + # m = flopy.modflow.Modflow.load(pars["nam_file"],model_ws=pars["model_ws"],load_only=["sfr"],check=False) + m = flopy.modflow.Modflow.load( + r_pars["nam_file"], load_only=[], check=False + ) sfr = flopy.modflow.ModflowSfr2.load(os.path.join(bak_sfr_file), m) sfrfile = r_pars["sfr_filename"] - r_mlt_df = pd.read_csv(r_pars["mult_file"],sep=',',index_col=0) + r_mlt_df = pd.read_csv(r_pars["mult_file"], sep=",", index_col=0) r_idx_cols = ["node", "k", "i", "j", "iseg", "ireach", "reachID", "outreach"] r_mlt_cols = r_mlt_df.columns.drop(r_idx_cols) r_df = pd.DataFrame.from_records(m.sfr.reach_data) r_df.loc[:, r_mlt_cols] *= r_mlt_df.loc[:, r_mlt_cols] sfr.reach_data = r_df.to_records(index=False) - - #m.remove_package("sfr") + # m.remove_package("sfr") if pars is not None and "time_mult_file" in pars: time_mult_file = pars["time_mult_file"] time_mlt_df = pd.read_csv(time_mult_file, delim_whitespace=False, index_col=0) for kper, sdata in m.sfr.segment_data.items(): - assert kper in time_mlt_df.index, "gw_utils.apply_sfr_seg_parameters() error: kper " + \ - "{0} not in time_mlt_df index".format(kper) + assert kper in time_mlt_df.index, ( + "gw_utils.apply_sfr_seg_parameters() error: kper " + + "{0} not in time_mlt_df index".format(kper) + ) for col in time_mlt_df.columns: sdata[col] *= time_mlt_df.loc[kper, col] sfr.write_file(filename=sfrfile) return sfr + def apply_sfr_parameters(seg_pars=True, reach_pars=False): """thin wrapper around `gw_utils.apply_sfr_seg_parameters()` @@ -1557,8 +1811,9 @@ def apply_sfr_parameters(seg_pars=True, reach_pars=False): return sfr -def setup_sfr_obs(sfr_out_file,seg_group_dict=None,ins_file=None,model=None, - include_path=False): +def setup_sfr_obs( + sfr_out_file, seg_group_dict=None, ins_file=None, model=None, include_path=False +): """setup observations using the sfr ASCII output file. Setups the ability to aggregate flows for groups of segments. Applies only flow to aquier and flow out. @@ -1591,16 +1846,18 @@ def setup_sfr_obs(sfr_out_file,seg_group_dict=None,ins_file=None,model=None, kpers.sort() if seg_group_dict is None: - seg_group_dict = {"seg{0:04d}".format(s):s for s in sfr_dict[kpers[0]].segment} + seg_group_dict = {"seg{0:04d}".format(s): s for s in sfr_dict[kpers[0]].segment} else: - warnings.warn("Flow out (flout) of grouped segments will be aggregated... ", PyemuWarning) + warnings.warn( + "Flow out (flout) of grouped segments will be aggregated... ", PyemuWarning + ) sfr_segs = set(sfr_dict[list(sfr_dict.keys())[0]].segment) keys = ["sfr_out_file"] if include_path: values = [os.path.split(sfr_out_file)[-1]] else: values = [sfr_out_file] - for oname,segs in seg_group_dict.items(): + for oname, segs in seg_group_dict.items(): if np.isscalar(segs): segs_set = {segs} segs = [segs] @@ -1608,22 +1865,25 @@ def setup_sfr_obs(sfr_out_file,seg_group_dict=None,ins_file=None,model=None, segs_set = set(segs) diff = segs_set.difference(sfr_segs) if len(diff) > 0: - raise Exception("the following segs listed with oname {0} where not found: {1}". - format(oname,','.join([str(s) for s in diff]))) + raise Exception( + "the following segs listed with oname {0} where not found: {1}".format( + oname, ",".join([str(s) for s in diff]) + ) + ) for seg in segs: keys.append(oname) values.append(seg) - df_key = pd.DataFrame({"obs_base":keys,"segment":values}) + df_key = pd.DataFrame({"obs_base": keys, "segment": values}) if include_path: pth = os.path.join(*[p for p in os.path.split(sfr_out_file)[:-1]]) - config_file = os.path.join(pth,"sfr_obs.config") + config_file = os.path.join(pth, "sfr_obs.config") else: config_file = "sfr_obs.config" print("writing 'sfr_obs.config' to {0}".format(config_file)) df_key.to_csv(config_file) - bd = '.' + bd = "." if include_path: bd = os.getcwd() os.chdir(pth) @@ -1634,34 +1894,45 @@ def setup_sfr_obs(sfr_out_file,seg_group_dict=None,ins_file=None,model=None, raise Exception("error in apply_sfr_obs(): {0}".format(str(e))) os.chdir(bd) if model is not None: - dts = (pd.to_datetime(model.start_datetime) + pd.to_timedelta(np.cumsum(model.dis.perlen.array),unit='d')).date - df.loc[:,"datetime"] = df.kper.apply(lambda x: dts[x]) - df.loc[:,"time_str"] = df.datetime.apply(lambda x: x.strftime("%Y%m%d")) + dts = ( + pd.to_datetime(model.start_datetime) + + pd.to_timedelta(np.cumsum(model.dis.perlen.array), unit="d") + ).date + df.loc[:, "datetime"] = df.kper.apply(lambda x: dts[x]) + df.loc[:, "time_str"] = df.datetime.apply(lambda x: x.strftime("%Y%m%d")) else: - df.loc[:,"time_str"] = df.kper.apply(lambda x: "{0:04d}".format(x)) - df.loc[:,"flaqx_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fa",x.obs_base,x.time_str),axis=1) - df.loc[:,"flout_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fo",x.obs_base,x.time_str),axis=1) + df.loc[:, "time_str"] = df.kper.apply(lambda x: "{0:04d}".format(x)) + df.loc[:, "flaqx_obsnme"] = df.apply( + lambda x: "{0}_{1}_{2}".format("fa", x.obs_base, x.time_str), axis=1 + ) + df.loc[:, "flout_obsnme"] = df.apply( + lambda x: "{0}_{1}_{2}".format("fo", x.obs_base, x.time_str), axis=1 + ) if ins_file is None: ins_file = sfr_out_file + ".processed.ins" - with open(ins_file,'w') as f: + with open(ins_file, "w") as f: f.write("pif ~\nl1\n") - for fla,flo in zip(df.flaqx_obsnme,df.flout_obsnme): - f.write("l1 w w !{0}! !{1}!\n".format(fla,flo)) + for fla, flo in zip(df.flaqx_obsnme, df.flout_obsnme): + f.write("l1 w w !{0}! !{1}!\n".format(fla, flo)) df = None pth = os.path.split(ins_file)[:-1] pth = os.path.join(*pth) - if pth == '': - pth = '.' + if pth == "": + pth = "." bd = os.getcwd() os.chdir(pth) - df = try_process_output_file(os.path.split(ins_file)[-1],os.path.split(sfr_out_file+".processed")[-1]) + df = try_process_output_file( + os.path.split(ins_file)[-1], os.path.split(sfr_out_file + ".processed")[-1] + ) os.chdir(bd) if df is not None: - df.loc[:,"obsnme"] = df.index.values - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: "flaqx" if x.startswith("fa") else "flout") + df.loc[:, "obsnme"] = df.index.values + df.loc[:, "obgnme"] = df.obsnme.apply( + lambda x: "flaqx" if x.startswith("fa") else "flout" + ) return df @@ -1679,11 +1950,11 @@ def apply_sfr_obs(): **pandas.DataFrame**: a dataframe of aggregrated sfr segment aquifer and outflow """ assert os.path.exists("sfr_obs.config") - df_key = pd.read_csv("sfr_obs.config",index_col=0) + df_key = pd.read_csv("sfr_obs.config", index_col=0) - assert df_key.iloc[0,0] == "sfr_out_file",df_key.iloc[0,:] - sfr_out_file = df_key.iloc[0,1] - df_key = df_key.iloc[1:,:] + assert df_key.iloc[0, 0] == "sfr_out_file", df_key.iloc[0, :] + sfr_out_file = df_key.iloc[0, 1] + df_key = df_key.iloc[1:, :] df_key.loc[:, "segment"] = df_key.segment.apply(np.int) df_key.index = df_key.segment seg_group_dict = df_key.groupby(df_key.obs_base).groups @@ -1691,17 +1962,19 @@ def apply_sfr_obs(): sfr_kper = load_sfr_out(sfr_out_file) kpers = list(sfr_kper.keys()) kpers.sort() - #results = {o:[] for o in seg_group_dict.keys()} + # results = {o:[] for o in seg_group_dict.keys()} results = [] for kper in kpers: df = sfr_kper[kper] - for obs_base,segs in seg_group_dict.items(): - agg = df.loc[segs.values,:].sum() # still agg flout where seg groups are passed! - #print(obs_base,agg) - results.append([kper,obs_base,agg["flaqx"],agg["flout"]]) - df = pd.DataFrame(data=results,columns=["kper","obs_base","flaqx","flout"]) - df.sort_values(by=["kper","obs_base"],inplace=True) - df.to_csv(sfr_out_file+".processed",sep=' ',index=False) + for obs_base, segs in seg_group_dict.items(): + agg = df.loc[ + segs.values, : + ].sum() # still agg flout where seg groups are passed! + # print(obs_base,agg) + results.append([kper, obs_base, agg["flaqx"], agg["flout"]]) + df = pd.DataFrame(data=results, columns=["kper", "obs_base", "flaqx", "flout"]) + df.sort_values(by=["kper", "obs_base"], inplace=True) + df.to_csv(sfr_out_file + ".processed", sep=" ", index=False) return df @@ -1721,40 +1994,43 @@ def load_sfr_out(sfr_out_file, selection=None): **dict**: dictionary of {kper:`pandas.DataFrame`} of SFR output. """ - assert os.path.exists(sfr_out_file),"couldn't find sfr out file {0}".\ - format(sfr_out_file) + assert os.path.exists(sfr_out_file), "couldn't find sfr out file {0}".format( + sfr_out_file + ) tag = " stream listing" lcount = 0 sfr_dict = {} if selection is None: pass elif isinstance(selection, str): - assert selection == 'all', \ - "If string passed as selection only 'all' allowed: " \ - "{}".format(selection) + assert ( + selection == "all" + ), "If string passed as selection only 'all' allowed: " "{}".format(selection) else: - assert isinstance(selection, pd.DataFrame), \ - "'selection needs to be pandas Dataframe. " \ + assert isinstance(selection, pd.DataFrame), ( + "'selection needs to be pandas Dataframe. " "Type {} passed.".format(type(selection)) - assert np.all([sr in selection.columns for sr in ['segment', 'reach']] - ), "Either 'segment' or 'reach' not in selection columns" + ) + assert np.all( + [sr in selection.columns for sr in ["segment", "reach"]] + ), "Either 'segment' or 'reach' not in selection columns" with open(sfr_out_file) as f: while True: line = f.readline().lower() lcount += 1 - if line == '': + if line == "": break if line.startswith(tag): raw = line.strip().split() kper = int(raw[3]) - 1 kstp = int(raw[5]) - 1 - [f.readline() for _ in range(4)] #skip to where the data starts + [f.readline() for _ in range(4)] # skip to where the data starts lcount += 4 dlines = [] while True: dline = f.readline() lcount += 1 - if dline.strip() == '': + if dline.strip() == "": break draw = dline.strip().split() dlines.append(draw) @@ -1764,49 +2040,70 @@ def load_sfr_out(sfr_out_file, selection=None): df["reach"] = df.reach.astype(np.int) df["flaqx"] = df.flaqx.astype(np.float) df["flout"] = df.flout.astype(np.float) - df.index = ["{0:03d}_{1:03d}".format(s, r) for s, r in - np.array([df.segment.values, df.reach.values]).T] + df.index = [ + "{0:03d}_{1:03d}".format(s, r) + for s, r in np.array([df.segment.values, df.reach.values]).T + ] # df.index = df.apply( # lambda x: "{0:03d}_{1:03d}".format( # int(x.segment), int(x.reach)), axis=1) if selection is None: # setup for all segs, aggregate gp = df.groupby(df.segment) - bot_reaches = gp[['reach']].max().apply( - lambda x: "{0:03d}_{1:03d}".format( - int(x.name), int(x.reach)), axis=1) + bot_reaches = ( + gp[["reach"]] + .max() + .apply( + lambda x: "{0:03d}_{1:03d}".format( + int(x.name), int(x.reach) + ), + axis=1, + ) + ) # only sum distributed output # take flow out of seg df2 = pd.DataFrame( - {'flaqx':gp.flaqx.sum(), - 'flout': df.loc[bot_reaches, 'flout'].values}, - index=gp.groups.keys()) + { + "flaqx": gp.flaqx.sum(), + "flout": df.loc[bot_reaches, "flout"].values, + }, + index=gp.groups.keys(), + ) # df = df.groupby(df.segment).sum() df2["segment"] = df2.index - elif isinstance(selection, str) and selection == 'all': + elif isinstance(selection, str) and selection == "all": df2 = df else: seg_reach_id = selection.apply( lambda x: "{0:03d}_{1:03d}".format( - int(x.segment), int(x.reach)), axis=1).values + int(x.segment), int(x.reach) + ), + axis=1, + ).values for sr in seg_reach_id: if sr not in df.index: - s, r = [x.lstrip('0') for x in sr.split('_')] + s, r = [x.lstrip("0") for x in sr.split("_")] warnings.warn( "Requested segment reach pair ({0},{1}) " "is not in sfr output. Dropping...".format( - int(r), int(s)), PyemuWarning) + int(r), int(s) + ), + PyemuWarning, + ) seg_reach_id = np.delete( - seg_reach_id, - np.where(seg_reach_id == sr), axis=0) + seg_reach_id, np.where(seg_reach_id == sr), axis=0 + ) df2 = df.loc[seg_reach_id].copy() if kper in sfr_dict.keys(): - print("multiple entries found for kper {0}, " - "replacing...".format(kper)) + print( + "multiple entries found for kper {0}, " + "replacing...".format(kper) + ) sfr_dict[kper] = df2 return sfr_dict -def setup_sfr_reach_obs(sfr_out_file,seg_reach=None,ins_file=None,model=None, - include_path=False): +def setup_sfr_reach_obs( + sfr_out_file, seg_reach=None, ins_file=None, model=None, include_path=False +): """setup observations using the sfr ASCII output file. Setups sfr point observations using segment and reach numbers. @@ -1835,54 +2132,76 @@ def setup_sfr_reach_obs(sfr_out_file,seg_reach=None,ins_file=None,model=None, """ if seg_reach is None: warnings.warn("Obs will be set up for every reach", PyemuWarning) - seg_reach = 'all' + seg_reach = "all" elif isinstance(seg_reach, list) or isinstance(seg_reach, np.ndarray): if np.ndim(seg_reach) == 1: seg_reach = [seg_reach] - assert np.shape( - seg_reach)[1] == 2, "varible seg_reach expected shape (n,2), received {0}".format(np.shape(seg_reach)) - seg_reach = pd.DataFrame(seg_reach, columns=['segment', 'reach']) - seg_reach.index = seg_reach.apply(lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1) + assert ( + np.shape(seg_reach)[1] == 2 + ), "varible seg_reach expected shape (n,2), received {0}".format( + np.shape(seg_reach) + ) + seg_reach = pd.DataFrame(seg_reach, columns=["segment", "reach"]) + seg_reach.index = seg_reach.apply( + lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1 + ) elif isinstance(seg_reach, dict): - seg_reach = pd.DataFrame.from_dict(seg_reach, orient='index', columns=['segment', 'reach']) + seg_reach = pd.DataFrame.from_dict( + seg_reach, orient="index", columns=["segment", "reach"] + ) else: assert isinstance( - seg_reach, pd.DataFrame), "'selection needs to be pandas Dataframe. Type {} passed.".format(type(seg_reach)) - assert np.all([sr in seg_reach.columns for sr in ['segment', 'reach']] - ), "Either 'segment' or 'reach' not in selection columns" + seg_reach, pd.DataFrame + ), "'selection needs to be pandas Dataframe. Type {} passed.".format( + type(seg_reach) + ) + assert np.all( + [sr in seg_reach.columns for sr in ["segment", "reach"]] + ), "Either 'segment' or 'reach' not in selection columns" sfr_dict = load_sfr_out(sfr_out_file, selection=seg_reach) kpers = list(sfr_dict.keys()) kpers.sort() - if isinstance(seg_reach, str) and seg_reach == 'all': - seg_reach = sfr_dict[kpers[0]][['segment', 'reach']] - seg_reach.index = seg_reach.apply(lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1) + if isinstance(seg_reach, str) and seg_reach == "all": + seg_reach = sfr_dict[kpers[0]][["segment", "reach"]] + seg_reach.index = seg_reach.apply( + lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1 + ) keys = ["sfr_out_file"] if include_path: values = [os.path.split(sfr_out_file)[-1]] else: values = [sfr_out_file] - diff = seg_reach.loc[seg_reach.apply(lambda x: "{0:03d}_{1:03d}".format(int(x.segment), int(x.reach)) - not in sfr_dict[list(sfr_dict.keys())[0]].index, axis=1)] + diff = seg_reach.loc[ + seg_reach.apply( + lambda x: "{0:03d}_{1:03d}".format(int(x.segment), int(x.reach)) + not in sfr_dict[list(sfr_dict.keys())[0]].index, + axis=1, + ) + ] if len(diff) > 0: for ob in diff.itertuples(): - warnings.warn("segs,reach pair listed with onames {0} was not found: {1}". - format(ob.Index, "({},{})".format(ob.segment, ob.reach)), PyemuWarning) + warnings.warn( + "segs,reach pair listed with onames {0} was not found: {1}".format( + ob.Index, "({},{})".format(ob.segment, ob.reach) + ), + PyemuWarning, + ) seg_reach = seg_reach.drop(diff.index) - seg_reach['obs_base'] = seg_reach.index - df_key = pd.DataFrame({"obs_base": keys, "segment": 0, 'reach': values}) + seg_reach["obs_base"] = seg_reach.index + df_key = pd.DataFrame({"obs_base": keys, "segment": 0, "reach": values}) df_key = pd.concat([df_key, seg_reach], sort=True).reset_index(drop=True) if include_path: pth = os.path.join(*[p for p in os.path.split(sfr_out_file)[:-1]]) - config_file = os.path.join(pth,"sfr_reach_obs.config") + config_file = os.path.join(pth, "sfr_reach_obs.config") else: config_file = "sfr_reach_obs.config" print("writing 'sfr_reach_obs.config' to {0}".format(config_file)) df_key.to_csv(config_file) - bd = '.' + bd = "." if include_path: bd = os.getcwd() os.chdir(pth) @@ -1893,18 +2212,25 @@ def setup_sfr_reach_obs(sfr_out_file,seg_reach=None,ins_file=None,model=None, raise Exception("error in apply_sfr_reach_obs(): {0}".format(str(e))) os.chdir(bd) if model is not None: - dts = (pd.to_datetime(model.start_datetime) + pd.to_timedelta(np.cumsum(model.dis.perlen.array), unit='d')).date + dts = ( + pd.to_datetime(model.start_datetime) + + pd.to_timedelta(np.cumsum(model.dis.perlen.array), unit="d") + ).date df.loc[:, "datetime"] = df.kper.apply(lambda x: dts[x]) df.loc[:, "time_str"] = df.datetime.apply(lambda x: x.strftime("%Y%m%d")) else: df.loc[:, "time_str"] = df.kper.apply(lambda x: "{0:04d}".format(x)) - df.loc[:, "flaqx_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fa", x.obs_base, x.time_str), axis=1) - df.loc[:, "flout_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fo", x.obs_base, x.time_str), axis=1) + df.loc[:, "flaqx_obsnme"] = df.apply( + lambda x: "{0}_{1}_{2}".format("fa", x.obs_base, x.time_str), axis=1 + ) + df.loc[:, "flout_obsnme"] = df.apply( + lambda x: "{0}_{1}_{2}".format("fo", x.obs_base, x.time_str), axis=1 + ) if ins_file is None: ins_file = sfr_out_file + ".reach_processed.ins" - with open(ins_file, 'w') as f: + with open(ins_file, "w") as f: f.write("pif ~\nl1\n") for fla, flo in zip(df.flaqx_obsnme, df.flout_obsnme): f.write("l1 w w !{0}! !{1}!\n".format(fla, flo)) @@ -1912,19 +2238,23 @@ def setup_sfr_reach_obs(sfr_out_file,seg_reach=None,ins_file=None,model=None, df = None pth = os.path.split(ins_file)[:-1] pth = os.path.join(*pth) - if pth == '': - pth = '.' + if pth == "": + pth = "." bd = os.getcwd() os.chdir(pth) try: - df = try_process_output_file(os.path.split(ins_file)[-1],os.path.split(sfr_out_file+".processed")[-1]) + df = try_process_output_file( + os.path.split(ins_file)[-1], os.path.split(sfr_out_file + ".processed")[-1] + ) except Exception as e: pass os.chdir(bd) if df is not None: df.loc[:, "obsnme"] = df.index.values - df.loc[:, "obgnme"] = df.obsnme.apply(lambda x: "flaqx" if x.startswith("fa") else "flout") + df.loc[:, "obgnme"] = df.obsnme.apply( + lambda x: "flaqx" if x.startswith("fa") else "flout" + ) return df @@ -1951,7 +2281,7 @@ def apply_sfr_reach_obs(): df_key = df_key.iloc[1:, :].copy() df_key.loc[:, "segment"] = df_key.segment.apply(np.int) df_key.loc[:, "reach"] = df_key.reach.apply(np.int) - df_key = df_key.set_index('obs_base') + df_key = df_key.set_index("obs_base") sfr_kper = load_sfr_out(sfr_out_file, df_key) kpers = list(sfr_kper.keys()) @@ -1965,11 +2295,13 @@ def apply_sfr_reach_obs(): results.append([kper, sr.Index, ob["flaqx"], ob["flout"]]) df = pd.DataFrame(data=results, columns=["kper", "obs_base", "flaqx", "flout"]) df.sort_values(by=["kper", "obs_base"], inplace=True) - df.to_csv(sfr_out_file+".reach_processed", sep=' ', index=False) + df.to_csv(sfr_out_file + ".reach_processed", sep=" ", index=False) return df -def modflow_sfr_gag_to_instruction_file(gage_output_file, ins_file=None, parse_filename=False): +def modflow_sfr_gag_to_instruction_file( + gage_output_file, ins_file=None, parse_filename=False +): """writes an instruction file for an SFR gage output file to read Flow only at all times Args: @@ -2000,41 +2332,52 @@ def modflow_sfr_gag_to_instruction_file(gage_output_file, ins_file=None, parse_f """ if ins_file is None: - ins_file = gage_output_file + '.ins' + ins_file = gage_output_file + ".ins" # navigate the file to be sure the header makes sense - indat = [line.strip() for line in open(gage_output_file, 'r').readlines()] + indat = [line.strip() for line in open(gage_output_file, "r").readlines()] header = [i for i in indat if i.startswith('"')] # yank out the gage number to identify the observation names if parse_filename: - gage_num = os.path.basename(gage_output_file).split('.')[0] + gage_num = os.path.basename(gage_output_file).split(".")[0] else: - gage_num = re.sub("[^0-9]", "", indat[0].lower().split("gage no.")[-1].strip().split()[0]) + gage_num = re.sub( + "[^0-9]", "", indat[0].lower().split("gage no.")[-1].strip().split()[0] + ) # get the column names - cols = [i.lower() for i in header if 'data' in i.lower()][0].lower().replace('"', '').replace('data:', '').split() + cols = ( + [i.lower() for i in header if "data" in i.lower()][0] + .lower() + .replace('"', "") + .replace("data:", "") + .split() + ) # make sure "Flow" is included in the columns - if 'flow' not in cols: + if "flow" not in cols: raise Exception('Requested field "Flow" not in gage output columns') # find which column is for "Flow" - flowidx = np.where(np.array(cols) == 'flow')[0][0] + flowidx = np.where(np.array(cols) == "flow")[0][0] # write out the instruction file lines - inslines = ['l1 ' + (flowidx + 1) * 'w ' + '!g{0}_{1:d}!'.format(gage_num, j) - for j in range(len(indat) - len(header))] - inslines[0] = inslines[0].replace('l1', 'l{0:d}'.format(len(header) + 1)) + inslines = [ + "l1 " + (flowidx + 1) * "w " + "!g{0}_{1:d}!".format(gage_num, j) + for j in range(len(indat) - len(header)) + ] + inslines[0] = inslines[0].replace("l1", "l{0:d}".format(len(header) + 1)) # write the instruction file - with open(ins_file, 'w') as ofp: - ofp.write('pif ~\n') - [ofp.write('{0}\n'.format(line)) for line in inslines] + with open(ins_file, "w") as ofp: + ofp.write("pif ~\n") + [ofp.write("{0}\n".format(line)) for line in inslines] df = try_process_output_file(ins_file, gage_output_file) return df, ins_file, gage_output_file -def setup_gage_obs(gage_file,ins_file=None,start_datetime=None,times=None): + +def setup_gage_obs(gage_file, ins_file=None, start_datetime=None, times=None): """setup a forward run post processor routine for the modflow gage file Args: @@ -2062,26 +2405,38 @@ def setup_gage_obs(gage_file,ins_file=None,start_datetime=None,times=None): This is the companion function of `gw_utils.apply_gage_obs()` """ - with open(gage_file, 'r') as f: + with open(gage_file, "r") as f: line1 = f.readline() - gage_num = int(re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0])) + gage_num = int( + re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0]) + ) gage_type = line1.split("GAGE No.")[-1].strip().split()[1].lower() - obj_num = int(line1.replace('"', '').strip().split()[-1]) + obj_num = int(line1.replace('"', "").strip().split()[-1]) line2 = f.readline() - df = pd.read_csv(f, delim_whitespace=True, names=line2.replace('"', '').split()[1:]) + df = pd.read_csv( + f, delim_whitespace=True, names=line2.replace('"', "").split()[1:] + ) - df.columns = [c.lower().replace("-", "_").replace('.', '_').strip('_') for c in df.columns] + df.columns = [ + c.lower().replace("-", "_").replace(".", "_").strip("_") for c in df.columns + ] # get unique observation ids - obs_ids = {col:"" for col in df.columns[1:]} # empty dictionary for observation ids - for col in df.columns[1:]: # exclude column 1 (TIME) - colspl = col.split('_') + obs_ids = { + col: "" for col in df.columns[1:] + } # empty dictionary for observation ids + for col in df.columns[1:]: # exclude column 1 (TIME) + colspl = col.split("_") if len(colspl) > 1: # obs name built out of "g"(for gage) "s" or "l"(for gage type) 2 chars from column name - date added later - obs_ids[col] = "g{0}{1}{2}".format(gage_type[0], colspl[0][0],colspl[-1][0]) + obs_ids[col] = "g{0}{1}{2}".format( + gage_type[0], colspl[0][0], colspl[-1][0] + ) else: obs_ids[col] = "g{0}{1}".format(gage_type[0], col[0:2]) - with open("_gage_obs_ids.csv", "w") as f: # write file relating obs names to meaningfull keys! - [f.write('{0},{1}\n'.format(key, obs)) for key, obs in obs_ids.items()] + with open( + "_gage_obs_ids.csv", "w" + ) as f: # write file relating obs names to meaningfull keys! + [f.write("{0},{1}\n".format(key, obs)) for key, obs in obs_ids.items()] # find passed times in df if times is None: times = df.time.unique() @@ -2092,38 +2447,50 @@ def setup_gage_obs(gage_file,ins_file=None,start_datetime=None,times=None): missing.append(str(t)) if len(missing) > 0: print(df.time) - raise Exception("the following times are missing:{0}".format(','.join(missing))) + raise Exception("the following times are missing:{0}".format(",".join(missing))) # write output times to config file - with open("gage_obs.config", 'w') as f: - f.write(gage_file+'\n') + with open("gage_obs.config", "w") as f: + f.write(gage_file + "\n") [f.write("{0:15.10E}\n".format(t)) for t in times] # extract data for times: returns dataframe and saves a processed df - read by pest df, obs_file = apply_gage_obs(return_obs_file=True) utimes = df.time.unique() for t in times: - assert np.isclose(t, utimes).any(), "time {0} missing in processed dataframe".format(t) - idx = df.time.apply(lambda x: np.isclose(x, times).any()) # boolean selector of desired times in df + assert np.isclose( + t, utimes + ).any(), "time {0} missing in processed dataframe".format(t) + idx = df.time.apply( + lambda x: np.isclose(x, times).any() + ) # boolean selector of desired times in df if start_datetime is not None: # convert times to usable observation times start_datetime = pd.to_datetime(start_datetime) - df.loc[:, "time_str"] = pd.to_timedelta(df.time, unit='d') + start_datetime - df.loc[:, "time_str"] = df.time_str.apply(lambda x: datetime.strftime(x, "%Y%m%d")) + df.loc[:, "time_str"] = pd.to_timedelta(df.time, unit="d") + start_datetime + df.loc[:, "time_str"] = df.time_str.apply( + lambda x: datetime.strftime(x, "%Y%m%d") + ) else: df.loc[:, "time_str"] = df.time.apply(lambda x: "{0:08.2f}".format(x)) # set up instructions (line feed for lines without obs (not in time) df.loc[:, "ins_str"] = "l1\n" df_times = df.loc[idx, :] # Slice by desired times # TODO include GAGE No. in obs name (if permissible) - df.loc[df_times.index, "ins_str"] = df_times.apply(lambda x: "l1 w {}\n".format( - ' w '.join(["!{0}{1}!".format(obs, x.time_str) for key,obs in obs_ids.items()])), axis=1) + df.loc[df_times.index, "ins_str"] = df_times.apply( + lambda x: "l1 w {}\n".format( + " w ".join( + ["!{0}{1}!".format(obs, x.time_str) for key, obs in obs_ids.items()] + ) + ), + axis=1, + ) df.index = np.arange(df.shape[0]) if ins_file is None: - ins_file = gage_file+".processed.ins" + ins_file = gage_file + ".processed.ins" - with open(ins_file, 'w') as f: + with open(ins_file, "w") as f: f.write("pif ~\nl1\n") [f.write(i) for i in df.ins_str] - df = try_process_output_file(ins_file, gage_file+".processed") + df = try_process_output_file(ins_file, gage_file + ".processed") return df, ins_file, obs_file @@ -2145,24 +2512,28 @@ def apply_gage_obs(return_obs_file=False): gage_file = f.readline().strip() for line in f: times.append(float(line.strip())) - obs_file = gage_file+".processed" - with open(gage_file, 'r') as f: + obs_file = gage_file + ".processed" + with open(gage_file, "r") as f: line1 = f.readline() - gage_num = int(re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0])) + gage_num = int( + re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0]) + ) gage_type = line1.split("GAGE No.")[-1].strip().split()[1].lower() - obj_num = int(line1.replace('"', '').strip().split()[-1]) + obj_num = int(line1.replace('"', "").strip().split()[-1]) line2 = f.readline() - df = pd.read_csv(f, delim_whitespace=True, names=line2.replace('"', '').split()[1:]) - df.columns = [c.lower().replace("-", "_").replace('.', '_') for c in df.columns] + df = pd.read_csv( + f, delim_whitespace=True, names=line2.replace('"', "").split()[1:] + ) + df.columns = [c.lower().replace("-", "_").replace(".", "_") for c in df.columns] df = df.loc[df.time.apply(lambda x: np.isclose(x, times).any()), :] - df.to_csv(obs_file, sep=' ', index=False) + df.to_csv(obs_file, sep=" ", index=False) if return_obs_file: return df, obs_file else: return df -def apply_hfb_pars(par_file='hfb6_pars.csv'): +def apply_hfb_pars(par_file="hfb6_pars.csv"): """ a function to apply HFB multiplier parameters. Args: @@ -2182,19 +2553,28 @@ def apply_hfb_pars(par_file='hfb6_pars.csv'): """ hfb_pars = pd.read_csv(par_file) - hfb_mults_contents = open(hfb_pars.mlt_file.values[0], 'r').readlines() - skiprows = sum([1 if i.strip().startswith('#') else 0 - for i in hfb_mults_contents]) + 1 + hfb_mults_contents = open(hfb_pars.mlt_file.values[0], "r").readlines() + skiprows = ( + sum([1 if i.strip().startswith("#") else 0 for i in hfb_mults_contents]) + 1 + ) header = hfb_mults_contents[:skiprows] # read in the multipliers - names = ['lay', 'irow1','icol1','irow2','icol2', 'hydchr'] - hfb_mults = pd.read_csv(hfb_pars.mlt_file.values[0], skiprows=skiprows, - delim_whitespace=True, names=names).dropna() + names = ["lay", "irow1", "icol1", "irow2", "icol2", "hydchr"] + hfb_mults = pd.read_csv( + hfb_pars.mlt_file.values[0], + skiprows=skiprows, + delim_whitespace=True, + names=names, + ).dropna() # read in the original file - hfb_org = pd.read_csv(hfb_pars.org_file.values[0], skiprows=skiprows, - delim_whitespace=True, names=names).dropna() + hfb_org = pd.read_csv( + hfb_pars.org_file.values[0], + skiprows=skiprows, + delim_whitespace=True, + names=names, + ).dropna() # multiply it out hfb_org.hydchr *= hfb_mults.hydchr @@ -2203,11 +2583,12 @@ def apply_hfb_pars(par_file='hfb6_pars.csv'): hfb_mults[cn] = hfb_mults[cn].astype(np.int) hfb_org[cn] = hfb_org[cn].astype(np.int) # write the results - with open(hfb_pars.model_file.values[0], 'w', newline='') as ofp: - [ofp.write('{0}\n'.format(line.strip())) for line in header] + with open(hfb_pars.model_file.values[0], "w", newline="") as ofp: + [ofp.write("{0}\n".format(line.strip())) for line in header] ofp.flush() - hfb_org[['lay', 'irow1', 'icol1', 'irow2', 'icol2', 'hydchr']].to_csv( - ofp, sep=' ', header=None, index=None) + hfb_org[["lay", "irow1", "icol1", "irow2", "icol2", "hydchr"]].to_csv( + ofp, sep=" ", header=None, index=None + ) def write_hfb_zone_multipliers_template(m): @@ -2230,61 +2611,72 @@ def write_hfb_zone_multipliers_template(m): hfb_file = os.path.join(m.model_ws, m.hfb6.file_name[0]) # this will use multipliers, so need to copy down the original - if not os.path.exists(os.path.join(m.model_ws, 'hfb6_org')): - os.mkdir(os.path.join(m.model_ws, 'hfb6_org')) + if not os.path.exists(os.path.join(m.model_ws, "hfb6_org")): + os.mkdir(os.path.join(m.model_ws, "hfb6_org")) # copy down the original file - shutil.copy2(os.path.join(m.model_ws, m.hfb6.file_name[0]), - os.path.join(m.model_ws,'hfb6_org', m.hfb6.file_name[0])) + shutil.copy2( + os.path.join(m.model_ws, m.hfb6.file_name[0]), + os.path.join(m.model_ws, "hfb6_org", m.hfb6.file_name[0]), + ) - if not os.path.exists(os.path.join(m.model_ws, 'hfb6_mlt')): - os.mkdir(os.path.join(m.model_ws, 'hfb6_mlt')) + if not os.path.exists(os.path.join(m.model_ws, "hfb6_mlt")): + os.mkdir(os.path.join(m.model_ws, "hfb6_mlt")) # read in the model file - hfb_file_contents = open(hfb_file, 'r').readlines() + hfb_file_contents = open(hfb_file, "r").readlines() # navigate the header - skiprows = sum([1 if i.strip().startswith('#') else 0 - for i in hfb_file_contents]) + 1 + skiprows = ( + sum([1 if i.strip().startswith("#") else 0 for i in hfb_file_contents]) + 1 + ) header = hfb_file_contents[:skiprows] # read in the data - names = ['lay', 'irow1', 'icol1', 'irow2', 'icol2', 'hydchr'] - hfb_in = pd.read_csv(hfb_file, skiprows=skiprows, - delim_whitespace=True, names=names).dropna() + names = ["lay", "irow1", "icol1", "irow2", "icol2", "hydchr"] + hfb_in = pd.read_csv( + hfb_file, skiprows=skiprows, delim_whitespace=True, names=names + ).dropna() for cn in names[:-1]: hfb_in[cn] = hfb_in[cn].astype(np.int) # set up a multiplier for each unique conductivity value unique_cond = hfb_in.hydchr.unique() - hfb_mults = dict(zip(unique_cond, ['hbz_{0:04d}'.format(i) - for i in range(len(unique_cond))])) + hfb_mults = dict( + zip(unique_cond, ["hbz_{0:04d}".format(i) for i in range(len(unique_cond))]) + ) # set up the TPL line for each parameter and assign - hfb_in['tpl'] = 'blank' - for cn, cg in hfb_in.groupby('hydchr'): - hfb_in.loc[hfb_in.hydchr == cn, 'tpl'] = '~{0:^10s}~'.format( - hfb_mults[cn]) + hfb_in["tpl"] = "blank" + for cn, cg in hfb_in.groupby("hydchr"): + hfb_in.loc[hfb_in.hydchr == cn, "tpl"] = "~{0:^10s}~".format(hfb_mults[cn]) - assert 'blank' not in hfb_in.tpl + assert "blank" not in hfb_in.tpl # write out the TPL file tpl_file = os.path.join(m.model_ws, "hfb6.mlt.tpl") - with open(tpl_file, 'w', newline='') as ofp: - ofp.write('ptf ~\n') - [ofp.write('{0}\n'.format(line.strip())) for line in header] + with open(tpl_file, "w", newline="") as ofp: + ofp.write("ptf ~\n") + [ofp.write("{0}\n".format(line.strip())) for line in header] ofp.flush() - hfb_in[['lay', 'irow1', 'icol1', 'irow2', 'icol2', 'tpl']].to_csv( - ofp, sep=' ', quotechar=' ', header=None, index=None, mode='a') + hfb_in[["lay", "irow1", "icol1", "irow2", "icol2", "tpl"]].to_csv( + ofp, sep=" ", quotechar=" ", header=None, index=None, mode="a" + ) # make a lookup for lining up the necessary files to # perform multiplication with the helpers.apply_hfb_pars() function # which must be added to the forward run script - with open(os.path.join(m.model_ws, 'hfb6_pars.csv'), 'w') as ofp: - ofp.write('org_file,mlt_file,model_file\n') - ofp.write('{0},{1},{2}\n'.format( - os.path.join(m.model_ws, 'hfb6_org', m.hfb6.file_name[0]), - os.path.join(m.model_ws, 'hfb6_mlt', - os.path.basename(tpl_file).replace('.tpl', '')), - hfb_file)) + with open(os.path.join(m.model_ws, "hfb6_pars.csv"), "w") as ofp: + ofp.write("org_file,mlt_file,model_file\n") + ofp.write( + "{0},{1},{2}\n".format( + os.path.join(m.model_ws, "hfb6_org", m.hfb6.file_name[0]), + os.path.join( + m.model_ws, + "hfb6_mlt", + os.path.basename(tpl_file).replace(".tpl", ""), + ), + hfb_file, + ) + ) return hfb_mults, tpl_file @@ -2306,14 +2698,14 @@ def write_hfb_template(m): """ assert m.hfb6 is not None - hfb_file = os.path.join(m.model_ws,m.hfb6.file_name[0]) - assert os.path.exists(hfb_file),"couldn't find hfb_file {0}".format(hfb_file) - f_in = open(hfb_file,'r') - tpl_file = hfb_file+".tpl" - f_tpl = open(tpl_file,'w') + hfb_file = os.path.join(m.model_ws, m.hfb6.file_name[0]) + assert os.path.exists(hfb_file), "couldn't find hfb_file {0}".format(hfb_file) + f_in = open(hfb_file, "r") + tpl_file = hfb_file + ".tpl" + f_tpl = open(tpl_file, "w") f_tpl.write("ptf ~\n") - parnme,parval1,xs,ys = [],[],[],[] - iis,jjs,kks = [],[],[] + parnme, parval1, xs, ys = [], [], [], [] + iis, jjs, kks = [], [], [] xc = m.sr.xcentergrid yc = m.sr.ycentergrid @@ -2337,15 +2729,15 @@ def write_hfb_template(m): k = int(raw[0]) - 1 i = int(raw[1]) - 1 j = int(raw[2]) - 1 - pn = "hb{0:02}{1:04d}{2:04}".format(k,i,j) + pn = "hb{0:02}{1:04d}{2:04}".format(k, i, j) pv = float(raw[5]) raw[5] = "~ {0} ~".format(pn) - line = ' '.join(raw)+'\n' + line = " ".join(raw) + "\n" f_tpl.write(line) parnme.append(pn) parval1.append(pv) - xs.append(xc[i,j]) - ys.append(yc[i,j]) + xs.append(xc[i, j]) + ys.append(yc[i, j]) iis.append(i) jjs.append(j) kks.append(k) @@ -2354,11 +2746,19 @@ def write_hfb_template(m): f_tpl.close() f_in.close() - df = pd.DataFrame({"parnme":parnme,"parval1":parval1,"x":xs,"y":ys, - "i":iis,"j":jjs,"k":kks},index=parnme) - df.loc[:,"pargp"] = "hfb_hydfac" - df.loc[:,"parubnd"] = df.parval1.max() * 10.0 - df.loc[:,"parlbnd"] = df.parval1.min() * 0.1 - return tpl_file,df - - + df = pd.DataFrame( + { + "parnme": parnme, + "parval1": parval1, + "x": xs, + "y": ys, + "i": iis, + "j": jjs, + "k": kks, + }, + index=parnme, + ) + df.loc[:, "pargp"] = "hfb_hydfac" + df.loc[:, "parubnd"] = df.parval1.max() * 10.0 + df.loc[:, "parlbnd"] = df.parval1.min() * 0.1 + return tpl_file, df diff --git a/pyemu/utils/helpers.py b/pyemu/utils/helpers.py index 0316bbb32..b8b623878 100644 --- a/pyemu/utils/helpers.py +++ b/pyemu/utils/helpers.py @@ -30,9 +30,9 @@ from pyemu.utils.os_utils import run, start_workers - -def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=True, - scale_offset=True): +def geostatistical_draws( + pst, struct_dict, num_reals=100, sigma_range=4, verbose=True, scale_offset=True +): """construct a parameter ensemble from a prior covariance matrix implied by geostatistical structure(s) and parameter bounds. @@ -76,12 +76,15 @@ def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=Tr if isinstance(pst, str): pst = pyemu.Pst(pst) - assert isinstance(pst, pyemu.Pst), "pst arg must be a Pst instance, not {0}". \ - format(type(pst)) - if verbose: print("building diagonal cov") - - full_cov = pyemu.Cov.from_parameter_data(pst, sigma_range=sigma_range, - scale_offset=scale_offset) + assert isinstance(pst, pyemu.Pst), "pst arg must be a Pst instance, not {0}".format( + type(pst) + ) + if verbose: + print("building diagonal cov") + + full_cov = pyemu.Cov.from_parameter_data( + pst, sigma_range=sigma_range, scale_offset=scale_offset + ) full_cov_dict = {n: float(v) for n, v in zip(full_cov.col_names, full_cov.x)} # par_org = pst.parameter_data.copy # not sure about the need or function of this line? (BH) @@ -93,12 +96,14 @@ def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=Tr for gs in keys: items = struct_dict[gs] - if verbose: print("processing ", gs) + if verbose: + print("processing ", gs) if isinstance(gs, str): gss = pyemu.geostats.read_struct_file(gs) if isinstance(gss, list): - warnings.warn("using first geostat structure in file {0}". \ - format(gs), PyemuWarning) + warnings.warn( + "using first geostat structure in file {0}".format(gs), PyemuWarning + ) gs = gss[0] else: gs = gss @@ -107,10 +112,9 @@ def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=Tr if not isinstance(items, list): items = [items] # items.sort() - for iitem,item in enumerate(items): + for iitem, item in enumerate(items): if isinstance(item, str): - assert os.path.exists(item), "file {0} not found". \ - format(item) + assert os.path.exists(item), "file {0} not found".format(item) if item.lower().endswith(".tpl"): df = pyemu.pp_utils.pp_tpl_to_dataframe(item) elif item.lower.endswith(".csv"): @@ -118,20 +122,25 @@ def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=Tr else: df = item if "pargp" in df.columns: - if verbose: print("working on pargroups {0}".format(df.pargp.unique().tolist())) - for req in ['x', 'y', 'parnme']: + if verbose: + print("working on pargroups {0}".format(df.pargp.unique().tolist())) + for req in ["x", "y", "parnme"]: if req not in df.columns: raise Exception("{0} is not in the columns".format(req)) - missing = df.loc[df.parnme.apply( - lambda x: x not in par.parnme), "parnme"] + missing = df.loc[df.parnme.apply(lambda x: x not in par.parnme), "parnme"] if len(missing) > 0: - warnings.warn("the following parameters are not " + \ - "in the control file: {0}". \ - format(','.join(missing)), PyemuWarning) + warnings.warn( + "the following parameters are not " + + "in the control file: {0}".format(",".join(missing)), + PyemuWarning, + ) df = df.loc[df.parnme.apply(lambda x: x not in missing)] if df.shape[0] == 0: - warnings.warn("geostatistical_draws(): empty parameter df at position {0} items for geostruct {1}, skipping...".\ - format(iitem,gs)) + warnings.warn( + "geostatistical_draws(): empty parameter df at position {0} items for geostruct {1}, skipping...".format( + iitem, gs + ) + ) continue if "zone" not in df.columns: df.loc[:, "zone"] = 1 @@ -141,50 +150,62 @@ def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=Tr df_zone = df.loc[df.zone == zone, :].copy() df_zone = df_zone.loc[df_zone.parnme.apply(lambda x: x in aset), :] if df_zone.shape[0] == 0: - warnings.warn("all parameters in zone {0} tied and/or fixed, skipping...".format(zone), - PyemuWarning) + warnings.warn( + "all parameters in zone {0} tied and/or fixed, skipping...".format( + zone + ), + PyemuWarning, + ) continue # df_zone.sort_values(by="parnme",inplace=True) df_zone.sort_index(inplace=True) - if verbose: print("build cov matrix") + if verbose: + print("build cov matrix") cov = gs.covariance_matrix(df_zone.x, df_zone.y, df_zone.parnme) - if verbose: print("done") + if verbose: + print("done") - if verbose: print("getting diag var cov", df_zone.shape[0]) + if verbose: + print("getting diag var cov", df_zone.shape[0]) # tpl_var = np.diag(full_cov.get(list(df_zone.parnme)).x).max() tpl_var = max([full_cov_dict[pn] for pn in df_zone.parnme]) - if verbose: print("scaling full cov by diag var cov") + if verbose: + print("scaling full cov by diag var cov") # cov.x *= tpl_var for i in range(cov.shape[0]): cov.x[i, :] *= tpl_var # no fixed values here - pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst=pst, cov=cov, num_reals=num_reals, - by_groups=False, fill=False) + pe = pyemu.ParameterEnsemble.from_gaussian_draw( + pst=pst, cov=cov, num_reals=num_reals, by_groups=False, fill=False + ) # df = pe.iloc[:,:] par_ens.append(pe._df) pars_in_cov.update(set(pe.columns)) - if verbose: print("adding remaining parameters to diagonal") + if verbose: + print("adding remaining parameters to diagonal") fset = set(full_cov.row_names) diff = list(fset.difference(pars_in_cov)) - if (len(diff) > 0): + if len(diff) > 0: name_dict = {name: i for i, name in enumerate(full_cov.row_names)} vec = np.atleast_2d(np.array([full_cov.x[name_dict[d]] for d in diff])) cov = pyemu.Cov(x=vec, names=diff, isdiagonal=True) # cov = full_cov.get(diff,diff) # here we fill in the fixed values - pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst, cov, num_reals=num_reals, - fill=False) + pe = pyemu.ParameterEnsemble.from_gaussian_draw( + pst, cov, num_reals=num_reals, fill=False + ) par_ens.append(pe._df) par_ens = pd.concat(par_ens, axis=1) par_ens = pyemu.ParameterEnsemble(pst=pst, df=par_ens) return par_ens -def geostatistical_prior_builder(pst, struct_dict, sigma_range=4, - verbose=False, scale_offset=False): +def geostatistical_prior_builder( + pst, struct_dict, sigma_range=4, verbose=False, scale_offset=False +): """construct a full prior covariance matrix using geostastical structures and parameter bounds information. @@ -225,48 +246,55 @@ def geostatistical_prior_builder(pst, struct_dict, sigma_range=4, if isinstance(pst, str): pst = pyemu.Pst(pst) - assert isinstance(pst, pyemu.Pst), "pst arg must be a Pst instance, not {0}". \ - format(type(pst)) - if verbose: print("building diagonal cov") - full_cov = pyemu.Cov.from_parameter_data(pst, sigma_range=sigma_range, - scale_offset=scale_offset) + assert isinstance(pst, pyemu.Pst), "pst arg must be a Pst instance, not {0}".format( + type(pst) + ) + if verbose: + print("building diagonal cov") + full_cov = pyemu.Cov.from_parameter_data( + pst, sigma_range=sigma_range, scale_offset=scale_offset + ) full_cov_dict = {n: float(v) for n, v in zip(full_cov.col_names, full_cov.x)} # full_cov = None par = pst.parameter_data for gs, items in struct_dict.items(): - if verbose: print("processing ", gs) + if verbose: + print("processing ", gs) if isinstance(gs, str): gss = pyemu.geostats.read_struct_file(gs) if isinstance(gss, list): - warnings.warn("using first geostat structure in file {0}". \ - format(gs), PyemuWarning) + warnings.warn( + "using first geostat structure in file {0}".format(gs), PyemuWarning + ) gs = gss[0] else: gs = gss if gs.sill != 1.0: - warnings.warn("geostatistical_prior_builder() warning: geostruct sill != 1.0, user beware!") + warnings.warn( + "geostatistical_prior_builder() warning: geostruct sill != 1.0, user beware!" + ) if not isinstance(items, list): items = [items] for item in items: if isinstance(item, str): - assert os.path.exists(item), "file {0} not found". \ - format(item) + assert os.path.exists(item), "file {0} not found".format(item) if item.lower().endswith(".tpl"): df = pyemu.pp_utils.pp_tpl_to_dataframe(item) elif item.lower.endswith(".csv"): df = pd.read_csv(item) else: df = item - for req in ['x', 'y', 'parnme']: + for req in ["x", "y", "parnme"]: if req not in df.columns: raise Exception("{0} is not in the columns".format(req)) - missing = df.loc[df.parnme.apply( - lambda x: x not in par.parnme), "parnme"] + missing = df.loc[df.parnme.apply(lambda x: x not in par.parnme), "parnme"] if len(missing) > 0: - warnings.warn("the following parameters are not " + \ - "in the control file: {0}". \ - format(','.join(missing)), PyemuWarning) + warnings.warn( + "the following parameters are not " + + "in the control file: {0}".format(",".join(missing)), + PyemuWarning, + ) df = df.loc[df.parnme.apply(lambda x: x not in missing)] if "zone" not in df.columns: df.loc[:, "zone"] = 1 @@ -276,33 +304,42 @@ def geostatistical_prior_builder(pst, struct_dict, sigma_range=4, df_zone = df.loc[df.zone == zone, :].copy() df_zone = df_zone.loc[df_zone.parnme.apply(lambda x: x in aset), :] if df_zone.shape[0] == 0: - warnings.warn("all parameters in zone {0} tied and/or fixed, skipping...".format(zone), - PyemuWarning) + warnings.warn( + "all parameters in zone {0} tied and/or fixed, skipping...".format( + zone + ), + PyemuWarning, + ) continue # df_zone.sort_values(by="parnme",inplace=True) df_zone.sort_index(inplace=True) - if verbose: print("build cov matrix") + if verbose: + print("build cov matrix") cov = gs.covariance_matrix(df_zone.x, df_zone.y, df_zone.parnme) - if verbose: print("done") + if verbose: + print("done") # find the variance in the diagonal cov - if verbose: print("getting diag var cov", df_zone.shape[0]) + if verbose: + print("getting diag var cov", df_zone.shape[0]) # tpl_var = np.diag(full_cov.get(list(df_zone.parnme)).x).max() tpl_var = max([full_cov_dict[pn] for pn in df_zone.parnme]) # if np.std(tpl_var) > 1.0e-6: # warnings.warn("pars have different ranges" +\ # " , using max range as variance for all pars") # tpl_var = tpl_var.max() - if verbose: print("scaling full cov by diag var cov") + if verbose: + print("scaling full cov by diag var cov") cov *= tpl_var - if verbose: print("test for inversion") + if verbose: + print("test for inversion") try: ci = cov.inv except: df_zone.to_csv("prior_builder_crash.csv") - raise Exception("error inverting cov {0}". - format(cov.row_names[:3])) + raise Exception("error inverting cov {0}".format(cov.row_names[:3])) - if verbose: print('replace in full cov') + if verbose: + print("replace in full cov") full_cov.replace(cov) # d = np.diag(full_cov.x) # idx = np.argwhere(d==0.0) @@ -310,6 +347,7 @@ def geostatistical_prior_builder(pst, struct_dict, sigma_range=4, # print(full_cov.names[i]) return full_cov + def _rmse(v1, v2): """return root mean squared error between v1 and v2 @@ -321,9 +359,12 @@ def _rmse(v1, v2): scalar: root mean squared error of v1,v2 """ - return np.sqrt(np.mean(np.square(v1-v2))) + return np.sqrt(np.mean(np.square(v1 - v2))) -def calc_observation_ensemble_quantiles(ens, pst, quantiles, subset_obsnames=None, subset_obsgroups=None): + +def calc_observation_ensemble_quantiles( + ens, pst, quantiles, subset_obsnames=None, subset_obsgroups=None +): """Given an observation ensemble, and requested quantiles, this function calculates the requested quantile point-by-point in the ensemble. This resulting set of values does not, however, correspond to a single realization in the ensemble. So, this function finds the minimum weighted squared @@ -343,8 +384,8 @@ def calc_observation_ensemble_quantiles(ens, pst, quantiles, subset_obsnames=Non quantile_idx (dictionary): dictionary with keys being quantiles and values being realizations corresponding to each realization """ - #TODO: handle zero weights due to PDC - + # TODO: handle zero weights due to PDC + quantile_idx = {} # make sure quantiles and subset names and groups are lists if not isinstance(quantiles, list): @@ -353,61 +394,73 @@ def calc_observation_ensemble_quantiles(ens, pst, quantiles, subset_obsnames=Non subset_obsnames = list(subset_obsnames) if not isinstance(subset_obsgroups, list) and subset_obsgroups is not None: subset_obsgroups = list(subset_obsgroups) - - - if 'real_name' in ens.columns: - ens.set_index('real_name') + + if "real_name" in ens.columns: + ens.set_index("real_name") # if 'base' real was lost, then the index is of type int. needs to be string later so set here ens.index = [str(i) for i in ens.index] if not isinstance(pst, pyemu.Pst): - raise Exception('pst object must be of type pyemu.Pst') - + raise Exception("pst object must be of type pyemu.Pst") + # get the observation data obs = pst.observation_data.copy() - + # confirm that the indices and weights line up - if False in np.unique(ens.columns == obs.index): - raise Exception('ens and pst observation names do not align') - + if False in np.unique(ens.columns == obs.index): + raise Exception("ens and pst observation names do not align") + # deal with any subsetting of observations that isn't handled through weights - + trimnames = obs.index.values if subset_obsgroups is not None and subset_obsnames is not None: - raise Exception('can only specify information in one of subset_obsnames of subset_obsgroups. not both') - + raise Exception( + "can only specify information in one of subset_obsnames of subset_obsgroups. not both" + ) + if subset_obsnames is not None: trimnames = subset_obsnames if len(set(trimnames) - set(obs.index.values)) != 0: - raise Exception('the following names in subset_obsnames are not in the ensemble:\n' + - ['{}\n'.format(i) for i in (set(trimnames) - set(obs.index.values))]) + raise Exception( + "the following names in subset_obsnames are not in the ensemble:\n" + + ["{}\n".format(i) for i in (set(trimnames) - set(obs.index.values))] + ) - if subset_obsgroups is not None: if len((set(subset_obsgroups) - set(pst.obs_groups))) != 0: - raise Exception('the following groups in subset_obsgroups are not in pst:\n' + - ['{}\n'.format(i) for i in (set(subset_obsgroups) - set(pst.obs_groups))]) + raise Exception( + "the following groups in subset_obsgroups are not in pst:\n" + + [ + "{}\n".format(i) + for i in (set(subset_obsgroups) - set(pst.obs_groups)) + ] + ) trimnames = obs.loc[obs.obgnme.isin(subset_obsgroups)].obsnme.tolist() if len((set(trimnames) - set(obs.index.values))) != 0: - raise Exception('the following names in subset_obsnames are not in the ensemble:\n' + - ['{}\n'.format(i) for i in (set(trimnames) - set(obs.index.values))]) - # trim the data to subsets (or complete ) + raise Exception( + "the following names in subset_obsnames are not in the ensemble:\n" + + ["{}\n".format(i) for i in (set(trimnames) - set(obs.index.values))] + ) + # trim the data to subsets (or complete ) ens_eval = ens[trimnames].copy() weights = obs.loc[trimnames].weight.values - + for cq in quantiles: # calculate the point-wise quantile values qfit = np.quantile(ens_eval, cq, axis=0) # calculate the weighted distance between all reals and the desired quantile - qreal = np.argmin(np.linalg.norm([(i - qfit)*weights for i in ens_eval.values], axis=1)) - quantile_idx['q{}'.format(cq)] = qreal + qreal = np.argmin( + np.linalg.norm([(i - qfit) * weights for i in ens_eval.values], axis=1) + ) + quantile_idx["q{}".format(cq)] = qreal ens = ens.append(ens.iloc[qreal]) idx = ens.index.values - idx[-1] = 'q{}'.format(cq) + idx[-1] = "q{}".format(cq) ens.set_index(idx, inplace=True) return ens, quantile_idx + def calc_rmse_ensemble(ens, pst, bygroups=True, subset_realizations=None): """Calculates RMSE (without weights) to quantify fit to observations for ensemble members @@ -422,39 +475,41 @@ def calc_rmse_ensemble(ens, pst, bygroups=True, subset_realizations=None): rmse (pandas DataFrame object): rows are realizations. Columns are groups. Content is RMSE """ - #TODO: handle zero weights due to PDC - + # TODO: handle zero weights due to PDC + # make sure subset_realizations is a list if not isinstance(subset_realizations, list) and subset_realizations is not None: subset_realizations = list(subset_realizations) - - if 'real_name' in ens.columns: - ens.set_index('real_name') + + if "real_name" in ens.columns: + ens.set_index("real_name") if not isinstance(pst, pyemu.Pst): - raise Exception('pst object must be of type pyemu.Pst') - + raise Exception("pst object must be of type pyemu.Pst") + # get the observation data obs = pst.observation_data.copy() - + # confirm that the indices and observations line up - if False in np.unique(ens.columns == obs.index): - raise Exception('ens and pst observation names do not align') + if False in np.unique(ens.columns == obs.index): + raise Exception("ens and pst observation names do not align") - rmse = pd.DataFrame(index=ens.index) if subset_realizations is not None: rmse = rmse.loc[subset_realizations] - + # calculate the rmse total first - rmse['total'] = [_rmse(ens.loc[i], obs.obsval) for i in rmse.index] - + rmse["total"] = [_rmse(ens.loc[i], obs.obsval) for i in rmse.index] + # if bygroups, do the groups as columns if bygroups is True: for cg in obs.obgnme.unique(): cnames = obs.loc[obs.obgnme == cg].obsnme - rmse[cg] = [_rmse(ens.loc[i][cnames], obs.loc[cnames].obsval) for i in rmse.index] + rmse[cg] = [ + _rmse(ens.loc[i][cnames], obs.loc[cnames].obsval) for i in rmse.index + ] return rmse + def _condition_on_par_knowledge(cov, par_knowledge_dict): """ experimental function to include conditional prior information for one or more parameters in a full covariance matrix @@ -465,8 +520,9 @@ def _condition_on_par_knowledge(cov, par_knowledge_dict): if parnme not in cov.row_names: missing.append(parnme) if len(missing): - raise Exception("par knowledge dict parameters not found: {0}". \ - format(','.join(missing))) + raise Exception( + "par knowledge dict parameters not found: {0}".format(",".join(missing)) + ) # build the selection matrix and sigma epsilon # sel = pyemu.Cov(x=np.identity(cov.shape[0]),names=cov.row_names) sel = cov.zero2d @@ -493,10 +549,16 @@ def _condition_on_par_knowledge(cov, par_knowledge_dict): return new_cov_diag -def kl_setup(num_eig, sr, struct, prefixes, - factors_file="kl_factors.dat", - islog=True, basis_file=None, - tpl_dir="."): +def kl_setup( + num_eig, + sr, + struct, + prefixes, + factors_file="kl_factors.dat", + islog=True, + basis_file=None, + tpl_dir=".", +): """setup a karhuenen-Loeve based parameterization for a given geostatistical structure. @@ -553,9 +615,9 @@ def kl_setup(num_eig, sr, struct, prefixes, for i in range(sr.nrow): names.extend(["i{0:04d}j{1:04d}".format(i, j) for j in range(sr.ncol)]) - cov = gs.covariance_matrix(sr.xcentergrid.flatten(), - sr.ycentergrid.flatten(), - names=names) + cov = gs.covariance_matrix( + sr.xcentergrid.flatten(), sr.ycentergrid.flatten(), names=names + ) eig_names = ["eig_{0:04d}".format(i) for i in range(cov.shape[0])] trunc_basis = cov.u @@ -573,8 +635,9 @@ def kl_setup(num_eig, sr, struct, prefixes, pp_df.loc[:, "parval1"] = 1.0 pyemu.pp_utils.write_pp_file(os.path.join("temp.dat"), pp_df) - _eigen_basis_to_factor_file(sr.nrow, sr.ncol, trunc_basis, - factors_file=factors_file, islog=islog) + _eigen_basis_to_factor_file( + sr.nrow, sr.ncol, trunc_basis, factors_file=factors_file, islog=islog + ) dfs = [] for prefix in prefixes: tpl_file = os.path.join(tpl_dir, "{0}.dat_kl.tpl".format(prefix)) @@ -615,7 +678,7 @@ def kl_setup(num_eig, sr, struct, prefixes, def _eigen_basis_to_factor_file(nrow, ncol, basis, factors_file, islog=True): assert nrow * ncol == basis.shape[0] - with open(factors_file, 'w') as f: + with open(factors_file, "w") as f: f.write("junk.dat\n") f.write("junk.zone.dat\n") f.write("{0} {1}\n".format(ncol, nrow)) @@ -626,7 +689,10 @@ def _eigen_basis_to_factor_file(nrow, ncol, basis, factors_file, islog=True): t = 1 for i in range(nrow * ncol): f.write("{0} {1} {2} {3:8.5e}".format(i + 1, t, basis.shape[1], 0.0)) - [f.write(" {0} {1:12.8g} ".format(i + 1, w)) for i, w in enumerate(basis.x[i, :])] + [ + f.write(" {0} {1:12.8g} ".format(i + 1, w)) + for i, w in enumerate(basis.x[i, :]) + ] f.write("\n") @@ -656,8 +722,7 @@ def kl_apply(par_file, basis_file, par_to_file_dict, arr_shape): df.loc[:, "prefix"] = df.name.apply(lambda x: x[:-4]) for prefix in df.prefix.unique(): - assert prefix in par_to_file_dict.keys(), "missing prefix:{0}". \ - format(prefix) + assert prefix in par_to_file_dict.keys(), "missing prefix:{0}".format(prefix) basis = pyemu.Matrix.from_binary(basis_file) assert basis.shape[1] == arr_shape[0] * arr_shape[1] arr_min = 1.0e-10 # a temp hack @@ -668,15 +733,14 @@ def kl_apply(par_file, basis_file, par_to_file_dict, arr_shape): for prefix, filename in par_to_file_dict.items(): factors = pyemu.Matrix.from_dataframe(df.loc[df.prefix == prefix, ["new_val"]]) factors.autoalign = False - basis_prefix = basis[:factors.shape[0], :] + basis_prefix = basis[: factors.shape[0], :] arr = (factors.T * basis_prefix).x.reshape(arr_shape) # arr += means.loc[means.prefix==prefix,"new_val"].values arr[arr < arr_min] = arr_min np.savetxt(filename, arr, fmt="%20.8E") -def zero_order_tikhonov(pst, parbounds=True, par_groups=None, - reset=True): +def zero_order_tikhonov(pst, parbounds=True, par_groups=None, reset=True): """setup preferred-value regularization in a pest control file. Args: @@ -707,8 +771,7 @@ def zero_order_tikhonov(pst, parbounds=True, par_groups=None, pt = pt.decode() except: pass - if pt not in ["tied", "fixed"] and \ - row["pargp"] in par_groups: + if pt not in ["tied", "fixed"] and row["pargp"] in par_groups: pilbl.append(row["parnme"]) weight.append(1.0) ogp_name = "regul" + row["pargp"] @@ -722,15 +785,13 @@ def zero_order_tikhonov(pst, parbounds=True, par_groups=None, equation.append(eq) if reset: - pst.prior_information = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "obgnme": obgnme, - "weight": weight}) + pst.prior_information = pd.DataFrame( + {"pilbl": pilbl, "equation": equation, "obgnme": obgnme, "weight": weight} + ) else: - pi = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "obgnme": obgnme, - "weight": weight}) + pi = pd.DataFrame( + {"pilbl": pilbl, "equation": equation, "obgnme": obgnme, "weight": weight} + ) pst.prior_information = pst.prior_information.append(pi) if parbounds: _regweight_from_parbound(pst) @@ -757,8 +818,12 @@ def _regweight_from_parbound(pst): weight = 1.0 / (ubnd - lbnd) pst.prior_information.loc[parnme, "weight"] = weight else: - print("prior information name does not correspond" + \ - " to a parameter: " + str(parnme)) + print( + "prior information name does not correspond" + + " to a parameter: " + + str(parnme) + ) + def first_order_pearson_tikhonov(pst, cov, reset=True, abs_drop_tol=1.0e-3): """setup preferred-difference regularization from a covariance matrix. @@ -802,7 +867,7 @@ def first_order_pearson_tikhonov(pst, cov, reset=True, abs_drop_tol=1.0e-3): for i, iname in enumerate(cc_mat.row_names): if iname not in sadj_names: continue - for j, jname in enumerate(cc_mat.row_names[i + 1:]): + for j, jname in enumerate(cc_mat.row_names[i + 1 :]): if jname not in sadj_names: continue # print(i,iname,i+j+1,jname) @@ -816,13 +881,13 @@ def first_order_pearson_tikhonov(pst, cov, reset=True, abs_drop_tol=1.0e-3): jjname = str(jname) if str(ptrans[jname]) == "log": jjname = "log(" + jname + ")" - equation.append("1.0 * {0} - 1.0 * {1} = 0.0". \ - format(iiname, jjname)) + equation.append("1.0 * {0} - 1.0 * {1} = 0.0".format(iiname, jjname)) weight.append(cc) obgnme.append("regul_cc") pi_num += 1 - df = pd.DataFrame({"pilbl": pilbl, "equation": equation, - "obgnme": obgnme, "weight": weight}) + df = pd.DataFrame( + {"pilbl": pilbl, "equation": equation, "obgnme": obgnme, "weight": weight} + ) df.index = df.pilbl if reset: pst.prior_information = df @@ -833,7 +898,7 @@ def first_order_pearson_tikhonov(pst, cov, reset=True, abs_drop_tol=1.0e-3): pst.control_data.pestmode = "regularization" -def simple_tpl_from_pars(parnames, tplfilename='model.input.tpl'): +def simple_tpl_from_pars(parnames, tplfilename="model.input.tpl"): """Make a simple template file from a list of parameter names. Args: @@ -846,12 +911,12 @@ def simple_tpl_from_pars(parnames, tplfilename='model.input.tpl'): writes a file `tplfilename` with each parameter name in `parnames` on a line """ - with open(tplfilename, 'w') as ofp: - ofp.write('ptf ~\n') - [ofp.write('~{0:^12}~\n'.format(cname)) for cname in parnames] + with open(tplfilename, "w") as ofp: + ofp.write("ptf ~\n") + [ofp.write("~{0:^12}~\n".format(cname)) for cname in parnames] -def simple_ins_from_obs(obsnames, insfilename='model.output.ins'): +def simple_ins_from_obs(obsnames, insfilename="model.output.ins"): """write a simple instruction file that reads the values named in obsnames in order, one per line from a model output file @@ -866,13 +931,14 @@ def simple_ins_from_obs(obsnames, insfilename='model.output.ins'): of a single line """ - with open(insfilename, 'w') as ofp: - ofp.write('pif ~\n') - [ofp.write('!{0}!\n'.format(cob)) for cob in obsnames] + with open(insfilename, "w") as ofp: + ofp.write("pif ~\n") + [ofp.write("!{0}!\n".format(cob)) for cob in obsnames] -def pst_from_parnames_obsnames(parnames, obsnames, - tplfilename='model.input.tpl', insfilename='model.output.ins'): +def pst_from_parnames_obsnames( + parnames, obsnames, tplfilename="model.input.tpl", insfilename="model.output.ins" +): """Creates a Pst object from a list of parameter names and a list of observation names. Args: @@ -888,10 +954,12 @@ def pst_from_parnames_obsnames(parnames, obsnames, simple_tpl_from_pars(parnames, tplfilename) simple_ins_from_obs(obsnames, insfilename) - modelinputfilename = tplfilename.replace('.tpl', '') - modeloutputfilename = insfilename.replace('.ins', '') + modelinputfilename = tplfilename.replace(".tpl", "") + modeloutputfilename = insfilename.replace(".ins", "") - return pyemu.Pst.from_io_files(tplfilename, modelinputfilename, insfilename, modeloutputfilename) + return pyemu.Pst.from_io_files( + tplfilename, modelinputfilename, insfilename, modeloutputfilename + ) def read_pestpp_runstorage(filename, irun=0, with_metadata=False): @@ -913,8 +981,14 @@ def read_pestpp_runstorage(filename, irun=0, with_metadata=False): """ - header_dtype = np.dtype([("n_runs", np.int64), ("run_size", np.int64), ("p_name_size", np.int64), - ("o_name_size", np.int64)]) + header_dtype = np.dtype( + [ + ("n_runs", np.int64), + ("run_size", np.int64), + ("p_name_size", np.int64), + ("o_name_size", np.int64), + ] + ) try: irun = int(irun) @@ -922,8 +996,9 @@ def read_pestpp_runstorage(filename, irun=0, with_metadata=False): if irun.lower() == "all": irun = irun.lower() else: - raise Exception("unrecognized 'irun': should be int or 'all', not '{0}'". - format(irun)) + raise Exception( + "unrecognized 'irun': should be int or 'all', not '{0}'".format(irun) + ) def status_str(r_status): if r_status == 0: @@ -939,10 +1014,20 @@ def status_str(r_status): f = open(filename, "rb") header = np.fromfile(f, dtype=header_dtype, count=1) p_name_size, o_name_size = header["p_name_size"][0], header["o_name_size"][0] - par_names = struct.unpack('{0}s'.format(p_name_size), - f.read(p_name_size))[0].strip().lower().decode().split('\0')[:-1] - obs_names = struct.unpack('{0}s'.format(o_name_size), - f.read(o_name_size))[0].strip().lower().decode().split('\0')[:-1] + par_names = ( + struct.unpack("{0}s".format(p_name_size), f.read(p_name_size))[0] + .strip() + .lower() + .decode() + .split("\0")[:-1] + ) + obs_names = ( + struct.unpack("{0}s".format(o_name_size), f.read(o_name_size))[0] + .strip() + .lower() + .decode() + .split("\0")[:-1] + ) n_runs, run_size = header["n_runs"][0], header["run_size"][0] run_start = f.tell() @@ -1017,13 +1102,19 @@ def jco_from_pestpp_runstorage(rnj_filename, pst_filename): """ - header_dtype = np.dtype([("n_runs", np.int64), ("run_size", np.int64), ("p_name_size", np.int64), - ("o_name_size", np.int64)]) + header_dtype = np.dtype( + [ + ("n_runs", np.int64), + ("run_size", np.int64), + ("p_name_size", np.int64), + ("o_name_size", np.int64), + ] + ) pst = pyemu.Pst(pst_filename) par = pst.parameter_data log_pars = set(par.loc[par.partrans == "log", "parnme"].values) - with open(rnj_filename, 'rb') as f: + with open(rnj_filename, "rb") as f: header = np.fromfile(f, dtype=header_dtype, count=1) try: @@ -1041,7 +1132,9 @@ def jco_from_pestpp_runstorage(rnj_filename, pst_filename): par_diff = base_par - par_df # check only one non-zero element per col(par) if len(par_diff[par_diff.parval1 != 0]) > 1: - raise Exception("more than one par diff - looks like the file wasn't created during jco filling...") + raise Exception( + "more than one par diff - looks like the file wasn't created during jco filling..." + ) parnme = par_diff[par_diff.parval1 != 0].index[0] parval = par_diff.parval1.loc[parnme] @@ -1049,11 +1142,18 @@ def jco_from_pestpp_runstorage(rnj_filename, pst_filename): jco_col = obs_diff / parval # some tracking, checks print("processing par {0}: {1}...".format(irun, parnme)) - print("%nzsens: {0}%...".format((jco_col[abs(jco_col.obsval) > 1e-8].shape[0] / jco_col.shape[0]) * 100.)) + print( + "%nzsens: {0}%...".format( + (jco_col[abs(jco_col.obsval) > 1e-8].shape[0] / jco_col.shape[0]) + * 100.0 + ) + ) jco_cols[parnme] = jco_col.obsval - jco_cols = pd.DataFrame.from_records(data=jco_cols, index=list(obs_diff.index.values)) + jco_cols = pd.DataFrame.from_records( + data=jco_cols, index=list(obs_diff.index.values) + ) jco_cols = pyemu.Jco.from_dataframe(jco_cols) @@ -1064,7 +1164,7 @@ def jco_from_pestpp_runstorage(rnj_filename, pst_filename): return jco_cols -def parse_dir_for_io_files(d,prepend_path=False): +def parse_dir_for_io_files(d, prepend_path=False): """ find template/input file pairs and instruction file/output file pairs by extension. @@ -1095,7 +1195,7 @@ def parse_dir_for_io_files(d,prepend_path=False): ins_files = [f for f in files if f.endswith(".ins")] out_files = [f.replace(".ins", "") for f in ins_files] if prepend_path: - tpl_files = [os.path.join(d,item) for item in tpl_files] + tpl_files = [os.path.join(d, item) for item in tpl_files] in_files = [os.path.join(d, item) for item in in_files] ins_files = [os.path.join(d, item) for item in ins_files] out_files = [os.path.join(d, item) for item in out_files] @@ -1103,8 +1203,9 @@ def parse_dir_for_io_files(d,prepend_path=False): return tpl_files, in_files, ins_files, out_files -def pst_from_io_files(tpl_files, in_files, ins_files, out_files, - pst_filename=None, pst_path=None): +def pst_from_io_files( + tpl_files, in_files, ins_files, out_files, pst_filename=None, pst_path=None +): """ create a Pst instance from model interface files. Args: @@ -1177,7 +1278,7 @@ def pst_from_io_files(tpl_files, in_files, ins_files, out_files, new_pst = pyemu.pst_utils.generic_pst(list(par_names), list(obs_names)) if "window" in platform.platform().lower() and pst_path == ".": - pst_path = '' + pst_path = "" new_pst.instruction_files = ins_files new_pst.output_files = out_files @@ -1188,15 +1289,22 @@ def pst_from_io_files(tpl_files, in_files, ins_files, out_files, new_pst.template_files = tpl_files new_pst.input_files = in_files else: - new_pst.template_files = [os.path.join( - pst_path, os.path.split(tpl_file)[-1]) for tpl_file in tpl_files] - new_pst.input_files = [os.path.join( - pst_path, os.path.split(in_file)[-1]) for in_file in in_files] + new_pst.template_files = [ + os.path.join(pst_path, os.path.split(tpl_file)[-1]) + for tpl_file in tpl_files + ] + new_pst.input_files = [ + os.path.join(pst_path, os.path.split(in_file)[-1]) for in_file in in_files + ] # now set the true path location to instruction files and output files - new_pst.instruction_files = [os.path.join( - pst_path, os.path.split(ins_file)[-1]) for ins_file in ins_files] - new_pst.output_files = [os.path.join( - pst_path, os.path.split(out_file)[-1]) for out_file in out_files] + new_pst.instruction_files = [ + os.path.join(pst_path, os.path.split(ins_file)[-1]) + for ins_file in ins_files + ] + new_pst.output_files = [ + os.path.join(pst_path, os.path.split(out_file)[-1]) + for out_file in out_files + ] new_pst.try_parse_name_metadata() if pst_filename: @@ -1205,11 +1313,16 @@ def pst_from_io_files(tpl_files, in_files, ins_files, out_files, return new_pst -wildass_guess_par_bounds_dict = {"hk": [0.01, 100.0], "vka": [0.1, 10.0], - "sy": [0.25, 1.75], "ss": [0.1, 10.0], - "cond": [0.01, 100.0], "flux": [0.25, 1.75], - "rech": [0.9, 1.1], "stage": [0.9, 1.1], - } +wildass_guess_par_bounds_dict = { + "hk": [0.01, 100.0], + "vka": [0.1, 10.0], + "sy": [0.25, 1.75], + "ss": [0.1, 10.0], + "cond": [0.01, 100.0], + "flux": [0.25, 1.75], + "rech": [0.9, 1.1], + "stage": [0.9, 1.1], +} class PstFromFlopyModel(object): @@ -1380,19 +1493,49 @@ class PstFromFlopyModel(object): """ - def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_props=[], - temporal_bc_props=[], temporal_list_props=[], grid_props=[], - grid_geostruct=None, pp_space=None, - zone_props=[], pp_geostruct=None, par_bounds_dict=None, sfr_pars=False, temporal_sfr_pars=False, - temporal_list_geostruct=None, remove_existing=False, k_zone_dict=None, - mflist_waterbudget=True, mfhyd=True, hds_kperk=[], use_pp_zones=False, - obssim_smp_pairs=None, external_tpl_in_pairs=None, - external_ins_out_pairs=None, extra_pre_cmds=None, - extra_model_cmds=None, extra_post_cmds=None, redirect_forward_output=True, - tmp_files=None, model_exe_name=None, build_prior=True, - sfr_obs=False, - spatial_bc_props=[], spatial_list_props=[], spatial_list_geostruct=None, - hfb_pars=False, kl_props=None, kl_num_eig=100, kl_geostruct=None): + def __init__( + self, + model, + new_model_ws, + org_model_ws=None, + pp_props=[], + const_props=[], + temporal_bc_props=[], + temporal_list_props=[], + grid_props=[], + grid_geostruct=None, + pp_space=None, + zone_props=[], + pp_geostruct=None, + par_bounds_dict=None, + sfr_pars=False, + temporal_sfr_pars=False, + temporal_list_geostruct=None, + remove_existing=False, + k_zone_dict=None, + mflist_waterbudget=True, + mfhyd=True, + hds_kperk=[], + use_pp_zones=False, + obssim_smp_pairs=None, + external_tpl_in_pairs=None, + external_ins_out_pairs=None, + extra_pre_cmds=None, + extra_model_cmds=None, + extra_post_cmds=None, + redirect_forward_output=True, + tmp_files=None, + model_exe_name=None, + build_prior=True, + sfr_obs=False, + spatial_bc_props=[], + spatial_list_props=[], + spatial_list_geostruct=None, + hfb_pars=False, + kl_props=None, + kl_num_eig=100, + kl_geostruct=None, + ): self.logger = pyemu.logger.Logger("PstFromFlopyModel.log") self.log = self.logger.log @@ -1436,30 +1579,45 @@ def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_pr if len(temporal_bc_props) > 0: if len(temporal_list_props) > 0: - self.logger.lraise("temporal_bc_props and temporal_list_props. " + \ - "temporal_bc_props is deprecated and replaced by temporal_list_props") - self.logger.warn("temporal_bc_props is deprecated and replaced by temporal_list_props") + self.logger.lraise( + "temporal_bc_props and temporal_list_props. " + + "temporal_bc_props is deprecated and replaced by temporal_list_props" + ) + self.logger.warn( + "temporal_bc_props is deprecated and replaced by temporal_list_props" + ) temporal_list_props = temporal_bc_props if len(spatial_bc_props) > 0: if len(spatial_list_props) > 0: - self.logger.lraise("spatial_bc_props and spatial_list_props. " + \ - "spatial_bc_props is deprecated and replaced by spatial_list_props") - self.logger.warn("spatial_bc_props is deprecated and replaced by spatial_list_props") + self.logger.lraise( + "spatial_bc_props and spatial_list_props. " + + "spatial_bc_props is deprecated and replaced by spatial_list_props" + ) + self.logger.warn( + "spatial_bc_props is deprecated and replaced by spatial_list_props" + ) spatial_list_props = spatial_bc_props self.temporal_list_props = temporal_list_props self.temporal_list_geostruct = temporal_list_geostruct if self.temporal_list_geostruct is None: - v = pyemu.geostats.ExpVario(contribution=1.0, a=180.0) # 180 correlation length - self.temporal_list_geostruct = pyemu.geostats.GeoStruct(variograms=v, name="temporal_list_geostruct") + v = pyemu.geostats.ExpVario( + contribution=1.0, a=180.0 + ) # 180 correlation length + self.temporal_list_geostruct = pyemu.geostats.GeoStruct( + variograms=v, name="temporal_list_geostruct" + ) self.spatial_list_props = spatial_list_props self.spatial_list_geostruct = spatial_list_geostruct if self.spatial_list_geostruct is None: - dist = 10 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) + dist = 10 * float( + max(self.m.dis.delr.array.max(), self.m.dis.delc.array.max()) + ) v = pyemu.geostats.ExpVario(contribution=1.0, a=dist) - self.spatial_list_geostruct = pyemu.geostats.GeoStruct(variograms=v, name="spatial_list_geostruct") + self.spatial_list_geostruct = pyemu.geostats.GeoStruct( + variograms=v, name="spatial_list_geostruct" + ) self.obssim_smp_pairs = obssim_smp_pairs self.hds_kperk = hds_kperk @@ -1475,7 +1633,9 @@ def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_pr self.tmp_files.extend(tmp_files) if k_zone_dict is None: - self.k_zone_dict = {k: self.m.bas6.ibound[k].array for k in np.arange(self.m.nlay)} + self.k_zone_dict = { + k: self.m.bas6.ibound[k].array for k in np.arange(self.m.nlay) + } else: # check if k_zone_dict is a dictionary of dictionaries if np.all([isinstance(v, dict) for v in k_zone_dict.values()]): @@ -1483,25 +1643,37 @@ def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_pr for par_key in k_zone_dict.keys(): for k, arr in k_zone_dict[par_key].items(): if k not in np.arange(self.m.nlay): - self.logger.lraise("k_zone_dict for par {1}, layer index not in nlay:{0}". - format(k, par_key)) + self.logger.lraise( + "k_zone_dict for par {1}, layer index not in nlay:{0}".format( + k, par_key + ) + ) if arr.shape != (self.m.nrow, self.m.ncol): - self.logger.lraise("k_zone_dict arr for k {0} for par{2} has wrong shape:{1}". - format(k, arr.shape, par_key)) + self.logger.lraise( + "k_zone_dict arr for k {0} for par{2} has wrong shape:{1}".format( + k, arr.shape, par_key + ) + ) else: for k, arr in k_zone_dict.items(): if k not in np.arange(self.m.nlay): - self.logger.lraise("k_zone_dict layer index not in nlay:{0}". - format(k)) + self.logger.lraise( + "k_zone_dict layer index not in nlay:{0}".format(k) + ) if arr.shape != (self.m.nrow, self.m.ncol): - self.logger.lraise("k_zone_dict arr for k {0} has wrong shape:{1}". - format(k, arr.shape)) + self.logger.lraise( + "k_zone_dict arr for k {0} has wrong shape:{1}".format( + k, arr.shape + ) + ) self.k_zone_dict = k_zone_dict # add any extra commands to the forward run lines - for alist, ilist in zip([self.frun_pre_lines, self.frun_model_lines, self.frun_post_lines], - [extra_pre_cmds, extra_model_cmds, extra_post_cmds]): + for alist, ilist in zip( + [self.frun_pre_lines, self.frun_model_lines, self.frun_post_lines], + [extra_pre_cmds, extra_model_cmds, extra_post_cmds], + ): if ilist is None: continue @@ -1515,11 +1687,17 @@ def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_pr if model_exe_name is None: model_exe_name = self.m.exe_name - self.logger.warn("using flopy binary to execute the model:{0}".format(model)) + self.logger.warn( + "using flopy binary to execute the model:{0}".format(model) + ) if redirect_forward_output: - line = "pyemu.os_utils.run('{0} {1} 1>{1}.stdout 2>{1}.stderr')".format(model_exe_name, self.m.namefile) + line = "pyemu.os_utils.run('{0} {1} 1>{1}.stdout 2>{1}.stderr')".format( + model_exe_name, self.m.namefile + ) else: - line = "pyemu.os_utils.run('{0} {1} ')".format(model_exe_name, self.m.namefile) + line = "pyemu.os_utils.run('{0} {1} ')".format( + model_exe_name, self.m.namefile + ) self.logger.statement("forward_run line:{0}".format(line)) self.frun_model_lines.append(line) @@ -1559,16 +1737,26 @@ def __init__(self, model, new_model_ws, org_model_ws=None, pp_props=[], const_pr self.parcov = self.build_prior() else: self.parcov = None - self.log("saving intermediate _setup_<> dfs into {0}". - format(self.m.model_ws)) + self.log("saving intermediate _setup_<> dfs into {0}".format(self.m.model_ws)) for tag, df in self.par_dfs.items(): - df.to_csv(os.path.join(self.m.model_ws, "_setup_par_{0}_{1}.csv". - format(tag.replace(" ", '_'), self.pst_name))) + df.to_csv( + os.path.join( + self.m.model_ws, + "_setup_par_{0}_{1}.csv".format( + tag.replace(" ", "_"), self.pst_name + ), + ) + ) for tag, df in self.obs_dfs.items(): - df.to_csv(os.path.join(self.m.model_ws, "_setup_obs_{0}_{1}.csv". - format(tag.replace(" ", '_'), self.pst_name))) - self.log("saving intermediate _setup_<> dfs into {0}". - format(self.m.model_ws)) + df.to_csv( + os.path.join( + self.m.model_ws, + "_setup_obs_{0}_{1}.csv".format( + tag.replace(" ", "_"), self.pst_name + ), + ) + ) + self.log("saving intermediate _setup_<> dfs into {0}".format(self.m.model_ws)) self.logger.statement("all done") @@ -1579,18 +1767,29 @@ def _setup_sfr_obs(self): if self.m.sfr is None: self.logger.lraise("no sfr package found...") - org_sfr_out_file = os.path.join(self.org_model_ws, "{0}.sfr.out".format(self.m.name)) + org_sfr_out_file = os.path.join( + self.org_model_ws, "{0}.sfr.out".format(self.m.name) + ) if not os.path.exists(org_sfr_out_file): - self.logger.lraise("setup_sfr_obs() error: could not locate existing sfr out file: {0}". - format(org_sfr_out_file)) - new_sfr_out_file = os.path.join(self.m.model_ws, os.path.split(org_sfr_out_file)[-1]) + self.logger.lraise( + "setup_sfr_obs() error: could not locate existing sfr out file: {0}".format( + org_sfr_out_file + ) + ) + new_sfr_out_file = os.path.join( + self.m.model_ws, os.path.split(org_sfr_out_file)[-1] + ) shutil.copy2(org_sfr_out_file, new_sfr_out_file) seg_group_dict = None if isinstance(self.sfr_obs, dict): seg_group_dict = self.sfr_obs - df = pyemu.gw_utils.setup_sfr_obs(new_sfr_out_file, seg_group_dict=seg_group_dict, - model=self.m, include_path=True) + df = pyemu.gw_utils.setup_sfr_obs( + new_sfr_out_file, + seg_group_dict=seg_group_dict, + model=self.m, + include_path=True, + ) if df is not None: self.obs_dfs["sfr"] = df self.frun_post_lines.append("pyemu.gw_utils.apply_sfr_obs()") @@ -1605,8 +1804,8 @@ def _setup_sfr_pars(self, par_cols=None, include_temporal_pars=None): seg_pars = True par_dfs = {} df = pyemu.gw_utils.setup_sfr_seg_parameters( - self.m, par_cols=par_cols, - include_temporal_pars=include_temporal_pars) # now just pass model + self.m, par_cols=par_cols, include_temporal_pars=include_temporal_pars + ) # now just pass model # self.par_dfs["sfr"] = df if df.empty: warnings.warn("No sfr segment parameters have been set up", PyemuWarning) @@ -1633,7 +1832,10 @@ def _setup_sfr_pars(self, par_cols=None, include_temporal_pars=None): if len(par_dfs["sfr"]) > 0: self.par_dfs["sfr"] = pd.concat(par_dfs["sfr"]) self.frun_pre_lines.append( - "pyemu.gw_utils.apply_sfr_parameters(seg_pars={0}, reach_pars={1})".format(seg_pars, reach_pars)) + "pyemu.gw_utils.apply_sfr_parameters(seg_pars={0}, reach_pars={1})".format( + seg_pars, reach_pars + ) + ) else: warnings.warn("No sfr parameters have been set up!", PyemuWarning) @@ -1658,11 +1860,13 @@ def _setup_mult_dirs(self): set_dirs = [] # if len(self.pp_props) > 0 or len(self.zone_props) > 0 or \ # len(self.grid_props) > 0: - if self.pp_props is not None or \ - self.zone_props is not None or \ - self.grid_props is not None or \ - self.const_props is not None or \ - self.kl_props is not None: + if ( + self.pp_props is not None + or self.zone_props is not None + or self.grid_props is not None + or self.const_props is not None + or self.kl_props is not None + ): set_dirs.append(self.arr_org) set_dirs.append(self.arr_mlt) # if len(self.bc_props) > 0: @@ -1678,8 +1882,7 @@ def _setup_mult_dirs(self): if self.remove_existing: shutil.rmtree(d, onerror=remove_readonly) else: - raise Exception("dir '{0}' already exists". - format(d)) + raise Exception("dir '{0}' already exists".format(d)) os.mkdir(d) self.log("setting up '{0}' dir".format(d)) @@ -1691,8 +1894,11 @@ def _setup_model(self, model, org_model_ws, new_model_ws): """ split_new_mws = [i for i in os.path.split(new_model_ws) if len(i) > 0] if len(split_new_mws) != 1: - self.logger.lraise("new_model_ws can only be 1 folder-level deep:{0}". - format(str(split_new_mws))) + self.logger.lraise( + "new_model_ws can only be 1 folder-level deep:{0}".format( + str(split_new_mws) + ) + ) if isinstance(model, str): self.log("loading flopy model") @@ -1703,8 +1909,9 @@ def _setup_model(self, model, org_model_ws, new_model_ws): # prepare the flopy model self.org_model_ws = org_model_ws self.new_model_ws = new_model_ws - self.m = flopy.modflow.Modflow.load(model, model_ws=org_model_ws, - check=False, verbose=True, forgive=False) + self.m = flopy.modflow.Modflow.load( + model, model_ws=org_model_ws, check=False, verbose=True, forgive=False + ) self.log("loading flopy model") else: self.m = model @@ -1714,7 +1921,7 @@ def _setup_model(self, model, org_model_ws, new_model_ws): self.log("updating model attributes") self.m.array_free_format = True self.m.free_format_input = True - self.m.external_path = '.' + self.m.external_path = "." self.log("updating model attributes") if os.path.exists(new_model_ws): if not self.remove_existing: @@ -1724,7 +1931,7 @@ def _setup_model(self, model, org_model_ws, new_model_ws): shutil.rmtree(new_model_ws, onerror=pyemu.os_utils._remove_readonly) time.sleep(1) self.m.change_model_ws(new_model_ws, reset_external=True) - self.m.exe_name = self.m.exe_name.replace(".exe", '') + self.m.exe_name = self.m.exe_name.replace(".exe", "") self.m.exe = self.m.version self.log("writing new modflow input files") self.m.write_input() @@ -1748,12 +1955,20 @@ def _prep_mlt_arrays(self): writes generic (ones) multiplier arrays """ - par_props = [self.pp_props, self.grid_props, - self.zone_props, self.const_props, - self.kl_props] - par_suffixs = [self.pp_suffix, self.gr_suffix, - self.zn_suffix, self.cn_suffix, - self.kl_suffix] + par_props = [ + self.pp_props, + self.grid_props, + self.zone_props, + self.const_props, + self.kl_props, + ] + par_suffixs = [ + self.pp_suffix, + self.gr_suffix, + self.zn_suffix, + self.cn_suffix, + self.kl_suffix, + ] # Need to remove props and suffixes for which no info was provided (e.g. still None) del_idx = [] @@ -1761,8 +1976,8 @@ def _prep_mlt_arrays(self): if cp is None: del_idx.append(i) for i in del_idx[::-1]: - del (par_props[i]) - del (par_suffixs[i]) + del par_props[i] + del par_suffixs[i] mlt_dfs = [] for par_prop, suffix in zip(par_props, par_suffixs): @@ -1772,7 +1987,7 @@ def _prep_mlt_arrays(self): if len(par_prop) == 0: continue for pakattr, k_org in par_prop: - attr_name = pakattr.split('.')[1] + attr_name = pakattr.split(".")[1] pak, attr = self._parse_pakattr(pakattr) ks = np.arange(self.m.nlay) if isinstance(attr, flopy.utils.Transient2d): @@ -1780,13 +1995,13 @@ def _prep_mlt_arrays(self): try: k_parse = self._parse_k(k_org, ks) except Exception as e: - self.logger.lraise("error parsing k {0}:{1}". - format(k_org, str(e))) + self.logger.lraise("error parsing k {0}:{1}".format(k_org, str(e))) org, mlt, mod, layer = [], [], [], [] c = self._get_count(attr_name) mlt_prefix = "{0}{1}".format(attr_name, c) - mlt_name = os.path.join(self.arr_mlt, "{0}.dat{1}" - .format(mlt_prefix, suffix)) + mlt_name = os.path.join( + self.arr_mlt, "{0}.dat{1}".format(mlt_prefix, suffix) + ) for k in k_parse: # horrible kludge to avoid passing int64 to flopy # this gift may give again... @@ -1805,7 +2020,14 @@ def _prep_mlt_arrays(self): mod.append(os.path.join(self.m.external_path, fname)) mlt.append(mlt_name) org.append(os.path.join(self.arr_org, fname)) - df = pd.DataFrame({"org_file": org, "mlt_file": mlt, "model_file": mod, "layer": layer}) + df = pd.DataFrame( + { + "org_file": org, + "mlt_file": mlt, + "model_file": mod, + "layer": layer, + } + ) df.loc[:, "suffix"] = suffix df.loc[:, "prefix"] = mlt_prefix df.loc[:, "attr_name"] = attr_name @@ -1820,8 +2042,11 @@ def _write_u2d(self, u2d): """ filename = os.path.split(u2d.filename)[-1] - np.savetxt(os.path.join(self.m.model_ws, self.arr_org, filename), - u2d.array, fmt="%15.6E") + np.savetxt( + os.path.join(self.m.model_ws, self.arr_org, filename), + u2d.array, + fmt="%15.6E", + ) return filename def _write_const_tpl(self, name, tpl_file, zn_array): @@ -1829,7 +2054,7 @@ def _write_const_tpl(self, name, tpl_file, zn_array): """ parnme = [] - with open(os.path.join(self.m.model_ws, tpl_file), 'w') as f: + with open(os.path.join(self.m.model_ws, tpl_file), "w") as f: f.write("ptf ~\n") for i in range(self.m.nrow): for j in range(self.m.ncol): @@ -1838,15 +2063,16 @@ def _write_const_tpl(self, name, tpl_file, zn_array): else: pname = "{0}{1}".format(name, self.cn_suffix) if len(pname) > 12: - self.logger.warn("zone pname too long for pest:{0}". \ - format(pname)) + self.logger.warn( + "zone pname too long for pest:{0}".format(pname) + ) parnme.append(pname) pname = " ~ {0} ~".format(pname) f.write(pname) f.write("\n") df = pd.DataFrame({"parnme": parnme}, index=parnme) # df.loc[:,"pargp"] = "{0}{1}".format(self.cn_suffixname) - df.loc[:, "pargp"] = "{0}_{1}".format(self.cn_suffix.replace('_', ''), name) + df.loc[:, "pargp"] = "{0}_{1}".format(self.cn_suffix.replace("_", ""), name) df.loc[:, "tpl"] = tpl_file return df @@ -1855,25 +2081,26 @@ def _write_grid_tpl(self, name, tpl_file, zn_array): """ parnme, x, y = [], [], [] - with open(os.path.join(self.m.model_ws, tpl_file), 'w') as f: + with open(os.path.join(self.m.model_ws, tpl_file), "w") as f: f.write("ptf ~\n") for i in range(self.m.nrow): for j in range(self.m.ncol): if zn_array[i, j] < 1: - pname = ' 1.0 ' + pname = " 1.0 " else: pname = "{0}{1:03d}{2:03d}".format(name, i, j) if len(pname) > 12: - self.logger.warn("grid pname too long for pest:{0}". \ - format(pname)) + self.logger.warn( + "grid pname too long for pest:{0}".format(pname) + ) parnme.append(pname) - pname = ' ~ {0} ~ '.format(pname) + pname = " ~ {0} ~ ".format(pname) x.append(self.m.sr.xcentergrid[i, j]) y.append(self.m.sr.ycentergrid[i, j]) f.write(pname) f.write("\n") df = pd.DataFrame({"parnme": parnme, "x": x, "y": y}, index=parnme) - df.loc[:, "pargp"] = "{0}{1}".format(self.gr_suffix.replace('_', ''), name) + df.loc[:, "pargp"] = "{0}{1}".format(self.gr_suffix.replace("_", ""), name) df.loc[:, "tpl"] = tpl_file return df @@ -1885,12 +2112,17 @@ def _grid_prep(self): return if self.grid_geostruct is None: - self.logger.warn("grid_geostruct is None," \ - " using ExpVario with contribution=1 and a=(max(delc,delr)*10") - dist = 10 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) + self.logger.warn( + "grid_geostruct is None," + " using ExpVario with contribution=1 and a=(max(delc,delr)*10" + ) + dist = 10 * float( + max(self.m.dis.delr.array.max(), self.m.dis.delc.array.max()) + ) v = pyemu.geostats.ExpVario(contribution=1.0, a=dist) - self.grid_geostruct = pyemu.geostats.GeoStruct(variograms=v, name="grid_geostruct", transform="log") + self.grid_geostruct = pyemu.geostats.GeoStruct( + variograms=v, name="grid_geostruct", transform="log" + ) def _pp_prep(self, mlt_df): """ prepare pilot point based parameterization @@ -1903,17 +2135,24 @@ def _pp_prep(self, mlt_df): self.logger.warn("pp_space is None, using 10...\n") self.pp_space = 10 if self.pp_geostruct is None: - self.logger.warn("pp_geostruct is None," \ - " using ExpVario with contribution=1 and a=(pp_space*max(delr,delc))") - pp_dist = self.pp_space * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) + self.logger.warn( + "pp_geostruct is None," + " using ExpVario with contribution=1 and a=(pp_space*max(delr,delc))" + ) + pp_dist = self.pp_space * float( + max(self.m.dis.delr.array.max(), self.m.dis.delc.array.max()) + ) v = pyemu.geostats.ExpVario(contribution=1.0, a=pp_dist) - self.pp_geostruct = pyemu.geostats.GeoStruct(variograms=v, name="pp_geostruct", transform="log") + self.pp_geostruct = pyemu.geostats.GeoStruct( + variograms=v, name="pp_geostruct", transform="log" + ) pp_df = mlt_df.loc[mlt_df.suffix == self.pp_suffix, :] layers = pp_df.layer.unique() layers.sort() - pp_dict = {l: list(pp_df.loc[pp_df.layer == l, "prefix"].unique()) for l in layers} + pp_dict = { + l: list(pp_df.loc[pp_df.layer == l, "prefix"].unique()) for l in layers + } # big assumption here - if prefix is listed more than once, use the lowest layer index pp_dict_sort = {} for i, l in enumerate(layers): @@ -1921,7 +2160,7 @@ def _pp_prep(self, mlt_df): pl = list(p) pl.sort() pp_dict_sort[l] = pl - for ll in layers[i + 1:]: + for ll in layers[i + 1 :]: pp = set(pp_dict[ll]) d = list(pp - p) d.sort() @@ -1935,20 +2174,28 @@ def _pp_prep(self, mlt_df): if self.use_pp_zones: # check if k_zone_dict is a dictionary of dictionaries if np.all([isinstance(v, dict) for v in self.k_zone_dict.values()]): - ib = {p.split('.')[-1]: k_dict for p, k_dict in self.k_zone_dict.items()} + ib = { + p.split(".")[-1]: k_dict for p, k_dict in self.k_zone_dict.items() + } for attr in pp_df.attr_name.unique(): - if attr not in [p.split('.')[-1] for p in ib.keys()]: - if 'general_zn' not in ib.keys(): - warnings.warn("Dictionary of dictionaries passed as zones, {0} not in keys: {1}. " - "Will use ibound for zones".format(attr, ib.keys()), PyemuWarning) + if attr not in [p.split(".")[-1] for p in ib.keys()]: + if "general_zn" not in ib.keys(): + warnings.warn( + "Dictionary of dictionaries passed as zones, {0} not in keys: {1}. " + "Will use ibound for zones".format(attr, ib.keys()), + PyemuWarning, + ) else: self.logger.statement( "Dictionary of dictionaries passed as pp zones, " - "using 'general_zn' for {0}".format(attr)) - if 'general_zn' not in ib.keys(): - ib['general_zn'] = {k: self.m.bas6.ibound[k].array for k in range(self.m.nlay)} + "using 'general_zn' for {0}".format(attr) + ) + if "general_zn" not in ib.keys(): + ib["general_zn"] = { + k: self.m.bas6.ibound[k].array for k in range(self.m.nlay) + } else: - ib = {'general_zn': self.k_zone_dict} + ib = {"general_zn": self.k_zone_dict} else: ib = {} for k in range(self.m.nlay): @@ -1959,30 +2206,35 @@ def _pp_prep(self, mlt_df): if np.any(i < 0): u, c = np.unique(i[i > 0], return_counts=True) counts = dict(zip(u, c)) - mx = -1.0e+10 + mx = -1.0e10 imx = None for u, c in counts.items(): if c > mx: mx = c imx = u - self.logger.warn("resetting negative ibound values for PP zone"+ \ - "array in layer {0} : {1}".format(k+1, u)) - i[i<0] = u + self.logger.warn( + "resetting negative ibound values for PP zone" + + "array in layer {0} : {1}".format(k + 1, u) + ) + i[i < 0] = u ib[k] = i - ib = {'general_zn': ib} - pp_df = pyemu.pp_utils.setup_pilotpoints_grid(self.m, - ibound=ib, - use_ibound_zones=self.use_pp_zones, - prefix_dict=pp_dict, - every_n_cell=self.pp_space, - pp_dir=self.m.model_ws, - tpl_dir=self.m.model_ws, - shapename=os.path.join( - self.m.model_ws, "pp.shp")) - self.logger.statement("{0} pilot point parameters created". - format(pp_df.shape[0])) - self.logger.statement("pilot point 'pargp':{0}". - format(','.join(pp_df.pargp.unique()))) + ib = {"general_zn": ib} + pp_df = pyemu.pp_utils.setup_pilotpoints_grid( + self.m, + ibound=ib, + use_ibound_zones=self.use_pp_zones, + prefix_dict=pp_dict, + every_n_cell=self.pp_space, + pp_dir=self.m.model_ws, + tpl_dir=self.m.model_ws, + shapename=os.path.join(self.m.model_ws, "pp.shp"), + ) + self.logger.statement( + "{0} pilot point parameters created".format(pp_df.shape[0]) + ) + self.logger.statement( + "pilot point 'pargp':{0}".format(",".join(pp_df.pargp.unique())) + ) self.log("calling setup_pilot_point_grid()") # calc factors for each layer @@ -1994,21 +2246,29 @@ def _pp_prep(self, mlt_df): for pg in pargp: ks = pp_df.loc[pp_df.pargp == pg, "k"].unique() if len(ks) == 0: - self.logger.lraise("something is wrong in fac calcs for par group {0}".format(pg)) + self.logger.lraise( + "something is wrong in fac calcs for par group {0}".format(pg) + ) if len(ks) == 1: - if np.all([isinstance(v, dict) for v in ib.values()]): # check is dict of dicts + if np.all( + [isinstance(v, dict) for v in ib.values()] + ): # check is dict of dicts if np.any([pg.startswith(p) for p in ib.keys()]): p = next(p for p in ib.keys() if pg.startswith(p)) # get dict relating to parameter prefix ib_k = ib[p][ks[0]] else: - p = 'general_zn' + p = "general_zn" ib_k = ib[p][ks[0]] else: ib_k = ib[ks[0]] if len(ks) != 1: # TODO # self.logger.lraise("something is wrong in fac calcs for par group {0}".format(pg)) - self.logger.warn("multiple k values for {0},forming composite zone array...".format(pg)) + self.logger.warn( + "multiple k values for {0},forming composite zone array...".format( + pg + ) + ) ib_k = np.zeros((self.m.nrow, self.m.ncol)) for k in ks: t = ib["general_zn"][k].copy() @@ -2023,12 +2283,19 @@ def _pp_prep(self, mlt_df): var_file = fac_file.replace(".fac", ".var.dat") pp_df_k = pp_df.loc[pp_df.pargp == pg] if kattr_id not in pp_processed: - self.logger.statement("saving krige variance file:{0}" - .format(var_file)) - self.logger.statement("saving krige factors file:{0}" - .format(fac_file)) + self.logger.statement( + "saving krige variance file:{0}".format(var_file) + ) + self.logger.statement( + "saving krige factors file:{0}".format(fac_file) + ) ok_pp = pyemu.geostats.OrdinaryKrige(self.pp_geostruct, pp_df_k) - ok_pp.calc_factors_grid(self.m.sr, var_filename=var_file, zone_array=ib_k, num_threads=10) + ok_pp.calc_factors_grid( + self.m.sr, + var_filename=var_file, + zone_array=ib_k, + num_threads=10, + ) ok_pp.to_grid_factors_file(fac_file) pp_processed.add(kattr_id) fac_files[kp_id] = fac_file @@ -2036,45 +2303,59 @@ def _pp_prep(self, mlt_df): pp_dfs_k[kp_id] = pp_df_k for kp_id, fac_file in fac_files.items(): - k = int(kp_id.split('_')[0]) - pp_prefix = kp_id.split('_', 1)[-1] + k = int(kp_id.split("_")[0]) + pp_prefix = kp_id.split("_", 1)[-1] # pp_files = pp_df.pp_filename.unique() fac_file = os.path.split(fac_file)[-1] # pp_prefixes = pp_dict[k] # for pp_prefix in pp_prefixes: self.log("processing pp_prefix:{0}".format(pp_prefix)) if pp_prefix not in pp_array_file.keys(): - self.logger.lraise("{0} not in self.pp_array_file.keys()". - format(pp_prefix, ','. - join(pp_array_file.keys()))) - - out_file = os.path.join(self.arr_mlt, os.path.split(pp_array_file[pp_prefix])[-1]) - - pp_files = pp_df.loc[pp_df.pp_filename.apply( - lambda x: - os.path.split(x)[-1].split( - '.')[0] == "{0}pp".format(pp_prefix)), 'pp_filename'] + self.logger.lraise( + "{0} not in self.pp_array_file.keys()".format( + pp_prefix, ",".join(pp_array_file.keys()) + ) + ) + + out_file = os.path.join( + self.arr_mlt, os.path.split(pp_array_file[pp_prefix])[-1] + ) + + pp_files = pp_df.loc[ + pp_df.pp_filename.apply( + lambda x: os.path.split(x)[-1].split(".")[0] + == "{0}pp".format(pp_prefix) + ), + "pp_filename", + ] if pp_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of pp_files found:{0}".format(','.join(pp_files))) + self.logger.lraise( + "wrong number of pp_files found:{0}".format(",".join(pp_files)) + ) pp_file = os.path.split(pp_files.iloc[0])[-1] pp_df.loc[pp_df.pargp == pp_prefix, "fac_file"] = fac_file pp_df.loc[pp_df.pargp == pp_prefix, "pp_file"] = pp_file pp_df.loc[pp_df.pargp == pp_prefix, "out_file"] = out_file pp_df.loc[:, "pargp"] = pp_df.pargp.apply(lambda x: "pp_{0}".format(x)) - out_files = mlt_df.loc[mlt_df.mlt_file. - apply(lambda x: x.endswith(self.pp_suffix)), "mlt_file"] + out_files = mlt_df.loc[ + mlt_df.mlt_file.apply(lambda x: x.endswith(self.pp_suffix)), "mlt_file" + ] # mlt_df.loc[:,"fac_file"] = np.NaN # mlt_df.loc[:,"pp_file"] = np.NaN for out_file in out_files: pp_df_pf = pp_df.loc[pp_df.out_file == out_file, :] fac_files = pp_df_pf.fac_file if fac_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of fac files:{0}".format(str(fac_files.unique()))) + self.logger.lraise( + "wrong number of fac files:{0}".format(str(fac_files.unique())) + ) fac_file = fac_files.iloc[0] pp_files = pp_df_pf.pp_file if pp_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of pp files:{0}".format(str(pp_files.unique()))) + self.logger.lraise( + "wrong number of pp files:{0}".format(str(pp_files.unique())) + ) pp_file = pp_files.iloc[0] mlt_df.loc[mlt_df.mlt_file == out_file, "fac_file"] = fac_file mlt_df.loc[mlt_df.mlt_file == out_file, "pp_file"] = pp_file @@ -2090,12 +2371,17 @@ def _kl_prep(self, mlt_df): return if self.kl_geostruct is None: - self.logger.warn("kl_geostruct is None," \ - " using ExpVario with contribution=1 and a=(10.0*max(delr,delc))") - kl_dist = 10.0 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) + self.logger.warn( + "kl_geostruct is None," + " using ExpVario with contribution=1 and a=(10.0*max(delr,delc))" + ) + kl_dist = 10.0 * float( + max(self.m.dis.delr.array.max(), self.m.dis.delc.array.max()) + ) v = pyemu.geostats.ExpVario(contribution=1.0, a=kl_dist) - self.kl_geostruct = pyemu.geostats.GeoStruct(variograms=v, name="kl_geostruct", transform="log") + self.kl_geostruct = pyemu.geostats.GeoStruct( + variograms=v, name="kl_geostruct", transform="log" + ) kl_df = mlt_df.loc[mlt_df.suffix == self.kl_suffix, :] layers = kl_df.layer.unique() @@ -2116,22 +2402,30 @@ def _kl_prep(self, mlt_df): self.log("calling kl_setup() with factors file {0}".format(fac_file)) - kl_df = kl_setup(self.kl_num_eig, self.m.sr, self.kl_geostruct, kl_prefix, - factors_file=fac_file, basis_file=fac_file + ".basis.jcb", - tpl_dir=self.m.model_ws) - self.logger.statement("{0} kl parameters created". - format(kl_df.shape[0])) - self.logger.statement("kl 'pargp':{0}". - format(','.join(kl_df.pargp.unique()))) + kl_df = kl_setup( + self.kl_num_eig, + self.m.sr, + self.kl_geostruct, + kl_prefix, + factors_file=fac_file, + basis_file=fac_file + ".basis.jcb", + tpl_dir=self.m.model_ws, + ) + self.logger.statement("{0} kl parameters created".format(kl_df.shape[0])) + self.logger.statement("kl 'pargp':{0}".format(",".join(kl_df.pargp.unique()))) self.log("calling kl_setup() with factors file {0}".format(fac_file)) kl_mlt_df = mlt_df.loc[mlt_df.suffix == self.kl_suffix] for prefix in kl_df.prefix.unique(): prefix_df = kl_df.loc[kl_df.prefix == prefix, :] in_file = os.path.split(prefix_df.loc[:, "in_file"].iloc[0])[-1] - assert prefix in mlt_df.prefix.values, "{0}:{1}".format(prefix, mlt_df.prefix) + assert prefix in mlt_df.prefix.values, "{0}:{1}".format( + prefix, mlt_df.prefix + ) mlt_df.loc[mlt_df.prefix == prefix, "pp_file"] = in_file - mlt_df.loc[mlt_df.prefix == prefix, "fac_file"] = os.path.split(fac_file)[-1] + mlt_df.loc[mlt_df.prefix == prefix, "fac_file"] = os.path.split(fac_file)[ + -1 + ] print(kl_mlt_df) mlt_df.loc[mlt_df.suffix == self.kl_suffix, "tpl_file"] = np.NaN @@ -2145,7 +2439,9 @@ def _setup_array_pars(self): mlt_df = self._prep_mlt_arrays() if mlt_df is None: return - mlt_df.loc[:, "tpl_file"] = mlt_df.mlt_file.apply(lambda x: os.path.split(x)[-1] + ".tpl") + mlt_df.loc[:, "tpl_file"] = mlt_df.mlt_file.apply( + lambda x: os.path.split(x)[-1] + ".tpl" + ) # mlt_df.loc[mlt_df.tpl_file.apply(lambda x:pd.notnull(x.pp_file)),"tpl_file"] = np.NaN mlt_files = mlt_df.mlt_file.unique() # for suffix,tpl_file,layer,name in zip(self.mlt_df.suffix, @@ -2155,14 +2451,12 @@ def _setup_array_pars(self): for mlt_file in mlt_files: suffixes = mlt_df.loc[mlt_df.mlt_file == mlt_file, "suffix"] if suffixes.unique().shape[0] != 1: - self.logger.lraise("wrong number of suffixes for {0}" \ - .format(mlt_file)) + self.logger.lraise("wrong number of suffixes for {0}".format(mlt_file)) suffix = suffixes.iloc[0] tpl_files = mlt_df.loc[mlt_df.mlt_file == mlt_file, "tpl_file"] if tpl_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of tpl_files for {0}" \ - .format(mlt_file)) + self.logger.lraise("wrong number of tpl_files for {0}".format(mlt_file)) tpl_file = tpl_files.iloc[0] layers = mlt_df.loc[mlt_df.mlt_file == mlt_file, "layer"] # if layers.unique().shape[0] != 1: @@ -2171,12 +2465,13 @@ def _setup_array_pars(self): layer = layers.iloc[0] names = mlt_df.loc[mlt_df.mlt_file == mlt_file, "prefix"] if names.unique().shape[0] != 1: - self.logger.lraise("wrong number of names for {0}" \ - .format(mlt_file)) + self.logger.lraise("wrong number of names for {0}".format(mlt_file)) name = names.iloc[0] attr_names = mlt_df.loc[mlt_df.mlt_file == mlt_file, "attr_name"] if attr_names.unique().shape[0] != 1: - self.logger.lraise("wrong number of attr_names for {0}".format(mlt_file)) + self.logger.lraise( + "wrong number of attr_names for {0}".format(mlt_file) + ) attr_name = attr_names.iloc[0] # ib = self.k_zone_dict[layer] @@ -2185,41 +2480,72 @@ def _setup_array_pars(self): self.log("writing const tpl:{0}".format(tpl_file)) # df = self.write_const_tpl(name,tpl_file,self.m.bas6.ibound[layer].array) try: - df = write_const_tpl(name, os.path.join(self.m.model_ws, tpl_file), self.cn_suffix, - self.m.bas6.ibound[layer].array, (self.m.nrow, self.m.ncol), self.m.sr) + df = write_const_tpl( + name, + os.path.join(self.m.model_ws, tpl_file), + self.cn_suffix, + self.m.bas6.ibound[layer].array, + (self.m.nrow, self.m.ncol), + self.m.sr, + ) except Exception as e: - self.logger.lraise("error writing const template: {0}".format(str(e))) + self.logger.lraise( + "error writing const template: {0}".format(str(e)) + ) self.log("writing const tpl:{0}".format(tpl_file)) elif suffix == self.gr_suffix: self.log("writing grid tpl:{0}".format(tpl_file)) # df = self.write_grid_tpl(name,tpl_file,self.m.bas6.ibound[layer].array) try: - df = write_grid_tpl(name, os.path.join(self.m.model_ws, tpl_file), self.gr_suffix, - self.m.bas6.ibound[layer].array, (self.m.nrow, self.m.ncol), self.m.sr) + df = write_grid_tpl( + name, + os.path.join(self.m.model_ws, tpl_file), + self.gr_suffix, + self.m.bas6.ibound[layer].array, + (self.m.nrow, self.m.ncol), + self.m.sr, + ) except Exception as e: - self.logger.lraise("error writing grid template: {0}".format(str(e))) + self.logger.lraise( + "error writing grid template: {0}".format(str(e)) + ) self.log("writing grid tpl:{0}".format(tpl_file)) elif suffix == self.zn_suffix: self.log("writing zone tpl:{0}".format(tpl_file)) - if np.all([isinstance(v, dict) for v in self.k_zone_dict.values()]): # check is dict of dicts - if attr_name in [p.split('.')[-1] for p in self.k_zone_dict.keys()]: - k_zone_dict = next(k_dict for p, k_dict in self.k_zone_dict.items() - if p.split('.')[-1] == attr_name) # get dict relating to parameter prefix + if np.all( + [isinstance(v, dict) for v in self.k_zone_dict.values()] + ): # check is dict of dicts + if attr_name in [p.split(".")[-1] for p in self.k_zone_dict.keys()]: + k_zone_dict = next( + k_dict + for p, k_dict in self.k_zone_dict.items() + if p.split(".")[-1] == attr_name + ) # get dict relating to parameter prefix else: - assert 'general_zn' in self.k_zone_dict.keys(), \ - "Neither {0} nor 'general_zn' are in k_zone_dict keys: {1}".format(attr_name, - self.k_zone_dict.keys()) - k_zone_dict = self.k_zone_dict['general_zn'] + assert ( + "general_zn" in self.k_zone_dict.keys() + ), "Neither {0} nor 'general_zn' are in k_zone_dict keys: {1}".format( + attr_name, self.k_zone_dict.keys() + ) + k_zone_dict = self.k_zone_dict["general_zn"] else: k_zone_dict = self.k_zone_dict # df = self.write_zone_tpl(self.m, name, tpl_file, self.k_zone_dict[layer], self.zn_suffix, self.logger) try: - df = write_zone_tpl(name, os.path.join(self.m.model_ws, tpl_file), self.zn_suffix, - k_zone_dict[layer], (self.m.nrow, self.m.ncol), self.m.sr) + df = write_zone_tpl( + name, + os.path.join(self.m.model_ws, tpl_file), + self.zn_suffix, + k_zone_dict[layer], + (self.m.nrow, self.m.ncol), + self.m.sr, + ) except Exception as e: - self.logger.lraise("error writing zone template: {0}".format(str(e))) + self.logger.lraise( + "error writing zone template: {0}".format(str(e)) + ) self.log("writing zone tpl:{0}".format(tpl_file)) if df is None: @@ -2250,13 +2576,11 @@ def _setup_array_pars(self): ones = np.ones((self.m.nrow, self.m.ncol)) for mlt_file in mlt_df.mlt_file.unique(): self.log("save test mlt array {0}".format(mlt_file)) - np.savetxt(os.path.join(self.m.model_ws, mlt_file), - ones, fmt="%15.6E") + np.savetxt(os.path.join(self.m.model_ws, mlt_file), ones, fmt="%15.6E") self.log("save test mlt array {0}".format(mlt_file)) tpl_files = mlt_df.loc[mlt_df.mlt_file == mlt_file, "tpl_file"] if tpl_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of tpl_files for {0}" \ - .format(mlt_file)) + self.logger.lraise("wrong number of tpl_files for {0}".format(mlt_file)) tpl_file = tpl_files.iloc[0] if pd.notnull(tpl_file): self.tpl_files.append(tpl_file) @@ -2273,8 +2597,9 @@ def _setup_array_pars(self): apply_array_pars() except Exception as e: os.chdir("..") - self.logger.lraise("error test running apply_array_pars():{0}". - format(str(e))) + self.logger.lraise( + "error test running apply_array_pars():{0}".format(str(e)) + ) os.chdir("..") line = "pyemu.helpers.apply_array_pars()\n" self.logger.statement("forward_run line:{0}".format(line)) @@ -2284,18 +2609,29 @@ def _setup_observations(self): """ main entry point for setting up observations """ - obs_methods = [self._setup_water_budget_obs, self._setup_hyd, - self._setup_smp, self._setup_hob, self._setup_hds, - self._setup_sfr_obs] - obs_types = ["mflist water budget obs", "hyd file", - "external obs-sim smp files", "hob", "hds", "sfr"] + obs_methods = [ + self._setup_water_budget_obs, + self._setup_hyd, + self._setup_smp, + self._setup_hob, + self._setup_hds, + self._setup_sfr_obs, + ] + obs_types = [ + "mflist water budget obs", + "hyd file", + "external obs-sim smp files", + "hob", + "hds", + "sfr", + ] self.obs_dfs = {} for obs_method, obs_type in zip(obs_methods, obs_types): self.log("processing obs type {0}".format(obs_type)) obs_method() self.log("processing obs type {0}".format(obs_type)) - def draw(self, num_reals=100, sigma_range=6,use_specsim=False, scale_offset=True): + def draw(self, num_reals=100, sigma_range=6, use_specsim=False, scale_offset=True): """ draw from the geostatistically-implied parameter covariance matrix @@ -2344,19 +2680,35 @@ def draw(self, num_reals=100, sigma_range=6,use_specsim=False, scale_offset=True # gr_dfs = [gr_df.loc[gr_df.pargp==pargp,:].copy() for pargp in gr_df.pargp.unique()] struct_dict[self.grid_geostruct] = gr_dfs else: - if not pyemu.geostats.SpecSim2d.grid_is_regular(self.m.dis.delr.array, self.m.dis.delc.array): - self.logger.lraise("draw() error: can't use spectral simulation with irregular grid") + if not pyemu.geostats.SpecSim2d.grid_is_regular( + self.m.dis.delr.array, self.m.dis.delc.array + ): + self.logger.lraise( + "draw() error: can't use spectral simulation with irregular grid" + ) gr_df.loc[:, "i"] = gr_df.parnme.apply(lambda x: int(x[-6:-3])) gr_df.loc[:, "j"] = gr_df.parnme.apply(lambda x: int(x[-3:])) if gr_df.i.max() > self.m.nrow - 1 or gr_df.i.min() < 0: - self.logger.lraise("draw(): error parsing grid par names for 'i' index") + self.logger.lraise( + "draw(): error parsing grid par names for 'i' index" + ) if gr_df.j.max() > self.m.ncol - 1 or gr_df.j.min() < 0: - self.logger.lraise("draw(): error parsing grid par names for 'j' index") + self.logger.lraise( + "draw(): error parsing grid par names for 'j' index" + ) self.log("spectral simulation for grid-scale pars") - ss = pyemu.geostats.SpecSim2d(delx=self.m.dis.delr.array, dely=self.m.dis.delc.array, - geostruct=self.grid_geostruct) - gr_par_pe = ss.grid_par_ensemble_helper(pst=self.pst, gr_df=gr_df, num_reals=num_reals, - sigma_range=sigma_range, logger=self.logger) + ss = pyemu.geostats.SpecSim2d( + delx=self.m.dis.delr.array, + dely=self.m.dis.delc.array, + geostruct=self.grid_geostruct, + ) + gr_par_pe = ss.grid_par_ensemble_helper( + pst=self.pst, + gr_df=gr_df, + num_reals=num_reals, + sigma_range=sigma_range, + logger=self.logger, + ) self.log("spectral simulation for grid-scale pars") if "temporal_list" in self.par_dfs.keys(): bc_df = self.par_dfs["temporal_list"] @@ -2380,15 +2732,21 @@ def draw(self, num_reals=100, sigma_range=6,use_specsim=False, scale_offset=True bc_dfs.append(gp_df) struct_dict[self.spatial_list_geostruct] = bc_dfs - pe = geostatistical_draws(self.pst,struct_dict=struct_dict,num_reals=num_reals, - sigma_range=sigma_range,scale_offset=scale_offset) + pe = geostatistical_draws( + self.pst, + struct_dict=struct_dict, + num_reals=num_reals, + sigma_range=sigma_range, + scale_offset=scale_offset, + ) if gr_par_pe is not None: pe.loc[:, gr_par_pe.columns] = gr_par_pe.values self.log("drawing realizations") return pe - def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, - sigma_range=6): + def build_prior( + self, fmt="ascii", filename=None, droptol=None, chunk=None, sigma_range=6 + ): """ build and optionally save the prior parameter covariance matrix. Args: @@ -2412,8 +2770,11 @@ def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, fmt = fmt.lower() acc_fmts = ["ascii", "binary", "uncfile", "none", "coo"] if fmt not in acc_fmts: - self.logger.lraise("unrecognized prior save 'fmt':{0}, options are: {1}". - format(fmt, ','.join(acc_fmts))) + self.logger.lraise( + "unrecognized prior save 'fmt':{0}, options are: {1}".format( + fmt, ",".join(acc_fmts) + ) + ) self.log("building prior covariance matrix") struct_dict = {} @@ -2466,23 +2827,25 @@ def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, self.logger.warn("geospatial prior not implemented for SFR pars") if len(struct_dict) > 0: - cov = pyemu.helpers.geostatistical_prior_builder(self.pst, - struct_dict=struct_dict, - sigma_range=sigma_range) + cov = pyemu.helpers.geostatistical_prior_builder( + self.pst, struct_dict=struct_dict, sigma_range=sigma_range + ) else: cov = pyemu.Cov.from_parameter_data(self.pst, sigma_range=sigma_range) if filename is None: filename = os.path.join(self.m.model_ws, self.pst_name + ".prior.cov") if fmt != "none": - self.logger.statement("saving prior covariance matrix to file {0}".format(filename)) - if fmt == 'ascii': + self.logger.statement( + "saving prior covariance matrix to file {0}".format(filename) + ) + if fmt == "ascii": cov.to_ascii(filename) - elif fmt == 'binary': + elif fmt == "binary": cov.to_binary(filename, droptol=droptol, chunk=chunk) - elif fmt == 'uncfile': + elif fmt == "uncfile": cov.to_uncfile(filename) - elif fmt == 'coo': + elif fmt == "coo": cov.to_coo(filename, droptol=droptol, chunk=chunk) self.log("building prior covariance matrix") return cov @@ -2507,13 +2870,15 @@ def build_pst(self, filename=None): tpl_files = copy.deepcopy(self.tpl_files) in_files = copy.deepcopy(self.in_files) try: - files = os.listdir('.') - new_tpl_files = [f for f in files if f.endswith(".tpl") and f not in tpl_files] - new_in_files = [f.replace(".tpl", '') for f in new_tpl_files] + files = os.listdir(".") + new_tpl_files = [ + f for f in files if f.endswith(".tpl") and f not in tpl_files + ] + new_in_files = [f.replace(".tpl", "") for f in new_tpl_files] tpl_files.extend(new_tpl_files) in_files.extend(new_in_files) ins_files = [f for f in files if f.endswith(".ins")] - out_files = [f.replace(".ins", '') for f in ins_files] + out_files = [f.replace(".ins", "") for f in ins_files] for tpl_file, in_file in zip(tpl_files, in_files): if tpl_file not in self.tpl_files: self.tpl_files.append(tpl_file) @@ -2526,16 +2891,18 @@ def build_pst(self, filename=None): self.log("instantiating control file from i/o files") self.logger.statement("tpl files: {0}".format(",".join(self.tpl_files))) self.logger.statement("ins files: {0}".format(",".join(self.ins_files))) - pst = pyemu.Pst.from_io_files(tpl_files=self.tpl_files, - in_files=self.in_files, - ins_files=self.ins_files, - out_files=self.out_files) + pst = pyemu.Pst.from_io_files( + tpl_files=self.tpl_files, + in_files=self.in_files, + ins_files=self.ins_files, + out_files=self.out_files, + ) self.log("instantiating control file from i/o files") except Exception as e: os.chdir("..") self.logger.lraise("error build Pst:{0}".format(str(e))) - os.chdir('..') + os.chdir("..") # more customization here par = pst.parameter_data for name, df in self.par_dfs.items(): @@ -2610,30 +2977,41 @@ def _add_external(self): external_tpl_in_pairs = [self.external_tpl_in_pairs] for tpl_file, in_file in self.external_tpl_in_pairs: if not os.path.exists(tpl_file): - self.logger.lraise("couldn't find external tpl file:{0}". \ - format(tpl_file)) + self.logger.lraise( + "couldn't find external tpl file:{0}".format(tpl_file) + ) self.logger.statement("external tpl:{0}".format(tpl_file)) - shutil.copy2(tpl_file, os.path.join(self.m.model_ws, - os.path.split(tpl_file)[-1])) + shutil.copy2( + tpl_file, os.path.join(self.m.model_ws, os.path.split(tpl_file)[-1]) + ) if os.path.exists(in_file): - shutil.copy2(in_file, os.path.join(self.m.model_ws, - os.path.split(in_file)[-1])) + shutil.copy2( + in_file, + os.path.join(self.m.model_ws, os.path.split(in_file)[-1]), + ) if self.external_ins_out_pairs is not None: if not isinstance(self.external_ins_out_pairs, list): external_ins_out_pairs = [self.external_ins_out_pairs] for ins_file, out_file in self.external_ins_out_pairs: if not os.path.exists(ins_file): - self.logger.lraise("couldn't find external ins file:{0}". \ - format(ins_file)) + self.logger.lraise( + "couldn't find external ins file:{0}".format(ins_file) + ) self.logger.statement("external ins:{0}".format(ins_file)) - shutil.copy2(ins_file, os.path.join(self.m.model_ws, - os.path.split(ins_file)[-1])) + shutil.copy2( + ins_file, os.path.join(self.m.model_ws, os.path.split(ins_file)[-1]) + ) if os.path.exists(out_file): - shutil.copy2(out_file, os.path.join(self.m.model_ws, - os.path.split(out_file)[-1])) - self.logger.warn("obs listed in {0} will have values listed in {1}" - .format(ins_file, out_file)) + shutil.copy2( + out_file, + os.path.join(self.m.model_ws, os.path.split(out_file)[-1]), + ) + self.logger.warn( + "obs listed in {0} will have values listed in {1}".format( + ins_file, out_file + ) + ) else: self.logger.warn("obs listed in {0} will have generic values") @@ -2645,26 +3023,30 @@ def write_forward_run(self): changed to the pre- and/or post-processing routines. """ - with open(os.path.join(self.m.model_ws, self.forward_run_file), 'w') as f: - f.write("import os\nimport multiprocessing as mp\nimport numpy as np" + \ - "\nimport pandas as pd\nimport flopy\n") + with open(os.path.join(self.m.model_ws, self.forward_run_file), "w") as f: + f.write( + "import os\nimport multiprocessing as mp\nimport numpy as np" + + "\nimport pandas as pd\nimport flopy\n" + ) f.write("import pyemu\n") f.write("def main():\n") f.write("\n") s = " " for ex_imp in self.extra_forward_imports: - f.write(s + 'import {0}\n'.format(ex_imp)) + f.write(s + "import {0}\n".format(ex_imp)) for tmp_file in self.tmp_files: f.write(s + "try:\n") f.write(s + " os.remove('{0}')\n".format(tmp_file)) f.write(s + "except Exception as e:\n") - f.write(s + " print('error removing tmp file:{0}')\n".format(tmp_file)) + f.write( + s + " print('error removing tmp file:{0}')\n".format(tmp_file) + ) for line in self.frun_pre_lines: - f.write(s + line + '\n') + f.write(s + line + "\n") for line in self.frun_model_lines: - f.write(s + line + '\n') + f.write(s + line + "\n") for line in self.frun_post_lines: - f.write(s + line + '\n') + f.write(s + line + "\n") f.write("\n") f.write("if __name__ == '__main__':\n") f.write(" mp.freeze_support()\n main()\n\n") @@ -2685,8 +3067,7 @@ def _parse_k(self, k, vals): try: k_vals = vals[k] except Exception as e: - raise Exception("error slicing vals with {0}:{1}". - format(k, str(e))) + raise Exception("error slicing vals with {0}:{1}".format(k, str(e))) return k_vals def _parse_pakattr(self, pakattr): @@ -2695,7 +3076,7 @@ def _parse_pakattr(self, pakattr): """ - raw = pakattr.lower().split('.') + raw = pakattr.lower().split(".") if len(raw) != 2: self.logger.lraise("pakattr is wrong:{0}".format(pakattr)) pakname = raw[0] @@ -2704,7 +3085,13 @@ def _parse_pakattr(self, pakattr): if pak is None: if pakname == "extra": self.logger.statement("'extra' pak detected:{0}".format(pakattr)) - ud = flopy.utils.Util3d(self.m, (self.m.nlay, self.m.nrow, self.m.ncol), np.float32, 1.0, attrname) + ud = flopy.utils.Util3d( + self.m, + (self.m.nlay, self.m.nrow, self.m.ncol), + np.float32, + 1.0, + attrname, + ) return "extra", ud self.logger.lraise("pak {0} not found".format(pakname)) @@ -2714,8 +3101,11 @@ def _parse_pakattr(self, pakattr): elif hasattr(pak, "stress_period_data"): dtype = pak.stress_period_data.dtype if attrname not in dtype.names: - self.logger.lraise("attr {0} not found in dtype.names for {1}.stress_period_data". \ - format(attrname, pakname)) + self.logger.lraise( + "attr {0} not found in dtype.names for {1}.stress_period_data".format( + attrname, pakname + ) + ) attr = pak.stress_period_data return pak, attr, attrname # elif hasattr(pak,'hfb_data'): @@ -2742,8 +3132,10 @@ def _setup_list_pars(self): apply_list_pars() except Exception as e: os.chdir("..") - self.logger.lraise("error test running apply_list_pars():{0}".format(str(e))) - os.chdir('..') + self.logger.lraise( + "error test running apply_list_pars():{0}".format(str(e)) + ) + os.chdir("..") line = "pyemu.helpers.apply_list_pars()\n" self.logger.statement("forward_run line:{0}".format(line)) self.frun_pre_lines.append(line) @@ -2772,15 +3164,21 @@ def _setup_temporal_list_pars(self): pak_name = pak.name[0].lower() bc_pak.append(pak_name) bc_k.append(k) - bc_dtype_names.append(','.join(attr.dtype.names)) + bc_dtype_names.append(",".join(attr.dtype.names)) bc_parnme.append("{0}{1}_{2:03d}".format(pak_name, col, c)) - df = pd.DataFrame({"filename": bc_filenames, "col": bc_cols, - "kper": bc_k, "pak": bc_pak, - "dtype_names": bc_dtype_names, - "parnme": bc_parnme}) - tds = pd.to_timedelta(np.cumsum(self.m.dis.perlen.array), unit='d') + df = pd.DataFrame( + { + "filename": bc_filenames, + "col": bc_cols, + "kper": bc_k, + "pak": bc_pak, + "dtype_names": bc_dtype_names, + "parnme": bc_parnme, + } + ) + tds = pd.to_timedelta(np.cumsum(self.m.dis.perlen.array), unit="d") dts = pd.to_datetime(self.m._start_datetime) + tds df.loc[:, "datetime"] = df.kper.apply(lambda x: dts[x]) df.loc[:, "timedelta"] = df.kper.apply(lambda x: tds[x]) @@ -2790,12 +3188,22 @@ def _setup_temporal_list_pars(self): df.loc[:, "tpl_str"] = df.parnme.apply(lambda x: "~ {0} ~".format(x)) df.loc[:, "list_org"] = self.list_org df.loc[:, "model_ext_path"] = self.m.external_path - df.loc[:, "pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) - names = ["filename", "dtype_names", "list_org", "model_ext_path", "col", "kper", "pak", "val"] - df.loc[:, names]. \ - to_csv(os.path.join(self.m.model_ws, "temporal_list_pars.dat"), sep=' ') + df.loc[:, "pargp"] = df.parnme.apply(lambda x: x.split("_")[0]) + names = [ + "filename", + "dtype_names", + "list_org", + "model_ext_path", + "col", + "kper", + "pak", + "val", + ] + df.loc[:, names].to_csv( + os.path.join(self.m.model_ws, "temporal_list_pars.dat"), sep=" " + ) df.loc[:, "val"] = df.tpl_str - tpl_name = os.path.join(self.m.model_ws, 'temporal_list_pars.dat.tpl') + tpl_name = os.path.join(self.m.model_ws, "temporal_list_pars.dat.tpl") # f_tpl = open(tpl_name,'w') # f_tpl.write("ptf ~\n") # f_tpl.flush() @@ -2803,7 +3211,9 @@ def _setup_temporal_list_pars(self): # f_tpl.write("index ") # f_tpl.write(df.loc[:,names].to_string(index_names=True)) # f_tpl.close() - _write_df_tpl(tpl_name, df.loc[:, names], sep=' ', index_label="index", quotechar=" ") + _write_df_tpl( + tpl_name, df.loc[:, names], sep=" ", index_label="index", quotechar=" " + ) self.par_dfs["temporal_list"] = df self.log("processing temporal_list_props") @@ -2828,10 +3238,13 @@ def _setup_spatial_list_pars(self): pak, attr, col = self._parse_pakattr(pakattr) k_parse = self._parse_k(k_org, np.arange(self.m.nlay)) if len(k_parse) > 1: - self.logger.lraise("spatial_list_pars error: each set of spatial list pars can only be applied " + \ - "to a single layer (e.g. [wel.flux,0].\n" + \ - "You passed [{0},{1}], implying broadcasting to layers {2}". - format(pakattr, k_org, k_parse)) + self.logger.lraise( + "spatial_list_pars error: each set of spatial list pars can only be applied " + + "to a single layer (e.g. [wel.flux,0].\n" + + "You passed [{0},{1}], implying broadcasting to layers {2}".format( + pakattr, k_org, k_parse + ) + ) # # horrible special case for HFB since it cannot vary over time # if type(pak) != flopy.modflow.mfhfb.ModflowHfb: for k in range(self.m.nper): @@ -2840,11 +3253,17 @@ def _setup_spatial_list_pars(self): pak_name = pak.name[0].lower() bc_pak.append(pak_name) bc_k.append(k_parse[0]) - bc_dtype_names.append(','.join(attr.dtype.names)) - - info_df = pd.DataFrame({"filename": bc_filenames, "col": bc_cols, - "k": bc_k, "pak": bc_pak, - "dtype_names": bc_dtype_names}) + bc_dtype_names.append(",".join(attr.dtype.names)) + + info_df = pd.DataFrame( + { + "filename": bc_filenames, + "col": bc_cols, + "k": bc_k, + "pak": bc_pak, + "dtype_names": bc_dtype_names, + } + ) info_df.loc[:, "list_mlt"] = self.list_mlt info_df.loc[:, "list_org"] = self.list_org info_df.loc[:, "model_ext_path"] = self.m.external_path @@ -2856,12 +3275,16 @@ def _setup_spatial_list_pars(self): df_pak = info_df.loc[info_df.pak == pak, :] itmp = [] for filename in df_pak.filename: - names = df_pak.dtype_names.iloc[0].split(',') + names = df_pak.dtype_names.iloc[0].split(",") # mif pak != 'hfb6': - fdf = pd.read_csv(os.path.join(self.m.model_ws, filename), - delim_whitespace=True, header=None, names=names) - for c in ['k', 'i', 'j']: + fdf = pd.read_csv( + os.path.join(self.m.model_ws, filename), + delim_whitespace=True, + header=None, + names=names, + ) + for c in ["k", "i", "j"]: fdf.loc[:, c] -= 1 # else: # # need to navigate the HFB file to skip both comments and header line @@ -2879,18 +3302,24 @@ def _setup_spatial_list_pars(self): info_df.loc[info_df.pak == pak, "itmp"] = itmp if np.unique(np.array(itmp)).shape[0] != 1: info_df.to_csv("spatial_list_trouble.csv") - self.logger.lraise("spatial_list_pars() error: must have same number of " + \ - "entries for every stress period for {0}".format(pak)) + self.logger.lraise( + "spatial_list_pars() error: must have same number of " + + "entries for every stress period for {0}".format(pak) + ) # make the pak dfs have unique model indices for pak, df in pak_dfs.items(): # if pak != 'hfb6': - df.loc[:, "idx"] = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k, x.i, x.j), axis=1) + df.loc[:, "idx"] = df.apply( + lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k, x.i, x.j), axis=1 + ) # else: # df.loc[:, "idx"] = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}{2:04.0f}{2:04.0f}".format(x.k, x.irow1, x.icol1, # x.irow2, x.icol2), axis=1) if df.idx.unique().shape[0] != df.shape[0]: - self.logger.warn("duplicate entries in list pak {0}...collapsing".format(pak)) + self.logger.warn( + "duplicate entries in list pak {0}...collapsing".format(pak) + ) df.drop_duplicates(subset="idx", inplace=True) df.index = df.idx pak_dfs[pak] = df @@ -2901,16 +3330,29 @@ def _setup_spatial_list_pars(self): pak_df = info_df.loc[info_df.pak == pak, :] # reset all non-index cols to 1.0 for col in df.columns: - if col not in ['k', 'i', 'j', 'inode', 'irow1', 'icol1', 'irow2', 'icol2']: + if col not in [ + "k", + "i", + "j", + "inode", + "irow1", + "icol1", + "irow2", + "icol2", + ]: df.loc[:, col] = 1.0 in_file = os.path.join(self.list_mlt, pak + ".csv") tpl_file = os.path.join(pak + ".csv.tpl") # save an all "ones" mult df for testing - df.to_csv(os.path.join(self.m.model_ws, in_file), sep=' ') + df.to_csv(os.path.join(self.m.model_ws, in_file), sep=" ") parnme, pargp = [], [] # if pak != 'hfb6': - x = df.apply(lambda x: self.m.sr.xcentergrid[int(x.i), int(x.j)], axis=1).values - y = df.apply(lambda x: self.m.sr.ycentergrid[int(x.i), int(x.j)], axis=1).values + x = df.apply( + lambda x: self.m.sr.xcentergrid[int(x.i), int(x.j)], axis=1 + ).values + y = df.apply( + lambda x: self.m.sr.ycentergrid[int(x.i), int(x.j)], axis=1 + ).values # else: # # note -- for HFB6, only row and col for node 1 # x = df.apply(lambda x: self.m.sr.xcentergrid[int(x.irow1),int(x.icol1)],axis=1).values @@ -2926,13 +3368,20 @@ def _setup_spatial_list_pars(self): df.loc[:, col] = names.map(lambda x: "~ {0} ~".format(x)) df.loc[df.k.apply(lambda x: x not in k_vals), col] = 1.0 - par_df = pd.DataFrame({"parnme": names, "x": x, "y": y, "k": df.k.values}, index=names) + par_df = pd.DataFrame( + {"parnme": names, "x": x, "y": y, "k": df.k.values}, index=names + ) par_df = par_df.loc[par_df.k.apply(lambda x: x in k_vals)] if par_df.shape[0] == 0: - self.logger.lraise("no parameters found for spatial list k,pak,attr {0}, {1}, {2}". - format(k_vals, pak, col)) + self.logger.lraise( + "no parameters found for spatial list k,pak,attr {0}, {1}, {2}".format( + k_vals, pak, col + ) + ) - par_df.loc[:, "pargp"] = df.k.apply(lambda x: "{0}{1}_k{2:02.0f}".format(pak, col, int(x))).values + par_df.loc[:, "pargp"] = df.k.apply( + lambda x: "{0}{1}_k{2:02.0f}".format(pak, col, int(x)) + ).values par_df.loc[:, "tpl_file"] = tpl_file par_df.loc[:, "in_file"] = in_file @@ -2944,13 +3393,19 @@ def _setup_spatial_list_pars(self): # df.to_csv(f) # f.write("index ") # f.write(df.to_string(index_names=False)+'\n') - _write_df_tpl(os.path.join(self.m.model_ws, tpl_file), df, sep=' ', quotechar=" ", index_label="index") + _write_df_tpl( + os.path.join(self.m.model_ws, tpl_file), + df, + sep=" ", + quotechar=" ", + index_label="index", + ) self.tpl_files.append(tpl_file) self.in_files.append(in_file) par_df = pd.concat(par_dfs) self.par_dfs["spatial_list"] = par_df - info_df.to_csv(os.path.join(self.m.model_ws, "spatial_list_pars.dat"), sep=' ') + info_df.to_csv(os.path.join(self.m.model_ws, "spatial_list_pars.dat"), sep=" ") self.log("processing spatial_list_props") return True @@ -2966,8 +3421,10 @@ def _list_helper(self, k, pak, attr, col): # else: filename = attr.get_filename(k) filename_model = os.path.join(self.m.external_path, filename) - shutil.copy2(os.path.join(self.m.model_ws, filename_model), - os.path.join(self.m.model_ws, self.list_org, filename)) + shutil.copy2( + os.path.join(self.m.model_ws, filename_model), + os.path.join(self.m.model_ws, self.list_org, filename), + ) return filename_model def _setup_hds(self): @@ -2979,6 +3436,7 @@ def _setup_hds(self): if self.hds_kperk is None or len(self.hds_kperk) == 0: return from .gw_utils import setup_hds_obs + # if len(self.hds_kperk) == 2: # try: # if len(self.hds_kperk[0] == 2): @@ -2992,10 +3450,13 @@ def _setup_hds(self): raise Exception("OC not saving hds, can't setup grid obs") hds_unit = oc.iuhead hds_file = self.m.get_output(unit=hds_unit) - assert os.path.exists(os.path.join(self.org_model_ws, hds_file)), \ - "couldn't find existing hds file {0} in org_model_ws".format(hds_file) - shutil.copy2(os.path.join(self.org_model_ws, hds_file), - os.path.join(self.m.model_ws, hds_file)) + assert os.path.exists( + os.path.join(self.org_model_ws, hds_file) + ), "couldn't find existing hds file {0} in org_model_ws".format(hds_file) + shutil.copy2( + os.path.join(self.org_model_ws, hds_file), + os.path.join(self.m.model_ws, hds_file), + ) inact = None if self.m.lpf is not None: inact = self.m.lpf.hdry @@ -3006,10 +3467,15 @@ def _setup_hds(self): else: skip = lambda x: np.NaN if x == self.m.bas6.hnoflo or x == inact else x print(self.hds_kperk) - frun_line, df = setup_hds_obs(os.path.join(self.m.model_ws, hds_file), - kperk_pairs=self.hds_kperk, skip=skip) + frun_line, df = setup_hds_obs( + os.path.join(self.m.model_ws, hds_file), + kperk_pairs=self.hds_kperk, + skip=skip, + ) self.obs_dfs["hds"] = df - self.frun_post_lines.append("pyemu.gw_utils.apply_hds_obs('{0}')".format(hds_file)) + self.frun_post_lines.append( + "pyemu.gw_utils.apply_hds_obs('{0}')".format(hds_file) + ) self.tmp_files.append(hds_file) def _setup_smp(self): @@ -3027,11 +3493,9 @@ def _setup_smp(self): self.logger.lraise("couldn't find obs smp: {0}".format(obs_smp)) if not os.path.exists(sim_smp): self.logger.lraise("couldn't find sim smp: {0}".format(sim_smp)) - new_obs_smp = os.path.join(self.m.model_ws, - os.path.split(obs_smp)[-1]) + new_obs_smp = os.path.join(self.m.model_ws, os.path.split(obs_smp)[-1]) shutil.copy2(obs_smp, new_obs_smp) - new_sim_smp = os.path.join(self.m.model_ws, - os.path.split(sim_smp)[-1]) + new_sim_smp = os.path.join(self.m.model_ws, os.path.split(sim_smp)[-1]) shutil.copy2(sim_smp, new_sim_smp) pyemu.smp_utils.smp_to_ins(new_sim_smp) @@ -3042,11 +3506,17 @@ def _setup_hob(self): if self.m.hob is None: return hob_out_unit = self.m.hob.iuhobsv - new_hob_out_fname = os.path.join(self.m.model_ws, self.m.get_output_attribute(unit=hob_out_unit)) - org_hob_out_fname = os.path.join(self.org_model_ws, self.m.get_output_attribute(unit=hob_out_unit)) + new_hob_out_fname = os.path.join( + self.m.model_ws, self.m.get_output_attribute(unit=hob_out_unit) + ) + org_hob_out_fname = os.path.join( + self.org_model_ws, self.m.get_output_attribute(unit=hob_out_unit) + ) if not os.path.exists(org_hob_out_fname): - self.logger.warn("could not find hob out file: {0}...skipping".format(hob_out_fname)) + self.logger.warn( + "could not find hob out file: {0}...skipping".format(hob_out_fname) + ) return shutil.copy2(org_hob_out_fname, new_hob_out_fname) hob_df = pyemu.gw_utils.modflow_hob_to_instruction_file(new_hob_out_fname) @@ -3061,15 +3531,19 @@ def _setup_hyd(self): if self.mfhyd: org_hyd_out = os.path.join(self.org_model_ws, self.m.name + ".hyd.bin") if not os.path.exists(org_hyd_out): - self.logger.warn("can't find existing hyd out file:{0}...skipping". - format(org_hyd_out)) + self.logger.warn( + "can't find existing hyd out file:{0}...skipping".format( + org_hyd_out + ) + ) return new_hyd_out = os.path.join(self.m.model_ws, os.path.split(org_hyd_out)[-1]) shutil.copy2(org_hyd_out, new_hyd_out) df = pyemu.gw_utils.modflow_hydmod_to_instruction_file(new_hyd_out) - df.loc[:, "obgnme"] = df.obsnme.apply(lambda x: '_'.join(x.split('_')[:-1])) - line = "pyemu.gw_utils.modflow_read_hydmod_file('{0}')". \ - format(os.path.split(new_hyd_out)[-1]) + df.loc[:, "obgnme"] = df.obsnme.apply(lambda x: "_".join(x.split("_")[:-1])) + line = "pyemu.gw_utils.modflow_read_hydmod_file('{0}')".format( + os.path.split(new_hyd_out)[-1] + ) self.logger.statement("forward_run line: {0}".format(line)) self.frun_post_lines.append(line) self.obs_dfs["hyd"] = df @@ -3083,30 +3557,35 @@ def _setup_water_budget_obs(self): if self.mflist_waterbudget: org_listfile = os.path.join(self.org_model_ws, self.m.lst.file_name[0]) if os.path.exists(org_listfile): - shutil.copy2(org_listfile, os.path.join(self.m.model_ws, - self.m.lst.file_name[0])) + shutil.copy2( + org_listfile, os.path.join(self.m.model_ws, self.m.lst.file_name[0]) + ) else: - self.logger.warn("can't find existing list file:{0}...skipping". - format(org_listfile)) + self.logger.warn( + "can't find existing list file:{0}...skipping".format(org_listfile) + ) return list_file = os.path.join(self.m.model_ws, self.m.lst.file_name[0]) flx_file = os.path.join(self.m.model_ws, "flux.dat") vol_file = os.path.join(self.m.model_ws, "vol.dat") - df = pyemu.gw_utils.setup_mflist_budget_obs(list_file, - flx_filename=flx_file, - vol_filename=vol_file, - start_datetime=self.m.start_datetime) + df = pyemu.gw_utils.setup_mflist_budget_obs( + list_file, + flx_filename=flx_file, + vol_filename=vol_file, + start_datetime=self.m.start_datetime, + ) if df is not None: self.obs_dfs["wb"] = df # line = "try:\n os.remove('{0}')\nexcept:\n pass".format(os.path.split(list_file)[-1]) # self.logger.statement("forward_run line:{0}".format(line)) # self.frun_pre_lines.append(line) self.tmp_files.append(os.path.split(list_file)[-1]) - line = "pyemu.gw_utils.apply_mflist_budget_obs('{0}',flx_filename='{1}',vol_filename='{2}',start_datetime='{3}')". \ - format(os.path.split(list_file)[-1], - os.path.split(flx_file)[-1], - os.path.split(vol_file)[-1], - self.m.start_datetime) + line = "pyemu.gw_utils.apply_mflist_budget_obs('{0}',flx_filename='{1}',vol_filename='{2}',start_datetime='{3}')".format( + os.path.split(list_file)[-1], + os.path.split(flx_file)[-1], + os.path.split(vol_file)[-1], + self.m.start_datetime, + ) self.logger.statement("forward_run line:{0}".format(line)) self.frun_post_lines.append(line) @@ -3131,26 +3610,22 @@ def apply_list_and_array_pars(arr_par_file="mult2model_info.csv", chunk_len=50): arr_pars = df.loc[df.index_cols.isna()].copy() list_pars = df.loc[df.index_cols.notna()].copy() # extract lists from string in input df - list_pars['index_cols'] = list_pars.index_cols.apply( - lambda x: literal_eval(x)) - list_pars['use_cols'] = list_pars.use_cols.apply( - lambda x: literal_eval(x)) - list_pars['lower_bound'] = list_pars.lower_bound.apply( - lambda x: literal_eval(x)) - list_pars['upper_bound'] = list_pars.upper_bound.apply( - lambda x: literal_eval(x)) + list_pars["index_cols"] = list_pars.index_cols.apply(lambda x: literal_eval(x)) + list_pars["use_cols"] = list_pars.use_cols.apply(lambda x: literal_eval(x)) + list_pars["lower_bound"] = list_pars.lower_bound.apply(lambda x: literal_eval(x)) + list_pars["upper_bound"] = list_pars.upper_bound.apply(lambda x: literal_eval(x)) # TODO check use_cols is always present apply_genericlist_pars(list_pars) - apply_array_pars(arr_pars,chunk_len=chunk_len) - + apply_array_pars(arr_pars, chunk_len=chunk_len) + -def _process_chunk_fac2real(chunk,i): +def _process_chunk_fac2real(chunk, i): for args in chunk: pyemu.geostats.fac2real(**args) - print("process",i," processed ",len(chunk),"fac2real calls") + print("process", i, " processed ", len(chunk), "fac2real calls") -def _process_chunk_model_files(chunk,i,df): +def _process_chunk_model_files(chunk, i, df): for model_file in chunk: _process_model_file(model_file, df) print("process", i, " processed ", len(chunk), "process_model_file calls") @@ -3162,8 +3637,7 @@ def _process_model_file(model_file, df): results = [] org_file = df_mf.org_file.unique() if org_file.shape[0] != 1: - raise Exception("wrong number of org_files for {0}". - format(model_file)) + raise Exception("wrong number of org_files for {0}".format(model_file)) org_arr = np.loadtxt(org_file[0]) for mlt in df_mf.mlt_file: @@ -3171,8 +3645,11 @@ def _process_model_file(model_file, df): continue mlt_data = np.loadtxt(mlt) if org_arr.shape != mlt_data.shape: - raise Exception("shape of org file {}:{} differs from mlt file {}:{}".format(org_file, org_arr.shape, - mlt, mlt_data.shape)) + raise Exception( + "shape of org file {}:{} differs from mlt file {}:{}".format( + org_file, org_arr.shape, mlt, mlt_data.shape + ) + ) org_arr *= np.loadtxt(mlt) if "upper_bound" in df.columns: ub_vals = df_mf.upper_bound.value_counts().dropna().to_dict() @@ -3183,7 +3660,7 @@ def _process_model_file(model_file, df): raise Exception("different upper bound values for {0}".format(org_file)) else: ub = float(list(ub_vals.keys())[0]) - org_arr[org_arr>ub] = ub + org_arr[org_arr > ub] = ub if "lower_bound" in df.columns: lb_vals = df_mf.lower_bound.value_counts().dropna().to_dict() if len(lb_vals) == 0: @@ -3194,11 +3671,10 @@ def _process_model_file(model_file, df): lb = float(list(lb_vals.keys())[0]) org_arr[org_arr < lb] = lb - np.savetxt(model_file, np.atleast_2d(org_arr), fmt="%15.6E", delimiter='') - + np.savetxt(model_file, np.atleast_2d(org_arr), fmt="%15.6E", delimiter="") -def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None,chunk_len=50): +def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None, chunk_len=50): """ a function to apply array-based multipler parameters. Args: @@ -3227,42 +3703,52 @@ def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None,chunk_len=50): """ if arr_par_file is not None: - warnings.warn("`arr_par_file` argument is deprecated and replaced " - "by arr_par. Method now support passing DataFrame as " - "arr_par arg.", - PyemuWarning) + warnings.warn( + "`arr_par_file` argument is deprecated and replaced " + "by arr_par. Method now support passing DataFrame as " + "arr_par arg.", + PyemuWarning, + ) arr_par = arr_par_file if isinstance(arr_par, str): df = pd.read_csv(arr_par, index_col=0) elif isinstance(arr_par, pd.DataFrame): df = arr_par else: - raise TypeError("`arr_par` argument must be filename string or " - "Pandas DataFrame, " - "type {0} passed".format(type(arr_par))) + raise TypeError( + "`arr_par` argument must be filename string or " + "Pandas DataFrame, " + "type {0} passed".format(type(arr_par)) + ) # for fname in df.model_file: # try: # os.remove(fname) # except: # print("error removing mult array:{0}".format(fname)) - if 'pp_file' in df.columns: + if "pp_file" in df.columns: print("starting fac2real", datetime.now()) - pp_df = df.loc[df.pp_file.notna(), - ['pp_file', 'fac_file', 'mlt_file']].rename( - columns={'fac_file': 'factors_file', 'mlt_file': 'out_file'}) - pp_df.loc[:, 'lower_lim'] = 1.0e-10 + pp_df = df.loc[df.pp_file.notna(), ["pp_file", "fac_file", "mlt_file"]].rename( + columns={"fac_file": "factors_file", "mlt_file": "out_file"} + ) + pp_df.loc[:, "lower_lim"] = 1.0e-10 # don't need to process all (e.g. if const. mults apply across kper...) - pp_args = pp_df.drop_duplicates().to_dict('records') + pp_args = pp_df.drop_duplicates().to_dict("records") num_ppargs = len(pp_args) num_chunk_floor = num_ppargs // chunk_len - main_chunks = np.array(pp_args)[:num_chunk_floor * chunk_len].reshape( - [-1, chunk_len]).tolist() - remainder = np.array(pp_args)[num_chunk_floor * chunk_len:].tolist() + main_chunks = ( + np.array(pp_args)[: num_chunk_floor * chunk_len] + .reshape([-1, chunk_len]) + .tolist() + ) + remainder = np.array(pp_args)[num_chunk_floor * chunk_len :].tolist() chunks = main_chunks + [remainder] pool = mp.Pool() - x = [pool.apply_async(_process_chunk_fac2real,args=(chunk,i)) for i,chunk in enumerate(chunks)] + x = [ + pool.apply_async(_process_chunk_fac2real, args=(chunk, i)) + for i, chunk in enumerate(chunks) + ] [xx.get() for xx in x] pool.close() pool.join() @@ -3274,8 +3760,6 @@ def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None,chunk_len=50): # for p in procs: # p.join() - - print("finished fac2real", datetime.now()) print("starting arr mlt", datetime.now()) @@ -3284,9 +3768,10 @@ def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None,chunk_len=50): # number of files to send to each processor # lazy plitting the files to be processed into even chunks num_chunk_floor = num_uniq // chunk_len # number of whole chunks - main_chunks = uniq[:num_chunk_floor * chunk_len].reshape( - [-1, chunk_len]).tolist() # the list of files broken down into chunks - remainder = uniq[num_chunk_floor * chunk_len:].tolist() # remaining files + main_chunks = ( + uniq[: num_chunk_floor * chunk_len].reshape([-1, chunk_len]).tolist() + ) # the list of files broken down into chunks + remainder = uniq[num_chunk_floor * chunk_len :].tolist() # remaining files chunks = main_chunks + [remainder] # procs = [] # for chunk in chunks: # now only spawn processor for each chunk @@ -3297,7 +3782,10 @@ def apply_array_pars(arr_par="arr_pars.csv", arr_par_file=None,chunk_len=50): # r = p.get(False) # p.join() pool = mp.Pool() - x = [pool.apply_async(_process_chunk_model_files,args=(chunk,i,df)) for i,chunk in enumerate(chunks)] + x = [ + pool.apply_async(_process_chunk_model_files, args=(chunk, i, df)) + for i, chunk in enumerate(chunks) + ] [xx.get() for xx in x] pool.close() pool.join() @@ -3323,12 +3811,16 @@ def apply_list_pars(): temp_df, spat_df = None, None if os.path.exists(temp_file): temp_df = pd.read_csv(temp_file, delim_whitespace=True) - temp_df.loc[:, "split_filename"] = temp_df.filename.apply(lambda x: os.path.split(x)[-1]) + temp_df.loc[:, "split_filename"] = temp_df.filename.apply( + lambda x: os.path.split(x)[-1] + ) org_dir = temp_df.list_org.iloc[0] model_ext_path = temp_df.model_ext_path.iloc[0] if os.path.exists(spat_file): spat_df = pd.read_csv(spat_file, delim_whitespace=True) - spat_df.loc[:, "split_filename"] = spat_df.filename.apply(lambda x: os.path.split(x)[-1]) + spat_df.loc[:, "split_filename"] = spat_df.filename.apply( + lambda x: os.path.split(x)[-1] + ) mlt_dir = spat_df.list_mlt.iloc[0] org_dir = spat_df.list_org.iloc[0] model_ext_path = spat_df.model_ext_path.iloc[0] @@ -3340,9 +3832,13 @@ def apply_list_pars(): for f in os.listdir(mlt_dir): pak = f.split(".")[0].lower() - df = pd.read_csv(os.path.join(mlt_dir, f), index_col=0, delim_whitespace=True) + df = pd.read_csv( + os.path.join(mlt_dir, f), index_col=0, delim_whitespace=True + ) # if pak != 'hfb6': - df.index = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k, x.i, x.j), axis=1) + df.index = df.apply( + lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k, x.i, x.j), axis=1 + ) # else: # df.index = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}{2:04.0f}{2:04.0f}".format(x.k, x.irow1, x.icol1, # x.irow2, x.icol2), axis = 1) @@ -3369,25 +3865,43 @@ def apply_list_pars(): if temp_df is not None and fname in temp_df.split_filename.values: temp_df_fname = temp_df.loc[temp_df.split_filename == fname, :] if temp_df_fname.shape[0] > 0: - names = temp_df_fname.dtype_names.iloc[0].split(',') + names = temp_df_fname.dtype_names.iloc[0].split(",") if spat_df is not None and fname in spat_df.split_filename.values: spat_df_fname = spat_df.loc[spat_df.split_filename == fname, :] if spat_df_fname.shape[0] > 0: - names = spat_df_fname.dtype_names.iloc[0].split(',') + names = spat_df_fname.dtype_names.iloc[0].split(",") if names is not None: - df_list = pd.read_csv(os.path.join(org_dir, fname), - delim_whitespace=True, header=None, names=names) + df_list = pd.read_csv( + os.path.join(org_dir, fname), + delim_whitespace=True, + header=None, + names=names, + ) df_list.loc[:, "idx"] = df_list.apply( - lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k - 1, x.i - 1, x.j - 1), axis=1) + lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format( + x.k - 1, x.i - 1, x.j - 1 + ), + axis=1, + ) df_list.index = df_list.idx - pak_name = fname.split('_')[0].lower() + pak_name = fname.split("_")[0].lower() if pak_name in sp_mlts: mlt_df = sp_mlts[pak_name] mlt_df_ri = mlt_df.reindex(df_list.index) for col in df_list.columns: - if col in ["k", "i", "j", "inode", 'irow1', 'icol1', 'irow2', 'icol2', 'idx']: + if col in [ + "k", + "i", + "j", + "inode", + "irow1", + "icol1", + "irow2", + "icol2", + "idx", + ]: continue if col in mlt_df.columns: # print(mlt_df.loc[mlt_df.index.duplicated(),:]) @@ -3398,13 +3912,15 @@ def apply_list_pars(): temp_df_fname = temp_df.loc[temp_df.split_filename == fname, :] for col, val in zip(temp_df_fname.col, temp_df_fname.val): df_list.loc[:, col] *= val - fmts = '' + fmts = "" for name in names: - if name in ["i", "j", "k", "inode", 'irow1', 'icol1', 'irow2', 'icol2']: + if name in ["i", "j", "k", "inode", "irow1", "icol1", "irow2", "icol2"]: fmts += " %9d" else: fmts += " %9G" - np.savetxt(os.path.join(model_ext_path, fname), df_list.loc[:, names].values, fmt=fmts) + np.savetxt( + os.path.join(model_ext_path, fname), df_list.loc[:, names].values, fmt=fmts + ) def apply_genericlist_pars(df): @@ -3432,36 +3948,39 @@ def apply_genericlist_pars(df): """ uniq = df.model_file.unique() for model_file in uniq: - print("processing model file:",model_file) + print("processing model file:", model_file) df_mf = df.loc[df.model_file == model_file, :].copy() # read data stored in org (mults act on this) org_file = df_mf.org_file.unique() if org_file.shape[0] != 1: - raise Exception("wrong number of org_files for {0}". - format(model_file)) + raise Exception("wrong number of org_files for {0}".format(model_file)) org_file = org_file[0] - print("org file:",org_file) - notfree = df_mf.fmt[df_mf.fmt != 'free'] + print("org file:", org_file) + notfree = df_mf.fmt[df_mf.fmt != "free"] if len(notfree) > 1: - raise Exception("too many different format specifiers for " - "model file: {0}".format(model_file)) + raise Exception( + "too many different format specifiers for " + "model file: {0}".format(model_file) + ) elif len(notfree) == 1: fmt = notfree.values[0] else: fmt = df_mf.fmt.values[-1] - if fmt == 'free': + if fmt == "free": if df_mf.sep.dropna().nunique() > 1: - raise Exception("too many different sep specifiers for " - "model file: {0}".format(model_file)) + raise Exception( + "too many different sep specifiers for " + "model file: {0}".format(model_file) + ) else: sep = df_mf.sep.dropna().values[-1] else: sep = None datastrtrow = df_mf.head_rows.values[-1] - if fmt.lower() == 'free' and sep == ' ': + if fmt.lower() == "free" and sep == " ": delim_whitespace = True if datastrtrow > 0: - with open(org_file, 'r') as fp: + with open(org_file, "r") as fp: storehead = [next(fp) for _ in range(datastrtrow)] else: storehead = [] @@ -3471,35 +3990,43 @@ def apply_genericlist_pars(df): # TODO: add test for model file with headers # index_cols can be from header str header = 0 - hheader=True + hheader = True elif isinstance(index_col_eg, int): # index_cols are column numbers in input file header = None hheader = None # actually do need index cols to be list of strings # to be compatible when the saved original file is read in. - df_mf.loc[:, 'index_cols'] = df_mf.index_cols.apply( - lambda x: [str(i) for i in x]) + df_mf.loc[:, "index_cols"] = df_mf.index_cols.apply( + lambda x: [str(i) for i in x] + ) # if writen by PstFrom this should always be comma delim - tidy - org_data = pd.read_csv(org_file, skiprows=datastrtrow, - header=header) + org_data = pd.read_csv(org_file, skiprows=datastrtrow, header=header) # mult columns will be string type, so to make sure they align org_data.columns = org_data.columns.astype(str) - print("org_data columns:",org_data.columns) - print("org_data shape:",org_data.shape) + print("org_data columns:", org_data.columns) + print("org_data shape:", org_data.shape) new_df = org_data.copy() for mlt in df_mf.itertuples(): try: - new_df = new_df.reset_index().rename( - columns={'index': 'oidx'}).set_index(mlt.index_cols) + new_df = ( + new_df.reset_index() + .rename(columns={"index": "oidx"}) + .set_index(mlt.index_cols) + ) new_df = new_df.sort_index() except Exception as e: - print("error setting mlt index_cols: ",str(mlt.index_cols)," for new_df with cols: ",list(new_df.columns)) - raise Exception("error setting mlt index_cols: "+str(e)) - - if not hasattr(mlt,"mlt_file") or pd.isna(mlt.mlt_file): + print( + "error setting mlt index_cols: ", + str(mlt.index_cols), + " for new_df with cols: ", + list(new_df.columns), + ) + raise Exception("error setting mlt index_cols: " + str(e)) + + if not hasattr(mlt, "mlt_file") or pd.isna(mlt.mlt_file): print("null mlt file for org_file '" + org_file + "', continuing...") else: mlts = pd.read_csv(mlt.mlt_file) @@ -3507,53 +4034,61 @@ def apply_genericlist_pars(df): # mult idxs will always be written zero based # if original model files is not zero based need to add 1 add1 = int(mlt.zero_based == False) - mlts.index = pd.MultiIndex.from_tuples(mlts.sidx.apply( - lambda x: tuple(add1 + np.array(literal_eval(x)))), - names=mlt.index_cols) + mlts.index = pd.MultiIndex.from_tuples( + mlts.sidx.apply(lambda x: tuple(add1 + np.array(literal_eval(x)))), + names=mlt.index_cols, + ) if mlts.index.nlevels < 2: # just in case only one index col is used mlts.index = mlts.index.get_level_values(0) - common_idx = new_df.index.intersection( - mlts.index).sort_values().drop_duplicates() + common_idx = ( + new_df.index.intersection(mlts.index) + .sort_values() + .drop_duplicates() + ) mlt_cols = [str(col) for col in mlt.use_cols] - new_df.loc[common_idx, mlt_cols] = (new_df.loc[common_idx, mlt_cols] - * mlts.loc[common_idx, mlt_cols] - ).values + new_df.loc[common_idx, mlt_cols] = ( + new_df.loc[common_idx, mlt_cols] * mlts.loc[common_idx, mlt_cols] + ).values # bring mult index back to columns AND re-order - new_df = new_df.reset_index().set_index( - 'oidx')[org_data.columns].sort_index() + new_df = ( + new_df.reset_index().set_index("oidx")[org_data.columns].sort_index() + ) if "upper_bound" in df.columns: ub = df_mf.apply( lambda x: pd.Series( - {str(c): b for c, b in - zip(x.use_cols, x.upper_bound)}), axis=1).max() + {str(c): b for c, b in zip(x.use_cols, x.upper_bound)} + ), + axis=1, + ).max() if ub.notnull().any(): for col, val in ub.items(): new_df.loc[new_df.loc[:, col] > val, col] = val if "lower_bound" in df.columns: lb = df_mf.apply( lambda x: pd.Series( - {str(c): b for c, b in - zip(x.use_cols, x.lower_bound)}), axis=1).min() + {str(c): b for c, b in zip(x.use_cols, x.lower_bound)} + ), + axis=1, + ).min() if lb.notnull().any(): for col, val in lb.items(): new_df.loc[new_df.loc[:, col] < val, col] = val - with open(model_file, 'w') as fo: + with open(model_file, "w") as fo: kwargs = {} if "win" in platform.platform().lower(): kwargs = {"line_terminator": "\n"} if len(storehead) != 0: - fo.write('\n'.join(storehead)) + fo.write("\n".join(storehead)) fo.flush() - if fmt.lower() == 'free': - new_df.to_csv(fo, index=False, mode='a', - sep=sep, header=hheader, - **kwargs) + if fmt.lower() == "free": + new_df.to_csv( + fo, index=False, mode="a", sep=sep, header=hheader, **kwargs + ) else: np.savetxt(fo, np.atleast_2d(new_df.values), fmt=fmt) -def write_const_tpl(name, tpl_file, suffix, zn_array=None, - shape=None, longnames=False): +def write_const_tpl(name, tpl_file, suffix, zn_array=None, shape=None, longnames=False): """ write a constant (uniform) template file for a 2-D array Args: @@ -3578,7 +4113,7 @@ def write_const_tpl(name, tpl_file, suffix, zn_array=None, shape = zn_array.shape parnme = [] - with open(tpl_file, 'w') as f: + with open(tpl_file, "w") as f: f.write("ptf ~\n") for i in range(shape[0]): for j in range(shape[1]): @@ -3590,21 +4125,29 @@ def write_const_tpl(name, tpl_file, suffix, zn_array=None, else: pname = "{0}{1}".format(name, suffix) if len(pname) > 12: - warnings.warn("zone pname too long for pest:{0}". \ - format(pname)) + warnings.warn( + "zone pname too long for pest:{0}".format(pname) + ) parnme.append(pname) pname = " ~ {0} ~".format(pname) f.write(pname) f.write("\n") df = pd.DataFrame({"parnme": parnme}, index=parnme) # df.loc[:,"pargp"] = "{0}{1}".format(self.cn_suffixname) - df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace('_', ''), name) + df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace("_", ""), name) df.loc[:, "tpl"] = tpl_file return df -def write_grid_tpl(name, tpl_file, suffix, zn_array=None, shape=None, - spatial_reference=None, longnames=False): +def write_grid_tpl( + name, + tpl_file, + suffix, + zn_array=None, + shape=None, + spatial_reference=None, + longnames=False, +): """ write a grid-based template file for a 2-D array Args: @@ -3631,26 +4174,28 @@ def write_grid_tpl(name, tpl_file, suffix, zn_array=None, shape=None, shape = zn_array.shape parnme, x, y = [], [], [] - with open(tpl_file, 'w') as f: + with open(tpl_file, "w") as f: f.write("ptf ~\n") for i in range(shape[0]): for j in range(shape[1]): if zn_array is not None and zn_array[i, j] < 1: - pname = ' 1.0 ' + pname = " 1.0 " else: if longnames: pname = "{0}_i:{0}_j:{1}_{2}".format(name, i, j, suffix) if spatial_reference is not None: pname += "_x:{0:10.2E}_y:{1:10.2E}".format( - spatial_reference.xcentergrid[i,j], - spatial_reference.ycentergrid[i,j]) + spatial_reference.xcentergrid[i, j], + spatial_reference.ycentergrid[i, j], + ) else: pname = "{0}{1:03d}{2:03d}".format(name, i, j) if len(pname) > 12: - warnings.warn("grid pname too long for pest:{0}". \ - format(pname)) + warnings.warn( + "grid pname too long for pest:{0}".format(pname) + ) parnme.append(pname) - pname = ' ~ {0} ~ '.format(pname) + pname = " ~ {0} ~ ".format(pname) if spatial_reference is not None: x.append(spatial_reference.xcentergrid[i, j]) y.append(spatial_reference.ycentergrid[i, j]) @@ -3659,15 +4204,22 @@ def write_grid_tpl(name, tpl_file, suffix, zn_array=None, shape=None, f.write("\n") df = pd.DataFrame({"parnme": parnme}, index=parnme) if spatial_reference is not None: - df.loc[:, 'x'] = x - df.loc[:, 'y'] = y - df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace('_', ''), name) + df.loc[:, "x"] = x + df.loc[:, "y"] = y + df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace("_", ""), name) df.loc[:, "tpl"] = tpl_file return df -def write_zone_tpl(name, tpl_file, suffix="", zn_array=None, shape=None, - longnames=False,fill_value="1.0"): +def write_zone_tpl( + name, + tpl_file, + suffix="", + zn_array=None, + shape=None, + longnames=False, + fill_value="1.0", +): """ write a zone-based template file for a 2-D array Args: @@ -3695,7 +4247,7 @@ def write_zone_tpl(name, tpl_file, suffix="", zn_array=None, shape=None, parnme = [] zone = [] - with open(tpl_file, 'w') as f: + with open(tpl_file, "w") as f: f.write("ptf ~\n") for i in range(shape[0]): for j in range(shape[1]): @@ -3711,15 +4263,16 @@ def write_zone_tpl(name, tpl_file, suffix="", zn_array=None, shape=None, pname = "{0}_zn{1}".format(name, zval) if len(pname) > 12: - warnings.warn("zone pname too long for pest:{0}". \ - format(pname)) + warnings.warn( + "zone pname too long for pest:{0}".format(pname) + ) parnme.append(pname) zone.append(zval) pname = " ~ {0} ~".format(pname) f.write(pname) f.write("\n") - df = pd.DataFrame({"parnme": parnme,"zone":zone}, index=parnme) - df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace("_", ''), name) + df = pd.DataFrame({"parnme": parnme, "zone": zone}, index=parnme) + df.loc[:, "pargp"] = "{0}_{1}".format(suffix.replace("_", ""), name) return df @@ -3784,19 +4337,19 @@ def build_jac_test_csv(pst, num_steps, par_names=None, forward=True): sign = -1.0 val = org_val + (sign * incr[par_name]) if val < lbnd[par_name]: - raise Exception("parameter {0} went out of bounds". - format(par_name)) + raise Exception("parameter {0} went out of bounds".format(par_name)) elif val < lbnd[par_name]: sign = 1.0 val = org_val + (sign * incr[par_name]) if val > ubnd[par_name]: - raise Exception("parameter {0} went out of bounds". - format(par_name)) + raise Exception("parameter {0} went out of bounds".format(par_name)) vals.loc[par_name] = val vals.loc[li] = 10 ** vals.loc[li] df.loc[idx[irow], pst.par_names] = vals - full_names.append("{0}_{1:<15.6E}".format(par_name, vals.loc[par_name]).strip()) + full_names.append( + "{0}_{1:<15.6E}".format(par_name, vals.loc[par_name]).strip() + ) irow += 1 last_val = val @@ -3804,20 +4357,22 @@ def build_jac_test_csv(pst, num_steps, par_names=None, forward=True): return df -def _write_df_tpl(filename, df, sep=',', tpl_marker='~', **kwargs): +def _write_df_tpl(filename, df, sep=",", tpl_marker="~", **kwargs): """function write a pandas dataframe to a template file. """ if "line_terminator" not in kwargs: if "win" in platform.platform().lower(): kwargs["line_terminator"] = "\n" - with open(filename, 'w') as f: + with open(filename, "w") as f: f.write("ptf {0}\n".format(tpl_marker)) f.flush() - df.to_csv(f, sep=sep, mode='a', **kwargs) + df.to_csv(f, sep=sep, mode="a", **kwargs) -def setup_fake_forward_run(pst, new_pst_name, org_cwd='.', bak_suffix="._bak", new_cwd='.'): +def setup_fake_forward_run( + pst, new_pst_name, org_cwd=".", bak_suffix="._bak", new_cwd="." +): """setup a fake forward run for a pst. Args: @@ -3890,7 +4445,7 @@ def setup_fake_forward_run(pst, new_pst_name, org_cwd='.', bak_suffix="._bak", n if os.path.exists(org_pth): shutil.copy2(org_pth, new_pth) - with open(os.path.join(new_cwd, "fake_forward_run.py"), 'w') as f: + with open(os.path.join(new_cwd, "fake_forward_run.py"), "w") as f: f.write("import os\nimport shutil\n") for org, bak in pairs.items(): f.write("shutil.copy2(r'{0}',r'{1}')\n".format(bak, org)) @@ -3900,10 +4455,16 @@ def setup_fake_forward_run(pst, new_pst_name, org_cwd='.', bak_suffix="._bak", n return pst -def setup_temporal_diff_obs(pst, ins_file, out_file=None, - include_zero_weight=False, include_path=False, - sort_by_name=True,long_names=True, - prefix="dif"): +def setup_temporal_diff_obs( + pst, + ins_file, + out_file=None, + include_zero_weight=False, + include_path=False, + sort_by_name=True, + long_names=True, + prefix="dif", +): """ a helper function to setup difference-in-time observations based on an existing set of observations in an instruction file using the observation grouping in the control file @@ -3943,14 +4504,18 @@ def setup_temporal_diff_obs(pst, ins_file, out_file=None, """ if not os.path.exists(ins_file): - raise Exception("setup_temporal_diff_obs() error: ins_file '{0}' not found". \ - format(ins_file)) + raise Exception( + "setup_temporal_diff_obs() error: ins_file '{0}' not found".format(ins_file) + ) # the ins routines will check for missing obs, etc try: ins = pyemu.pst_utils.InstructionFile(ins_file, pst) except Exception as e: - raise Exception("setup_temporal_diff_obs(): error processing instruction file: {0}". \ - format(str(e))) + raise Exception( + "setup_temporal_diff_obs(): error processing instruction file: {0}".format( + str(e) + ) + ) if out_file is None: out_file = ins_file.replace(".ins", "") @@ -3962,67 +4527,84 @@ def setup_temporal_diff_obs(pst, ins_file, out_file=None, group_vc = pst.observation_data.loc[ins.obs_name_set, "obgnme"].value_counts() else: - group_vc = obs.loc[obs.apply(lambda x: x.weight > 0 and \ - x.obsnme in ins.obs_name_set, axis=1),\ - "obgnme"].value_counts() + group_vc = obs.loc[ + obs.apply(lambda x: x.weight > 0 and x.obsnme in ins.obs_name_set, axis=1), + "obgnme", + ].value_counts() groups = list(group_vc.loc[group_vc > 1].index) if len(groups) == 0: - raise Exception("setup_temporal_diff_obs() error: no obs groups found " + - "with more than one non-zero weighted obs") + raise Exception( + "setup_temporal_diff_obs() error: no obs groups found " + + "with more than one non-zero weighted obs" + ) # process each group diff_dfs = [] for group in groups: # get a sub dataframe with non-zero weighted obs that are in this group and in the instruction file - obs_group = obs.loc[obs.obgnme == group,:].copy() - obs_group = obs_group.loc[obs_group.apply(lambda x: x.weight > 0 and x.obsnme in ins.obs_name_set,axis=1),:] + obs_group = obs.loc[obs.obgnme == group, :].copy() + obs_group = obs_group.loc[ + obs_group.apply( + lambda x: x.weight > 0 and x.obsnme in ins.obs_name_set, axis=1 + ), + :, + ] # sort if requested if sort_by_name: - obs_group = obs_group.sort_values(by="obsnme",ascending=True) + obs_group = obs_group.sort_values(by="obsnme", ascending=True) # the names starting with the first diff1 = obs_group.obsnme[:-1].values # the names ending with the last diff2 = obs_group.obsnme[1:].values # form a dataframe - diff_df = pd.DataFrame({"diff1":diff1,"diff2":diff2}) - #build up some obs names + diff_df = pd.DataFrame({"diff1": diff1, "diff2": diff2}) + # build up some obs names if long_names: - diff_df.loc[:,"obsnme"] = ["{0}_{1}__{2}".format(prefix,d1,d2) for d1,d2 in zip(diff1,diff2)] + diff_df.loc[:, "obsnme"] = [ + "{0}_{1}__{2}".format(prefix, d1, d2) for d1, d2 in zip(diff1, diff2) + ] else: - diff_df.loc[:,"obsnme"] = ["{0}_{1}_{2}".format(prefix,group,c) for c in len(diff1)] + diff_df.loc[:, "obsnme"] = [ + "{0}_{1}_{2}".format(prefix, group, c) for c in len(diff1) + ] # set the obs names as the index (per usual) diff_df.index = diff_df.obsnme # set the group name for the diff obs - diff_df.loc[:,"obgnme"] = "{0}_{1}".format(prefix,group) + diff_df.loc[:, "obgnme"] = "{0}_{1}".format(prefix, group) # set the weights using the standard prop of variance formula - d1_std,d2_std = 1./obs_group.weight[:-1].values,1./obs_group.weight[1:].values - diff_df.loc[:,"weight"] = 1./(np.sqrt((d1_std**2)+(d2_std**2))) + d1_std, d2_std = ( + 1.0 / obs_group.weight[:-1].values, + 1.0 / obs_group.weight[1:].values, + ) + diff_df.loc[:, "weight"] = 1.0 / (np.sqrt((d1_std ** 2) + (d2_std ** 2))) diff_dfs.append(diff_df) # concat all the diff dataframes diff_df = pd.concat(diff_dfs) - #save the dataframe as a config file - config_file = ins_file.replace(".ins",".diff.config") + # save the dataframe as a config file + config_file = ins_file.replace(".ins", ".diff.config") - f = open(config_file, 'w') + f = open(config_file, "w") if include_path: - #ins_path = os.path.split(ins_file)[0] - #f = open(os.path.join(ins_path,config_file),'w') - f.write("{0},{1}\n".format(os.path.split(ins_file)[-1],os.path.split(out_file)[-1])) - #diff_df.to_csv(os.path.join(ins_path,config_file)) + # ins_path = os.path.split(ins_file)[0] + # f = open(os.path.join(ins_path,config_file),'w') + f.write( + "{0},{1}\n".format(os.path.split(ins_file)[-1], os.path.split(out_file)[-1]) + ) + # diff_df.to_csv(os.path.join(ins_path,config_file)) else: - f.write("{0},{1}\n".format(ins_file,out_file)) - #diff_df.to_csv(os.path.join(config_file)) + f.write("{0},{1}\n".format(ins_file, out_file)) + # diff_df.to_csv(os.path.join(config_file)) f.flush() - diff_df.to_csv(f,mode="a") + diff_df.to_csv(f, mode="a") f.flush() f.close() # write the instruction file diff_ins_file = config_file.replace(".config", ".processed.ins") - with open(diff_ins_file, 'w') as f: + with open(diff_ins_file, "w") as f: f.write("pif ~\n") f.write("l1 \n") for oname in diff_df.obsnme: @@ -4047,14 +4629,14 @@ def setup_temporal_diff_obs(pst, ins_file, out_file=None, # ok, now we can use the new instruction file to process the diff outputs ins = pyemu.pst_utils.InstructionFile(diff_ins_file) - ins_pro_diff_df = ins.read_output_file(diff_ins_file.replace(".ins","")) + ins_pro_diff_df = ins.read_output_file(diff_ins_file.replace(".ins", "")) if include_path: os.chdir(b_d) print(ins_pro_diff_df) - diff_df.loc[ins_pro_diff_df.index,"obsval"] = ins_pro_diff_df.obsval + diff_df.loc[ins_pro_diff_df.index, "obsval"] = ins_pro_diff_df.obsval frun_line = "pyemu.helpers.apply_temporal_diff_obs('{0}')\n".format(config_file) - return frun_line,diff_df + return frun_line, diff_df def apply_temporal_diff_obs(config_file): @@ -4073,42 +4655,60 @@ def apply_temporal_diff_obs(config_file): """ if not os.path.exists(config_file): - raise Exception("apply_temporal_diff_obs() error: config_file '{0}' not found".format(config_file)) - with open(config_file,'r') as f: - line = f.readline().strip().split(',') - ins_file,out_file = line[0],line[1] + raise Exception( + "apply_temporal_diff_obs() error: config_file '{0}' not found".format( + config_file + ) + ) + with open(config_file, "r") as f: + line = f.readline().strip().split(",") + ins_file, out_file = line[0], line[1] diff_df = pd.read_csv(f) if not os.path.exists(out_file): - raise Exception("apply_temporal_diff_obs() error: out_file '{0}' not found".format(out_file)) + raise Exception( + "apply_temporal_diff_obs() error: out_file '{0}' not found".format(out_file) + ) if not os.path.exists(ins_file): - raise Exception("apply_temporal_diff_obs() error: ins_file '{0}' not found".format(ins_file)) + raise Exception( + "apply_temporal_diff_obs() error: ins_file '{0}' not found".format(ins_file) + ) try: ins = pyemu.pst_utils.InstructionFile(ins_file) except Exception as e: - raise Exception("apply_temporal_diff_obs() error instantiating ins file: {0}".format(str(e))) + raise Exception( + "apply_temporal_diff_obs() error instantiating ins file: {0}".format(str(e)) + ) try: out_df = ins.read_output_file(out_file) except Exception as e: - raise Exception("apply_temporal_diff_obs() error processing ins-out file pair: {0}".format(str(e))) + raise Exception( + "apply_temporal_diff_obs() error processing ins-out file pair: {0}".format( + str(e) + ) + ) - #make sure all the listed obs names in the diff_df are in the out_df + # make sure all the listed obs names in the diff_df are in the out_df diff_names = set(diff_df.diff1.to_list()) diff_names.update(set(diff_df.diff2.to_list())) missing = diff_names - set(list(out_df.index.values)) if len(missing) > 0: - raise Exception("apply_temporal_diff_obs() error: the following obs names in the config file "+\ - "are not in the instruction file processed outputs :" + ",".join(missing)) - diff_df.loc[:,"diff1_obsval"] = out_df.loc[diff_df.diff1.values,"obsval"].values + raise Exception( + "apply_temporal_diff_obs() error: the following obs names in the config file " + + "are not in the instruction file processed outputs :" + + ",".join(missing) + ) + diff_df.loc[:, "diff1_obsval"] = out_df.loc[diff_df.diff1.values, "obsval"].values diff_df.loc[:, "diff2_obsval"] = out_df.loc[diff_df.diff2.values, "obsval"].values - diff_df.loc[:,"diff_obsval"] = diff_df.diff1_obsval - diff_df.diff2_obsval - processed_name = config_file.replace(".config",".processed") - diff_df.loc[:, ["obsnme","diff1_obsval", "diff2_obsval", "diff_obsval"]].\ - to_csv(processed_name,sep=' ',index=False) + diff_df.loc[:, "diff_obsval"] = diff_df.diff1_obsval - diff_df.diff2_obsval + processed_name = config_file.replace(".config", ".processed") + diff_df.loc[:, ["obsnme", "diff1_obsval", "diff2_obsval", "diff_obsval"]].to_csv( + processed_name, sep=" ", index=False + ) return diff_df # web address of spatial reference dot org -srefhttp = 'https://spatialreference.org' +srefhttp = "https://spatialreference.org" class SpatialReference(object): @@ -4203,51 +4803,72 @@ class SpatialReference(object): xul, yul = None, None xll, yll = None, None - rotation = 0. - length_multiplier = 1. - origin_loc = 'ul' # or ll - - defaults = {"xul": None, "yul": None, "rotation": 0., - "proj4_str": None, - "units": None, "lenuni": 2, - "length_multiplier": None, - "source": 'defaults'} - - lenuni_values = {'undefined': 0, - 'feet': 1, - 'meters': 2, - 'centimeters': 3} + rotation = 0.0 + length_multiplier = 1.0 + origin_loc = "ul" # or ll + + defaults = { + "xul": None, + "yul": None, + "rotation": 0.0, + "proj4_str": None, + "units": None, + "lenuni": 2, + "length_multiplier": None, + "source": "defaults", + } + + lenuni_values = {"undefined": 0, "feet": 1, "meters": 2, "centimeters": 3} lenuni_text = {v: k for k, v in lenuni_values.items()} - def __init__(self, delr=np.array([]), delc=np.array([]), lenuni=2, - xul=None, yul=None, xll=None, yll=None, rotation=0.0, - proj4_str=None, epsg=None, prj=None, units=None, - length_multiplier=None, source=None): + def __init__( + self, + delr=np.array([]), + delc=np.array([]), + lenuni=2, + xul=None, + yul=None, + xll=None, + yll=None, + rotation=0.0, + proj4_str=None, + epsg=None, + prj=None, + units=None, + length_multiplier=None, + source=None, + ): for delrc in [delr, delc]: if isinstance(delrc, float) or isinstance(delrc, int): - msg = ('delr and delcs must be an array or sequences equal in ' - 'length to the number of rows/columns.') + msg = ( + "delr and delcs must be an array or sequences equal in " + "length to the number of rows/columns." + ) raise TypeError(msg) self.delc = np.atleast_1d(np.array(delc)).astype( - np.float64) # * length_multiplier + np.float64 + ) # * length_multiplier self.delr = np.atleast_1d(np.array(delr)).astype( - np.float64) # * length_multiplier + np.float64 + ) # * length_multiplier if self.delr.sum() == 0 or self.delc.sum() == 0: if xll is None or yll is None: - msg = ('Warning: no grid spacing. ' - 'Lower-left corner offset calculation methods requires ' - 'arguments for delr and delc. Origin will be set to ' - 'upper-left') + msg = ( + "Warning: no grid spacing. " + "Lower-left corner offset calculation methods requires " + "arguments for delr and delc. Origin will be set to " + "upper-left" + ) warnings.warn(msg, PyemuWarning) xll, yll = None, None # xul, yul = None, None self._lenuni = lenuni self._proj4_str = proj4_str - # + # self._epsg = epsg # if epsg is not None: # self._proj4_str = getproj4(self._epsg) @@ -4263,45 +4884,49 @@ def __init__(self, delr=np.array([]), delc=np.array([]), lenuni=2, @property def xll(self): - if self.origin_loc == 'll': - xll = self._xll if self._xll is not None else 0. - elif self.origin_loc == 'ul': + if self.origin_loc == "ll": + xll = self._xll if self._xll is not None else 0.0 + elif self.origin_loc == "ul": # calculate coords for lower left corner - xll = self._xul - (np.sin(self.theta) * self.yedge[0] * - self.length_multiplier) + xll = self._xul - ( + np.sin(self.theta) * self.yedge[0] * self.length_multiplier + ) return xll @property def yll(self): - if self.origin_loc == 'll': - yll = self._yll if self._yll is not None else 0. - elif self.origin_loc == 'ul': + if self.origin_loc == "ll": + yll = self._yll if self._yll is not None else 0.0 + elif self.origin_loc == "ul": # calculate coords for lower left corner - yll = self._yul - (np.cos(self.theta) * self.yedge[0] * - self.length_multiplier) + yll = self._yul - ( + np.cos(self.theta) * self.yedge[0] * self.length_multiplier + ) return yll @property def xul(self): - if self.origin_loc == 'll': + if self.origin_loc == "ll": # calculate coords for upper left corner - xul = self._xll + (np.sin(self.theta) * self.yedge[0] * - self.length_multiplier) - if self.origin_loc == 'ul': + xul = self._xll + ( + np.sin(self.theta) * self.yedge[0] * self.length_multiplier + ) + if self.origin_loc == "ul": # calculate coords for lower left corner - xul = self._xul if self._xul is not None else 0. + xul = self._xul if self._xul is not None else 0.0 return xul @property def yul(self): - if self.origin_loc == 'll': + if self.origin_loc == "ll": # calculate coords for upper left corner - yul = self._yll + (np.cos(self.theta) * self.yedge[0] * - self.length_multiplier) + yul = self._yll + ( + np.cos(self.theta) * self.yedge[0] * self.length_multiplier + ) - if self.origin_loc == 'ul': + if self.origin_loc == "ul": # calculate coords for lower left corner - yul = self._yul if self._yul is not None else 0. + yul = self._yul if self._yul is not None else 0.0 return yul @property @@ -4314,13 +4939,12 @@ def proj4_str(self): else: proj4_str = self._proj4_str # set the epsg if proj4 specifies it - tmp = [i for i in self._proj4_str.split() if - 'epsg' in i.lower()] - self._epsg = int(tmp[0].split(':')[1]) + tmp = [i for i in self._proj4_str.split() if "epsg" in i.lower()] + self._epsg = int(tmp[0].split(":")[1]) else: proj4_str = self._proj4_str elif self.epsg is not None: - proj4_str = '+init=epsg:{}'.format(self.epsg) + proj4_str = "+init=epsg:{}".format(self.epsg) return proj4_str @property @@ -4373,15 +4997,16 @@ def _parse_units_from_proj4(self): # "ft", "0.3048", "International Foot", if "units=m" in proj_str: units = "meters" - elif "units=ft" in proj_str or \ - "units=us-ft" in proj_str or \ - "to_meters:0.3048" in proj_str: + elif ( + "units=ft" in proj_str + or "units=us-ft" in proj_str + or "to_meters:0.3048" in proj_str + ): units = "feet" return units except: if self.proj4_str is not None: - print(' could not parse units from {}'.format( - self.proj4_str)) + print(" could not parse units from {}".format(self.proj4_str)) @property def units(self): @@ -4391,7 +5016,7 @@ def units(self): units = self._parse_units_from_proj4() if units is None: # print("warning: assuming SpatialReference units are meters") - units = 'meters' + units = "meters" assert units in self.supported_units return units @@ -4405,23 +5030,23 @@ def length_multiplier(self): if self._length_multiplier is not None: lm = self._length_multiplier else: - if self.model_length_units == 'feet': - if self.units == 'meters': + if self.model_length_units == "feet": + if self.units == "meters": lm = 0.3048 - elif self.units == 'feet': - lm = 1. - elif self.model_length_units == 'meters': - if self.units == 'feet': - lm = 1 / .3048 - elif self.units == 'meters': - lm = 1. - elif self.model_length_units == 'centimeters': - if self.units == 'meters': - lm = 1 / 100. - elif self.units == 'feet': + elif self.units == "feet": + lm = 1.0 + elif self.model_length_units == "meters": + if self.units == "feet": + lm = 1 / 0.3048 + elif self.units == "meters": + lm = 1.0 + elif self.model_length_units == "centimeters": + if self.units == "meters": + lm = 1 / 100.0 + elif self.units == "feet": lm = 1 / 30.48 else: # model units unspecified; default to 1 - lm = 1. + lm = 1.0 return lm @property @@ -4437,7 +5062,7 @@ def bounds(self): return xmin, ymin, xmax, ymax @staticmethod - def load(namefile=None, reffile='usgs.model.reference'): + def load(namefile=None, reffile="usgs.model.reference"): """ Attempts to load spatial reference information from the following files (in order): @@ -4459,118 +5084,120 @@ def load(namefile=None, reffile='usgs.model.reference'): def attribs_from_namfile_header(namefile): # check for reference info in the nam file header d = SpatialReference.defaults.copy() - d['source'] = 'namfile' + d["source"] = "namfile" if namefile is None: return None header = [] - with open(namefile, 'r') as f: + with open(namefile, "r") as f: for line in f: - if not line.startswith('#'): + if not line.startswith("#"): break - header.extend(line.strip().replace( - '#', '').replace(',', ';').split(';')) + header.extend( + line.strip().replace("#", "").replace(",", ";").split(";") + ) for item in header: if "xul" in item.lower(): try: - d['xul'] = float(item.split(':')[1]) + d["xul"] = float(item.split(":")[1]) except: - print(' could not parse xul ' + - 'in {}'.format(namefile)) + print(" could not parse xul " + "in {}".format(namefile)) elif "yul" in item.lower(): try: - d['yul'] = float(item.split(':')[1]) + d["yul"] = float(item.split(":")[1]) except: - print(' could not parse yul ' + - 'in {}'.format(namefile)) + print(" could not parse yul " + "in {}".format(namefile)) elif "rotation" in item.lower(): try: - d['rotation'] = float(item.split(':')[1]) + d["rotation"] = float(item.split(":")[1]) except: - print(' could not parse rotation ' + - 'in {}'.format(namefile)) + print(" could not parse rotation " + "in {}".format(namefile)) elif "proj4_str" in item.lower(): try: - proj4_str = ':'.join(item.split(':')[1:]).strip() - if proj4_str.lower() == 'none': + proj4_str = ":".join(item.split(":")[1:]).strip() + if proj4_str.lower() == "none": proj4_str = None - d['proj4_str'] = proj4_str + d["proj4_str"] = proj4_str except: - print(' could not parse proj4_str ' + - 'in {}'.format(namefile)) + print(" could not parse proj4_str " + "in {}".format(namefile)) elif "start" in item.lower(): try: - d['start_datetime'] = item.split(':')[1].strip() + d["start_datetime"] = item.split(":")[1].strip() except: - print(' could not parse start ' + - 'in {}'.format(namefile)) + print(" could not parse start " + "in {}".format(namefile)) # spatial reference length units elif "units" in item.lower(): - d['units'] = item.split(':')[1].strip() + d["units"] = item.split(":")[1].strip() # model length units elif "lenuni" in item.lower(): - d['lenuni'] = int(item.split(':')[1].strip()) + d["lenuni"] = int(item.split(":")[1].strip()) # multiplier for converting from model length units to sr length units elif "length_multiplier" in item.lower(): - d['length_multiplier'] = float(item.split(':')[1].strip()) + d["length_multiplier"] = float(item.split(":")[1].strip()) return d @staticmethod - def read_usgs_model_reference_file(reffile='usgs.model.reference'): + def read_usgs_model_reference_file(reffile="usgs.model.reference"): """ read spatial reference info from the usgs.model.reference file https://water.usgs.gov/ogw/policy/gw-model/modelers-setup.html """ - ITMUNI = {0: "undefined", 1: "seconds", 2: "minutes", 3: "hours", - 4: "days", - 5: "years"} + ITMUNI = { + 0: "undefined", + 1: "seconds", + 2: "minutes", + 3: "hours", + 4: "days", + 5: "years", + } itmuni_values = {v: k for k, v in ITMUNI.items()} d = SpatialReference.defaults.copy() - d['source'] = 'usgs.model.reference' + d["source"] = "usgs.model.reference" # discard default to avoid confusion with epsg code if entered - d.pop('proj4_str') + d.pop("proj4_str") if os.path.exists(reffile): with open(reffile) as fref: for line in fref: if len(line) > 1: - if line.strip()[0] != '#': - info = line.strip().split('#')[0].split() + if line.strip()[0] != "#": + info = line.strip().split("#")[0].split() if len(info) > 1: - d[info[0].lower()] = ' '.join(info[1:]) - d['xul'] = float(d['xul']) - d['yul'] = float(d['yul']) - d['rotation'] = float(d['rotation']) + d[info[0].lower()] = " ".join(info[1:]) + d["xul"] = float(d["xul"]) + d["yul"] = float(d["yul"]) + d["rotation"] = float(d["rotation"]) # convert the model.reference text to a lenuni value # (these are the model length units) - if 'length_units' in d.keys(): - d['lenuni'] = SpatialReference.lenuni_values[d['length_units']] - if 'time_units' in d.keys(): - d['itmuni'] = itmuni_values[d['time_units']] - if 'start_date' in d.keys(): - start_datetime = d.pop('start_date') - if 'start_time' in d.keys(): - start_datetime += ' {}'.format(d.pop('start_time')) - d['start_datetime'] = start_datetime - if 'epsg' in d.keys(): + if "length_units" in d.keys(): + d["lenuni"] = SpatialReference.lenuni_values[d["length_units"]] + if "time_units" in d.keys(): + d["itmuni"] = itmuni_values[d["time_units"]] + if "start_date" in d.keys(): + start_datetime = d.pop("start_date") + if "start_time" in d.keys(): + start_datetime += " {}".format(d.pop("start_time")) + d["start_datetime"] = start_datetime + if "epsg" in d.keys(): try: - d['epsg'] = int(d['epsg']) + d["epsg"] = int(d["epsg"]) except Exception as e: - raise Exception( - "error reading epsg code from file:\n" + str(e)) + raise Exception("error reading epsg code from file:\n" + str(e)) # this prioritizes epsg over proj4 if both are given # (otherwise 'proj4' entry will be dropped below) - elif 'proj4' in d.keys(): - d['proj4_str'] = d['proj4'] + elif "proj4" in d.keys(): + d["proj4_str"] = d["proj4"] # drop any other items that aren't used in sr class - d = {k: v for k, v in d.items() if - k.lower() in SpatialReference.defaults.keys() - or k.lower() in {'epsg', 'start_datetime', 'itmuni', - 'source'}} + d = { + k: v + for k, v in d.items() + if k.lower() in SpatialReference.defaults.keys() + or k.lower() in {"epsg", "start_datetime", "itmuni", "source"} + } return d else: return None @@ -4578,59 +5205,52 @@ def read_usgs_model_reference_file(reffile='usgs.model.reference'): def __setattr__(self, key, value): reset = True if key == "delr": - super(SpatialReference, self). \ - __setattr__("delr", np.atleast_1d(np.array(value))) + super(SpatialReference, self).__setattr__( + "delr", np.atleast_1d(np.array(value)) + ) elif key == "delc": - super(SpatialReference, self). \ - __setattr__("delc", np.atleast_1d(np.array(value))) + super(SpatialReference, self).__setattr__( + "delc", np.atleast_1d(np.array(value)) + ) elif key == "xul": - super(SpatialReference, self). \ - __setattr__("_xul", float(value)) - self.origin_loc = 'ul' + super(SpatialReference, self).__setattr__("_xul", float(value)) + self.origin_loc = "ul" elif key == "yul": - super(SpatialReference, self). \ - __setattr__("_yul", float(value)) - self.origin_loc = 'ul' + super(SpatialReference, self).__setattr__("_yul", float(value)) + self.origin_loc = "ul" elif key == "xll": - super(SpatialReference, self). \ - __setattr__("_xll", float(value)) - self.origin_loc = 'll' + super(SpatialReference, self).__setattr__("_xll", float(value)) + self.origin_loc = "ll" elif key == "yll": - super(SpatialReference, self). \ - __setattr__("_yll", float(value)) - self.origin_loc = 'll' + super(SpatialReference, self).__setattr__("_yll", float(value)) + self.origin_loc = "ll" elif key == "length_multiplier": - super(SpatialReference, self). \ - __setattr__("_length_multiplier", float(value)) + super(SpatialReference, self).__setattr__( + "_length_multiplier", float(value) + ) elif key == "rotation": - super(SpatialReference, self). \ - __setattr__("rotation", float(value)) + super(SpatialReference, self).__setattr__("rotation", float(value)) elif key == "lenuni": - super(SpatialReference, self). \ - __setattr__("_lenuni", int(value)) + super(SpatialReference, self).__setattr__("_lenuni", int(value)) elif key == "units": value = value.lower() assert value in self.supported_units - super(SpatialReference, self). \ - __setattr__("_units", value) + super(SpatialReference, self).__setattr__("_units", value) elif key == "proj4_str": - super(SpatialReference, self). \ - __setattr__("_proj4_str", value) + super(SpatialReference, self).__setattr__("_proj4_str", value) # reset the units and epsg units = self._parse_units_from_proj4() if units is not None: self._units = units self._epsg = None elif key == "epsg": - super(SpatialReference, self). \ - __setattr__("_epsg", value) + super(SpatialReference, self).__setattr__("_epsg", value) # reset the units and proj4 # self._units = None # self._proj4_str = getproj4(self._epsg) # self.crs = crs(epsg=value) elif key == "prj": - super(SpatialReference, self). \ - __setattr__("prj", value) + super(SpatialReference, self).__setattr__("prj", value) # translation to proj4 strings in crs class not robust yet # leave units and proj4 alone for now. # self.crs = CRS(prj=value, epsg=self.epsg) @@ -4677,22 +5297,25 @@ def __eq__(self, other): @classmethod def from_namfile(cls, namefile, delr=np.array([]), delc=np.array([])): if delr is None or delc is None: - warnings.warn("One or both of grid spacing information " - "missing,\n required for most pyemu methods " - "that use sr,\n can be passed later if desired " - "(e.g. sr.delr = row spacing)", PyemuWarning) + warnings.warn( + "One or both of grid spacing information " + "missing,\n required for most pyemu methods " + "that use sr,\n can be passed later if desired " + "(e.g. sr.delr = row spacing)", + PyemuWarning, + ) attribs = SpatialReference.attribs_from_namfile_header(namefile) - attribs['delr'] = delr - attribs['delc'] = delc + attribs["delr"] = delr + attribs["delc"] = delc try: attribs.pop("start_datetime") except: - print(' could not remove start_datetime') + print(" could not remove start_datetime") return SpatialReference(**attribs) @classmethod def from_gridspec(cls, gridspec_file, lenuni=0): - f = open(gridspec_file, 'r') + f = open(gridspec_file, "r") raw = f.readline().strip().split() nrow = int(raw[0]) ncol = int(raw[1]) @@ -4703,8 +5326,8 @@ def from_gridspec(cls, gridspec_file, lenuni=0): while j < ncol: raw = f.readline().strip().split() for r in raw: - if '*' in r: - rraw = r.split('*') + if "*" in r: + rraw = r.split("*") for n in range(int(rraw[0])): delr.append(float(rraw[1])) j += 1 @@ -4716,8 +5339,8 @@ def from_gridspec(cls, gridspec_file, lenuni=0): while i < nrow: raw = f.readline().strip().split() for r in raw: - if '*' in r: - rraw = r.split('*') + if "*" in r: + rraw = r.split("*") for n in range(int(rraw[0])): delc.append(float(rraw[1])) i += 1 @@ -4725,50 +5348,59 @@ def from_gridspec(cls, gridspec_file, lenuni=0): delc.append(float(r)) i += 1 f.close() - return cls(np.array(delr), np.array(delc), - lenuni, xul=xul, yul=yul, rotation=rot) + return cls( + np.array(delr), np.array(delc), lenuni, xul=xul, yul=yul, rotation=rot + ) @property def attribute_dict(self): - return {"xul": self.xul, "yul": self.yul, "rotation": self.rotation, - "proj4_str": self.proj4_str} - - def set_spatialreference(self, xul=None, yul=None, xll=None, yll=None, - rotation=0.0): + return { + "xul": self.xul, + "yul": self.yul, + "rotation": self.rotation, + "proj4_str": self.proj4_str, + } + + def set_spatialreference( + self, xul=None, yul=None, xll=None, yll=None, rotation=0.0 + ): """ set spatial reference - can be called from model instance """ if xul is not None and xll is not None: - msg = ('Both xul and xll entered. Please enter either xul, yul or ' - 'xll, yll.') + msg = ( + "Both xul and xll entered. Please enter either xul, yul or " "xll, yll." + ) raise ValueError(msg) if yul is not None and yll is not None: - msg = ('Both yul and yll entered. Please enter either xul, yul or ' - 'xll, yll.') + msg = ( + "Both yul and yll entered. Please enter either xul, yul or " "xll, yll." + ) raise ValueError(msg) # set the origin priority based on the left corner specified # (the other left corner will be calculated). If none are specified # then default to upper left if xul is None and yul is None and xll is None and yll is None: - self.origin_loc = 'ul' - xul = 0. + self.origin_loc = "ul" + xul = 0.0 yul = self.delc.sum() elif xll is not None: - self.origin_loc = 'll' + self.origin_loc = "ll" else: - self.origin_loc = 'ul' + self.origin_loc = "ul" self.rotation = rotation - self._xll = xll if xll is not None else 0. - self._yll = yll if yll is not None else 0. - self._xul = xul if xul is not None else 0. - self._yul = yul if yul is not None else 0. + self._xll = xll if xll is not None else 0.0 + self._yll = yll if yll is not None else 0.0 + self._xul = xul if xul is not None else 0.0 + self._yul = yul if yul is not None else 0.0 return def __repr__(self): - s = "xul:{0:<.10G}; yul:{1:<.10G}; rotation:{2: 0), ("delr not passed to " - "spatial reference object") + assert self.delr is not None and len(self.delr) > 0, ( + "delr not passed to " "spatial reference object" + ) x = np.add.accumulate(self.delr) - 0.5 * self.delr return x @@ -4966,12 +5595,11 @@ def get_ycenter_array(self): coordinate for every row in the grid in model space - not offset of rotated. """ - assert (self.delc is not None - and len(self.delc) > 0), ("delc not passed to " - "spatial reference object") + assert self.delc is not None and len(self.delc) > 0, ( + "delc not passed to " "spatial reference object" + ) Ly = np.add.reduce(self.delc) - y = Ly - (np.add.accumulate(self.delc) - 0.5 * - self.delc) + y = Ly - (np.add.accumulate(self.delc) - 0.5 * self.delc) return y def get_xedge_array(self): @@ -4981,10 +5609,10 @@ def get_xedge_array(self): or rotated. Array is of size (ncol + 1) """ - assert (self.delr is not None - and len(self.delr) > 0), ("delr not passed to " - "spatial reference object") - xedge = np.concatenate(([0.], np.add.accumulate(self.delr))) + assert self.delr is not None and len(self.delr) > 0, ( + "delr not passed to " "spatial reference object" + ) + xedge = np.concatenate(([0.0], np.add.accumulate(self.delr))) return xedge def get_yedge_array(self): @@ -4994,31 +5622,32 @@ def get_yedge_array(self): rotated. Array is of size (nrow + 1) """ - assert (self.delc is not None - and len(self.delc) > 0), ("delc not passed to " - "spatial reference object") + assert self.delc is not None and len(self.delc) > 0, ( + "delc not passed to " "spatial reference object" + ) length_y = np.add.reduce(self.delc) - yedge = np.concatenate(([length_y], length_y - - np.add.accumulate(self.delc))) + yedge = np.concatenate(([length_y], length_y - np.add.accumulate(self.delc))) return yedge def write_gridspec(self, filename): """ write a PEST-style grid specification file """ - f = open(filename, 'w') + f = open(filename, "w") + f.write("{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0])) f.write( - "{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0])) - f.write("{0:15.6E} {1:15.6E} {2:15.6E}\n".format( - self.xul * self.length_multiplier, - self.yul * self.length_multiplier, - self.rotation)) + "{0:15.6E} {1:15.6E} {2:15.6E}\n".format( + self.xul * self.length_multiplier, + self.yul * self.length_multiplier, + self.rotation, + ) + ) for r in self.delr: f.write("{0:15.6E} ".format(r)) - f.write('\n') + f.write("\n") for c in self.delc: f.write("{0:15.6E} ".format(c)) - f.write('\n') + f.write("\n") return def get_vertices(self, i, j): @@ -5102,7 +5731,7 @@ def get_ij(self, x, y): # """ # Write a numpy array to Arc Ascii grid or shapefile with the # model reference. - # + # # Parameters # ---------- # filename : str @@ -5122,7 +5751,7 @@ def get_ij(self, x, y): # keyword arguments to np.savetxt (ascii) # rasterio.open (GeoTIFF) # or flopy.export.shapefile_utils.write_grid_shapefile2 - # + # # Notes # ----- # Rotated grids will be either be unrotated prior to export, @@ -5134,14 +5763,14 @@ def get_ij(self, x, y): # Arc Ascii and GeoTiff (besides disk usage) is that the # unrotated Arc Ascii will have a different grid size, whereas the GeoTiff # will have the same number of rows and pixels as the original. - # + # # """ - # + # # if filename.lower().endswith(".asc"): # if len(np.unique(self.delr)) != len(np.unique(self.delc)) != 1 \ # or self.delr[0] != self.delc[0]: # raise ValueError('Arc ascii arrays require a uniform grid.') - # + # # xll, yll = self.xll, self.yll # cellsize = self.delr[0] * self.length_multiplier # fmt = kwargs.get('fmt', '%.18e') @@ -5160,7 +5789,7 @@ def get_ij(self, x, y): # xll, yll = xmin, ymin # except ImportError: # print('scipy package required to export rotated grid.') - # + # # filename = '.'.join( # filename.split('.')[:-1]) + '.asc' # enforce .asc ending # nrow, ncol = a.shape @@ -5177,7 +5806,7 @@ def get_ij(self, x, y): # with open(filename, 'ab') as output: # np.savetxt(output, a, **kwargs) # print('wrote {}'.format(filename)) - # + # # elif filename.lower().endswith(".tif"): # if len(np.unique(self.delr)) != len(np.unique(self.delc)) != 1 \ # or self.delr[0] != self.delc[0]: @@ -5192,7 +5821,7 @@ def get_ij(self, x, y): # trans = Affine.translation(self.xul, self.yul) * \ # Affine.rotation(self.rotation) * \ # Affine.scale(dxdy, -dxdy) - # + # # # third dimension is the number of bands # a = a.copy() # if len(a.shape) == 2: @@ -5209,7 +5838,7 @@ def get_ij(self, x, y): # else: # msg = 'ERROR: invalid dtype "{}"'.format(a.dtype.name) # raise TypeError(msg) - # + # # meta = {'count': a.shape[0], # 'width': a.shape[2], # 'height': a.shape[1], @@ -5223,7 +5852,7 @@ def get_ij(self, x, y): # with rasterio.open(filename, 'w', **meta) as dst: # dst.write(a) # print('wrote {}'.format(filename)) - # + # # elif filename.lower().endswith(".shp"): # from ..export.shapefile_utils import write_grid_shapefile2 # epsg = kwargs.get('epsg', None) @@ -5239,7 +5868,7 @@ def get_ij(self, x, y): # **kwargs): # """ # Convert matplotlib contour plot object to shapefile. - # + # # Parameters # ---------- # filename : str @@ -5251,23 +5880,23 @@ def get_ij(self, x, y): # prj : str # Existing projection file to be used with new shapefile. # **kwargs : key-word arguments to flopy.export.shapefile_utils.recarray2shp - # + # # Returns # ------- # df : dataframe of shapefile contents - # + # # """ # from flopy.utils.geometry import LineString # from flopy.export.shapefile_utils import recarray2shp - # + # # if not isinstance(contours, list): # contours = [contours] - # + # # if epsg is None: # epsg = self._epsg # if prj is None: # prj = self.proj4_str - # + # # geoms = [] # level = [] # for ctr in contours: @@ -5276,13 +5905,13 @@ def get_ij(self, x, y): # paths = c.get_paths() # geoms += [LineString(p.vertices) for p in paths] # level += list(np.ones(len(paths)) * levels[i]) - # + # # # convert the dictionary to a recarray # ra = np.array(level, # dtype=[(fieldname, float)]).view(np.recarray) - # + # # recarray2shp(ra, geoms, filename, epsg=epsg, prj=prj, **kwargs) - # + # # def export_array_contours(self, filename, a, # fieldname='level', # interval=None, @@ -5293,7 +5922,7 @@ def get_ij(self, x, y): # **kwargs): # """ # Contour an array using matplotlib; write shapefile of contours. - # + # # Parameters # ---------- # filename : str @@ -5305,15 +5934,15 @@ def get_ij(self, x, y): # prj : str # Existing projection file to be used with new shapefile. # **kwargs : key-word arguments to flopy.export.shapefile_utils.recarray2shp - # + # # """ # import matplotlib.pyplot as plt - # + # # if epsg is None: # epsg = self._epsg # if prj is None: # prj = self.proj4_str - # + # # if interval is not None: # vmin = np.nanmin(a) # vmax = np.nanmax(a) @@ -5327,30 +5956,30 @@ def get_ij(self, x, y): # ctr = self.contour_array(ax, a, levels=levels) # self.export_contours(filename, ctr, fieldname, epsg, prj, **kwargs) # plt.close() - # + # # def contour_array(self, ax, a, **kwargs): # """ # Create a QuadMesh plot of the specified array using pcolormesh - # + # # Parameters # ---------- # ax : matplotlib.axes.Axes # ax to add the contours - # + # # a : np.ndarray # array to contour - # + # # Returns # ------- # contour_set : ContourSet - # + # # """ # from flopy.plot import ModelMap - # + # # kwargs['ax'] = ax # mm = ModelMap(sr=self) # contour_set = mm.contour_array(a=a, **kwargs) - # + # # return contour_set @property @@ -5599,12 +6228,12 @@ def _set_vertices(self): # """ # Sets up a local database of text representations of coordinate reference # systems, keyed by EPSG code. -# +# # The database is epsgref.json, located in the user's data directory. If # optional 'appdirs' package is available, this is in the platform-dependent # user directory, otherwise in the user's 'HOME/.flopy' directory. # """ -# +# # def __init__(self): # try: # from appdirs import user_data_dir @@ -5619,7 +6248,7 @@ def _set_vertices(self): # os.makedirs(datadir) # dbname = 'epsgref.json' # self.location = os.path.join(datadir, dbname) -# +# # def to_dict(self): # """ # Returns dict with EPSG code integer key, and WKT CRS text @@ -5635,18 +6264,18 @@ def _set_vertices(self): # except ValueError: # data[key] = value # return data -# +# # def _write(self, data): # with open(self.location, 'w') as f: # json.dump(data, f, indent=0) # f.write('\n') -# +# # def reset(self, verbose=True): # if os.path.exists(self.location): # os.remove(self.location) # if verbose: # print('Resetting {}'.format(self.location)) -# +# # def add(self, epsg, prj): # """ # add an epsg code to epsgref.json @@ -5654,14 +6283,14 @@ def _set_vertices(self): # data = self.to_dict() # data[epsg] = prj # self._write(data) -# +# # def get(self, epsg): # """ # returns prj from a epsg code, otherwise None if not found # """ # data = self.to_dict() # return data.get(epsg) -# +# # def remove(self, epsg): # """ # removes an epsg entry from epsgref.json @@ -5670,7 +6299,7 @@ def _set_vertices(self): # if epsg in data: # del data[epsg] # self._write(data) -# +# # @staticmethod # def show(): # ep = EpsgRef() @@ -5684,7 +6313,7 @@ def _set_vertices(self): # Container to parse and store coordinate reference system parameters, # and translate between different formats. # """ -# +# # def __init__(self, prj=None, esri_wkt=None, epsg=None): # warnings.warn( # "crs has been deprecated. Use CRS in shapefile_utils instead.", @@ -5701,7 +6330,7 @@ def _set_vertices(self): # self.wktstr = wktstr # if self.wktstr is not None: # self.parse_wkt() -# +# # @property # def crs(self): # """ @@ -5727,7 +6356,7 @@ def _set_vertices(self): # proj = 'aea' # elif self.projcs is None and self.geogcs is not None: # proj = 'longlat' -# +# # # datum # datum = None # if 'NAD' in self.datum.lower() or \ @@ -5740,7 +6369,7 @@ def _set_vertices(self): # datum += '27' # elif '84' in self.datum.lower(): # datum = 'wgs84' -# +# # # ellipse # ellps = None # if '1866' in self.spheroid_name: @@ -5749,10 +6378,10 @@ def _set_vertices(self): # ellps = 'grs80' # elif 'wgs' in self.spheroid_name.lower(): # ellps = 'wgs84' -# +# # # prime meridian # pm = self.primem[0].lower() -# +# # return {'proj': proj, # 'datum': datum, # 'ellps': ellps, @@ -5767,7 +6396,7 @@ def _set_vertices(self): # 'y_0': self.false_northing, # 'units': self.projcs_unit, # 'zone': self.utm_zone} -# +# # @property # def grid_mapping_attribs(self): # """ @@ -5799,16 +6428,16 @@ def _set_vertices(self): # 'false_easting': self.crs['x_0'], # 'false_northing': self.crs['y_0']} # return {k: v for k, v in attribs.items() if v is not None} -# +# # @property # def proj4(self): # """ # Not implemented yet # """ # return None -# +# # def parse_wkt(self): -# +# # self.projcs = self._gettxt('PROJCS["', '"') # self.utm_zone = None # if self.projcs is not None and 'utm' in self.projcs.lower(): @@ -5830,7 +6459,7 @@ def _set_vertices(self): # self.false_easting = self._getvalue('false_easting') # self.false_northing = self._getvalue('false_northing') # self.projcs_unit = self._getprojcs_unit() -# +# # def _gettxt(self, s1, s2): # s = self.wktstr.lower() # strt = s.find(s1.lower()) @@ -5838,7 +6467,7 @@ def _set_vertices(self): # strt += len(s1) # end = s[strt:].find(s2.lower()) + strt # return self.wktstr[strt:end] -# +# # def _getvalue(self, k): # s = self.wktstr.lower() # strt = s.find(k.lower()) @@ -5849,7 +6478,7 @@ def _set_vertices(self): # return float(self.wktstr[strt:end].split(',')[1]) # except: # print(' could not typecast wktstr to a float') -# +# # def _getgcsparam(self, txt): # nvalues = 3 if txt.lower() == 'spheroid' else 2 # tmp = self._gettxt('{}["'.format(txt), ']') @@ -5860,7 +6489,7 @@ def _set_vertices(self): # return name + values # else: # return [None] * nvalues -# +# # def _getprojcs_unit(self): # if self.projcs is not None: # tmp = self.wktstr.lower().split('unit["')[-1] @@ -5874,7 +6503,7 @@ def _set_vertices(self): # """ # Gets projection file (.prj) text for given epsg code from # spatialreference.org -# +# # Parameters # ---------- # epsg : int @@ -5882,16 +6511,16 @@ def _set_vertices(self): # addlocalreference : boolean # adds the projection file text associated with epsg to a local # database, epsgref.json, located in the user's data directory. -# +# # References # ---------- # https://www.epsg-registry.org/ -# +# # Returns # ------- # prj : str # text for a projection (*.prj) file. -# +# # """ # warnings.warn("SpatialReference has been deprecated. Use StructuredGrid " # "instead.", category=DeprecationWarning) @@ -5903,33 +6532,33 @@ def _set_vertices(self): # epsgfile.add(epsg, wktstr) # return wktstr -# +# # def get_spatialreference(epsg, text='esriwkt'): # """ # Gets text for given epsg code and text format from spatialreference.org -# +# # Fetches the reference text using the url: # https://spatialreference.org/ref/epsg/// -# +# # See: https://www.epsg-registry.org/ -# +# # Parameters # ---------- # epsg : int # epsg code for coordinate system # text : str # string added to url -# +# # Returns # ------- # url : str -# +# # """ # from flopy.utils.flopy_io import get_url_text -# +# # warnings.warn("SpatialReference has been deprecated. Use StructuredGrid " # "instead.", category=DeprecationWarning) -# +# # epsg_categories = ['epsg', 'esri'] # for cat in epsg_categories: # url = "{}/ref/{}/{}/{}/".format(srefhttp, cat, epsg, text) @@ -5949,30 +6578,31 @@ def _set_vertices(self): # # may still work with pyproj # elif text == 'epsg': # return '+init=epsg:{}'.format(epsg) -# -# +# +# # def getproj4(epsg): # """ # Get projection file (.prj) text for given epsg code from # spatialreference.org. See: https://www.epsg-registry.org/ -# +# # Parameters # ---------- # epsg : int # epsg code for coordinate system -# +# # Returns # ------- # prj : str # text for a projection (*.prj) file. -# +# # """ # warnings.warn("SpatialReference has been deprecated. Use StructuredGrid " # "instead.", category=DeprecationWarning) -# +# # return get_spatialreference(epsg, text='proj4') -def get_maha_obs_summary(sim_en,l1_crit_val=6.34,l2_crit_val=9.2): + +def get_maha_obs_summary(sim_en, l1_crit_val=6.34, l2_crit_val=9.2): """ calculate the 1-D and 2-D mahalanobis distance Args: @@ -5997,38 +6627,37 @@ def get_maha_obs_summary(sim_en,l1_crit_val=6.34,l2_crit_val=9.2): """ - if not isinstance(sim_en,pyemu.ObservationEnsemble): - raise Exception("'sim_en' must be a "+\ - " pyemu.ObservationEnsemble instance") + if not isinstance(sim_en, pyemu.ObservationEnsemble): + raise Exception("'sim_en' must be a " + " pyemu.ObservationEnsemble instance") if sim_en.pst.nnz_obs < 1: raise Exception(" at least one non-zero weighted obs is needed") # process the simulated ensemblet to only have non-zero weighted obs obs = sim_en.pst.observation_data - nz_names =sim_en.pst.nnz_obs_names + nz_names = sim_en.pst.nnz_obs_names # get the full cov matrix nz_cov_df = sim_en.covariance_matrix().to_dataframe() - nnz_en = sim_en.loc[:,nz_names].copy() - nz_cov_df = nz_cov_df.loc[nz_names,nz_names] + nnz_en = sim_en.loc[:, nz_names].copy() + nz_cov_df = nz_cov_df.loc[nz_names, nz_names] # get some noise realizations nnz_en.reseed() obsmean = obs.loc[nnz_en.columns.values, "obsval"] - noise_en = pyemu.ObservationEnsemble.from_gaussian_draw(sim_en.pst,num_reals=sim_en.shape[0]) - noise_en -= obsmean #subtract off the obs val bc we just want the noise + noise_en = pyemu.ObservationEnsemble.from_gaussian_draw( + sim_en.pst, num_reals=sim_en.shape[0] + ) + noise_en -= obsmean # subtract off the obs val bc we just want the noise noise_en.index = nnz_en.index nnz_en += noise_en - - - #obsval_dict = obs.loc[nnz_en.columns.values,"obsval"].to_dict() + # obsval_dict = obs.loc[nnz_en.columns.values,"obsval"].to_dict() # first calculate the 1-D subspace maha distances print("calculating L-1 maha distances") sim_mean = nnz_en.mean() - obs_mean = obs.loc[nnz_en.columns.values,"obsval"] - simvar_inv = 1. / (nnz_en.std()**2) + obs_mean = obs.loc[nnz_en.columns.values, "obsval"] + simvar_inv = 1.0 / (nnz_en.std() ** 2) res_mean = sim_mean - obs_mean - l1_maha_sq_df = res_mean**2 * simvar_inv + l1_maha_sq_df = res_mean ** 2 * simvar_inv l1_maha_sq_df = l1_maha_sq_df.loc[l1_maha_sq_df > l1_crit_val] # now calculate the 2-D subspace maha distances print("preparing L-2 maha distance containers") @@ -6042,16 +6671,21 @@ def get_maha_obs_summary(sim_en,l1_crit_val=6.34,l2_crit_val=9.2): for i1, o1 in enumerate(nz_names): var[o1] = var_arr[i1] - cov_vals = nz_cov_df.loc[o1, :].values[i1+1:] - ostr_vals = ["{0}_{1}".format(o1, o2) for o2 in nz_names[i1+1:]] - cd = {o:c for o,c in zip(ostr_vals,cov_vals)} + cov_vals = nz_cov_df.loc[o1, :].values[i1 + 1 :] + ostr_vals = ["{0}_{1}".format(o1, o2) for o2 in nz_names[i1 + 1 :]] + cd = {o: c for o, c in zip(ostr_vals, cov_vals)} cov.update(cd) print("starting L-2 maha distance parallel calcs") - #pool = mp.Pool(processes=5) + # pool = mp.Pool(processes=5) with mp.get_context("spawn").Pool() as pool: - for i1,o1 in enumerate(nz_names): - o2names = [o2 for o2 in nz_names[i1+1:]] - rresults = [pool.apply_async(_l2_maha_worker,args=(o1,o2names,mean,var,cov,results,l2_crit_val))] + for i1, o1 in enumerate(nz_names): + o2names = [o2 for o2 in nz_names[i1 + 1 :]] + rresults = [ + pool.apply_async( + _l2_maha_worker, + args=(o1, o2names, mean, var, cov, results, l2_crit_val), + ) + ] [r.get() for r in rresults] print("closing pool") @@ -6060,19 +6694,21 @@ def get_maha_obs_summary(sim_en,l1_crit_val=6.34,l2_crit_val=9.2): print("joining pool") pool.join() - #print(results) - #print(len(results),len(ostr_vals)) + # print(results) + # print(len(results),len(ostr_vals)) keys = list(results.keys()) - onames1 = [k.split('|')[0] for k in keys] - onames2 = [k.split('|')[1] for k in keys] + onames1 = [k.split("|")[0] for k in keys] + onames2 = [k.split("|")[1] for k in keys] l2_maha_sq_vals = [results[k] for k in keys] - l2_maha_sq_df = pd.DataFrame({"obsnme_1":onames1,"obsnme_2":onames2,"sq_distance":l2_maha_sq_vals}) + l2_maha_sq_df = pd.DataFrame( + {"obsnme_1": onames1, "obsnme_2": onames2, "sq_distance": l2_maha_sq_vals} + ) - return l1_maha_sq_df,l2_maha_sq_df + return l1_maha_sq_df, l2_maha_sq_df -def _l2_maha_worker(o1,o2names,mean,var,cov,results,l2_crit_val): +def _l2_maha_worker(o1, o2names, mean, var, cov, results, l2_crit_val): rresults = {} v1 = var[o1] @@ -6080,12 +6716,12 @@ def _l2_maha_worker(o1,o2names,mean,var,cov,results,l2_crit_val): c[0, 0] = v1 r1 = mean[o1] for o2 in o2names: - ostr = "{0}_{1}".format(o1,o2) + ostr = "{0}_{1}".format(o1, o2) cv = cov[ostr] v2 = var[o2] - c[1,1] = v2 - c[0,1] = cv - c[1,0] = cv + c[1, 1] = v2 + c[0, 1] = cv + c[1, 0] = cv c_inv = np.linalg.inv(c) r2 = mean[o2] @@ -6094,4 +6730,4 @@ def _l2_maha_worker(o1,o2names,mean,var,cov,results,l2_crit_val): if l2_maha_sq_val > l2_crit_val: rresults[ostr] = l2_maha_sq_val results.update(rresults) - print(o1,"done") \ No newline at end of file + print(o1, "done") diff --git a/pyemu/utils/optimization.py b/pyemu/utils/optimization.py index 05609d734..59b1a0710 100644 --- a/pyemu/utils/optimization.py +++ b/pyemu/utils/optimization.py @@ -2,38 +2,44 @@ import os, sys import numpy as np import pandas as pd -from pyemu import Matrix,Pst,Schur,Cov +from pyemu import Matrix, Pst, Schur, Cov -OPERATOR_WORDS = ["l","g","n","e"] -OPERATOR_SYMBOLS = ["<=",">=","=","="] +OPERATOR_WORDS = ["l", "g", "n", "e"] +OPERATOR_SYMBOLS = ["<=", ">=", "=", "="] # self.prior_information = pd.DataFrame({"pilbl": pilbl, # "equation": equation, # "weight": weight, # "obgnme": obgnme}) -def add_pi_obj_func(pst,obj_func_dict=None,out_pst_name=None): - if not isinstance(pst,Pst): + +def add_pi_obj_func(pst, obj_func_dict=None, out_pst_name=None): + if not isinstance(pst, Pst): pst = Pst(pst) if obj_func_dict is None: - obj_func_dict = {name:1.0 for name in pst.adj_par_names} - pi_equation = '' - for name,coef in obj_func_dict.items(): - assert(name in pst.adj_par_names),"obj func component not in adjustable pars:"+name + obj_func_dict = {name: 1.0 for name in pst.adj_par_names} + pi_equation = "" + for name, coef in obj_func_dict.items(): + assert name in pst.adj_par_names, ( + "obj func component not in adjustable pars:" + name + ) if coef < 0.0: - pi_equation += ' - {0}*{1}'.format(coef,name) + pi_equation += " - {0}*{1}".format(coef, name) else: - pi_equation += ' + {0}*{1}'.format(coef, name) - pi_equation += ' = 0.0' + pi_equation += " + {0}*{1}".format(coef, name) + pi_equation += " = 0.0" pilbl = "pi_obj_func" - pi_df = pd.DataFrame({"pilbl":pilbl,"equation":pi_equation,"weight":0.0,"obgnme":pilbl},index=[pilbl]) + pi_df = pd.DataFrame( + {"pilbl": pilbl, "equation": pi_equation, "weight": 0.0, "obgnme": pilbl}, + index=[pilbl], + ) if pst.prior_information.shape[0] == 0: pst.prior_information = pi_df else: assert pilbl not in pst.prior_information.index # append by enlargement - pst.prior_information.loc[pilbl,:] = pi_df.loc[pilbl,:] + pst.prior_information.loc[pilbl, :] = pi_df.loc[pilbl, :] if out_pst_name is not None: pst.write(out_pst_name) @@ -401,6 +407,3 @@ def add_pi_obj_func(pst,obj_func_dict=None,out_pst_name=None): # f.write(" {0:2} {1:8} {2:8} {3:10G}\n".\ # format("LO","BOUND",name,lw)) # f.write("ENDATA\n") - - - diff --git a/pyemu/utils/os_utils.py b/pyemu/utils/os_utils.py index 97bc555d7..2369ca62a 100644 --- a/pyemu/utils/os_utils.py +++ b/pyemu/utils/os_utils.py @@ -13,15 +13,15 @@ import pandas as pd from ..pyemu_warnings import PyemuWarning -ext = '' -bin_path = os.path.join("..","bin") +ext = "" +bin_path = os.path.join("..", "bin") if "linux" in platform.platform().lower(): - bin_path = os.path.join(bin_path,"linux") + bin_path = os.path.join(bin_path, "linux") elif "darwin" in platform.platform().lower(): - bin_path = os.path.join(bin_path,"mac") + bin_path = os.path.join(bin_path, "mac") else: - bin_path = os.path.join(bin_path,"win") - ext = '.exe' + bin_path = os.path.join(bin_path, "win") + ext = ".exe" bin_path = os.path.abspath(bin_path) os.environ["PATH"] += os.pathsep + bin_path @@ -40,6 +40,7 @@ def _istextfile(filename, blocksize=512): """ import sys + PY3 = sys.version_info[0] == 3 # A function that takes an integer in the 8-bit range and returns @@ -48,11 +49,9 @@ def _istextfile(filename, blocksize=512): # int2byte = (lambda x: bytes((x,))) if PY3 else chr - _text_characters = ( - b''.join(int2byte(i) for i in range(32, 127)) + - b'\n\r\t\f\b') - block = open(filename,'rb').read(blocksize) - if b'\x00' in block: + _text_characters = b"".join(int2byte(i) for i in range(32, 127)) + b"\n\r\t\f\b" + block = open(filename, "rb").read(blocksize) + if b"\x00" in block: # Files with null bytes are binary return False elif not block: @@ -68,10 +67,11 @@ def _istextfile(filename, blocksize=512): def _remove_readonly(func, path, excinfo): """remove readonly dirs, apparently only a windows issue add to all rmtree calls: shutil.rmtree(**,onerror=remove_readonly), wk""" - os.chmod(path, 128) #stat.S_IWRITE==128==normal + os.chmod(path, 128) # stat.S_IWRITE==128==normal func(path) -def run(cmd_str,cwd='.',verbose=False): + +def run(cmd_str, cwd=".", verbose=False): """ an OS agnostic function to execute a command line Args: @@ -99,14 +99,14 @@ def run(cmd_str,cwd='.',verbose=False): if not exe_name.lower().endswith("exe"): raw = cmd_str.split() raw[0] = exe_name + ".exe" - cmd_str = ' '.join(raw) + cmd_str = " ".join(raw) else: - if exe_name.lower().endswith('exe'): + if exe_name.lower().endswith("exe"): raw = cmd_str.split() - exe_name = exe_name.replace('.exe','') + exe_name = exe_name.replace(".exe", "") raw[0] = exe_name - cmd_str = '{0} {1} '.format(*raw) - if os.path.exists(exe_name) and not exe_name.startswith('./'): + cmd_str = "{0} {1} ".format(*raw) + if os.path.exists(exe_name) and not exe_name.startswith("./"): cmd_str = "./" + cmd_str except Exception as e: @@ -131,9 +131,21 @@ def run(cmd_str,cwd='.',verbose=False): raise Exception("run() returned non-zero: {0}".format(estat)) -def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_root="..", - port=4004,rel_path=None,local=True,cleanup=True,master_dir=None, - verbose=False,silent_master=False, reuse_master=False): +def start_workers( + worker_dir, + exe_rel_path, + pst_rel_path, + num_workers=None, + worker_root="..", + port=4004, + rel_path=None, + local=True, + cleanup=True, + master_dir=None, + verbose=False, + silent_master=False, + reuse_master=False, +): """ start a group of pest(++) workers on the local machine Args: @@ -189,22 +201,22 @@ def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_r num_workers = mp.cpu_count() else: num_workers = int(num_workers) - #assert os.path.exists(os.path.join(worker_dir,rel_path,exe_rel_path)) + # assert os.path.exists(os.path.join(worker_dir,rel_path,exe_rel_path)) exe_verf = True if rel_path: - if not os.path.exists(os.path.join(worker_dir,rel_path,exe_rel_path)): - #print("warning: exe_rel_path not verified...hopefully exe is in the PATH var") + if not os.path.exists(os.path.join(worker_dir, rel_path, exe_rel_path)): + # print("warning: exe_rel_path not verified...hopefully exe is in the PATH var") exe_verf = False else: - if not os.path.exists(os.path.join(worker_dir,exe_rel_path)): - #print("warning: exe_rel_path not verified...hopefully exe is in the PATH var") + if not os.path.exists(os.path.join(worker_dir, exe_rel_path)): + # print("warning: exe_rel_path not verified...hopefully exe is in the PATH var") exe_verf = False if rel_path is not None: - if not os.path.exists(os.path.join(worker_dir,rel_path,pst_rel_path)): + if not os.path.exists(os.path.join(worker_dir, rel_path, pst_rel_path)): raise Exception("pst_rel_path not found from worker_dir using rel_path") else: - if not os.path.exists(os.path.join(worker_dir,pst_rel_path)): + if not os.path.exists(os.path.join(worker_dir, pst_rel_path)): raise Exception("pst_rel_path not found from worker_dir") if local: hostname = "localhost" @@ -214,65 +226,76 @@ def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_r base_dir = os.getcwd() port = int(port) - if os.path.exists(os.path.join(worker_dir,exe_rel_path)): + if os.path.exists(os.path.join(worker_dir, exe_rel_path)): if "window" in platform.platform().lower(): if not exe_rel_path.lower().endswith("exe"): exe_rel_path = exe_rel_path + ".exe" else: - if not exe_rel_path.startswith('./'): + if not exe_rel_path.startswith("./"): exe_rel_path = "./" + exe_rel_path if master_dir is not None: - if master_dir != '.' and os.path.exists(master_dir) and not reuse_master: + if master_dir != "." and os.path.exists(master_dir) and not reuse_master: try: - shutil.rmtree(master_dir, onerror=_remove_readonly)#, onerror=del_rw) + shutil.rmtree(master_dir, onerror=_remove_readonly) # , onerror=del_rw) except Exception as e: - raise Exception("unable to remove existing master dir:" + \ - "{0}\n{1}".format(master_dir,str(e))) - if master_dir != '.' and not reuse_master: + raise Exception( + "unable to remove existing master dir:" + + "{0}\n{1}".format(master_dir, str(e)) + ) + if master_dir != "." and not reuse_master: try: - shutil.copytree(worker_dir,master_dir) + shutil.copytree(worker_dir, master_dir) except Exception as e: - raise Exception("unable to copy files from base worker dir: " + \ - "{0} to master dir: {1}\n{2}".\ - format(worker_dir,master_dir,str(e))) + raise Exception( + "unable to copy files from base worker dir: " + + "{0} to master dir: {1}\n{2}".format( + worker_dir, master_dir, str(e) + ) + ) args = [exe_rel_path, pst_rel_path, "/h", ":{0}".format(port)] if rel_path is not None: - cwd = os.path.join(master_dir,rel_path) + cwd = os.path.join(master_dir, rel_path) else: cwd = master_dir if verbose: - print("master:{0} in {1}".format(' '.join(args),cwd)) - stdout=None + print("master:{0} in {1}".format(" ".join(args), cwd)) + stdout = None if silent_master: - stdout = open(os.devnull,'w') + stdout = open(os.devnull, "w") try: os.chdir(cwd) - master_p = sp.Popen(args,stdout=stdout)#,stdout=sp.PIPE,stderr=sp.PIPE) + master_p = sp.Popen(args, stdout=stdout) # ,stdout=sp.PIPE,stderr=sp.PIPE) os.chdir(base_dir) except Exception as e: - raise Exception("error starting master instance: {0}".\ - format(str(e))) - time.sleep(1.5) # a few cycles to let the master get ready + raise Exception("error starting master instance: {0}".format(str(e))) + time.sleep(1.5) # a few cycles to let the master get ready - - tcp_arg = "{0}:{1}".format(hostname,port) + tcp_arg = "{0}:{1}".format(hostname, port) procs = [] worker_dirs = [] for i in range(num_workers): - new_worker_dir = os.path.join(worker_root,"worker_{0}".format(i)) + new_worker_dir = os.path.join(worker_root, "worker_{0}".format(i)) if os.path.exists(new_worker_dir): try: - shutil.rmtree(new_worker_dir, onerror=_remove_readonly)#, onerror=del_rw) + shutil.rmtree( + new_worker_dir, onerror=_remove_readonly + ) # , onerror=del_rw) except Exception as e: - raise Exception("unable to remove existing worker dir:" + \ - "{0}\n{1}".format(new_worker_dir,str(e))) + raise Exception( + "unable to remove existing worker dir:" + + "{0}\n{1}".format(new_worker_dir, str(e)) + ) try: - shutil.copytree(worker_dir,new_worker_dir) + shutil.copytree(worker_dir, new_worker_dir) except Exception as e: - raise Exception("unable to copy files from worker dir: " + \ - "{0} to new worker dir: {1}\n{2}".format(worker_dir,new_worker_dir,str(e))) + raise Exception( + "unable to copy files from worker dir: " + + "{0} to new worker dir: {1}\n{2}".format( + worker_dir, new_worker_dir, str(e) + ) + ) try: if exe_verf: # if rel_path is not None: @@ -282,17 +305,17 @@ def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_r else: exe_path = exe_rel_path args = [exe_path, pst_rel_path, "/h", tcp_arg] - #print("starting worker in {0} with args: {1}".format(new_worker_dir,args)) + # print("starting worker in {0} with args: {1}".format(new_worker_dir,args)) if rel_path is not None: - cwd = os.path.join(new_worker_dir,rel_path) + cwd = os.path.join(new_worker_dir, rel_path) else: cwd = new_worker_dir os.chdir(cwd) if verbose: - print("worker:{0} in {1}".format(' '.join(args),cwd)) - with open(os.devnull,'w') as f: - p = sp.Popen(args,stdout=f,stderr=f) + print("worker:{0} in {1}".format(" ".join(args), cwd)) + with open(os.devnull, "w") as f: + p = sp.Popen(args, stdout=f, stderr=f) procs.append(p) os.chdir(base_dir) except Exception as e: @@ -317,7 +340,7 @@ def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_r time.sleep(5) else: master_p.wait() - time.sleep(1.5) # a few cycles to let the workers end gracefully + time.sleep(1.5) # a few cycles to let the workers end gracefully # kill any remaining workers for p in procs: p.kill() @@ -325,13 +348,15 @@ def start_workers(worker_dir,exe_rel_path,pst_rel_path,num_workers=None,worker_r for p in procs: p.wait() if cleanup: - cleanit=0 - while len(worker_dirs)>0 and cleanit<100000: # arbitrary 100000 limit - cleanit=cleanit+1 + cleanit = 0 + while len(worker_dirs) > 0 and cleanit < 100000: # arbitrary 100000 limit + cleanit = cleanit + 1 for d in worker_dirs: try: shutil.rmtree(d, onerror=_remove_readonly) - worker_dirs.pop(worker_dirs.index(d)) #if successfully removed + worker_dirs.pop(worker_dirs.index(d)) # if successfully removed except Exception as e: - warnings.warn("unable to remove slavr dir{0}:{1}".format(d,str(e)),PyemuWarning) - + warnings.warn( + "unable to remove slavr dir{0}:{1}".format(d, str(e)), + PyemuWarning, + ) diff --git a/pyemu/utils/pp_utils.py b/pyemu/utils/pp_utils.py index 5877e3213..fa1252dcd 100644 --- a/pyemu/utils/pp_utils.py +++ b/pyemu/utils/pp_utils.py @@ -5,20 +5,36 @@ import numpy as np import pandas as pd import warnings + pd.options.display.max_colwidth = 100 -from pyemu.pst.pst_utils import SFMT,IFMT,FFMT,pst_config +from pyemu.pst.pst_utils import SFMT, IFMT, FFMT, pst_config from pyemu.utils.helpers import run, _write_df_tpl from ..pyemu_warnings import PyemuWarning -PP_FMT = {"name": SFMT, "x": FFMT, "y": FFMT, "zone": IFMT, "tpl": SFMT, - "parval1": FFMT} -PP_NAMES = ["name","x","y","zone","parval1"] - -def setup_pilotpoints_grid(ml=None, sr=None, ibound=None, prefix_dict=None, - every_n_cell=4, ninst=1, - use_ibound_zones=False, - pp_dir='.', tpl_dir='.', - shapename="pp.shp", longnames=False): +PP_FMT = { + "name": SFMT, + "x": FFMT, + "y": FFMT, + "zone": IFMT, + "tpl": SFMT, + "parval1": FFMT, +} +PP_NAMES = ["name", "x", "y", "zone", "parval1"] + + +def setup_pilotpoints_grid( + ml=None, + sr=None, + ibound=None, + prefix_dict=None, + every_n_cell=4, + ninst=1, + use_ibound_zones=False, + pp_dir=".", + tpl_dir=".", + shapename="pp.shp", + longnames=False, +): """ setup a regularly-spaced (gridded) pilot point parameterization Args: @@ -58,79 +74,77 @@ def setup_pilotpoints_grid(ml=None, sr=None, ibound=None, prefix_dict=None, try: import flopy except Exception as e: - raise ImportError( - "error importing flopy: {0}".format( - str(e))) - assert isinstance(ml,flopy.modflow.Modflow) + raise ImportError("error importing flopy: {0}".format(str(e))) + assert isinstance(ml, flopy.modflow.Modflow) sr = ml.sr if ibound is None: ibound = ml.bas6.ibound.array # build a generic prefix_dict if prefix_dict is None: - prefix_dict = {k: ["pp_{0:02d}_".format(k)] for k in - range(ml.nlay)} + prefix_dict = {k: ["pp_{0:02d}_".format(k)] for k in range(ml.nlay)} else: assert sr is not None, "if 'ml' is not passed, 'sr' must be passed" if prefix_dict is None: - prefix_dict = {k: ["pp_{0:02d}_".format(k)] for k in - range(ninst)} + prefix_dict = {k: ["pp_{0:02d}_".format(k)] for k in range(ninst)} if ibound is None: print("ibound not passed, using array of ones") - ibound = {k: np.ones((sr.nrow, sr.ncol)) - for k in prefix_dict.keys()} - #assert ibound is not None,"if 'ml' is not pass, 'ibound' must be passed" + ibound = {k: np.ones((sr.nrow, sr.ncol)) for k in prefix_dict.keys()} + # assert ibound is not None,"if 'ml' is not pass, 'ibound' must be passed" if isinstance(ibound, np.ndarray): - assert np.ndim(ibound) == 2 or np.ndim(ibound) == 3, \ - "ibound needs to be either 3d np.ndarray or k_dict of 2d arrays. " \ + assert np.ndim(ibound) == 2 or np.ndim(ibound) == 3, ( + "ibound needs to be either 3d np.ndarray or k_dict of 2d arrays. " "Array of {0} dimensions passed".format(np.ndim(ibound)) + ) if np.ndim(ibound) == 2: ibound = {0: ibound} else: ibound = {k: arr for k, arr in enumerate(ibound)} - try: + try: xcentergrid = sr.xcentergrid ycentergrid = sr.ycentergrid except AttributeError as e0: - warnings.warn("xcentergrid and/or ycentergrid not in 'sr':{0}", - PyemuWarning) + warnings.warn("xcentergrid and/or ycentergrid not in 'sr':{0}", PyemuWarning) try: xcentergrid = sr.xcellcenters ycentergrid = sr.ycellcenters except AttributeError as e1: - raise Exception("error getting xcentergrid and/or ycentergrid " - "from 'sr':{0}:{1}".format(str(e0), str(e1))) + raise Exception( + "error getting xcentergrid and/or ycentergrid " + "from 'sr':{0}:{1}".format(str(e0), str(e1)) + ) start = int(float(every_n_cell) / 2.0) - + # check prefix_dict keys = list(prefix_dict.keys()) keys.sort() - - #for k, prefix in prefix_dict.items(): + + # for k, prefix in prefix_dict.items(): for k in keys: prefix = prefix_dict[k] - if not isinstance(prefix,list): + if not isinstance(prefix, list): prefix_dict[k] = [prefix] if np.all([isinstance(v, dict) for v in ibound.values()]): for p in prefix_dict[k]: if np.any([p.startswith(key) for key in ibound.keys()]): ib_sel = next(key for key in ibound.keys() if p.startswith(key)) else: - ib_sel = 'general_zn' - assert k < len(ibound[ib_sel]), "layer index {0} > nlay {1}".format(k, len(ibound[ib_sel])) + ib_sel = "general_zn" + assert k < len(ibound[ib_sel]), "layer index {0} > nlay {1}".format( + k, len(ibound[ib_sel]) + ) else: - assert k < len(ibound),"layer index {0} > nlay {1}".format(k,len(ibound)) - + assert k < len(ibound), "layer index {0} > nlay {1}".format(k, len(ibound)) - #try: - #ibound = ml.bas6.ibound.array - #except Exception as e: + # try: + # ibound = ml.bas6.ibound.array + # except Exception as e: # raise Exception("error getting model.bas6.ibound:{0}".format(str(e))) par_info = [] - pp_files,tpl_files = [],[] + pp_files, tpl_files = [], [] pp_names = copy.copy(PP_NAMES) - pp_names.extend(["k","i","j"]) + pp_names.extend(["k", "i", "j"]) if not np.all([isinstance(v, dict) for v in ibound.values()]): ibound = {"general_zn": ibound} @@ -140,98 +154,110 @@ def setup_pilotpoints_grid(ml=None, sr=None, ibound=None, prefix_dict=None, for k in range(len(ibound[par])): pp_df = None ib = ibound[par][k] - assert ib.shape == xcentergrid.shape,"ib.shape != xcentergrid.shape for k {0}".\ - format(k) + assert ( + ib.shape == xcentergrid.shape + ), "ib.shape != xcentergrid.shape for k {0}".format(k) pp_count = 0 - #skip this layer if not in prefix_dict + # skip this layer if not in prefix_dict if k not in prefix_dict.keys(): continue - #cycle through rows and cols - for i in range(start,ib.shape[0]-start,every_n_cell): - for j in range(start,ib.shape[1]-start,every_n_cell): + # cycle through rows and cols + for i in range(start, ib.shape[0] - start, every_n_cell): + for j in range(start, ib.shape[1] - start, every_n_cell): # skip if this is an inactive cell - if ib[i,j] == 0: + if ib[i, j] == 0: continue # get the attributes we need - x = xcentergrid[i,j] - y = ycentergrid[i,j] + x = xcentergrid[i, j] + y = ycentergrid[i, j] name = "pp_{0:04d}".format(pp_count) parval1 = 1.0 - #decide what to use as the zone + # decide what to use as the zone zone = 1 if use_ibound_zones: - zone = ib[i,j] - #stick this pilot point into a dataframe container + zone = ib[i, j] + # stick this pilot point into a dataframe container if pp_df is None: - data = {"name": name, "x": x, "y": y, "zone": zone, - "parval1": parval1, "k":k, "i":i, "j":j} - pp_df = pd.DataFrame(data=data,index=[0],columns=pp_names) + data = { + "name": name, + "x": x, + "y": y, + "zone": zone, + "parval1": parval1, + "k": k, + "i": i, + "j": j, + } + pp_df = pd.DataFrame(data=data, index=[0], columns=pp_names) else: data = [name, x, y, zone, parval1, k, i, j] - pp_df.loc[pp_count,:] = data + pp_df.loc[pp_count, :] = data pp_count += 1 - #if we found some acceptable locs... + # if we found some acceptable locs... if pp_df is not None: for prefix in prefix_dict[k]: # if parameter prefix relates to current zone definition if prefix.startswith(par) or ( - ~np.any([prefix.startswith(p) for p in ibound.keys()]) and par == "general_zn"): - base_filename = "{0}pp.dat".format( - prefix.replace(':', '')) + ~np.any([prefix.startswith(p) for p in ibound.keys()]) + and par == "general_zn" + ): + base_filename = "{0}pp.dat".format(prefix.replace(":", "")) pp_filename = os.path.join(pp_dir, base_filename) # write the base pilot point file write_pp_file(pp_filename, pp_df) tpl_filename = os.path.join(tpl_dir, base_filename + ".tpl") - #write the tpl file - pilot_points_to_tpl(pp_df, tpl_filename, - name_prefix=prefix, - longnames=longnames) - pp_df.loc[:,"tpl_filename"] = tpl_filename - pp_df.loc[:,"pp_filename"] = pp_filename - pp_df.loc[:,"pargp"] = prefix - #save the parameter names and parval1s for later + # write the tpl file + pilot_points_to_tpl( + pp_df, tpl_filename, name_prefix=prefix, longnames=longnames + ) + pp_df.loc[:, "tpl_filename"] = tpl_filename + pp_df.loc[:, "pp_filename"] = pp_filename + pp_df.loc[:, "pargp"] = prefix + # save the parameter names and parval1s for later par_info.append(pp_df.copy()) - #save the pp_filename and tpl_filename for later + # save the pp_filename and tpl_filename for later pp_files.append(pp_filename) tpl_files.append(tpl_filename) - par_info = pd.concat(par_info) - for field in ["k","i","j"]: - par_info.loc[:,field] = par_info.loc[:,field].apply(np.int) - for key,default in pst_config["par_defaults"].items(): + for field in ["k", "i", "j"]: + par_info.loc[:, field] = par_info.loc[:, field].apply(np.int) + for key, default in pst_config["par_defaults"].items(): if key in par_info.columns: continue - par_info.loc[:,key] = default + par_info.loc[:, key] = default if shapename is not None: try: import shapefile except Exception as e: - print("error importing shapefile, try pip install pyshp...{0}"\ - .format(str(e))) + print( + "error importing shapefile, try pip install pyshp...{0}".format(str(e)) + ) return par_info try: - shp = shapefile.Writer(target=shapename,shapeType=shapefile.POINT) + shp = shapefile.Writer(target=shapename, shapeType=shapefile.POINT) except: shp = shapefile.Writer(shapeType=shapefile.POINT) - for name,dtype in par_info.dtypes.iteritems(): + for name, dtype in par_info.dtypes.iteritems(): if dtype == object: - shp.field(name=name,fieldType='C',size=50) - elif dtype in [int,np.int,np.int64,np.int32]: - shp.field(name=name, fieldType='N', size=50, decimal=0) - elif dtype in [float,np.float,np.float32,np.float64]: - shp.field(name=name, fieldType='N', size=50, decimal=10) + shp.field(name=name, fieldType="C", size=50) + elif dtype in [int, np.int, np.int64, np.int32]: + shp.field(name=name, fieldType="N", size=50, decimal=0) + elif dtype in [float, np.float, np.float32, np.float64]: + shp.field(name=name, fieldType="N", size=50, decimal=10) else: - raise Exception("unrecognized field type in par_info:{0}:{1}".format(name,dtype)) + raise Exception( + "unrecognized field type in par_info:{0}:{1}".format(name, dtype) + ) - #some pandas awesomeness.. - par_info.apply(lambda x:shp.point(x.x,x.y), axis=1) - par_info.apply(lambda x:shp.record(*x),axis=1) + # some pandas awesomeness.. + par_info.apply(lambda x: shp.point(x.x, x.y), axis=1) + par_info.apply(lambda x: shp.record(*x), axis=1) try: shp.save(shapename) except: @@ -257,11 +283,17 @@ def pp_file_to_dataframe(pp_filename): """ - df = pd.read_csv(pp_filename, delim_whitespace=True, - header=None, names=PP_NAMES,usecols=[0,1,2,3,4]) - df.loc[:,"name"] = df.name.apply(str).apply(str.lower) + df = pd.read_csv( + pp_filename, + delim_whitespace=True, + header=None, + names=PP_NAMES, + usecols=[0, 1, 2, 3, 4], + ) + df.loc[:, "name"] = df.name.apply(str).apply(str.lower) return df + def pp_tpl_to_dataframe(tpl_filename): """ read a pilot points template file to a pandas dataframe @@ -281,20 +313,26 @@ def pp_tpl_to_dataframe(tpl_filename): df = pyemu.pp_utils.pp_tpl_file_to_dataframe("my_pp.dat.tpl") """ - inlines = open(tpl_filename, 'r').readlines() + inlines = open(tpl_filename, "r").readlines() header = inlines.pop(0) marker = header.strip().split()[1] assert len(marker) == 1 - usecols = [0,1,2,3] - df = pd.read_csv(tpl_filename, delim_whitespace=True,skiprows=1, - header=None, names=PP_NAMES[:-1],usecols=usecols) - df.loc[:,"name"] = df.name.apply(str).apply(str.lower) + usecols = [0, 1, 2, 3] + df = pd.read_csv( + tpl_filename, + delim_whitespace=True, + skiprows=1, + header=None, + names=PP_NAMES[:-1], + usecols=usecols, + ) + df.loc[:, "name"] = df.name.apply(str).apply(str.lower) df["parnme"] = [i.split(marker)[1].strip() for i in inlines] - return df -def write_pp_shapfile(pp_df,shapename=None): + +def write_pp_shapfile(pp_df, shapename=None): """write pilot points dataframe to a shapefile Args: @@ -312,15 +350,17 @@ def write_pp_shapfile(pp_df,shapename=None): try: import shapefile except Exception as e: - raise Exception("error importing shapefile: {0}, \ntry pip install pyshp...".format(str(e))) + raise Exception( + "error importing shapefile: {0}, \ntry pip install pyshp...".format(str(e)) + ) - if not isinstance(pp_df,list): + if not isinstance(pp_df, list): pp_df = [pp_df] dfs = [] for pp in pp_df: - if isinstance(pp,pd.DataFrame): + if isinstance(pp, pd.DataFrame): dfs.append(pp) - elif isinstance(pp,str): + elif isinstance(pp, str): dfs.append(pp_file_to_dataframe(pp)) else: raise Exception("unsupported arg type:{0}".format(type(pp))) @@ -333,18 +373,19 @@ def write_pp_shapfile(pp_df,shapename=None): shp = shapefile.Writer(target=shapename, shapeType=shapefile.POINT) for name, dtype in dfs[0].dtypes.iteritems(): if dtype == object: - shp.field(name=name, fieldType='C', size=50) + shp.field(name=name, fieldType="C", size=50) elif dtype in [int, np.int, np.int64, np.int32]: - shp.field(name=name, fieldType='N', size=50, decimal=0) + shp.field(name=name, fieldType="N", size=50, decimal=0) elif dtype in [float, np.float, np.float32, np.float32]: - shp.field(name=name, fieldType='N', size=50, decimal=8) + shp.field(name=name, fieldType="N", size=50, decimal=8) else: - raise Exception("unrecognized field type in pp_df:{0}:{1}".format(name, dtype)) - + raise Exception( + "unrecognized field type in pp_df:{0}:{1}".format(name, dtype) + ) # some pandas awesomeness.. for df in dfs: - #df.apply(lambda x: shp.poly([[[x.x, x.y]]]), axis=1) + # df.apply(lambda x: shp.poly([[[x.x, x.y]]]), axis=1) df.apply(lambda x: shp.point(x.x, x.y), axis=1) df.apply(lambda x: shp.record(*x), axis=1) @@ -354,9 +395,7 @@ def write_pp_shapfile(pp_df,shapename=None): shp.close() - - -def write_pp_file(filename,pp_df): +def write_pp_file(filename, pp_df): """write a pilot points dataframe to a pilot points file Args: @@ -365,17 +404,21 @@ def write_pp_file(filename,pp_df): at least columns "x","y","zone", and "value" """ - with open(filename,'w') as f: - f.write(pp_df.to_string(col_space=0, - columns=PP_NAMES, - formatters=PP_FMT, - justify="right", - header=False, - index=False) + '\n') - - -def pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None, - longnames=False): + with open(filename, "w") as f: + f.write( + pp_df.to_string( + col_space=0, + columns=PP_NAMES, + formatters=PP_FMT, + justify="right", + header=False, + index=False, + ) + + "\n" + ) + + +def pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None, longnames=False): """write a template file for a pilot points file Args: @@ -394,13 +437,12 @@ def pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None, (parnme,tpl_str) """ - if isinstance(pp_file,pd.DataFrame): + if isinstance(pp_file, pd.DataFrame): pp_df = pp_file assert tpl_file is not None else: assert os.path.exists(pp_file) - pp_df = pd.read_csv(pp_file, delim_whitespace=True, - header=None, names=PP_NAMES) + pp_df = pd.read_csv(pp_file, delim_whitespace=True, header=None, names=PP_NAMES) if tpl_file is None: tpl_file = pp_file + ".tpl" @@ -408,28 +450,38 @@ def pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None, if longnames: if name_prefix is not None: pp_df.loc[:, "parnme"] = pp_df.apply( - lambda x: "{0}_i:{1}_j:{2}".format( - name_prefix, int(x.i), int(x.j)), axis=1) + lambda x: "{0}_i:{1}_j:{2}".format(name_prefix, int(x.i), int(x.j)), + axis=1, + ) pp_df.loc[:, "tpl"] = pp_df.parnme.apply( - lambda x: "~ {0} ~".format(x)) + lambda x: "~ {0} ~".format(x) + ) else: names = pp_df.name.copy() pp_df.loc[:, "parnme"] = pp_df.name pp_df.loc[:, "tpl"] = pp_df.parnme.apply( - lambda x: "~ {0} ~".format(x)) - _write_df_tpl(tpl_file, - pp_df.loc[:, ["name", "x", "y", "zone", "tpl"]], - sep=' ', index_label="index", header=False, index=False, - quotechar=" ",quoting=2) + lambda x: "~ {0} ~".format(x) + ) + _write_df_tpl( + tpl_file, + pp_df.loc[:, ["name", "x", "y", "zone", "tpl"]], + sep=" ", + index_label="index", + header=False, + index=False, + quotechar=" ", + quoting=2, + ) else: if name_prefix is not None: digits = str(len(str(pp_df.shape[0]))) - fmt = "{0:0"+digits+"d}" + fmt = "{0:0" + digits + "d}" if len(name_prefix) + 1 + int(digits) > 12: - warnings.warn("name_prefix too long for parameter names", - PyemuWarning) - names = ["{0}_{1}".format(name_prefix, fmt.format(i)) - for i in range(pp_df.shape[0])] + warnings.warn("name_prefix too long for parameter names", PyemuWarning) + names = [ + "{0}_{1}".format(name_prefix, fmt.format(i)) + for i in range(pp_df.shape[0]) + ] else: names = pp_df.name.copy() too_long = [] @@ -437,18 +489,24 @@ def pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None, if len(name) > 12: too_long.append(name) if len(too_long) > 0: - raise Exception("the following parameter names are too long:" - ",".join(too_long)) + raise Exception( + "the following parameter names are too long:" ",".join(too_long) + ) tpl_entries = ["~ {0} ~".format(name) for name in names] pp_df.loc[:, "tpl"] = tpl_entries pp_df.loc[:, "parnme"] = names - f_tpl = open(tpl_file,'w') + f_tpl = open(tpl_file, "w") f_tpl.write("ptf ~\n") - f_tpl.write(pp_df.to_string(col_space=0, - columns=["name","x","y","zone","tpl"], - formatters=PP_FMT, - justify="left", - header=False, - index=False) + '\n') + f_tpl.write( + pp_df.to_string( + col_space=0, + columns=["name", "x", "y", "zone", "tpl"], + formatters=PP_FMT, + justify="left", + header=False, + index=False, + ) + + "\n" + ) return pp_df diff --git a/pyemu/utils/pst_from.py b/pyemu/utils/pst_from.py index 33f58ada7..70381b244 100644 --- a/pyemu/utils/pst_from.py +++ b/pyemu/utils/pst_from.py @@ -14,19 +14,21 @@ import string -#the tolerable percent difference (100 * (max - min)/mean) -#used when checking that constant and zone type parameters are in fact constant (within -#a given zone) -#DIRECT_PAR_PERCENT_DIFF_TOL = 1.0 +# the tolerable percent difference (100 * (max - min)/mean) +# used when checking that constant and zone type parameters are in fact constant (within +# a given zone) +# DIRECT_PAR_PERCENT_DIFF_TOL = 1.0 def _get_datetime_from_str(sdt): # could be expanded if someone is feeling clever. if isinstance(sdt, str): - PyemuWarning("Assuming passed reference start date time is " - "year first str {0}".format(sdt)) + PyemuWarning( + "Assuming passed reference start date time is " + "year first str {0}".format(sdt) + ) sdt = pd.to_datetime(sdt, yearfirst=True) - assert isinstance(sdt, pd.Timestamp), ("Error interpreting start_datetime") + assert isinstance(sdt, pd.Timestamp), "Error interpreting start_datetime" return sdt @@ -34,9 +36,9 @@ def _check_var_len(var, n, fill=None): if not isinstance(var, list): var = [var] if fill is not None: - if fill == 'first': + if fill == "first": fill = var[0] - elif fill == 'last': + elif fill == "last": fill = var[-1] nv = len(var) if nv < n: @@ -60,10 +62,17 @@ class PstFrom(object): """ # TODO: doc strings!!! - def __init__(self, original_d, new_d, longnames=True, - remove_existing=False, spatial_reference=None, - zero_based=True, start_datetime=None): - # TODO geostruct? + def __init__( + self, + original_d, + new_d, + longnames=True, + remove_existing=False, + spatial_reference=None, + zero_based=True, + start_datetime=None, + ): + # TODO geostruct? self.original_d = original_d self.new_d = new_d @@ -90,14 +99,14 @@ def __init__(self, original_d, new_d, longnames=True, self.py_run_file = "forward_run.py" self.mod_command = "python {0}".format(self.py_run_file) self.pre_py_cmds = [] - self.pre_sys_cmds = [] # a list of preprocessing commands to add to - # the forward_run.py script commands are executed with os.system() + self.pre_sys_cmds = [] # a list of preprocessing commands to add to + # the forward_run.py script commands are executed with os.system() # within forward_run.py. self.mod_py_cmds = [] self.mod_sys_cmds = [] self.post_py_cmds = [] - self.post_sys_cmds = [] # a list of post-processing commands to add to - # the forward_run.py script. Commands are executed with os.system() + self.post_sys_cmds = [] # a list of post-processing commands to add to + # the forward_run.py script. Commands are executed with os.system() # within forward_run.py. self.extra_py_imports = [] self.tmp_files = [] @@ -120,56 +129,71 @@ def __init__(self, original_d, new_d, longnames=True, self._parfile_relations = [] self._pp_facs = {} self.pst = None - self._function_lines_list = [] #each function is itself a list of lines + self._function_lines_list = [] # each function is itself a list of lines self.direct_org_files = [] - self.ult_ubound_fill = 1.0e+30 - self.ult_lbound_fill = -1.0e+30 + self.ult_ubound_fill = 1.0e30 + self.ult_lbound_fill = -1.0e30 @property def parfile_relations(self): if isinstance(self._parfile_relations, list): - pr = pd.concat(self._parfile_relations, - ignore_index=True) + pr = pd.concat(self._parfile_relations, ignore_index=True) else: pr = self._parfile_relations # quick checker - for name, g in pr.groupby('model_file'): + for name, g in pr.groupby("model_file"): if g.sep.nunique() > 1: self.logger.warn( "separator mismatch for {0}, seps passed {1}" - "".format(name, [s for s in g.sep.unique()])) + "".format(name, [s for s in g.sep.unique()]) + ) if g.fmt.nunique() > 1: self.logger.warn( "format mismatch for {0}, fmt passed {1}" - "".format(name, [f for f in g.fmt.unique()])) + "".format(name, [f for f in g.fmt.unique()]) + ) # if ultimate parameter bounds have been set for only one instance # of the model file we need to pass this through to all ubound = g.apply( lambda x: pd.Series( - {k: v for n, c in enumerate(x.use_cols) - for k, v in [['ubound{0}'.format(c), x.upper_bound[n]]]}) + { + k: v + for n, c in enumerate(x.use_cols) + for k, v in [["ubound{0}".format(c), x.upper_bound[n]]] + } + ) if x.use_cols is not None - else pd.Series( - {k: v for k, v in [['ubound', x.upper_bound]]}), axis=1) + else pd.Series({k: v for k, v in [["ubound", x.upper_bound]]}), + axis=1, + ) if ubound.nunique(0, False).gt(1).any(): ub_min = ubound.min().fillna(self.ult_ubound_fill).to_dict() - pr.loc[g.index, 'upper_bound'] = g.use_cols.apply( - lambda x: [ub_min['ubound{0}'.format(c)] for c in x] - if x is not None else ub_min["ubound"]) + pr.loc[g.index, "upper_bound"] = g.use_cols.apply( + lambda x: [ub_min["ubound{0}".format(c)] for c in x] + if x is not None + else ub_min["ubound"] + ) # repeat for lower bounds lbound = g.apply( lambda x: pd.Series( - {k: v for n, c in enumerate(x.use_cols) - for k, v in [['lbound{0}'.format(c), x.lower_bound[n]]]}) + { + k: v + for n, c in enumerate(x.use_cols) + for k, v in [["lbound{0}".format(c), x.lower_bound[n]]] + } + ) if x.use_cols is not None - else pd.Series( - {k: v for k, v in [['lbound', x.lower_bound]]}), axis=1) + else pd.Series({k: v for k, v in [["lbound", x.lower_bound]]}), + axis=1, + ) if lbound.nunique(0, False).gt(1).any(): lb_max = lbound.max().fillna(self.ult_lbound_fill).to_dict() - pr.loc[g.index, 'lower_bound'] = g.use_cols.apply( - lambda x: [lb_max['lbound{0}'.format(c)] for c in x] - if x is not None else lb_max['lbound']) - pr['zero_based'] = self.zero_based + pr.loc[g.index, "lower_bound"] = g.use_cols.apply( + lambda x: [lb_max["lbound{0}".format(c)] for c in x] + if x is not None + else lb_max["lbound"] + ) + pr["zero_based"] = self.zero_based return pr def _generic_get_xy(self, args, **kwargs): @@ -181,8 +205,10 @@ def _flopy_sr_get_xy(self, args, **kwargs): if all([ij is None for ij in [i, j]]): return i, j else: - return (self._spatial_reference.xcentergrid[i, j], - self._spatial_reference.ycentergrid[i, j]) + return ( + self._spatial_reference.xcentergrid[i, j], + self._spatial_reference.ycentergrid[i, j], + ) def _flopy_mg_get_xy(self, args, **kwargs): i, j = self.parse_kij_args(args, kwargs) @@ -193,49 +219,56 @@ def _flopy_mg_get_xy(self, args, **kwargs): self._spatial_ref_xarray = self._spatial_reference.xcellcenters self._spatial_ref_yarray = self._spatial_reference.ycellcenters - return (self._spatial_ref_xarray[i, j], - self._spatial_ref_yarray[i, j]) + return (self._spatial_ref_xarray[i, j], self._spatial_ref_yarray[i, j]) def parse_kij_args(self, args, kwargs): if len(args) >= 2: ij_id = None - if 'ij_id' in kwargs: - ij_id = kwargs['ij_id'] + if "ij_id" in kwargs: + ij_id = kwargs["ij_id"] if ij_id is not None: i, j = [args[ij] for ij in ij_id] else: # assume i and j are the final two entries in index_cols i, j = args[-2], args[-1] else: - self.logger.warn(("get_xy() warning: need locational information " - "(e.g. i,j) to generate xy, " - "insufficient index cols passed to interpret: {}" - "").format(str(args))) + self.logger.warn( + ( + "get_xy() warning: need locational information " + "(e.g. i,j) to generate xy, " + "insufficient index cols passed to interpret: {}" + "" + ).format(str(args)) + ) i, j = None, None return i, j def initialize_spatial_reference(self): if self._spatial_reference is None: self.get_xy = self._generic_get_xy - elif (hasattr(self._spatial_reference, "xcentergrid") and - hasattr(self._spatial_reference, "ycentergrid")): + elif hasattr(self._spatial_reference, "xcentergrid") and hasattr( + self._spatial_reference, "ycentergrid" + ): self.get_xy = self._flopy_sr_get_xy - elif (hasattr(self._spatial_reference, "xcellcenters") and - hasattr(self._spatial_reference, "ycellcenters")): + elif hasattr(self._spatial_reference, "xcellcenters") and hasattr( + self._spatial_reference, "ycellcenters" + ): # support modelgrid style cell locs self._spatial_reference.xcentergrid = self._spatial_reference.xcellcenters self._spatial_reference.ycentergrid = self._spatial_reference.ycellcenters self.get_xy = self._flopy_mg_get_xy else: - self.logger.lraise("initialize_spatial_reference() error: " - "unsupported spatial_reference") + self.logger.lraise( + "initialize_spatial_reference() error: " "unsupported spatial_reference" + ) self.spatial_reference = self._spatial_reference def write_forward_run(self): # update python commands with system style commands for alist, ilist in zip( - [self.pre_py_cmds, self.mod_py_cmds, self.post_py_cmds], - [self.pre_sys_cmds, self.mod_sys_cmds, self.post_sys_cmds]): + [self.pre_py_cmds, self.mod_py_cmds, self.post_py_cmds], + [self.pre_sys_cmds, self.mod_sys_cmds, self.post_sys_cmds], + ): if ilist is None: continue @@ -244,20 +277,23 @@ def write_forward_run(self): for cmd in ilist: new_sys_cmd = "pyemu.os_utils.run(r'{0}')\n".format(cmd) if new_sys_cmd in alist: - self.logger.warn("sys_cmd '{0}' already in sys cmds, skipping...".format(new_sys_cmd)) + self.logger.warn( + "sys_cmd '{0}' already in sys cmds, skipping...".format( + new_sys_cmd + ) + ) else: self.logger.statement("forward_run line:{0}".format(new_sys_cmd)) alist.append(new_sys_cmd) - with open(os.path.join(self.new_d, self.py_run_file), - 'w') as f: + with open(os.path.join(self.new_d, self.py_run_file), "w") as f: f.write( - "import os\nimport multiprocessing as mp\nimport numpy as np" + \ - "\nimport pandas as pd\n") + "import os\nimport multiprocessing as mp\nimport numpy as np" + + "\nimport pandas as pd\n" + ) f.write("import pyemu\n") for ex_imp in self.extra_py_imports: - f.write('import {0}\n'.format(ex_imp)) - + f.write("import {0}\n".format(ex_imp)) for func_lines in self._function_lines_list: f.write("\n") @@ -272,14 +308,15 @@ def write_forward_run(self): f.write(s + "try:\n") f.write(s + " os.remove('{0}')\n".format(tmp_file)) f.write(s + "except Exception as e:\n") - f.write(s + " print('error removing tmp file:{0}')\n".format( - tmp_file)) + f.write( + s + " print('error removing tmp file:{0}')\n".format(tmp_file) + ) for line in self.pre_py_cmds: - f.write(s + line + '\n') + f.write(s + line + "\n") for line in self.mod_py_cmds: - f.write(s + line + '\n') + f.write(s + line + "\n") for line in self.post_py_cmds: - f.write(s + line + '\n') + f.write(s + line + "\n") f.write("\n") f.write("if __name__ == '__main__':\n") f.write(" mp.freeze_support()\n main()\n\n") @@ -290,15 +327,16 @@ def _pivot_par_struct_dict(self): par_dfs = [] for _, l in gps.items(): df = pd.concat(l) - if 'timedelta' in df.columns: + if "timedelta" in df.columns: df.loc[:, "y"] = 0 # df.loc[:, "x"] = df.timedelta.apply(lambda x: x.days) par_dfs.append(df) struct_dict[gs] = par_dfs return struct_dict - def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, - sigma_range=6): + def build_prior( + self, fmt="ascii", filename=None, droptol=None, chunk=None, sigma_range=6 + ): """ Args: @@ -314,29 +352,30 @@ def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, struct_dict = self._pivot_par_struct_dict() self.logger.log("building prior covariance matrix") if len(struct_dict) > 0: - cov = pyemu.helpers.geostatistical_prior_builder(self.pst, - struct_dict=struct_dict, - sigma_range=sigma_range) + cov = pyemu.helpers.geostatistical_prior_builder( + self.pst, struct_dict=struct_dict, sigma_range=sigma_range + ) else: cov = pyemu.Cov.from_parameter_data(self.pst, sigma_range=sigma_range) if filename is None: - filename = self.pst.filename.replace('.pst', ".prior.cov") + filename = self.pst.filename.replace(".pst", ".prior.cov") if fmt != "none": - self.logger.statement("saving prior covariance matrix to file {0}".format(filename)) - if fmt == 'ascii': + self.logger.statement( + "saving prior covariance matrix to file {0}".format(filename) + ) + if fmt == "ascii": cov.to_ascii(filename) - elif fmt == 'binary': + elif fmt == "binary": cov.to_binary(filename, droptol=droptol, chunk=chunk) - elif fmt == 'uncfile': + elif fmt == "uncfile": cov.to_uncfile(filename) - elif fmt == 'coo': + elif fmt == "coo": cov.to_coo(filename, droptol=droptol, chunk=chunk) self.logger.log("building prior covariance matrix") return cov - def draw(self, num_reals=100, sigma_range=6, use_specsim=False, - scale_offset=True): + def draw(self, num_reals=100, sigma_range=6, use_specsim=False, scale_offset=True): """ Args: @@ -355,14 +394,17 @@ def draw(self, num_reals=100, sigma_range=6, use_specsim=False, gr_pe_l = [] if use_specsim: if not pyemu.geostats.SpecSim2d.grid_is_regular( - self.spatial_reference.delr, self.spatial_reference.delc): - self.logger.lraise("draw() error: can't use spectral simulation with irregular grid") + self.spatial_reference.delr, self.spatial_reference.delc + ): + self.logger.lraise( + "draw() error: can't use spectral simulation with irregular grid" + ) self.logger.log("spectral simulation for grid-scale pars") # loop over geostructures defined in PestFrom object # (setup through add_parameters) for geostruct, par_df_l in struct_dict.items(): par_df = pd.concat(par_df_l) # force to single df - if 'i' in par_df.columns: # need 'i' and 'j' for specsim + if "i" in par_df.columns: # need 'i' and 'j' for specsim # grid par slicer grd_p = pd.notna(par_df.i) # & (par_df.partype == 'grid') & else: @@ -371,18 +413,23 @@ def draw(self, num_reals=100, sigma_range=6, use_specsim=False, if grd_p.sum() > 0: # select pars to use specsim for gr_df = par_df.loc[grd_p] - gr_df = gr_df.astype({'i': int, 'j': int}) # make sure int + gr_df = gr_df.astype({"i": int, "j": int}) # make sure int # (won't be if there were nans in concatenated df) if len(gr_df) > 0: # get specsim object for this geostruct ss = pyemu.geostats.SpecSim2d( delx=self.spatial_reference.delr, dely=self.spatial_reference.delc, - geostruct=geostruct) + geostruct=geostruct, + ) # specsim draw (returns df) gr_pe1 = ss.grid_par_ensemble_helper( - pst=self.pst, gr_df=gr_df, num_reals=num_reals, - sigma_range=sigma_range, logger=self.logger) + pst=self.pst, + gr_df=gr_df, + num_reals=num_reals, + sigma_range=sigma_range, + logger=self.logger, + ) # append to list of specsim drawn pars gr_pe_l.append(gr_pe1) # rebuild struct_dict entry for this geostruct @@ -399,8 +446,12 @@ def draw(self, num_reals=100, sigma_range=6, use_specsim=False, # draw remaining pars based on their geostruct self.logger.log("Drawing non-specsim pars") pe = pyemu.helpers.geostatistical_draws( - self.pst, struct_dict=struct_dict, num_reals=num_reals, - sigma_range=sigma_range, scale_offset=scale_offset) + self.pst, + struct_dict=struct_dict, + num_reals=num_reals, + sigma_range=sigma_range, + scale_offset=scale_offset, + ) self.logger.log("Drawing non-specsim pars") if len(gr_pe_l) > 0: gr_par_pe = pd.concat(gr_pe_l, axis=1) @@ -434,8 +485,10 @@ def build_pst(self, filename=None, update=False, version=1): obs_data_cols = pyemu.pst_utils.pst_config["obs_fieldnames"] if update: if self.pst is None: - self.logger.warn("Can't update Pst object not initialised. " - "Setting update to False") + self.logger.warn( + "Can't update Pst object not initialised. " + "Setting update to False" + ) update = False else: if filename is None: @@ -443,49 +496,55 @@ def build_pst(self, filename=None, update=False, version=1): else: if filename is None: filename = os.path.join(self.new_d, self.original_d) - if os.path.dirname(filename) in ['', '.']: + if os.path.dirname(filename) in ["", "."]: filename = os.path.join(self.new_d, filename) if update: pst = self.pst if update is True: - update = {'pars': False, 'obs': False} + update = {"pars": False, "obs": False} elif isinstance(update, str): update = {update: True} elif isinstance(update, (set, list)): update = {s: True for s in update} uupdate = True else: - update = {'pars': False, 'obs': False} + update = {"pars": False, "obs": False} uupdate = False pst = pyemu.Pst(filename, load=False) - if 'pars' in update.keys() or not uupdate: + if "pars" in update.keys() or not uupdate: if len(self.par_dfs) > 0: # parameter data from object par_data = pd.concat(self.par_dfs).loc[:, par_data_cols] # info relating parameter multiplier files to model input files parfile_relations = self.parfile_relations - parfile_relations.to_csv(os.path.join(self.new_d, - 'mult2model_info.csv')) - if not any(["apply_list_and_array_pars" in s - for s in self.pre_py_cmds]): - self.pre_py_cmds.insert(0, + parfile_relations.to_csv( + os.path.join(self.new_d, "mult2model_info.csv") + ) + if not any( + ["apply_list_and_array_pars" in s for s in self.pre_py_cmds] + ): + self.pre_py_cmds.insert( + 0, "pyemu.helpers.apply_list_and_array_pars(" - "arr_par_file='mult2model_info.csv')") + "arr_par_file='mult2model_info.csv')", + ) else: par_data = pyemu.pst_utils._populate_dataframe( - [], pst.par_fieldnames, pst.par_defaults, pst.par_dtype) + [], pst.par_fieldnames, pst.par_defaults, pst.par_dtype + ) pst.parameter_data = par_data pst.template_files = self.tpl_filenames pst.input_files = self.input_filenames - if 'obs' in update.keys() or not uupdate: + if "obs" in update.keys() or not uupdate: if len(self.obs_dfs) > 0: obs_data = pd.concat(self.obs_dfs).loc[:, obs_data_cols] else: obs_data = pyemu.pst_utils._populate_dataframe( - [], pst.obs_fieldnames, pst.obs_defaults, pst.obs_dtype) + [], pst.obs_fieldnames, pst.obs_defaults, pst.obs_dtype + ) obs_data.loc[:, "obsnme"] = [] obs_data.index = [] obs_data.sort_index(inplace=True) @@ -505,46 +564,60 @@ def build_pst(self, filename=None, update=False, version=1): def _setup_dirs(self): self.logger.log("setting up dirs") if not os.path.exists(self.original_d): - self.logger.lraise("original_d '{0}' not found" - "".format(self.original_d)) + self.logger.lraise("original_d '{0}' not found" "".format(self.original_d)) if not os.path.isdir(self.original_d): - self.logger.lraise("original_d '{0}' is not a directory" - "".format(self.original_d)) + self.logger.lraise( + "original_d '{0}' is not a directory" "".format(self.original_d) + ) if os.path.exists(self.new_d): if self.remove_existing: - self.logger.log("removing existing new_d '{0}'" - "".format(self.new_d)) + self.logger.log("removing existing new_d '{0}'" "".format(self.new_d)) shutil.rmtree(self.new_d) time.sleep(0.0001) - self.logger.log("removing existing new_d '{0}'" - "".format(self.new_d)) + self.logger.log("removing existing new_d '{0}'" "".format(self.new_d)) else: - self.logger.lraise("new_d '{0}' already exists " - "- use remove_existing=True" - "".format(self.new_d)) + self.logger.lraise( + "new_d '{0}' already exists " + "- use remove_existing=True" + "".format(self.new_d) + ) - self.logger.log("copying original_d '{0}' to new_d '{1}'" - "".format(self.original_d, self.new_d)) + self.logger.log( + "copying original_d '{0}' to new_d '{1}'" + "".format(self.original_d, self.new_d) + ) shutil.copytree(self.original_d, self.new_d) - self.logger.log("copying original_d '{0}' to new_d '{1}'" - "".format(self.original_d, self.new_d)) + self.logger.log( + "copying original_d '{0}' to new_d '{1}'" + "".format(self.original_d, self.new_d) + ) self.original_file_d = os.path.join(self.new_d, "org") if os.path.exists(self.original_file_d): - self.logger.lraise("'org' subdir already exists in new_d '{0}'" - "".format(self.new_d)) + self.logger.lraise( + "'org' subdir already exists in new_d '{0}'" "".format(self.new_d) + ) os.makedirs(self.original_file_d) self.mult_file_d = os.path.join(self.new_d, "mult") if os.path.exists(self.mult_file_d): - self.logger.lraise("'mult' subdir already exists in new_d '{0}'" - "".format(self.new_d)) + self.logger.lraise( + "'mult' subdir already exists in new_d '{0}'" "".format(self.new_d) + ) os.makedirs(self.mult_file_d) self.logger.log("setting up dirs") - def _par_prep(self, filenames, index_cols, use_cols, fmts=None, seps=None, - skip_rows=None, c_char=None): + def _par_prep( + self, + filenames, + index_cols, + use_cols, + fmts=None, + seps=None, + skip_rows=None, + c_char=None, + ): # todo: cast str column names, index_cols and use_cols to lower if str? # todo: check that all index_cols and use_cols are the same type @@ -552,57 +625,70 @@ def _par_prep(self, filenames, index_cols, use_cols, fmts=None, seps=None, fmt_dict = {} sep_dict = {} skip_dict = {} - (filenames, fmts, seps, skip_rows, - index_cols, use_cols) = self._prep_arg_list_lengths( - filenames, fmts, seps, skip_rows, index_cols, use_cols) + ( + filenames, + fmts, + seps, + skip_rows, + index_cols, + use_cols, + ) = self._prep_arg_list_lengths( + filenames, fmts, seps, skip_rows, index_cols, use_cols + ) if index_cols is not None: - for filename, sep, fmt, skip in zip(filenames, seps, fmts, - skip_rows): + for filename, sep, fmt, skip in zip(filenames, seps, fmts, skip_rows): file_path = os.path.join(self.new_d, filename) self.logger.log("loading list {0}".format(file_path)) df, storehead = self._load_listtype_file( - filename, index_cols, use_cols, fmt, sep, skip, c_char) + filename, index_cols, use_cols, fmt, sep, skip, c_char + ) # Currently just passing through comments in header (i.e. before the table data) - stkeys = np.array(sorted(storehead.keys())) # comments line numbers as sorted array - if stkeys.size > 0 and stkeys.min() == 0: # TODO pass comment_char through to par_file_rel so mid-table comments can be preserved + stkeys = np.array( + sorted(storehead.keys()) + ) # comments line numbers as sorted array + if ( + stkeys.size > 0 and stkeys.min() == 0 + ): # TODO pass comment_char through to par_file_rel so mid-table comments can be preserved skip = 1 + np.sum(np.diff(stkeys) == 1) # # looping over model input filenames - if fmt.lower() == 'free': + if fmt.lower() == "free": if sep is None: - sep = ' ' + sep = " " if filename.lower().endswith(".csv"): - sep = ',' + sep = "," if df.columns.is_integer(): hheader = False else: hheader = df.columns - self.logger.statement("loaded list '{0}' of shape {1}" - "".format(file_path, df.shape)) + self.logger.statement( + "loaded list '{0}' of shape {1}" "".format(file_path, df.shape) + ) # TODO BH: do we need to be careful of the format of the model # files? -- probs not necessary for the version in # original_file_d - but for the eventual product model file, # it might be format sensitive - yuck # Update, BH: I think the `original files` saved can always - # be comma delim --they are never directly used - # as model inputs-- as long as we pass the required model - # input file format (and sep), right? + # be comma delim --they are never directly used + # as model inputs-- as long as we pass the required model + # input file format (and sep), right? # write orig version of input file to `org` (e.g.) dir if len(storehead) != 0: kwargs = {} if "win" in platform.platform().lower(): kwargs = {"line_terminator": "\n"} - with open(os.path.join( - self.original_file_d, filename), 'w') as fp: + with open(os.path.join(self.original_file_d, filename), "w") as fp: lc = 0 fr = 0 for key in sorted(storehead.keys()): if key > lc: - self.logger.warn("Detected mid-table comment " - "on line {0} tabular model file, " - "comment will be lost" - "".format(key + 1)) + self.logger.warn( + "Detected mid-table comment " + "on line {0} tabular model file, " + "comment will be lost" + "".format(key + 1) + ) lc += 1 continue # TODO if we want to preserve mid-table comments, @@ -620,11 +706,20 @@ def _par_prep(self, filenames, index_cols, use_cols, fmts=None, seps=None, lc += 1 if lc < len(df): df.iloc[fr:].to_csv( - fp, sep=',', mode='a', header=hheader, - index=False, **kwargs) + fp, + sep=",", + mode="a", + header=hheader, + index=False, + **kwargs + ) else: - df.to_csv(os.path.join(self.original_file_d, filename), - index=False, sep=',', header=hheader) + df.to_csv( + os.path.join(self.original_file_d, filename), + index=False, + sep=",", + header=hheader, + ) file_dict[filename] = df fmt_dict[filename] = fmt sep_dict[filename] = sep @@ -635,36 +730,40 @@ def _par_prep(self, filenames, index_cols, use_cols, fmts=None, seps=None, fnames = list(file_dict.keys()) for i in range(len(fnames)): for j in range(i + 1, len(fnames)): - if (file_dict[fnames[i]].shape[1] != - file_dict[fnames[j]].shape[1]): + if file_dict[fnames[i]].shape[1] != file_dict[fnames[j]].shape[1]: self.logger.lraise( "shape mismatch for array types, '{0}' " - "shape {1} != '{2}' shape {3}". - format(fnames[i], file_dict[fnames[i]].shape[1], - fnames[j], file_dict[fnames[j]].shape[1])) + "shape {1} != '{2}' shape {3}".format( + fnames[i], + file_dict[fnames[i]].shape[1], + fnames[j], + file_dict[fnames[j]].shape[1], + ) + ) else: # load array type files # loop over model input files - for filename, sep, fmt, skip in zip(filenames, seps, fmts, - skip_rows): - if fmt.lower() == 'free': + for filename, sep, fmt, skip in zip(filenames, seps, fmts, skip_rows): + if fmt.lower() == "free": if filename.lower().endswith(".csv"): if sep is None: - sep = ',' + sep = "," else: # TODO - or not? - raise NotImplementedError("Only free format array " - "par files currently supported") + raise NotImplementedError( + "Only free format array " "par files currently supported" + ) file_path = os.path.join(self.new_d, filename) self.logger.log("loading array {0}".format(file_path)) if not os.path.exists(file_path): - self.logger.lraise("par filename '{0}' not found ". - format(file_path)) - # read array type input file - arr = np.loadtxt(os.path.join(self.new_d, filename), - delimiter=sep) + self.logger.lraise( + "par filename '{0}' not found ".format(file_path) + ) + # read array type input file + arr = np.loadtxt(os.path.join(self.new_d, filename), delimiter=sep) self.logger.log("loading array {0}".format(file_path)) - self.logger.statement("loaded array '{0}' of shape {1}". - format(filename, arr.shape)) + self.logger.statement( + "loaded array '{0}' of shape {1}".format(filename, arr.shape) + ) # save copy of input file to `org` dir np.savetxt(os.path.join(self.original_file_d, filename), arr) file_dict[filename] = arr @@ -675,13 +774,17 @@ def _par_prep(self, filenames, index_cols, use_cols, fmts=None, seps=None, fnames = list(file_dict.keys()) for i in range(len(fnames)): for j in range(i + 1, len(fnames)): - if (file_dict[fnames[i]].shape != - file_dict[fnames[j]].shape): + if file_dict[fnames[i]].shape != file_dict[fnames[j]].shape: self.logger.lraise( "shape mismatch for array types, '{0}' " "shape {1} != '{2}' shape {3}" - "".format(fnames[i], file_dict[fnames[i]].shape, - fnames[j], file_dict[fnames[j]].shape)) + "".format( + fnames[i], + file_dict[fnames[i]].shape, + fnames[j], + file_dict[fnames[j]].shape, + ) + ) return index_cols, use_cols, file_dict, fmt_dict, sep_dict, skip_dict def _next_count(self, prefix): @@ -692,8 +795,7 @@ def _next_count(self, prefix): return self._prefix_count[prefix] - - def add_py_function(self,file_name,function_name, is_pre_cmd=True): + def add_py_function(self, file_name, function_name, is_pre_cmd=True): """add a python function to the forward run script Args: @@ -729,27 +831,38 @@ def add_py_function(self,file_name,function_name, is_pre_cmd=True): """ if not os.path.exists(file_name): - self.logger.lraise("add_py_function(): couldnt find python source file '{0}'".\ - format(file_name)) - if '(' not in function_name or ')' not in function_name: - self.logger.lraise("add_py_function(): function_name '{0}' missing paretheses".\ - format(function_name)) + self.logger.lraise( + "add_py_function(): couldnt find python source file '{0}'".format( + file_name + ) + ) + if "(" not in function_name or ")" not in function_name: + self.logger.lraise( + "add_py_function(): function_name '{0}' missing paretheses".format( + function_name + ) + ) func_lines = [] search_str = "def " + function_name abet_set = set(string.ascii_uppercase) abet_set.update(set(string.ascii_lowercase)) - with open(file_name,'r') as f: + with open(file_name, "r") as f: while True: line = f.readline() - if line == '': - self.logger.lraise("add_py_function(): EOF while searching for function '{0}'".\ - format(search_str)) - if line.startswith(search_str): #case sens and no strip since 'def' should be flushed left + if line == "": + self.logger.lraise( + "add_py_function(): EOF while searching for function '{0}'".format( + search_str + ) + ) + if line.startswith( + search_str + ): # case sens and no strip since 'def' should be flushed left func_lines.append(line) while True: line = f.readline() - if line == '': + if line == "": break if line[0] in abet_set: break @@ -762,13 +875,25 @@ def add_py_function(self,file_name,function_name, is_pre_cmd=True): elif is_pre_cmd is False: self.post_py_cmds.append(function_name) else: - self.logger.warn("add_py_function() command: {0} is not being called directly".\ - format(function_name)) - - def add_observations(self, filename, insfile=None, - index_cols=None, use_cols=None, - use_rows=None, prefix='', ofile_skip=None, - ofile_sep=None, rebuild_pst=False, obsgp=True): + self.logger.warn( + "add_py_function() command: {0} is not being called directly".format( + function_name + ) + ) + + def add_observations( + self, + filename, + insfile=None, + index_cols=None, + use_cols=None, + use_rows=None, + prefix="", + ofile_skip=None, + ofile_sep=None, + rebuild_pst=False, + obsgp=True, + ): """ Add list style outputs as observation files to PstFrom object @@ -793,34 +918,55 @@ def add_observations(self, filename, insfile=None, insfile = "{0}.ins".format(filename) self.logger.log("adding observations from tabular output file") # precondition arguments - (filenames, fmts, seps, skip_rows, - index_cols, use_cols) = self._prep_arg_list_lengths( - filename, index_cols=index_cols, use_cols=use_cols, - fmts=None, seps=ofile_sep, skip_rows=ofile_skip) + ( + filenames, + fmts, + seps, + skip_rows, + index_cols, + use_cols, + ) = self._prep_arg_list_lengths( + filename, + index_cols=index_cols, + use_cols=use_cols, + fmts=None, + seps=ofile_sep, + skip_rows=ofile_skip, + ) # load model output file df, storehead = self._load_listtype_file( - filenames, index_cols, use_cols, fmts, seps, skip_rows) + filenames, index_cols, use_cols, fmts, seps, skip_rows + ) # Currently just passing through comments in header (i.e. before the table data) lenhead = 0 - stkeys = np.array(sorted(storehead.keys())) # comments line numbers as sorted array + stkeys = np.array( + sorted(storehead.keys()) + ) # comments line numbers as sorted array if stkeys.size > 0 and stkeys.min() == 0: lenhead += 1 + np.sum(np.diff(stkeys) == 1) new_obs_l = [] for filename, sep in zip(filenames, seps): # should only ever be one but hey... - self.logger.log("building insfile for tabular output file {0}" - "".format(filename)) - df_temp = _get_tpl_or_ins_df(filename, df, prefix, typ='obs', - index_cols=index_cols, - use_cols=use_cols, - longnames=self.longnames) - df.loc[:, 'idx_str'] = df_temp.idx_strs + self.logger.log( + "building insfile for tabular output file {0}" "".format(filename) + ) + df_temp = _get_tpl_or_ins_df( + filename, + df, + prefix, + typ="obs", + index_cols=index_cols, + use_cols=use_cols, + longnames=self.longnames, + ) + df.loc[:, "idx_str"] = df_temp.idx_strs if use_rows is not None: if isinstance(use_rows, str): if use_rows not in df.idx_str: self.logger.warn( "can't find {0} in generated observation idx_str. " "setting up obs for all rows instead" - "".format(use_rows)) + "".format(use_rows) + ) use_rows = None elif isinstance(use_rows, int): use_rows = [use_rows] @@ -831,35 +977,44 @@ def add_observations(self, filename, insfile=None, obsgp = _check_var_len(obsgp, ncol, fill=True) df_ins = pyemu.pst_utils.csv_to_ins_file( - df.set_index('idx_str'), - ins_filename=os.path.join( - self.new_d, insfile), - only_cols=use_cols, only_rows=use_rows, marker='~', - includes_header=True, includes_index=False, prefix=prefix, - longnames=True, head_lines_len=lenhead, sep=sep, - gpname=obsgp) - self.logger.log("building insfile for tabular output file {0}" - "".format(filename)) + df.set_index("idx_str"), + ins_filename=os.path.join(self.new_d, insfile), + only_cols=use_cols, + only_rows=use_rows, + marker="~", + includes_header=True, + includes_index=False, + prefix=prefix, + longnames=True, + head_lines_len=lenhead, + sep=sep, + gpname=obsgp, + ) + self.logger.log( + "building insfile for tabular output file {0}" "".format(filename) + ) new_obs = self.add_observations_from_ins( - ins_file=insfile, out_file=os.path.join(self.new_d, filename)) - if 'obgnme' in df_ins.columns: - new_obs.loc[:, 'obgnme'] = df_ins.loc[new_obs.index, 'obgnme'] + ins_file=insfile, out_file=os.path.join(self.new_d, filename) + ) + if "obgnme" in df_ins.columns: + new_obs.loc[:, "obgnme"] = df_ins.loc[new_obs.index, "obgnme"] new_obs_l.append(new_obs) new_obs = pd.concat(new_obs_l) self.logger.log("adding observations from tabular output file") if rebuild_pst: if self.pst is not None: - self.logger.log("Adding obs to control file " - "and rewriting pst") - self.build_pst(filename=self.pst.filename, update='obs') + self.logger.log("Adding obs to control file " "and rewriting pst") + self.build_pst(filename=self.pst.filename, update="obs") else: self.build_pst(filename=self.pst.filename, update=False) - self.logger.warn("pst object not available, " - "new control file will be written") + self.logger.warn( + "pst object not available, " "new control file will be written" + ) return new_obs - def add_observations_from_ins(self, ins_file, out_file=None, pst_path=None, - inschek=True): + def add_observations_from_ins( + self, ins_file, out_file=None, pst_path=None, inschek=True + ): """ add new observations to a control file Args: @@ -893,12 +1048,13 @@ def add_observations_from_ins(self, ins_file, out_file=None, pst_path=None, """ # lifted almost completely from `Pst().add_observation()` - if os.path.dirname(ins_file) in ['', '.']: + if os.path.dirname(ins_file) in ["", "."]: ins_file = os.path.join(self.new_d, ins_file) - pst_path = '.' + pst_path = "." if not os.path.exists(ins_file): - self.logger.lraise("ins file not found: {0}, {1}" - "".format(os.getcwd(), ins_file)) + self.logger.lraise( + "ins file not found: {0}, {1}" "".format(os.getcwd(), ins_file) + ) if out_file is None: out_file = ins_file.replace(".ins", "") if ins_file == out_file: @@ -906,7 +1062,8 @@ def add_observations_from_ins(self, ins_file, out_file=None, pst_path=None, # get the parameter names in the template file self.logger.log( - "adding observation from instruction file '{0}'".format(ins_file)) + "adding observation from instruction file '{0}'".format(ins_file) + ) obsnme = pyemu.pst_utils.parse_ins_file(ins_file) sobsnme = set(obsnme) @@ -919,31 +1076,31 @@ def add_observations_from_ins(self, ins_file, out_file=None, pst_path=None, if len(sint) > 0: self.logger.lraise( "the following obs instruction file {0} are already in the " - "control file:{1}". - format(ins_file, ','.join(sint))) + "control file:{1}".format(ins_file, ",".join(sint)) + ) # find "new" obs that are not already in the control file new_obsnme = sobsnme - sexist if len(new_obsnme) == 0: self.logger.lraise( - "no new observations found in instruction file {0}".format( - ins_file)) + "no new observations found in instruction file {0}".format(ins_file) + ) # extend observation_data new_obsnme = np.sort(list(new_obsnme)) new_obs_data = pyemu.pst_utils._populate_dataframe( - new_obsnme, pyemu.pst_utils.pst_config["obs_fieldnames"], + new_obsnme, + pyemu.pst_utils.pst_config["obs_fieldnames"], pyemu.pst_utils.pst_config["obs_defaults"], - pyemu.pst_utils.pst_config["obs_dtype"]) + pyemu.pst_utils.pst_config["obs_dtype"], + ) new_obs_data.loc[new_obsnme, "obsnme"] = new_obsnme new_obs_data.index = new_obsnme # cwd = '.' if pst_path is not None: # cwd = os.path.join(*os.path.split(ins_file)[:-1]) - ins_file_pstrel = os.path.join(pst_path, - os.path.split(ins_file)[-1]) - out_file_pstrel = os.path.join(pst_path, - os.path.split(out_file)[-1]) + ins_file_pstrel = os.path.join(pst_path, os.path.split(ins_file)[-1]) + out_file_pstrel = os.path.join(pst_path, os.path.split(out_file)[-1]) self.ins_filenames.append(ins_file_pstrel) self.output_filenames.append(out_file_pstrel) # add to temporary files to be removed at start of forward run @@ -953,27 +1110,47 @@ def add_observations_from_ins(self, ins_file, out_file=None, pst_path=None, # df = pst_utils._try_run_inschek(ins_file,out_file,cwd=cwd) # ins_file = os.path.join(cwd, ins_file) # out_file = os.path.join(cwd, out_file) - df = pyemu.pst_utils.try_process_output_file(ins_file=ins_file, - output_file=out_file) + df = pyemu.pst_utils.try_process_output_file( + ins_file=ins_file, output_file=out_file + ) if df is not None: # print(self.observation_data.index,df.index) new_obs_data.loc[df.index, "obsval"] = df.obsval self.obs_dfs.append(new_obs_data) self.logger.log( - "adding observation from instruction file '{0}'".format(ins_file)) + "adding observation from instruction file '{0}'".format(ins_file) + ) return new_obs_data - def add_parameters(self, filenames, par_type, zone_array=None, - dist_type="gaussian", sigma_range=4.0, - upper_bound=1.0e10, lower_bound=1.0e-10, - transform="log", par_name_base="p", index_cols=None, - use_cols=None, pargp=None, pp_space=10, - use_pp_zones=False, num_eig_kl=100, - spatial_reference=None, geostruct=None, - datetime=None, mfile_fmt='free', mfile_skip=None, - ult_ubound=None, ult_lbound=None, rebuild_pst=False, - alt_inst_str='inst', comment_char=None, - par_style="multiplier"): + def add_parameters( + self, + filenames, + par_type, + zone_array=None, + dist_type="gaussian", + sigma_range=4.0, + upper_bound=1.0e10, + lower_bound=1.0e-10, + transform="log", + par_name_base="p", + index_cols=None, + use_cols=None, + pargp=None, + pp_space=10, + use_pp_zones=False, + num_eig_kl=100, + spatial_reference=None, + geostruct=None, + datetime=None, + mfile_fmt="free", + mfile_skip=None, + ult_ubound=None, + ult_lbound=None, + rebuild_pst=False, + alt_inst_str="inst", + comment_char=None, + par_style="multiplier", + ): """ Add list or array style model input files to PstFrom object. This method @@ -1050,49 +1227,65 @@ def add_parameters(self, filenames, par_type, zone_array=None, # TODO support passing par_file (i,j)/(x,y) directly where information # is not contained in model parameter file - e.g. no i,j columns - # this keeps denormal values for creeping into the model input arrays if ult_ubound is None: ult_ubound = self.ult_ubound_fill if ult_lbound is None: ult_lbound = self.ult_lbound_fill - #some checks for direct parameters + # some checks for direct parameters par_style = par_style.lower() if par_style not in ["multiplier", "direct"]: - self.logger.lraise("add_parameters(): unrecognized 'style': {0}, should be either 'multiplier' or 'direct'". \ - format(par_style)) - if isinstance(filenames,str): + self.logger.lraise( + "add_parameters(): unrecognized 'style': {0}, should be either 'multiplier' or 'direct'".format( + par_style + ) + ) + if isinstance(filenames, str): filenames = [filenames] if par_style == "direct": if len(filenames) != 1: - self.logger.lraise("add_parameters(): 'filenames' arg for 'direct' style must contain " + \ - "one and only one filename, not {0} files".format(len(filenames))) + self.logger.lraise( + "add_parameters(): 'filenames' arg for 'direct' style must contain " + + "one and only one filename, not {0} files".format(len(filenames)) + ) if filenames[0] in self.direct_org_files: - self.logger.lraise("add_parameters(): original model input file '{0}' ".format(filenames[0]) + \ - " already used for 'direct' parameterization") + self.logger.lraise( + "add_parameters(): original model input file '{0}' ".format( + filenames[0] + ) + + " already used for 'direct' parameterization" + ) # Default par data columns used for pst par_data_cols = pyemu.pst_utils.pst_config["par_fieldnames"] - self.logger.log("adding {0} type {1} style parameters for file(s) {2}".format(par_type, par_style, str(filenames))) + self.logger.log( + "adding {0} type {1} style parameters for file(s) {2}".format( + par_type, par_style, str(filenames) + ) + ) if geostruct is not None: if geostruct.sill != 1.0 and par_style != "multiplier": - self.logger.warn("geostruct sill != 1.0 for 'multiplier' style parameters") + self.logger.warn( + "geostruct sill != 1.0 for 'multiplier' style parameters" + ) if geostruct.transform != transform: - self.logger.warn("0) Inconsistency between " - "geostruct transform and partrans.") - self.logger.warn("1) Setting geostruct transform to " - "{0}".format(transform)) + self.logger.warn( + "0) Inconsistency between " "geostruct transform and partrans." + ) + self.logger.warn( + "1) Setting geostruct transform to " "{0}".format(transform) + ) if geostruct not in self.par_struct_dict.keys(): # safe to just reset transform geostruct.transform = transform else: - self.logger.warn("2) This will create a new copy of " - "geostruct") + self.logger.warn("2) This will create a new copy of " "geostruct") # to avoid flip flopping transform need to make a new geostruct geostruct = copy.copy(geostruct) geostruct.transform = transform - self.logger.warn("-) Better to pass an appropriately " - "transformed geostruct") + self.logger.warn( + "-) Better to pass an appropriately " "transformed geostruct" + ) # Get useful variables from arguments passed # if index_cols passed as a dictionary that maps i,j information @@ -1104,33 +1297,49 @@ def add_parameters(self, filenames, par_type, zone_array=None, # TODO: or datetime str? keys = np.array([k.lower() for k in index_cols.keys()]) idx_cols = [index_cols[k] for k in keys] - if any(all(a in keys for a in aa) - for aa in [['i', 'j'], ['x', 'y']]): - if all(ij in keys for ij in ['i', 'j']): + if any(all(a in keys for a in aa) for aa in [["i", "j"], ["x", "y"]]): + if all(ij in keys for ij in ["i", "j"]): o_idx = np.argsort(keys) - ij_in_idx = o_idx[np.searchsorted(keys[o_idx], ['i', 'j'])] - if all(xy in keys for xy in ['x', 'y']): + ij_in_idx = o_idx[np.searchsorted(keys[o_idx], ["i", "j"])] + if all(xy in keys for xy in ["x", "y"]): o_idx = np.argsort(keys) - xy_in_idx = o_idx[np.searchsorted(keys[o_idx], ['x', 'y'])] + xy_in_idx = o_idx[np.searchsorted(keys[o_idx], ["x", "y"])] else: - self.logger.lraise("If passing `index_cols` as type == dict, " - "keys need to contain [`i` and `j`] or " - "[`x` and `y`]") - - (index_cols, use_cols, file_dict, - fmt_dict, sep_dict, skip_dict) = self._par_prep( - filenames, idx_cols, use_cols, fmts=mfile_fmt, - skip_rows=mfile_skip, c_char=comment_char) + self.logger.lraise( + "If passing `index_cols` as type == dict, " + "keys need to contain [`i` and `j`] or " + "[`x` and `y`]" + ) + + ( + index_cols, + use_cols, + file_dict, + fmt_dict, + sep_dict, + skip_dict, + ) = self._par_prep( + filenames, + idx_cols, + use_cols, + fmts=mfile_fmt, + skip_rows=mfile_skip, + c_char=comment_char, + ) if datetime is not None: # convert and check datetime # TODO: something needed here to allow a different relative point. datetime = _get_datetime_from_str(datetime) if self.start_datetime is None: - self.logger.warn("NO START_DATEIME PROVIDED, ASSUMING PAR " - "DATETIME IS START {}".format(datetime)) + self.logger.warn( + "NO START_DATEIME PROVIDED, ASSUMING PAR " + "DATETIME IS START {}".format(datetime) + ) self.start_datetime = datetime - assert datetime >= self.start_datetime, ( - "passed datetime is earlier than start_datetime {0}, {1}". - format(datetime, self.start_datetime)) + assert ( + datetime >= self.start_datetime + ), "passed datetime is earlier than start_datetime {0}, {1}".format( + datetime, self.start_datetime + ) t_offest = datetime - self.start_datetime # Pull out and construct name-base for parameters @@ -1142,14 +1351,16 @@ def add_parameters(self, filenames, par_type, zone_array=None, elif use_cols is not None and len(par_name_base) == len(use_cols): pass else: - self.logger.lraise("par_name_base should be a string, " - "single-element container, or container of " - "len use_cols, not '{0}'" - "".format(str(par_name_base))) + self.logger.lraise( + "par_name_base should be a string, " + "single-element container, or container of " + "len use_cols, not '{0}'" + "".format(str(par_name_base)) + ) # otherewise, things get tripped up in the ensemble/cov stuff if pargp is not None: - if isinstance(pargp,list): + if isinstance(pargp, list): pargp = [pg.lower() for pg in pargp] else: pargp = pargp.lower() @@ -1164,29 +1375,32 @@ def add_parameters(self, filenames, par_type, zone_array=None, # increment name base if already passed for i in range(len(par_name_base)): par_name_base[i] += fmt.format( - self._next_count(par_name_base[i] + chk_prefix)) + self._next_count(par_name_base[i] + chk_prefix) + ) # multiplier file name will be taken first par group, if passed # (the same multipliers will apply to all pars passed in this call) # Remove `:` for filenames - par_name_store = par_name_base[0].replace(':', '') # for os filename + par_name_store = par_name_base[0].replace(":", "") # for os filename # Define requisite filenames if par_style == "multiplier": mlt_filename = "{0}_{1}.csv".format(par_name_store, par_type) # pst input file (for tpl->in pair) is multfile (in mult dir) - in_filepst = os.path.relpath(os.path.join( - self.mult_file_d, mlt_filename), self.new_d) + in_filepst = os.path.relpath( + os.path.join(self.mult_file_d, mlt_filename), self.new_d + ) tpl_filename = mlt_filename + ".tpl" in_fileabs = os.path.join(self.mult_file_d, mlt_filename) else: mlt_filename = np.NaN # pst input file (for tpl->in pair) is multfile (in mult dir) - in_filepst = os.path.relpath(os.path.join( - self.original_file_d, filenames[0]), self.new_d) + in_filepst = os.path.relpath( + os.path.join(self.original_file_d, filenames[0]), self.new_d + ) tpl_filename = filenames[0] + ".tpl" - in_fileabs = os.path.join(self.new_d,in_filepst) + in_fileabs = os.path.join(self.new_d, in_filepst) pp_filename = None # setup placeholder variables fac_filename = None @@ -1197,87 +1411,129 @@ def add_parameters(self, filenames, par_type, zone_array=None, ult_lbound = _check_var_len(ult_lbound, ncol) ult_ubound = _check_var_len(ult_ubound, ncol) pargp = _check_var_len(pargp, ncol) - lower_bound = _check_var_len(lower_bound, ncol, fill='first') - upper_bound = _check_var_len(upper_bound, ncol, fill='first') + lower_bound = _check_var_len(lower_bound, ncol, fill="first") + upper_bound = _check_var_len(upper_bound, ncol, fill="first") if len(use_cols) != len(ult_lbound) != len(ult_ubound): - self.logger.lraise("mismatch in number of columns to use {0} " - "and number of ultimate lower {0} or upper " - "{1} par bounds defined" - "".format(len(use_cols), len(ult_lbound), - len(ult_ubound))) + self.logger.lraise( + "mismatch in number of columns to use {0} " + "and number of ultimate lower {0} or upper " + "{1} par bounds defined" + "".format(len(use_cols), len(ult_lbound), len(ult_ubound)) + ) self.logger.log( - "writing list-based template file '{0}'".format(tpl_filename)) + "writing list-based template file '{0}'".format(tpl_filename) + ) # Generate tabular type template - also returns par data dfs = [file_dict[filename] for filename in filenames] - df = write_list_tpl(filenames, - dfs, par_name_base, + df = write_list_tpl( + filenames, + dfs, + par_name_base, tpl_filename=os.path.join(self.new_d, tpl_filename), - par_type=par_type, suffix='', index_cols=index_cols, - use_cols=use_cols, zone_array=zone_array, gpname=pargp, - longnames=self.longnames, ij_in_idx=ij_in_idx, - xy_in_idx=xy_in_idx, get_xy=self.get_xy, + par_type=par_type, + suffix="", + index_cols=index_cols, + use_cols=use_cols, + zone_array=zone_array, + gpname=pargp, + longnames=self.longnames, + ij_in_idx=ij_in_idx, + xy_in_idx=xy_in_idx, + get_xy=self.get_xy, zero_based=self.zero_based, input_filename=in_fileabs, - par_style=par_style) - assert np.mod(len(df), len(use_cols)) == 0., ( + par_style=par_style, + ) + assert np.mod(len(df), len(use_cols)) == 0.0, ( "Parameter dataframe wrong shape for number of cols {0}" - "".format(use_cols)) + "".format(use_cols) + ) # variables need to be passed to each row in df lower_bound = np.tile(lower_bound, int(len(df) / ncol)) upper_bound = np.tile(upper_bound, int(len(df) / ncol)) self.logger.log( - "writing list-based template file '{0}'".format(tpl_filename)) + "writing list-based template file '{0}'".format(tpl_filename) + ) else: # Assume array type parameter file self.logger.log( - "writing array-based template file '{0}'".format(tpl_filename)) + "writing array-based template file '{0}'".format(tpl_filename) + ) shp = file_dict[list(file_dict.keys())[0]].shape # ARRAY constant, zones or grid (cell-by-cell) if par_type in {"constant", "zone", "grid"}: self.logger.log( "writing template file " - "{0} for {1}".format(tpl_filename, par_name_base)) + "{0} for {1}".format(tpl_filename, par_name_base) + ) # Generate array type template - also returns par data df = write_array_tpl( name=par_name_base[0], tpl_filename=os.path.join(self.new_d, tpl_filename), - suffix='', par_type=par_type, zone_array=zone_array, - shape=shp, longnames=self.longnames, get_xy=self.get_xy, - fill_value=1.0, gpname=pargp, - input_filename=in_fileabs,par_style=par_style) + suffix="", + par_type=par_type, + zone_array=zone_array, + shape=shp, + longnames=self.longnames, + get_xy=self.get_xy, + fill_value=1.0, + gpname=pargp, + input_filename=in_fileabs, + par_style=par_style, + ) self.logger.log( "writing template file" - " {0} for {1}".format(tpl_filename, par_name_base)) + " {0} for {1}".format(tpl_filename, par_name_base) + ) # ARRAY PILOTPOINT setup - elif par_type in {"pilotpoints", "pilot_points", - "pilotpoint", "pilot_point", - "pilot-point", "pilot-points"}: + elif par_type in { + "pilotpoints", + "pilot_points", + "pilotpoint", + "pilot_point", + "pilot-point", + "pilot-points", + }: if par_style == "direct": - self.logger.lraise("pilot points not supported for 'direct' par_style") + self.logger.lraise( + "pilot points not supported for 'direct' par_style" + ) # Setup pilotpoints for array type par files self.logger.log("setting up pilot point parameters") # finding spatial references for for setting up pilot points if spatial_reference is None: # if none passed with add_pars call - self.logger.statement("No spatial reference " - "(containing cell spacing) passed.") + self.logger.statement( + "No spatial reference " "(containing cell spacing) passed." + ) if self.spatial_reference is not None: # using global sr on PestFrom object - self.logger.statement("OK - using spatial reference " - "in parent object.") + self.logger.statement( + "OK - using spatial reference " "in parent object." + ) spatial_reference = self.spatial_reference else: # uhoh - self.logger.lraise("No spatial reference in parent " - "object either. " - "Can't set-up pilotpoints") + self.logger.lraise( + "No spatial reference in parent " + "object either. " + "Can't set-up pilotpoints" + ) # check that spatial reference lines up with the original array dimensions for mod_file, ar in file_dict.items(): orgdata = ar.shape - assert orgdata[0] == spatial_reference.nrow, "Spatial reference nrow not equal to original data nrow for\n" + \ - os.path.join(*os.path.split(self.original_file_d)[1:], mod_file) - assert orgdata[1] == spatial_reference.ncol, "Spatial reference ncol not equal to original data ncol for\n" + \ - os.path.join(*os.path.split(self.original_file_d)[1:], mod_file) + assert orgdata[0] == spatial_reference.nrow, ( + "Spatial reference nrow not equal to original data nrow for\n" + + os.path.join( + *os.path.split(self.original_file_d)[1:], mod_file + ) + ) + assert orgdata[1] == spatial_reference.ncol, ( + "Spatial reference ncol not equal to original data ncol for\n" + + os.path.join( + *os.path.split(self.original_file_d)[1:], mod_file + ) + ) # (stolen from helpers.PstFromFlopyModel()._pp_prep()) # but only settting up one set of pps at a time pp_dict = {0: par_name_base} @@ -1292,29 +1548,41 @@ def add_parameters(self, filenames, par_type, zone_array=None, if geostruct is None: # need a geostruct for pilotpoints # can use model default, if provided if self.geostruct is None: # but if no geostruct passed... - self.logger.warn("pp_geostruct is None," - "using ExpVario with contribution=1 " - "and a=(pp_space*max(delr,delc))") + self.logger.warn( + "pp_geostruct is None," + "using ExpVario with contribution=1 " + "and a=(pp_space*max(delr,delc))" + ) # set up a default pp_dist = pp_space * float( - max(spatial_reference.delr.max(), - spatial_reference.delc.max())) + max( + spatial_reference.delr.max(), + spatial_reference.delc.max(), + ) + ) v = pyemu.geostats.ExpVario(contribution=1.0, a=pp_dist) pp_geostruct = pyemu.geostats.GeoStruct( - variograms=v, name="pp_geostruct", - transform=transform) + variograms=v, name="pp_geostruct", transform=transform + ) else: pp_geostruct = self.geostruct if pp_geostruct.transform != transform: - self.logger.warn("0) Inconsistency between " - "pp_geostruct transform and " - "partrans.") - self.logger.warn("1) Setting pp_geostruct transform " - "to {0}".format(transform)) - self.logger.warn("2) This will create a new copy of " - "pp_geostruct") - self.logger.warn("3) Better to pass an appropriately " - "transformed geostruct") + self.logger.warn( + "0) Inconsistency between " + "pp_geostruct transform and " + "partrans." + ) + self.logger.warn( + "1) Setting pp_geostruct transform " + "to {0}".format(transform) + ) + self.logger.warn( + "2) This will create a new copy of " "pp_geostruct" + ) + self.logger.warn( + "3) Better to pass an appropriately " + "transformed geostruct" + ) pp_geostruct = copy.copy(pp_geostruct) pp_geostruct.transform = transform else: @@ -1329,17 +1597,19 @@ def add_parameters(self, filenames, par_type, zone_array=None, pp_dir=self.new_d, tpl_dir=self.new_d, shapename=os.path.join( - self.new_d, "{0}.shp".format(par_name_store)), - longnames=self.longnames) - df.set_index('parnme', drop=False, inplace=True) + self.new_d, "{0}.shp".format(par_name_store) + ), + longnames=self.longnames, + ) + df.set_index("parnme", drop=False, inplace=True) # df includes most of the par info for par_dfs and also for # relate_parfiles - self.logger.statement("{0} pilot point parameters created". - format(df.shape[0])) + self.logger.statement( + "{0} pilot point parameters created".format(df.shape[0]) + ) # should be only one group at a time pargp = df.pargp.unique() - self.logger.statement("pilot point 'pargp':{0}". - format(','.join(pargp))) + self.logger.statement("pilot point 'pargp':{0}".format(",".join(pargp))) self.logger.log("setting up pilot point parameters") # Calculating pp factors @@ -1349,56 +1619,64 @@ def add_parameters(self, filenames, par_type, zone_array=None, # build krige reference information on the fly - used to help # prevent unnecessary krig factor calculation pp_info_dict = { - 'pp_data': ok_pp.point_data.loc[:, ['x', 'y', 'zone']], - 'cov': ok_pp.point_cov_df, - 'zn_ar': zone_array} + "pp_data": ok_pp.point_data.loc[:, ["x", "y", "zone"]], + "cov": ok_pp.point_cov_df, + "zn_ar": zone_array, + } fac_processed = False for facfile, info in self._pp_facs.items(): # check against # factors already calculated - if (info['pp_data'].equals(pp_info_dict['pp_data']) and - info['cov'].equals(pp_info_dict['cov']) and - np.array_equal(info['zn_ar'], - pp_info_dict['zn_ar'])): + if ( + info["pp_data"].equals(pp_info_dict["pp_data"]) + and info["cov"].equals(pp_info_dict["cov"]) + and np.array_equal(info["zn_ar"], pp_info_dict["zn_ar"]) + ): fac_processed = True # don't need to re-calc same factors fac_filename = facfile # relate to existing fac file break if not fac_processed: # TODO need better way of naming squential fac_files? - self.logger.log( - "calculating factors for pargp={0}".format(pg)) + self.logger.log("calculating factors for pargp={0}".format(pg)) fac_filename = os.path.join( - self.new_d, "{0}pp.fac".format(par_name_store)) + self.new_d, "{0}pp.fac".format(par_name_store) + ) var_filename = fac_filename.replace(".fac", ".var.dat") self.logger.statement( - "saving krige variance file:{0}".format(var_filename)) + "saving krige variance file:{0}".format(var_filename) + ) self.logger.statement( - "saving krige factors file:{0}".format(fac_filename)) + "saving krige factors file:{0}".format(fac_filename) + ) # store info on pilotpoints self._pp_facs[fac_filename] = pp_info_dict # this is slow (esp on windows) so only want to do this # when required - ok_pp.calc_factors_grid(spatial_reference, - var_filename=var_filename, - zone_array=zone_array, - num_threads=10) + ok_pp.calc_factors_grid( + spatial_reference, + var_filename=var_filename, + zone_array=zone_array, + num_threads=10, + ) ok_pp.to_grid_factors_file(fac_filename) - self.logger.log( - "calculating factors for pargp={0}".format(pg)) + self.logger.log("calculating factors for pargp={0}".format(pg)) # TODO - other par types - JTW? elif par_type == "kl": self.logger.lraise("array type 'kl' not implemented") else: - self.logger.lraise("unrecognized 'par_type': '{0}', " - "should be in " - "['constant','zone','grid','pilotpoints'," - "'kl'") - self.logger.log("writing array-based template file " - "'{0}'".format(tpl_filename)) + self.logger.lraise( + "unrecognized 'par_type': '{0}', " + "should be in " + "['constant','zone','grid','pilotpoints'," + "'kl'" + ) + self.logger.log( + "writing array-based template file " "'{0}'".format(tpl_filename) + ) if datetime is not None: # add time info to par_dfs - df['datetime'] = datetime - df['timedelta'] = t_offest + df["datetime"] = datetime + df["timedelta"] = t_offest # accumulate information that relates mult_files (set-up here and # eventually filled by PEST) to actual model files so that actual # model input file can be generated @@ -1407,8 +1685,8 @@ def add_parameters(self, filenames, par_type, zone_array=None, for mod_file in file_dict.keys(): mult_dict = { "org_file": os.path.join( - *os.path.split(self.original_file_d)[1:], mod_file), - + *os.path.split(self.original_file_d)[1:], mod_file + ), "model_file": mod_file, "use_cols": use_cols, "index_cols": index_cols, @@ -1416,18 +1694,18 @@ def add_parameters(self, filenames, par_type, zone_array=None, "sep": sep_dict[mod_file], "head_rows": skip_dict[mod_file], "upper_bound": ult_ubound, - "lower_bound": ult_lbound} + "lower_bound": ult_lbound, + } if par_style == "multiplier": - mult_dict["mlt_file"] = os.path.join( - *os.path.split(self.mult_file_d)[1:], mlt_filename) + mult_dict["mlt_file"] = os.path.join( + *os.path.split(self.mult_file_d)[1:], mlt_filename + ) if pp_filename is not None: # if pilotpoint need to store more info - assert fac_filename is not None, ( - "missing pilot-point input filename") - mult_dict["fac_file"] = os.path.relpath(fac_filename, - self.new_d) - mult_dict['pp_file'] = pp_filename + assert fac_filename is not None, "missing pilot-point input filename" + mult_dict["fac_file"] = os.path.relpath(fac_filename, self.new_d) + mult_dict["pp_file"] = pp_filename relate_parfiles.append(mult_dict) relate_pars_df = pd.DataFrame(relate_parfiles) # store on self for use in pest build etc @@ -1452,7 +1730,7 @@ def add_parameters(self, filenames, par_type, zone_array=None, # - BH: think we can get away with dropping duplicates? missing = set(par_data_cols) - set(df.columns) for field in missing: # fill missing pst.parameter_data cols with defaults - df[field] = pyemu.pst_utils.pst_config['par_defaults'][field] + df[field] = pyemu.pst_utils.pst_config["par_defaults"][field] df = df.drop_duplicates() # drop pars that appear multiple times # df = df.loc[:, par_data_cols] # just storing pst required cols # - need to store more for cov builder (e.g. x,y) @@ -1465,10 +1743,10 @@ def add_parameters(self, filenames, par_type, zone_array=None, # TODO maybe use different marker to denote a relationship between pars # at the moment relating pars using common geostruct and pargp but may # want to reserve pargp for just PEST - if 'covgp' not in df.columns: - gp_dict = {g: [d] for g, d in df.groupby('pargp')} + if "covgp" not in df.columns: + gp_dict = {g: [d] for g, d in df.groupby("pargp")} else: - gp_dict = {g: [d] for g, d in df.groupby('covgp')} + gp_dict = {g: [d] for g, d in df.groupby("covgp")} # df_list = [d for g, d in df.groupby('pargp')] if geostruct is not None: # relating pars to geostruct.... @@ -1491,7 +1769,7 @@ def add_parameters(self, filenames, par_type, zone_array=None, self.par_struct_dict[geostruct][gp].extend(gppars) # self.par_struct_dict_l[geostruct].extend(list(gp_dict.values())) else: # TODO some rules for if geostruct is not passed.... - if 'x' in df.columns: + if "x" in df.columns: pass # TODO warn that it looks like spatial pars but no geostruct? # if self.geostruct is not None: @@ -1516,21 +1794,23 @@ def add_parameters(self, filenames, par_type, zone_array=None, self.logger.log( "adding {0} type {1} style parameters for file(s) {2}" - "".format(par_type, par_style, str(filenames))) + "".format(par_type, par_style, str(filenames)) + ) if rebuild_pst: # may want to just update pst and rebuild # (with new relations) if self.pst is not None: - self.logger.log("Adding pars to control file " - "and rewriting pst") - self.build_pst(filename=self.pst.filename, update='pars') + self.logger.log("Adding pars to control file " "and rewriting pst") + self.build_pst(filename=self.pst.filename, update="pars") else: self.build_pst(filename=self.pst.filename, update=False) - self.logger.warn("pst object not available, " - "new control file will be written") + self.logger.warn( + "pst object not available, " "new control file will be written" + ) - def _load_listtype_file(self, filename, index_cols, use_cols, - fmt=None, sep=None, skip=None, c_char=None): + def _load_listtype_file( + self, filename, index_cols, use_cols, fmt=None, sep=None, skip=None, c_char=None + ): if isinstance(filename, list): assert len(filename) == 1 filename = filename[0] @@ -1550,71 +1830,78 @@ def _load_listtype_file(self, filename, index_cols, use_cols, # index_cols are column numbers in input file header = None else: - self.logger.lraise("unrecognized type for index_cols or use_cols " - "should be str or int and both should be of the " - "same type, not {0} or {1}". - format(str(type(index_cols[0])), - str(type(use_cols[0])))) + self.logger.lraise( + "unrecognized type for index_cols or use_cols " + "should be str or int and both should be of the " + "same type, not {0} or {1}".format( + str(type(index_cols[0])), str(type(use_cols[0])) + ) + ) itype = type(index_cols) utype = type(use_cols) if itype != utype: - self.logger.lraise("index_cols type '{0} != use_cols " - "type '{1}'". - format(str(itype), str(utype))) + self.logger.lraise( + "index_cols type '{0} != use_cols " + "type '{1}'".format(str(itype), str(utype)) + ) si = set(index_cols) su = set(use_cols) i = si.intersection(su) if len(i) > 0: - self.logger.lraise("use_cols also listed in " - "index_cols: {0}".format(str(i))) + self.logger.lraise( + "use_cols also listed in " "index_cols: {0}".format(str(i)) + ) file_path = os.path.join(self.new_d, filename) if not os.path.exists(file_path): - self.logger.lraise("par filename '{0}' not found " - "".format(file_path)) + self.logger.lraise("par filename '{0}' not found " "".format(file_path)) self.logger.log("reading list {0}".format(file_path)) - if fmt.lower() == 'free': + if fmt.lower() == "free": if sep is None: sep = "\s+" if filename.lower().endswith(".csv"): - sep = ',' + sep = "," else: # TODO support reading fixed-format # (based on value of fmt passed) # ... or not? - self.logger.warn("0) Only reading free format list par " - "files currently supported.") - self.logger.warn("1) Assuming safe to read as whitespace " - "delim.") - self.logger.warn("2) Desired format string will still " - "be passed through") - sep = '\s+' + self.logger.warn( + "0) Only reading free format list par " "files currently supported." + ) + self.logger.warn("1) Assuming safe to read as whitespace " "delim.") + self.logger.warn("2) Desired format string will still " "be passed through") + sep = "\s+" try: # read each input file if skip > 0 or c_char is not None: if c_char is None: - with open(file_path, 'r') as fp: - storehead = {lp: line - for lp, line in enumerate(fp) if lp < skip} + with open(file_path, "r") as fp: + storehead = { + lp: line for lp, line in enumerate(fp) if lp < skip + } else: - with open(file_path, 'r') as fp: - storehead = {lp: line - for lp, line in enumerate(fp) - if lp < skip - or line.strip().startswith(c_char)} + with open(file_path, "r") as fp: + storehead = { + lp: line + for lp, line in enumerate(fp) + if lp < skip or line.strip().startswith(c_char) + } else: storehead = {} except TypeError: c_char = skip skip = None - with open(file_path, 'r') as fp: - storehead = {lp: line - for lp, line in enumerate(fp) - if line.strip().startswith(c_char)} - df = pd.read_csv(file_path, comment=c_char, sep=sep, skiprows=skip, - header=header) + with open(file_path, "r") as fp: + storehead = { + lp: line + for lp, line in enumerate(fp) + if line.strip().startswith(c_char) + } + df = pd.read_csv( + file_path, comment=c_char, sep=sep, skiprows=skip, header=header + ) self.logger.log("reading list {0}".format(file_path)) # ensure that column ids from index_col is in input file missing = [] @@ -1623,22 +1910,33 @@ def _load_listtype_file(self, filename, index_cols, use_cols, missing.append(index_col) # df.loc[:, index_col] = df.loc[:, index_col].astype(np.int) # TODO int? why? if len(missing) > 0: - self.logger.lraise("the following index_cols were not " - "found in file '{0}':{1}" - "".format(file_path, str(missing))) + self.logger.lraise( + "the following index_cols were not " + "found in file '{0}':{1}" + "".format(file_path, str(missing)) + ) # ensure requested use_cols are in input file for use_col in use_cols: if use_col not in df.columns: missing.append(use_cols) if len(missing) > 0: - self.logger.lraise("the following use_cols were not found " - "in file '{0}':{1}" - "".format(file_path, str(missing))) + self.logger.lraise( + "the following use_cols were not found " + "in file '{0}':{1}" + "".format(file_path, str(missing)) + ) return df, storehead - def _prep_arg_list_lengths(self, filenames, fmts=None, seps=None, - skip_rows=None, index_cols=None, use_cols=None): + def _prep_arg_list_lengths( + self, + filenames, + fmts=None, + seps=None, + skip_rows=None, + index_cols=None, + use_cols=None, + ): """ Private wrapper function to align filenames, formats, delimiters, reading options and setup columns for passing sequentially to @@ -1664,42 +1962,48 @@ def _prep_arg_list_lengths(self, filenames, fmts=None, seps=None, if not isinstance(filenames, list): filenames = [filenames] if fmts is None: - fmts = ['free' for _ in filenames] + fmts = ["free" for _ in filenames] if not isinstance(fmts, list): fmts = [fmts] if len(fmts) != len(filenames): - self.logger.warn("Discrepancy between number of filenames ({0}) " - "and number of formatter strings ({1}). " - "Will repeat first ({2})" - "".format(len(filenames), len(fmts), fmts[0])) + self.logger.warn( + "Discrepancy between number of filenames ({0}) " + "and number of formatter strings ({1}). " + "Will repeat first ({2})" + "".format(len(filenames), len(fmts), fmts[0]) + ) fmts = [fmts[0] for _ in filenames] - fmts = ['free' if fmt is None else fmt for fmt in fmts] + fmts = ["free" if fmt is None else fmt for fmt in fmts] if seps is None: seps = [None for _ in filenames] if not isinstance(seps, list): seps = [seps] if len(seps) != len(filenames): - self.logger.warn("Discrepancy between number of filenames ({0}) " - "and number of seps defined ({1}). " - "Will repeat first ({2})" - "".format(len(filenames), len(seps), seps[0])) + self.logger.warn( + "Discrepancy between number of filenames ({0}) " + "and number of seps defined ({1}). " + "Will repeat first ({2})" + "".format(len(filenames), len(seps), seps[0]) + ) seps = [seps[0] for _ in filenames] if skip_rows is None: skip_rows = [None for _ in filenames] if not isinstance(skip_rows, list): skip_rows = [skip_rows] if len(skip_rows) != len(filenames): - self.logger.warn("Discrepancy between number of filenames ({0}) " - "and number of skip_rows defined ({1}). " - "Will repeat first ({2})" - "".format(len(filenames), len(skip_rows), - skip_rows[0])) + self.logger.warn( + "Discrepancy between number of filenames ({0}) " + "and number of skip_rows defined ({1}). " + "Will repeat first ({2})" + "".format(len(filenames), len(skip_rows), skip_rows[0]) + ) skip_rows = [skip_rows[0] for _ in filenames] skip_rows = [0 if s is None else s for s in skip_rows] if index_cols is None and use_cols is not None: - self.logger.lraise("index_cols is None, but use_cols is not ({0})" - "".format(str(use_cols))) + self.logger.lraise( + "index_cols is None, but use_cols is not ({0})" "".format(str(use_cols)) + ) if index_cols is not None: if not isinstance(index_cols, list): @@ -1709,10 +2013,25 @@ def _prep_arg_list_lengths(self, filenames, fmts=None, seps=None, return filenames, fmts, seps, skip_rows, index_cols, use_cols -def write_list_tpl(filenames, dfs, name, tpl_filename, index_cols, par_type, - use_cols=None, suffix='', zone_array=None, gpname=None, - longnames=False, get_xy=None, ij_in_idx=None, xy_in_idx=None, - zero_based=True,input_filename=None,par_style="multiplier"): +def write_list_tpl( + filenames, + dfs, + name, + tpl_filename, + index_cols, + par_type, + use_cols=None, + suffix="", + zone_array=None, + gpname=None, + longnames=False, + get_xy=None, + ij_in_idx=None, + xy_in_idx=None, + zero_based=True, + input_filename=None, + par_style="multiplier", +): """ Write template files for a list style input. Args: @@ -1758,24 +2077,46 @@ def write_list_tpl(filenames, dfs, name, tpl_filename, index_cols, par_type, # get dataframe with autogenerated parnames based on `name`, `index_cols`, # `use_cols`, `suffix` and `par_type` if par_style == "direct": - df_tpl = _write_direct_df_tpl(filenames[0],tpl_filename,dfs[0],name,index_cols,par_type, - use_cols=use_cols,suffix=suffix,gpname=gpname, - zone_array=zone_array,longnames=longnames, - get_xy=get_xy,ij_in_idx=ij_in_idx, - xy_in_idx=xy_in_idx,zero_based=zero_based) + df_tpl = _write_direct_df_tpl( + filenames[0], + tpl_filename, + dfs[0], + name, + index_cols, + par_type, + use_cols=use_cols, + suffix=suffix, + gpname=gpname, + zone_array=zone_array, + longnames=longnames, + get_xy=get_xy, + ij_in_idx=ij_in_idx, + xy_in_idx=xy_in_idx, + zero_based=zero_based, + ) else: - df_tpl = _get_tpl_or_ins_df(filenames, dfs, name, index_cols, par_type, - use_cols=use_cols, suffix=suffix, gpname=gpname, - zone_array=zone_array, longnames=longnames, - get_xy=get_xy, ij_in_idx=ij_in_idx, - xy_in_idx=xy_in_idx, zero_based=zero_based) + df_tpl = _get_tpl_or_ins_df( + filenames, + dfs, + name, + index_cols, + par_type, + use_cols=use_cols, + suffix=suffix, + gpname=gpname, + zone_array=zone_array, + longnames=longnames, + get_xy=get_xy, + ij_in_idx=ij_in_idx, + xy_in_idx=xy_in_idx, + zero_based=zero_based, + ) for col in use_cols: # corellations flagged using pargp - df_tpl["covgp{0}".format(col)] = df_tpl.loc[:, - "pargp{0}".format(col)].values + df_tpl["covgp{0}".format(col)] = df_tpl.loc[:, "pargp{0}".format(col)].values # needs modifying if colocated pars in same group - if par_type == 'grid' and 'x' in df_tpl.columns: - if df_tpl.duplicated(['x', 'y']).any(): + if par_type == "grid" and "x" in df_tpl.columns: + if df_tpl.duplicated(["x", "y"]).any(): # may need to use a different grouping for parameter correlations # where parameter x and y values are the same but pars are not # correlated (e.g. 2d correlation but different layers) @@ -1792,58 +2133,68 @@ def write_list_tpl(filenames, dfs, name, tpl_filename, index_cols, par_type, # then parse_kij assumes that i is at idx[-2] and j at idx[-1] third_d.pop() # pops -1 third_d.pop() # pops -2 - PyemuWarning("Coincidently located pars in list-like file, " - "attempting to separate pars based on `index_cols` " - "passed - using index_col[{0}] for third dimension" - "".format(third_d[-1])) + PyemuWarning( + "Coincidently located pars in list-like file, " + "attempting to separate pars based on `index_cols` " + "passed - using index_col[{0}] for third dimension" + "".format(third_d[-1]) + ) for col in use_cols: - df_tpl["covgp{0}".format(col) - ] = df_tpl.loc[:, "covgp{0}".format(col) - ].str.cat( - pd.DataFrame( - df_tpl.sidx.to_list()).iloc[:, 0].astype(str), - '_cov') + df_tpl["covgp{0}".format(col)] = df_tpl.loc[ + :, "covgp{0}".format(col) + ].str.cat( + pd.DataFrame(df_tpl.sidx.to_list()).iloc[:, 0].astype(str), + "_cov", + ) else: - PyemuWarning("Coincidently located pars in list-like file. " - "Likely to cause issues building par cov or " - "drawing par ensemble. Can be resolved by passing " - "an additional `index_col` as a basis for " - "splitting colocated correlations (e.g. Layer)") + PyemuWarning( + "Coincidently located pars in list-like file. " + "Likely to cause issues building par cov or " + "drawing par ensemble. Can be resolved by passing " + "an additional `index_col` as a basis for " + "splitting colocated correlations (e.g. Layer)" + ) # pull out par details where multiple `use_cols` are requested parnme = list(df_tpl.loc[:, use_cols].values.flatten()) pargp = list( - df_tpl.loc[:, ["pargp{0}".format(col) - for col in use_cols]].values.flatten()) + df_tpl.loc[:, ["pargp{0}".format(col) for col in use_cols]].values.flatten() + ) covgp = list( - df_tpl.loc[:, ["covgp{0}".format(col) - for col in use_cols]].values.flatten()) - df_tpl = df_tpl.drop([ - col for col in df_tpl.columns if str(col).startswith('covgp')], axis=1) - df_par = pd.DataFrame({"parnme": parnme, "pargp": pargp, 'covgp': covgp}, - index=parnme) + df_tpl.loc[:, ["covgp{0}".format(col) for col in use_cols]].values.flatten() + ) + df_tpl = df_tpl.drop( + [col for col in df_tpl.columns if str(col).startswith("covgp")], axis=1 + ) + df_par = pd.DataFrame( + {"parnme": parnme, "pargp": pargp, "covgp": covgp}, index=parnme + ) parval_cols = [c for c in df_tpl.columns if "parval1" in str(c)] - parval = list( - df_tpl.loc[:, [pc for pc in parval_cols]].values.flatten()) - - if par_type == 'grid' and 'x' in df_tpl.columns: # TODO work out if x,y needed for constant and zone pars too - df_par['x'], df_par['y'] = np.concatenate( - df_tpl.apply(lambda r: [[r.x, r.y] for _ in use_cols], - axis=1).values).T + parval = list(df_tpl.loc[:, [pc for pc in parval_cols]].values.flatten()) + + if ( + par_type == "grid" and "x" in df_tpl.columns + ): # TODO work out if x,y needed for constant and zone pars too + df_par["x"], df_par["y"] = np.concatenate( + df_tpl.apply(lambda r: [[r.x, r.y] for _ in use_cols], axis=1).values + ).T if not longnames: - too_long = df_par.loc[df_par.parnme.apply(lambda x: len(x) > 12), - "parnme"] + too_long = df_par.loc[df_par.parnme.apply(lambda x: len(x) > 12), "parnme"] if too_long.shape[0] > 0: - raise Exception("write_list_tpl() error: the following parameter " - "names are too long:{0}" - "".format(','.join(list(too_long)))) + raise Exception( + "write_list_tpl() error: the following parameter " + "names are too long:{0}" + "".format(",".join(list(too_long))) + ) for use_col in use_cols: df_tpl.loc[:, use_col] = df_tpl.loc[:, use_col].apply( - lambda x: "~ {0} ~".format(x)) + lambda x: "~ {0} ~".format(x) + ) if par_style == "multiplier": - pyemu.helpers._write_df_tpl(filename=tpl_filename, df=df_tpl, sep=',', - tpl_marker='~') + pyemu.helpers._write_df_tpl( + filename=tpl_filename, df=df_tpl, sep=",", tpl_marker="~" + ) if input_filename is not None: df_in = df_tpl.copy() @@ -1851,13 +2202,27 @@ def write_list_tpl(filenames, dfs, name, tpl_filename, index_cols, par_type, df_in.to_csv(input_filename) df_par.loc[:, "tpl_filename"] = tpl_filename df_par.loc[:, "input_filename"] = input_filename - df_par.loc[:,"parval1"] = parval + df_par.loc[:, "parval1"] = parval return df_par -def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_cols=None, - suffix='',zone_array=None,longnames=False,get_xy=None, - ij_in_idx=None,xy_in_idx=None,zero_based=True,gpname=None): +def _write_direct_df_tpl( + in_filename, + tpl_filename, + df, + name, + index_cols, + typ, + use_cols=None, + suffix="", + zone_array=None, + longnames=False, + get_xy=None, + ij_in_idx=None, + xy_in_idx=None, + zero_based=True, + gpname=None, +): """ Private method to auto-generate parameter or obs names from tabular @@ -1897,14 +2262,13 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co sidx = set() - didx = set(df.loc[:, index_cols].apply( - lambda x: tuple(x), axis=1)) + didx = set(df.loc[:, index_cols].apply(lambda x: tuple(x), axis=1)) sidx.update(didx) df_ti = pd.DataFrame({"sidx": list(sidx)}, columns=["sidx"]) # get some index strings for naming if longnames: - j = '_' + j = "_" fmt = "{0}|{1}" if isinstance(index_cols[0], str): inames = index_cols @@ -1912,18 +2276,17 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co inames = ["idx{0}".format(i) for i in range(len(index_cols))] else: fmt = "{1|3}" - j = '' + j = "" if isinstance(index_cols[0], str): inames = index_cols else: inames = ["{0}".format(i) for i in range(len(index_cols))] if not zero_based: - df_ti.loc[:, "sidx"] = df_ti.sidx.apply( - lambda x: tuple(xx - 1 for xx in x)) + df_ti.loc[:, "sidx"] = df_ti.sidx.apply(lambda x: tuple(xx - 1 for xx in x)) df_ti.loc[:, "idx_strs"] = df_ti.sidx.apply( - lambda x: j.join([fmt.format(iname, xx) - for xx, iname in zip(x, inames)])).str.replace(' ', '') + lambda x: j.join([fmt.format(iname, xx) for xx, iname in zip(x, inames)]) + ).str.replace(" ", "") df_ti.loc[:, "idx_strs"] = df_ti.idx_strs.str.replace(":", "") df_ti.loc[:, "idx_strs"] = df_ti.idx_strs.str.replace("|", ":") @@ -1931,18 +2294,15 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co if get_xy is not None: if xy_in_idx is not None: # x and y already in index cols - df_ti[['x', 'y']] = pd.DataFrame( - df_ti.sidx.to_list()).iloc[:, xy_in_idx] + df_ti[["x", "y"]] = pd.DataFrame(df_ti.sidx.to_list()).iloc[:, xy_in_idx] else: - df_ti.loc[:, 'xy'] = df_ti.sidx.apply(get_xy, ij_id=ij_in_idx) - df_ti.loc[:, 'x'] = df_ti.xy.apply(lambda x: x[0]) - df_ti.loc[:, 'y'] = df_ti.xy.apply(lambda x: x[1]) - + df_ti.loc[:, "xy"] = df_ti.sidx.apply(get_xy, ij_id=ij_in_idx) + df_ti.loc[:, "x"] = df_ti.xy.apply(lambda x: x[0]) + df_ti.loc[:, "y"] = df_ti.xy.apply(lambda x: x[1]) if use_cols is None: use_cols = [c for c in df_ti.columns if c not in index_cols] - direct_tpl_df = df.copy() for iuc, use_col in enumerate(use_cols): if not isinstance(name, str): @@ -1952,14 +2312,13 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co nname = name if zone_array is not None and typ in ["zone", "grid"]: if zone_array.ndim != len(index_cols): - raise Exception("get_tpl_or_ins_df() error: " - "zone_array.ndim " - "({0}) != len(index_cols)({1})" - "".format(zone_array.ndim, - len(index_cols))) - df_ti.loc[:, "zval"] = df_ti.sidx.apply( - lambda x: zone_array[x]) - + raise Exception( + "get_tpl_or_ins_df() error: " + "zone_array.ndim " + "({0}) != len(index_cols)({1})" + "".format(zone_array.ndim, len(index_cols)) + ) + df_ti.loc[:, "zval"] = df_ti.sidx.apply(lambda x: zone_array[x]) if gpname is None or gpname[iuc] is None: ngpname = nname @@ -1973,12 +2332,13 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co # one par for entire use_col column if longnames: df_ti.loc[:, use_col] = "{0}_usecol:{1}_{2}".format( - nname, use_col, "direct") - if suffix != '': + nname, use_col, "direct" + ) + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix _check_diff(df.loc[:, use_col].values, in_filename) @@ -1988,56 +2348,79 @@ def _write_direct_df_tpl(in_filename, tpl_filename,df,name,index_cols,typ,use_co # one par for each zone if longnames: df_ti.loc[:, use_col] = "{0}_usecol:{1}_{2}".format( - nname, use_col, "direct") + nname, use_col, "direct" + ) if zone_array is not None: df_ti.loc[:, use_col] += df_ti.zval.apply( - lambda x: "_zone:{0}".format(x)) - if suffix != '': + lambda x: "_zone:{0}".format(x) + ) + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix - #todo check that values are constant within zones and - #assign parval1 - raise NotImplementedError("list-based direct zone-type parameters not implemented") + # todo check that values are constant within zones and + # assign parval1 + raise NotImplementedError( + "list-based direct zone-type parameters not implemented" + ) elif typ == "grid": # one par for each index if longnames: df_ti.loc[:, use_col] = "{0}_usecol:{1}_{2}".format( - nname, use_col, "direct") + nname, use_col, "direct" + ) if zone_array is not None: df_ti.loc[:, use_col] += df_ti.zval.apply( - lambda x: "_zone:{0}".format(x)) - df_ti.loc[:, use_col] += '_' + df_ti.idx_strs - if suffix != '': + lambda x: "_zone:{0}".format(x) + ) + df_ti.loc[:, use_col] += "_" + df_ti.idx_strs + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) df_ti.loc[:, use_col] += df_ti.idx_strs - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix df_ti.loc[:, "parval1_{0}".format(use_col)] = df.loc[:, use_col].values else: - raise Exception("get_tpl_or_ins_df() error: " - "unrecognized 'typ', if not 'obs', " - "should be 'constant','zone', " - "or 'grid', not '{0}'".format(typ)) - direct_tpl_df.loc[:, use_col] = df_ti.loc[:, use_col].apply(lambda x: "~ {0} ~".format(x)).values - - pyemu.helpers._write_df_tpl(tpl_filename,direct_tpl_df,index=False,header=False) + raise Exception( + "get_tpl_or_ins_df() error: " + "unrecognized 'typ', if not 'obs', " + "should be 'constant','zone', " + "or 'grid', not '{0}'".format(typ) + ) + direct_tpl_df.loc[:, use_col] = ( + df_ti.loc[:, use_col].apply(lambda x: "~ {0} ~".format(x)).values + ) + + pyemu.helpers._write_df_tpl(tpl_filename, direct_tpl_df, index=False, header=False) return df_ti -def _get_tpl_or_ins_df(filenames, dfs, name, index_cols, typ, use_cols=None, - suffix='', zone_array=None, longnames=False, get_xy=None, - ij_in_idx=None, xy_in_idx=None, zero_based=True, - gpname=None): + +def _get_tpl_or_ins_df( + filenames, + dfs, + name, + index_cols, + typ, + use_cols=None, + suffix="", + zone_array=None, + longnames=False, + get_xy=None, + ij_in_idx=None, + xy_in_idx=None, + zero_based=True, + gpname=None, +): """ Private method to auto-generate parameter or obs names from tabular model files (input or output) read into pandas dataframes @@ -2085,25 +2468,23 @@ def _get_tpl_or_ins_df(filenames, dfs, name, index_cols, typ, use_cols=None, dfs = list(dfs) # work out the union of indices across all dfs - if typ != 'obs': + if typ != "obs": sidx = set() for df in dfs: - didx = set(df.loc[:, index_cols].apply( - lambda x: tuple(x), axis=1)) + didx = set(df.loc[:, index_cols].apply(lambda x: tuple(x), axis=1)) sidx.update(didx) else: # order matters for obs sidx = [] for df in dfs: - didx = df.loc[:, index_cols].apply( - lambda x: tuple(x), axis=1).values + didx = df.loc[:, index_cols].apply(lambda x: tuple(x), axis=1).values aidx = [i for i in didx if i not in sidx] sidx.extend(aidx) df_ti = pd.DataFrame({"sidx": list(sidx)}, columns=["sidx"]) # get some index strings for naming if longnames: - j = '_' + j = "_" fmt = "{0}|{1}" if isinstance(index_cols[0], str): inames = index_cols @@ -2111,33 +2492,31 @@ def _get_tpl_or_ins_df(filenames, dfs, name, index_cols, typ, use_cols=None, inames = ["idx{0}".format(i) for i in range(len(index_cols))] else: fmt = "{1:3}" - j = '' + j = "" if isinstance(index_cols[0], str): inames = index_cols else: inames = ["{0}".format(i) for i in range(len(index_cols))] if not zero_based: - df_ti.loc[:, "sidx"] = df_ti.sidx.apply( - lambda x: tuple(xx - 1 for xx in x)) + df_ti.loc[:, "sidx"] = df_ti.sidx.apply(lambda x: tuple(xx - 1 for xx in x)) df_ti.loc[:, "idx_strs"] = df_ti.sidx.apply( - lambda x: j.join([fmt.format(iname, xx) - for xx, iname in zip(x, inames)])).str.replace(' ', '') - df_ti.loc[:,"idx_strs"] = df_ti.idx_strs.str.replace(":","") + lambda x: j.join([fmt.format(iname, xx) for xx, iname in zip(x, inames)]) + ).str.replace(" ", "") + df_ti.loc[:, "idx_strs"] = df_ti.idx_strs.str.replace(":", "") df_ti.loc[:, "idx_strs"] = df_ti.idx_strs.str.replace("|", ":") if get_xy is not None: if xy_in_idx is not None: # x and y already in index cols - df_ti[['x', 'y']] = pd.DataFrame( - df_ti.sidx.to_list()).iloc[:, xy_in_idx] + df_ti[["x", "y"]] = pd.DataFrame(df_ti.sidx.to_list()).iloc[:, xy_in_idx] else: - df_ti.loc[:, 'xy'] = df_ti.sidx.apply(get_xy, ij_id=ij_in_idx) - df_ti.loc[:, 'x'] = df_ti.xy.apply(lambda x: x[0]) - df_ti.loc[:, 'y'] = df_ti.xy.apply(lambda x: x[1]) + df_ti.loc[:, "xy"] = df_ti.sidx.apply(get_xy, ij_id=ij_in_idx) + df_ti.loc[:, "x"] = df_ti.xy.apply(lambda x: x[0]) + df_ti.loc[:, "y"] = df_ti.xy.apply(lambda x: x[1]) - if typ == 'obs': + if typ == "obs": return df_ti #################### RETURN if OBS if use_cols is None: @@ -2152,13 +2531,13 @@ def _get_tpl_or_ins_df(filenames, dfs, name, index_cols, typ, use_cols=None, nname = name if zone_array is not None and typ in ["zone", "grid"]: if zone_array.ndim != len(index_cols): - raise Exception("get_tpl_or_ins_df() error: " - "zone_array.ndim " - "({0}) != len(index_cols)({1})" - "".format(zone_array.ndim, - len(index_cols))) - df_ti.loc[:, "zval"] = df_ti.sidx.apply( - lambda x: zone_array[x]) + raise Exception( + "get_tpl_or_ins_df() error: " + "zone_array.ndim " + "({0}) != len(index_cols)({1})" + "".format(zone_array.ndim, len(index_cols)) + ) + df_ti.loc[:, "zval"] = df_ti.sidx.apply(lambda x: zone_array[x]) if gpname is None or gpname[iuc] is None: ngpname = nname @@ -2172,59 +2551,71 @@ def _get_tpl_or_ins_df(filenames, dfs, name, index_cols, typ, use_cols=None, if typ == "constant": # one par for entire use_col column if longnames: - df_ti.loc[:, use_col] = "{0}_usecol:{1}".format( - nname, use_col) - if suffix != '': + df_ti.loc[:, use_col] = "{0}_usecol:{1}".format(nname, use_col) + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix elif typ == "zone": # one par for each zone if longnames: - df_ti.loc[:, use_col] = "{0}_usecol:{1}".format( - nname, use_col) + df_ti.loc[:, use_col] = "{0}_usecol:{1}".format(nname, use_col) if zone_array is not None: df_ti.loc[:, use_col] += df_ti.zval.apply( - lambda x: "_zone:{0}".format(x)) - if suffix != '': + lambda x: "_zone:{0}".format(x) + ) + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix elif typ == "grid": # one par for each index if longnames: - df_ti.loc[:, use_col] = "{0}_usecol:{1}".format( - nname, use_col) + df_ti.loc[:, use_col] = "{0}_usecol:{1}".format(nname, use_col) if zone_array is not None: df_ti.loc[:, use_col] += df_ti.zval.apply( - lambda x: "_zone:{0}".format(x)) - df_ti.loc[:, use_col] += '_' + df_ti.idx_strs - if suffix != '': + lambda x: "_zone:{0}".format(x) + ) + df_ti.loc[:, use_col] += "_" + df_ti.idx_strs + if suffix != "": df_ti.loc[:, use_col] += "_{0}".format(suffix) else: df_ti.loc[:, use_col] = "{0}{1}".format(nname, use_col) df_ti.loc[:, use_col] += df_ti.idx_strs - if suffix != '': + if suffix != "": df_ti.loc[:, use_col] += suffix else: - raise Exception("get_tpl_or_ins_df() error: " - "unrecognized 'typ', if not 'obs', " - "should be 'constant','zone', " - "or 'grid', not '{0}'".format(typ)) + raise Exception( + "get_tpl_or_ins_df() error: " + "unrecognized 'typ', if not 'obs', " + "should be 'constant','zone', " + "or 'grid', not '{0}'".format(typ) + ) return df_ti -def write_array_tpl(name, tpl_filename, suffix, par_type, zone_array=None, - gpname=None, shape=None, longnames=False, fill_value=1.0, - get_xy=None, input_filename=None,par_style="multiplier"): +def write_array_tpl( + name, + tpl_filename, + suffix, + par_type, + zone_array=None, + gpname=None, + shape=None, + longnames=False, + fill_value=1.0, + get_xy=None, + input_filename=None, + par_style="multiplier", +): """ write a template file for a 2D array. Args: @@ -2249,52 +2640,60 @@ def write_array_tpl(name, tpl_filename, suffix, par_type, zone_array=None, """ if shape is None and zone_array is None: - raise Exception("write_array_tpl() error: must pass either zone_array " - "or shape") + raise Exception( + "write_array_tpl() error: must pass either zone_array " "or shape" + ) elif shape is not None and zone_array is not None: if shape != zone_array.shape: - raise Exception("write_array_tpl() error: passed " - "shape {0} != zone_array.shape {1}".format(shape,zone_array.shape)) + raise Exception( + "write_array_tpl() error: passed " + "shape {0} != zone_array.shape {1}".format(shape, zone_array.shape) + ) elif shape is None: shape = zone_array.shape if len(shape) != 2: - raise Exception("write_array_tpl() error: shape '{0}' not 2D" - "".format(str(shape))) + raise Exception( + "write_array_tpl() error: shape '{0}' not 2D" "".format(str(shape)) + ) par_style = par_style.lower() if par_style == "direct": if not os.path.exists(input_filename): - raise Exception("write_grid_tpl() error: couldn't find input file "+ - " {0}, which is required for 'direct' par_style"\ - .format(input_filename)) + raise Exception( + "write_grid_tpl() error: couldn't find input file " + + " {0}, which is required for 'direct' par_style".format( + input_filename + ) + ) org_arr = np.loadtxt(input_filename) if par_type == "grid": pass elif par_type == "constant": - _check_diff(org_arr,input_filename) + _check_diff(org_arr, input_filename) elif par_type == "zone": for zval in np.unique(zone_array): if zval < 1: continue zone_org_arr = org_arr.copy() - zone_org_arr[zone_array!=zval] = np.NaN - _check_diff(zone_org_arr,input_filename,zval) + zone_org_arr[zone_array != zval] = np.NaN + _check_diff(zone_org_arr, input_filename, zval) elif par_style == "multiplier": org_arr = np.ones(shape) else: - raise Exception("write_grid_tpl() error: unrecognized 'par_style' {0} ".format(par_style) + - "should be 'direct' or 'multiplier'") + raise Exception( + "write_grid_tpl() error: unrecognized 'par_style' {0} ".format(par_style) + + "should be 'direct' or 'multiplier'" + ) def constant_namer(i, j): if longnames: - pname = "{0}_const_{1}".format(par_style,name) - if suffix != '': + pname = "{0}_const_{1}".format(par_style, name) + if suffix != "": pname += "_{0}".format(suffix) else: - pname = "{1}{2}".format(par_style[0],name, suffix) + pname = "{1}{2}".format(par_style[0], name, suffix) if len(pname) > 12: - raise Exception("constant par name too long:" - "{0}".format(pname)) + raise Exception("constant par name too long:" "{0}".format(pname)) return pname def zone_namer(i, j): @@ -2302,26 +2701,26 @@ def zone_namer(i, j): if zone_array is not None: zval = zone_array[i, j] if longnames: - pname = "{0}_{1}_zone:{2}".format(par_style,name, zval) - if suffix != '': + pname = "{0}_{1}_zone:{2}".format(par_style, name, zval) + if suffix != "": pname += "_{0}".format(suffix) else: - pname = "{1}_zn{2}".format(par_style[0],name, zval) + pname = "{1}_zn{2}".format(par_style[0], name, zval) if len(pname) > 12: raise Exception("zone par name too long:{0}".format(pname)) return pname def grid_namer(i, j): if longnames: - pname = "{0}_{1}_i:{2}_j:{3}".format(par_style,name, i, j) + pname = "{0}_{1}_i:{2}_j:{3}".format(par_style, name, i, j) if get_xy is not None: pname += "_x:{0:0.2f}_y:{1:0.2f}".format(*get_xy([i, j])) if zone_array is not None: pname += "_zone:{0}".format(zone_array[i, j]) - if suffix != '': + if suffix != "": pname += "_{0}".format(suffix) else: - pname = "{1}{2:03d}{3:03d}".format(par_style[0],name, i, j) + pname = "{1}{2:03d}{3:03d}".format(par_style[0], name, i, j) if len(pname) > 12: raise Exception("grid pname too long:{0}".format(pname)) return pname @@ -2333,14 +2732,16 @@ def grid_namer(i, j): elif par_type == "grid": namer = grid_namer else: - raise Exception("write_array_tpl() error: unsupported par_type" - ", options are 'constant', 'zone', or 'grid', not" - "'{0}'".format(par_type)) + raise Exception( + "write_array_tpl() error: unsupported par_type" + ", options are 'constant', 'zone', or 'grid', not" + "'{0}'".format(par_type) + ) parnme = [] org_par_val_dict = {} xx, yy, ii, jj = [], [], [], [] - with open(tpl_filename, 'w') as f: + with open(tpl_filename, "w") as f: f.write("ptf ~\n") for i in range(shape[0]): for j in range(shape[1]): @@ -2356,23 +2757,24 @@ def grid_namer(i, j): pname = namer(i, j) parnme.append(pname) - org_par_val_dict[pname] = org_arr[i,j] + org_par_val_dict[pname] = org_arr[i, j] pname = " ~ {0} ~".format(pname) f.write(pname) f.write("\n") df = pd.DataFrame({"parnme": parnme}, index=parnme) - df.loc[:,"parval1"] = df.parnme.apply(lambda x: org_par_val_dict[x]) - if par_type == 'grid': - df.loc[:, 'i'] = ii - df.loc[:, 'j'] = jj + df.loc[:, "parval1"] = df.parnme.apply(lambda x: org_par_val_dict[x]) + if par_type == "grid": + df.loc[:, "i"] = ii + df.loc[:, "j"] = jj if get_xy is not None: - df.loc[:, 'x'] = xx - df.loc[:, 'y'] = yy + df.loc[:, "x"] = xx + df.loc[:, "y"] = yy if gpname is None: gpname = name df.loc[:, "pargp"] = "{0}_{1}_{2}".format( - gpname, suffix.replace('_', ''),par_style).rstrip('_') + gpname, suffix.replace("_", ""), par_style + ).rstrip("_") df.loc[:, "tpl_filename"] = tpl_filename df.loc[:, "input_filename"] = input_filename if input_filename is not None: @@ -2383,11 +2785,15 @@ def grid_namer(i, j): def _check_diff(org_arr, input_filename, zval=None): - percent_diff = 100. * np.abs(np.nanmax(org_arr) - np.nanmin(org_arr) / np.nanmean(org_arr)) + percent_diff = 100.0 * np.abs( + np.nanmax(org_arr) - np.nanmin(org_arr) / np.nanmean(org_arr) + ) if percent_diff > DIRECT_PAR_PERCENT_DIFF_TOL: - message = "_check_diff() error: direct par for file '{0}'".format(input_filename) + \ - "exceeds tolerance for percent difference: {2} > {3}".\ - format(percent_diff, DIRECT_PAR_PERCENT_DIFF_TOL) + message = "_check_diff() error: direct par for file '{0}'".format( + input_filename + ) + "exceeds tolerance for percent difference: {2} > {3}".format( + percent_diff, DIRECT_PAR_PERCENT_DIFF_TOL + ) if zval is not None: message += " in zone {0}".format(zval) - raise Exception(message) \ No newline at end of file + raise Exception(message) diff --git a/pyemu/utils/smp_utils.py b/pyemu/utils/smp_utils.py index d46cc42b9..9a7dd2c88 100644 --- a/pyemu/utils/smp_utils.py +++ b/pyemu/utils/smp_utils.py @@ -13,8 +13,15 @@ import pandas as pd from ..pyemu_warnings import PyemuWarning -def smp_to_ins(smp_filename,ins_filename=None,use_generic_names=False, - gwutils_compliant=False, datetime_format=None,prefix=''): + +def smp_to_ins( + smp_filename, + ins_filename=None, + use_generic_names=False, + gwutils_compliant=False, + datetime_format=None, + prefix="", +): """create an instruction file for an smp file Args: @@ -47,43 +54,55 @@ def smp_to_ins(smp_filename,ins_filename=None,use_generic_names=False, """ if ins_filename is None: - ins_filename = smp_filename+".ins" - df = smp_to_dataframe(smp_filename,datetime_format=datetime_format) - df.loc[:,"ins_strings"] = None - df.loc[:,"observation_names"] = None + ins_filename = smp_filename + ".ins" + df = smp_to_dataframe(smp_filename, datetime_format=datetime_format) + df.loc[:, "ins_strings"] = None + df.loc[:, "observation_names"] = None name_groups = df.groupby("name").groups - for name,idxs in name_groups.items(): + for name, idxs in name_groups.items(): if not use_generic_names and len(name) <= 11: - onames = df.loc[idxs,"datetime"].apply(lambda x: prefix+name+'_'+x.strftime("%d%m%Y")).values + onames = ( + df.loc[idxs, "datetime"] + .apply(lambda x: prefix + name + "_" + x.strftime("%d%m%Y")) + .values + ) else: - onames = [prefix+name+"_{0:d}".format(i) for i in range(len(idxs))] - if False in (map(lambda x :len(x) <= 20,onames)): + onames = [prefix + name + "_{0:d}".format(i) for i in range(len(idxs))] + if False in (map(lambda x: len(x) <= 20, onames)): long_names = [oname for oname in onames if len(oname) > 20] - raise Exception("observation names longer than 20 chars:\n{0}".format(str(long_names))) + raise Exception( + "observation names longer than 20 chars:\n{0}".format(str(long_names)) + ) if gwutils_compliant: ins_strs = ["l1 ({0:s})39:46".format(on) for on in onames] else: ins_strs = ["l1 w w w !{0:s}!".format(on) for on in onames] - df.loc[idxs,"observation_names"] = onames - df.loc[idxs,"ins_strings"] = ins_strs + df.loc[idxs, "observation_names"] = onames + df.loc[idxs, "ins_strings"] = ins_strs counts = df.observation_names.value_counts() dup_sites = [name for name in counts.index if counts[name] > 1] if len(dup_sites) > 0: - raise Exception("duplicate observation names found:{0}"\ - .format(','.join(dup_sites))) + raise Exception( + "duplicate observation names found:{0}".format(",".join(dup_sites)) + ) - with open(ins_filename,'w') as f: + with open(ins_filename, "w") as f: f.write("pif ~\n") - [f.write(ins_str+"\n") for ins_str in df.loc[:,"ins_strings"]] + [f.write(ins_str + "\n") for ins_str in df.loc[:, "ins_strings"]] return df -def dataframe_to_smp(dataframe,smp_filename,name_col="name", - datetime_col="datetime",value_col="value", - datetime_format="dd/mm/yyyy", - value_format="{0:15.6E}", - max_name_len=12): +def dataframe_to_smp( + dataframe, + smp_filename, + name_col="name", + datetime_col="datetime", + value_col="value", + datetime_format="dd/mm/yyyy", + value_format="{0:15.6E}", + max_name_len=12, +): """ write a dataframe as an smp file Args: @@ -107,31 +126,32 @@ def dataframe_to_smp(dataframe,smp_filename,name_col="name", pyemu.smp_utils.dataframe_to_smp(df,"my.smp") """ - formatters = {"name":lambda x:"{0:<20s}".format(str(x)[:max_name_len]), - "value":lambda x:value_format.format(x)} + formatters = { + "name": lambda x: "{0:<20s}".format(str(x)[:max_name_len]), + "value": lambda x: value_format.format(x), + } if datetime_format.lower().startswith("d"): dt_fmt = "%d/%m/%Y %H:%M:%S" elif datetime_format.lower().startswith("m"): dt_fmt = "%m/%d/%Y %H:%M:%S" else: - raise Exception("unrecognized datetime_format: " +\ - "{0}".format(str(datetime_format))) + raise Exception( + "unrecognized datetime_format: " + "{0}".format(str(datetime_format)) + ) - for col in [name_col,datetime_col,value_col]: + for col in [name_col, datetime_col, value_col]: assert col in dataframe.columns - dataframe.loc[:,"datetime_str"] = dataframe.loc[:,"datetime"].\ - apply(lambda x:x.strftime(dt_fmt)) - if isinstance(smp_filename,str): - smp_filename = open(smp_filename,'w') + dataframe.loc[:, "datetime_str"] = dataframe.loc[:, "datetime"].apply( + lambda x: x.strftime(dt_fmt) + ) + if isinstance(smp_filename, str): + smp_filename = open(smp_filename, "w") # need this to remove the leading space that pandas puts in front - s = dataframe.loc[:,[name_col,"datetime_str",value_col]].\ - to_string(col_space=0, - formatters=formatters, - justify=None, - header=False, - index=False) - for ss in s.split('\n'): + s = dataframe.loc[:, [name_col, "datetime_str", value_col]].to_string( + col_space=0, formatters=formatters, justify=None, header=False, index=False + ) + for ss in s.split("\n"): smp_filename.write("{0: Date: Wed, 16 Sep 2020 12:07:28 -0600 Subject: [PATCH 2/5] added docs/ dir, finally not looking so shitty --- docs/Makefile | 13 +- docs/_autosummary/pyemu.ErrVar.rst | 6 + docs/_autosummary/pyemu.ParameterEnsemble.rst | 6 + docs/_autosummary/pyemu.Pst.rst | 6 + docs/_autosummary/pyemu.Schur.rst | 6 + docs/_autosummary/pyemu.rst | 6 + docs/_build/doctrees/environment.pickle | Bin 1932298 -> 1678213 bytes docs/_build/doctrees/index.doctree | Bin 23610 -> 4383 bytes docs/_build/doctrees/pst_demo.doctree | Bin 110199 -> 0 bytes .../doctrees/source/Monte_carlo.doctree | Bin 44887 -> 0 bytes docs/_build/doctrees/source/glossary.doctree | Bin 78393 -> 0 bytes .../doctrees/source/introduction.doctree | Bin 2898 -> 0 bytes docs/_build/doctrees/source/modules.doctree | Bin 2567 -> 0 bytes docs/_build/doctrees/source/oop.doctree | Bin 12579 -> 0 bytes docs/_build/doctrees/source/pst_demo.doctree | Bin 77313 -> 0 bytes docs/_build/doctrees/source/pyemu.doctree | Bin 572270 -> 0 bytes docs/_build/doctrees/source/pyemu.mat.doctree | Bin 288032 -> 0 bytes docs/_build/doctrees/source/pyemu.pst.doctree | Bin 314985 -> 0 bytes .../doctrees/source/pyemu.utils.doctree | Bin 798161 -> 0 bytes docs/_build/html/.buildinfo | 2 +- ...11b516230a84ec156ff202328317ec690ff0f1.png | Bin 3497 -> 0 bytes ...16230a84ec156ff202328317ec690ff0f1.png.map | 4 - ...828f53ebc0ac04313b120b956c8bfa6d4cd889.png | Bin 1541 -> 0 bytes ...53ebc0ac04313b120b956c8bfa6d4cd889.png.map | 4 - ...df8db1489ca584e38df01b2622db9a4b10e48a.png | Bin 10581 -> 0 bytes ...b1489ca584e38df01b2622db9a4b10e48a.png.map | 10 - ...f5d5fae6fb934b70ec8414c454588210adf45e.png | Bin 1541 -> 0 bytes ...fae6fb934b70ec8414c454588210adf45e.png.map | 4 - ...ee50e57b3bae647c584906ad0feaf20a6e4f7c.png | Bin 1541 -> 0 bytes ...e57b3bae647c584906ad0feaf20a6e4f7c.png.map | 4 - ...40c976ebd4a948735a6f3b1ac497179194e79f.png | Bin 12105 -> 0 bytes ...76ebd4a948735a6f3b1ac497179194e79f.png.map | 10 - ...0a33f1201dfb56f6290cf4845573529231ae71.png | Bin 21026 -> 0 bytes ...f1201dfb56f6290cf4845573529231ae71.png.map | 10 - ...0f6b737f5ddec889aff4c410c14c5213f14fb3.png | Bin 1025 -> 0 bytes ...737f5ddec889aff4c410c14c5213f14fb3.png.map | 4 - ...298a59d1eab4201c55375acd02f445cf2195c8.png | Bin 18880 -> 0 bytes ...59d1eab4201c55375acd02f445cf2195c8.png.map | 10 - ...9077eec04d50a84e94b51e9635e45b6a364b3d.png | Bin 1008 -> 0 bytes ...eec04d50a84e94b51e9635e45b6a364b3d.png.map | 4 - ...7254e72748d47f1911f42b1c9e8860122c2e47.png | Bin 5990 -> 0 bytes ...e72748d47f1911f42b1c9e8860122c2e47.png.map | 10 - ...a6d2626903000ee343435cae755d8b04c958e3.png | Bin 10581 -> 0 bytes ...626903000ee343435cae755d8b04c958e3.png.map | 10 - ...7a877d0fb670712625492e4856c5acf9e6f592.png | Bin 10581 -> 0 bytes ...7d0fb670712625492e4856c5acf9e6f592.png.map | 10 - ...dbbd2331d49aa0135b8d3f14983bbd402879ca.png | Bin 4940 -> 0 bytes ...2331d49aa0135b8d3f14983bbd402879ca.png.map | 10 - docs/_build/html/_modules/index.html | 97 - docs/_build/html/_modules/mc.html | 389 - docs/_build/html/_modules/numpy.html | 282 - docs/_build/html/_modules/pyemu/en.html | 1714 --- docs/_build/html/_modules/pyemu/ev.html | 750 -- docs/_build/html/_modules/pyemu/la.html | 1157 -- docs/_build/html/_modules/pyemu/logger.html | 209 - .../html/_modules/pyemu/mat/mat_handler.html | 2952 ----- docs/_build/html/_modules/pyemu/mc.html | 397 - .../_modules/pyemu/pst/pst_controldata.html | 389 - .../html/_modules/pyemu/pst/pst_handler.html | 2308 ---- .../html/_modules/pyemu/pst/pst_utils.html | 1010 -- docs/_build/html/_modules/pyemu/sc.html | 1093 -- docs/_build/html/_modules/pyemu/smoother.html | 1266 -- .../html/_modules/pyemu/utils/geostats.html | 2118 ---- .../html/_modules/pyemu/utils/gw_utils.html | 2063 ---- .../html/_modules/pyemu/utils/helpers.html | 3964 ------ .../_modules/pyemu/utils/optimization.html | 487 - .../html/_modules/pyemu/utils/pp_utils.html | 470 - docs/_build/html/_sources/index.rst.txt | 82 +- docs/_build/html/_sources/pst_demo.ipynb.txt | 1724 --- .../html/_sources/source/Monte_carlo.rst.txt | 20 - .../_sources/source/Monte_carlo_page.rst.txt | 24 - .../html/_sources/source/ensembles.rst.txt | 17 - .../html/_sources/source/glossary.rst.txt | 330 - .../html/_sources/source/introduction.rst.txt | 7 - .../html/_sources/source/modules.rst.txt | 7 - docs/_build/html/_sources/source/oop.rst.txt | 50 - .../html/_sources/source/pst_demo.ipynb.txt | 1228 -- .../html/_sources/source/pyemu.mat.rst.txt | 22 - .../html/_sources/source/pyemu.pst.rst.txt | 38 - .../_build/html/_sources/source/pyemu.rst.txt | 79 - .../html/_sources/source/pyemu.utils.rst.txt | 54 - docs/_build/html/_static/ajax-loader.gif | Bin 673 -> 0 bytes docs/_build/html/_static/alabaster.css | 684 -- docs/_build/html/_static/basic.css | 230 +- docs/_build/html/_static/comment-bright.png | Bin 756 -> 0 bytes docs/_build/html/_static/comment-close.png | Bin 829 -> 0 bytes docs/_build/html/_static/comment.png | Bin 641 -> 0 bytes docs/_build/html/_static/custom.css | 1 - docs/_build/html/_static/doctools.js | 18 +- .../html/_static/documentation_options.js | 7 +- docs/_build/html/_static/down-pressed.png | Bin 222 -> 0 bytes docs/_build/html/_static/down.png | Bin 202 -> 0 bytes docs/_build/html/_static/jquery-3.2.1.js | 10253 ---------------- docs/_build/html/_static/jquery.js | 6 +- docs/_build/html/_static/pygments.css | 119 +- docs/_build/html/_static/searchtools.js | 431 +- docs/_build/html/_static/up-pressed.png | Bin 214 -> 0 bytes docs/_build/html/_static/up.png | Bin 203 -> 0 bytes docs/_build/html/_static/websupport.js | 808 -- docs/_build/html/genindex.html | 5623 +++++++-- docs/_build/html/index.html | 369 +- docs/_build/html/objects.inv | Bin 11991 -> 12626 bytes docs/_build/html/py-modindex.html | 315 +- docs/_build/html/search.html | 249 +- docs/_build/html/searchindex.js | 2 +- docs/_build/html/source/Monte_carlo.html | 311 - docs/_build/html/source/Monte_carlo_page.html | 328 - docs/_build/html/source/ensembles.html | 1057 -- docs/_build/html/source/glossary.html | 364 - docs/_build/html/source/introduction.html | 95 - docs/_build/html/source/modules.html | 134 - docs/_build/html/source/oop.html | 139 - docs/_build/html/source/pst_demo.html | 1307 -- docs/_build/html/source/pyemu.html | 3087 ----- docs/_build/html/source/pyemu.mat.html | 1572 --- docs/_build/html/source/pyemu.pst.html | 1717 --- docs/_build/html/source/pyemu.utils.html | 3440 ------ docs/_build/latex/Makefile | 81 - docs/_build/latex/footnotehyper-sphinx.sty | 269 - docs/_build/latex/latexmkjarc | 7 - docs/_build/latex/latexmkrc | 9 - docs/_build/latex/pyEMU.aux | 55 - docs/_build/latex/pyEMU.dvi | Bin 22120 -> 0 bytes docs/_build/latex/pyEMU.idx | 3 - docs/_build/latex/pyEMU.out | 6 - docs/_build/latex/pyEMU.tex | 142 - docs/_build/latex/pyEMU.toc | 7 - docs/_build/latex/pyemu.log | 1075 -- docs/_build/latex/pyemu.pdf | Bin 80677 -> 0 bytes docs/_build/latex/python.ist | 13 - docs/_build/latex/sphinx.sty | 1652 --- docs/_build/latex/sphinxhighlight.sty | 105 - docs/_build/latex/sphinxhowto.cls | 95 - docs/_build/latex/sphinxmanual.cls | 114 - docs/_build/latex/sphinxmulticell.sty | 317 - docs/conf.py | 175 +- docs/conf.py.bak | 185 - docs/index.rst | 82 +- docs/index.rst.bak | 87 - docs/make.bat | 5 +- docs/{source => }/modules.rst | 0 docs/pyemu.docx | Bin 124765 -> 0 bytes docs/pyemu.mat.rst | 21 + docs/pyemu.plot.rst | 21 + docs/pyemu.prototypes.rst | 37 + docs/pyemu.pst.rst | 37 + docs/pyemu.rst | 81 + docs/pyemu.utils.rst | 77 + .../pst_demo-checkpoint.ipynb | 1072 -- docs/source/Monte_carlo.rst.bak | 20 - docs/source/Monte_carlo_page.rst | 24 - docs/source/ensembles.rst | 17 - docs/source/ensembles.rst.bak | 16 - docs/source/glossary.rst | 330 - docs/source/glossary.rst.bak | 330 - docs/source/oop.rst | 50 - docs/source/oop.rst.bak | 50 - docs/source/pst_demo.ipynb | 1228 -- docs/source/pyemu.mat.rst | 22 - docs/source/pyemu.pst.rst | 38 - docs/source/pyemu.rst | 79 - docs/source/pyemu.utils.rst | 54 - docs/source/test.pst | 71 - docs2/Makefile | 20 - docs2/conf.py | 76 - docs2/index.rst | 65 - docs2/make.bat | 35 - pyemu/plot/plot_utils.py | 3 - pyemu/pst/pst_handler.py | 3 - pyemu/utils/geostats.py | 40 +- pyemu/utils/gw_utils.py | 3 +- pyemu/utils/helpers.py | 53 +- pyemu/utils/pst_from.py | 99 +- 173 files changed, 6247 insertions(+), 61921 deletions(-) create mode 100644 docs/_autosummary/pyemu.ErrVar.rst create mode 100644 docs/_autosummary/pyemu.ParameterEnsemble.rst create mode 100644 docs/_autosummary/pyemu.Pst.rst create mode 100644 docs/_autosummary/pyemu.Schur.rst create mode 100644 docs/_autosummary/pyemu.rst delete mode 100644 docs/_build/doctrees/pst_demo.doctree delete mode 100644 docs/_build/doctrees/source/Monte_carlo.doctree delete mode 100644 docs/_build/doctrees/source/glossary.doctree delete mode 100644 docs/_build/doctrees/source/introduction.doctree delete mode 100644 docs/_build/doctrees/source/modules.doctree delete mode 100644 docs/_build/doctrees/source/oop.doctree delete mode 100644 docs/_build/doctrees/source/pst_demo.doctree delete mode 100644 docs/_build/doctrees/source/pyemu.doctree delete mode 100644 docs/_build/doctrees/source/pyemu.mat.doctree delete mode 100644 docs/_build/doctrees/source/pyemu.pst.doctree delete mode 100644 docs/_build/doctrees/source/pyemu.utils.doctree delete mode 100644 docs/_build/html/_images/inheritance-1211b516230a84ec156ff202328317ec690ff0f1.png delete mode 100644 docs/_build/html/_images/inheritance-1211b516230a84ec156ff202328317ec690ff0f1.png.map delete mode 100644 docs/_build/html/_images/inheritance-19828f53ebc0ac04313b120b956c8bfa6d4cd889.png delete mode 100644 docs/_build/html/_images/inheritance-19828f53ebc0ac04313b120b956c8bfa6d4cd889.png.map delete mode 100644 docs/_build/html/_images/inheritance-3fdf8db1489ca584e38df01b2622db9a4b10e48a.png delete mode 100644 docs/_build/html/_images/inheritance-3fdf8db1489ca584e38df01b2622db9a4b10e48a.png.map delete mode 100644 docs/_build/html/_images/inheritance-3ff5d5fae6fb934b70ec8414c454588210adf45e.png delete mode 100644 docs/_build/html/_images/inheritance-3ff5d5fae6fb934b70ec8414c454588210adf45e.png.map delete mode 100644 docs/_build/html/_images/inheritance-41ee50e57b3bae647c584906ad0feaf20a6e4f7c.png delete mode 100644 docs/_build/html/_images/inheritance-41ee50e57b3bae647c584906ad0feaf20a6e4f7c.png.map delete mode 100644 docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png delete mode 100644 docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png.map delete mode 100644 docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png delete mode 100644 docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png.map delete mode 100644 docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png delete mode 100644 docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png.map delete mode 100644 docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png delete mode 100644 docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png.map delete mode 100644 docs/_build/html/_images/inheritance-9e9077eec04d50a84e94b51e9635e45b6a364b3d.png delete mode 100644 docs/_build/html/_images/inheritance-9e9077eec04d50a84e94b51e9635e45b6a364b3d.png.map delete mode 100644 docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png delete mode 100644 docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png.map delete mode 100644 docs/_build/html/_images/inheritance-c7a6d2626903000ee343435cae755d8b04c958e3.png delete mode 100644 docs/_build/html/_images/inheritance-c7a6d2626903000ee343435cae755d8b04c958e3.png.map delete mode 100644 docs/_build/html/_images/inheritance-dc7a877d0fb670712625492e4856c5acf9e6f592.png delete mode 100644 docs/_build/html/_images/inheritance-dc7a877d0fb670712625492e4856c5acf9e6f592.png.map delete mode 100644 docs/_build/html/_images/inheritance-eddbbd2331d49aa0135b8d3f14983bbd402879ca.png delete mode 100644 docs/_build/html/_images/inheritance-eddbbd2331d49aa0135b8d3f14983bbd402879ca.png.map delete mode 100644 docs/_build/html/_modules/index.html delete mode 100644 docs/_build/html/_modules/mc.html delete mode 100644 docs/_build/html/_modules/numpy.html delete mode 100644 docs/_build/html/_modules/pyemu/en.html delete mode 100644 docs/_build/html/_modules/pyemu/ev.html delete mode 100644 docs/_build/html/_modules/pyemu/la.html delete mode 100644 docs/_build/html/_modules/pyemu/logger.html delete mode 100644 docs/_build/html/_modules/pyemu/mat/mat_handler.html delete mode 100644 docs/_build/html/_modules/pyemu/mc.html delete mode 100644 docs/_build/html/_modules/pyemu/pst/pst_controldata.html delete mode 100644 docs/_build/html/_modules/pyemu/pst/pst_handler.html delete mode 100644 docs/_build/html/_modules/pyemu/pst/pst_utils.html delete mode 100644 docs/_build/html/_modules/pyemu/sc.html delete mode 100644 docs/_build/html/_modules/pyemu/smoother.html delete mode 100644 docs/_build/html/_modules/pyemu/utils/geostats.html delete mode 100644 docs/_build/html/_modules/pyemu/utils/gw_utils.html delete mode 100644 docs/_build/html/_modules/pyemu/utils/helpers.html delete mode 100644 docs/_build/html/_modules/pyemu/utils/optimization.html delete mode 100644 docs/_build/html/_modules/pyemu/utils/pp_utils.html delete mode 100644 docs/_build/html/_sources/pst_demo.ipynb.txt delete mode 100644 docs/_build/html/_sources/source/Monte_carlo.rst.txt delete mode 100644 docs/_build/html/_sources/source/Monte_carlo_page.rst.txt delete mode 100644 docs/_build/html/_sources/source/ensembles.rst.txt delete mode 100644 docs/_build/html/_sources/source/glossary.rst.txt delete mode 100644 docs/_build/html/_sources/source/introduction.rst.txt delete mode 100644 docs/_build/html/_sources/source/modules.rst.txt delete mode 100644 docs/_build/html/_sources/source/oop.rst.txt delete mode 100644 docs/_build/html/_sources/source/pst_demo.ipynb.txt delete mode 100644 docs/_build/html/_sources/source/pyemu.mat.rst.txt delete mode 100644 docs/_build/html/_sources/source/pyemu.pst.rst.txt delete mode 100644 docs/_build/html/_sources/source/pyemu.rst.txt delete mode 100644 docs/_build/html/_sources/source/pyemu.utils.rst.txt delete mode 100644 docs/_build/html/_static/ajax-loader.gif delete mode 100644 docs/_build/html/_static/alabaster.css delete mode 100644 docs/_build/html/_static/comment-bright.png delete mode 100644 docs/_build/html/_static/comment-close.png delete mode 100644 docs/_build/html/_static/comment.png delete mode 100644 docs/_build/html/_static/custom.css delete mode 100644 docs/_build/html/_static/down-pressed.png delete mode 100644 docs/_build/html/_static/down.png delete mode 100644 docs/_build/html/_static/jquery-3.2.1.js delete mode 100644 docs/_build/html/_static/up-pressed.png delete mode 100644 docs/_build/html/_static/up.png delete mode 100644 docs/_build/html/_static/websupport.js delete mode 100644 docs/_build/html/source/Monte_carlo.html delete mode 100644 docs/_build/html/source/Monte_carlo_page.html delete mode 100644 docs/_build/html/source/ensembles.html delete mode 100644 docs/_build/html/source/glossary.html delete mode 100644 docs/_build/html/source/introduction.html delete mode 100644 docs/_build/html/source/modules.html delete mode 100644 docs/_build/html/source/oop.html delete mode 100644 docs/_build/html/source/pst_demo.html delete mode 100644 docs/_build/html/source/pyemu.html delete mode 100644 docs/_build/html/source/pyemu.mat.html delete mode 100644 docs/_build/html/source/pyemu.pst.html delete mode 100644 docs/_build/html/source/pyemu.utils.html delete mode 100644 docs/_build/latex/Makefile delete mode 100644 docs/_build/latex/footnotehyper-sphinx.sty delete mode 100644 docs/_build/latex/latexmkjarc delete mode 100644 docs/_build/latex/latexmkrc delete mode 100644 docs/_build/latex/pyEMU.aux delete mode 100644 docs/_build/latex/pyEMU.dvi delete mode 100644 docs/_build/latex/pyEMU.idx delete mode 100644 docs/_build/latex/pyEMU.out delete mode 100644 docs/_build/latex/pyEMU.tex delete mode 100644 docs/_build/latex/pyEMU.toc delete mode 100644 docs/_build/latex/pyemu.log delete mode 100644 docs/_build/latex/pyemu.pdf delete mode 100644 docs/_build/latex/python.ist delete mode 100644 docs/_build/latex/sphinx.sty delete mode 100644 docs/_build/latex/sphinxhighlight.sty delete mode 100644 docs/_build/latex/sphinxhowto.cls delete mode 100644 docs/_build/latex/sphinxmanual.cls delete mode 100644 docs/_build/latex/sphinxmulticell.sty delete mode 100644 docs/conf.py.bak delete mode 100644 docs/index.rst.bak rename docs/{source => }/modules.rst (100%) delete mode 100644 docs/pyemu.docx create mode 100644 docs/pyemu.mat.rst create mode 100644 docs/pyemu.plot.rst create mode 100644 docs/pyemu.prototypes.rst create mode 100644 docs/pyemu.pst.rst create mode 100644 docs/pyemu.rst create mode 100644 docs/pyemu.utils.rst delete mode 100644 docs/source/.ipynb_checkpoints/pst_demo-checkpoint.ipynb delete mode 100644 docs/source/Monte_carlo.rst.bak delete mode 100644 docs/source/Monte_carlo_page.rst delete mode 100644 docs/source/ensembles.rst delete mode 100644 docs/source/ensembles.rst.bak delete mode 100644 docs/source/glossary.rst delete mode 100644 docs/source/glossary.rst.bak delete mode 100644 docs/source/oop.rst delete mode 100644 docs/source/oop.rst.bak delete mode 100644 docs/source/pst_demo.ipynb delete mode 100644 docs/source/pyemu.mat.rst delete mode 100644 docs/source/pyemu.pst.rst delete mode 100644 docs/source/pyemu.rst delete mode 100644 docs/source/pyemu.utils.rst delete mode 100644 docs/source/test.pst delete mode 100644 docs2/Makefile delete mode 100644 docs2/conf.py delete mode 100644 docs2/index.rst delete mode 100644 docs2/make.bat diff --git a/docs/Makefile b/docs/Makefile index 36b1e1076..d4bb2cbb9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,13 +1,12 @@ # Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = pyemu +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build SOURCEDIR = . -#BUILDDIR = _build -BUILDDIR = ../../pyemudoc +BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @@ -18,4 +17,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_autosummary/pyemu.ErrVar.rst b/docs/_autosummary/pyemu.ErrVar.rst new file mode 100644 index 000000000..703f78637 --- /dev/null +++ b/docs/_autosummary/pyemu.ErrVar.rst @@ -0,0 +1,6 @@ +pyemu.ErrVar +============ + +.. currentmodule:: pyemu + +.. autoclass:: ErrVar \ No newline at end of file diff --git a/docs/_autosummary/pyemu.ParameterEnsemble.rst b/docs/_autosummary/pyemu.ParameterEnsemble.rst new file mode 100644 index 000000000..3a8ef3122 --- /dev/null +++ b/docs/_autosummary/pyemu.ParameterEnsemble.rst @@ -0,0 +1,6 @@ +pyemu.ParameterEnsemble +======================= + +.. currentmodule:: pyemu + +.. autoclass:: ParameterEnsemble \ No newline at end of file diff --git a/docs/_autosummary/pyemu.Pst.rst b/docs/_autosummary/pyemu.Pst.rst new file mode 100644 index 000000000..f31d280dc --- /dev/null +++ b/docs/_autosummary/pyemu.Pst.rst @@ -0,0 +1,6 @@ +pyemu.Pst +========= + +.. currentmodule:: pyemu + +.. autoclass:: Pst \ No newline at end of file diff --git a/docs/_autosummary/pyemu.Schur.rst b/docs/_autosummary/pyemu.Schur.rst new file mode 100644 index 000000000..35dc3d506 --- /dev/null +++ b/docs/_autosummary/pyemu.Schur.rst @@ -0,0 +1,6 @@ +pyemu.Schur +=========== + +.. currentmodule:: pyemu + +.. autoclass:: Schur \ No newline at end of file diff --git a/docs/_autosummary/pyemu.rst b/docs/_autosummary/pyemu.rst new file mode 100644 index 000000000..e1e71889c --- /dev/null +++ b/docs/_autosummary/pyemu.rst @@ -0,0 +1,6 @@ +pyemu +===== + +.. currentmodule:: pyemu + +.. automodule:: pyemu \ No newline at end of file diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 90f8dcdd58aafd042bc072ca3bb6a48686822755..f7d7c1edddfb9ccf3963327bee5016c610c58157 100644 GIT binary patch literal 1678213 zcmd3v2b?5Fb^q^f_x3j2?(OB%DXsD;+@2C52cfJBI&>l!5JT@w@6OCk()7$FA%YDi zc#I^p0uc*`Rid$wNP*8cJ5V@_LD^ZHls zTUA}TpF4Q_@@31HiT~@Z3tHt$x=5Lzc z)*J9zt@%B@(NeS6Y5RVu(w^^~v*U?@-wt+E50)z(f5)Z%fgP>Ge!aT`5(PU7Gs2ko zZ}Ed*zBd%Ki}H8J_4^&9D>fUmmAU!ec&JhRJ#-01M50ET2mE%sQUdEY&G%LYey1x% z`iDAxBd9bR!Tj~0J<98$&a~IFA;Jes5K)-K_dWaOuj{;mdYEalSWFY_<-!D|2NCd(`>o zo`3E+Q&;;cw)H;{=dl9!m74k^S!_adFM@^JALkaZ*`c9Lg#P`f`?j%;Su1EC>+2EXZD7t zrxEhJ*PH10^;XU6_(7rNb;=@ov=D_sf=T~SvDPj5BD&+ZLmWd`GQcO-mAmzsV7fE| z|2=)_zQVpvv+aZXM$4UgtpM4rgO*7|QfPNdQIL!eI$ozzj2^wJfB{07-|Eg3ia}7A zt<-=X2%C^UNd=9|plTw~c+l*&i+&;K&dyd2&G*KN%~}mG%97L@yQW2@IMP1_GT?QXHth3F0xg!!O1-ssk$6c=Wj?Yh^Ib!b(9w!lsM@V}-`6Feaq z{BXXv>hj9mvE1`CH zhv&=t@XrzXXJNM9ncssU!!@rl*M&Nv0>(g*SMv+1fULs5gLw~sumy8hP`{H%8lE7# z>Be@m*|}-@MiqC{jo`bRP*ZQT?gdb23bMAYtTamgp-}B3%8xOfnHBX?bnYR)LRXc=@dI*_dE;$)EMQwN9Z8O@PYO7Ozw)%yt_RJ(RFE zG>>IJ03W{ye>Eu*)SIO)w0AFjO7pa&4TZ*}~$gGI~aNTUpv! zqiSYh2AWI>P==51_x)Dkpx17QrbdZb8+2PODAqw7bA9-*O^|Piqfl(t>&-?1(uwK< z@zUTZ)QN)Ms??fu%AYa_+QdZAI_W6!c7VE5n5}v6?9jOrE$|^Uu7JMoRiK!zXhD$_ z1-ZAn0q-7gkbHT~@7W{5CNV5(e+6V2vAXUxWb<9plLdlDqxh@e_@MM9W?+x5=sULcDLoz;WuVqux8&TC#HlQo29<;2(@}({+l6bKW zfP25w;yRf^y?axMdgzT#vltfP0W^xr zMKJWB!fF^L4WRD=PQEeue5mjRsHCEA6t-y_nWZ~-_BfEH0vL`hK z4O&+;;?OmL7c5$ul&K-_?M|U%_#5m3rCtOh(hM}AO{niu#%dWd>m8_oBf`M)MdgRe z*z&{556AyFuY5kHUnA2)L)rGydEJPn;e}i1wo1^WgSVf2=i?hf#s*L}XW%U=UZFA= ze*`Z=@N~1`)xe}E4k~)R{1rCgf@-BG8zuQuj1%wBmJi({C6O0BW0gYgh&0p!RZe4;m`s~ z6;hxQGy>mi7t5l5ln^9pnEGu<1_dRJh7o8^)`NIN5Q>e;35dlPTo{3zMPV0xOXXn9 zTeI6~b>V#*b%9pOE>Nf*3o*Q>q4xp(2k1457lYJ}`0#}$-dC#AyJC7O{Bh_!ndP|e zkh%L3i_R)Q^U)Tq>puA=2%Rqaj3sPAKvv8IvnpO6;+?Jhe4HdDdcd*+EPmlP znyRBzz5;WEPnB;}=yQp_dRx62rP&J$qE&#vu+%{Rpi{0wgC$2Vy{DBsomTLO9XpDp zMm3l&)|%ZC^h?_QH280a2MKr7Dl@^3@K-y|pML1{LwCsWPiMLsJQn!CTl~@2`A^w? z@$P-wckkX+xbnK)yP9qIuh65igCL%$J8VKp30p<+c3tuGZBQqke&)Q7*^7Q$ssu3X zIxJtoE1v!|k?4ArC^Cdx!4yi=*&$DqM^m!!cd>>ew1Z#jQifYeT5$IN%#$vr1DF%?>n4jR1Tp# z{={9oruJOC`?9G=UAFt;t9Cu^;!7{R_OgBZ9y1?3d}I7;9FM@j)2q+-Htf21&lOL+ z_zL*xu3e9-Ri>Nmxx~*_3tWeww#V`qp5qYQtqGr(Eid$XShPkt`a| zTaUempwHBW*HCBPll5T~pNY-744K3bm~eqYEL+X6PkJmyh_(p-s}AE9X!>j2dL!)3 zhB**P4ndo%I=RPS`cAV|^AGs7LO2i=LniD)Z@>h4Yc2JJHWTme6%BX=iK4L{ekl1V=nu%K35(aq*)VFcMrJK>4Wo_>xpF|LSqT1lyyvP$4Rf z*`^qw%RY~)FX0oRSAJskvyi-Z;x!F_3gr7!iLTsKb9O44CL7;f35tFVdgnmg6w9JN)LV7d zqqjf%;s{Ah z%q6S1V_?#)(kj4YPs0;aZ-p7JxF233L0RQ!Ba|B`!G5g;@25s7oUDQIVs9jhk?+6t z(Vr?Iyz2H;2Up-La6=qj@(;kQ&YYY+Tj@8pLurkYZHbeGk5ePw-tzO}WD{{R?6^k; z63^K`1I~vDCNYVORd+ki)kcXYV>6IgPH{wW2}3Z+EM}t8@tfn{;#7`mYhE0gI3`X4 zlhE*bZPyDmcyU6@7&|86w*cNQ(3X{g`SRx@BMFT-FPMy66F(sw7L8&XbtM3c-F0l7 zO!NQ+ZYc9&5DwKKmXdIOlnkepu8(bPifze!L%$b0e`|JhgZ7S@N@GX4((P1g@lO)2 zitOTV)<(Zk5%I64;s-%@P0ZpJpmxI?IE<4z%_fY1V^2>)nZhy}&zguCJ{c&B=UUMS zr;|Y6MYQYC_KKc0)D+oUmuWJ_BXOj7i>SVS*p**%1H3#%6^S2@E&OB{A!j({{AKuy z)nNp_r+X_KF#bEBMh(e0($SU6CGvd?O$dx<#In;$d_Q9gxk23 zoAAz!lPDuwq7?9UfMITALnSyNN>FLwqDuv4HenV9W+Wl8Y7tK&$zZbe0Ca&3QU)!* zSb_4Z+dMV0Df*pW3Fd_5lz^D)gi`OpA`!fp!fB#dKt^IQXC9?Iy`R}=Vm(T}HkBOm zB*uBNoUBX5z&i*A3L%?xR9miog;5()zuNB~7LA+wT?`D~kkC%U+(sbFNF--t^mAbp zUZ@qQUD4!ijI-Eme9Nr@H| z#nKOSoy4zU^)zfdCc;1Co^3evRpF|Ji}CR)^^o2;yxW__?a|+B!ZIC)Op4CK_OJrN zyAH;F>tztG;cSnVbJv80Jn>VNsVFEMkIR+=v}-NZ@XXSTOr;^F#7#tLZ^AyLZDA8|(Ia<*Nf+1`NmY>x$mr4<>@rZ@?u(6JQt{Mi+tQe+* zDNa{E$7SDwsF{%KTBQgrNYwa?qAh2RByy4QFnPK;uU;z3hD;beS(U(o_?2k7l zs0|aHn!#hQc=h|x3%@MP)O;_~J211H@{UHJ9+fotmX0-sk3K1#c~xIhCNrNq=fkR= z*NG~gZYZuPrQAXgR!+^CnkAGXI{ko6dF+@Bk6qSoKgok4ticdj3Nx#wmtg27>XImb zP?a)F?G{7%IlSMW(w2uA;*7ifIQO;pknDQ?E?iOfmqYW)LLE{(h7n;WDADG9V z-?^ffqE_-orBP@=1`wKq& zqy`=SlGrE$-H8|VR<~d;i5N%9`G($$gRbg;^Y^7wU__W!!)PAeOxT`%PxitdCMVx3_9*%_aqE@@9k=m zS_ZuJga^Kw?4)4equzRbFWJUm=u`YJWwa3(SNY1d$_%tu^B8#B;D^O@TKw9V!EZaO z`@`yQpx-M0`qYsheZ2Z$^w&-Advx{N(O*%i|duFQlM}Ix{C*|rd zqQCy^!(Xj_H~Q-ZkK9=OO7zz~C!JRPa`e~7-qo%CTlCl0uD!DQ#pthZE#F@KW%SoC z-tJd_7X9_#(^pl$8UJ<9_p4ux{`$kpOR7Ite|_eWb9Yp~6#cdH-jl1pivF5=!)*0m zqQ5?TZ=?FP=&z4``JU>JqrX1+oI9%zM1OtuwBJ{M82$CN=XR?1MSp$cLl;*6Df;W1 z+b`_)U@M$qHu{a~2ZXO?Om-SE5ELHt#R-W} zcy~s2L8?7?5@tb4DetS^0wJLKHX&>^c5A~SdrEUUs`^SuUVR(<56G^*3jPJT)z`qk zVCuE_&+GA@H{d^SlK*t8Z-IZSZ-xH>MWXAofTGQ)h)%nt#O032t|NpyG@(6kn7CcOHxb!Eh(w$Z_(Yc)idb^&ecP@CwhW{b;8faSr|c zmYAc<_tqR(PxPI7Ymy%F8=7x$a}}J9&-BXk`zhU44MZk+KB*pt-|VR#k^d{b@w|S| zFz|+$qqOzLfgEof$n?fv^;5b?-gs5NXBc=x%+Z>AV|AizXs*cUP^h|cXFrXLqyPZEES*cr*x6L@wtA_Fz|+$oj3Q!`b4Ri#(7W8BjGR$ z<1)SS<9>=4$tyqT_Y4ECh*b)6uS_Pr0{uzNC5g=@$-#`OGs{=@Y4aD!AHP3Je~5J& zbAKGC`C}S&w08Zq+G{E=Mw%&JGb`l($ck6hL783rB^tC;2c9fWVD%DejSt-pqVNVevGX3#jKdp=8kNf*Q!@wV6PmQ@hrcxzG zEMlfNRcro%Yer;qL=$HE>$m;#vPk~=O}}Rt_)F|MGWS=yM-aLp+)ShSO7#fRi8B4P zVaWXmWNp7^82Cx-$1?X*x9p{hndqBe6@(+(*Zz z>Yk`r(T0A_NgKjymr9fArOW%}V3E9ZNxx?pcuDNuGxt(zj27gK(SqzTTA`oPMe@ee z`#rg;%pB^MgGKUEx8E}iyd-vY9-WuM13-Pf6sF1a z(yje+ut;8dX}@O}cuDMbHTP1gjmB~kZrahDlw1az4wGd1=>PQ7zeqlMYrkh0_(<#l zHuurGWE-9J{z~oR&Khlew4dHZ^2xpZo?+k!mPFrkBp>mxDzrOQ-gGhJlyF zi3@WtrN(Gjo1)#Znw!EgnwBWjPmk-Dheh(!qx(I>z)#{VO1_`u22F=s>7I0S>bt$s zS`)7J5toz38)@avR@`|iHjCnpPq7`dD)&^1&5%{Oqfu-_tjfKFVoP6D?#mM!>#A~h zoY>AL4`Ecr<}rDwp(-|NRpo9gu@OpcP_K$@M{+lKRc!E)$FZtnTMcf55xYa=F|4ZC z86l5gRmH9VdHkv>*2U$~tEyNNmdCEDVjWZ-xvGj4KzZD%D%QBsw(EE<@BM2B_S1xxO|F7LxFOTC0ld}NpX$k0-T zf6!vn)?$JZTB_PwN)-&X-@F3W5__iNBGSl-*jDZ2#8eOGMJ%v)z54eN>$ii6Y<&VDlS=SOT!+yOtt{NH11;&qV;r0VmJO~fi*e>5u8$qRO|vaXxC z=QEN+?&=4lYPuXQ_g{=^Dh`+|R84&m#A{#Ke;x@x+1f#Wb4@mP4LT=})PC9J$u z8L7v|vqg}4yw%o3T!S{-nuzN$Vn3>S{2SZCCsaT^mc8hutH+7cOsSgt*{J6J3EQpN zuLqH_g=+4T*v}#bX`eK)bTxNiueLVblOb3;53%!O=)2yis-5t3QXjkQ#}YTTZB4}W zv1w}}u8#=wsOsb1qfr6%QI2Jou0E#D^QCI!H=-K(59};umnxC9g=*yAv7beX(mSMy zrK^!EGmc4yVOT4_z|NDQ|9Y##T8Rosz5FNkgNZXgV{0O=m!GsX5!XvZdQ|oDZAYU5 z>ZP1n%Bz>#s*64tWzW-9e-u^Nk&(se{E9p;RAGnM&mv`PK$^&}ux;q6zs(k51fZz9bO55H=l|F^6)9C@FP_#;0(Qo-+$_jT!Pm| z2akZ8_2+8M8Mw@As9Y(*wLkD*GmchfBM5fYdT=$gh;jFINVsY5ZPR0`$uh*nY`wpGkG;tv?0s9TQ(q*$&rAZVy$FW_l0%`J@W% zv^5bo)5ygFZ8(sS6YhBpcnZX4 z&VkyrdNNOmR&S5GP2|tv+lqP%SbZZr8a}xgw}XRmI|yfI7qRgRz4B;yAhvSb`nGc6 z{e6jMZV;>UymY3;T^(^VhdeCmLR_7?N23Dz8*8g`<2SjQlFd$a0ov2$qXO&Axzt)h{R&@b^k z_>b8SCa(QKf7ezQwZ)RfCDCGS+oSH(gky{Ie=a@=A8wF^BU z{W*N{TW|cfRo9I|(cd8cXOsA!E#iN+!vFaHe_F)T`?iSa193SocKcRgF&hBAys7Yz zcHAB!?W1ZBAH)K)*xkNSsBr2g*JPswUpJ2<&<~Gb`@yujjfv`}RFK;CQ1)|4ZM)Fc zMEpK-x{isL!#hZ7vnvNf?(IBH%$9C0k-cndo_mN?+j4ANM=k^OLjwl(1#$8lkQS{yiA7M)7VWRpz#bdnT7tsCQZK}|s&m##Zfb|ivA zTf-JYpsltw;T&36OM0ENUNkEgQpW6EPZRaGdAC$pbSU|c6nEaimDaP$?Wlyl=?^y-c5yqS7+ zUAP1bpBspzkaB*A{dnTquB~fr7gW}G0eo9e97={smNtAmI^|#zi++9$JENw4KAHM? zT31N?{z|qS;`f)^nuyz%+YsGiPkl^?`??G81;0ihzbBO9qemj1d)b*W#dA^y9!(#i z^Fg*6Lg)RqCY;k5EH>dn(i9U6LK5F+3pXXPIfI1MLj3<-wg%$=@7S7f?teJv0AJs& zh|jEMHeea?NM?AotG?=~nPha81k*~kAcARytqJFthDz-we1afdQB)uTIfpIYw5lAF z0VLEzD4fYwK`5MVYr;8&G4avL0(^MhgU|V<+N@Lz^4QDHhAEE|GI%7F5h%OaQV5i* zZB00bGAy%R4AK`K_u^p0(P3x66vwf7ID{s`;s&-3!lGeo!a0ix$wGVqW>$Q?HC;#a zNaS({J2R$Svie$@K0@bqwi-g`Wws{V)0utRF1tN%fws=zzS(PHxLnz$JRzWDd)YgP^ z3L{a6DgAOzgd>Z)*jX@Tky$BJH;%q3-oq9`aJI%-~hEX8M}b9tL4i_%YjkrbS^QZXcxtQWt-~el&sqpsfk#ZXejCdtHX0(^pTp z>LIJUOkiD62p~QmVLzGpe8|>>vCplS&evZv6Zq`|ay%a`tENVdnJEy;x$JzI)`a8Z znvf-sK-$h$LLi+bO;{|c+cq=9aF8Zjc+y!6S0Z~dfL|AkBsZDQk{>C zcq}o$V0+fI#3UBu3~tE!DO(3ABtMcS{`fi`6Kh;`xfA;{h>QDlJI2_8h_8ojO~h4U zr8KeVgU%7JR4s`6DYaF#L^ziG^VwN2E%}MJSQZu~k2I7!*op|3b8JmG=c4cLOEph= zBocZOJ2R$)&Whh=l+Z%E0Qa$F5lnk+O*qE{hn8V)W>FhUMWG1h2wT1>m{aL`0`_dBo=hB07g6tl)G=l5{wkDh-o0Mbyx~yAbZbIuW&}hW< zLw1Htahw9cr1l9w#CY-|>m#|u18}B(wRV^(JF^x{T>T~yR14XThWD-WJ z*s2JlL0c2f8Eue^;NXqdnCsTOcE*ffb`m5u&CZx<#Y)Wi(av6`XaeqRwm1TAo2?1w z;MPhXbQ=|MC?|tjMm!?BmYpS2WZM^P3sZ6luWQ)K2(MkXCYStX4Sxtpzr5V_0NgmWT^%|NLhTp}E)Jjl+1 zDV4rcbMYP~!sULpBEsc9TNBQ?40h=YMFt^_p|!61(tTejQUL)mz!pP5EVDJCgRoc$ ziWie`wZg(gtish|lKT2ls*&-6VBPJ!Q8`i zA*@!zw>8sk6p2MTKVoOclupzci(VJHK7!}_Y%v7ScWq5L$1@pbzwN^Z5jvG-Bh80K zBcQQ$u6oPSDQX>1tdI~IW~(8DR@$0yPH2NTrR=w1*ELKwXP

PJ*O%urp>_m7-B* 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 oGb>Ra{BPTne}+R5}1qf4r%YzyJUM literal 1932298 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 z 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

XE_JthHhZ7s&E{1+8}-+~*_?C;v_f6N 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
c&h~O&2nniz{vsY|WjR(BV5e2*edK9z$!SSnZ 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

tL305%)$Zji4*w7M05jc3Dd=dBF@RInFwKt})+ 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 z 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 lf(p5hc(J%^cv|9N4#)r;E04wV$IXy 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 diff --git a/docs/_build/doctrees/source/Monte_carlo.doctree b/docs/_build/doctrees/source/Monte_carlo.doctree deleted file mode 100644 index 8a9f3a14f823a4f559cf1bbad742a517d849886f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44887 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

6dOoNku4W!( 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 diff --git a/docs/_build/doctrees/source/oop.doctree b/docs/_build/doctrees/source/oop.doctree deleted file mode 100644 index 257468d34c6df95682be876887ca240b85b1626f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12579 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 DWeM}? diff --git a/docs/_build/doctrees/source/pst_demo.doctree b/docs/_build/doctrees/source/pst_demo.doctree deleted file mode 100644 index 11193b2b9cb61a29ad85e00fdb65af131a33bbd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77313 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

3Jncvtb^ z?xfVRJa~n2J?0sVQWd^gU~diQMsXRfq5E)Fh0-TAD}d)j+b*Gndb4S;HtM~=B{zt; zjBamm_iN+{@fA6uC2By`M=XCIiaRoLlbCoKJR~R{-AN4hQ3oUtnO~0Q6eBk49|`^t zSv}0v(mv|H1W3r=hhqYthpfK^B75)@^7nR9Ld}`JEtPYJLf*fxF@mV0#3&k@<#{1OY=Wvi%VHz z!n2Y2?^PqS;MwKBCFli(z<@xUJzXoWZ) z0R58V0p6rcM;7t1vlR(n(YZ(VC7nyg(0w>L%{j$^b7;f1^XZt9Oe)L5$(rpB)G(+p%L3cE zcYy{$-2ALT7%mjI%q5_JE$PQ`5}~x#MfyTzrjpaAPvJvtu~NyI&(z=HIqN<~ z@mg&r_hDp;k7DyX0P{h1z|%9+)f|G1;?oL~?-40JihbGuyp>kMKx1}(zLpyhjN;Ym zbZ!U@rZ~pT(6f^9da;9NB`U5wUN0EK9C9`-nCp1G$gP0m^#a$W(pCd}tc=%-+(^0M z>xH}}{1MfbXS9Q(FVVr#8x(Mc9vtyKkh~ildT_LtHds4xaP({2I5<*lE8jtQrR<&a z+sePJpiR+(qjLnk;I{HfR!^ccxbV}^9Me*!v9$|FJ<;w4PtkscPD3JPEly4NuBAjp zVgu&bHW)YbgmEj0HFN=wnHV!6USkpsN$rW%a10{0O0F^kW$Y2SZ8F`HV^54bG7wCB zrt=#vz(pXk_Ru7Pu`H2QG%_>6mSaJZYz~X8ZFCGGvXBM{CSh_cNTkeRku~FuK}6Ql zL=_VDI2I(`C0Jz7mR93IGwvkNI_~FqsPO(0{d|);NoI|fV%m;y{A3iEx#IWyzWk#tqv?7G2-2dj zuCrzt4&r|xbd~A0d~g?XnLt5Cn16l2Y;c5R+wM9A(xM}v9^%x{;S)d6VM(|O-9rqm zDA*PJ`Qr>pFZpohLieHJkZE1_TH3n4k%SK!6#1(C(U?BDQyrY8AQ;2t%i=s@FWxd5 z-@alPWcAN~R7wDb`EOCl^vY%?r%uOY3JWw#3E30iOh5fAqhh=E67^a< z*TR@y-NAkN7e)0^W zcrBHx3$wFjT;89Vo?oaf&R6EAwQFyDXnOHcaNH()IIUF|=OKVB)M}N*xoTx@wo*H( zu}>tYh&;H;UY@Hh)~a(0_%~CVj`BqIKv6bGGE#asNxu%yyo8eAM7tM3dB`2XZHw&D zPI%bFVuDBmW;U|grXK&!)xZPVhZs<)$1Xeznfu|=a>H1)oNMnu00RqF6Yn;>1`HHB z*Dqo=b+D*4P2Ka)E-x=%TfTlt)xFEtj#J+rSNH32>Qnlr=+EPW*_IEcg0PyRLMqQomjULThM}23 z(DhHBI<;yAX0K5O^-l448&in>89kn|d>^vHsp;9pnG(l28lB_L*cF{2&%_55Gcmo= zChj&PcUw^`y_CPra|_Arllk~il>_IvnF2#@Sc_1!k<^%o#MO6_c@!ePSQL{LHd(Ti zmY1j!qojMXMU$03%zp!qSpd0qN;rIM6VlW5H4M%L0qi#qfE8gl%Z{HBfs=W_LD%~i2 zf{`FAnN3QIbod%BR?StWHMmcmt5h>m&A{(6x&N+$+}jZMe_Llr;72L=FUZy;%AaED^L8POEI6NnF7U^;_Rj=wS(oHznWhiUPNQ1 z)q(QJ^!SE~9tBs2uM_x!tHbZHdQwR9Sjx2R*1bAY*3;sEB_evsEfC*N8nby>xsm@< z)yPrTi0>6>ZM;T+(8(IX+b;KfAtwPjmYz+>?!E`F5qH71PNBMycy9Q1zD8VRd*2TG z9c_yFcKDLUKgC<{>~{N7W>MKWfO3bvjl5!YXv}c<1;@ z?RfV%$i;WGcw@WQOSjK9bWFHW_wVuuKdBhuqpnlelTj@*pP}m%=MT~MVXfda=bpON&?I9MV8p}K01E|ATI>n22 zg2^a5)M#~D|5HWSImYHQ3TimEEd`2cy{O%-xnXX*on=O(_; zVgSC3CQb&>9yDmhb}?evAmoD=ib6Wg<5mPrT^K+&!eeS%YlgPcbDE?u;o7-4)O*{H zJ9SLM!ZeI$B-3m3RD*t0q3-~Sr6akw`CO3MoiZUw+yTaMs5_F-UZ4 zJqSFj(F=^^EaeYhgl>147tO55jVs z?Zk;lcs}m*I=0>!UX16VGBUWhY_(F!z#~(&nRSzo${t}P$f{8@M`h=^#2k-k zjNrw^#ZqcWif8puB#~O9aVtx>I}KLu+(kv_Zbu^hI<2j945%!TKD`Brbjze%VsyN1 zgwQci*;4Q_gvPEB7Uffl}w z&btr-0uur(w{_QAuHly)Hz4DPd3K-y`wF^gfB*>t zc1VdS19rS+geG^BP8MP!)xyNYM45>h@D%X|3Go!);bY5Gpr6b~@AU;0fI z0Y?>bzgU2hv5+f8kl=L9jHjC{u=D{<~}%i$>?l{oO5o| z9VfxN$Q*QDikw^GW+Q&GzA>wjkM5jv%OtyYG4jQk(W;aDR#gq zLE79~WDIFqK#F~FN{}{p-5Eog5s*YsblHdK>>xTN*+uJS1teTiRZx6x5Tqo#fHWr{ z%_||z4}z3r7m(&NCMDx>i;xG=U>ronWS9#o)b{ z=ticz^8cw~va$Z0)XVY3ecJy+Pg_4M-78*nz` z)aI8Tb95ZTw{(a^YyNU~V|s4cwi?Smrsh;vZ(h+?4J2cx>Hadj))>o{(=x8Yn7Z8E zI6XZ*e;Rkr12WQn4^9K#>UKBYqk9fsPnRBx>|Dt_)7wB2Y4Q<+fE^aVGY#&^b~otQ z07nS61qV1Qp4-98+w?$xoNRu}rFS9ZL60oAwX*FtFM94eqJW}$!#mk@ZSi~wT1ReD zOXmZ)mcUZ~>l$abQNir`b(;T$JanLwjJPpqE{5HAX& zlkE42(vEe*Y<1iXgS!DExkLTn%g5;##`6K^Z{zuJ+p(qGe2|yZwqiNmj)Lh?5;<2H zP+7sG;XdT33MR13z_=PTurT?y?wi-H#|v2|Jzm!`>1>(Hq_I^mlSYeRCP`isGasWR zGLy-d9^x!%Is z1liJx%hbi20(O(1-Gu1EZjx$Yc9Vx;V;G?ycVC!FRA3}iG~K0XTf5bfRH+Id_5Yj* zkyfSVoz%aeYHdMP>Q@MSK~?H+a%v@4Yd(Xk@hx&GX+S6@9XVHjF)x<7XMKQQVrN=( zezV-?7PemGdk>1ZSgWoz+@90Ih4l@i(<}D^%ch8Vb{9IWGTm;kyB-F5J+Q7oVd$<+ zk`@=XdrGUl0ipK(b3)49j(I!?nQHl)stz7iwfvt7^mMFRmb{wte$R~eBDR1dM*llR zq5Z0gIq>I6$0d1W68%s`qJnea2L&i2>`niS)sww=BWo1Mm)@C{wT3Ipez0M~2{2iJ zlu50-LJDP42_p(Z;GK3Idt<6;S~lKH?HbERd1)g#rNh0gb6SHRS`$~>DfxP70Hi@> zZDVC{QHn_gB;=Z*LuS!=lNAy#c&O$5xj+`uT4wXQM(=Zqkf@#%u8@gS;BExSjC5PrB z2iePMTCF@?saB>dKlY|d1!bg=c#H#gvZ z))_6<+y_XB@k#}pp*CM!-j??sLu1YTZxZGUSv<9W9XG(kePl?{hj`z&PBe8ha< zT#xPCdySpi#5{(ki;aA|(UGbARt0s6Mw@#xfiJj`Kf~%tY@#lF7EKp|wualXs}6Q4 z^ur`#dWho7vGW=6vHbEYA{v_C=?W+vQ9tV8xN>) z|B-CJ>-$fh>=zZ2eS{cw?LOLeSSm5b^Es={)H=X;sKw^BNO$f6htk`K|fT0qCyRHoz;^<`s2k_p*_b6 zHX?>Pn!fWSw5424diHVL@Ha-(fBv0>e!CJ~xhp@V>dH}-o}Vnxr?Jvg3N^-iSe_+? z>}j_oFDOR)^GR=Ur6=W)N%S=pi3%z`zgmDYVzmDjt0y~WWu@m=5fg2!^n`*>Y(kmr zkJwpInVyB6c~+szHt&!asKk=IA264MES62*0i|cLZ8B+$2JBFT5SJJ9P)5=|sW+ga zd^R_vqa+DWY6WC2Kz$-q4UicVphWADw#CIz8lp2JloV24ag=iUvSKJrqlN+9H&BXI zA~isbm)imtLyI(}L(wWqs{v>gqZW`Ds#qLzvRgy3NP}tfW0f%{Bc@kuevUa=QA+J{ zP9}?q-&Bc-sZCpQW0Nbx6j2-hfRuMHD&P!N-f_86mZ66#@4iGr=}su`zO^0YokAGj zSIQ{w^1}E5WXWxf3ku_R68M73yB~64JhsBQU~i(li&S@&w0=zpVR<1g(Tj;ZB&3pR zu9RG0e?+t4zGZ;Gl|VqbNJ$q#U}~_8noqIj=OD!8O5}3_xJXWh6#D!KLZU~DaW9dp zi7bS{I3y5a9I|@22ja0Y?uwh;6*8myi;5{Yf=ccF0<$nyseu6`Dm6YQM__dSH)eF( zR;f+wqs!W`VoV|yy`EWKc5qD=)|sB+!@Z^m-~yu}L^Y>#h)G;bFC`fh)q<1?w*R9< zmK0N#O)(jGJ|*r8_u?%9#BA4|Q1nUGSds$QQZ39B^H>JgxQoN=7X@x|7i8-Z;c9D) zWYi|qn7yr>QtB~HWK9vSc9lqyGRkCmVnkWHEmgYBHKM5 zu255vgoOPiOfKQIAEuT8f_Hm}Rpnc)9zy*fG!QOAce<{FXyAV7Au_ClnGyG*82d;p zBil(NMnQ<^5h?YgAaa&@?e(a}I;y~(V`FjzAXyIk!Gzo|ht|mhpGv3VqA$J;3U6`FM&n?W)OwTXW7UwJT z)7tc$c8!JOfd_#mdpNCC7w5~B*@apS23pn1+-#+mjq!*)bF&NOx!Pi_I=6s-GqvgK zrRg~c7fOJ$kwF_XsKT$AJir>{itFTMLdZ7%cVL2S@WcbGhb zWFCJx_Pew_ATcLwe+%>k-})x~NeRMo+}<>S>EKKVo6n3SY$~E(e~+q73!C}q*WXul zctK(Fj|hB0Ve`AJo|J_Avpl#C69YF93|GBwy=656NR`(nNO3y>hul~$vjXEJ#8#2_ zta#Xl@VM_tOupTSZ9K}@uHgWX%_)&a6$bC57H`s{u`nn__~!j9Pv1g^-a9A^PB0SW z07~LQ_z$=+xC7E!wK`KqRGj2ze#_$D;$j9;Ru;U)u$G5rRWUTTBMQEs7ML;vD)sG! zpWA{cCDx&zm8Oh zH&o=SPA`-fYO@QK`3eLKs2CTvEKpW!(-}}%iSlLCB6&!EQ8A>qBW-?$7O!7pKxJw3 zpKL+e+-bp4J!FarqWLj$TS%gj06OkH4StOs!)WpN!TOqOkwX?2zJfcqig5Znin%_j zaQgR>`7JL*LxoemYRCncY^ta0CrM^qctkf}cb?sW$tftZ3B`}U{;b>To9=WKL; z&E!&Ns#2brf``%aT-jJ%g@Cm?o*{f_YwS>s zVxzGK8gdPn1>J=6&R)mS=o7iYC%r85E2`PXnqikqZ!=bO*g!{Kx1-z$1rMhq0J08W z)S9PTJ{5)q5YfoBHG+3;!B_Q&ZHEVIRofv_;!5O8-Fhhw z^LRvzM}#R&UE=! zEmLkQrx{BKKh*0AF96sy(GJJ3H*yYAamP(Et&(`2SaU16sPbKucCD50<`kbn(M?KGY zqClU==NTAo$$17J0rDij+2soWpQDU&2p_)D=*G1F3!!))vB{q z^@+ve3|x*bRu*R$sx!0G(~Am|>KY8H=PCN@~UD`Zf95h zU0R2F45)k}@$oI3NGR-I6n&quTvl{?>dZmW+j049FW*i_4&&}2&F^kEfqqUgThluI zA_gztPiCmR7z{mA;G8>Cps$rP(?0k55)o=_?4_puB55spjVzKI-E?$oCYU+S}1+S&(wddJ*I~usn zIz?Wiuc&T6@bWhL;k_%a8#r!Yq&_}%M|jjXnt{{n)UV>cTc?kdV}9USUAnZ^p}TGX z`8u9r;LMCt>SX0#XH*Bus4*%)-M|v=@(hyX>g`j9j)&EF%%yPQDpl&!#w*`v!!^6E`Wolu{ zxA6Rc{ISlCN`%$`bS6}rK|j3P?FBNF{R~P!d@%DR1ET$Vm^tAw4mZI;lzu?|kSQGA z6NkhDiEOk9qt(`()gI!vg5oW_-)T7g@Rgo{m~^dr%jjZ!pc;!2_5f~ht)Q8ph}Ei7 zTnX!H-#ic=(|dtiPm;nrEexuotzM^Fj|UzI%MvV%InRNAG=RCArGUDQPu-s+b$z4P za_d*&`}2Hwk8!o@`UXaRQj(35W>8e6Dq1W4EO$+0>+vfl-aY$it`ma~LFU6G8jX)nfkNOB})PHROEv z7@daE!jy!+F5fha0MPDh+OB@3Zgh0Zt|QV3uCfvX_(-@H;Tvgmtr`I-@rajz8Ufg4 zBRs^JgF#&F9x^|~P!H;X?jbW92CqQf7$^aFB*@h=R(x>8@LnmEjs{pVpsmC&2WUdH zSo%RqVG4^vv|9M#VagF~bQv6$=e%#e{(8Utilf*JAMwMZ=}u)pk2Wpv zEW!#E@f4bPA|J;-ox<~BXrpU84TM6Lvc4*l80S7LWjictW$^En;Dd*H0Wt0Svaq5r ud%{Ct3vsq%@Q{ev1mgJ=F`8!Gz47dql8?m6tfxvXVo1^4rS-GdDF1)p3HdPq diff --git a/docs/_build/doctrees/source/pyemu.doctree b/docs/_build/doctrees/source/pyemu.doctree deleted file mode 100644 index e7fdf9f018a5e01aca88d9c7b29e620420310e30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 572270 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

  • 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/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 diff --git a/docs/_build/doctrees/source/pyemu.pst.doctree b/docs/_build/doctrees/source/pyemu.pst.doctree deleted file mode 100644 index d1d7978df0ab74398848a49f7976af54ffb1f052..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314985 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 diff --git a/docs/_build/doctrees/source/pyemu.utils.doctree b/docs/_build/doctrees/source/pyemu.utils.doctree deleted file mode 100644 index 71dcafbc18a613daaf91b9802cbfef2e859bc989..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 798161 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�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

    >yku#g)E*%zARgf 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 mF!47x9z29M2i~R>MlFE|+ diff --git a/docs/_build/html/_images/inheritance-1211b516230a84ec156ff202328317ec690ff0f1.png.map b/docs/_build/html/_images/inheritance-1211b516230a84ec156ff202328317ec690ff0f1.png.map deleted file mode 100644 index 058b712e7..000000000 --- a/docs/_build/html/_images/inheritance-1211b516230a84ec156ff202328317ec690ff0f1.png.map +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/_build/html/_images/inheritance-19828f53ebc0ac04313b120b956c8bfa6d4cd889.png b/docs/_build/html/_images/inheritance-19828f53ebc0ac04313b120b956c8bfa6d4cd889.png deleted file mode 100644 index 5fee5955230f02130f893ba6ef947496d570d095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541 zcmcIkX;2af6s9~fTg_|DOz|RWO(~4E8BU5^8j^FS5e7wlkgzV%_CdQG^RW- z^EM$r9P0S;4scEO#+21>F-h1wi& ztSr;fCAWHZ--G;zDoFDOFk1!3G2Yc;TQtZ&Im=WBk-^r|Qu{+NhMB(EFpw>7=zh0- z*Q-u3M?`u*02GArf_?7oPQr9B_W*FtD1eg54xKbbcY~2m1NC$(U``=TDQq~pH6Mfy zwkeh^#z&yQcQap!KAu)pz%_VVfX)pW36GEO{4%4Vb;{w}g5XX#zu?6CJ^i%fgeg1Ky=@!+NSzXr5I%0v*cLg8_qeZ5Z0$Gg zg)G)Jcy9Eo(knm<%9X_FEa9hI6Vq&fSM&xb=)0R=-t{_^CRj6MI8%Q6`xCnqO5Hg=SVe+C??M~#h*^~81DYi>T_L+3@u#hE9{ zKI7XgR7^72TU#9~n_JuPoYQ7=(h)mo;86q)rq5YgYRJg=L0KV|L<(y)jnv3P=><*A z&$}QH8(Wp>-zJ1m7 z-NuonhOPN_=u9`&hG(b%>~ZeWGegqr9SvZ<)DXUxPiS^aUTJ=raj=i>YtK@32Rf)Q zc?|eOoE&4PJa;9UY2(LU(&j&~xzGwYph5j>HOSe`J4$c$tzx) z+%iHg60`c-OIzSq>-6(GV% - - - diff --git a/docs/_build/html/_images/inheritance-3fdf8db1489ca584e38df01b2622db9a4b10e48a.png b/docs/_build/html/_images/inheritance-3fdf8db1489ca584e38df01b2622db9a4b10e48a.png deleted file mode 100644 index 34f4139ca97d04d315a186aeda3faef74dbac401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10581 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 - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-3ff5d5fae6fb934b70ec8414c454588210adf45e.png b/docs/_build/html/_images/inheritance-3ff5d5fae6fb934b70ec8414c454588210adf45e.png deleted file mode 100644 index 5fee5955230f02130f893ba6ef947496d570d095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541 zcmcIkX;2af6s9~fTg_|DOz|RWO(~4E8BU5^8j^FS5e7wlkgzV%_CdQG^RW- z^EM$r9P0S;4scEO#+21>F-h1wi& ztSr;fCAWHZ--G;zDoFDOFk1!3G2Yc;TQtZ&Im=WBk-^r|Qu{+NhMB(EFpw>7=zh0- z*Q-u3M?`u*02GArf_?7oPQr9B_W*FtD1eg54xKbbcY~2m1NC$(U``=TDQq~pH6Mfy zwkeh^#z&yQcQap!KAu)pz%_VVfX)pW36GEO{4%4Vb;{w}g5XX#zu?6CJ^i%fgeg1Ky=@!+NSzXr5I%0v*cLg8_qeZ5Z0$Gg zg)G)Jcy9Eo(knm<%9X_FEa9hI6Vq&fSM&xb=)0R=-t{_^CRj6MI8%Q6`xCnqO5Hg=SVe+C??M~#h*^~81DYi>T_L+3@u#hE9{ zKI7XgR7^72TU#9~n_JuPoYQ7=(h)mo;86q)rq5YgYRJg=L0KV|L<(y)jnv3P=><*A z&$}QH8(Wp>-zJ1m7 z-NuonhOPN_=u9`&hG(b%>~ZeWGegqr9SvZ<)DXUxPiS^aUTJ=raj=i>YtK@32Rf)Q zc?|eOoE&4PJa;9UY2(LU(&j&~xzGwYph5j>HOSe`J4$c$tzx) z+%iHg60`c-OIzSq>-6(GV% - - - diff --git a/docs/_build/html/_images/inheritance-41ee50e57b3bae647c584906ad0feaf20a6e4f7c.png b/docs/_build/html/_images/inheritance-41ee50e57b3bae647c584906ad0feaf20a6e4f7c.png deleted file mode 100644 index 5fee5955230f02130f893ba6ef947496d570d095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541 zcmcIkX;2af6s9~fTg_|DOz|RWO(~4E8BU5^8j^FS5e7wlkgzV%_CdQG^RW- z^EM$r9P0S;4scEO#+21>F-h1wi& ztSr;fCAWHZ--G;zDoFDOFk1!3G2Yc;TQtZ&Im=WBk-^r|Qu{+NhMB(EFpw>7=zh0- z*Q-u3M?`u*02GArf_?7oPQr9B_W*FtD1eg54xKbbcY~2m1NC$(U``=TDQq~pH6Mfy zwkeh^#z&yQcQap!KAu)pz%_VVfX)pW36GEO{4%4Vb;{w}g5XX#zu?6CJ^i%fgeg1Ky=@!+NSzXr5I%0v*cLg8_qeZ5Z0$Gg zg)G)Jcy9Eo(knm<%9X_FEa9hI6Vq&fSM&xb=)0R=-t{_^CRj6MI8%Q6`xCnqO5Hg=SVe+C??M~#h*^~81DYi>T_L+3@u#hE9{ zKI7XgR7^72TU#9~n_JuPoYQ7=(h)mo;86q)rq5YgYRJg=L0KV|L<(y)jnv3P=><*A z&$}QH8(Wp>-zJ1m7 z-NuonhOPN_=u9`&hG(b%>~ZeWGegqr9SvZ<)DXUxPiS^aUTJ=raj=i>YtK@32Rf)Q zc?|eOoE&4PJa;9UY2(LU(&j&~xzGwYph5j>HOSe`J4$c$tzx) z+%iHg60`c-OIzSq>-6(GV% - - - diff --git a/docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png b/docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png deleted file mode 100644 index d6e8bb6c180e260bbcceddf6a1e40bbdbead8520..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12105 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 F{|7*D>cRj3 diff --git a/docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png.map b/docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png.map deleted file mode 100644 index 5c681e5c3..000000000 --- a/docs/_build/html/_images/inheritance-4c40c976ebd4a948735a6f3b1ac497179194e79f.png.map +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png b/docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png deleted file mode 100644 index 02273fed63c934844ee0e26814a9e98e244eba07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21026 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 diff --git a/docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png.map b/docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png.map deleted file mode 100644 index 2e572a737..000000000 --- a/docs/_build/html/_images/inheritance-5b0a33f1201dfb56f6290cf4845573529231ae71.png.map +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png b/docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png deleted file mode 100644 index f6b4c8f8494530e51eb498fc4cc5618f7f9436c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1025 zcmV+c1pfPpP)tJT=K07i9P%X+;|oklR~39(gGC7aFW&-=y(e|dQ+&(F`% zlLB1a-QA^5VG!yGv31o5OEdtGDH=QgWQqn40GXn}13;$suE7^Yp*}7&rUvb~HR7e* zAIL-BIh5)gD~jUZ&(Jty*t-TFL#nUyDT+dxrkPwrPG~K$myta+o{awf$eYp?4<=V}!E6=|9T=s=;P9b^h#<0D1 zJsujjwfkaD_2d)RmY<2KftnhvEv_xU_Maj7ZFBU^!z5OXs9nLgZ(m%K?7QXJjTth> zkoh&@?w(X#^SSN|)9bF>L-GjOMq7+I-qwb=zP^^%*H`)a`cjEEr+RXYJ4U^^Y`4h& z#%!vo(ENOBSZiJzwFcj{?%GbcYXe zr>CcKb8{o_@9*;S^Aj9zPW2QT*X46Vw%0eGp<>kVP{yw+7*pyiy0Pc?SrX~{oa+_B zkh!|-UTL^AB-`g~zQ4ca<>kdc$q^kM9!g!;QrES7ett?-RdRfMEGH)?QkLatd8DqV zxf{Cwrndi+H~5rSbf#sG*t)jYH zCQnaKa&d7Xi^U@7%hKjvPcB^Z3puYfn4WXFUaU-^xr-<^c+&$6pD{k;`r@>?6pATE zS5q^^2+gxS)|QJljk(5LqM3v9^K*H4c#x~BD>*ng7#weo^)w{ESaJK<{;`7T*@Elu zed;P^ji@fs#C#Cw`#NM?*WYdNeaD5wvBmF-p|#yEbM?KSYg2D~dwWxlIrn;+dR)^O z*ZOk#y;ffsg8%3Q0GW#Zkk$7#6Vt)5n;wn{e5f>d*JTIH3&$=E>Iqs{q5*(R(cl3f zQ#5!0$P^7805U~`2Y^f!jp4C;S(dU|ty1Iz{887ntk>(*X#}I55L;DMve|6@yl-sq v0D$>pi3R{NMS}-`Owr&0AX7AW0LauYc9%%C`Gsl<00000NkvXXu0mjfONay} diff --git a/docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png.map b/docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png.map deleted file mode 100644 index 7cdb50e20..000000000 --- a/docs/_build/html/_images/inheritance-640f6b737f5ddec889aff4c410c14c5213f14fb3.png.map +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png b/docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png deleted file mode 100644 index f7dd42c4b16d4d1a2c48d30f378043b06da40fb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18880 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 diff --git a/docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png.map b/docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png.map deleted file mode 100644 index 050ffc9ca..000000000 --- a/docs/_build/html/_images/inheritance-7f298a59d1eab4201c55375acd02f445cf2195c8.png.map +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-9e9077eec04d50a84e94b51e9635e45b6a364b3d.png b/docs/_build/html/_images/inheritance-9e9077eec04d50a84e94b51e9635e45b6a364b3d.png deleted file mode 100644 index ff089d0b75d9b56436db34b5a49a400fca39ffd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1008 zcmV^Df6Hnm(B(ytW^86CpZ{h?f>bkB40N~|M?gRi}OemTF zpg_?C00oLB04V%5ZQR}6mE+^%p>qL@7K??vyu9R2BN+2(oT{pl*=#1Kr>Ebx8;j<2 zI+gqT`#}-|x?Eph=T0FC^J$zO9vFN$ z_X~}gLh80t#LIa;VAs_zx%#arivM4+afa~gie?{j{rY}U6jImq#uX|4oWbU@jg?em zNE>H0hF4#kHkM5&ngFs`70jkF6wV|?b9IoWOKhLluKn8I3u&V(_t>RDJ3m{VUG4dt z3vEszd9-7=Ui`&i9{Z3pNZGxfc)zV5w+#O>NAWlY~#n!eM-{E4cn zlAD_wIXpa+lamv9eSMX$udm>Ep=00YeKB)Vn@_Ah=0z;yuTc&u_m#)c?Q539UGMY# z)QxG&rv2&d^P%Z_pUdaxr#wA9>608#S(dU~E@eKS%X~hU_xE?%+1Zi({e9Wq-p-v~ zRDx|x$FlQ)EmrY=y!U zBQ($EzOGzSOVQSl%kwcfIy#c`^K&^nJCn&|GFZE=^BYqXWBSeYkkWLo*7y6GvD)Xj zOS(Mo^I?Vdb;y3U|GVOA_X~;Rif@aN>bQN{>e~KO3uV*u^K-ITImUjfxTbw#ALfM4 z(Z0qVKim!g3cWuF(k|JtIp{lJU6P{dyX3^U(09N(mgfMVK+yyM1&SsBC{Q#3K!KtO z01C#Z=WlOsa(Q_>=! - - - diff --git a/docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png b/docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png deleted file mode 100644 index ea4d40342f74b77b63143d3741cadf6a30ad7a8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5990 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 diff --git a/docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png.map b/docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png.map deleted file mode 100644 index 8f6dcd276..000000000 --- a/docs/_build/html/_images/inheritance-a27254e72748d47f1911f42b1c9e8860122c2e47.png.map +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-c7a6d2626903000ee343435cae755d8b04c958e3.png b/docs/_build/html/_images/inheritance-c7a6d2626903000ee343435cae755d8b04c958e3.png deleted file mode 100644 index 34f4139ca97d04d315a186aeda3faef74dbac401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10581 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 - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-dc7a877d0fb670712625492e4856c5acf9e6f592.png b/docs/_build/html/_images/inheritance-dc7a877d0fb670712625492e4856c5acf9e6f592.png deleted file mode 100644 index 34f4139ca97d04d315a186aeda3faef74dbac401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10581 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 - - - - - - - - - diff --git a/docs/_build/html/_images/inheritance-eddbbd2331d49aa0135b8d3f14983bbd402879ca.png b/docs/_build/html/_images/inheritance-eddbbd2331d49aa0135b8d3f14983bbd402879ca.png deleted file mode 100644 index 30320b92c2f031e25670de0efce228966240ee6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4940 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 X`Dd2OJRuH_$Dj+wS5Rf=Tps=pOlg - - - - - - - - - diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html deleted file mode 100644 index 0e6631a27..000000000 --- a/docs/_build/html/_modules/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - Overview: module code — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -

    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/mc.html b/docs/_build/html/_modules/mc.html deleted file mode 100644 index 3ce105846..000000000 --- a/docs/_build/html/_modules/mc.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - - - mc — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for mc

    -"""pyEMU Monte Carlo module.  Supports easy Monte Carlo
    -and GLUE analyses.  The MonteCarlo class inherits from
    -pyemu.LinearAnalysis
    -"""
    -from __future__ import print_function, division
    -import os
    -import numpy as np
    -from pyemu.la import LinearAnalysis
    -from pyemu.en import ObservationEnsemble, ParameterEnsemble
    -from pyemu.mat import Cov
    -#from pyemu.utils.helpers import zero_order_tikhonov
    -
    -
    [docs]class MonteCarlo(LinearAnalysis): - """LinearAnalysis derived type for monte carlo analysis - - Parameters - ---------- - **kwargs : dict - dictionary of keyword arguments. See pyemu.LinearAnalysis for - complete definitions - - Attributes - ---------- - parensemble : pyemu.ParameterEnsemble - obsensemble : pyemu.ObservationEnsemble - - Returns - ------- - MonteCarlo : MonteCarlo - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(pst="pest.pst")`` - - """ - def __init__(self,**kwargs): - super(MonteCarlo,self).__init__(**kwargs) - assert self.pst is not None, \ - "monte carlo requires a pest control file" - self.parensemble = ParameterEnsemble(pst=self.pst) - self.obsensemble = ObservationEnsemble(pst=self.pst) - - @property - def num_reals(self): - """ get the number of realizations in the parameter ensemble - - Returns - ------- - num_real : int - - """ - return self.parensemble.shape[0] - -
    [docs] def get_nsing(self,epsilon=1.0e-4): - """ get the number of solution space dimensions given - a ratio between the largest and smallest singular - values - - Parameters - ---------- - epsilon: float - singular value ratio - - Returns - ------- - nsing : float - number of singular components above the epsilon ratio threshold - - Note - ----- - If nsing == nadj_par, then None is returned - - """ - mx = self.xtqx.shape[0] - nsing = mx - np.searchsorted( - np.sort((self.xtqx.s.x / self.xtqx.s.x.max())[:,0]),epsilon) - if nsing == mx: - self.logger.warn("optimal nsing=npar") - nsing = None - return nsing
    - -
    [docs] def get_null_proj(self,nsing=None): - """ get a null-space projection matrix of XTQX - - Parameters - ---------- - nsing: int - optional number of singular components to use - If Nonte, then nsing is determined from - call to MonteCarlo.get_nsing() - - Returns - ------- - v2_proj : pyemu.Matrix - the null-space projection matrix (V2V2^T) - - """ - if nsing is None: - nsing = self.get_nsing() - if nsing is None: - raise Exception("nsing is None") - print("using {0} singular components".format(nsing)) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) - - v2_proj = (self.xtqx.v[:,nsing:] * self.xtqx.v[:,nsing:].T) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) - - return v2_proj
    - -
    [docs] def draw(self, num_reals=1, par_file = None, obs=False, - enforce_bounds=None, cov=None, how="gaussian"): - """draw stochastic realizations of parameters and - optionally observations, filling MonteCarlo.parensemble and - optionally MonteCarlo.obsensemble. - - Parameters - ---------- - num_reals : int - number of realization to generate - par_file : str - parameter file to use as mean values. If None, - use MonteCarlo.pst.parameter_data.parval1. - Default is None - obs : bool - add a realization of measurement noise to observation values, - forming MonteCarlo.obsensemble.Default is False - enforce_bounds : str - enforce parameter bounds based on control file information. - options are 'reset', 'drop' or None. Default is None - how : str - type of distribution to draw from. Must be in ["gaussian","uniform"] - default is "gaussian". - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(pst="pest.pst")`` - - ``>>>mc.draw(1000)`` - - """ - if par_file is not None: - self.pst.parrep(par_file) - how = how.lower().strip() - assert how in ["gaussian","uniform"] - - if cov is not None: - assert isinstance(cov,Cov) - if how == "uniform": - raise Exception("MonteCarlo.draw() error: 'how'='uniform'," +\ - " 'cov' arg cannot be passed") - else: - cov = self.parcov - - self.log("generating {0:d} parameter realizations".format(num_reals)) - - if how == "gaussian": - self.parensemble = ParameterEnsemble.from_gaussian_draw(pst=self.pst,cov=cov, - num_reals=num_reals, - use_homegrown=True, - enforce_bounds=False) - - elif how == "uniform": - self.parensemble = ParameterEnsemble.from_uniform_draw(pst=self.pst,num_reals=num_reals) - - else: - raise Exception("MonteCarlo.draw(): unrecognized 'how' arg: {0}".format(how)) - - #self.parensemble = ParameterEnsemble(pst=self.pst) - #self.obsensemble = ObservationEnsemble(pst=self.pst) - #self.parensemble.draw(cov,num_reals=num_reals, how=how, - # enforce_bounds=enforce_bounds) - if enforce_bounds is not None: - self.parensemble.enforce(enforce_bounds) - self.log("generating {0:d} parameter realizations".format(num_reals)) - - if obs: - self.log("generating {0:d} observation realizations".format(num_reals)) - self.obsensemble = ObservationEnsemble.from_id_gaussian_draw(pst=self.pst,num_reals=num_reals) - self.log("generating {0:d} observation realizations".format(num_reals))
    - - - - -
    [docs] def project_parensemble(self,par_file=None,nsing=None, - inplace=True,enforce_bounds='reset'): - """ perform the null-space projection operations for null-space monte carlo - - Parameters - ---------- - par_file: str - an optional file of parameter values to use - nsing: int - number of singular values to in forming null subspace matrix - inplace: bool - overwrite the existing parameter ensemble with the - projected values - enforce_bounds: str - how to enforce parameter bounds. can be None, 'reset', or 'drop'. - Default is None - - Returns - ------- - par_en : pyemu.ParameterEnsemble - if inplace is False, otherwise None - - Note - ---- - to use this method, the MonteCarlo instance must have been constructed - with the ``jco`` argument. - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(jco="pest.jcb")`` - - ``>>>mc.draw(1000)`` - - ``>>>mc.project_parensemble(par_file="final.par",nsing=100)`` - - """ - assert self.jco is not None,"MonteCarlo.project_parensemble()" +\ - "requires a jacobian attribute" - if par_file is not None: - assert os.path.exists(par_file),"monte_carlo.draw() error: par_file not found:" +\ - par_file - self.parensemble.pst.parrep(par_file) - - # project the ensemble - self.log("projecting parameter ensemble") - en = self.parensemble.project(self.get_null_proj(nsing),inplace=inplace,log=self.log) - self.log("projecting parameter ensemble") - return en
    - -
    [docs] def write_psts(self,prefix,existing_jco=None,noptmax=None): - """ write parameter and optionally observation realizations - to a series of pest control files - - Parameters - ---------- - prefix: str - pest control file prefix - - existing_jco: str - filename of an existing jacobian matrix to add to the - pest++ options in the control file. This is useful for - NSMC since this jco can be used to get the first set of - parameter upgrades for free! Needs to be the path the jco - file as seen from the location where pest++ will be run - - noptmax: int - value of NOPTMAX to set in new pest control files - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(jco="pest.jcb")`` - - ``>>>mc.draw(1000, obs=True)`` - - ``>>>mc.write_psts("mc_", existing_jco="pest.jcb", noptmax=1)`` - - """ - self.log("writing realized pest control files") - # get a copy of the pest control file - pst = self.pst.get(par_names=self.pst.par_names,obs_names=self.pst.obs_names) - - if noptmax is not None: - pst.control_data.noptmax = noptmax - pst.control_data.noptmax = noptmax - - if existing_jco is not None: - pst.pestpp_options["BASE_JACOBIAN"] = existing_jco - - # set the indices - pst.parameter_data.index = pst.parameter_data.parnme - pst.observation_data.index = pst.observation_data.obsnme - - if self.parensemble.istransformed: - par_en = self.parensemble._back_transform(inplace=False) - else: - par_en = self.parensemble - - for i in range(self.num_reals): - pst_name = prefix + "{0:d}.pst".format(i) - self.log("writing realized pest control file " + pst_name) - pst.parameter_data.loc[par_en.columns,"parval1"] = par_en.iloc[i, :].T - - # reset the regularization - #if pst.control_data.pestmode == "regularization": - #pst.zero_order_tikhonov(parbounds=True) - #zero_order_tikhonov(pst,parbounds=True) - # add the obs noise realization if needed - if self.obsensemble.shape[0] == self.num_reals: - pst.observation_data.loc[self.obsensemble.columns,"obsval"] = \ - self.obsensemble.iloc[i, :].T - - # write - pst.write(pst_name) - self.log("writing realized pest control file " + pst_name) - self.log("writing realized pest control files")
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/numpy.html b/docs/_build/html/_modules/numpy.html deleted file mode 100644 index 332f44801..000000000 --- a/docs/_build/html/_modules/numpy.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - - - numpy — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for numpy

    -"""
    -NumPy
    -=====
    -
    -Provides
    -  1. An array object of arbitrary homogeneous items
    -  2. Fast mathematical operations over arrays
    -  3. Linear Algebra, Fourier Transforms, Random Number Generation
    -
    -How to use the documentation
    -----------------------------
    -Documentation is available in two forms: docstrings provided
    -with the code, and a loose standing reference guide, available from
    -`the NumPy homepage <http://www.scipy.org>`_.
    -
    -We recommend exploring the docstrings using
    -`IPython <http://ipython.scipy.org>`_, an advanced Python shell with
    -TAB-completion and introspection capabilities.  See below for further
    -instructions.
    -
    -The docstring examples assume that `numpy` has been imported as `np`::
    -
    -  >>> import numpy as np
    -
    -Code snippets are indicated by three greater-than signs::
    -
    -  >>> x = 42
    -  >>> x = x + 1
    -
    -Use the built-in ``help`` function to view a function's docstring::
    -
    -  >>> help(np.sort)
    -  ... # doctest: +SKIP
    -
    -For some objects, ``np.info(obj)`` may provide additional help.  This is
    -particularly true if you see the line "Help on ufunc object:" at the top
    -of the help() page.  Ufuncs are implemented in C, not Python, for speed.
    -The native Python help() does not know how to view their help, but our
    -np.info() function does.
    -
    -To search for documents containing a keyword, do::
    -
    -  >>> np.lookfor('keyword')
    -  ... # doctest: +SKIP
    -
    -General-purpose documents like a glossary and help on the basic concepts
    -of numpy are available under the ``doc`` sub-module::
    -
    -  >>> from numpy import doc
    -  >>> help(doc)
    -  ... # doctest: +SKIP
    -
    -Available subpackages
    ----------------------
    -doc
    -    Topical documentation on broadcasting, indexing, etc.
    -lib
    -    Basic functions used by several sub-packages.
    -random
    -    Core Random Tools
    -linalg
    -    Core Linear Algebra Tools
    -fft
    -    Core FFT routines
    -polynomial
    -    Polynomial tools
    -testing
    -    NumPy testing tools
    -f2py
    -    Fortran to Python Interface Generator.
    -distutils
    -    Enhancements to distutils with support for
    -    Fortran compilers support and more.
    -
    -Utilities
    ----------
    -test
    -    Run numpy unittests
    -show_config
    -    Show numpy build configuration
    -dual
    -    Overwrite certain functions with high-performance Scipy tools
    -matlib
    -    Make everything matrices.
    -__version__
    -    NumPy version string
    -
    -Viewing documentation using IPython
    ------------------------------------
    -Start IPython with the NumPy profile (``ipython -p numpy``), which will
    -import `numpy` under the alias `np`.  Then, use the ``cpaste`` command to
    -paste examples into the shell.  To see which functions are available in
    -`numpy`, type ``np.<TAB>`` (where ``<TAB>`` refers to the TAB key), or use
    -``np.*cos*?<ENTER>`` (where ``<ENTER>`` refers to the ENTER key) to narrow
    -down the list.  To view the docstring for a function, use
    -``np.cos?<ENTER>`` (to view the docstring) and ``np.cos??<ENTER>`` (to view
    -the source code).
    -
    -Copies vs. in-place operation
    ------------------------------
    -Most of the functions in `numpy` return a copy of the array argument
    -(e.g., `np.sort`).  In-place versions of these functions are often
    -available as array methods, i.e. ``x = np.array([1,2,3]); x.sort()``.
    -Exceptions to this rule are documented.
    -
    -"""
    -from __future__ import division, absolute_import, print_function
    -
    -import sys
    -import warnings
    -
    -from ._globals import ModuleDeprecationWarning, VisibleDeprecationWarning
    -from ._globals import _NoValue
    -
    -# We first need to detect if we're being called as part of the numpy setup
    -# procedure itself in a reliable manner.
    -try:
    -    __NUMPY_SETUP__
    -except NameError:
    -    __NUMPY_SETUP__ = False
    -
    -if __NUMPY_SETUP__:
    -    sys.stderr.write('Running from numpy source directory.\n')
    -else:
    -    try:
    -        from numpy.__config__ import show as show_config
    -    except ImportError:
    -        msg = """Error importing numpy: you should not try to import numpy from
    -        its source directory; please exit the numpy source tree, and relaunch
    -        your python interpreter from there."""
    -        raise ImportError(msg)
    -
    -    from .version import git_revision as __git_revision__
    -    from .version import version as __version__
    -
    -    from ._import_tools import PackageLoader
    -
    -    def pkgload(*packages, **options):
    -        loader = PackageLoader(infunc=True)
    -        return loader(*packages, **options)
    -
    -    from . import add_newdocs
    -    __all__ = ['add_newdocs',
    -               'ModuleDeprecationWarning',
    -               'VisibleDeprecationWarning']
    -
    -    pkgload.__doc__ = PackageLoader.__call__.__doc__
    -
    -    # We don't actually use this ourselves anymore, but I'm not 100% sure that
    -    # no-one else in the world is using it (though I hope not)
    -    from .testing import Tester
    -    test = testing.nosetester._numpy_tester().test
    -    bench = testing.nosetester._numpy_tester().bench
    -
    -    # Allow distributors to run custom init code
    -    from . import _distributor_init
    -
    -    from . import core
    -    from .core import *
    -    from . import compat
    -    from . import lib
    -    from .lib import *
    -    from . import linalg
    -    from . import fft
    -    from . import polynomial
    -    from . import random
    -    from . import ctypeslib
    -    from . import ma
    -    from . import matrixlib as _mat
    -    from .matrixlib import *
    -    from .compat import long
    -
    -    # Make these accessible from numpy name-space
    -    # but not imported in from numpy import *
    -    if sys.version_info[0] >= 3:
    -        from builtins import bool, int, float, complex, object, str
    -        unicode = str
    -    else:
    -        from __builtin__ import bool, int, float, complex, object, unicode, str
    -
    -    from .core import round, abs, max, min
    -
    -    __all__.extend(['__version__', 'pkgload', 'PackageLoader',
    -               'show_config'])
    -    __all__.extend(core.__all__)
    -    __all__.extend(_mat.__all__)
    -    __all__.extend(lib.__all__)
    -    __all__.extend(['linalg', 'fft', 'random', 'ctypeslib', 'ma'])
    -
    -
    -    # Filter annoying Cython warnings that serve no good purpose.
    -    warnings.filterwarnings("ignore", message="numpy.dtype size changed")
    -    warnings.filterwarnings("ignore", message="numpy.ufunc size changed")
    -    warnings.filterwarnings("ignore", message="numpy.ndarray size changed")
    -
    -    # oldnumeric and numarray were removed in 1.9. In case some packages import
    -    # but do not use them, we define them here for backward compatibility.
    -    oldnumeric = 'removed'
    -    numarray = 'removed'
    -# 
    -__mkl_version__ = '2017.0.1' 
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/en.html b/docs/_build/html/_modules/pyemu/en.html deleted file mode 100644 index 4e78d0919..000000000 --- a/docs/_build/html/_modules/pyemu/en.html +++ /dev/null @@ -1,1714 +0,0 @@ - - - - - - - - pyemu.en — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.en

    -from __future__ import print_function, division
    -import os
    -from datetime import datetime
    -import copy
    -import warnings
    -warnings.filterwarnings("ignore",category=UserWarning)
    -from .pyemu_warnings import PyemuWarning
    -import math
    -import numpy as np
    -import pandas as pd
    -
    -from pyemu.mat.mat_handler import get_common_elements,Matrix,Cov,SparseMatrix
    -from pyemu.pst.pst_utils import write_parfile,read_parfile
    -from pyemu.plot.plot_utils import ensemble_helper
    -
    -#warnings.filterwarnings("ignore",message="Pandas doesn't allow columns to be "+\
    -#                                         "created via a new attribute name - see"+\
    -#                                         "https://pandas.pydata.org/pandas-docs/"+\
    -#                                         "stable/indexing.html#attribute-access")
    -SEED = 358183147 #from random.org on 5 Dec 2016
    -#print("setting random seed")
    -np.random.seed(SEED)
    -
    -
    -
    [docs]class Ensemble(pd.DataFrame): - """ The base class type for handling parameter and observation ensembles. - It is directly derived from pandas.DataFrame. This class should not be - instantiated directly. - - Parameters - ---------- - *args : list - positional args to pass to pandas.DataFrame() - **kwargs : dict - keyword args to pass to pandas.DataFrame(). Must contain - 'columns' and 'mean_values' - - Returns - ------- - Ensemble : Ensemble - - """ - def __init__(self,*args,**kwargs): - """constructor for base Ensemble type. 'columns' and 'mean_values' - must be in the kwargs - """ - - assert "columns" in kwargs.keys(),"ensemble requires 'columns' kwarg" - - mean_values = kwargs.pop("mean_values",None) - - super(Ensemble,self).__init__(*args,**kwargs) - - if mean_values is None: - raise Exception("Ensemble requires 'mean_values' kwarg") - self._mean_values = mean_values - -
    [docs] def as_pyemu_matrix(self,typ=Matrix): - """ - Create a pyemu.Matrix from the Ensemble. - - Parameters - ---------- - typ : pyemu.Matrix or derived type - the type of matrix to return - - Returns - ------- - pyemu.Matrix : pyemu.Matrix - - """ - x = self.values.copy().astype(np.float) - return typ(x=x,row_names=list(self.index), - col_names=list(self.columns))
    - -
    [docs] def drop(self,arg): - """ overload of pandas.DataFrame.drop() - - Parameters - ---------- - arg : iterable - argument to pass to pandas.DataFrame.drop() - - Returns - ------- - Ensemble : Ensemble - - """ - df = super(Ensemble,self).drop(arg) - return type(self)(data=df,pst=self.pst)
    - -
    [docs] def dropna(self,*args,**kwargs): - """overload of pandas.DataFrame.dropna() - - Parameters - ---------- - *args : list - positional args to pass to pandas.DataFrame.dropna() - **kwargs : dict - keyword args to pass to pandas.DataFrame.dropna() - - Returns - ------- - Ensemble : Ensemble - - """ - df = super(Ensemble,self).dropna(*args,**kwargs) - return type(self)(data=df,pst=self.pst)
    - -
    [docs] def draw(self,cov,num_reals=1,names=None): - """ draw random realizations from a multivariate - Gaussian distribution - - Parameters - ---------- - cov: pyemu.Cov - covariance structure to draw from - num_reals: int - number of realizations to generate - names : list - list of columns names to draw for. If None, values all names - are drawn - - """ - real_names = np.arange(num_reals,dtype=np.int64) - - # make sure everything is cool WRT ordering - if names is not None: - vals = self.mean_values.loc[names] - cov = cov.get(names) - elif self.names != cov.row_names: - names = get_common_elements(self.names, - cov.row_names) - vals = self.mean_values.loc[names] - cov = cov.get(names) - pass - else: - vals = self.mean_values - names = self.names - - # generate random numbers - if cov.isdiagonal: #much faster - val_array = np.array([np.random.normal(mu,std,size=num_reals) for\ - mu,std in zip(vals,np.sqrt(cov.x))]).transpose() - else: - val_array = np.random.multivariate_normal(vals, cov.as_2d,num_reals) - - self.loc[:,:] = np.NaN - self.dropna(inplace=True) - - # this sucks - can only set by enlargement one row at a time - for rname,vals in zip(real_names,val_array): - self.loc[rname, names] = vals - # set NaNs to mean_values - idx = pd.isnull(self.loc[rname,:]) - self.loc[rname,idx] = self.mean_values[idx]
    - - # def enforce(self): - # """ placeholder method for derived ParameterEnsemble type - # to enforce parameter bounds - # - # - # Raises - # ------ - # Exception if called - # """ - # raise Exception("Ensemble.enforce() must overloaded by derived types") - -
    [docs] def plot(self,bins=10,facecolor='0.5',plot_cols=None, - filename="ensemble.pdf",func_dict = None, - **kwargs): - """plot ensemble histograms to multipage pdf - - Parameters - ---------- - bins : int - number of bins - facecolor : str - color - plot_cols : list of str - subset of ensemble columns to plot. If None, all are plotted. - Default is None - filename : str - pdf filename. Default is "ensemble.pdf" - func_dict : dict - a dict of functions to apply to specific columns (e.g., np.log10) - - **kwargs : dict - keyword args to pass to plot_utils.ensemble_helper() - - Returns - ------- - None - - """ - ensemble_helper(self,bins=bins,facecolor=facecolor,plot_cols=plot_cols, - filename=filename)
    - - - def __sub__(self,other): - """overload of pandas.DataFrame.__sub__() operator to difference two - Ensembles - - Parameters - ---------- - other : pyemu.Ensemble or pandas.DataFrame - the instance to difference against - - Returns - ------- - Ensemble : Ensemble - - """ - diff = super(Ensemble,self).__sub__(other) - return Ensemble.from_dataframe(df=diff) - - -
    [docs] @classmethod - def from_dataframe(cls,**kwargs): - """class method constructor to create an Ensemble from - a pandas.DataFrame - - Parameters - ---------- - **kwargs : dict - optional args to pass to the - Ensemble Constructor. Expects 'df' in kwargs.keys() - that must be a pandas.DataFrame instance - - Returns - ------- - Ensemble : Ensemble - """ - df = kwargs.pop("df") - assert isinstance(df,pd.DataFrame) - df.columns = [c.lower() for c in df.columns] - mean_values = kwargs.pop("mean_values",df.mean(axis=0)) - e = cls(data=df,index=df.index,columns=df.columns, - mean_values=mean_values,**kwargs) - return e
    - -
    [docs] @staticmethod - def reseed(): - """method to reset the numpy.random seed using the pyemu.en - SEED global variable - - """ - np.random.seed(SEED)
    - -
    [docs] def copy(self): - """make a deep copy of self - - Returns - ------- - Ensemble : Ensemble - - """ - df = super(Ensemble,self).copy() - return type(self).from_dataframe(df=df)
    - - - -
    [docs] def covariance_matrix(self,localizer=None): - """calculate the approximate covariance matrix implied by the ensemble using - mean-differencing operation at the core of EnKF - - Parameters - ---------- - localizer : pyemu.Matrix - covariance localizer to apply - - Returns - ------- - cov : pyemu.Cov - covariance matrix - - """ - - - - - mean = np.array(self.mean(axis=0)) - delta = self.as_pyemu_matrix(typ=Cov) - for i in range(self.shape[0]): - delta.x[i, :] -= mean - delta *= (1.0 / np.sqrt(float(self.shape[0] - 1.0))) - - if localizer is not None: - delta = delta.T * delta - return delta.hadamard_product(localizer) - - return delta.T * delta
    - - - -
    [docs]class ObservationEnsemble(Ensemble): - """ Ensemble derived type for observations. This class is primarily used to - generate realizations of observation noise. These are typically generated from - the weights listed in the control file. However, a general covariance matrix can - be explicitly used. - - Note: - Does not generate noise realizations for observations with zero weight - """ - - def __init__(self,pst,**kwargs): - """ObservationEnsemble constructor. - - Parameters - ---------- - pst : pyemu.Pst - required Ensemble constructor kwwargs - 'columns' and 'mean_values' are generated from pst.observation_data.obsnme - and pst.observation_data.obsval resepctively. - - **kwargs : dict - keyword args to pass to Ensemble constructor - - Returns - ------- - ObservationEnsemble : ObservationEnsemble - - """ - kwargs["columns"] = pst.observation_data.obsnme - kwargs["mean_values"] = pst.observation_data.obsval - super(ObservationEnsemble,self).__init__(**kwargs) - self.pst = pst - self.pst.observation_data.index = self.pst.observation_data.obsnme - -
    [docs] def copy(self): - """overload of Ensemble.copy() - - Returns - ------- - ObservationEnsemble : ObservationEnsemble - - """ - df = super(Ensemble,self).copy() - return type(self).from_dataframe(df=df,pst=self.pst.get())
    - - @property - def names(self): - """property decorated method to get current non-zero weighted - column names. Uses ObservationEnsemble.pst.nnz_obs_names - - Returns - ------- - list : list - non-zero weight observation names - """ - return self.pst.nnz_obs_names - - - @property - def mean_values(self): - """ property decorated method to get mean values of observation noise. - This is a zero-valued pandas.Series - - Returns - ------- - mean_values : pandas Series - - """ - vals = self.pst.observation_data.obsval.copy() - vals.loc[self.names] = 0.0 - return vals - - -
    [docs] def draw(self,cov,num_reals): - """ draw realizations of observation noise and add to mean_values - Note: only draws noise realizations for non-zero weighted observations - zero-weighted observations are set to mean value for all realizations - - Parameters - ---------- - cov : pyemu.Cov - covariance matrix that describes the support volume around the - mean values. - num_reals : int - number of realizations to draw - - """ - super(ObservationEnsemble,self).draw(cov,num_reals, - names=self.pst.nnz_obs_names) - self.loc[:,self.names] += self.pst.observation_data.obsval
    - - @property - def nonzero(self): - """ property decorated method to get a new ObservationEnsemble - of only non-zero weighted observations - - Returns - ------- - ObservationEnsemble : ObservationEnsemble - - """ - df = self.loc[:,self.pst.nnz_obs_names] - return ObservationEnsemble.from_dataframe(df=df, - pst=self.pst.get(obs_names=self.pst.nnz_obs_names)) - -
    [docs] @classmethod - def from_id_gaussian_draw(cls,pst,num_reals): - """ this is an experiemental method to help speed up independent draws - for a really large (>1E6) ensemble sizes. - - Parameters - ---------- - pst : pyemu.Pst - a control file instance - num_reals : int - number of realizations to draw - - Returns - ------- - ObservationEnsemble : ObservationEnsemble - - """ - # set up some column names - real_names = np.arange(num_reals,dtype=np.int64) - #arr = np.empty((num_reals,len(pst.obs_names))) - obs = pst.observation_data - stds = {name:1.0/obs.loc[name,"weight"] for name in pst.nnz_obs_names} - nz_names = set(pst.nnz_obs_names) - arr = np.random.randn(num_reals,pst.nobs) - for i,oname in enumerate(pst.obs_names): - if oname in nz_names: - arr[:,i] *= stds[oname] - else: - arr[:,i] = 0.0 - df = pd.DataFrame(arr,index=real_names,columns=pst.obs_names) - df.loc[:,pst.obs_names] += pst.observation_data.obsval - new_oe = cls.from_dataframe(pst=pst,df=df) - return new_oe
    - -
    [docs] def to_binary(self, filename): - """write the observation ensemble to a jco-style binary file. The - ensemble is transposed in the binary file so that the 20-char obs - names are carried - - Parameters - ---------- - filename : str - the filename to write - - Returns - ------- - None - - - Note - ---- - The ensemble is transposed in the binary file - - """ - self.as_pyemu_matrix().T.to_binary(filename)
    - - -
    [docs] @classmethod - def from_binary(cls,pst,filename): - """instantiate an observation obsemble from a jco-type file - - Parameters - ---------- - pst : pyemu.Pst - a Pst instance - filename : str - the binary file name - - Returns - ------- - oe : ObservationEnsemble - - """ - m = Matrix.from_binary(filename) - return ObservationEnsemble(data=m.T.x,pst=pst)
    - - - @property - def phi_vector(self): - """property decorated method to get a vector of L2 norm (phi) - for the realizations. The ObservationEnsemble.pst.weights can be - updated prior to calling this method to evaluate new weighting strategies - - Return - ------ - pandas.DataFrame : pandas.DataFrame - - """ - weights = self.pst.observation_data.loc[self.names,"weight"] - obsval = self.pst.observation_data.loc[self.names,"obsval"] - phi_vec = [] - for idx in self.index.values: - simval = self.loc[idx,self.names] - phi = (((simval - obsval) * weights)**2).sum() - phi_vec.append(phi) - #return pd.DataFrame({"phi":phi_vec},index=self.index) - return pd.Series(data=phi_vec,index=self.index) - - -
    [docs] def add_base(self): - """ add "base" control file values as a realization - - """ - if "base" in self.index: - raise Exception("'base' already in index") - self.loc["base",:] = self.pst.observation_data.loc[self.columns,"obsval"]
    - - -
    [docs]class ParameterEnsemble(Ensemble): - """ Ensemble derived type for parameters - implements bounds enforcement, log10 transformation, - fixed parameters and null-space projection - Note: uses the parnme attribute of Pst.parameter_data from column names - and uses the parval1 attribute of Pst.parameter_data as mean values - - Parameters - ---------- - pst : pyemu.Pst - The 'columns' and 'mean_values' args need for Ensemble - are derived from the pst.parameter_data.parnme and pst.parameter_data.parval1 - items, respectively - istransformed : bool - flag indicating the transformation status (log10) of the arguments be passed - **kwargs : dict - keyword arguments to pass to Ensemble constructor. - bound_tol : float - fractional amount to reset bounds transgression within the bound. - This has been shown to be very useful for the subsequent recalibration - because it moves parameters off of their bounds, so they are not treated as frozen in - the upgrade calculations. defaults to 0.0 - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - - """ - - def __init__(self,pst,istransformed=False,**kwargs): - """ ParameterEnsemble constructor. - - - """ - if "columns" not in kwargs: - kwargs["columns"] = pst.parameter_data.parnme - if "mean_values" not in kwargs: - kwargs["mean_values"] = pst.parameter_data.parval1 - - super(ParameterEnsemble,self).__init__(**kwargs) - # a flag for current log transform status - self.__istransformed = bool(istransformed) - self.pst = pst - if "tied" in list(self.pst.parameter_data.partrans.values): - #raise NotImplementedError("ParameterEnsemble does not " +\ - # "support tied parameters") - warnings.warn("tied parameters are treated as fixed in "+\ - "ParameterEnsemble",PyemuWarning) - self.pst.parameter_data.index = self.pst.parameter_data.parnme - self.bound_tol = kwargs.get("bound_tol",0.0) - -
    [docs] def dropna(self, *args, **kwargs): - """overload of pandas.DataFrame.dropna() - - Parameters - ---------- - *args : list - positional args to pass to pandas.DataFrame.dropna() - **kwargs : dict - keyword args to pass to pandas.DataFrame.dropna() - - Returns - ------- - Ensemble : Ensemble - - """ - df = super(Ensemble, self).dropna(*args, **kwargs) - if df is not None: - pe = ParameterEnsemble.from_dataframe(df=df,pst=self.pst) - pe.__istransformed = self.istransformed - return pe
    - -
    [docs] def copy(self): - """ overload of Ensemble.copy() - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - - """ - df = super(Ensemble,self).copy() - pe = ParameterEnsemble.from_dataframe(df=df,pst=self.pst.get()) - pe.__istransformed = self.istransformed - return pe
    - - @property - def istransformed(self): - """property decorated method to get the current - transformation status of the ParameterEnsemble - - Returns - ------- - istransformed : bool - - """ - return copy.copy(self.__istransformed) - - @property - def mean_values(self): - """ the mean value vector while respecting log transform - - Returns - ------- - mean_values : pandas.Series - - """ - if not self.istransformed: - return self.pst.parameter_data.parval1.copy() - else: - # vals = (self.pst.parameter_data.parval1 * - # self.pst.parameter_data.scale) +\ - # self.pst.parameter_data.offset - vals = self.pst.parameter_data.parval1.copy() - vals[self.log_indexer] = np.log10(vals[self.log_indexer]) - return vals - - @property - def names(self): - """ Get the names of the parameters in the ParameterEnsemble - - Returns - ------- - list : list - parameter names - - """ - return list(self.pst.parameter_data.parnme) - - @property - def adj_names(self): - """ Get the names of adjustable parameters in the ParameterEnsemble - - Returns - ------- - list : list - adjustable parameter names - - """ - return list(self.pst.parameter_data.parnme.loc[~self.fixed_indexer]) - - @property - def ubnd(self): - """ the upper bound vector while respecting log transform - - Returns - ------- - ubnd : pandas.Series - - """ - if not self.istransformed: - return self.pst.parameter_data.parubnd.copy() - else: - ub = self.pst.parameter_data.parubnd.copy() - ub[self.log_indexer] = np.log10(ub[self.log_indexer]) - return ub - - @property - def lbnd(self): - """ the lower bound vector while respecting log transform - - Returns - ------- - lbnd : pandas.Series - - """ - if not self.istransformed: - return self.pst.parameter_data.parlbnd.copy() - else: - lb = self.pst.parameter_data.parlbnd.copy() - lb[self.log_indexer] = np.log10(lb[self.log_indexer]) - return lb - - @property - def log_indexer(self): - """ indexer for log transform - - Returns - ------- - log_indexer : pandas.Series - - """ - istransformed = self.pst.parameter_data.partrans == "log" - return istransformed.values - - @property - def fixed_indexer(self): - """ indexer for fixed status - - Returns - ------- - fixed_indexer : pandas.Series - - """ - #isfixed = self.pst.parameter_data.partrans == "fixed" - isfixed = self.pst.parameter_data.partrans.\ - apply(lambda x : x in ["fixed","tied"]) - return isfixed.values - - - -
    [docs] def draw(self,cov,num_reals=1,how="normal",enforce_bounds=None): - """draw realizations of parameter values - - Parameters - ---------- - cov : pyemu.Cov - covariance matrix that describes the support around - the mean parameter values - num_reals : int - number of realizations to generate - how : str - distribution to use to generate realizations. Options are - 'normal' or 'uniform'. Default is 'normal'. If 'uniform', - cov argument is ignored - enforce_bounds : str - how to enforce parameter bound violations. Options are - 'reset' (reset individual violating values), 'drop' (drop realizations - that have one or more violating values. Default is None (no bounds enforcement) - - """ - how = how.lower().strip() - if not self.istransformed: - self._transform() - if how == "uniform": - self._draw_uniform(num_reals=num_reals) - else: - super(ParameterEnsemble,self).draw(cov,num_reals=num_reals) - # replace the realizations for fixed parameters with the original - # parval1 in the control file - self.pst.parameter_data.index = self.pst.parameter_data.parnme - fixed_vals = self.pst.parameter_data.loc[self.fixed_indexer,"parval1"] - for fname,fval in zip(fixed_vals.index,fixed_vals.values): - #if fname not in self.columns: - # continue - self.loc[:,fname] = fval - istransformed = self.pst.parameter_data.loc[:,"partrans"] == "log" - self.loc[:,istransformed] = 10.0**self.loc[:,istransformed] - self.__istransformed = False - - #self._applied_tied() - - - self.enforce(enforce_bounds)
    - - def _draw_uniform(self,num_reals=1): - """ Draw parameter realizations from a (log10) uniform distribution - described by the parameter bounds. Respect Log10 transformation - - Parameters - ---------- - num_reals : int - number of realizations to generate - - """ - if not self.istransformed: - self._transform() - self.loc[:,:] = np.NaN - self.dropna(inplace=True) - ub = self.ubnd - lb = self.lbnd - for pname in self.names: - if pname in self.adj_names: - self.loc[:,pname] = np.random.uniform(lb[pname], - ub[pname], - size=num_reals) - else: - self.loc[:,pname] = np.zeros((num_reals)) + \ - self.pst.parameter_data.\ - loc[pname,"parval1"] - -
    [docs] @classmethod - def from_uniform_draw(cls,pst,num_reals): - """ this is an experiemental method to help speed up uniform draws - for a really large (>1E6) ensemble sizes. WARNING: this constructor - transforms the pe argument - - Parameters - ---------- - pst : pyemu.Pst - a control file instance - num_reals : int - number of realizations to generate - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - - """ - #if not pe.istransformed: - # pe._transform() - #ub = pe.ubnd - #lb = pe.lbnd - li = pst.parameter_data.partrans == "log" - ub = pst.parameter_data.parubnd.copy() - ub.loc[li] = ub.loc[li].apply(np.log10) - ub = ub.to_dict() - lb = pst.parameter_data.parlbnd.copy() - lb.loc[li] = lb.loc[li].apply(np.log10) - lb = lb.to_dict() - - # set up some column names - #real_names = ["{0:d}".format(i) - # for i in range(num_reals)] - real_names = np.arange(num_reals,dtype=np.int64) - arr = np.empty((num_reals,len(ub))) - for i,pname in enumerate(pst.parameter_data.parnme): - print(pname,lb[pname],ub[pname]) - if pname in pst.adj_par_names: - arr[:,i] = np.random.uniform(lb[pname], - ub[pname], - size=num_reals) - else: - arr[:,i] = np.zeros((num_reals)) + \ - pe.pst.parameter_data.\ - loc[pname,"parval1"] - print("back transforming") - - df = pd.DataFrame(arr,index=real_names,columns=pst.par_names) - df.loc[:,li] = 10.0**df.loc[:,li] - - new_pe = cls.from_dataframe(pst=pst,df=pd.DataFrame(data=arr,columns=pst.par_names)) - #new_pe._applied_tied() - return new_pe
    - - -
    [docs] @classmethod - def from_sparse_gaussian_draw(cls,pst,cov,num_reals): - """ instantiate a parameter ensemble from a sparse covariance matrix. - This is an advanced user method that assumes you know what you are doing - - few guard rails... - - Parameters - ---------- - pst : pyemu.Pst - a control file instance - cov : (pyemu.SparseMatrix) - sparse covariance matrix to use for drawing - num_reals : int - number of realizations to generate - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - - """ - - - assert isinstance(cov,SparseMatrix) - real_names = np.arange(num_reals, dtype=np.int64) - - li = pst.parameter_data.partrans == "log" - vals = pst.parameter_data.parval1.copy() - vals.loc[li] = vals.loc[li].apply(np.log10) - - - par_cov = pst.parameter_data.loc[cov.row_names, :] - par_cov.loc[:, "idxs"] = np.arange(cov.shape[0]) - # print("algning cov") - # cov.align(list(par_cov.parnme)) - pargps = par_cov.pargp.unique() - print("reserving reals matrix") - reals = np.zeros((num_reals, cov.shape[0])) - - for ipg, pargp in enumerate(pargps): - pnames = list(par_cov.loc[par_cov.pargp == pargp, "parnme"]) - idxs = par_cov.loc[par_cov.pargp == pargp, "idxs"] - print("{0} of {1} drawing for par group '{2}' with {3} pars " - .format(ipg + 1, len(pargps), pargp, len(idxs))) - - snv = np.random.randn(num_reals, len(pnames)) - - print("...extracting cov from sparse matrix") - cov_pg = cov.get_matrix(col_names=pnames,row_names=pnames) - if len(pnames) == 1: - std = np.sqrt(cov_pg.x) - reals[:, idxs] = vals[pnames].values[0] + (snv * std) - else: - try: - cov_pg.inv - except: - covname = "trouble_{0}.cov".format(pargp) - print('saving toubled cov matrix to {0}'.format(covname)) - cov_pg.to_ascii(covname) - print(cov_pg.get_diagonal_vector()) - raise Exception("error inverting cov for par group '{0}'," + \ - "saved trouble cov to {1}". - format(pargp, covname)) - v, w = np.linalg.eigh(cov_pg.as_2d) - # check for near zero eig values - - # vdiag = np.diag(v) - for i in range(v.shape[0]): - if v[i] > 1.0e-10: - pass - else: - print("near zero eigen value found", v[i], \ - "at index", i, " of ", v.shape[0]) - v[i] = 0.0 - vsqrt = np.sqrt(v) - vsqrt[i:] = 0.0 - v = np.diag(vsqrt) - a = np.dot(w, v) - pg_vals = vals[pnames] - for i in range(num_reals): - # v = snv[i,:] - # p = np.dot(a,v) - reals[i, idxs] = pg_vals + np.dot(a, snv[i, :]) - - df = pd.DataFrame(reals, columns=cov.row_names, index=real_names) - df.loc[:, li] = 10.0 ** df.loc[:, li] - - # replace the realizations for fixed parameters with the original - # parval1 in the control file - print("handling fixed pars") - # pe.pst.parameter_data.index = pe.pst.parameter_data.parnme - par = pst.parameter_data - fixed_vals = par.loc[par.partrans == "fixed", "parval1"] - for fname, fval in zip(fixed_vals.index, fixed_vals.values): - # print(fname) - df.loc[:, fname] = fval - - # print("apply tied") - new_pe = cls.from_dataframe(pst=pst, df=df) - - return new_pe
    - -
    [docs] @classmethod - def from_gaussian_draw(cls,pst,cov,num_reals=1,use_homegrown=True,group_chunks=False, - fill_fixed=True,enforce_bounds=False): - """ instantiate a parameter ensemble from a covariance matrix - - Parameters - ---------- - pst : pyemu.Pst - a control file instance - cov : (pyemu.Cov) - covariance matrix to use for drawing - num_reals : int - number of realizations to generate - use_homegrown : bool - flag to use home-grown full cov draws...much faster - than numpy... - group_chunks : bool - flag to break up draws by par groups. Only applies - to homegrown, full cov case. Default is False - fill_fixed : bool - flag to fill in fixed parameters from the pst into the - ensemble using the parval1 from the pst. Default is True - enforce_bounds : bool - flag to enforce parameter bounds from the pst. realized - parameter values that violate bounds are simply changed to the - value of the violated bound. Default is False - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - - - """ - - # set up some column names - #real_names = ["{0:d}".format(i) - # for i in range(num_reals)] - real_names = np.arange(num_reals,dtype=np.int64) - - li = pst.parameter_data.partrans == "log" - vals = pst.parameter_data.parval1.copy() - vals[li] = vals.loc[li].apply(np.log10) - - # make sure everything is cool WRT ordering - if list(vals.index.values) != cov.row_names: - common_names = get_common_elements(vals.index.values, - cov.row_names) - if len(common_names) == 0: - raise Exception("ParameterEnsemble::from_gaussian_draw() error: cov and pst share no common names") - vals = vals.loc[common_names] - cov = cov.get(common_names) - pass - else: - common_names = cov.row_names - - li = pst.parameter_data.partrans.loc[common_names] == "log" - if cov.isdiagonal: - print("making diagonal cov draws") - print("building mean and std dicts") - arr = np.zeros((num_reals,len(vals))) - stds = {pname:std for pname,std in zip(common_names,np.sqrt(cov.x.flatten()))} - means = {pname:val for pname,val in zip(common_names,vals)} - print("numpy draw") - arr = np.random.randn(num_reals,len(common_names)) - print("post-processing") - adj_pars = set(pst.adj_par_names) - for i,pname in enumerate(common_names): - if pname in adj_pars: - #s = stds[pname] - #v = means[pname] - #arr[:,i] = np.random.normal(means[pname],stds[pname], - # size=num_reals) - arr[:,i] = (arr[:,i] * stds[pname]) + means[pname] - else: - arr[:,i] = means[pname] - print("build df") - df = pd.DataFrame(data=arr,columns=common_names,index=real_names) - else: - if use_homegrown: - print("making full cov draws with home-grown goodness") - # generate standard normal vectors - - - # jwhite - 18-dec-17: the cholesky version is giving significantly diff - # results compared to eigen solve, so turning this off for now - need to - # learn more about this... - # use_chol = False - # if use_chol: - # a = np.linalg.cholesky(cov.as_2d) - # - # else: - # decompose... - if group_chunks: - par_cov = pst.parameter_data.loc[cov.names,:] - par_cov.loc[:,"idxs"] = np.arange(cov.shape[0]) - #print("algning cov") - #cov.align(list(par_cov.parnme)) - pargps = par_cov.pargp.unique() - print("reserving reals matrix") - reals = np.zeros((num_reals,cov.shape[0])) - - for ipg,pargp in enumerate(pargps): - pnames = list(par_cov.loc[par_cov.pargp==pargp,"parnme"]) - idxs = par_cov.loc[par_cov.pargp == pargp, "idxs"] - print("{0} of {1} drawing for par group '{2}' with {3} pars " - .format(ipg+1,len(pargps),pargp, len(idxs))) - - s,e = idxs[0],idxs[-1] - #print("generating snv matrix") - snv = np.random.randn(num_reals, len(pnames)) - - cov_pg = cov.get(pnames) - if len(pnames) == 1: - std = np.sqrt(cov_pg.x) - reals[:,idxs] = vals[pnames].values[0] + (snv * std) - else: - try: - cov_pg.inv - except: - covname = "trouble_{0}.cov".format(pargp) - print('saving toubled cov matrix to {0}'.format(covname)) - cov_pg.to_ascii(covname) - print(cov_pg.get_diagonal_vector()) - raise Exception("error inverting cov for par group '{0}',"+\ - "saved trouble cov to {1}". - format(pargp,covname)) - v, w = np.linalg.eigh(cov_pg.as_2d) - # check for near zero eig values - - #vdiag = np.diag(v) - for i in range(v.shape[0]): - if v[i] > 1.0e-10: - pass - else: - print("near zero eigen value found",v[i],\ - "at index",i," of ",v.shape[0]) - v[i] = 0.0 - vsqrt = np.sqrt(v) - vsqrt[i:] = 0.0 - v = np.diag(vsqrt) - a = np.dot(w, v) - pg_vals = vals[pnames] - for i in range(num_reals): - #v = snv[i,:] - #p = np.dot(a,v) - reals[i,idxs] = pg_vals + np.dot(a,snv[i,:]) - else: - - print("generating snv matrix") - snv = np.random.randn(num_reals, cov.shape[0]) - - print("eigen solve for full cov") - v, w = np.linalg.eigh(cov.as_2d) - #w, v, other = np.linalg.svd(cov.as_2d,full_matrices=True,compute_uv=True) - # vdiag = np.diag(v) - for i in range(v.shape[0]): - if v[i] > 1.0e-10: - pass - else: - print("near zero eigen value found", v[i], \ - "at index", i, " of ", v.shape[0]) - v[i] = 0.0 - # form projection matrix - print("form projection") - a = np.dot(w, np.sqrt(np.diag(v))) - #print(a) - # project... - reals = [] - for vec in snv: - real = vals + np.dot(a, vec) - reals.append(real) - - df = pd.DataFrame(reals, columns=common_names, index=real_names) - - #vals = pe.mean_values - else: - print("making full cov draws with numpy") - df = pd.DataFrame(data=np.random.multivariate_normal(vals, cov.as_2d,num_reals), - columns = common_names,index=real_names) - #print(df.shape,cov.shape) - - - df.loc[:,li] = 10.0**df.loc[:,li] - - # replace the realizations for fixed parameters with the original - # parval1 in the control file - print("handling fixed pars") - #pe.pst.parameter_data.index = pe.pst.parameter_data.parnme - if fill_fixed: - par = pst.parameter_data - fixed_vals = par.loc[par.partrans.apply(lambda x: x in ["fixed","tied"]),"parval1"] - for fname,fval in zip(fixed_vals.index,fixed_vals.values): - #print(fname) - df.loc[:,fname] = fval - - #print("apply tied") - new_pe = cls.from_dataframe(pst=pst,df=df) - if enforce_bounds: - new_pe.enforce() - return new_pe
    - -
    [docs] @classmethod - def from_binary(cls, pst, filename): - """instantiate an parameter obsemble from a jco-type file - - Parameters - ---------- - pst : pyemu.Pst - a Pst instance - filename : str - the binary file name - - Returns - ------- - pe : ParameterEnsemble - - """ - m = Matrix.from_binary(filename).to_dataframe() - - return ParameterEnsemble.from_dataframe(df=m, pst=pst)
    - - def _back_transform(self,inplace=True): - """ Private method to remove log10 transformation from ensemble - - Parameters - ---------- - inplace: bool - back transform self in place - - Returns - ------ - ParameterEnsemble : ParameterEnsemble - if inplace if False - - Note - ---- - Don't call this method unless you know what you are doing - - """ - if not self.istransformed: - raise Exception("ParameterEnsemble already back transformed") - - istransformed = self.pst.parameter_data.loc[:,"partrans"] == "log" - if inplace: - self.loc[:,istransformed] = 10.0**(self.loc[:,istransformed]) - self.loc[:,:] = (self.loc[:,:] -\ - self.pst.parameter_data.offset)/\ - self.pst.parameter_data.scale - - self.__istransformed = False - else: - vals = (self.pst.parameter_data.parval1 -\ - self.pst.parameter_data.offset) /\ - self.pst.parameter_data.scale - new_en = ParameterEnsemble(pst=self.pst.get(),data=self.loc[:,:].copy(), - columns=self.columns, - mean_values=vals,istransformed=False) - new_en.loc[:,istransformed] = 10.0**(self.loc[:,istransformed]) - new_en.loc[:,:] = (new_en.loc[:,:] -\ - new_en.pst.parameter_data.offset)/\ - new_en.pst.parameter_data.scale - return new_en - - - def _transform(self,inplace=True): - """ Private method to perform log10 transformation for ensemble - - Parameters - ---------- - inplace: bool - transform self in place - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - if inplace is False - - Note - ---- - Don't call this method unless you know what you are doing - - """ - if self.istransformed: - raise Exception("ParameterEnsemble already transformed") - - istransformed = self.pst.parameter_data.loc[:,"partrans"] == "log" - if inplace: - #self.loc[:,istransformed] = np.log10(self.loc[:,istransformed]) - self.loc[:,:] = (self.loc[:,:] * self.pst.parameter_data.scale) +\ - self.pst.parameter_data.offset - self.loc[:,istransformed] = self.loc[:,istransformed].applymap(lambda x: math.log10(x)) - - self.__istransformed = True - else: - vals = self.pst.parameter_data.parval1.copy() - new_en = ParameterEnsemble(pst=self.pst.get(),data=self.loc[:,:].copy(), - columns=self.columns, - mean_values=vals,istransformed=True) - new_en.loc[:,:] = (new_en.loc[:,:] * self.pst.parameter_data.scale) +\ - new_en.pst.parameter_data.offset - new_en.loc[:,istransformed] = self.loc[:,istransformed].applymap(lambda x: math.log10(x)) - return new_en - - - -
    [docs] def project(self,projection_matrix,inplace=True,log=None, - enforce_bounds="reset"): - """ project the ensemble using the null-space Monte Carlo method - - Parameters - ---------- - projection_matrix : pyemu.Matrix - projection operator - must already respect log transform - - inplace : bool - project self or return a new ParameterEnsemble instance - - log: pyemu.Logger - for logging progress - - enforce_bounds : str - parameter bound enforcement flag. 'drop' removes - offending realizations, 'reset' resets offending values - - Returns - ------- - ParameterEnsemble : ParameterEnsemble - if inplace is False - - """ - - if self.istransformed: - self._back_transform() - - istransformed = self.pst.parameter_data.loc[:,"partrans"] == "log" - self.loc[:,istransformed] = self.loc[:,istransformed].applymap(lambda x: math.log10(x)) - self.__istransformed = True - - #make sure everything is cool WRT ordering - common_names = get_common_elements(self.adj_names, - projection_matrix.row_names) - base = self.mean_values.loc[common_names] - projection_matrix = projection_matrix.get(common_names,common_names) - - if not inplace: - new_en = ParameterEnsemble(pst=self.pst.get(),data=self.loc[:,:].copy(), - columns=self.columns, - mean_values=self.mean_values.copy(), - istransformed=self.istransformed) - - for real in self.index: - if log is not None: - log("projecting realization {0}".format(real)) - - # null space projection of difference vector - pdiff = self.loc[real,common_names] - base - pdiff = np.dot(projection_matrix.x, - (self.loc[real,common_names] - base)\ - .values) - - if inplace: - self.loc[real,common_names] = base + pdiff - else: - new_en.loc[real,common_names] = base + pdiff - - if log is not None: - log("projecting realization {0}".format(real)) - if not inplace: - new_en.enforce(enforce_bounds) - new_en.loc[:,istransformed] = 10.0**new_en.loc[:,istransformed] - new_en.__istransformed = False - - #new_en._back_transform() - return new_en - - self.enforce(enforce_bounds) - self.loc[:,istransformed] = 10.0**self.loc[:,istransformed] - self.__istransformed = False
    - -
    [docs] def enforce(self,enforce_bounds="reset"): - """ entry point for bounds enforcement. This gets called for the - draw method(s), so users shouldn't need to call this - - Parameters - ---------- - enforce_bounds : str - can be 'reset' to reset offending values or 'drop' to drop - offending realizations - - """ - if isinstance(enforce_bounds,bool): - import warnings - warnings.warn("deprecation warning: enforce_bounds should be "+\ - "either 'reset', 'drop', 'scale', or None, not bool"+\ - "...resetting to None.",PyemuWarning) - enforce_bounds = None - if enforce_bounds is None: - return - - if enforce_bounds.lower() == "reset": - self.enforce_reset() - elif enforce_bounds.lower() == "drop": - self.enforce_drop() - elif enforce_bounds.lower() == "scale": - self.enfore_scale() - else: - raise Exception("unrecognized enforce_bounds arg:"+\ - "{0}, should be 'reset' or 'drop'".\ - format(enforce_bounds))
    - -
    [docs] def enfore_scale(self): - """ - enforce parameter bounds on the ensemble by finding the - scaling factor needed to bring the most violated parameter back in bounds - - Note - ---- - this method not fully implemented. - - Raises - ------ - NotImplementedError if called. - - """ - raise NotImplementedError()
    - # ub = self.ubnd - # lb = self.lbnd - # for id in self.index: - # mx_diff = (self.loc[id,:] - ub) / ub - # mn_diff = (lb - self.loc[id,:]) / lb - # - # # if this real has a violation - # mx = max(mx_diff.max(),mn_diff.max()) - # if mx > 1.0: - # scale_factor = 1.0 / mx - # self.loc[id,:] *= scale_factor - # - # mx = ub - self.loc[id,:] - # mn = lb - self.loc[id,:] - # print(mx.loc[mx<0.0]) - # print(mn.loc[mn>0.0]) - # if (ub - self.loc[id,:]).min() < 0.0 or\ - # (lb - self.loc[id,:]).max() > 0.0: - # raise Exception() - -
    [docs] def enforce_drop(self): - """ enforce parameter bounds on the ensemble by dropping - violating realizations - - """ - ub = self.ubnd - lb = self.lbnd - drop = [] - for id in self.index: - #mx = (ub - self.loc[id,:]).min() - #mn = (lb - self.loc[id,:]).max() - if (ub - self.loc[id,:]).min() < 0.0 or\ - (lb - self.loc[id,:]).max() > 0.0: - drop.append(id) - self.loc[drop,:] = np.NaN - self.dropna(inplace=True)
    - - -
    [docs] def enforce_reset(self): - """enforce parameter bounds on the ensemble by resetting - violating vals to bound - """ - - ub = (self.ubnd * (1.0+self.bound_tol)).to_dict() - lb = (self.lbnd * (1.0 - self.bound_tol)).to_dict() - #for iname,name in enumerate(self.columns): - #self.loc[self.loc[:,name] > ub[name],name] = ub[name] * (1.0 + self.bound_tol) - #self.loc[self.loc[:,name] < lb[name],name] = lb[name].copy() * (1.0 - self.bound_tol) - # self.loc[self.loc[:,name] > ub[name],name] = ub[name] - # self.loc[self.loc[:,name] < lb[name],name] = lb[name] - - val_arr = self.values - for iname, name in enumerate(self.columns): - val_arr[val_arr[:,iname] > ub[name],iname] = ub[name] - val_arr[val_arr[:, iname] < lb[name],iname] = lb[name]
    - - -
    [docs] def read_parfiles_prefix(self,prefix): - """ thin wrapper around read_parfiles using the pnulpar prefix concept. Used to - fill ParameterEnsemble from PEST-type par files - - Parameters - ---------- - prefix : str - the par file prefix - - """ - raise Exception("ParameterEnsemble.read_parfiles_prefix() is deprecated. Use ParameterEnsemble.from_parfiles()")
    - - # pfile_count = 1 - # parfile_names = [] - # while True: - # pfile_name = prefix +"{0:d}.par".format(pfile_count) - # if not os.path.exists(pfile_name): - # break - # parfile_names.append(pfile_name) - # pfile_count += 1 - # - # if len(parfile_names) == 0: - # raise Exception("ParameterEnsemble.read_parfiles_prefix() error: " + \ - # "no parfiles found with prefix {0}".format(prefix)) - # - # return self.read_parfiles(parfile_names) - - -
    [docs] def read_parfiles(self,parfile_names): - """ read the ParameterEnsemble realizations from par files. Used to fill - the ParameterEnsemble with realizations from PEST-type par files - - Parameters - ---------- - parfile_names: list - list of par files to load - - Note - ---- - log transforms after loading according and possibly resets - self.__istransformed - - """ - raise Exception("ParameterEnsemble.read_parfiles() is deprecated. Use ParameterEnsemble.from_parfiles()")
    - # for pfile in parfile_names: - # assert os.path.exists(pfile),"ParameterEnsemble.read_parfiles() error: " +\ - # "file: {0} not found".format(pfile) - # df = read_parfile(pfile) - # self.loc[pfile] = df.loc[:,'parval1'] - # self.loc[:,:] = self.loc[:,:].astype(np.float64) - -
    [docs] @classmethod - def from_parfiles(cls,pst,parfile_names,real_names=None): - """ create a parameter ensemble from parfiles. Accepts parfiles with less than the - parameters in the control (get NaNs in the ensemble) or extra parameters in the - parfiles (get dropped) - - Parameters: - pst : pyemu.Pst - - parfile_names : list of str - par file names - - real_names : str - optional list of realization names. If None, a single integer counter is used - - Returns: - pyemu.ParameterEnsemble - - - """ - if isinstance(pst,str): - pst = pyemu.Pst(pst) - dfs = {} - if real_names is not None: - assert len(real_names) == len(parfile_names) - else: - real_names = np.arange(len(parfile_names)) - - for rname,pfile in zip(real_names,parfile_names): - assert os.path.exists(pfile), "ParameterEnsemble.read_parfiles() error: " + \ - "file: {0} not found".format(pfile) - df = read_parfile(pfile) - #check for scale differences - I don't who is dumb enough - #to change scale between par files and pst... - diff = df.scale - pst.parameter_data.scale - if diff.apply(np.abs).sum() > 0.0: - warnings.warn("differences in scale detected, applying scale in par file", - PyemuWarning) - #df.loc[:,"parval1"] *= df.scale - - dfs[rname] = df.parval1.values - - df_all = pd.DataFrame(data=dfs).T - df_all.columns = df.index - - - - if len(pst.par_names) != df_all.shape[1]: - #if len(pst.par_names) < df_all.shape[1]: - # raise Exception("pst is not compatible with par files") - pset = set(pst.par_names) - dset = set(df_all.columns) - diff = pset.difference(dset) - if len(diff) > 0: - warnings.warn("the following parameters are not in the par files (getting NaNs) :{0}". - format(','.join(diff)),PyemuWarning) - blank_df = pd.DataFrame(index=df_all.index,columns=diff) - - df_all = pd.concat([df_all,blank_df],axis=1) - - diff = dset.difference(pset) - if len(diff) > 0: - warnings.warn("the following par file parameters are not in the control (being dropped):{0}". - format(','.join(diff)),PyemuWarning) - df_all = df_all.loc[:, pst.par_names] - - return ParameterEnsemble.from_dataframe(df=df_all,pst=pst)
    - - -
    [docs] def to_csv(self,*args,**kwargs): - """overload of pandas.DataFrame.to_csv() to account - for parameter transformation so that the saved - ParameterEnsemble csv is not in Log10 space - - Parameters - ---------- - *args : list - positional arguments to pass to pandas.DataFrame.to_csv() - **kwrags : dict - keyword arguments to pass to pandas.DataFrame.to_csv() - - Note - ---- - this function back-transforms inplace with respect to - log10 before writing - - """ - retrans = False - if self.istransformed: - self._back_transform(inplace=True) - retrans = True - if self.isnull().values.any(): - warnings.warn("NaN in par ensemble",PyemuWarning) - super(ParameterEnsemble,self).to_csv(*args,**kwargs) - if retrans: - self._transform(inplace=True)
    - - -
    [docs] def to_binary(self,filename): - """write the parameter ensemble to a jco-style binary file - - Parameters - ---------- - filename : str - the filename to write - - Returns - ------- - None - - - Note - ---- - this function back-transforms inplace with respect to - log10 before writing - - """ - - retrans = False - if self.istransformed: - self._back_transform(inplace=True) - retrans = True - if self.isnull().values.any(): - warnings.warn("NaN in par ensemble",PyemuWarning) - self.as_pyemu_matrix().to_binary(filename) - if retrans: - self._transform(inplace=True)
    - - -
    [docs] def to_parfiles(self,prefix): - """ - write the parameter ensemble to PEST-style parameter files - - Parameters - ---------- - prefix: str - file prefix for par files - - Note - ---- - this function back-transforms inplace with respect to - log10 before writing - - """ - if self.isnull().values.any(): - warnings.warn("NaN in par ensemble",PyemuWarning) - if self.istransformed: - self._back_transform(inplace=True) - - par_df = self.pst.parameter_data.loc[:, - ["parnme","parval1","scale","offset"]].copy() - - for real in self.index: - par_file = "{0}{1}.par".format(prefix,real) - par_df.loc[:,"parval1"] =self.loc[real,:] - write_parfile(par_df,par_file)
    - - -
    [docs] def add_base(self): - """ add "base" control file values as a realization - - """ - if "base" in self.index: - raise Exception("'base' already in index") - self.loc["base",:] = self.pst.parameter_data.loc[self.columns,"parval1"]
    - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/ev.html b/docs/_build/html/_modules/pyemu/ev.html deleted file mode 100644 index 6c36a7e3e..000000000 --- a/docs/_build/html/_modules/pyemu/ev.html +++ /dev/null @@ -1,750 +0,0 @@ - - - - - - - - pyemu.ev — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.ev

    -"""module for error variance analysis, using FOSM
    -assumptions.
    -"""
    -from __future__ import print_function, division
    -import numpy as np
    -import pandas as pd
    -from pyemu.la import LinearAnalysis
    -from pyemu.mat.mat_handler import Matrix, Jco, Cov
    -
    -
    [docs]class ErrVar(LinearAnalysis): - """Derived class for error variance analysis. Supports 2-term and - 3-term error variance analysis. Inherits from pyemu.LinearAnalysis. - - - Parameters - ---------- - **kwargs : dict - keyword args to pass to the LinearAnalysis base constructor. - omitted_parameters : list - list of parameters to treat as "omitted". Passing this argument - activates 3-term error variance analysis. - - omitted_parcov : varies - argument that can be cast to a parcov for the omitted parameters. - If None, omitted_parcov will be formed by extracting from the - LinearAnalsis.parcov attribute. - omitted_predictions : varies - argument that can be cast to a "predictions" attribute for - prediction sensitivity vectors WRT omitted parameters. If None, - these vectors will be extracted from the LinearAnalysis.predictions - attribute - kl : bool - flag to perform KL scaling on the jacobian before error variance - calculations - - Note - ---- - There are some additional keyword args that can be passed to active - the 3-term error variance calculation - - - """ - - def __init__(self,jco,**kwargs): - - self.__need_omitted = False - if "omitted_parameters" in kwargs.keys(): - self.omitted_par_arg = kwargs["omitted_parameters"] - kwargs.pop("omitted_parameters") - self.__need_omitted = True - else: - self.omitted_par_arg = None - if "omitted_parcov" in kwargs.keys(): - self.omitted_parcov_arg = kwargs["omitted_parcov"] - kwargs.pop("omitted_parcov") - self.__need_omitted = True - else: - self.omitted_parcov_arg = None - - if "omitted_forecasts" in kwargs.keys(): - self.omitted_predictions_arg = kwargs["omitted_forecasts"] - kwargs.pop("omitted_forecasts") - self.__need_omitted = True - elif "omitted_predictions" in kwargs.keys(): - self.omitted_predictions_arg = kwargs["omitted_predictions"] - kwargs.pop("omitted_predictions") - self.__need_omitted = True - else: - self.omitted_predictions_arg = None - - kl = False - if "kl" in kwargs.keys(): - kl = bool(kwargs["kl"]) - kwargs.pop("kl") - - - self.__qhalfx = None - self.__R = None - self.__R_sv = None - self.__G = None - self.__G_sv = None - self.__I_R = None - self.__I_R_sv = None - self.__omitted_jco = None - self.__omitted_parcov = None - self.__omitted_predictions = None - - # instantiate the parent class - super(ErrVar, self).__init__(jco, **kwargs) - if self.__need_omitted: - self.log("pre-loading omitted components") - #self._LinearAnalysis__load_jco() - #self._LinearAnalysis__load_parcov() - #self._LinearAnalysis__load_obscov() - #if self.prediction_arg is not None: - # self._LinearAnalysis__load_predictions() - self.__load_omitted_jco() - self.__load_omitted_parcov() - if self.prediction_arg is not None: - self.__load_omitted_predictions() - self.log("pre-loading omitted components") - if kl: - self.log("applying KL scaling") - self.apply_karhunen_loeve_scaling() - self.log("applying KL scaling") - - self.valid_terms = ["null","solution", "omitted", "all"] - self.valid_return_types = ["parameters", "predictions"] - - def __load_omitted_predictions(self): - """private: set the omitted_predictions attribute - """ - # if there are no base predictions - if self.predictions is None: - raise Exception("ErrVar.__load_omitted_predictions(): " + - "no 'included' predictions is None") - if self.omitted_predictions_arg is None and \ - self.omitted_par_arg is None: - raise Exception("ErrVar.__load_omitted_predictions: " + - "both omitted args are None") - # try to set omitted_predictions by - # extracting from existing predictions - if self.omitted_predictions_arg is None and \ - self.omitted_par_arg is not None: - # check to see if omitted par names are in each predictions - found = True - missing_par,missing_pred = None, None - for par_name in self.omitted_jco.col_names: - for prediction in self.predictions_iter: - if par_name not in prediction.row_names: - found = False - missing_par = par_name - missing_pred = prediction.col_names[0] - break - if found: - opreds = [] - # need to access the attribute directly, - # not a view of attribute - opred_mat = self._LinearAnalysis__predictions.extract( - row_names=self.omitted_jco.col_names) - opreds = [opred_mat.get(col_names=name) for name in self.forecast_names] - - #for prediction in self._LinearAnalysis__predictions: - # opred = prediction.extract(self.omitted_jco.col_names) - # opreds.append(opred) - self.__omitted_predictions = opreds - else: - raise Exception("ErrVar.__load_omitted_predictions(): " + - " omitted parameter " + str(missing_par) +\ - " not found in prediction vector " + - str(missing_pred)) - elif self.omitted_parcov_arg is not None: - raise NotImplementedError() - - def __load_omitted_parcov(self): - """private: set the omitted_parcov attribute - """ - if self.omitted_parcov_arg is None and self.omitted_par_arg is None: - raise Exception("ErrVar.__load_omitted_parcov: " + - "both omitted args are None") - # try to set omitted_parcov by extracting from base parcov - if self.omitted_parcov_arg is None and self.omitted_par_arg is not None: - # check to see if omitted par names are in parcov - found = True - for par_name in self.omitted_jco.col_names: - if par_name not in self.parcov.col_names: - found = False - break - if found: - # need to access attribute directly, not view of attribute - self.__omitted_parcov = \ - self._LinearAnalysis__parcov.extract( - row_names=self.omitted_jco.col_names) - else: - self.logger.warn("ErrVar.__load_omitted_parun: " + - "no omitted parcov arg passed: " + - "setting omitted parcov as identity Matrix") - self.__omitted_parcov = Cov( - x=np.ones(self.omitted_jco.shape[1]), - names=self.omitted_jco.col_names, isdiagonal=True) - elif self.omitted_parcov_arg is not None: - raise NotImplementedError() - - def __load_omitted_jco(self): - """private: set the omitted jco attribute - """ - if self.omitted_par_arg is None: - raise Exception("ErrVar.__load_omitted: omitted_arg is None") - if isinstance(self.omitted_par_arg,str): - if self.omitted_par_arg in self.jco.col_names: - # need to access attribute directly, not view of attribute - self.__omitted_jco = \ - self._LinearAnalysis__jco.extract( - col_names=self.omitted_par_arg) - else: - # must be a filename - self.__omitted_jco = self.__fromfile(self.omitted_par_arg) - # if the arg is an already instantiated Matrix (or jco) object - elif isinstance(self.omitted_par_arg,Jco) or \ - isinstance(self.omitted_par_arg,Matrix): - self.__omitted_jco = \ - Jco(x=self.omitted_par_arg.newx(), - row_names=self.omitted_par_arg.row_names, - col_names=self.omitted_par_arg.col_names) - # if it is a list, then it must be a list - # of parameter names in self.jco - elif isinstance(self.omitted_par_arg,list): - for arg in self.omitted_par_arg: - if isinstance(arg,str): - assert arg in self.jco.col_names,\ - "ErrVar.__load_omitted_jco: omitted_jco " +\ - "arg str not in jco par_names: " + str(arg) - self.__omitted_jco = \ - self._LinearAnalysis__jco.extract(col_names=self.omitted_par_arg) - - # these property decorators help keep from loading potentially - # unneeded items until they are called - # returns a view only - cheap, but can be dangerous - - @property - def omitted_predictions(self): - """ get the omitted prediction sensitivity vectors (stored as - a pyemu.Matrix) - - Returns - ------- - omitted_predictions : pyemu.Matrix - a matrix of prediction sensitivity vectors (column wise) to - omitted parameters - - Note - ---- - returns a reference - - if ErrorVariance.__omitted_predictions is not set, then dynamically load the - attribute before returning - - """ - if self.__omitted_predictions is None: - self.log("loading omitted_predictions") - self.__load_omitted_predictions() - self.log("loading omitted_predictions") - return self.__omitted_predictions - - @property - def omitted_jco(self): - """get the omitted jco - - Returns - ------- - omitted_jco : pyemu.Jco - - Note - ---- - returns a reference - - if ErrorVariance.__omitted_jco is None, - then dynamically load the attribute before returning - - """ - if self.__omitted_jco is None: - self.log("loading omitted_jco") - self.__load_omitted_jco() - self.log("loading omitted_jco") - return self.__omitted_jco - - @property - def omitted_parcov(self): - """get the omitted prior parameter covariance matrix - - Returns - ------- - omitted_parcov : pyemu.Cov - - Note - ---- - returns a reference - - If ErrorVariance.__omitted_parcov is None, - attribute is dynamically loaded - - """ - if self.__omitted_parcov is None: - self.log("loading omitted_parcov") - self.__load_omitted_parcov() - self.log("loading omitted_parcov") - return self.__omitted_parcov - -
    [docs] def get_errvar_dataframe(self, singular_values=None): - """get a pandas dataframe of error variance results indexed - on singular value and (prediction name,<errvar term>) - - Parameters - ---------- - singular_values : list - singular values to test. defaults to - range(0,min(nnz_obs,nadj_par) + 1) - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - multi-indexed pandas dataframe - - """ - if singular_values is None: - singular_values = \ - np.arange(0, min(self.pst.nnz_obs, self.pst.npar_adj) + 1) - if not isinstance(singular_values, list) and \ - not isinstance(singular_values, np.ndarray): - singular_values = [singular_values] - results = {} - for singular_value in singular_values: - sv_results = self.variance_at(singular_value) - for key, val in sv_results.items(): - if key not in results.keys(): - results[key] = [] - results[key].append(val) - return pd.DataFrame(results, index=singular_values)
    - - -
    [docs] def get_identifiability_dataframe(self,singular_value=None,precondition=False): - """get the parameter identifiability as a pandas dataframe - - Parameters - ---------- - singular_value : int - the singular spectrum truncation point. Defaults to minimum of - non-zero-weighted observations and adjustable parameters - precondition : bool - flag to use the preconditioned hessian (xtqt + sigma_theta^-1). - Default is False - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - A pandas dataframe of the V_1**2 Matrix with the - identifiability in the column labeled "ident" - - """ - if singular_value is None: - singular_value = int(min(self.pst.nnz_obs, self.pst.npar_adj)) - #v1_df = self.qhalfx.v[:, :singular_value].to_dataframe() ** 2 - xtqx = self.xtqx - if precondition: - xtqx = xtqx + self.parcov.inv - #v1_df = self.xtqx.v[:, :singular_value].to_dataframe() ** 2 - v1_df = xtqx.v[:, :singular_value].to_dataframe() ** 2 - v1_df["ident"] = v1_df.sum(axis=1) - return v1_df
    - -
    [docs] def variance_at(self, singular_value): - """get the error variance of all three terms at a singluar value - - Parameters - ---------- - singular_value : int - singular value to test - - Returns - ------- - dict : dict - dictionary of (err var term,prediction_name), standard_deviation pairs - - """ - results = {} - results.update(self.first_prediction(singular_value)) - results.update(self.second_prediction(singular_value)) - results.update(self.third_prediction(singular_value)) - return results
    - -
    [docs] def R(self, singular_value): - """get resolution Matrix (V_1 * V_1^T) at a singular value - - Parameters - ---------- - singular_value : int - singular value to calc R at - - Returns - ------- - R : pyemu.Matrix - resolution matrix at singular_value - """ - if self.__R is not None and singular_value == self.__R_sv: - return self.__R - - elif singular_value > self.jco.ncol: - self.__R_sv = self.jco.ncol - return self.parcov.identity - else: - self.log("calc R @" + str(singular_value)) - #v1 = self.qhalfx.v[:, :singular_value] - v1 = self.xtqx.v[:, :singular_value] - self.__R = v1 * v1.T - self.__R_sv = singular_value - self.log("calc R @" + str(singular_value)) - return self.__R
    - -
    [docs] def I_minus_R(self,singular_value): - """get I - R at singular value - - Parameters - ---------- - singular_value : int - singular value to calc R at - - Returns - ------- - I - R : pyemu.Matrix - identity matrix minus resolution matrix at singular_value - - """ - if self.__I_R is not None and singular_value == self.__I_R_sv: - return self.__I_R - else: - if singular_value > self.jco.ncol: - return self.parcov.zero - else: - #v2 = self.qhalfx.v[:, singular_value:] - v2 = self.xtqx.v[:, singular_value:] - self.__I_R = v2 * v2.T - self.__I_R_sv = singular_value - return self.__I_R
    - -
    [docs] def G(self, singular_value): - """get the parameter solution Matrix at a singular value - V_1 * S_1^(_1) * U_1^T - - Parameters - ---------- - singular_value : int - singular value to calc R at - - Returns - ------- - G : pyemu.Matrix - parameter solution matrix at singular value - - """ - if self.__G is not None and singular_value == self.__G_sv: - return self.__G - - if singular_value == 0: - self.__G_sv = 0 - self.__G = Matrix( - x=np.zeros((self.jco.ncol,self.jco.nrow)), - row_names=self.jco.col_names, col_names=self.jco.row_names) - return self.__G - mn = min(self.jco.shape) - try: - mn = min(self.pst.npar_adj, self.pst.nnz_obs) - except: - pass - if singular_value > mn: - self.logger.warn( - "ErrVar.G(): singular_value > min(npar,nobs):" + - "resetting to min(npar,nobs): " + - str(min(self.pst.npar_adj, self.pst.nnz_obs))) - singular_value = min(self.pst.npar_adj, self.pst.nnz_obs) - self.log("calc G @" + str(singular_value)) - #v1 = self.qhalfx.v[:, :singular_value] - v1 = self.xtqx.v[:, :singular_value] - #s1 = ((self.qhalfx.s[:singular_value]) ** 2).inv - s1 = (self.xtqx.s[:singular_value]).inv - self.__G = v1 * s1 * v1.T * self.jco.T * self.obscov.inv - self.__G_sv = singular_value - self.__G.row_names = self.jco.col_names - self.__G.col_names = self.jco.row_names - self.__G.autoalign = True - self.log("calc G @" + str(singular_value)) - return self.__G
    - -
    [docs] def first_forecast(self,singular_value): - """wrapper around ErrVar.first_forecast - """ - return self.first_prediction(singular_value)
    - -
    [docs] def first_prediction(self, singular_value): - """get the null space term (first term) contribution to prediction error variance - at a singular value. used to construct error variance dataframe - - Parameters - ---------- - singular_value : int - singular value to calc first term at - - Returns - ------- - dict : dict - dictionary of ("first",prediction_names),error variance pairs at singular_value - - """ - if not self.predictions: - raise Exception("ErrVar.first(): no predictions are set") - if singular_value > self.jco.ncol: - zero_preds = {} - for pred in self.predictions_iter: - zero_preds[("first", pred.col_names[0])] = 0.0 - return zero_preds - self.log("calc first term parameter @" + str(singular_value)) - first_term = self.I_minus_R(singular_value).T * self.parcov *\ - self.I_minus_R(singular_value) - if self.predictions: - results = {} - for prediction in self.predictions_iter: - results[("first",prediction.col_names[0])] = \ - float((prediction.T * first_term * prediction).x) - self.log("calc first term parameter @" + str(singular_value)) - return results
    - -
    [docs] def first_parameter(self, singular_value): - """get the null space term (first term) contribution to parameter error variance - at a singular value - - Parameters - ---------- - singular_value : int - singular value to calc first term at - - Returns - ------- - first_term : pyemu.Cov - first term contribution to parameter error variance - - """ - self.log("calc first term parameter @" + str(singular_value)) - first_term = self.I_minus_R(singular_value) * self.parcov * \ - self.I_minus_R(singular_value) - self.log("calc first term parameter @" + str(singular_value)) - return first_term
    - -
    [docs] def second_forecast(self,singular_value): - """wrapper around ErrVar.second_prediction - """ - return self.second_prediction(singular_value)
    - -
    [docs] def second_prediction(self, singular_value): - """get the solution space contribution to predictive error variance - at a singular value (y^t * G * obscov * G^T * y). Used to construct - error variance dataframe - - Parameters - ---------- - singular_value : int - singular value to calc second term at - - Returns - ------- - dict : dict - dictionary of ("second",prediction_names), error variance - - """ - - if not self.predictions: - raise Exception("ErrVar.second(): not predictions are set") - self.log("calc second term prediction @" + str(singular_value)) - - mn = min(self.jco.shape) - try: - mn = min(self.pst.npar_adj, self.pst.nnz_obs) - except: - pass - if singular_value > mn: - inf_pred = {} - for pred in self.predictions_iter: - inf_pred[("second",pred.col_names[0])] = 1.0E+35 - return inf_pred - else: - second_term = self.G(singular_value) * self.obscov * \ - self.G(singular_value).T - results = {} - for prediction in self.predictions_iter: - results[("second",prediction.col_names[0])] = \ - float((prediction.T * second_term * prediction).x) - self.log("calc second term prediction @" + str(singular_value)) - return results
    - -
    [docs] def second_parameter(self, singular_value): - """get the solution space contribution to parameter error variance - at a singular value (G * obscov * G^T) - - Parameters - ---------- - singular_value : int - singular value to calc second term at - - Returns - ------- - second_parameter : pyemu.Cov - second term contribution to parameter error variance - - """ - self.log("calc second term parameter @" + str(singular_value)) - result = self.G(singular_value) * self.obscov * self.G(singular_value).T - self.log("calc second term parameter @" + str(singular_value)) - return result
    - -
    [docs] def third_forecast(self,singular_value): - """wrapper around ErrVar.third_prediction - """ - return self.third_prediction(singular_value)
    - -
    [docs] def third_prediction(self,singular_value): - """get the omitted parameter contribution to prediction error variance - at a singular value. used to construct error variance dataframe - - Parameters - ---------- - singular_value : int - singular value to calc third term at - - Returns - ------- - dict : dict - dictionary of ("third",prediction_names),error variance - """ - if not self.predictions: - raise Exception("ErrVar.third(): not predictions are set") - if self.__need_omitted is False: - zero_preds = {} - for pred in self.predictions_iter: - zero_preds[("third", pred.col_names[0])] = 0.0 - return zero_preds - self.log("calc third term prediction @" + str(singular_value)) - mn = min(self.jco.shape) - try: - mn = min(self.pst.npar_adj, self.pst.nnz_obs) - except: - pass - if singular_value > mn: - inf_pred = {} - for pred in self.predictions_iter: - inf_pred[("third",pred.col_names[0])] = 1.0E+35 - return inf_pred - else: - results = {} - for prediction,omitted_prediction in \ - zip(self.predictions_iter, self.omitted_predictions): - # comes out as row vector, but needs to be a column vector - p = ((prediction.T * self.G(singular_value) * self.omitted_jco) - - omitted_prediction.T).T - result = float((p.T * self.omitted_parcov * p).x) - results[("third", prediction.col_names[0])] = result - self.log("calc third term prediction @" + str(singular_value)) - return results
    - -
    [docs] def third_parameter(self, singular_value): - """get the omitted parameter contribution to parameter error variance - at a singular value (G * omitted_jco * Sigma_(omitted_pars) * omitted_jco^T * G^T) - - Parameters - ---------- - singular_value : int - singular value to calc third term at - - Returns - ------- - third_parameter : pyemu.Cov - 0.0 if need_omitted is False - - """ - if self.__need_omitted is False: - return 0.0 - self.log("calc third term parameter @" + str(singular_value)) - GZo = self.G(singular_value) * self.omitted_jco - result = GZo * self.omitted_parcov * GZo.T - self.log("calc third term parameter @" + str(singular_value)) - return result
    - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/la.html b/docs/_build/html/_modules/pyemu/la.html deleted file mode 100644 index c6b938486..000000000 --- a/docs/_build/html/_modules/pyemu/la.html +++ /dev/null @@ -1,1157 +0,0 @@ - - - - - - - - pyemu.la — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.la

    -"""This module contains the LinearAnalysis object, which is the base class for
    -all other pyemu analysis objects (Schur, ErrVar, MonteCarlo, and EnsembleSmoother).
    -"""
    -from __future__ import print_function, division
    -import os
    -import copy
    -from datetime import datetime
    -import numpy as np
    -import pandas as pd
    -from pyemu.mat.mat_handler import Matrix, Jco, Cov
    -from pyemu.pst.pst_handler import Pst
    -from pyemu.utils.helpers import _istextfile
    -from .logger import Logger
    -
    -
    [docs]class LinearAnalysis(object): - """The super class for linear analysis. Can be used directly, but - for prior uncertainty analyses only. The derived types - (pyemu.Schur, pyemu.ErrVar, pyemu.MonteCarlo, pyemu.EnsembleSmoother) - are for different forms of posterior uncertainty analyses. This class - tries hard to not load items until they are needed; all arguments are - optional - - Parameters - ---------- - jco : (varies) - something that can be cast or loaded into a pyemu.Jco. Can be a - str for a filename or pyemu.Matrix/pyemu.Jco object. - pst : (varies) - something that can be cast into a pyemu.Pst. Can be an str for a - filename or an existing pyemu.Pst. If None, a pst filename is sought - with the same base name as the jco argument (if passed) - parcov : (varies) - prior parameter covariance matrix. If str, a filename is assumed and - the prior parameter covariance matrix is loaded from a file using - the file extension. If None, the prior parameter covariance matrix is - constructed from the parameter bounds in the control file represented - by the pst argument. Can also be a pyemu.Cov instance - obscov : (varies) - observation noise covariance matrix. If str, a filename is assumed and - the observation noise covariance matrix is loaded from a file using - the file extension. If None, the observation noise covariance matrix is - constructed from the weights in the control file represented - by the pst argument. Can also be a pyemu.Cov instance - predictions : (varies) - prediction (aka forecast) sensitivity vectors. If str, a filename - is assumed and predictions are loaded from a file using the file extension. - Can also be a pyemu.Matrix instance, a numpy.ndarray or a collection - of pyemu.Matrix or numpy.ndarray. - ref_var : (float) - reference variance - verbose : (either bool or string) - controls log file / screen output. If str, a filename is assumed and - and log file is written to verbose - sigma_range : float - defines range of upper bound - lower bound in terms of standard - deviation (sigma). For example, if sigma_range = 4, the bounds - represent 4 * sigma. Default is 4.0, representing approximately - 95% confidence of implied normal distribution. This arg is only - used if constructing parcov from parameter bounds. - - scale_offset : True - flag to apply parameter scale and offset to parameter bounds - when calculating prior parameter covariance matrix from - bounds. This arg is onlyused if constructing parcov - from parameter bounds.Default is True. - - Note - ---- - the class makes heavy use of property decorator to encapsulate - private attributes - - """ - def __init__(self, jco=None, pst=None, parcov=None, obscov=None, - predictions=None, ref_var=1.0, verbose=False, - resfile=False, forecasts=None,sigma_range=4.0, - scale_offset=True,**kwargs): - self.logger = Logger(verbose) - self.log = self.logger.log - self.jco_arg = jco - #if jco is None: - self.__jco = jco - if pst is None: - if isinstance(jco, str): - pst_case = jco.replace(".jco", ".pst").replace(".jcb",".pst") - if os.path.exists(pst_case): - pst = pst_case - self.pst_arg = pst - if parcov is None and pst is not None: - parcov = pst - self.parcov_arg = parcov - if obscov is None and pst is not None: - obscov = pst - self.obscov_arg = obscov - self.ref_var = ref_var - if forecasts is not None and predictions is not None: - raise Exception("can't pass both forecasts and predictions") - - - self.sigma_range = sigma_range - self.scale_offset = scale_offset - - #private attributes - access is through @decorated functions - self.__pst = None - self.__parcov = None - self.__obscov = None - self.__predictions = None - self.__qhalf = None - self.__qhalfx = None - self.__xtqx = None - self.__fehalf = None - self.__prior_prediction = None - self.prediction_extract = None - - self.log("pre-loading base components") - if jco is not None: - self.__load_jco() - if pst is not None: - self.__load_pst() - if parcov is not None: - self.__load_parcov() - if obscov is not None: - self.__load_obscov() - - self.prediction_arg = None - if predictions is not None: - self.prediction_arg = predictions - elif forecasts is not None: - self.prediction_arg = forecasts - elif self.pst is not None and self.jco is not None: - if self.pst.forecast_names is not None: - self.prediction_arg = self.pst.forecast_names - if self.prediction_arg: - self.__load_predictions() - - - self.log("pre-loading base components") - if len(kwargs.keys()) > 0: - self.logger.warn("unused kwargs in type " + - str(self.__class__.__name__) + - " : " + str(kwargs)) - raise Exception("unused kwargs" + - " : " + str(kwargs)) - # automatically do some things that should be done - self.log("dropping prior information") - pi = None - try: - pi = self.pst.prior_information - except: - self.logger.statement("unable to access self.pst: can't tell if " + - " any prior information needs to be dropped.") - if pi is not None: - self.drop_prior_information() - self.log("dropping prior information") - - - if resfile != False: - self.log("scaling obscov by residual phi components") - try: - self.adjust_obscov_resfile(resfile=resfile) - except: - self.logger.statement("unable to a find a residuals file for " +\ - " scaling obscov") - self.resfile = None - self.res = None - self.log("scaling obscov by residual phi components") - assert type(self.parcov) == Cov - assert type(self.obscov) == Cov - - def __fromfile(self, filename, astype=None): - """a private method to deduce and load a filename into a matrix object. - Uses extension: 'jco' or 'jcb': binary, 'mat','vec' or 'cov': ASCII, - 'unc': pest uncertainty file. - - Parameters - ---------- - filename : str - the name of the file - - Returns - ------- - m : pyemu.Matrix - - Raises - ------ - exception for unrecognized extension - - """ - assert os.path.exists(filename),"LinearAnalysis.__fromfile(): " +\ - "file not found:" + filename - ext = filename.split('.')[-1].lower() - if ext in ["jco", "jcb"]: - self.log("loading jco: "+filename) - if astype is None: - astype = Jco - m = astype.from_binary(filename) - self.log("loading jco: "+filename) - elif ext in ["mat","vec"]: - self.log("loading ascii: "+filename) - if astype is None: - astype = Matrix - m = astype.from_ascii(filename) - self.log("loading ascii: "+filename) - elif ext in ["cov"]: - self.log("loading cov: "+filename) - if astype is None: - astype = Cov - if _istextfile(filename): - m = astype.from_ascii(filename) - else: - m = astype.from_binary(filename) - self.log("loading cov: "+filename) - elif ext in["unc"]: - self.log("loading unc: "+filename) - if astype is None: - astype = Cov - m = astype.from_uncfile(filename) - self.log("loading unc: "+filename) - else: - raise Exception("linear_analysis.__fromfile(): unrecognized" + - " filename extension:" + str(ext)) - return m - - - def __load_pst(self): - """private method set the pst attribute - """ - if self.pst_arg is None: - return None - if isinstance(self.pst_arg, Pst): - self.__pst = self.pst_arg - return self.pst - else: - try: - self.log("loading pst: " + str(self.pst_arg)) - self.__pst = Pst(self.pst_arg) - self.log("loading pst: " + str(self.pst_arg)) - return self.pst - except Exception as e: - raise Exception("linear_analysis.__load_pst(): error loading"+\ - " pest control from argument: " + - str(self.pst_arg) + '\n->' + str(e)) - - - def __load_jco(self): - """private method to set the jco attribute from a file or a matrix object - """ - if self.jco_arg is None: - return None - #raise Exception("linear_analysis.__load_jco(): jco_arg is None") - if isinstance(self.jco_arg, Matrix): - self.__jco = self.jco_arg - elif isinstance(self.jco_arg, str): - self.__jco = self.__fromfile(self.jco_arg,astype=Jco) - else: - raise Exception("linear_analysis.__load_jco(): jco_arg must " + - "be a matrix object or a file name: " + - str(self.jco_arg)) - - def __load_parcov(self): - """private method to set the parcov attribute from: - a pest control file (parameter bounds) - a pst object - a matrix object - an uncert file - an ascii matrix file - """ - # if the parcov arg was not passed but the pst arg was, - # reset and use parbounds to build parcov - if not self.parcov_arg: - if self.pst_arg: - self.parcov_arg = self.pst_arg - else: - raise Exception("linear_analysis.__load_parcov(): " + - "parcov_arg is None") - if isinstance(self.parcov_arg, Matrix): - self.__parcov = self.parcov_arg - return - if isinstance(self.parcov_arg, np.ndarray): - # if the passed array is a vector, - # then assume it is the diagonal of the parcov matrix - if len(self.parcov_arg.shape) == 1: - assert self.parcov_arg.shape[0] == self.jco.shape[1] - isdiagonal = True - else: - assert self.parcov_arg.shape[0] == self.jco.shape[1] - assert self.parcov_arg.shape[1] == self.jco.shape[1] - isdiagonal = False - self.logger.warn("linear_analysis.__load_parcov(): " + - "instantiating parcov from ndarray, can't " + - "verify parameters alignment with jco") - self.__parcov = Matrix(x=self.parcov_arg, - isdiagonal=isdiagonal, - row_names=self.jco.col_names, - col_names=self.jco.col_names) - self.log("loading parcov") - if isinstance(self.parcov_arg,str): - # if the arg is a string ending with "pst" - # then load parcov from parbounds - if self.parcov_arg.lower().endswith(".pst"): - self.__parcov = Cov.from_parbounds(self.parcov_arg, - sigma_range=self.sigma_range, - scale_offset=self.scale_offset) - else: - self.__parcov = self.__fromfile(self.parcov_arg, astype=Cov) - # if the arg is a pst object - elif isinstance(self.parcov_arg,Pst): - self.__parcov = Cov.from_parameter_data(self.parcov_arg, - sigma_range=self.sigma_range, - scale_offset=self.scale_offset) - else: - raise Exception("linear_analysis.__load_parcov(): " + - "parcov_arg must be a " + - "matrix object or a file name: " + - str(self.parcov_arg)) - self.log("loading parcov") - - - def __load_obscov(self): - """private method to set the obscov attribute from: - a pest control file (observation weights) - a pst object - a matrix object - an uncert file - an ascii matrix file - """ - # if the obscov arg is None, but the pst arg is not None, - # reset and load from obs weights - if not self.obscov_arg: - if self.pst_arg: - self.obscov_arg = self.pst_arg - else: - raise Exception("linear_analysis.__load_obscov(): " + - "obscov_arg is None") - if isinstance(self.obscov_arg, Matrix): - self.__obscov = self.obscov_arg - return - if isinstance(self.obscov_arg,np.ndarray): - # if the ndarray arg is a vector, - # assume it is the diagonal of the obscov matrix - if len(self.obscov_arg.shape) == 1: - assert self.obscov_arg.shape[0] == self.jco.shape[1] - isdiagonal = True - else: - assert self.obscov_arg.shape[0] == self.jco.shape[0] - assert self.obscov_arg.shape[1] == self.jco.shape[0] - isdiagonal = False - self.logger.warn("linear_analysis.__load_obscov(): " + - "instantiating obscov from ndarray, " + - "can't verify observation alignment with jco") - self.__obscov = Matrix(x=self.obscov_arg, - isdiagonal=isdiagonal, - row_names=self.jco.row_names, - col_names=self.jco.row_names) - self.log("loading obscov") - if isinstance(self.obscov_arg, str): - if self.obscov_arg.lower().endswith(".pst"): - self.__obscov = Cov.from_obsweights(self.obscov_arg) - else: - self.__obscov = self.__fromfile(self.obscov_arg, astype=Cov) - elif isinstance(self.obscov_arg, Pst): - self.__obscov = Cov.from_observation_data(self.obscov_arg) - else: - raise Exception("linear_analysis.__load_obscov(): " + - "obscov_arg must be a " + - "matrix object or a file name: " + - str(self.obscov_arg)) - self.log("loading obscov") - - - def __load_predictions(self): - """private method set the predictions attribute from: - mixed list of row names, matrix files and ndarrays - a single row name - an ascii file - can be none if only interested in parameters. - - """ - if self.prediction_arg is None: - self.__predictions = None - return - self.log("loading forecasts") - if not isinstance(self.prediction_arg, list): - self.prediction_arg = [self.prediction_arg] - - row_names = [] - vecs = [] - mat = None - for arg in self.prediction_arg: - if isinstance(arg, Matrix): - # a vector - if arg.shape[1] == 1: - vecs.append(arg) - else: - if self.jco is not None: - assert arg.shape[0] == self.jco.shape[1],\ - "linear_analysis.__load_predictions(): " +\ - "multi-prediction matrix(npar,npred) not aligned " +\ - "with jco(nobs,npar): " + str(arg.shape) +\ - ' ' + str(self.jco.shape) - #for pred_name in arg.row_names: - # vecs.append(arg.extract(row_names=pred_name).T) - mat = arg - elif isinstance(arg, str): - if arg.lower() in self.jco.row_names: - row_names.append(arg.lower()) - else: - try: - pred_mat = self.__fromfile(arg,astype=Matrix) - except Exception as e: - raise Exception("forecast argument: "+arg+" not found in " +\ - "jco row names and could not be " +\ - "loaded from a file.") - # vector - if pred_mat.shape[1] == 1: - vecs.append(pred_mat) - else: - #for pred_name in pred_mat.row_names: - # vecs.append(pred_mat.get(row_names=pred_name)) - if mat is None: - mat = pred_mat - else: - mat = mat.extend((pred_mat)) - elif isinstance(arg, np.ndarray): - self.logger.warn("linear_analysis.__load_predictions(): " + - "instantiating prediction matrix from " + - "ndarray, can't verify alignment") - self.logger.warn("linear_analysis.__load_predictions(): " + - "instantiating prediction matrix from " + - "ndarray, generating generic prediction names") - - pred_names = ["pred_{0}".format(i+1) for i in range(arg.shape[0])] - - if self.jco: - names = self.jco.col_names - elif self.parcov: - names = self.parcov.col_names - else: - raise Exception("linear_analysis.__load_predictions(): " + - "ndarray passed for predicitons " + - "requires jco or parcov to get " + - "parameter names") - if mat is None: - mat = Matrix(x=arg,row_names=pred_names,col_names=names).T - else: - mat = mat.extend(Matrix(x=arg,row_names=pred_names,col_names=names).T) - #for pred_name in pred_names: - # vecs.append(pred_matrix.get(row_names=pred_name).T) - else: - raise Exception("unrecognized predictions argument: " + - str(arg)) - # turn vecs into a pyemu.Matrix - - if len(vecs) > 0: - xs = vecs[0].x - for vec in vecs[1:]: - xs = xs.extend(vec.x) - names = [vec.col_names[0] for vec in vecs] - if mat is None: - mat = Matrix(x=xs,row_names=vecs[0].row_names, - col_names=names) - else: - mat = mat.extend(Matrix(x = np.array(xs), - row_names=vecs[0].row_names, - col_names=names)) - - if len(row_names) > 0: - extract = self.jco.extract(row_names=row_names).T - if mat is None: - mat = extract - else: - mat = mat.extend(extract) - #for row_name in row_names: - # vecs.append(extract.get(row_names=row_name).T) - # call obscov to load __obscov so that __obscov - # (priavte) can be manipulated - self.obscov - self.__obscov.drop(row_names, axis=0) - self.__predictions = mat - try: - fnames = [fname for fname in self.forecast_names if fname in self.pst.nnz_obs_names] - except: - fnames = [] - if len(fnames) > 0: - raise Exception("forecasts with non-zero weight in pst: {0}".format(','.join(fnames))) - self.log("loading forecasts") - self.logger.statement("forecast names: {0}".format(','.join(mat.col_names))) - return self.__predictions - - # these property decorators help keep from loading potentially - # unneeded items until they are called - # returns a reference - cheap, but can be dangerous - - @property - def forecast_names(self): - """ get the forecast (aka prediction) names - - Returns - ------- - forecast_names : list - list of forecast names - - """ - if self.forecasts is None: - return [] - #return [fore.col_names[0] for fore in self.forecasts] - return list(self.predictions.col_names) - - @property - def parcov(self): - """ get the prior parameter covariance matrix attribute - - Return - ------ - parcov : pyemu.Cov - a reference to the parcov attribute - - Note - ---- - returns a reference - - if LinearAnalysis.__parcov is not set, the dynamically load the - parcov attribute before returning - - """ - if not self.__parcov: - self.__load_parcov() - return self.__parcov - - - @property - def obscov(self): - """ get the observation noise covariance matrix attribute - - Returns - ------- - obscov : pyemu.Cov - a reference to the obscov attribute - - Note - ---- - returns a reference - - if LinearAnalysis.__obscov is not set, the dynamically load the - obscov attribute before returning - - """ - if not self.__obscov: - self.__load_obscov() - return self.__obscov - - @property - def nnz_obs_names(self): - """ wrapper around pyemu.Pst.nnz_obs_names for listing non-zero - observation names - - Returns - ------- - nnz_obs_names : list - pyemu.Pst.nnz_obs_names - - """ - if self.__pst is not None: - return self.pst.nnz_obs_names - else: - return self.jco.obs_names - -
    [docs] def adj_par_names(self): - """ wrapper around pyemu.Pst.adj_par_names for list adjustable parameter - names - - Returns - ------- - adj_par_names : list - pyemu.Pst.adj_par_names - - """ - if self.__pst is not None: - return self.pst.adj_par_names - else: - return self.jco.par_names
    - - @property - def jco(self): - """ get the jacobian matrix attribute - - Returns - ------- - jco : pyemu.Jco - the jacobian matrix attribute - - Note - ---- - returns a reference - - if LinearAnalysis.__jco is not set, the dynamically load the - jco attribute before returning - - """ - if not self.__jco: - self.__load_jco() - return self.__jco - - - @property - def predictions(self): - """ get the predictions (aka forecasts) attribute - - Returns - ------- - predictions : pyemu.Matrix - a matrix of prediction sensitivity vectors (column wise) - - Note - ---- - returns a reference - - if LinearAnalysis.__predictions is not set, the dynamically load the - predictions attribute before returning - - """ - if not self.__predictions: - self.__load_predictions() - return self.__predictions - - @property - def predictions_iter(self): - """ property decorated prediction iterator - - Returns - ------- - iterator : iterator - iterator on prediction sensitivity vectors (matrix) - - """ - for fname in self.forecast_names: - yield self.predictions.get(col_names=fname) - - @property - def forecasts_iter(self): - """synonym for LinearAnalysis.predictions_iter() - """ - return self.predictions_iter - - @property - def forecasts(self): - """synonym for LinearAnalysis.predictions - """ - return self.predictions - - @property - def pst(self): - """ get the pyemu.Pst attribute - - Returns - ------- - pst : pyemu.Pst - - Note - ---- - returns a references - - If LinearAnalysis.__pst is None, then the pst attribute is - dynamically loaded before returning - """ - if self.__pst is None and self.pst_arg is None: - raise Exception("linear_analysis.pst: can't access self.pst:" + - "no pest control argument passed") - elif self.__pst: - return self.__pst - else: - self.__load_pst() - return self.__pst - - - @property - def fehalf(self): - """get the KL parcov scaling matrix attribute. Create the attribute if - it has not yet been created - - Returns - ------- - fehalf : pyemu.Matrix - - """ - if self.__fehalf != None: - return self.__fehalf - self.log("fehalf") - self.__fehalf = self.parcov.u * (self.parcov.s ** (0.5)) - self.log("fehalf") - return self.__fehalf - - - @property - def qhalf(self): - """get the square root of the cofactor matrix attribute. Create the attribute if - it has not yet been created - - Returns - ------- - qhalf : pyemu.Matrix - - """ - if self.__qhalf != None: - return self.__qhalf - self.log("qhalf") - self.__qhalf = self.obscov ** (-0.5) - self.log("qhalf") - return self.__qhalf - - @property - def qhalfx(self): - """get the half normal matrix attribute. Create the attribute if - it has not yet been created - - Returns - ------- - qhalfx : pyemu.Matrix - - """ - if self.__qhalfx is None: - self.log("qhalfx") - self.__qhalfx = self.qhalf * self.jco - self.log("qhalfx") - return self.__qhalfx - - @property - def xtqx(self): - """get the normal matrix attribute. Create the attribute if - it has not yet been created - - Returns - ------- - xtqx : pyemu.Matrix - - """ - if self.__xtqx is None: - self.log("xtqx") - self.__xtqx = self.jco.T * (self.obscov ** -1) * self.jco - self.log("xtqx") - return self.__xtqx - - @property - def mle_covariance(self): - """ get the maximum likelihood parameter covariance matrix. - - - Returns - ------- - pyemu.Matrix : pyemu.Matrix - - """ - return self.xtqx.inv - - @property - def prior_parameter(self): - """the prior parameter covariance matrix. Just a wrapper around - LinearAnalysis.parcov - - Returns - ------- - prior_parameter : pyemu.Cov - - """ - return self.parcov - - @property - def prior_forecast(self): - """thin wrapper for prior_prediction - - Returns - ------- - prior_forecast : dict - a dictionary of forecast name, prior variance pairs - - """ - return self.prior_prediction - - @property - def mle_parameter_estimate(self): - """ get the maximum likelihood parameter estimate. - - Returns - ------- - post_expt : pandas.Series - the maximum likelihood parameter estimates - - """ - res = self.pst.res - assert res is not None - # build the prior expectation parameter vector - prior_expt = self.pst.parameter_data.loc[:,["parval1"]].copy() - islog = self.pst.parameter_data.partrans == "log" - prior_expt.loc[islog] = prior_expt.loc[islog].apply(np.log10) - prior_expt = Matrix.from_dataframe(prior_expt) - prior_expt.col_names = ["prior_expt"] - # build the residual vector - res_vec = Matrix.from_dataframe(res.loc[:,["residual"]]) - - # calc posterior expectation - upgrade = self.mle_covariance * self.jco.T * res_vec - upgrade.col_names = ["prior_expt"] - post_expt = prior_expt + upgrade - - # post processing - back log transform - post_expt = pd.DataFrame(data=post_expt.x,index=post_expt.row_names, - columns=["post_expt"]) - post_expt.loc[islog,:] = 10.0**post_expt.loc[islog,:] - return post_expt - - @property - def prior_prediction(self): - """get a dict of prior prediction variances - - Returns - ------- - prior_prediction : dict - dictionary of prediction name, prior variance pairs - - """ - if self.__prior_prediction is not None: - return self.__prior_prediction - else: - if self.predictions is not None: - self.log("propagating prior to predictions") - prior_cov = self.predictions.T *\ - self.parcov * self.predictions - self.__prior_prediction = {n:v for n,v in - zip(prior_cov.row_names, - np.diag(prior_cov.x))} - self.log("propagating prior to predictions") - else: - self.__prior_prediction = {} - return self.__prior_prediction - - -
    [docs] def apply_karhunen_loeve_scaling(self): - """apply karhuene-loeve scaling to the jacobian matrix. - - Note - ---- - This scaling is not necessary for analyses using Schur's - complement, but can be very important for error variance - analyses. This operation effectively transfers prior knowledge - specified in the parcov to the jacobian and reset parcov to the - identity matrix. - - """ - cnames = copy.deepcopy(self.jco.col_names) - self.__jco *= self.fehalf - self.__jco.col_names = cnames - self.__parcov = self.parcov.identity
    - - -
    [docs] def clean(self): - """drop regularization and prior information observation from the jco - """ - if self.pst_arg is None: - self.logger.statement("linear_analysis.clean(): not pst object") - return - if not self.pst.estimation and self.pst.nprior > 0: - self.drop_prior_information()
    - -
    [docs] def reset_pst(self,arg): - """ reset the LinearAnalysis.pst attribute - - Parameters - ---------- - arg : (str or matrix) - the value to assign to the pst attribute - - """ - self.logger.statement("resetting pst") - self.__pst = None - self.pst_arg = arg
    - -
    [docs] def reset_parcov(self,arg=None): - """reset the parcov attribute to None - - Parameters - ---------- - arg : str or pyemu.Matrix - the value to assign to the parcov attribute. If None, - the private __parcov attribute is cleared but not reset - """ - self.logger.statement("resetting parcov") - self.__parcov = None - if arg is not None: - self.parcov_arg = arg
    - - -
    [docs] def reset_obscov(self,arg=None): - """reset the obscov attribute to None - - Parameters - ---------- - arg : str or pyemu.Matrix - the value to assign to the obscov attribute. If None, - the private __obscov attribute is cleared but not reset - """ - self.logger.statement("resetting obscov") - self.__obscov = None - if arg is not None: - self.obscov_arg = arg
    - - -
    [docs] def drop_prior_information(self): - """drop the prior information from the jco and pst attributes - """ - if self.jco is None: - self.logger.statement("can't drop prior info, LinearAnalysis.jco is None") - return - nprior_str = str(self.pst.nprior) - self.log("removing " + nprior_str + " prior info from jco, pst, and " + - "obs cov") - #pi_names = list(self.pst.prior_information.pilbl.values) - pi_names = list(self.pst.prior_names) - missing = [name for name in pi_names if name not in self.jco.obs_names] - if len(missing) > 0: - raise Exception("LinearAnalysis.drop_prior_information(): "+ - " prior info not found: {0}".format(missing)) - if self.jco is not None: - self.__jco.drop(pi_names, axis=0) - self.__pst.prior_information = self.pst.null_prior - self.__pst.control_data.pestmode = "estimation" - #self.__obscov.drop(pi_names,axis=0) - self.log("removing " + nprior_str + " prior info from jco, pst, and " + - "obs cov")
    - - -
    [docs] def get(self,par_names=None,obs_names=None,astype=None): - """method to get a new LinearAnalysis class using a - subset of parameters and/or observations - - Parameters - ---------- - par_names : list - par names for new object - obs_names : list - obs names for new object - astype : pyemu.Schur or pyemu.ErrVar - type to cast the new object. If None, return type is - same as self - - Returns - ------- - new : LinearAnalysis - - """ - # make sure we aren't fooling with unwanted prior information - self.clean() - # if there is nothing to do but copy - if par_names is None and obs_names is None: - if astype is not None: - self.logger.warn("LinearAnalysis.get(): astype is not None, " + - "but par_names and obs_names are None so" + - "\n ->Omitted attributes will not be " + - "propagated to new instance") - else: - return copy.deepcopy(self) - # make sure the args are lists - if par_names is not None and not isinstance(par_names, list): - par_names = [par_names] - if obs_names is not None and not isinstance(obs_names, list): - obs_names = [obs_names] - - if par_names is None: - par_names = self.jco.col_names - if obs_names is None: - obs_names = self.jco.row_names - # if possible, get a new parcov - if self.parcov: - new_parcov = self.parcov.get(col_names=[pname for pname in\ - par_names if pname in\ - self.parcov.col_names]) - else: - new_parcov = None - # if possible, get a new obscov - if self.obscov_arg is not None: - new_obscov = self.obscov.get(row_names=obs_names) - else: - new_obscov = None - # if possible, get a new pst - if self.pst_arg is not None: - new_pst = self.pst.get(par_names=par_names,obs_names=obs_names) - else: - new_pst = None - new_extract = None - if self.predictions: - # new_preds = [] - # for prediction in self.predictions: - # new_preds.append(prediction.get(row_names=par_names)) - new_preds = self.predictions.get(row_names=par_names) - - else: - new_preds = None - if self.jco_arg is not None: - new_jco = self.jco.get(row_names=obs_names, col_names=par_names) - else: - new_jco = None - if astype is not None: - new = astype(jco=new_jco, pst=new_pst, parcov=new_parcov, - obscov=new_obscov, predictions=new_preds, - verbose=False) - else: - # return a new object of the same type - new = type(self)(jco=new_jco, pst=new_pst, parcov=new_parcov, - obscov=new_obscov, predictions=new_preds, - verbose=False) - return new
    - -
    [docs] def adjust_obscov_resfile(self, resfile=None): - """reset the elements of obscov by scaling the implied weights - based on the phi components in res_file so that the total phi - is equal to the number of non-zero weights. - - Parameters - ---------- - resfile : str - residual file to use. If None, residual file with case name is - sought. default is None - - Note - ---- - calls pyemu.Pst.adjust_weights_resfile() - - """ - self.pst.adjust_weights_resfile(resfile) - self.__obscov.from_observation_data(self.pst)
    - - -
    [docs] def get_par_css_dataframe(self): - """ get a dataframe of composite scaled sensitivities. Includes both - PEST-style and Hill-style. - - Returns - ------- - css : pandas.DataFrame - - """ - - assert self.jco is not None - assert self.pst is not None - jco = self.jco.to_dataframe() - weights = self.pst.observation_data.loc[jco.index,"weight"].copy().values - jco = (jco.T * weights).T - - dss_sum = jco.apply(np.linalg.norm) - css = (dss_sum / float(self.pst.nnz_obs)).to_frame() - css.columns = ["pest_css"] - # log transform stuff - self.pst.add_transform_columns() - parval1 = self.pst.parameter_data.loc[dss_sum.index,"parval1_trans"].values - css.loc[:,"hill_css"] = (dss_sum * parval1) / (float(self.pst.nnz_obs)**2) - return css
    - -
    [docs] def get_cso_dataframe(self): - """ - get a dataframe of composite observation sensitivity, as returned by PEST in the - seo file. - - Note that this formulation deviates slightly from the PEST documentation in that the - values are divided by (npar-1) rather than by (npar). - - The equation is cso_j = ((Q^1/2*J*J^T*Q^1/2)^1/2)_jj/(NPAR-1) - Returns: - cso : pandas.DataFrame - - """ - assert self.jco is not None - assert self.pst is not None - weights = self.pst.observation_data.loc[self.jco.to_dataframe().index,"weight"].copy().values - cso = np.diag(np.sqrt((self.qhalfx.x.dot(self.qhalfx.x.T))))/(float(self.pst.npar-1)) - cso_df = pd.DataFrame.from_dict({'obnme':self.jco.to_dataframe().index,'cso':cso}) - cso_df.index=cso_df['obnme'] - cso_df.drop('obnme', axis=1, inplace=True) - return cso_df
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/logger.html b/docs/_build/html/_modules/pyemu/logger.html deleted file mode 100644 index fdcfd1c21..000000000 --- a/docs/_build/html/_modules/pyemu/logger.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - - pyemu.logger — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.logger

    -"""module for logging pyemu progress
    -"""
    -from __future__ import print_function, division
    -from datetime import datetime
    -import warnings
    -from .pyemu_warnings import PyemuWarning
    -import copy
    -
    -
    [docs]class Logger(object): - """ a basic class for logging events during the linear analysis calculations - if filename is passed, then a file handle is opened. - - Parameters - ---------- - filename : str - Filename to write logged events to. If False, no file will be created, - and logged events will be displayed on standard out. - echo : bool - Flag to cause logged events to be echoed to the screen. - - Attributes - ---------- - items : dict - Dictionary holding events to be logged. If a log entry is - not in `items`, then it is treated as a new entry with the string - being the key and the datetime as the value. If a log entry is - in `items`, then the end time and delta time are written and - the item is popped from the keys. - - """ - def __init__(self,filename, echo=False): - self.items = {} - self.echo = bool(echo) - if filename == True: - self.echo = True - self.filename = None - elif filename: - self.filename = filename - self.f = open(filename, 'w') - self.t = datetime.now() - self.log("opening " + str(filename) + " for logging") - else: - self.filename = None - - -
    [docs] def statement(self,phrase): - """ log a one time statement - - Parameters - ---------- - phrase : str - statement to log - - """ - t = datetime.now() - s = str(t) + ' ' + str(phrase) + '\n' - if self.echo: - print(s,end='') - if self.filename: - self.f.write(s) - self.f.flush()
    - - -
    [docs] def log(self,phrase): - """log something that happened. The first time phrase is passed the - start time is saved. The second time the phrase is logged, the - elapsed time is written - - Parameters - ---------- - phrase : str - the thing that happened - """ - pass - t = datetime.now() - if phrase in self.items.keys(): - s = str(t) + ' finished: ' + str(phrase) + " took: " + \ - str(t - self.items[phrase]) + '\n' - if self.echo: - print(s,end='') - if self.filename: - self.f.write(s) - self.f.flush() - self.items.pop(phrase) - else: - s = str(t) + ' starting: ' + str(phrase) + '\n' - if self.echo: - print(s,end='') - if self.filename: - self.f.write(s) - self.f.flush() - self.items[phrase] = copy.deepcopy(t)
    - -
    [docs] def warn(self,message): - """write a warning to the log file. - - Parameters - ---------- - message : str - the warning text - """ - s = str(datetime.now()) + " WARNING: " + message + '\n' - if self.echo: - print(s,end='') - if self.filename: - self.f.write(s) - self.f.flush - warnings.warn(s,PyemuWarning)
    - -
    [docs] def lraise(self,message): - """log an exception, close the log file, then raise the exception - - Parameters - ---------- - message : str - the exception message - - Raises - ------ - exception with message - """ - s = str(datetime.now()) + " ERROR: " + message + '\n' - print(s,end='') - if self.filename: - self.f.write(s) - self.f.flush - self.f.close() - raise Exception(message)
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/mat/mat_handler.html b/docs/_build/html/_modules/pyemu/mat/mat_handler.html deleted file mode 100644 index 0d799f8b9..000000000 --- a/docs/_build/html/_modules/pyemu/mat/mat_handler.html +++ /dev/null @@ -1,2952 +0,0 @@ - - - - - - - - pyemu.mat.mat_handler — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.mat.mat_handler

    -"""Matrix, Jco and Cov classes for easy linear algebra
    -"""
    -from __future__ import print_function, division
    -import copy
    -import struct
    -import warnings
    -from datetime import datetime
    -import numpy as np
    -import pandas
    -import scipy.linalg as la
    -from scipy.io import FortranFile
    -import scipy.sparse
    -
    -from pyemu.pst.pst_handler import Pst
    -from ..pyemu_warnings import PyemuWarning
    -
    -
    [docs]def save_coo(x, row_names, col_names, filename, chunk=None): - """write a PEST-compatible binary file. The data format is - [int,int,float] for i,j,value. It is autodetected during - the read with Matrix.from_binary(). - - Parameters - ---------- - x : numpy.sparse - coo sparse matrix - row_names : list - list of row_names - col_names : list - list of col_names - filename : str - filename to save binary file - droptol : float - absolute value tolerance to make values smaller than zero. Default is None - chunk : int - number of elements to write in a single pass. Default is None - - """ - - f = open(filename, 'wb') - # print("counting nnz") - # write the header - header = np.array((x.shape[1], x.shape[0], x.nnz), - dtype=Matrix.binary_header_dt) - header.tofile(f) - - data = np.core.records.fromarrays([x.row, x.col, x.data], dtype=Matrix.coo_rec_dt) - data.tofile(f) - - for name in col_names: - if len(name) > Matrix.new_par_length: - name = name[:Matrix.new_par_length - 1] - elif len(name) < Matrix.new_par_length: - for i in range(len(name), Matrix.new_par_length): - name = name + ' ' - f.write(name.encode()) - for name in row_names: - if len(name) > Matrix.new_obs_length: - name = name[:Matrix.new_obs_length - 1] - elif len(name) < Matrix.new_obs_length: - for i in range(len(name), Matrix.new_obs_length): - name = name + ' ' - f.write(name.encode()) - f.close()
    - - -
    [docs]def concat(mats): - """Concatenate Matrix objects. Tries either axis. - - Parameters - ---------- - mats: list - list of Matrix objects - - Returns - ------- - Matrix : Matrix - """ - for mat in mats: - if mat.isdiagonal: - raise NotImplementedError("concat not supported for diagonal mats") - - row_match = True - col_match = True - for mat in mats[1:]: - if sorted(mats[0].row_names) != sorted(mat.row_names): - row_match = False - if sorted(mats[0].col_names) != sorted(mat.col_names): - col_match = False - if not row_match and not col_match: - raise Exception("mat_handler.concat(): all Matrix objects"+\ - "must share either rows or cols") - - if row_match and col_match: - raise Exception("mat_handler.concat(): all Matrix objects"+\ - "share both rows and cols") - - if row_match: - row_names = copy.deepcopy(mats[0].row_names) - col_names = [] - for mat in mats: - col_names.extend(copy.deepcopy(mat.col_names)) - x = mats[0].newx.copy() - for mat in mats[1:]: - mat.align(mats[0].row_names, axis=0) - other_x = mat.newx - x = np.append(x, other_x, axis=1) - - else: - col_names = copy.deepcopy(mats[0].col_names) - row_names = [] - for mat in mats: - row_names.extend(copy.deepcopy(mat.row_names)) - x = mats[0].newx.copy() - for mat in mats[1:]: - mat.align(mats[0].col_names, axis=1) - other_x = mat.newx - x = np.append(x, other_x, axis=0) - return Matrix(x=x, row_names=row_names, col_names=col_names)
    - - -
    [docs]def get_common_elements(list1, list2): - """find the common elements in two lists. used to support auto align - might be faster with sets - - Parameters - ---------- - list1 : list - a list of objects - list2 : list - a list of objects - - Returns - ------- - list : list - list of common objects shared by list1 and list2 - - """ - #result = [] - #for item in list1: - # if item in list2: - # result.append(item) - #Return list(set(list1).intersection(set(list2))) - set2 = set(list2) - result = [item for item in list1 if item in set2] - return result
    - - -
    [docs]class Matrix(object): - """a class for easy linear algebra - - Parameters - ---------- - x : numpy.ndarray - Matrix entries - row_names : list - list of row names - col_names : list - list of column names - isdigonal : bool - to determine if the Matrix is diagonal - autoalign: bool - used to control the autoalignment of Matrix objects - during linear algebra operations - - Returns - ------- - Matrix : Matrix - - Attributes - ---------- - binary_header_dt : numpy.dtype - the header info in the PEST binary file type - binary_rec_dt : numpy.dtype - the record info in the PEST binary file type - - Methods - ------- - to_ascii : write a PEST-style ASCII matrix format file - to_binary : write a PEST-stle compressed binary format file - - Note - ---- - this class makes heavy use of property decorators to encapsulate - private attributes - - """ - integer = np.int32 - double = np.float64 - char = np.uint8 - - binary_header_dt = np.dtype([('itemp1', integer), - ('itemp2', integer), - ('icount', integer)]) - binary_rec_dt = np.dtype([('j', integer), - ('dtemp', double)]) - coo_rec_dt = np.dtype([('i', integer),('j', integer), - ('dtemp', double)]) - - par_length = 12 - obs_length = 20 - new_par_length = 100 - new_obs_length = 100 - - def __init__(self, x=None, row_names=[], col_names=[], isdiagonal=False, - autoalign=True): - - - self.col_names, self.row_names = [], [] - [self.col_names.append(str(c).lower()) for c in col_names] - [self.row_names.append(str(r).lower()) for r in row_names] - self.__x = None - self.__u = None - self.__s = None - self.__v = None - if x is not None: - assert x.ndim == 2 - #x = np.atleast_2d(x) - if isdiagonal and len(row_names) > 0: - #assert 1 in x.shape,"Matrix error: diagonal matrix must have " +\ - # "one dimension == 1,shape is {0}".format(x.shape) - mx_dim = max(x.shape) - assert len(row_names) == mx_dim,\ - 'Matrix.__init__(): diagonal shape[1] != len(row_names) ' +\ - str(x.shape) + ' ' + str(len(row_names)) - #x = x.transpose() - else: - if len(row_names) > 0: - assert len(row_names) == x.shape[0],\ - 'Matrix.__init__(): shape[0] != len(row_names) ' +\ - str(x.shape) + ' ' + str(len(row_names)) - if len(col_names) > 0: - # if this a row vector - if len(row_names) == 0 and x.shape[1] == 1: - x.transpose() - assert len(col_names) == x.shape[1],\ - 'Matrix.__init__(): shape[1] != len(col_names) ' + \ - str(x.shape) + ' ' + str(len(col_names)) - self.__x = x - - self.isdiagonal = bool(isdiagonal) - self.autoalign = bool(autoalign) - -
    [docs] def reset_x(self,x,copy=True): - """reset self.__x private attribute - - Parameters - ---------- - x : numpy.ndarray - copy : bool - flag to make a copy of 'x'. Defaule is True - - Note - ---- - makes a copy of 'x' argument - - """ - assert x.shape == self.shape - if copy: - self.__x = x.copy() - else: - self.__x = x
    - - def __str__(self): - """overload of object.__str__() - - Returns - ------- - str : str - - """ - s = "shape:{0}:{1}".format(*self.shape)+" row names: " + str(self.row_names) + \ - '\n' + "col names: " + str(self.col_names) + '\n' + str(self.__x) - return s - - def __getitem__(self, item): - """a very crude overload of object.__getitem__(). - - Parameters - ---------- - item : iterable - something that can be used as an index - - Returns - ------- - Matrix : Matrix - an object that is a sub-Matrix of self - - """ - if self.isdiagonal and isinstance(item, tuple): - submat = np.atleast_2d((self.__x[item[0]])) - else: - submat = np.atleast_2d(self.__x[item]) - # transpose a row vector to a column vector - if submat.shape[0] == 1: - submat = submat.transpose() - row_names = self.row_names[:submat.shape[0]] - if self.isdiagonal: - col_names = row_names - else: - col_names = self.col_names[:submat.shape[1]] - return type(self)(x=submat, isdiagonal=self.isdiagonal, - row_names=row_names, col_names=col_names, - autoalign=self.autoalign) - - - def __pow__(self, power): - """overload of numpy.ndarray.__pow__() operator - - Parameters - ---------- - power: (int or float) - interpreted as follows: -1 = inverse of self, - -0.5 = sqrt of inverse of self, - 0.5 = sqrt of self. All other positive - ints = elementwise self raised to power - - Returns - ------- - Matrix : Matrix - a new Matrix object - - """ - if power < 0: - if power == -1: - return self.inv - elif power == -0.5: - return (self.inv).sqrt - else: - raise NotImplementedError("Matrix.__pow__() not implemented " + - "for negative powers except for -1") - - elif int(power) != float(power): - if power == 0.5: - return self.sqrt - else: - raise NotImplementedError("Matrix.__pow__() not implemented " + - "for fractional powers except 0.5") - else: - return type(self)(self.__x**power, row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) - - - def __sub__(self, other): - """numpy.ndarray.__sub__() overload. Tries to speedup by - checking for scalars of diagonal matrices on either side of operator - - Parameters - ---------- - other : scalar,numpy.ndarray,Matrix object - the thing to difference - - Returns - ------- - Matrix : Matrix - - """ - - if np.isscalar(other): - return Matrix(x=self.x - other, row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) - else: - if isinstance(other, np.ndarray): - assert self.shape == other.shape, "Matrix.__sub__() shape" +\ - "mismatch: " +\ - str(self.shape) + ' ' + \ - str(other.shape) - if self.isdiagonal: - elem_sub = -1.0 * other - for j in range(self.shape[0]): - elem_sub[j, j] += self.x[j] - return type(self)(x=elem_sub, row_names=self.row_names, - col_names=self.col_names) - else: - return type(self)(x=self.x - other, - row_names=self.row_names, - col_names=self.col_names) - elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) - - if len(common_rows) == 0: - raise Exception("Matrix.__sub__ error: no common rows") - - if len(common_cols) == 0: - raise Exception("Matrix.__sub__ error: no common cols") - first = self.get(row_names=common_rows, - col_names=common_cols) - second = other.get(row_names=common_rows, - col_names=common_cols) - else: - assert self.shape == other.shape, \ - "Matrix.__sub__():shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) - first = self - second = other - - if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x - second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) - elif first.isdiagonal: - elem_sub = -1.0 * second.newx - for j in range(first.shape[0]): - elem_sub[j, j] += first.x[j, 0] - return type(self)(x=elem_sub, row_names=first.row_names, - col_names=first.col_names) - elif second.isdiagonal: - elem_sub = first.newx - for j in range(second.shape[0]): - elem_sub[j, j] -= second.x[j, 0] - return type(self)(x=elem_sub, row_names=first.row_names, - col_names=first.col_names) - else: - return type(self)(x=first.x - second.x, - row_names=first.row_names, - col_names=first.col_names) - - - def __add__(self, other): - """Overload of numpy.ndarray.__add__(). Tries to speedup by checking for - scalars of diagonal matrices on either side of operator - - Parameters - ---------- - other : scalar,numpy.ndarray,Matrix object - the thing to add - - Returns - ------- - Matrix : Matrix - - """ - if np.isscalar(other): - return type(self)(x=self.x + other,row_names=self.row_names, - col_names=self.col_names,isdiagonal=self.isdiagonal) - if isinstance(other, np.ndarray): - assert self.shape == other.shape, \ - "Matrix.__add__(): shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) - if self.isdiagonal: - raise NotImplementedError("Matrix.__add__ not supported for" + - "diagonal self") - else: - return type(self)(x=self.x + other, row_names=self.row_names, - col_names=self.col_names) - elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) - if len(common_rows) == 0: - raise Exception("Matrix.__add__ error: no common rows") - - if len(common_cols) == 0: - raise Exception("Matrix.__add__ error: no common cols") - - first = self.get(row_names=common_rows, col_names=common_cols) - second = other.get(row_names=common_rows, col_names=common_cols) - else: - assert self.shape == other.shape, \ - "Matrix.__add__(): shape mismatch: " +\ - str(self.shape) + ' ' + str(other.shape) - first = self - second = other - if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x + second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) - elif first.isdiagonal: - ox = second.newx - for j in range(first.shape[0]): - ox[j, j] += first.__x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=first.col_names) - elif second.isdiagonal: - x = first.x - for j in range(second.shape[0]): - x[j, j] += second.x[j] - return type(self)(x=x, row_names=first.row_names, - col_names=first.col_names) - else: - return type(self)(x=first.x + second.x, - row_names=first.row_names, - col_names=first.col_names) - else: - raise Exception("Matrix.__add__(): unrecognized type for " + - "other in __add__: " + str(type(other))) - -
    [docs] def hadamard_product(self, other): - """Overload of numpy.ndarray.__mult__(): element-wise multiplication. - Tries to speedup by checking for scalars of diagonal matrices on - either side of operator - - Parameters - ---------- - other : scalar,numpy.ndarray,Matrix object - the thing for element-wise multiplication - - Returns - ------- - Matrix : Matrix - - """ - if np.isscalar(other): - return type(self)(x=self.x * other) - if isinstance(other, np.ndarray): - assert self.shape == other.shape, \ - "Matrix.hadamard_product(): shape mismatch: " + \ - str(self.shape) + ' ' + str(other.shape) - if self.isdiagonal: - raise NotImplementedError("Matrix.hadamard_product() not supported for" + - "diagonal self") - else: - return type(self)(x=self.x * other, row_names=self.row_names, - col_names=self.col_names) - elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.element_isaligned(other): - common_rows = get_common_elements(self.row_names, - other.row_names) - common_cols = get_common_elements(self.col_names, - other.col_names) - if len(common_rows) == 0: - raise Exception("Matrix.hadamard_product error: no common rows") - - if len(common_cols) == 0: - raise Exception("Matrix.hadamard_product error: no common cols") - - first = self.get(row_names=common_rows, col_names=common_cols) - second = other.get(row_names=common_rows, col_names=common_cols) - else: - assert self.shape == other.shape, \ - "Matrix.hadamard_product(): shape mismatch: " + \ - str(self.shape) + ' ' + str(other.shape) - first = self - second = other - - if first.isdiagonal and second.isdiagonal: - return type(self)(x=first.x * second.x, isdiagonal=True, - row_names=first.row_names, - col_names=first.col_names) - # elif first.isdiagonal: - # #ox = second.as_2d - # #for j in range(first.shape[0]): - # # ox[j, j] *= first.__x[j] - # return type(self)(x=first.as_2d * second.as_2d, row_names=first.row_names, - # col_names=first.col_names) - # elif second.isdiagonal: - # #x = first.as_2d - # #for j in range(second.shape[0]): - # # x[j, j] *= second.x[j] - # return type(self)(x=first.x * second.as_2d, row_names=first.row_names, - # col_names=first.col_names) - else: - return type(self)(x=first.as_2d * second.as_2d, - row_names=first.row_names, - col_names=first.col_names) - else: - raise Exception("Matrix.hadamard_product(): unrecognized type for " + - "other: " + str(type(other)))
    - - - def __mul__(self, other): - """Dot product multiplication overload. Tries to speedup by - checking for scalars or diagonal matrices on either side of operator - - Parameters - ---------- - other : scalar,numpy.ndarray,Matrix object - the thing the dot product against - - Returns: - Matrix : Matrix - """ - if np.isscalar(other): - return type(self)(x=self.x.copy() * other, - row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal) - elif isinstance(other, np.ndarray): - assert self.shape[1] == other.shape[0], \ - "Matrix.__mul__(): matrices are not aligned: " +\ - str(self.shape) + ' ' + str(other.shape) - if self.isdiagonal: - return type(self)(x=np.dot(np.diag(self.__x.flatten()).transpose(), - other)) - else: - return type(self)(x=np.atleast_2d(np.dot(self.__x, other))) - elif isinstance(other, Matrix): - if self.autoalign and other.autoalign\ - and not self.mult_isaligned(other): - common = get_common_elements(self.col_names, other.row_names) - assert len(common) > 0,"Matrix.__mult__():self.col_names " +\ - "and other.row_names" +\ - "don't share any common elements. first 10: " +\ - ','.join(self.col_names[:9]) + '...and..' +\ - ','.join(other.row_names[:9]) - # these should be aligned - if isinstance(self, Cov): - first = self.get(row_names=common, col_names=common) - else: - first = self.get(row_names=self.row_names, col_names=common) - if isinstance(other, Cov): - second = other.get(row_names=common, col_names=common) - else: - second = other.get(row_names=common, - col_names=other.col_names) - - else: - assert self.shape[1] == other.shape[0], \ - "Matrix.__mul__(): matrices are not aligned: " +\ - str(self.shape) + ' ' + str(other.shape) - first = self - second = other - if first.isdiagonal and second.isdiagonal: - elem_prod = type(self)(x=first.x.transpose() * second.x, - row_names=first.row_names, - col_names=second.col_names) - elem_prod.isdiagonal = True - return elem_prod - elif first.isdiagonal: - ox = second.newx - for j in range(first.shape[0]): - ox[j, :] *= first.x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=second.col_names) - elif second.isdiagonal: - x = first.newx - ox = second.x - for j in range(first.shape[1]): - x[:, j] *= ox[j] - return type(self)(x=x, row_names=first.row_names, - col_names=second.col_names) - else: - return type(self)(np.dot(first.x, second.x), - row_names=first.row_names, - col_names=second.col_names) - else: - raise Exception("Matrix.__mul__(): unrecognized " + - "other arg type in __mul__: " + str(type(other))) - - - def __rmul__(self, other): - """Reverse order Dot product multiplication overload. - - Parameters - ---------- - other : scalar,numpy.ndarray,Matrix object - the thing the dot product against - - Returns - ------- - Matrix : Matrix - - """ - - if np.isscalar(other): - return type(self)(x=self.x.copy() * other,row_names=self.row_names,\ - col_names=self.col_names,isdiagonal=self.isdiagonal) - elif isinstance(other, np.ndarray): - assert self.shape[0] == other.shape[1], \ - "Matrix.__rmul__(): matrices are not aligned: " +\ - str(other.shape) + ' ' + str(self.shape) - if self.isdiagonal: - return type(self)(x=np.dot(other,np.diag(self.__x.flatten()).\ - transpose())) - else: - return type(self)(x=np.dot(other,self.__x)) - elif isinstance(other, Matrix): - if self.autoalign and other.autoalign \ - and not self.mult_isaligned(other): - common = get_common_elements(self.row_names, other.col_names) - assert len(common) > 0,"Matrix.__rmul__():self.col_names " +\ - "and other.row_names" +\ - "don't share any common elements" - # these should be aligned - if isinstance(self, Cov): - first = self.get(row_names=common, col_names=common) - else: - first = self.get(col_names=self.row_names, row_names=common) - if isinstance(other, Cov): - second = other.get(row_names=common, col_names=common) - else: - second = other.get(col_names=common, - row_names=other.col_names) - - else: - assert self.shape[0] == other.shape[1], \ - "Matrix.__rmul__(): matrices are not aligned: " +\ - str(other.shape) + ' ' + str(self.shape) - first = other - second = self - if first.isdiagonal and second.isdiagonal: - elem_prod = type(self)(x=first.x.transpose() * second.x, - row_names=first.row_names, - col_names=second.col_names) - elem_prod.isdiagonal = True - return elem_prod - elif first.isdiagonal: - ox = second.newx - for j in range(first.shape[0]): - ox[j, :] *= first.x[j] - return type(self)(x=ox, row_names=first.row_names, - col_names=second.col_names) - elif second.isdiagonal: - x = first.newx - ox = second.x - for j in range(first.shape[1]): - x[:, j] *= ox[j] - return type(self)(x=x, row_names=first.row_names, - col_names=second.col_names) - else: - return type(self)(np.dot(first.x, second.x), - row_names=first.row_names, - col_names=second.col_names) - else: - raise Exception("Matrix.__rmul__(): unrecognized " + - "other arg type in __mul__: " + str(type(other))) - - - def __set_svd(self): - """private method to set SVD components. - - Note: this should not be called directly - - """ - if self.isdiagonal: - x = np.diag(self.x.flatten()) - else: - # just a pointer to x - x = self.x - try: - - u, s, v = la.svd(x, full_matrices=True) - v = v.transpose() - except Exception as e: - print("standard SVD failed: {0}".format(str(e))) - try: - v, s, u = la.svd(x.transpose(), full_matrices=True) - u = u.transpose() - except Exception as e: - np.savetxt("failed_svd.dat",x,fmt="%15.6E") - raise Exception("Matrix.__set_svd(): " + - "unable to compute SVD of self.x, " + - "saved matrix to 'failed_svd.dat' -- {0}".\ - format(str(e))) - - col_names = ["left_sing_vec_" + str(i + 1) for i in range(u.shape[1])] - self.__u = Matrix(x=u, row_names=self.row_names, - col_names=col_names, autoalign=False) - - sing_names = ["sing_val_" + str(i + 1) for i in range(s.shape[0])] - self.__s = Matrix(x=np.atleast_2d(s).transpose(), row_names=sing_names, - col_names=sing_names, isdiagonal=True, - autoalign=False) - - col_names = ["right_sing_vec_" + str(i + 1) for i in range(v.shape[0])] - self.__v = Matrix(v, row_names=self.col_names, col_names=col_names, - autoalign=False) - -
    [docs] def mult_isaligned(self, other): - """check if matrices are aligned for dot product multiplication - - Parameters - ---------- - other : (Matrix) - - Returns - ------- - bool : bool - True if aligned, False if not aligned - """ - assert isinstance(other, Matrix), \ - "Matrix.isaligned(): other argumnent must be type Matrix, not: " +\ - str(type(other)) - if self.col_names == other.row_names: - return True - else: - return False
    - - -
    [docs] def element_isaligned(self, other): - """check if matrices are aligned for element-wise operations - - Parameters - ---------- - other : Matrix - - Returns - ------- - bool : bool - True if aligned, False if not aligned - """ - assert isinstance(other, Matrix), \ - "Matrix.isaligned(): other argument must be type Matrix, not: " +\ - str(type(other)) - if self.row_names == other.row_names \ - and self.col_names == other.col_names: - return True - else: - return False
    - - - @property - def newx(self): - """return a copy of x - - Returns - ------- - numpy.ndarray : numpy.ndarray - - """ - return self.__x.copy() - - - @property - def x(self): - """return a reference to x - - Returns - ------- - numpy.ndarray : numpy.ndarray - - """ - return self.__x - - @property - def as_2d(self): - """ get a 2D representation of x. If not self.isdiagonal, simply - return reference to self.x, otherwise, constructs and returns - a 2D, diagonal ndarray - - Returns - ------- - numpy.ndarray : numpy.ndarray - - """ - if not self.isdiagonal: - return self.x - return np.diag(self.x.flatten()) - - @property - def shape(self): - """get the implied, 2D shape of self - - Returns - ------- - tuple : tuple - length 2 tuple of ints - - """ - if self.__x is not None: - if self.isdiagonal: - return (max(self.__x.shape), max(self.__x.shape)) - if len(self.__x.shape) == 1: - raise Exception("Matrix.shape: Matrix objects must be 2D") - return self.__x.shape - return None - - @property - def ncol(self): - """ length of second dimension - - Returns - ------- - int : int - number of columns - - """ - return self.shape[1] - - @property - def nrow(self): - """ length of first dimensions - - Returns - ------- - int : int - number of rows - - """ - return self.shape[0] - - @property - def T(self): - """wrapper function for Matrix.transpose() method - - """ - return self.transpose - - - @property - def transpose(self): - """transpose operation of self - - Returns - ------- - Matrix : Matrix - transpose of self - - """ - if not self.isdiagonal: - return type(self)(x=self.__x.copy().transpose(), - row_names=self.col_names, - col_names=self.row_names, - autoalign=self.autoalign) - else: - return type(self)(x=self.__x.copy(), row_names=self.row_names, - col_names=self.col_names, - isdiagonal=True, autoalign=self.autoalign) - - - @property - def inv(self): - """inversion operation of self - - Returns - ------- - Matrix : Matrix - inverse of self - - """ - - if self.isdiagonal: - inv = 1.0 / self.__x - if (np.any(~np.isfinite(inv))): - idx = np.isfinite(inv) - np.savetxt("testboo.dat",idx) - invalid = [self.row_names[i] for i in range(idx.shape[0]) if idx[i] == 0.0] - raise Exception("Matrix.inv has produced invalid floating points " + - " for the following elements:" + ','.join(invalid)) - return type(self)(x=inv, isdiagonal=True, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - else: - return type(self)(x=la.inv(self.__x), row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - -
    [docs] def get_maxsing(self,eigthresh=1.0e-5): - """ Get the number of singular components with a singular - value ratio greater than or equal to eigthresh - - Parameters - ---------- - eigthresh : float - the ratio of the largest to smallest singular value - - Returns - ------- - int : int - number of singular components - - """ - #sthresh =np.abs((self.s.x / self.s.x[0]) - eigthresh) - sthresh = self.s.x.flatten()/self.s.x[0] - ising = 0 - for i,st in enumerate(sthresh): - if st > eigthresh: - ising += 1 - #return max(1,i) - else: - break - #return max(1,np.argmin(sthresh)) - return max(1,ising)
    - -
    [docs] def pseudo_inv_components(self,maxsing=None,eigthresh=1.0e-5): - """ Get the truncated SVD components - - Parameters - ---------- - maxsing : int - the number of singular components to use. If None, - maxsing is calculated using Matrix.get_maxsing() and eigthresh - eigthresh : float - the ratio of largest to smallest singular components to use - for truncation. Ignored if maxsing is not None - - Returns - ------- - u : Matrix - truncated left singular vectors - s : Matrix - truncated singular value matrix - v : Matrix - truncated right singular vectors - - """ - - if maxsing is None: - maxsing = self.get_maxsing(eigthresh=eigthresh) - - s = self.s[:maxsing,:maxsing] - v = self.v[:,:maxsing] - u = self.u[:,:maxsing] - return u,s,v
    - -
    [docs] def pseudo_inv(self,maxsing=None,eigthresh=1.0e-5): - """ The pseudo inverse of self. Formed using truncated singular - value decomposition and Matrix.pseudo_inv_components - - Parameters - ---------- - maxsing : int - the number of singular components to use. If None, - maxsing is calculated using Matrix.get_maxsing() and eigthresh - eigthresh : float - the ratio of largest to smallest singular components to use - for truncation. Ignored if maxsing is not None - - Returns - ------- - Matrix : Matrix - """ - if maxsing is None: - maxsing = self.get_maxsing(eigthresh=eigthresh) - full_s = self.full_s.T - for i in range(self.s.shape[0]): - if i <= maxsing: - full_s.x[i,i] = 1.0 / full_s.x[i,i] - else: - full_s.x[i,i] = 0.0 - return self.v * full_s * self.u.T
    - - @property - def sqrt(self): - """square root operation - - Returns - ------- - Matrix : Matrix - square root of self - - """ - if self.isdiagonal: - return type(self)(x=np.sqrt(self.__x), isdiagonal=True, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - elif self.shape[1] == 1: #a vector - return type(self)(x=np.sqrt(self.__x), isdiagonal=False, - row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - else: - return type(self)(x=la.sqrtm(self.__x), row_names=self.row_names, - col_names=self.col_names, - autoalign=self.autoalign) - @property - def full_s(self): - """ Get the full singular value matrix of self - - Returns - ------- - Matrix : Matrix - - """ - x = np.zeros((self.shape),dtype=np.float32) - - x[:self.s.shape[0],:self.s.shape[0]] = self.s.as_2d - s = Matrix(x=x, row_names=self.row_names, - col_names=self.col_names, isdiagonal=False, - autoalign=False) - return s - - @property - def s(self): - """the singular value (diagonal) Matrix - - Returns - ------- - Matrix : Matrix - - """ - if self.__s is None: - self.__set_svd() - return self.__s - - - @property - def u(self): - """the left singular vector Matrix - - Returns - ------- - Matrix : Matrix - - """ - if self.__u is None: - self.__set_svd() - return self.__u - - - @property - def v(self): - """the right singular vector Matrix - - Returns - ------- - Matrix : Matrix - - """ - if self.__v is None: - self.__set_svd() - return self.__v - - @property - def zero2d(self): - """ get an 2D instance of self with all zeros - - Returns - ------- - Matrix : Matrix - - """ - return type(self)(x=np.atleast_2d(np.zeros((self.shape[0],self.shape[1]))), - row_names=self.row_names, - col_names=self.col_names, - isdiagonal=False) - - -
    [docs] @staticmethod - def find_rowcol_indices(names,row_names,col_names,axis=None): - self_row_idxs = {row_names[i]: i for i in range(len(row_names))} - self_col_idxs = {col_names[i]: i for i in range(len(col_names))} - - scol = set(col_names) - srow = set(row_names) - row_idxs = [] - col_idxs = [] - for name in names: - name = name.lower() - if name not in scol \ - and name not in srow: - raise Exception('Matrix.indices(): name not found: ' + name) - if name in scol: - col_idxs.append(self_col_idxs[name]) - if name.lower() in srow: - row_idxs.append(self_row_idxs[name]) - if axis is None: - return np.array(row_idxs, dtype=np.int32), \ - np.array(col_idxs, dtype=np.int32) - elif axis == 0: - if len(row_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in row_names") - return np.array(row_idxs, dtype=np.int32) - elif axis == 1: - if len(col_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in col_names") - return np.array(col_idxs, dtype=np.int32) - else: - raise Exception("Matrix.indices(): " + - "axis argument must 0 or 1, not:" + str(axis))
    - -
    [docs] def indices(self, names, axis=None): - """get the row and col indices of names. If axis is None, two ndarrays - are returned, corresponding the indices of names for each axis - - Parameters - ---------- - names : iterable - column and/or row names - axis : (int) (optional) - the axis to search. - - Returns - ------- - numpy.ndarray : numpy.ndarray - indices of names. - - """ - return Matrix.find_rowcol_indices(names,self.row_names,self.col_names,axis=axis)
    - - - - -
    [docs] def old_indices(self, names, axis=None): - """get the row and col indices of names. If axis is None, two ndarrays - are returned, corresponding the indices of names for each axis - - Parameters - ---------- - names : iterable - column and/or row names - axis : (int) (optional) - the axis to search. - - Returns - ------- - numpy.ndarray : numpy.ndarray - indices of names. - - """ - warnings.warn("Matrix.old_indices() is deprecated - only here for testing. Use Matrix.indices()",PyemuWarning) - row_idxs, col_idxs = [], [] - for name in names: - if name.lower() not in self.col_names \ - and name.lower() not in self.row_names: - raise Exception('Matrix.indices(): name not found: ' + name) - if name.lower() in self.col_names: - col_idxs.append(self.col_names.index(name)) - if name.lower() in self.row_names: - row_idxs.append(self.row_names.index(name)) - if axis is None: - return np.array(row_idxs, dtype=np.int32),\ - np.array(col_idxs, dtype=np.int32) - elif axis == 0: - if len(row_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in row_names") - return np.array(row_idxs, dtype=np.int32) - elif axis == 1: - if len(col_idxs) != len(names): - raise Exception("Matrix.indices(): " + - "not all names found in col_names") - return np.array(col_idxs, dtype=np.int32) - else: - raise Exception("Matrix.indices(): " + - "axis argument must 0 or 1, not:" + str(axis))
    - - -
    [docs] def align(self, names, axis=None): - """reorder self by names. If axis is None, reorder both indices - - Parameters - ---------- - names : iterable - names in rowS and\or columnS - axis : (int) - the axis to reorder. if None, reorder both axes - - """ - if not isinstance(names, list): - names = [names] - row_idxs, col_idxs = self.indices(names) - if self.isdiagonal or isinstance(self, Cov): - assert row_idxs.shape == col_idxs.shape - assert row_idxs.shape[0] == self.shape[0] - if self.isdiagonal: - self.__x = self.__x[row_idxs] - else: - self.__x = self.__x[row_idxs, :] - self.__x = self.__x[:, col_idxs] - row_names = [] - [row_names.append(self.row_names[i]) for i in row_idxs] - self.row_names, self.col_names = row_names, row_names - - else: - if axis is None: - raise Exception("Matrix.align(): must specify axis in " + - "align call for non-diagonal instances") - if axis == 0: - assert row_idxs.shape[0] == self.shape[0], \ - "Matrix.align(): not all names found in self.row_names" - self.__x = self.__x[row_idxs, :] - row_names = [] - [row_names.append(self.row_names[i]) for i in row_idxs] - self.row_names = row_names - elif axis == 1: - assert col_idxs.shape[0] == self.shape[1], \ - "Matrix.align(): not all names found in self.col_names" - self.__x = self.__x[:, col_idxs] - col_names = [] - [col_names.append(self.col_names[i]) for i in row_idxs] - self.col_names = col_names - else: - raise Exception("Matrix.align(): axis argument to align()" + - " must be either 0 or 1")
    - - -
    [docs] def get(self, row_names=None, col_names=None, drop=False): - """get a new Matrix instance ordered on row_names or col_names - - Parameters - ---------- - row_names : iterable - row_names for new Matrix - col_names : iterable - col_names for new Matrix - drop : bool - flag to remove row_names and/or col_names - - Returns - ------- - Matrix : Matrix - - """ - if row_names is None and col_names is None: - raise Exception("Matrix.get(): must pass at least" + - " row_names or col_names") - - if row_names is not None and not isinstance(row_names, list): - row_names = [row_names] - if col_names is not None and not isinstance(col_names, list): - col_names = [col_names] - - if isinstance(self,Cov) and (row_names is None or col_names is None ): - if row_names is not None: - idxs = self.indices(row_names, axis=0) - names = row_names - else: - idxs = self.indices(col_names, axis=1) - names = col_names - - if self.isdiagonal: - extract = self.__x[idxs].copy() - else: - extract = self.__x[idxs, :].copy() - extract = extract[:, idxs] - if drop: - self.drop(names, 0) - return Cov(x=extract, names=names, isdiagonal=self.isdiagonal) - if self.isdiagonal: - extract = np.diag(self.__x[:, 0]) - else: - extract = self.__x.copy() - if row_names is not None: - row_idxs = self.indices(row_names, axis=0) - extract = np.atleast_2d(extract[row_idxs, :].copy()) - if drop: - self.drop(row_names, axis=0) - else: - row_names = self.row_names - if col_names is not None: - col_idxs = self.indices(col_names, axis=1) - extract = np.atleast_2d(extract[:, col_idxs].copy()) - if drop: - self.drop(col_names, axis=1) - else: - col_names = copy.deepcopy(self.col_names) - - return type(self)(x=extract, row_names=row_names, col_names=col_names)
    - - -
    [docs] def copy(self): - return type(self)(x=self.newx,row_names=self.row_names, - col_names=self.col_names, - isdiagonal=self.isdiagonal,autoalign=self.autoalign)
    - - -
    [docs] def drop(self, names, axis): - """ drop elements from self in place - - Parameters - ---------- - names : iterable - names to drop - axis : (int) - the axis to drop from. must be in [0,1] - - """ - if axis is None: - raise Exception("Matrix.drop(): axis arg is required") - if not isinstance(names, list): - names = [names] - if axis == 1: - assert len(names) < self.shape[1], "can't drop all names along axis 1" - else: - assert len(names) < self.shape[0], "can't drop all names along axis 0" - - idxs = self.indices(names, axis=axis) - - - - if self.isdiagonal: - self.__x = np.delete(self.__x, idxs, 0) - keep_names = [name for name in self.row_names if name not in names] - assert len(keep_names) == self.__x.shape[0],"shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape) - self.row_names = keep_names - self.col_names = copy.deepcopy(keep_names) - # idxs = np.sort(idxs) - # for idx in idxs[::-1]: - # del self.row_names[idx] - # del self.col_names[idx] - elif isinstance(self,Cov): - self.__x = np.delete(self.__x, idxs, 0) - self.__x = np.delete(self.__x, idxs, 1) - keep_names = [name for name in self.row_names if name not in names] - - assert len(keep_names) == self.__x.shape[0],"shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape) - self.row_names = keep_names - self.col_names = copy.deepcopy(keep_names) - # idxs = np.sort(idxs) - # for idx in idxs[::-1]: - # del self.row_names[idx] - # del self.col_names[idx] - elif axis == 0: - if idxs.shape[0] == self.shape[0]: - raise Exception("Matrix.drop(): can't drop all rows") - elif idxs.shape == 0: - raise Exception("Matrix.drop(): nothing to drop on axis 0") - self.__x = np.delete(self.__x, idxs, 0) - keep_names = [name for name in self.row_names if name not in names] - assert len(keep_names) == self.__x.shape[0],"shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape) - self.row_names = keep_names - # idxs = np.sort(idxs) - # for idx in idxs[::-1]: - # del self.row_names[idx] - elif axis == 1: - if idxs.shape[0] == self.shape[1]: - raise Exception("Matrix.drop(): can't drop all cols") - if idxs.shape == 0: - raise Exception("Matrix.drop(): nothing to drop on axis 1") - self.__x = np.delete(self.__x, idxs, 1) - keep_names = [name for name in self.col_names if name not in names] - assert len(keep_names) == self.__x.shape[1],"shape-name mismatch:"+\ - "{0}:{0}".format(len(keep_names),self.__x.shape) - self.col_names = keep_names - # idxs = np.sort(idxs) - # for idx in idxs[::-1]: - # del self.col_names[idx] - else: - raise Exception("Matrix.drop(): axis argument must be 0 or 1")
    - - -
    [docs] def extract(self, row_names=None, col_names=None): - """wrapper method that Matrix.gets() then Matrix.drops() elements. - one of row_names or col_names must be not None. - - Parameters - ---------- - row_names : iterable - row names to extract - col_names : (enumerate) - col_names to extract - - Returns - ------- - Matrix : Matrix - - """ - if row_names is None and col_names is None: - raise Exception("Matrix.extract() " + - "row_names and col_names both None") - extract = self.get(row_names, col_names, drop=True) - return extract
    - -
    [docs] def get_diagonal_vector(self, col_name="diag"): - """Get a new Matrix instance that is the diagonal of self. The - shape of the new matrix is (self.shape[0],1). Self must be square - - Parameters: - col_name : str - the name of the column in the new Matrix - - Returns: - Matrix : Matrix - """ - assert self.shape[0] == self.shape[1] - assert not self.isdiagonal - assert isinstance(col_name,str) - return type(self)(x=np.atleast_2d(np.diag(self.x)).transpose(), - row_names=self.row_names, - col_names=[col_name],isdiagonal=False)
    - - -
    [docs] def to_coo(self,filename,droptol=None,chunk=None): - """write a PEST-compatible binary file. The data format is - [int,int,float] for i,j,value. It is autodetected during - the read with Matrix.from_binary(). - - Parameters - ---------- - filename : str - filename to save binary file - droptol : float - absolute value tolerance to make values smaller than zero. Default is None - chunk : int - number of elements to write in a single pass. Default is None - - """ - if self.isdiagonal: - #raise NotImplementedError() - self.__x = self.as_2d - self.isdiagonal = False - if droptol is not None: - self.x[np.abs(self.x) < droptol] = 0.0 - f = open(filename, 'wb') - #print("counting nnz") - nnz = np.count_nonzero(self.x) #number of non-zero entries - # write the header - header = np.array((self.shape[1], self.shape[0], nnz), - dtype=self.binary_header_dt) - header.tofile(f) - # get the indices of non-zero entries - #print("getting nnz idxs") - row_idxs, col_idxs = np.nonzero(self.x) - - if chunk is None: - flat = self.x[row_idxs, col_idxs].flatten() - data = np.core.records.fromarrays([row_idxs,col_idxs,flat],dtype=self.coo_rec_dt) - data.tofile(f) - else: - - start,end = 0,min(chunk,row_idxs.shape[0]) - while True: - #print(row_idxs[start],row_idxs[end]) - #print("chunk",start,end) - flat = self.x[row_idxs[start:end],col_idxs[start:end]].flatten() - data = np.core.records.fromarrays([row_idxs[start:end],col_idxs[start:end], - flat], - dtype=self.coo_rec_dt) - data.tofile(f) - if end == row_idxs.shape[0]: - break - start = end - end = min(row_idxs.shape[0],start + chunk) - - - for name in self.col_names: - if len(name) > self.new_par_length: - name = name[:self.new_par_length - 1] - elif len(name) < self.new_par_length: - for i in range(len(name), self.new_par_length): - name = name + ' ' - f.write(name.encode()) - for name in self.row_names: - if len(name) > self.new_obs_length: - name = name[:self.new_obs_length - 1] - elif len(name) < self.new_obs_length: - for i in range(len(name), self.new_obs_length): - name = name + ' ' - f.write(name.encode()) - f.close()
    - - - -
    [docs] def to_binary(self, filename,droptol=None, chunk=None): - """write a PEST-compatible binary file. The format is the same - as the format used to storage a PEST Jacobian matrix - - Parameters - ---------- - filename : str - filename to save binary file - droptol : float - absolute value tolerance to make values smaller than zero. Default is None - chunk : int - number of elements to write in a single pass. Default is None - - """ - if np.any(np.isnan(self.x)): - raise Exception("Matrix.to_binary(): nans found") - if self.isdiagonal: - #raise NotImplementedError() - self.__x = self.as_2d - self.isdiagonal = False - if droptol is not None: - self.x[np.abs(self.x) < droptol] = 0.0 - f = open(filename, 'wb') - nnz = np.count_nonzero(self.x) #number of non-zero entries - # write the header - header = np.array((-self.shape[1], -self.shape[0], nnz), - dtype=self.binary_header_dt) - header.tofile(f) - # get the indices of non-zero entries - row_idxs, col_idxs = np.nonzero(self.x) - icount = row_idxs + 1 + col_idxs * self.shape[0] - # flatten the array - #flat = self.x[row_idxs, col_idxs].flatten() - # zip up the index position and value pairs - #data = np.array(list(zip(icount, flat)), dtype=self.binary_rec_dt) - - - if chunk is None: - flat = self.x[row_idxs, col_idxs].flatten() - data = np.core.records.fromarrays([icount, flat], dtype=self.binary_rec_dt) - # write - data.tofile(f) - else: - start,end = 0,min(chunk,row_idxs.shape[0]) - while True: - #print(row_idxs[start],row_idxs[end]) - flat = self.x[row_idxs[start:end], col_idxs[start:end]].flatten() - data = np.core.records.fromarrays([icount[start:end], - flat], - dtype=self.binary_rec_dt) - data.tofile(f) - if end == row_idxs.shape[0]: - break - start = end - end = min(row_idxs.shape[0],start + chunk) - - - for name in self.col_names: - if len(name) > self.par_length: - name = name[:self.par_length - 1] - elif len(name) < self.par_length: - for i in range(len(name), self.par_length): - name = name + ' ' - f.write(name.encode()) - for name in self.row_names: - if len(name) > self.obs_length: - name = name[:self.obs_length - 1] - elif len(name) < self.obs_length: - for i in range(len(name), self.obs_length): - name = name + ' ' - f.write(name.encode()) - f.close()
    - - -
    [docs] @classmethod - def from_binary(cls,filename): - """class method load from PEST-compatible binary file into a - Matrix instance - - Parameters - ---------- - filename : str - filename to read - - Returns - ------- - Matrix : Matrix - - """ - x,row_names,col_names = Matrix.read_binary(filename) - if np.any(np.isnan(x)): - warnings.warn("Matrix.from_binary(): nans in matrix",PyemuWarning) - return cls(x=x, row_names=row_names, col_names=col_names)
    - -
    [docs] @staticmethod - def read_binary(filename, sparse=False): - - - f = open(filename, 'rb') - # the header datatype - itemp1, itemp2, icount = np.fromfile(f, Matrix.binary_header_dt, 1)[0] - if itemp1 > 0 and itemp2 < 0 and icount < 0: - print(" WARNING: it appears this file was \n" +\ - " written with 'sequential` " +\ - " binary fortran specification\n...calling " +\ - " Matrix.from_fortranfile()") - f.close() - return Matrix.from_fortranfile(filename) - ncol, nrow = abs(itemp1), abs(itemp2) - if itemp1 >= 0: - # raise TypeError('Matrix.from_binary(): Jco produced by ' + - # 'deprecated version of PEST,' + - # 'Use JcoTRANS to convert to new format') - print("'COO' format detected...") - - data = np.fromfile(f, Matrix.coo_rec_dt, icount) - if sparse: - data = scipy.sparse.coo_matrix((data["dtemp"],(data["i"],data['j'])),shape=(nrow,ncol)) - else: - x = np.zeros((nrow, ncol)) - x[data['i'], data['j']] = data["dtemp"] - data = x - # read obs and parameter names - col_names = [] - row_names = [] - for j in range(ncol): - name = struct.unpack(str(Matrix.new_par_length) + "s", - f.read(Matrix.new_par_length))[0] \ - .strip().lower().decode() - col_names.append(name) - for i in range(nrow): - name = struct.unpack(str(Matrix.new_obs_length) + "s", - f.read(Matrix.new_obs_length))[0] \ - .strip().lower().decode() - row_names.append(name) - f.close() - else: - - # read all data records - # using this a memory hog, but really fast - data = np.fromfile(f, Matrix.binary_rec_dt, icount) - icols = ((data['j'] - 1) // nrow) + 1 - irows = data['j'] - ((icols - 1) * nrow) - if sparse: - data = scipy.sparse.coo_matrix((data["dtemp"],(irows-1,icols-1)),shape=(nrow,ncol)) - else: - x = np.zeros((nrow, ncol)) - x[irows - 1, icols - 1] = data["dtemp"] - data = x - # read obs and parameter names - col_names = [] - row_names = [] - for j in range(ncol): - name = struct.unpack(str(Matrix.par_length) + "s", - f.read(Matrix.par_length))[0]\ - .strip().lower().decode() - col_names.append(name) - for i in range(nrow): - name = struct.unpack(str(Matrix.obs_length) + "s", - f.read(Matrix.obs_length))[0]\ - .strip().lower().decode() - row_names.append(name) - f.close() - assert len(row_names) == data.shape[0],\ - "Matrix.read_binary() len(row_names) (" + str(len(row_names)) +\ - ") != x.shape[0] (" + str(data.shape[0]) + ")" - assert len(col_names) == data.shape[1],\ - "Matrix.read_binary() len(col_names) (" + str(len(col_names)) +\ - ") != self.shape[1] (" + str(data.shape[1]) + ")" - return data,row_names,col_names
    - - -
    [docs] @classmethod - def from_fortranfile(cls, filename): - """ a binary load method to accommodate one of the many - bizarre fortran binary writing formats - - Parameters - ---------- - filename : str - name of the binary matrix file - - Returns - ------- - Matrix : Matrix - - """ - f = FortranFile(filename,mode='r') - itemp1, itemp2 = f.read_ints() - icount = f.read_ints() - if itemp1 >= 0: - raise TypeError('Matrix.from_binary(): Jco produced by ' + - 'deprecated version of PEST,' + - 'Use JcoTRANS to convert to new format') - ncol, nrow = abs(itemp1), abs(itemp2) - data = [] - for i in range(icount): - d = f.read_record(Matrix.binary_rec_dt)[0] - data.append(d) - data = np.array(data,dtype=Matrix.binary_rec_dt) - icols = ((data['j'] - 1) // nrow) + 1 - irows = data['j'] - ((icols - 1) * nrow) - x = np.zeros((nrow, ncol)) - x[irows - 1, icols - 1] = data["dtemp"] - row_names = [] - col_names = [] - for j in range(ncol): - name = f.read_record("|S12")[0].strip().decode() - col_names.append(name) - #obs_rec = np.dtype((np.str_, self.obs_length)) - for i in range(nrow): - name = f.read_record("|S20")[0].strip().decode() - row_names.append(name) - assert len(row_names) == x.shape[0],\ - "Matrix.from_fortranfile() len(row_names) (" + \ - str(len(row_names)) +\ - ") != self.shape[0] (" + str(x.shape[0]) + ")" - assert len(col_names) == x.shape[1],\ - "Matrix.from_fortranfile() len(col_names) (" + \ - str(len(col_names)) +\ - ") != self.shape[1] (" + str(x.shape[1]) + ")" - return cls(x=x,row_names=row_names,col_names=col_names)
    - -
    [docs] def to_ascii(self, out_filename, icode=2): - """write a PEST-compatible ASCII Matrix/vector file - - Parameters - ---------- - out_filename : str - output filename - icode : (int) - PEST-style info code for Matrix style - - """ - nrow, ncol = self.shape - f_out = open(out_filename, 'w') - f_out.write(' {0:7.0f} {1:7.0f} {2:7.0f}\n'. - format(nrow, ncol, icode)) - f_out.close() - f_out = open(out_filename,'ab') - if self.isdiagonal: - x = np.diag(self.__x[:, 0]) - else: - x = self.__x - np.savetxt(f_out, x, fmt='%15.7E', delimiter='') - f_out.close() - f_out = open(out_filename,'a') - if icode == 1: - f_out.write('* row and column names\n') - for r in self.row_names: - f_out.write(r + '\n') - else: - f_out.write('* row names\n') - for r in self.row_names: - f_out.write(r + '\n') - f_out.write('* column names\n') - for c in self.col_names: - f_out.write(c + '\n') - f_out.close()
    - - -
    [docs] @classmethod - def from_ascii(cls,filename): - """load a pest-compatible ASCII Matrix/vector file into a - Matrix instance - - Parameters - ---------- - filename : str - name of the file to read - - """ - x,row_names,col_names,isdiag = Matrix.read_ascii(filename) - return cls(x=x,row_names=row_names,col_names=col_names,isdiagonal=isdiag)
    - - -
    [docs] @staticmethod - def read_ascii(filename): - - f = open(filename, 'r') - raw = f.readline().strip().split() - nrow, ncol, icode = int(raw[0]), int(raw[1]), int(raw[2]) - #x = np.fromfile(f, dtype=self.double, count=nrow * ncol, sep=' ') - # this painfully slow and ugly read is needed to catch the - # fortran floating points that have 3-digit exponents, - # which leave out the base (e.g. 'e') : "-1.23455+300" - count = 0 - x = [] - while True: - line = f.readline() - if line == '': - raise Exception("Matrix.from_ascii() error: EOF") - raw = line.strip().split() - for r in raw: - try: - x.append(float(r)) - except: - # overflow - if '+' in r: - x.append(1.0e+30) - # underflow - elif '-' in r: - x.append(0.0) - else: - raise Exception("Matrix.from_ascii() error: " + - " can't cast " + r + " to float") - count += 1 - if count == (nrow * ncol): - break - if count == (nrow * ncol): - break - - x = np.array(x,dtype=Matrix.double) - x.resize(nrow, ncol) - line = f.readline().strip().lower() - if not line.startswith('*'): - raise Exception('Matrix.from_ascii(): error loading ascii file," +\ - "line should start with * not ' + line) - if 'row' in line and 'column' in line: - assert nrow == ncol - names = [] - for i in range(nrow): - line = f.readline().strip().lower() - names.append(line) - row_names = copy.deepcopy(names) - col_names = names - - else: - names = [] - for i in range(nrow): - line = f.readline().strip().lower() - names.append(line) - row_names = names - line = f.readline().strip().lower() - assert "column" in line, \ - "Matrix.from_ascii(): line should be * column names " +\ - "instead of: " + line - names = [] - for j in range(ncol): - line = f.readline().strip().lower() - names.append(line) - col_names = names - f.close() - # test for diagonal - isdiagonal=False - if nrow == ncol: - diag = np.diag(np.diag(x)) - diag_tol = 1.0e-6 - diag_delta = np.abs(diag.sum() - x.sum()) - if diag_delta < diag_tol: - isdiagonal = True - x = np.atleast_2d(np.diag(x)).transpose() - return x,row_names,col_names,isdiagonal
    - -
    [docs] def df(self): - """wrapper of Matrix.to_dataframe() - """ - return self.to_dataframe()
    - -
    [docs] @classmethod - def from_dataframe(cls, df): - """ class method to create a new Matrix instance from a - pandas.DataFrame - - Parameters - ---------- - df : pandas.DataFrame - - Returns - ------- - Matrix : Matrix - - """ - assert isinstance(df, pandas.DataFrame) - row_names = copy.deepcopy(list(df.index)) - col_names = copy.deepcopy(list(df.columns)) - return cls(x=df.as_matrix(),row_names=row_names,col_names=col_names)
    - - -
    [docs] @classmethod - def from_names(cls,row_names,col_names,isdiagonal=False,autoalign=True, random=False): - """ class method to create a new Matrix instance from - row names and column names, filled with trash - - Parameters - ---------- - row_names : iterable - row names for the new matrix - col_names : iterable - col_names for the new matrix - isdiagonal : bool - flag for diagonal matrix. Default is False - autoalign : bool - flag for autoaligning new matrix - during linear algebra calcs. Default - is True - random : bool - flag for contents of the trash matrix. - If True, fill with random numbers, if False, fill with zeros - Default is False - Returns - ------- - mat : Matrix - the new Matrix instance - - """ - if random: - return cls(x=np.random.random((len(row_names), len(col_names))), row_names=row_names, - col_names=col_names, isdiagonal=isdiagonal, autoalign=autoalign) - else: - return cls(x=np.empty((len(row_names),len(col_names))),row_names=row_names, - col_names=col_names,isdiagonal=isdiagonal,autoalign=autoalign)
    - - -
    [docs] def to_dataframe(self): - """return a pandas.DataFrame representation of the Matrix object - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - - """ - if self.isdiagonal: - x = np.diag(self.__x[:, 0]) - else: - x = self.__x - return pandas.DataFrame(data=x,index=self.row_names,columns=self.col_names)
    - - -
    [docs] def to_sparse(self, trunc=0.0): - """get the COO sparse Matrix representation of the Matrix - - Returns - ------- - scipy.sparse.Matrix : scipy.sparse.Matrix - - """ - try: - import scipy.sparse as sparse - except: - raise Exception("mat.to_sparse() error importing scipy.sparse") - iidx, jidx = [], [] - data = [] - nrow, ncol = self.shape - for i in range(nrow): - for j in range(ncol): - val = self.x[i,j] - if val > trunc: - iidx.append(i) - jidx.append(j) - data.append(val) - # csr_Matrix( (data,(row,col)), shape=(3,3) - return sparse.coo_matrix((data, (iidx, jidx)), shape=(self.shape))
    - - -
    [docs] def extend(self,other,inplace=False): - """ extend self with the elements of other. - - Parameters - ---------- - other : (Matrix) - the Matrix to extend self by - inplace : bool - inplace = True not implemented - - Returns - ------- - Matrix : Matrix - if not inplace - - """ - if inplace == True: - raise NotImplementedError() - assert len(set(self.row_names).intersection(set(other.row_names))) == 0 - assert len(set(self.col_names).intersection(set(other.col_names))) == 0 - assert type(self) == type(other) - new_row_names = copy.copy(self.row_names) - new_row_names.extend(other.row_names) - new_col_names = copy.copy(self.col_names) - new_col_names.extend(other.col_names) - - new_x = np.zeros((len(new_row_names),len(new_col_names))) - new_x[0:self.shape[0],0:self.shape[1]] = self.as_2d - new_x[self.shape[0]:self.shape[0]+other.shape[0], - self.shape[1]:self.shape[1]+other.shape[1]] = other.as_2d - isdiagonal = True - if not self.isdiagonal or not other.isdiagonal: - isdiagonal = False - - return type(self)(x=new_x,row_names=new_row_names, - col_names=new_col_names,isdiagonal=isdiagonal)
    - - - - - -
    [docs]class Jco(Matrix): - """a thin wrapper class to get more intuitive attribute names. Functions - exactly like Matrix - """ - def __init(self, **kwargs): - """ Jco constuctor takes the same arguments as Matrix. - - Parameters - ---------- - **kwargs : (dict) - constructor arguments for Matrix - - Returns - ------- - Jco : Jco - - """ - - super(Jco, self).__init__(kwargs) - - - @property - def par_names(self): - """ thin wrapper around Matrix.col_names - - Returns - ------- - list : list - parameter names - - """ - return self.col_names - - - @property - def obs_names(self): - """ thin wrapper around Matrix.row_names - - Returns - ------- - list : list - observation names - - """ - return self.row_names - - - @property - def npar(self): - """ number of parameters in the Jco - - Returns - ------- - int : int - number of parameters (columns) - - """ - return self.shape[1] - - - @property - def nobs(self): - """ number of observations in the Jco - - Returns - ------- - int : int - number of observations (rows) - - """ - return self.shape[0] - -
    [docs] def replace_cols(self, other, parnames=None): - """ - Replaces columns in one Matrix with columns from another. - Intended for Jacobian matrices replacing parameters. - - Parameters - ---------- - other: Matrix - Matrix to use for replacing columns in self - - parnames: list - parameter (column) names to use in other. If None, all - columns in other are used - - """ - assert len(set(self.col_names).intersection(set(other.col_names))) > 0 - if not parnames: - parnames = other.col_names - assert len(set(self.col_names).intersection(set(other.col_names))) == len(parnames) - - assert len(set(self.row_names).intersection(set(other.row_names))) == len(self.row_names) - assert type(self) == type(other) - - # re-sort other by rows to be sure they line up with self - try: - other = other.get(row_names=self.row_names) - except: - raise Exception('could not align rows of the two matrices') - - # replace the columns in self with those from other - selfobs = np.array(self.col_names) - otherobs = np.array(other.col_names) - selfidx = [np.where(np.array(selfobs) == i)[0][0] for i in parnames] - otheridx = [np.where(np.array(otherobs) == i)[0][0] for i in parnames] - self.x[:,selfidx] = other.x[:,otheridx]
    - - -
    [docs] @classmethod - def from_pst(cls,pst, random=False): - """construct a new empty Jco from a control file filled - with trash - - Parameters - ---------- - pst : Pst - a control file instance. If type is 'str', - Pst is loaded from filename - random : bool - flag for contents of the trash matrix. - If True, fill with random numbers, if False, fill with zeros - Default is False - Return - ------ - jco : Jco - the new Jco instance - - """ - - if isinstance(pst,str): - pst = Pst(pst) - - return Jco.from_names(pst.obs_names, pst.adj_par_names, random=random)
    - -
    [docs]class Cov(Matrix): - """a subclass of Matrix for handling diagonal or dense Covariance matrices - todo:block diagonal - """ - def __init__(self, x=None, names=[], row_names=[], col_names=[], - isdiagonal=False, autoalign=True): - """ Cov constructor. - - - Parameters - ---------- - x : numpy.ndarray - elements in Cov - names : iterable - names for both columns and rows - row_names : iterable - names for rows - col_names : iterable - names for columns - isdiagonal : bool - diagonal Matrix flag - autoalign : bool - autoalignment flag - - Returns - ------- - Cov : Cov - - """ - self.__identity = None - self.__zero = None - #if len(row_names) > 0 and len(col_names) > 0: - # assert row_names == col_names - if len(names) != 0 and len(row_names) == 0: - row_names = names - if len(names) != 0 and len(col_names) == 0: - col_names = names - super(Cov, self).__init__(x=x, isdiagonal=isdiagonal, - row_names=row_names, - col_names=col_names, - autoalign=autoalign) - - - @property - def identity(self): - """get an identity Matrix like self - - Returns - ------- - Cov : Cov - - """ - if self.__identity is None: - self.__identity = Cov(x=np.atleast_2d(np.ones(self.shape[0])) - .transpose(), names=self.row_names, - isdiagonal=True) - return self.__identity - - - @property - def zero(self): - """ get an instance of self with all zeros - - Returns - ------- - Cov : Cov - - """ - if self.__zero is None: - self.__zero = Cov(x=np.atleast_2d(np.zeros(self.shape[0])) - .transpose(), names=self.row_names, - isdiagonal=True) - return self.__zero - - -
    [docs] def condition_on(self,conditioning_elements): - """get a new Covariance object that is conditional on knowing some - elements. uses Schur's complement for conditional Covariance - propagation - - Parameters - ---------- - conditioning_elements : iterable - names of elements to condition on - - Returns - ------- - Cov : Cov - """ - if not isinstance(conditioning_elements,list): - conditioning_elements = [conditioning_elements] - for iname, name in enumerate(conditioning_elements): - conditioning_elements[iname] = name.lower() - assert name.lower() in self.col_names,\ - "Cov.condition_on() name not found: " + name - keep_names = [] - for name in self.col_names: - if name not in conditioning_elements: - keep_names.append(name) - #C11 - new_Cov = self.get(keep_names) - if self.isdiagonal: - return new_Cov - #C22^1 - cond_Cov = self.get(conditioning_elements).inv - #C12 - upper_off_diag = self.get(keep_names, conditioning_elements) - #print(new_Cov.shape,upper_off_diag.shape,cond_Cov.shape) - return new_Cov - (upper_off_diag * cond_Cov * upper_off_diag.T)
    - -
    [docs] def draw(self, mean=1.0): - """Obtain a random draw from a covariance matrix either with mean==1 - or with specified mean vector - - Parameters - ---------- - mean: scalar of enumerable of length self.shape[0] - mean values. either a scalar applied to to the entire - vector of length N or an N-length vector - - Returns - ------- - numpy.nparray : numpy.ndarray - A vector of conditioned values, sampled - using the covariance matrix (self) and applied to the mean - - """ - if np.isscalar(mean): - mean = np.ones(self.ncol) * mean - else: - assert len(mean) == self.ncol, "mean vector must be {0} elements. {1} were provided".\ - format(self.ncol, len(mean)) - - return(np.random.multivariate_normal(mean, self.as_2d))
    - - - - @property - def names(self): - """wrapper for getting row_names. row_names == col_names for Cov - - Returns - ------- - list : list - names - - """ - return self.row_names - - -
    [docs] def replace(self,other): - """replace elements in the covariance matrix with elements from other. - if other is not diagonal, then self becomes non diagonal - - Parameters - ----------- - other : Cov - the Cov to replace elements in self with - - Note - ---- - operates in place - - """ - assert isinstance(other,Cov),"Cov.replace() other must be Cov, not {0}".\ - format(type(other)) - # make sure the names of other are in self - missing = [n for n in other.names if n not in self.names] - if len(missing) > 0: - raise Exception("Cov.replace(): the following other names are not" +\ - " in self names: {0}".format(','.join(missing))) - self_idxs = self.indices(other.names,0) - other_idxs = other.indices(other.names,0) - - if self.isdiagonal and other.isdiagonal: - self._Matrix__x[self_idxs] = other.x[other_idxs] - return - if self.isdiagonal: - self._Matrix__x = self.as_2d - self.isdiagonal = False - - #print("allocating other_x") - other_x = other.as_2d - #print("replacing") - for i,ii in zip(self_idxs,other_idxs): - self._Matrix__x[i,self_idxs] = other_x[ii,other_idxs].copy()
    - #print("resetting") - #self.reset_x(self_x) - #self.isdiagonal = False - -
    [docs] def to_uncfile(self, unc_file, covmat_file="Cov.mat", var_mult=1.0): - """write a PEST-compatible uncertainty file - - Parameters - ---------- - unc_file : str - filename of the uncertainty file - covmat_file : str Covariance Matrix filename. Default is - "Cov.mat". If None, and Cov.isdiaonal, then a standard deviation - form of the uncertainty file is written. Exception raised if None - and not Cov.isdiagonal - var_mult : float - variance multiplier for the covmat_file entry - - """ - assert len(self.row_names) == self.shape[0], \ - "Cov.to_uncfile(): len(row_names) != x.shape[0] " - if covmat_file: - f = open(unc_file, 'w') - f.write("START COVARIANCE_MATRIX\n") - f.write(" file " + covmat_file + "\n") - f.write(" variance_multiplier {0:15.6E}\n".format(var_mult)) - f.write("END COVARIANCE_MATRIX\n") - f.close() - self.to_ascii(covmat_file, icode=1) - else: - if self.isdiagonal: - f = open(unc_file, 'w') - f.write("START STANDARD_DEVIATION\n") - for iname, name in enumerate(self.row_names): - f.write(" {0:20s} {1:15.6E}\n". - format(name, np.sqrt(self.x[iname, 0]))) - f.write("END STANDARD_DEVIATION\n") - f.close() - else: - raise Exception("Cov.to_uncfile(): can't write non-diagonal " + - "object as standard deviation block")
    - -
    [docs] @classmethod - def from_obsweights(cls, pst_file): - """instantiates a Cov instance from observation weights in - a PEST control file. Calls Cov.from_observation_data() - - Parameters - ---------- - pst_file : str - pest control file name - - Returns - ------- - Cov : Cov - """ - if not pst_file.endswith(".pst"): - pst_file += ".pst" - return Cov.from_observation_data(Pst(pst_file))
    - -
    [docs] @classmethod - def from_observation_data(cls, pst): - """instantiates a Cov from a pandas dataframe - of pyemu.Pst.observation_data - - Parameters - ---------- - pst : pyemu.Pst - - Returns - ------- - Cov : Cov - - """ - nobs = pst.observation_data.shape[0] - x = np.zeros((nobs, 1)) - onames = [] - ocount = 0 - for idx,row in pst.observation_data.iterrows(): - w = float(row["weight"]) - w = max(w, 1.0e-30) - x[ocount] = (1.0 / w) ** 2 - ocount += 1 - onames.append(row["obsnme"].lower()) - return cls(x=x,names=onames,isdiagonal=True)
    - -
    [docs] @classmethod - def from_parbounds(cls, pst_file, sigma_range = 4.0,scale_offset=True): - """Instantiates a Cov from a pest control file parameter data section. - Calls Cov.from_parameter_data() - - Parameters - ---------- - pst_file : str - pest control file name - sigma_range: float - defines range of upper bound - lower bound in terms of standard - deviation (sigma). For example, if sigma_range = 4, the bounds - represent 4 * sigma. Default is 4.0, representing approximately - 95% confidence of implied normal distribution - scale_offset : bool - flag to apply scale and offset to parameter upper and lower - bounds before calculating varaince. Default is True - - Returns - ------- - Cov : Cov - - """ - if not pst_file.endswith(".pst"): - pst_file += ".pst" - new_pst = Pst(pst_file) - return Cov.from_parameter_data(new_pst, sigma_range)
    - -
    [docs] @classmethod - def from_parameter_data(cls, pst, sigma_range = 4.0, scale_offset=True): - """load Covariances from a pandas dataframe of - pyemu.Pst.parameter_data - - Parameters - ---------- - pst : (pyemu.Pst) - sigma_range: float - defines range of upper bound - lower bound in terms of standard - deviation (sigma). For example, if sigma_range = 4, the bounds - represent 4 * sigma. Default is 4.0, representing approximately - 95% confidence of implied normal distribution - scale_offset : bool - flag to apply scale and offset to parameter upper and lower - bounds before calculating varaince. Default is True - - Returns - ------- - Cov : Cov - - """ - npar = pst.npar_adj - x = np.zeros((npar, 1)) - names = [] - idx = 0 - for i, row in pst.parameter_data.iterrows(): - t = row["partrans"] - if t in ["fixed", "tied"]: - continue - if scale_offset: - lb = row.parlbnd * row.scale + row.offset - ub = row.parubnd * row.scale + row.offset - else: - lb = row.parlbnd - ub = row.parubnd - - if t == "log": - var = ((np.log10(np.abs(ub)) - np.log10(np.abs(lb))) / sigma_range) ** 2 - else: - var = ((ub - lb) / sigma_range) ** 2 - if np.isnan(var) or not np.isfinite(var): - raise Exception("Cov.from_parameter_data() error: " +\ - "variance for parameter {0} is nan".\ - format(row["parnme"])) - if (var == 0.0): - raise Exception("Cov.from_parameter_data() error: " +\ - "variance for parameter {0} is 0.0".\ - format(row["parnme"])) - x[idx] = var - names.append(row["parnme"].lower()) - idx += 1 - - return cls(x=x,names=names,isdiagonal=True)
    - -
    [docs] @classmethod - def from_uncfile(cls, filename): - """instaniates a Cov from a PEST-compatible uncertainty file - - Parameters - ---------- - filename : str - uncertainty file name - - Returns - ------- - Cov : Cov - - """ - - nentries = Cov.get_uncfile_dimensions(filename) - x = np.zeros((nentries, nentries)) - row_names = [] - col_names = [] - f = open(filename, 'r') - isdiagonal = True - idx = 0 - while True: - line = f.readline().lower() - if len(line) == 0: - break - line = line.strip() - if 'start' in line: - if 'standard_deviation' in line: - std_mult = 1.0 - while True: - line2 = f.readline().strip().lower() - if line2.strip().lower().startswith("end"): - break - - - raw = line2.strip().split() - name,val = raw[0], float(raw[1]) - if name == "std_multiplier": - std_mult = val - else: - x[idx, idx] = (val*std_mult)**2 - if name in row_names: - raise Exception("Cov.from_uncfile():" + - "duplicate name: " + str(name)) - row_names.append(name) - col_names.append(name) - idx += 1 - - elif 'covariance_matrix' in line: - isdiagonal = False - var = 1.0 - while True: - line2 = f.readline().strip().lower() - if line2.strip().lower().startswith("end"): - break - if line2.startswith('file'): - filename = line2.split()[1].replace("'",'').replace('"','') - cov = Matrix.from_ascii(filename) - - elif line2.startswith('variance_multiplier'): - var = float(line2.split()[1]) - else: - raise Exception("Cov.from_uncfile(): " + - "unrecognized keyword in" + - "std block: " + line2) - if var != 1.0: - cov *= var - for name in cov.row_names: - if name in row_names: - raise Exception("Cov.from_uncfile():" + - " duplicate name: " + str(name)) - row_names.extend(cov.row_names) - col_names.extend(cov.col_names) - - for i, rname in enumerate(cov.row_names): - x[idx + i,idx:idx + cov.shape[0]] = cov.x[i, :].copy() - idx += cov.shape[0] - else: - raise Exception('Cov.from_uncfile(): ' + - 'unrecognized block:' + str(line)) - f.close() - if isdiagonal: - x = np.atleast_2d(np.diag(x)).transpose() - return cls(x=x,names=row_names,isdiagonal=isdiagonal)
    - -
    [docs] @staticmethod - def get_uncfile_dimensions(filename): - """quickly read an uncertainty file to find the dimensions - - Parameters - ---------- - filename : str - uncertainty filename - - Returns - ------- - nentries : int - number of elements in file - """ - f = open(filename, 'r') - nentries = 0 - while True: - line = f.readline().lower() - if len(line) == 0: - break - line = line.strip() - if 'start' in line: - if 'standard_deviation' in line: - while True: - line2 = f.readline().strip().lower() - if "std_multiplier" in line2: - continue - if line2.strip().lower().startswith("end"): - break - nentries += 1 - - elif 'covariance_matrix' in line: - while True: - line2 = f.readline().strip().lower() - if line2.strip().lower().startswith("end"): - break - if line2.startswith('file'): - filename = line2.split()[1].replace("'", '').replace('"', '') - cov = Matrix.from_ascii(filename) - nentries += len(cov.row_names) - elif line2.startswith('variance_multiplier'): - var = float(line2.split()[1]) - else: - raise Exception('Cov.get_uncfile_dimensions(): ' + - 'unrecognized keyword in Covariance block: ' + - line2) - else: - raise Exception('Cov.get_uncfile_dimensions():' + - 'unrecognized block:' + str(line)) - f.close() - return nentries
    - -
    [docs] @classmethod - def identity_like(cls,other): - """ Get an identity matrix Cov instance like other - - Parameters - ---------- - other : Matrix - must be square - - Returns - ------- - Cov : Cov - - """ - assert other.shape[0] == other.shape[1] - x = np.identity(other.shape[0]) - return cls(x=x,names=other.row_names,isdiagonal=False)
    - -
    [docs] def to_pearson(self): - """ Convert Cov instance to Pearson correlation coefficient - matrix - - Returns - ------- - Matrix : Matrix - this is on purpose so that it is clear the returned - instance is not a Cov - - """ - std_dict = self.get_diagonal_vector().to_dataframe()["diag"].\ - apply(np.sqrt).to_dict() - pearson = self.identity.as_2d - if self.isdiagonal: - return Matrix(x=pearson,row_names=self.row_names, - col_names=self.col_names) - df = self.to_dataframe() - # fill the lower triangle - for i,iname in enumerate(self.row_names): - for j,jname in enumerate(self.row_names[i+1:]): - # cv = df.loc[iname,jname] - # std1,std2 = std_dict[iname],std_dict[jname] - # cc = cv / (std1*std2) - # v1 = np.sqrt(df.loc[iname,iname]) - # v2 = np.sqrt(df.loc[jname,jname]) - pearson[i,j+i+1] = df.loc[iname,jname] / (std_dict[iname] * std_dict[jname]) - - # replicate across diagonal - for i,iname in enumerate(self.row_names[:-1]): - pearson[i+1:,i] = pearson[i,i+1:] - return Matrix(x=pearson,row_names=self.row_names, - col_names=self.col_names)
    - - - -
    [docs]class SparseMatrix(object): - """Note: less rigid about references since this class is for big matrices and - don't want to be making copies""" - def __init__(self,x,row_names,col_names): - assert isinstance(x,scipy.sparse.coo_matrix) - assert x.shape[0] == len(row_names) - assert x.shape[1] == len(col_names) - self.x = x - self.row_names = list(row_names) - self.col_names = list(col_names) - - - @property - def shape(self): - return self.x.shape - -
    [docs] @classmethod - def from_binary(cls,filename): - x,row_names,col_names = Matrix.read_binary(filename,sparse=True) - return cls(x=x,row_names=row_names,col_names=col_names)
    - - - -
    [docs] def to_coo(self,filename): - save_coo(self.x,row_names=self.row_names,col_names=self.col_names,filename=filename)
    - - - -
    [docs] @classmethod - def from_coo(cls,filename): - return SparseMatrix.from_binary(filename)
    - - # @classmethod - # def from_csv(cls,filename): - # pass - # - # - # def to_csv(self,filename): - # pass - - -
    [docs] def to_matrix(self): - x = np.zeros(self.shape) - for i,j,d in zip(self.x.row,self.x.col,self.x.data): - x[i,j] = d - return Matrix(x=x,row_names=self.row_names,col_names=self.col_names)
    - - -
    [docs] @classmethod - def from_matrix(cls, matrix, droptol=None): - iidx,jidx = matrix.as_2d.nonzero() - coo = scipy.sparse.coo_matrix((matrix.as_2d[iidx,jidx],(iidx,jidx)),shape=matrix.shape) - return cls(x=coo,row_names=matrix.row_names,col_names=matrix.col_names)
    - - -
    [docs] def block_extend_ip(self,other): - """designed for combining dense martrices into a sparse block diagonal matrix. - other must not have any rows or columns in common with self """ - ss = set(self.row_names) - os = set(other.row_names) - inter = ss.intersection(os) - if len(inter) > 0: - raise Exception("SparseMatrix.block_extend_ip(): error shares the following rows:{0}". - format(','.join(inter))) - ss = set(self.col_names) - os = set(other.col_names) - inter = ss.intersection(os) - if len(inter) > 0: - raise Exception("SparseMatrix.block_extend_ip(): error shares the following cols:{0}". - format(','.join(inter))) - - if isinstance(other,Matrix): - iidx,jidx = other.as_2d.nonzero() - # this looks terrible but trying to do this as close to "in place" as possible - self.x = scipy.sparse.coo_matrix((np.append(self.x.data,other.as_2d[iidx,jidx]), - (np.append(self.x.row,(iidx+self.shape[0])), - np.append(self.x.col,(jidx+self.shape[1])))), - shape=(self.shape[0]+other.shape[0],self.shape[1]+other.shape[1])) - self.row_names.extend(other.row_names) - self.col_names.extend(other.col_names) - - elif isinstance(other,SparseMatrix): - self.x = scipy.sparse.coo_matrix((np.append(self.x.data, other.x.data), - (np.append(self.x.row, (self.shape[0] + other.x.row)), - np.append(self.x.col, (self.shape[1] + other.x.col)))), - shape=(self.shape[0] + other.shape[0], self.shape[1] + other.shape[1])) - self.row_names.extend(other.row_names) - self.col_names.extend(other.col_names) - - - else: - raise NotImplementedError("SparseMatrix.block_extend_ip() 'other' arg only supports Matrix types ")
    - - -
    [docs] def get_matrix(self,row_names,col_names): - if not isinstance(row_names,list): - row_names = [row_names] - if not isinstance(col_names,list): - col_names = [col_names] - - iidx = Matrix.find_rowcol_indices(row_names,self.row_names,self.col_names,axis=0) - jidx = Matrix.find_rowcol_indices(col_names,self.row_names,self.col_names,axis=1) - - imap = {ii:i for i,ii in enumerate(iidx)} - jmap = {jj:j for j,jj in enumerate(jidx)} - - iset = set(iidx) - jset = set(jidx) - - x = np.zeros((len(row_names),len(col_names))) - # for i,idx in enumerate(iidx): - # for j,jdx in enumerate(jidx): - # if jdx in jset and idx in iset: - # x[i,j] = self.x[idx,jdx] - - for i,j,d in zip(self.x.row,self.x.col,self.x.data): - if i in iset and j in jset: - x[imap[i],jmap[j]] = d - return Matrix(x=x,row_names=row_names,col_names=col_names)
    - -
    [docs] def get_sparse_matrix(self,row_names,col_names): - if not isinstance(row_names,list): - row_names = [row_names] - if not isinstance(col_names,list): - col_names = [col_names] - - iidx = Matrix.find_rowcol_indices(row_names,self.row_names,self.col_names,axis=0) - jidx = Matrix.find_rowcol_indices(col_names,self.row_names,self.col_names,axis=1) - - imap = {ii:i for i,ii in enumerate(iidx)} - jmap = {jj:j for j,jj in enumerate(jidx)} - - iset = set(iidx) - jset = set(jidx) - - x = np.zeros((len(row_names),len(col_names))) - # for i,idx in enumerate(iidx): - # for j,jdx in enumerate(jidx): - # if jdx in jset and idx in iset: - # x[i,j] = self.x[idx,jdx] - ii,jj,data = [],[],[] - for i,j,d in zip(self.x.row,self.x.col,self.x.data): - if i in iset and j in jset: - ii.append(i) - jj.append(j) - data.append(d) - coo = scipy.sparse.coo_matrix((data,(ii,jj)),shape=(len(row_names),len(col_names))) - return SparseMatrix(x=coo,row_names=row_names,col_names=col_names)
    - - - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/mc.html b/docs/_build/html/_modules/pyemu/mc.html deleted file mode 100644 index dbfa148cc..000000000 --- a/docs/_build/html/_modules/pyemu/mc.html +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - pyemu.mc — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.mc

    -"""pyEMU Monte Carlo module.  Supports easy Monte Carlo
    -and GLUE analyses.  The MonteCarlo class inherits from
    -pyemu.LinearAnalysis
    -"""
    -from __future__ import print_function, division
    -import os
    -import numpy as np
    -from pyemu.la import LinearAnalysis
    -from pyemu.en import ObservationEnsemble, ParameterEnsemble
    -from pyemu.mat import Cov
    -#from pyemu.utils.helpers import zero_order_tikhonov
    -
    -
    [docs]class MonteCarlo(LinearAnalysis): - """LinearAnalysis derived type for monte carlo analysis - - Parameters - ---------- - **kwargs : dict - dictionary of keyword arguments. See pyemu.LinearAnalysis for - complete definitions - - Attributes - ---------- - parensemble : pyemu.ParameterEnsemble - pyemu object derived from a pandas dataframe, the ensemble - of parameters from the PEST control file with associated - starting value and bounds. Object also exposes methods - relevant to the dataframe and parameters-- see documentation. - obsensemble : pyemu.ObservationEnsemble - pyemu object derived from a pandas dataframe, the ensemble - of observations from the PEST control file with associated - starting weights. Object also exposes methods - relevant to the dataframe and observations-- see documentation. - - Returns - ------- - MonteCarlo - pyEMU MonteCarlo object - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(pst="pest.pst")`` - - """ - def __init__(self,**kwargs): - super(MonteCarlo,self).__init__(**kwargs) - assert self.pst is not None, \ - "monte carlo requires a pest control file" - self.parensemble = ParameterEnsemble(pst=self.pst) - self.obsensemble = ObservationEnsemble(pst=self.pst) - - @property - def num_reals(self): - """ get the number of realizations in the parameter ensemble - - Returns - ------- - num_real : int - - """ - return self.parensemble.shape[0] - -
    [docs] def get_nsing(self,epsilon=1.0e-4): - """ get the number of solution space dimensions given - a ratio between the largest and smallest singular values - - Parameters - ---------- - epsilon: float - singular value ratio - - Returns - ------- - nsing : float - number of singular components above the epsilon ratio threshold - - Note - ----- - If nsing == nadj_par, then None is returned - - """ - mx = self.xtqx.shape[0] - nsing = mx - np.searchsorted( - np.sort((self.xtqx.s.x / self.xtqx.s.x.max())[:,0]),epsilon) - if nsing == mx: - self.logger.warn("optimal nsing=npar") - nsing = None - return nsing
    - -
    [docs] def get_null_proj(self,nsing=None): - """ get a null-space projection matrix of XTQX - - Parameters - ---------- - nsing: int - optional number of singular components to use - If Nonte, then nsing is determined from - call to MonteCarlo.get_nsing() - - Returns - ------- - v2_proj : pyemu.Matrix - the null-space projection matrix (V2V2^T) - - """ - if nsing is None: - nsing = self.get_nsing() - if nsing is None: - raise Exception("nsing is None") - print("using {0} singular components".format(nsing)) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) - - v2_proj = (self.xtqx.v[:,nsing:] * self.xtqx.v[:,nsing:].T) - self.log("forming null space projection matrix with " +\ - "{0} of {1} singular components".format(nsing,self.jco.shape[1])) - - return v2_proj
    - -
    [docs] def draw(self, num_reals=1, par_file = None, obs=False, - enforce_bounds=None, cov=None, how="gaussian"): - """draw stochastic realizations of parameters and - optionally observations, filling MonteCarlo.parensemble and - optionally MonteCarlo.obsensemble. - - Parameters - ---------- - num_reals : int - number of realization to generate - par_file : str - parameter file to use as mean values. If None, - use MonteCarlo.pst.parameter_data.parval1. - Default is None - obs : bool - add a realization of measurement noise to observation values, - forming MonteCarlo.obsensemble.Default is False - enforce_bounds : str - enforce parameter bounds based on control file information. - options are 'reset', 'drop' or None. Default is None - how : str - type of distribution to draw from. Must be in ["gaussian","uniform"] - default is "gaussian". - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(pst="pest.pst")`` - - ``>>>mc.draw(1000)`` - - """ - if par_file is not None: - self.pst.parrep(par_file) - how = how.lower().strip() - assert how in ["gaussian","uniform"] - - if cov is not None: - assert isinstance(cov,Cov) - if how == "uniform": - raise Exception("MonteCarlo.draw() error: 'how'='uniform'," +\ - " 'cov' arg cannot be passed") - else: - cov = self.parcov - - self.log("generating {0:d} parameter realizations".format(num_reals)) - - if how == "gaussian": - self.parensemble = ParameterEnsemble.from_gaussian_draw(pst=self.pst,cov=cov, - num_reals=num_reals, - use_homegrown=True, - enforce_bounds=False) - - elif how == "uniform": - self.parensemble = ParameterEnsemble.from_uniform_draw(pst=self.pst,num_reals=num_reals) - - else: - raise Exception("MonteCarlo.draw(): unrecognized 'how' arg: {0}".format(how)) - - #self.parensemble = ParameterEnsemble(pst=self.pst) - #self.obsensemble = ObservationEnsemble(pst=self.pst) - #self.parensemble.draw(cov,num_reals=num_reals, how=how, - # enforce_bounds=enforce_bounds) - if enforce_bounds is not None: - self.parensemble.enforce(enforce_bounds) - self.log("generating {0:d} parameter realizations".format(num_reals)) - - if obs: - self.log("generating {0:d} observation realizations".format(num_reals)) - self.obsensemble = ObservationEnsemble.from_id_gaussian_draw(pst=self.pst,num_reals=num_reals) - self.log("generating {0:d} observation realizations".format(num_reals))
    - - - - -
    [docs] def project_parensemble(self,par_file=None,nsing=None, - inplace=True,enforce_bounds='reset'): - """ perform the null-space projection operations for null-space monte carlo - - Parameters - ---------- - par_file: str - an optional file of parameter values to use - nsing: int - number of singular values to in forming null subspace matrix - inplace: bool - overwrite the existing parameter ensemble with the - projected values - enforce_bounds: str - how to enforce parameter bounds. can be None, 'reset', or 'drop'. - Default is None - - Returns - ------- - par_en : pyemu.ParameterEnsemble - if inplace is False, otherwise None - - Note - ---- - to use this method, the MonteCarlo instance must have been constructed - with the ``jco`` argument. - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(jco="pest.jcb")`` - - ``>>>mc.draw(1000)`` - - ``>>>mc.project_parensemble(par_file="final.par",nsing=100)`` - - """ - assert self.jco is not None,"MonteCarlo.project_parensemble()" +\ - "requires a jacobian attribute" - if par_file is not None: - assert os.path.exists(par_file),"monte_carlo.draw() error: par_file not found:" +\ - par_file - self.parensemble.pst.parrep(par_file) - - # project the ensemble - self.log("projecting parameter ensemble") - en = self.parensemble.project(self.get_null_proj(nsing),inplace=inplace,log=self.log) - self.log("projecting parameter ensemble") - return en
    - -
    [docs] def write_psts(self,prefix,existing_jco=None,noptmax=None): - """ write parameter and optionally observation realizations - to a series of pest control files - - Parameters - ---------- - prefix: str - pest control file prefix - - existing_jco: str - filename of an existing jacobian matrix to add to the - pest++ options in the control file. This is useful for - NSMC since this jco can be used to get the first set of - parameter upgrades for free! Needs to be the path the jco - file as seen from the location where pest++ will be run - - noptmax: int - value of NOPTMAX to set in new pest control files - - Example - ------- - ``>>>import pyemu`` - - ``>>>mc = pyemu.MonteCarlo(jco="pest.jcb")`` - - ``>>>mc.draw(1000, obs=True)`` - - ``>>>mc.write_psts("mc_", existing_jco="pest.jcb", noptmax=1)`` - - """ - self.log("writing realized pest control files") - # get a copy of the pest control file - pst = self.pst.get(par_names=self.pst.par_names,obs_names=self.pst.obs_names) - - if noptmax is not None: - pst.control_data.noptmax = noptmax - pst.control_data.noptmax = noptmax - - if existing_jco is not None: - pst.pestpp_options["BASE_JACOBIAN"] = existing_jco - - # set the indices - pst.parameter_data.index = pst.parameter_data.parnme - pst.observation_data.index = pst.observation_data.obsnme - - if self.parensemble.istransformed: - par_en = self.parensemble._back_transform(inplace=False) - else: - par_en = self.parensemble - - for i in range(self.num_reals): - pst_name = prefix + "{0:d}.pst".format(i) - self.log("writing realized pest control file " + pst_name) - pst.parameter_data.loc[par_en.columns,"parval1"] = par_en.iloc[i, :].T - - # reset the regularization - #if pst.control_data.pestmode == "regularization": - #pst.zero_order_tikhonov(parbounds=True) - #zero_order_tikhonov(pst,parbounds=True) - # add the obs noise realization if needed - if self.obsensemble.shape[0] == self.num_reals: - pst.observation_data.loc[self.obsensemble.columns,"obsval"] = \ - self.obsensemble.iloc[i, :].T - - # write - pst.write(pst_name) - self.log("writing realized pest control file " + pst_name) - self.log("writing realized pest control files")
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/pst/pst_controldata.html b/docs/_build/html/_modules/pyemu/pst/pst_controldata.html deleted file mode 100644 index 0e7718fbe..000000000 --- a/docs/_build/html/_modules/pyemu/pst/pst_controldata.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - - - pyemu.pst.pst_controldata — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.pst.pst_controldata

    -"""This module contains several class definitions for parts of the
    -PEST control file: ControlData, RegData and SvdData.  These
    -classes are automatically created and appended to a Pst object;
    -users shouldn't need to deal with these classes explicitly
    -"""
    -from __future__ import print_function, division
    -import os
    -import copy
    -import warnings
    -import numpy as np
    -import pandas
    -from ..pyemu_warnings import PyemuWarning
    -pandas.options.display.max_colwidth = 100
    -#from pyemu.pst.pst_handler import SFMT,SFMT_LONG,FFMT,IFMT
    -
    -#formatters
    -SFMT = lambda x: "{0:>20s}".format(str(x))
    -SFMT_LONG = lambda x: "{0:>50s}".format(str(x))
    -IFMT = lambda x: "{0:>10d}".format(int(x))
    -FFMT = lambda x: "{0:>15.6E}".format(float(x))
    -
    -CONTROL_DEFAULT_LINES = """restart estimation
    -     0     0       0    0      0  0
    -0  0  single  point  1  0  0 noobsreref
    -2.000000e+001  -3.000000e+000  3.000000e-001  1.000000e-002 -7 999 lamforgive noderforgive
    -1.000000e+001  1.000000e+001  1.000000e-003  0  0
    -1.000000e-001 1 1.1 noaui nosenreuse noboundscale
    -30 1.000000e-002  3  3  1.000000e-002  3  0.0 1  -1.0
    -0  0  0  0 jcosave verboserec jcosaveitn reisaveitn parsaveitn noparsaverun"""\
    -    .lower().split('\n')
    -
    -CONTROL_VARIABLE_LINES = """RSTFLE PESTMODE
    -NPAR NOBS NPARGP NPRIOR NOBSGP [MAXCOMPDIM]
    -NTPLFLE NINSFLE PRECIS DPOINT [NUMCOM] [JACFILE] [MESSFILE] [OBSREREF]
    -RLAMBDA1 RLAMFAC PHIRATSUF PHIREDLAM NUMLAM [JACUPDATE] [LAMFORGIVE] [DERFORGIVE]
    -RELPARMAX FACPARMAX FACORIG [IBOUNDSTICK] [UPVECBEND]
    -PHIREDSWH [NOPTSWITCH] [SPLITSWH] [DOAUI] [DOSENREUSE] [BOUNDSCALE]
    -NOPTMAX PHIREDSTP NPHISTP NPHINORED RELPARSTP NRELPAR [PHISTOPTHRESH] [LASTRUN] [PHIABANDON]
    -ICOV ICOR IEIG [IRES] [JCOSAVE] [VERBOSEREC] [JCOSAVEITN] [REISAVEITN] [PARSAVEITN] [PARSAVERUN]"""\
    -    .lower().split('\n')
    -
    -REG_VARIABLE_LINES = """PHIMLIM PHIMACCEPT [FRACPHIM] [MEMSAVE]
    -WFINIT WFMIN WFMAX [LINREG] [REGCONTINUE]
    -WFFAC WFTOL IREGADJ [NOPTREGADJ REGWEIGHTRAT [REGSINGTHRESH]]""".lower().split('\n')
    -
    -REG_DEFAULT_LINES = """   1.0e-10    1.05e-10  0.1  nomemsave
    -1.0 1.0e-10 1.0e10 linreg continue
    -1.3  1.0e-2  1 1.5 1.5 0.5""".lower().split('\n')
    -
    -
    [docs]class RegData(object): - """ an object that encapsulates the regularization section - of the PEST control file - """ - def __init__(self): - self.optional_dict = {} - for vline,dline in zip(REG_VARIABLE_LINES,REG_DEFAULT_LINES): - vraw = vline.split() - draw = dline.split() - for v,d in zip(vraw,draw): - o = False - if '[' in v: - o = True - v = v.replace('[','').replace(']','') - super(RegData,self).__setattr__(v,d) - self.optional_dict[v] = o - -
    [docs] def write(self,f): - """ write the regularization section to an open - file handle - - Parameters - ---------- - f : file handle - - """ - f.write("* regularization\n") - for vline in REG_VARIABLE_LINES: - vraw = vline.strip().split() - for v in vraw: - v = v.replace("[",'').replace("]",'') - if v not in self.optional_dict.keys(): - raise Exception("RegData missing attribute {0}".format(v)) - f.write("{0} ".format(self.__getattribute__(v))) - f.write("\n")
    - - -
    [docs]class SvdData(object): - """ an object that encapsulates the singular value decomposition - section of the PEST control file - """ - def __init__(self,**kwargs): - self.svdmode = kwargs.pop("svdmode",1) - self.maxsing = kwargs.pop("maxsing",10000000) - self.eigthresh = kwargs.pop("eigthresh",1.0e-6) - self.eigwrite = kwargs.pop("eigwrite",1) - -
    [docs] def write(self,f): - """ write an SVD section to a file handle - - Parameters - ---------- - f : file handle - - """ - f.write("* singular value decomposition\n") - f.write(IFMT(self.svdmode)+'\n') - f.write(IFMT(self.maxsing)+' '+FFMT(self.eigthresh)+"\n") - f.write('{0}\n'.format(self.eigwrite))
    - -
    [docs] def parse_values_from_lines(self,lines): - """ parse values from lines of the SVD section - - Parameters - ---------- - lines : list - - """ - assert len(lines) == 3,"SvdData.parse_values_from_lines: expected " + \ - "3 lines, not {0}".format(len(lines)) - try: - self.svdmode = int(lines[0].strip().split()[0]) - except Exception as e: - raise Exception("SvdData.parse_values_from_lines: error parsing" + \ - " svdmode from line {0}: {1} \n".format(lines[0],str(e))) - try: - raw = lines[1].strip().split() - self.maxsing = int(raw[0]) - self.eigthresh = float(raw[1]) - except Exception as e: - raise Exception("SvdData.parse_values_from_lines: error parsing" + \ - " maxsing and eigthresh from line {0}: {1} \n"\ - .format(lines[1],str(e))) - # try: - # self.eigwrite = int(lines[2].strip()) - # except Exception as e: - # raise Exception("SvdData.parse_values_from_lines: error parsing" + \ - # " eigwrite from line {0}: {1} \n".format(lines[2],str(e))) - self.eigwrite = lines[2].strip()
    - - -
    [docs]class ControlData(object): - """ an object that encapsulates the control data section - of the PEST control file - """ - def __init__(self): - - super(ControlData,self).__setattr__("formatters",{np.int32:IFMT,np.float64:FFMT,str:SFMT}) - super(ControlData,self).__setattr__("_df",self.get_dataframe()) - - # acceptable values for most optional string inputs - super(ControlData,self).__setattr__("accept_values",{'doaui':['aui','noaui'], - 'dosenreuse':['senreuse','nosenreuse'], - 'boundscale':['boundscale','noboundscale'], - 'jcosave':['jcosave','nojcosave'], - 'verboserec':['verboserec','noverboserec'], - 'jcosaveitn':['jcosaveitn','nojcosvaeitn'], - 'reisaveitn':['reisaveitn','noreisaveitn'], - 'parsaveitn':['parsaveitn','noparsaveitn'], - 'parsaverun':['parsaverun','noparsaverun']}) - - self._df.index = self._df.name.apply(lambda x:x.replace('[',''))\ - .apply(lambda x: x.replace(']','')) - - def __setattr__(self, key, value): - if key == "_df": - super(ControlData,self).__setattr__("_df",value) - return - assert key in self._df.index, str(key)+" not found in attributes" - self._df.loc[key,"value"] = self._df.loc[key,"type"](value) - - def __getattr__(self, item): - if item == "_df": - return self._df.copy() - assert item in self._df.index, str(item)+" not found in attributes" - return self._df.loc[item,"value"] - -
    [docs] @staticmethod - def get_dataframe(): - """ get a generic (default) control section dataframe - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - - """ - names = [] - [names.extend(line.split()) for line in CONTROL_VARIABLE_LINES] - - defaults = [] - [defaults.extend(line.split()) for line in CONTROL_DEFAULT_LINES] - - types, required,cast_defaults,formats = [],[],[],[] - for name,default in zip(names,defaults): - if '[' in name or ']' in name: - required.append(False) - else: - required.append(True) - v,t,f = ControlData._parse_value(default) - types.append(t) - formats.append(f) - cast_defaults.append(v) - return pandas.DataFrame({"name":names,"type":types, - "value":cast_defaults,"required":required, - "format":formats})
    - - - @staticmethod - def _parse_value(value): - try: - v = int(value) - t = np.int32 - f = IFMT - except Exception as e: - try: - v = float(value) - t = np.float64 - f = FFMT - except Exception as ee: - v = value.lower() - t = str - f = SFMT - return v,t,f - - -
    [docs] def parse_values_from_lines(self,lines): - """ cast the string lines for a pest control file into actual inputs - - Parameters - ---------- - lines : list - strings from pest control file - - """ - assert len(lines) == len(CONTROL_VARIABLE_LINES),\ - "ControlData error: len of lines not equal to " +\ - str(len(CONTROL_VARIABLE_LINES)) - - for iline,line in enumerate(lines): - vals = line.strip().split() - names = CONTROL_VARIABLE_LINES[iline].strip().split() - for name,val in zip(names,vals): - v,t,f = self._parse_value(val) - name = name.replace('[','').replace(']','') - - #if the parsed values type isn't right - if t != self._df.loc[name,"type"]: - - # if a float was expected and int return, not a problem - if t == np.int32 and self._df.loc[name,"type"] == np.float64: - self._df.loc[name,"value"] = np.float64(v) - - - # if this is a required input, throw - elif self._df.loc[name,"required"]: - raise Exception("wrong type found for variable " + name + ":" + str(t)) - else: - - #else, since this problem is usually a string, check for acceptable values - found = False - for nname,avalues in self.accept_values.items(): - if v in avalues: - if t == self._df.loc[nname,"type"]: - self._df.loc[nname,"value"] = v - found = True - break - if not found: - warnings.warn("non-conforming value found for " +\ - name + ":" + str(v) + "...ignoring",PyemuWarning) - - - else: - self._df.loc[name,"value"] = v
    - - -
    [docs] def copy(self): - cd = ControlData() - cd._df = self._df - return cd
    - - - @property - def formatted_values(self): - """ list the entries and current values in the control data section - - Returns - ------- - formatted_values : pandas.Series - - """ - return self._df.apply(lambda x: self.formatters[x["type"]](x["value"]),axis=1) - -
    [docs] def write(self,f): - """ write control data section to a file - - Parameters - ---------- - f: file handle or string filename - - """ - if isinstance(f,str): - f = open(f,'w') - f.write("pcf\n") - f.write("* control data\n") - for line in CONTROL_VARIABLE_LINES: - [f.write(self.formatted_values[name.replace('[','').replace(']','')]) for name in line.split()] - f.write('\n')
    - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/pst/pst_handler.html b/docs/_build/html/_modules/pyemu/pst/pst_handler.html deleted file mode 100644 index d71892f9e..000000000 --- a/docs/_build/html/_modules/pyemu/pst/pst_handler.html +++ /dev/null @@ -1,2308 +0,0 @@ - - - - - - - - pyemu.pst.pst_handler — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.pst.pst_handler

    -"""This module contains most of the pyemu.Pst object definition.  This object
    -is the primary mechanism for dealing with PEST control files
    -"""
    -
    -from __future__ import print_function, division
    -import os
    -import re
    -import copy
    -import warnings
    -import numpy as np
    -import pandas as pd
    -pd.options.display.max_colwidth = 100
    -import pyemu
    -from ..pyemu_warnings import PyemuWarning
    -from pyemu.pst.pst_controldata import ControlData, SvdData, RegData
    -from pyemu.pst import pst_utils
    -from pyemu.plot import plot_utils
    -#from pyemu.utils.os_utils import run
    -
    -
    [docs]class Pst(object): - """basic class for handling pest control files to support linear analysis - as well as replicate some of the functionality of the pest utilities - - Parameters - ---------- - filename : str - the name of the control file - load : (boolean) - flag to load the control file. Default is True - resfile : str - corresponding residual file. If None, a residual file - with the control file base name is sought. Default is None - - Returns - ------- - Pst : Pst - a control file object - - """ - def __init__(self, filename, load=True, resfile=None,flex=False): - - self.parameter_data = None - """pandas.DataFrame: parameter data loaded from pst control file""" - self.observation_data = None - """pandas.DataFrame: observation data loaded from pst control file""" - self.prior_information = None - """pandas.DataFrame: prior_information data loaded from pst control file""" - self.filename = filename - self.resfile = resfile - self.__res = None - self.__pi_count = 0 - self.with_comments = False - self.comments = {} - self.other_sections = {} - self.new_filename = None - for key,value in pst_utils.pst_config.items(): - self.__setattr__(key,copy.copy(value)) - #self.tied = None - self.control_data = ControlData() - """pyemu.pst.pst_controldata.ControlData: control data object loaded from pst control file""" - self.svd_data = SvdData() - """pyemu.pst.pst_controldata.SvdData: singular value decomposition (SVD) object loaded from pst control file""" - self.reg_data = RegData() - """pyemu.pst.pst_controldata.RegData: regularization data object loaded from pst control file""" - if load: - assert os.path.exists(filename),\ - "pst file not found:{0}".format(filename) - if flex: - self.flex_load(filename) - else: - self.load(filename) - - def __setattr__(self, key, value): - if key == "model_command": - if isinstance(value, str): - value = [value] - super(Pst,self).__setattr__(key,value) - - -
    [docs] @classmethod - def from_par_obs_names(cls,par_names=["par1"],obs_names=["obs1"]): - return pst_utils.generic_pst(par_names=par_names,obs_names=obs_names)
    - - @property - def phi(self): - """get the weighted total objective function - - Returns - ------- - phi : float - sum of squared residuals - - """ - sum = 0.0 - for grp, contrib in self.phi_components.items(): - sum += contrib - return sum - - @property - def phi_components(self): - """ get the individual components of the total objective function - - Returns - ------- - dict : dict - dictionary of observation group, contribution to total phi - - Raises - ------ - Assertion error if Pst.observation_data groups don't match - Pst.res groups - - """ - - # calculate phi components for each obs group - components = {} - ogroups = self.observation_data.groupby("obgnme").groups - rgroups = self.res.groupby("group").groups - self.res.index = self.res.name - for og,onames in ogroups.items(): - #assert og in rgroups.keys(),"Pst.phi_componentw obs group " +\ - # "not found: " + str(og) - #og_res_df = self.res.ix[rgroups[og]] - og_res_df = self.res.loc[onames,:].dropna() - #og_res_df.index = og_res_df.name - og_df = self.observation_data.ix[ogroups[og]] - og_df.index = og_df.obsnme - #og_res_df = og_res_df.loc[og_df.index,:] - assert og_df.shape[0] == og_res_df.shape[0],\ - " Pst.phi_components error: group residual dataframe row length" +\ - "doesn't match observation data group dataframe row length" + \ - str(og_df.shape) + " vs. " + str(og_res_df.shape) - components[og] = np.sum((og_res_df["residual"] * - og_df["weight"]) ** 2) - if not self.control_data.pestmode.startswith("reg") and \ - self.prior_information.shape[0] > 0: - ogroups = self.prior_information.groupby("obgnme").groups - for og in ogroups.keys(): - assert og in rgroups.keys(),"Pst.adjust_weights_res() obs group " +\ - "not found: " + str(og) - og_res_df = self.res.ix[rgroups[og]] - og_res_df.index = og_res_df.name - og_df = self.prior_information.ix[ogroups[og]] - og_df.index = og_df.pilbl - og_res_df = og_res_df.loc[og_df.index,:] - assert og_df.shape[0] == og_res_df.shape[0],\ - " Pst.phi_components error: group residual dataframe row length" +\ - "doesn't match observation data group dataframe row length" + \ - str(og_df.shape) + " vs. " + str(og_res_df.shape) - components[og] = np.sum((og_res_df["residual"] * - og_df["weight"]) ** 2) - - return components - - @property - def phi_components_normalized(self): - """ get the individual components of the total objective function - normalized to the total PHI being 1.0 - - Returns - ------- - dict : dict - dictionary of observation group, normalized contribution to total phi - - Raises - ------ - Assertion error if self.observation_data groups don't match - self.res groups - - """ - # use a dictionary comprehension to go through and normalize each component of phi to the total - phi_components_normalized = {i: self.phi_components[i]/self.phi for i in self.phi_components} - return phi_components_normalized - -
    [docs] def set_res(self,res): - """ reset the private Pst.res attribute - - Parameters - ---------- - res : (varies) - something to use as Pst.res attribute - - """ - if isinstance(res,str): - res = pst_utils.read_resfile(res) - self.__res = res
    - - @property - def res(self): - """get the residuals dataframe attribute - - Returns - ------- - res : pandas.DataFrame - - Note - ---- - if the Pst.__res attribute has not been loaded, - this call loads the res dataframe from a file - - """ - if self.__res is not None: - return self.__res - else: - if self.resfile is not None: - assert os.path.exists(self.resfile),"Pst.res: self.resfile " +\ - str(self.resfile) + " does not exist" - else: - self.resfile = self.filename.replace(".pst", ".res") - if not os.path.exists(self.resfile): - self.resfile = self.resfile.replace(".res", ".rei") - if not os.path.exists(self.resfile): - if self.new_filename is not None: - self.resfile = self.new_filename.replace(".pst",".res") - if not os.path.exists(self.resfile): - self.resfile = self.resfile.replace(".res","rei") - if not os.path.exists(self.resfile): - raise Exception("Pst.res: " + - "could not residual file case.res" + - " or case.rei") - - - res = pst_utils.read_resfile(self.resfile) - missing_bool = self.observation_data.obsnme.apply\ - (lambda x: x not in res.name) - missing = self.observation_data.obsnme[missing_bool] - if missing.shape[0] > 0: - raise Exception("Pst.res: the following observations " + - "were not found in " + - "{0}:{1}".format(self.resfile,','.join(missing))) - self.__res = res - return self.__res - - @property - def nprior(self): - """number of prior information equations - - Returns - ------- - nprior : int - the number of prior info equations - - """ - self.control_data.nprior = self.prior_information.shape[0] - return self.control_data.nprior - - @property - def nnz_obs(self): - """ get the number of non-zero weighted observations - - Returns - ------- - nnz_obs : int - the number of non-zeros weighted observations - - """ - nnz = 0 - for w in self.observation_data.weight: - if w > 0.0: - nnz += 1 - return nnz - - - @property - def nobs(self): - """get the number of observations - - Returns - ------- - nobs : int - the number of observations - - """ - self.control_data.nobs = self.observation_data.shape[0] - return self.control_data.nobs - - - @property - def npar_adj(self): - """get the number of adjustable parameters (not fixed or tied) - - Returns - ------- - npar_adj : int - the number of adjustable parameters - - """ - pass - np = 0 - for t in self.parameter_data.partrans: - if t not in ["fixed", "tied"]: - np += 1 - return np - - - @property - def npar(self): - """get number of parameters - - Returns - ------- - npar : int - the number of parameters - - """ - self.control_data.npar = self.parameter_data.shape[0] - return self.control_data.npar - - - @property - def forecast_names(self): - """get the forecast names from the pestpp options (if any). - Returns None if no forecasts are named - - Returns - ------- - forecast_names : list - a list of forecast names. - - """ - if "forecasts" in self.pestpp_options.keys(): - return self.pestpp_options["forecasts"].lower().split(',') - elif "predictions" in self.pestpp_options.keys(): - return self.pestpp_options["predictions"].lower().split(',') - else: - return None - - @property - def obs_groups(self): - """get the observation groups - - Returns - ------- - obs_groups : list - a list of unique observation groups - - """ - og = list(self.observation_data.groupby("obgnme").groups.keys()) - #og = list(map(pst_utils.SFMT, og)) - return og - - @property - def nnz_obs_groups(self): - """ get the observation groups that contain at least one non-zero weighted - observation - - Returns - ------- - nnz_obs_groups : list - a list of observation groups that contain at - least one non-zero weighted observation - - """ - og = [] - obs = self.observation_data - for g in self.obs_groups: - if obs.loc[obs.obgnme==g,"weight"].sum() > 0.0: - og.append(g) - return og - - - @property - def par_groups(self): - """get the parameter groups - - Returns - ------- - par_groups : list - a list of parameter groups - - """ - pass - return list(self.parameter_data.groupby("pargp").groups.keys()) - - - @property - def prior_groups(self): - """get the prior info groups - - Returns - ------- - prior_groups : list - a list of prior information groups - - """ - og = list(self.prior_information.groupby("obgnme").groups.keys()) - #og = list(map(pst_utils.SFMT, og)) - return og - - @property - def prior_names(self): - """ get the prior information names - - Returns - ------- - prior_names : list - a list of prior information names - - """ - return list(self.prior_information.groupby( - self.prior_information.index).groups.keys()) - - @property - def par_names(self): - """get the parameter names - - Returns - ------- - par_names : list - a list of parameter names - """ - return list(self.parameter_data.parnme.values) - - @property - def adj_par_names(self): - """ get the adjustable (not fixed or tied) parameter names - - Returns - ------- - adj_par_names : list - list of adjustable (not fixed or tied) parameter names - - """ - adj_names = [] - for t,n in zip(self.parameter_data.partrans, - self.parameter_data.parnme): - if t.lower() not in ["tied","fixed"]: - adj_names.append(n) - return adj_names - - @property - def obs_names(self): - """get the observation names - - Returns - ------- - obs_names : list - a list of observation names - - """ - pass - return list(self.observation_data.obsnme.values) - - @property - def nnz_obs_names(self): - """get the non-zero weight observation names - - Returns - ------- - nnz_obs_names : list - a list of non-zero weighted observation names - - """ - # nz_names = [] - # for w,n in zip(self.observation_data.weight, - # self.observation_data.obsnme): - # if w > 0.0: - # nz_names.append(n) - obs = self.observation_data - - nz_names = list(obs.loc[obs.weight>0.0,"obsnme"]) - return nz_names - - @property - def zero_weight_obs_names(self): - """ get the zero-weighted observation names - - Returns - ------- - zero_weight_obs_names : list - a list of zero-weighted observation names - - """ - self.observation_data.index = self.observation_data.obsnme - groups = self.observation_data.groupby( - self.observation_data.weight.apply(lambda x: x==0.0)).groups - if True in groups: - return list(self.observation_data.loc[groups[True],"obsnme"]) - else: - return [] - - # @property - # def regul_section(self): - # phimlim = float(self.nnz_obs) - # #sect = "* regularisation\n" - # sect = "{0:15.6E} {1:15.6E}\n".format(phimlim, phimlim*1.15) - # sect += "1.0 1.0e-10 1.0e10 linreg continue\n" - # sect += "1.3 1.0e-2 1\n" - # return sect - - @property - def estimation(self): - """ check if the control_data.pestmode is set to estimation - - Returns - ------- - estimation : bool - True if pestmode is estmation, False otherwise - - """ - if self.control_data.pestmode == "estimation": - return True - return False - - @property - def tied(self): - par = self.parameter_data - tied_pars = par.loc[par.partrans=="tied","parnme"] - if tied_pars.shape[0] == 0: - return None - if "partied" not in par.columns: - par.loc[:,"partied"] = np.NaN - tied = par.loc[tied_pars,["parnme","partied"]] - return tied - - @staticmethod - def _read_df(f,nrows,names,converters,defaults=None): - """ a private method to read part of an open file into a pandas.DataFrame. - - Parameters - ---------- - f : file object - nrows : int - number of rows to read - names : list - names to set the columns of the dataframe with - converters : dict - dictionary of lambda functions to convert strings - to numerical format - defaults : dict - dictionary of default values to assign columns. - Default is None - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - - """ - seek_point = f.tell() - df = pd.read_csv(f, header=None,names=names, - nrows=nrows,delim_whitespace=True, - converters=converters, index_col=False, - comment='#') - - # in case there was some extra junk at the end of the lines - if df.shape[1] > len(names): - df = df.iloc[:,len(names)] - df.columns = names - - if defaults is not None: - for name in names: - df.loc[:,name] = df.loc[:,name].fillna(defaults[name]) - elif np.any(pd.isnull(df)): - raise Exception("NANs found") - f.seek(seek_point) - extras = [] - for i in range(nrows): - line = f.readline() - extra = np.NaN - if '#' in line: - raw = line.strip().split('#') - extra = ' # '.join(raw[1:]) - extras.append(extra) - - df.loc[:,"extra"] = extras - - return df - - - def _read_line_comments(self,f,forgive): - comments = [] - while True: - line = f.readline().lower().strip() - self.lcount += 1 - if line == '': - if forgive: - line = None - break - else: - raise Exception("unexpected EOF") - if line.startswith("++"): - self._parse_pestpp_line(line) - elif line.startswith('#'): - comments.append(line.strip()) - else: - break - return line, comments - - - def _read_section_comments(self,f,forgive): - lines = [] - section_comments = [] - while True: - line,comments = self._read_line_comments(f,forgive) - section_comments.extend(comments) - if line is None or line.startswith("*"): - break - lines.append(line) - return line,lines,section_comments - - - def _cast_df_from_lines(self,name,lines, fieldnames, converters, defaults): - extra = [] - raw = [] - for line in lines: - - if '#' in line: - er = line.strip().split('#') - extra.append('#'.join(er[1:])) - r = er[0].split() - else: - r = line.strip().split() - extra.append(np.NaN) - raw.append(r) - found_fieldnames = fieldnames[:len(raw[0])] - df = pd.DataFrame(raw,columns=found_fieldnames) - for col in fieldnames: - if col not in df.columns: - df.loc[:,col] = np.NaN - if col in fieldnames: - df.loc[:, col] = df.loc[:, col].fillna(defaults[col]) - if col in converters: - - df.loc[:,col] = df.loc[:,col].apply(converters[col]) - df.loc[:,"extra"] = extra - return df - - - def _cast_prior_df_from_lines(self,lines): - pilbl, obgnme, weight, equation = [], [], [], [] - extra = [] - for line in lines: - if '#' in line: - er = line.split('#') - raw = er[0].split() - extra.append('#'.join(er[1:])) - else: - extra.append(np.NaN) - raw = line.split() - pilbl.append(raw[0].lower()) - obgnme.append(raw[-1].lower()) - weight.append(float(raw[-2])) - eq = ' '.join(raw[1:-2]) - equation.append(eq) - - self.prior_information = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "weight": weight, - "obgnme": obgnme}) - self.prior_information.index = self.prior_information.pilbl - self.prior_information.loc[:,"extra"] = extra - -
    [docs] def flex_load(self,filename): - self.lcount = 0 - self.comments = {} - self.prior_information = self.null_prior - assert os.path.exists(filename), "couldn't find control file {0}".format(filename) - f = open(filename, 'r') - - # this should be the pcf line - section = "initial" - line,self.comments[section] = self._read_line_comments(f,False) - - assert line.startswith("pcf") - - line, pcf_comments = self._read_line_comments(f,False) - - section = "* control data" - assert section in line, \ - "Pst.load() error: looking for {0}, found: {1}".format(section,line) - - next_section, section_lines, self.comments[section] = self._read_section_comments(f,False) - self.control_data.parse_values_from_lines(section_lines) - - # read anything until the SVD section - while True: - if next_section.startswith("* singular value") or next_section.startswith("* parameter groups"): - break - next_section, section_lines, c = self._read_section_comments(f,False) - - # SVD - if next_section.startswith("* singular value"): - section = "* singular value decomposition" - next_section, section_lines,self.comments[section] = self._read_section_comments(f, False) - self.svd_data.parse_values_from_lines(section_lines) - - # read anything until par groups - while True: - if next_section.startswith("* parameter groups"): - break - next_section, section_lines, c = self._read_section_comments(f, False) - - # parameter groups - section = "* parameter groups" - assert next_section == section - next_section, section_lines, self.comments[section] = self._read_section_comments(f, False) - self.parameter_groups = self._cast_df_from_lines(next_section,section_lines,self.pargp_fieldnames, - self.pargp_converters, self.pargp_defaults) - self.parameter_groups.index = self.parameter_groups.pargpnme - - # parameter data - section = "* parameter data" - assert next_section == section - next_section, section_lines,self.comments[section] = self._read_section_comments(f, False) - self.parameter_data = self._cast_df_from_lines(next_section, section_lines, self.par_fieldnames, - self.par_converters, self.par_defaults) - self.parameter_data.index = self.parameter_data.parnme - - # # oh the tied parameter bullshit, how do I hate thee - counts = self.parameter_data.partrans.value_counts() - if "tied" in counts.index: - #the tied lines got cast into the parameter data lines - ntied = counts["tied"] - # self.tied = self.parameter_data.iloc[-ntied:,:2] - # self.tied.columns = self.tied_fieldnames - # self.tied.index = self.tied.parnme - tied = self.parameter_data.iloc[-ntied:,:2] - tied.columns = self.tied_fieldnames - tied.index = tied.parnme - self.parameter_data = self.parameter_data.iloc[:-ntied,:] - self.parameter_data.loc[:,'partied'] = np.NaN - - self.parameter_data.loc[tied.index,"partied"] = tied.partied - - # observation groups - section = "* observation groups" - assert next_section == section - next_section, section_lines, self.comments[section] = self._read_section_comments(f, False) - - # observation data - section = "* observation data" - assert next_section == section - next_section, section_lines, self.comments[section] = self._read_section_comments(f, False) - self.observation_data = self._cast_df_from_lines(next_section, section_lines, self.obs_fieldnames, - self.obs_converters, self.obs_defaults) - self.observation_data.index = self.observation_data.obsnme - # model commands - section = "* model command line" - assert next_section == section - next_section, section_lines,self.comments[section] = self._read_section_comments(f, False) - - # model io - section = "* model input/output" - assert next_section == section - next_section, section_lines, self.comments[section] = self._read_section_comments(f, True) - ntpl,nins = self.control_data.ntplfle, self.control_data.ninsfle - assert len(section_lines) == ntpl + nins - for iline,line in enumerate(section_lines): - raw = line.split() - if iline < ntpl: - self.template_files.append(raw[0]) - self.input_files.append(raw[1]) - else: - self.instruction_files.append(raw[0]) - self.output_files.append(raw[1]) - - - # prior info - section = "* prior information" - if next_section == section: - next_line, section_lines,self.comments[section] = self._read_section_comments(f, True) - self._cast_prior_df_from_lines(section_lines) - - # any additional sections - final_comments = [] - - while True: - # TODO: catch a regul section - next_line, comments = self._read_line_comments(f, True) - if next_line is None: - break - self.other_lines.append(next_line) - #next_line,comments = self._read_line_comments(f,True) - final_comments.extend(comments) - self.comments["final"] = final_comments
    - - -
    [docs] def load(self, filename): - """load the pest control file information - - Parameters - ---------- - filename : str - pst filename - - Raises - ------ - lots of exceptions for incorrect format - - """ - - f = open(filename, 'r') - f.readline() - - #control section - line = f.readline() - assert "* control data" in line,\ - "Pst.load() error: looking for control" +\ - " data section, found:" + line - control_lines = [] - while True: - line = f.readline() - if line == '': - raise Exception("Pst.load() EOF while " +\ - "reading control data section") - if line.startswith('*'): - break - control_lines.append(line) - self.control_data.parse_values_from_lines(control_lines) - - - #anything between control data and SVD - while True: - if line == '': - raise Exception("EOF before parameter groups section found") - if "* singular value decomposition" in line.lower() or\ - "* parameter groups" in line.lower(): - break - self.other_lines.append(line) - line = f.readline() - - if "* singular value decomposition" in line.lower(): - svd_lines = [] - for _ in range(3): - line = f.readline() - if line == '': - raise Exception("EOF while reading SVD section") - svd_lines.append(line) - self.svd_data.parse_values_from_lines(svd_lines) - line = f.readline() - while True: - if line == '': - raise Exception("EOF before parameter groups section found") - if "* parameter groups" in line.lower(): - break - self.other_lines.append(line) - line = f.readline() - - #parameter data - assert "* parameter groups" in line.lower(),\ - "Pst.load() error: looking for parameter" +\ - " group section, found:" + line - try: - self.parameter_groups = self._read_df(f,self.control_data.npargp, - self.pargp_fieldnames, - self.pargp_converters, - self.pargp_defaults) - self.parameter_groups.index = self.parameter_groups.pargpnme - except Exception as e: - raise Exception("Pst.load() error reading parameter groups: {0}".format(str(e))) - - #parameter data - line = f.readline() - assert "* parameter data" in line.lower(),\ - "Pst.load() error: looking for parameter" +\ - " data section, found:" + line - - try: - self.parameter_data = self._read_df(f,self.control_data.npar, - self.par_fieldnames, - self.par_converters, - self.par_defaults) - self.parameter_data.index = self.parameter_data.parnme - except Exception as e: - raise Exception("Pst.load() error reading parameter data: {0}".format(str(e))) - - # oh the tied parameter bullshit, how do I hate thee - counts = self.parameter_data.partrans.value_counts() - if "tied" in counts.index: - # tied_lines = [f.readline().lower().strip().split() for _ in range(counts["tied"])] - # self.tied = pd.DataFrame(tied_lines,columns=["parnme","partied"]) - # self.tied.index = self.tied.pop("parnme") - tied = self._read_df(f,counts["tied"],self.tied_fieldnames, - self.tied_converters) - tied.index = tied.parnme - self.parameter_data.loc[:,"partied"] = np.NaN - self.parameter_data.loc[tied.index,"partied"] = tied.partied - - # obs groups - just read past for now - line = f.readline() - assert "* observation groups" in line.lower(),\ - "Pst.load() error: looking for obs" +\ - " group section, found:" + line - [f.readline() for _ in range(self.control_data.nobsgp)] - - # observation data - line = f.readline() - assert "* observation data" in line.lower(),\ - "Pst.load() error: looking for observation" +\ - " data section, found:" + line - if self.control_data.nobs > 0: - try: - self.observation_data = self._read_df(f,self.control_data.nobs, - self.obs_fieldnames, - self.obs_converters) - self.observation_data.index = self.observation_data.obsnme - except: - raise Exception("Pst.load() error reading observation data") - else: - raise Exception("nobs == 0") - #model command line - line = f.readline() - assert "* model command line" in line.lower(),\ - "Pst.load() error: looking for model " +\ - "command section, found:" + line - for i in range(self.control_data.numcom): - self.model_command.append(f.readline().strip()) - - #model io - line = f.readline() - assert "* model input/output" in line.lower(), \ - "Pst.load() error; looking for model " +\ - " i/o section, found:" + line - for i in range(self.control_data.ntplfle): - raw = f.readline().strip().split() - self.template_files.append(raw[0]) - self.input_files.append(raw[1]) - for i in range(self.control_data.ninsfle): - raw = f.readline().strip().split() - self.instruction_files.append(raw[0]) - self.output_files.append(raw[1]) - - #prior information - sort of hackish - if self.control_data.nprior == 0: - self.prior_information = self.null_prior - else: - pilbl, obgnme, weight, equation = [], [], [], [] - line = f.readline() - assert "* prior information" in line.lower(), \ - "Pst.load() error; looking for prior " +\ - " info section, found:" + line - for iprior in range(self.control_data.nprior): - line = f.readline() - if line == '': - raise Exception("EOF during prior information " + - "section") - raw = line.strip().split() - pilbl.append(raw[0].lower()) - obgnme.append(raw[-1].lower()) - weight.append(float(raw[-2])) - eq = ' '.join(raw[1:-2]) - equation.append(eq) - self.prior_information = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "weight": weight, - "obgnme": obgnme}) - self.prior_information.index = self.prior_information.pilbl - if "regul" in self.control_data.pestmode: - line = f.readline() - assert "* regul" in line.lower(), \ - "Pst.load() error; looking for regul " +\ - " section, found:" + line - #[self.regul_lines.append(f.readline()) for _ in range(3)] - regul_lines = [f.readline() for _ in range(3)] - raw = regul_lines[0].strip().split() - self.reg_data.phimlim = float(raw[0]) - self.reg_data.phimaccept = float(raw[1]) - raw = regul_lines[1].strip().split() - self.wfinit = float(raw[0]) - - - for line in f: - if line.strip().startswith("++") and '#' not in line: - self._parse_pestpp_line(line) - f.close() - - for df in [self.parameter_groups,self.parameter_data, - self.observation_data,self.prior_information]: - if "extra" in df.columns and df.extra.dropna().shape[0] > 0: - self.with_comments = False - break - return
    - - - def _parse_pestpp_line(self,line): - # args = line.replace('++','').strip().split() - args = line.replace("++", '').strip().split(')') - args = [a for a in args if a != ''] - # args = ['++'+arg.strip() for arg in args] - # self.pestpp_lines.extend(args) - keys = [arg.split('(')[0] for arg in args] - values = [arg.split('(')[1].replace(')', '') for arg in args if '(' in arg] - for _ in range(len(values)-1,len(keys)): - values.append('') - for key, value in zip(keys, values): - if key in self.pestpp_options: - print("Pst.load() warning: duplicate pest++ option found:" + str(key),PyemuWarning) - self.pestpp_options[key] = value - - def _update_control_section(self): - """ private method to synchronize the control section counters with the - various parts of the control file. This is usually called during the - Pst.write() method. - - """ - self.control_data.npar = self.npar - self.control_data.nobs = self.nobs - self.control_data.npargp = self.parameter_groups.shape[0] - self.control_data.nobsgp = self.observation_data.obgnme.\ - value_counts().shape[0] + self.prior_information.obgnme.\ - value_counts().shape[0] - - self.control_data.nprior = self.prior_information.shape[0] - self.control_data.ntplfle = len(self.template_files) - self.control_data.ninsfle = len(self.instruction_files) - self.control_data.numcom = len(self.model_command) - -
    [docs] def rectify_pgroups(self): - """ private method to synchronize parameter groups section with - the parameter data section - - - """ - # add any parameters groups - pdata_groups = list(self.parameter_data.loc[:,"pargp"].\ - value_counts().keys()) - #print(pdata_groups) - need_groups = [] - existing_groups = list(self.parameter_groups.pargpnme) - for pg in pdata_groups: - if pg not in existing_groups: - need_groups.append(pg) - if len(need_groups) > 0: - #print(need_groups) - defaults = copy.copy(pst_utils.pst_config["pargp_defaults"]) - for grp in need_groups: - defaults["pargpnme"] = grp - self.parameter_groups = \ - self.parameter_groups.append(defaults,ignore_index=True) - - # now drop any left over groups that aren't needed - for gp in self.parameter_groups.loc[:,"pargpnme"]: - if gp in pdata_groups and gp not in need_groups: - need_groups.append(gp) - self.parameter_groups.index = self.parameter_groups.pargpnme - self.parameter_groups = self.parameter_groups.loc[need_groups,:]
    - - - def _parse_pi_par_names(self): - """ private method to get the parameter names from prior information - equations. Sets a 'names' column in Pst.prior_information that is a list - of parameter names - - - """ - if self.prior_information.shape[0] == 0: - return - if "names" in self.prior_information.columns: - self.prior_information.pop("names") - if "rhs" in self.prior_information.columns: - self.prior_information.pop("rhs") - def parse(eqs): - raw = eqs.split('=') - rhs = float(raw[1]) - raw = re.split('[+,-]',raw[0].lower().strip()) - # in case of a leading '-' or '+' - if len(raw[0]) == 0: - raw = raw[1:] - # pnames = [] - # for r in raw: - # if '*' not in r: - # continue - # pname = r.split('*')[1].replace("log(", '').replace(')', '').strip() - # pnames.append(pname) - # return pnames - return [r.split('*')[1].replace("log(",'').replace(')','').strip() for r in raw if '*' in r] - - self.prior_information.loc[:,"names"] =\ - self.prior_information.equation.apply(lambda x: parse(x)) - - -
    [docs] def add_pi_equation(self,par_names,pilbl=None,rhs=0.0,weight=1.0, - obs_group="pi_obgnme",coef_dict={}): - """ a helper to construct a new prior information equation. - - Parameters - ---------- - par_names : list - parameter names in the equation - pilbl : str - name to assign the prior information equation. If None, - a generic equation name is formed. Default is None - rhs : (float) - the right-hand side of the equation - weight : (float) - the weight of the equation - obs_group : str - the observation group for the equation. Default is 'pi_obgnme' - coef_dict : dict - a dictionary of parameter name, coefficient pairs to assign - leading coefficients for one or more parameters in the equation. - If a parameter is not listed, 1.0 is used for its coefficients. - Default is {} - - """ - if pilbl is None: - pilbl = "pilbl_{0}".format(self.__pi_count) - self.__pi_count += 1 - missing,fixed = [],[] - - for par_name in par_names: - if par_name not in self.parameter_data.parnme: - missing.append(par_name) - elif self.parameter_data.loc[par_name,"partrans"] in ["fixed","tied"]: - fixed.append(par_name) - if len(missing) > 0: - raise Exception("Pst.add_pi_equation(): the following pars "+\ - " were not found: {0}".format(','.join(missing))) - if len(fixed) > 0: - raise Exception("Pst.add_pi_equation(): the following pars "+\ - " were are fixed/tied: {0}".format(','.join(missing))) - eqs_str = '' - sign = '' - for i,par_name in enumerate(par_names): - coef = coef_dict.get(par_name,1.0) - if coef < 0.0: - sign = '-' - coef = np.abs(coef) - elif i > 0: sign = '+' - if self.parameter_data.loc[par_name,"partrans"] == "log": - par_name = "log({})".format(par_name) - eqs_str += " {0} {1} * {2} ".format(sign,coef,par_name) - eqs_str += " = {0}".format(rhs) - self.prior_information.loc[pilbl,"pilbl"] = pilbl - self.prior_information.loc[pilbl,"equation"] = eqs_str - self.prior_information.loc[pilbl,"weight"] = weight - self.prior_information.loc[pilbl,"obgnme"] = obs_group
    - -
    [docs] def rectify_pi(self): - """ rectify the prior information equation with the current state of the - parameter_data dataframe. Equations that list fixed, tied or missing parameters - are removed. This method is called during Pst.write() - - """ - if self.prior_information.shape[0] == 0: - return - self._parse_pi_par_names() - adj_names = self.adj_par_names - def is_good(names): - for n in names: - if n not in adj_names: - return False - return True - keep_idx = self.prior_information.names.\ - apply(lambda x: is_good(x)) - self.prior_information = self.prior_information.loc[keep_idx,:]
    - - def _write_df(self,name,f,df,formatters,columns): - if name.startswith('*'): - f.write(name+'\n') - if self.with_comments: - for line in self.comments.get(name, []): - f.write(line+'\n') - if df.loc[:,columns].isnull().values.any(): - #warnings.warn("WARNING: NaNs in {0} dataframe".format(name)) - csv_name = "pst.{0}.nans.csv".format(name.replace(" ",'_').replace('*','')) - df.to_csv(csv_name) - raise Exception("NaNs in {0} dataframe, csv written to {1}".format(name, csv_name)) - def ext_fmt(x): - if pd.notnull(x): - return " # {0}".format(x) - return '' - if self.with_comments and 'extra' in df.columns: - df.loc[:,"extra_str"] = df.extra.apply(ext_fmt) - columns.append("extra_str") - #formatters["extra"] = lambda x: " # {0}".format(x) if pd.notnull(x) else 'test' - #formatters["extra"] = lambda x: ext_fmt(x) - - - f.write(df.to_string(col_space=0,formatters=formatters, - columns=columns, - justify="right", - header=False, - index=False) + '\n') - -
    [docs] def sanity_checks(self): - - dups = self.parameter_data.parnme.value_counts() - dups = dups.loc[dups>1] - if dups.shape[0] > 0: - warnings.warn("duplicate parameter names: {0}".format(','.join(list(dups.index))),PyemuWarning) - dups = self.observation_data.obsnme.value_counts() - dups = dups.loc[dups>1] - if dups.shape[0] > 0: - warnings.warn("duplicate observation names: {0}".format(','.join(list(dups.index))),PyemuWarning) - - if self.npar_adj == 0: - warnings.warn("no adjustable pars",PyemuWarning) - - if self.nnz_obs == 0: - warnings.warn("no non-zero weight obs",PyemuWarning) - - print("noptmax: {0}".format(self.control_data.noptmax))
    - - - -
    [docs] def write(self,new_filename,update_regul=False): - """write a pest control file - - Parameters - ---------- - new_filename : str - name of the new pest control file - update_regul : (boolean) - flag to update zero-order Tikhonov prior information - equations to prefer the current parameter values - - - """ - self.new_filename = new_filename - self.rectify_pgroups() - self.rectify_pi() - self._update_control_section() - self.sanity_checks() - - f_out = open(new_filename, 'w') - if self.with_comments: - for line in self.comments.get("initial",[]): - f_out.write(line+'\n') - f_out.write("pcf\n* control data\n") - self.control_data.write(f_out) - - # for line in self.other_lines: - # f_out.write(line) - if self.with_comments: - for line in self.comments.get("* singular value decompisition",[]): - f_out.write(line) - self.svd_data.write(f_out) - - #f_out.write("* parameter groups\n") - - # to catch the byte code ugliness in python 3 - pargpnme = self.parameter_groups.loc[:,"pargpnme"].copy() - self.parameter_groups.loc[:,"pargpnme"] = \ - self.parameter_groups.pargpnme.apply(self.pargp_format["pargpnme"]) - - self._write_df("* parameter groups", f_out, self.parameter_groups, - self.pargp_format, self.pargp_fieldnames) - self.parameter_groups.loc[:,"pargpnme"] = pargpnme - - self._write_df("* parameter data",f_out, self.parameter_data, - self.par_format, self.par_fieldnames) - - if self.tied is not None: - self._write_df("tied parameter data", f_out, self.tied, - self.tied_format, self.tied_fieldnames) - - f_out.write("* observation groups\n") - for group in self.obs_groups: - try: - group = group.decode() - except: - pass - f_out.write(pst_utils.SFMT(str(group))+'\n') - for group in self.prior_groups: - try: - group = group.decode() - except: - pass - f_out.write(pst_utils.SFMT(str(group))+'\n') - - self._write_df("* observation data", f_out, self.observation_data, - self.obs_format, self.obs_fieldnames) - - f_out.write("* model command line\n") - for cline in self.model_command: - f_out.write(cline+'\n') - - f_out.write("* model input/output\n") - for tplfle,infle in zip(self.template_files,self.input_files): - f_out.write(tplfle+' '+infle+'\n') - for insfle,outfle in zip(self.instruction_files,self.output_files): - f_out.write(insfle+' '+outfle+'\n') - - if self.nprior > 0: - if self.prior_information.isnull().values.any(): - #print("WARNING: NaNs in prior_information dataframe") - warnings.warn("NaNs in prior_information dataframe",PyemuWarning) - f_out.write("* prior information\n") - #self.prior_information.index = self.prior_information.pop("pilbl") - max_eq_len = self.prior_information.equation.apply(lambda x:len(x)).max() - eq_fmt_str = " {0:<" + str(max_eq_len) + "s} " - eq_fmt_func = lambda x:eq_fmt_str.format(x) - # 17/9/2016 - had to go with a custom writer loop b/c pandas doesn't want to - # output strings longer than 100, even with display.max_colwidth - #f_out.write(self.prior_information.to_string(col_space=0, - # columns=self.prior_fieldnames, - # formatters=pi_formatters, - # justify="right", - # header=False, - # index=False) + '\n') - #self.prior_information["pilbl"] = self.prior_information.index - # for idx,row in self.prior_information.iterrows(): - # f_out.write(pst_utils.SFMT(row["pilbl"])) - # f_out.write(eq_fmt_func(row["equation"])) - # f_out.write(pst_utils.FFMT(row["weight"])) - # f_out.write(pst_utils.SFMT(row["obgnme"]) + '\n') - for idx, row in self.prior_information.iterrows(): - f_out.write(pst_utils.SFMT(row["pilbl"])) - f_out.write(eq_fmt_func(row["equation"])) - f_out.write(pst_utils.FFMT(row["weight"])) - f_out.write(pst_utils.SFMT(row["obgnme"])) - if self.with_comments and 'extra' in row: - f_out.write(" # {0}".format(row['extra'])) - f_out.write('\n') - - if self.control_data.pestmode.startswith("regul"): - #f_out.write("* regularisation\n") - #if update_regul or len(self.regul_lines) == 0: - # f_out.write(self.regul_section) - #else: - # [f_out.write(line) for line in self.regul_lines] - self.reg_data.write(f_out) - - for line in self.other_lines: - f_out.write(line+'\n') - - for key,value in self.pestpp_options.items(): - if isinstance(value,list): - value = ','.join([str(v) for v in value]) - f_out.write("++{0}({1})\n".format(str(key),str(value))) - - if self.with_comments: - for line in self.comments.get("final",[]): - f_out.write(line+'\n') - - f_out.close()
    - - -
    [docs] def get(self, par_names=None, obs_names=None): - """get a new pst object with subset of parameters and/or observations - - Parameters - ---------- - par_names : list - a list of parameter names to have in the new Pst instance. - If None, all parameters are in the new Pst instance. Default - is None - obs_names : list - a list of observation names to have in the new Pst instance. - If None, all observations are in teh new Pst instance. Default - is None - - Returns - ------- - Pst : Pst - a new Pst instance - - """ - pass - #if par_names is None and obs_names is None: - # return copy.deepcopy(self) - if par_names is None: - par_names = self.parameter_data.parnme - if obs_names is None: - obs_names = self.observation_data.obsnme - - new_par = self.parameter_data.copy() - if par_names is not None: - new_par.index = new_par.parnme - new_par = new_par.loc[par_names, :] - new_obs = self.observation_data.copy() - new_res = None - - if obs_names is not None: - new_obs.index = new_obs.obsnme - new_obs = new_obs.loc[obs_names] - if self.__res is not None: - new_res = copy.deepcopy(self.res) - new_res.index = new_res.name - new_res = new_res.loc[obs_names, :] - - new_pargp = self.parameter_groups.copy() - new_pargp.index = new_pargp.pargpnme.apply(str.strip) - new_pargp_names = new_par.pargp.value_counts().index - new_pargp = new_pargp.loc[new_pargp_names,:] - - new_pst = Pst(self.filename, resfile=self.resfile, load=False) - new_pst.parameter_data = new_par - new_pst.observation_data = new_obs - new_pst.parameter_groups = new_pargp - new_pst.__res = new_res - new_pst.prior_information = self.prior_information - new_pst.rectify_pi() - new_pst.control_data = self.control_data.copy() - - new_pst.model_command = self.model_command - new_pst.template_files = self.template_files - new_pst.input_files = self.input_files - new_pst.instruction_files = self.instruction_files - new_pst.output_files = self.output_files - - if self.tied is not None: - warnings.warn("Pst.get() not checking for tied parameter " + - "compatibility in new Pst instance",PyemuWarning) - #new_pst.tied = self.tied.copy() - new_pst.other_lines = self.other_lines - new_pst.pestpp_options = self.pestpp_options - new_pst.regul_lines = self.regul_lines - - return new_pst
    - -
    [docs] def zero_order_tikhonov(self,parbounds=True): - raise Exception("Pst.zero_oder_tikhonov has moved to utils.helpers")
    - - -
    [docs] def parrep(self, parfile=None,enforce_bounds=True): - """replicates the pest parrep util. replaces the parval1 field in the - parameter data section dataframe - - Parameters - ---------- - parfile : str - parameter file to use. If None, try to use - a parameter file that corresponds to the case name. - Default is None - enforce_hounds : bool - flag to enforce parameter bounds after parameter values are updated. - This is useful because PEST and PEST++ round the parameter values in the - par file, which may cause slight bound violations - - """ - if parfile is None: - parfile = self.filename.replace(".pst", ".par") - par_df = pst_utils.read_parfile(parfile) - self.parameter_data.index = self.parameter_data.parnme - par_df.index = par_df.parnme - self.parameter_data.parval1 = par_df.parval1 - self.parameter_data.scale = par_df.scale - self.parameter_data.offset = par_df.offset - - if enforce_bounds: - par = self.parameter_data - idx = par.loc[par.parval1 > par.parubnd,"parnme"] - par.loc[idx,"parval1"] = par.loc[idx,"parubnd"] - idx = par.loc[par.parval1 < par.parlbnd,"parnme"] - par.loc[idx, "parval1"] = par.loc[idx, "parlbnd"]
    - - - - -
    [docs] def adjust_weights_recfile(self, recfile=None): - """adjusts the weights by group of the observations based on the phi components - in a pest record file so that total phi is equal to the number of - non-zero weighted observations - - Parameters - ---------- - recfile : str - record file name. If None, try to use a record file - with the Pst case name. Default is None - - """ - if recfile is None: - recfile = self.filename.replace(".pst", ".rec") - assert os.path.exists(recfile), \ - "Pst.adjust_weights_recfile(): recfile not found: " +\ - str(recfile) - iter_components = pst_utils.get_phi_comps_from_recfile(recfile) - iters = iter_components.keys() - iters.sort() - obs = self.observation_data - ogroups = obs.groupby("obgnme").groups - last_complete_iter = None - for ogroup, idxs in ogroups.iteritems(): - for iiter in iters[::-1]: - incomplete = False - if ogroup not in iter_components[iiter]: - incomplete = True - break - if not incomplete: - last_complete_iter = iiter - break - if last_complete_iter is None: - raise Exception("Pst.pwtadj2(): no complete phi component" + - " records found in recfile") - self._adjust_weights_by_phi_components( - iter_components[last_complete_iter])
    - -
    [docs] def adjust_weights_resfile(self, resfile=None): - """adjusts the weights by group of the observations based on the phi components - in a pest residual file so that total phi is equal to the number of - non-zero weighted observations - - Parameters - ---------- - resfile : str - residual file name. If None, try to use a residual file - with the Pst case name. Default is None - - """ - if resfile is not None: - self.resfile = resfile - self.__res = None - phi_comps = self.phi_components - self._adjust_weights_by_phi_components(phi_comps)
    - - def _adjust_weights_by_phi_components(self, components): - """resets the weights of observations by group to account for - residual phi components. - - Parameters - ---------- - components : dict - a dictionary of obs group:phi contribution pairs - - """ - obs = self.observation_data - nz_groups = obs.groupby(obs["weight"].map(lambda x: x == 0)).groups - ogroups = obs.groupby("obgnme").groups - for ogroup, idxs in ogroups.items(): - if self.control_data.pestmode.startswith("regul") \ - and "regul" in ogroup.lower(): - continue - og_phi = components[ogroup] - nz_groups = obs.loc[idxs,:].groupby(obs.loc[idxs,"weight"].\ - map(lambda x: x == 0)).groups - og_nzobs = 0 - if False in nz_groups.keys(): - og_nzobs = len(nz_groups[False]) - if og_nzobs == 0 and og_phi > 0: - raise Exception("Pst.adjust_weights_by_phi_components():" - " no obs with nonzero weight," + - " but phi > 0 for group:" + str(ogroup)) - if og_phi > 0: - factor = np.sqrt(float(og_nzobs) / float(og_phi)) - obs.loc[idxs,"weight"] = obs.weight[idxs] * factor - self.observation_data = obs - - def __reset_weights(self, target_phis, res_idxs, obs_idxs): - """private method to reset weights based on target phi values - for each group. This method should not be called directly - - Parameters - ---------- - target_phis : dict - target phi contribution for groups to reweight - res_idxs : dict - the index positions of each group of interest - in the res dataframe - obs_idxs : dict - the index positions of each group of interest - in the observation data dataframe - - """ - - for item in target_phis.keys(): - assert item in res_idxs.keys(),\ - "Pst.__reset_weights(): " + str(item) +\ - " not in residual group indices" - assert item in obs_idxs.keys(), \ - "Pst.__reset_weights(): " + str(item) +\ - " not in observation group indices" - actual_phi = ((self.res.loc[res_idxs[item], "residual"] * - self.observation_data.loc - [obs_idxs[item], "weight"])**2).sum() - if actual_phi > 0.0: - weight_mult = np.sqrt(target_phis[item] / actual_phi) - self.observation_data.loc[obs_idxs[item], "weight"] *= weight_mult - else: - ("Pst.__reset_weights() warning: phi group {0} has zero phi, skipping...".format(item)) - -
    [docs] def adjust_weights_by_list(self,obslist,weight): - """reset the weight for a list of observation names. Supports the - data worth analyses in pyemu.Schur class - - Parameters - ---------- - obslist : list - list of observation names - weight : (float) - new weight to assign - - """ - - obs = self.observation_data - if not isinstance(obslist,list): - obslist = [obslist] - obslist = set([str(i).lower() for i in obslist]) - #groups = obs.groupby([lambda x:x in obslist, - # obs.weight.apply(lambda x:x==0.0)]).groups - #if (True,True) in groups: - # obs.loc[groups[True,True],"weight"] = weight - reset_names = obs.loc[obs.apply(lambda x: x.obsnme in obslist and x.weight==0,axis=1),"obsnme"] - if len(reset_names) > 0: - obs.loc[reset_names,"weight"] = weight
    - -
    [docs] def adjust_weights(self,obs_dict=None, - obsgrp_dict=None): - """reset the weights of observation groups to contribute a specified - amount to the composite objective function - - Parameters - ---------- - obs_dict : dict - dictionary of obs name,new contribution pairs - obsgrp_dict : dict - dictionary of obs group name,contribution pairs - - Note - ---- - if all observations in a named obs group have zero weight, they will be - assigned a non-zero weight so that the request phi contribution - can be met. Similarly, any observations listed in obs_dict with zero - weight will also be reset - - """ - - self.observation_data.index = self.observation_data.obsnme - self.res.index = self.res.name - - if obsgrp_dict is not None: - # reset groups with all zero weights - obs = self.observation_data - for grp in obsgrp_dict.keys(): - if obs.loc[obs.obgnme==grp,"weight"].sum() == 0.0: - obs.loc[obs.obgnme==grp,"weight"] = 1.0 - res_groups = self.res.groupby("group").groups - obs_groups = self.observation_data.groupby("obgnme").groups - self.__reset_weights(obsgrp_dict, res_groups, obs_groups) - if obs_dict is not None: - # reset obs with zero weight - obs = self.observation_data - for oname in obs_dict.keys(): - if obs.loc[oname,"weight"] == 0.0: - obs.loc[oname,"weight"] = 1.0 - - res_groups = self.res.groupby("name").groups - obs_groups = self.observation_data.groupby("obsnme").groups - self.__reset_weights(obs_dict, res_groups, obs_groups)
    - - -
    [docs] def proportional_weights(self, fraction_stdev=1.0, wmax=100.0, - leave_zero=True): - """setup weights inversely proportional to the observation value - - Parameters - ---------- - fraction_stdev : float - the fraction portion of the observation - val to treat as the standard deviation. set to 1.0 for - inversely proportional - wmax : float - maximum weight to allow - leave_zero : bool - flag to leave existing zero weights - - """ - new_weights = [] - for oval, ow in zip(self.observation_data.obsval, - self.observation_data.weight): - if leave_zero and ow == 0.0: - ow = 0.0 - elif oval == 0.0: - ow = wmax - else: - nw = 1.0 / (np.abs(oval) * fraction_stdev) - ow = min(wmax, nw) - new_weights.append(ow) - self.observation_data.weight = new_weights
    - -
    [docs] def calculate_pertubations(self): - """ experimental method to calculate finite difference parameter - pertubations. The pertubation values are added to the - Pst.parameter_data attribute - - Note - ---- - user beware! - - """ - self.build_increments() - self.parameter_data.loc[:,"pertubation"] = \ - self.parameter_data.parval1 + \ - self.parameter_data.increment - - self.parameter_data.loc[:,"out_forward"] = \ - self.parameter_data.loc[:,"pertubation"] > \ - self.parameter_data.loc[:,"parubnd"] - - out_forward = self.parameter_data.groupby("out_forward").groups - if True in out_forward: - self.parameter_data.loc[out_forward[True],"pertubation"] = \ - self.parameter_data.loc[out_forward[True],"parval1"] - \ - self.parameter_data.loc[out_forward[True],"increment"] - - self.parameter_data.loc[:,"out_back"] = \ - self.parameter_data.loc[:,"pertubation"] < \ - self.parameter_data.loc[:,"parlbnd"] - out_back = self.parameter_data.groupby("out_back").groups - if True in out_back: - still_out = out_back[True] - print(self.parameter_data.loc[still_out,:],flush=True) - - raise Exception("Pst.calculate_pertubations(): " +\ - "can't calc pertubations for the following "+\ - "Parameters {0}".format(','.join(still_out)))
    - -
    [docs] def build_increments(self): - """ experimental method to calculate parameter increments for use - in the finite difference pertubation calculations - - Note - ---- - user beware! - - """ - self.enforce_bounds() - self.add_transform_columns() - par_groups = self.parameter_data.groupby("pargp").groups - inctype = self.parameter_groups.groupby("inctyp").groups - for itype,inc_groups in inctype.items(): - pnames = [] - for group in inc_groups: - pnames.extend(par_groups[group]) - derinc = self.parameter_groups.loc[group,"derinc"] - self.parameter_data.loc[par_groups[group],"derinc"] = derinc - if itype == "absolute": - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] - elif itype == "relative": - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] * \ - self.parameter_data.loc[pnames,"parval1"] - elif itype == "rel_to_max": - mx = self.parameter_data.loc[pnames,"parval1"].max() - self.parameter_data.loc[pnames,"increment"] = \ - self.parameter_data.loc[pnames,"derinc"] * mx - else: - raise Exception('Pst.get_derivative_increments(): '+\ - 'unrecognized increment type:{0}'.format(itype)) - - #account for fixed pars - isfixed = self.parameter_data.partrans=="fixed" - self.parameter_data.loc[isfixed,"increment"] = \ - self.parameter_data.loc[isfixed,"parval1"]
    - -
    [docs] def add_transform_columns(self): - """ add transformed values to the Pst.parameter_data attribute - - """ - for col in ["parval1","parlbnd","parubnd","increment"]: - if col not in self.parameter_data.columns: - continue - self.parameter_data.loc[:,col+"_trans"] = (self.parameter_data.parval1 * - self.parameter_data.scale) +\ - self.parameter_data.offset - #isnotfixed = self.parameter_data.partrans != "fixed" - islog = self.parameter_data.partrans == "log" - self.parameter_data.loc[islog,col+"_trans"] = \ - self.parameter_data.loc[islog,col+"_trans"].\ - apply(lambda x:np.log10(x))
    - -
    [docs] def enforce_bounds(self): - """ enforce bounds violation resulting from the - parameter pertubation calculations - - """ - too_big = self.parameter_data.loc[:,"parval1"] > \ - self.parameter_data.loc[:,"parubnd"] - self.parameter_data.loc[too_big,"parval1"] = \ - self.parameter_data.loc[too_big,"parubnd"] - - too_small = self.parameter_data.loc[:,"parval1"] < \ - self.parameter_data.loc[:,"parlbnd"] - self.parameter_data.loc[too_small,"parval1"] = \ - self.parameter_data.loc[too_small,"parlbnd"]
    - - -
    [docs] @classmethod - def from_io_files(cls,tpl_files,in_files,ins_files,out_files,pst_filename=None): - """ create a Pst instance from model interface files. Assigns generic values for - parameter info. Tries to use INSCHEK to set somewhat meaningful observation - values - - Parameters - ---------- - tpl_files : list - list of template file names - in_files : list - list of model input file names (pairs with template files) - ins_files : list - list of instruction file names - out_files : list - list of model output file names (pairs with instruction files) - pst_filename : str - name of control file to write. If None, no file is written. - Default is None - - Returns - ------- - Pst : Pst - - Note - ---- - calls pyemu.helpers.pst_from_io_files() - - """ - from pyemu import helpers - return helpers.pst_from_io_files(tpl_files=tpl_files,in_files=in_files, - ins_files=ins_files,out_files=out_files, - pst_filename=pst_filename)
    - - -
    [docs] def add_parameters(self,template_file,in_file=None,pst_path=None): - """ add new parameters to a control file - - Parameters - ---------- - template_file : str - template file - in_file : str(optional) - model input file. If None, template_file.replace('.tpl','') is used - pst_path : str(optional) - the path to append to the template_file and in_file in the control file. If - not None, then any existing path in front of the template or in file is split off - and pst_path is prepended. Default is None - - Returns - ------- - new_par_data : pandas.DataFrame - the data for the new parameters that were added. If no new parameters are in the - new template file, returns None - - Note - ---- - populates the new parameter information with default values - - """ - assert os.path.exists(template_file),"template file '{0}' not found".format(template_file) - assert template_file != in_file - # get the parameter names in the template file - parnme = pst_utils.parse_tpl_file(template_file) - - # find "new" parameters that are not already in the control file - new_parnme = [p for p in parnme if p not in self.parameter_data.parnme] - - if len(new_parnme) == 0: - warnings.warn("no new parameters found in template file {0}".format(template_file),PyemuWarning) - new_par_data = None - else: - # extend pa - # rameter_data - new_par_data = pst_utils.populate_dataframe(new_parnme,pst_utils.pst_config["par_fieldnames"], - pst_utils.pst_config["par_defaults"], - pst_utils.pst_config["par_dtype"]) - new_par_data.loc[new_parnme,"parnme"] = new_parnme - self.parameter_data = self.parameter_data.append(new_par_data) - if in_file is None: - in_file = template_file.replace(".tpl",'') - if pst_path is not None: - template_file = os.path.join(pst_path,os.path.split(template_file)[-1]) - in_file = os.path.join(pst_path, os.path.split(in_file)[-1]) - self.template_files.append(template_file) - self.input_files.append(in_file) - - return new_par_data
    - - -
    [docs] def add_observations(self,ins_file,out_file,pst_path=None,inschek=True): - """ add new parameters to a control file - - Parameters - ---------- - ins_file : str - instruction file - out_file : str - model output file - pst_path : str(optional) - the path to append to the instruction file and out file in the control file. If - not None, then any existing path in front of the template or in file is split off - and pst_path is prepended. Default is None - inschek : bool - flag to run inschek. If successful, inscheck outputs are used as obsvals - - Returns - ------- - new_obs_data : pandas.DataFrame - the data for the new observations that were added - - Note - ---- - populates the new observation information with default values - - """ - assert os.path.exists(ins_file),"{0}, {1}".format(os.getcwd(),ins_file) - assert ins_file != out_file, "doh!" - - # get the parameter names in the template file - obsnme = pst_utils.parse_ins_file(ins_file) - - sobsnme = set(obsnme) - sexist = set(self.obs_names) - sint = sobsnme.intersection(sexist) - if len(sint) > 0: - raise Exception("the following obs instruction file {0} are already in the control file:{1}". - format(ins_file,','.join(sint))) - - # find "new" parameters that are not already in the control file - new_obsnme = [o for o in obsnme if o not in self.observation_data.obsnme] - - if len(new_obsnme) == 0: - raise Exception("no new observations found in instruction file {0}".format(ins_file)) - - # extend observation_data - new_obs_data = pst_utils.populate_dataframe(new_obsnme,pst_utils.pst_config["obs_fieldnames"], - pst_utils.pst_config["obs_defaults"], - pst_utils.pst_config["obs_dtype"]) - new_obs_data.loc[new_obsnme,"obsnme"] = new_obsnme - new_obs_data.index = new_obsnme - self.observation_data = self.observation_data.append(new_obs_data) - - if pst_path is not None: - ins_file = os.path.join(pst_path,os.path.split(ins_file)[-1]) - out_file = os.path.join(pst_path, os.path.split(out_file)[-1]) - self.instruction_files.append(ins_file) - self.output_files.append(out_file) - df = None - if inschek: - df = pst_utils._try_run_inschek(ins_file,out_file) - if df is not None: - #print(self.observation_data.index,df.index) - self.observation_data.loc[df.index,"obsval"] = df.obsval - new_obs_data.loc[df.index,"obsval"] = df.obsval - return new_obs_data
    - -
    [docs] def write_input_files(self): - """writes model input files using template files and current parvals. - just syntatic sugar for pst_utils.write_input_files() - - Note - ---- - adds "parval1_trans" column to Pst.parameter_data that includes the - effect of scale and offset - - """ - pst_utils.write_input_files(self)
    - -
    [docs] def get_res_stats(self,nonzero=True): - """ get some common residual stats from the current obsvals, - weights and grouping in self.observation_data and the modelled values in - self.res. The key here is 'current' because if obsval, weights and/or - groupings have changed in self.observation_data since the res file was generated - then the current values for obsval, weight and group are used - - Parameters - ---------- - nonzero : bool - calculate stats using only nonzero-weighted observations. This may seem - obsvious to most users, but you never know.... - - Returns - ------- - df : pd.DataFrame - a dataframe with columns for groups names and indices of statistic name. - - Note - ---- - the normalized RMSE is normalized against the obsval range (max - min) - - """ - res = self.res.copy() - res.loc[:,"obsnme"] = res.pop("name") - res.index = res.obsnme - if nonzero: - obs = self.observation_data.loc[self.nnz_obs_names,:] - #print(obs.shape,res.shape) - res = res.loc[obs.obsnme,:] - #print(obs.shape, res.shape) - - #reset the res parts to current obs values and remove - #duplicate attributes - res.loc[:,"weight"] = obs.weight - res.loc[:,"obsval"] = obs.obsval - res.loc[:,"obgnme"] = obs.obgnme - res.pop("group") - res.pop("measured") - - #build these attribute lists for faster lookup later - og_dict = {og:res.loc[res.obgnme==og,"obsnme"] for og in res.obgnme.unique()} - og_names = list(og_dict.keys()) - - # the list of functions and names - sfuncs = [self._stats_rss, self._stats_mean,self._stats_mae, - self._stats_rmse,self._stats_nrmse] - snames = ["rss","mean","mae","rmse","nrmse"] - - data = [] - for sfunc,sname in zip(sfuncs,snames): - full = sfunc(res) - groups = [full] - for og in og_names: - onames = og_dict[og] - res_og = res.loc[onames,:] - groups.append(sfunc(res_og)) - data.append(groups) - - og_names.insert(0,"all") - df = pd.DataFrame(data,columns=og_names,index=snames) - return df
    - - def _stats_rss(self,df): - return (((df.modelled - df.obsval) * df.weight)**2).sum() - - def _stats_mean(self,df): - return (df.modelled - df.obsval).mean() - - def _stats_mae(self,df): - return ((df.modelled - df.obsval).apply(np.abs)).sum() / df.shape[0] - - def _stats_rmse(self,df): - return np.sqrt(((df.modelled - df.obsval)**2).sum() / df.shape[0]) - - def _stats_nrmse(self,df): - return self._stats_rmse(df) / (df.obsval.max() - df.obsval.min()) - - -
    [docs] def plot(self,kind=None,**kwargs): - """method to plot various parts of the control. Depending - on 'kind' argument, a multipage pdf is written - - Parameters - ---------- - kind : str - options are 'prior' (prior parameter histograms, '1to1' (line of equality - and sim vs res), 'obs_v_sim' (time series using datetime suffix), 'phi_pie' - (pie chart of phi components) - kwargs : dict - optional args for plots - - Returns - ------- - None - - - """ - return plot_utils.pst_helper(self,kind,**kwargs)
    - - - - -
    [docs] def write_par_summary_table(self,filename=None,group_names=None, - sigma_range = 4.0): - """write a stand alone parameter summary latex table - - - Parameters - ---------- - filename : str - latex filename. If None, use <case>.par.tex. Default is None - group_names: dict - par group names : table names for example {"w0":"well stress period 1"}. - Default is None - sigma_range : float - number of standard deviations represented by parameter bounds. Default - is 4.0, implying 95% confidence bounds - - Returns - ------- - None - """ - - ffmt = lambda x: "{0:5G}".format(x) - par = self.parameter_data.copy() - pargp = par.groupby(par.pargp).groups - #cols = ["parval1","parubnd","parlbnd","stdev","partrans","pargp"] - cols = ["pargp","partrans","count","parval1","parubnd","parlbnd","stdev"] - - labels = {"parval1":"initial value","parubnd":"upper bound", - "parlbnd":"lower bound","partrans":"transform", - "stdev":"standard deviation","pargp":"type","count":"count"} - - li = par.partrans == "log" - par.loc[li,"parval1"] = par.parval1.loc[li].apply(np.log10) - par.loc[li, "parubnd"] = par.parubnd.loc[li].apply(np.log10) - par.loc[li, "parlbnd"] = par.parlbnd.loc[li].apply(np.log10) - par.loc[:,"stdev"] = (par.parubnd - par.parlbnd) / sigma_range - - data = {c:[] for c in cols} - for pg,pnames in pargp.items(): - par_pg = par.loc[pnames,:] - data["pargp"].append(pg) - for col in cols: - if col in ["pargp","partrans"]: - continue - if col == "count": - data["count"].append(par_pg.shape[0]) - continue - #print(col) - mn = par_pg.loc[:,col].min() - mx = par_pg.loc[:,col].max() - if mn == mx: - data[col].append(ffmt(mn)) - else: - data[col].append("{0} to {1}".format(ffmt(mn),ffmt(mx))) - - pts = par_pg.partrans.unique() - if len(pts) == 1: - data["partrans"].append(pts[0]) - else: - data["partrans"].append("mixed") - - pargp_df = pd.DataFrame(data=data,index=list(pargp.keys())) - pargp_df = pargp_df.loc[:, cols] - if group_names is not None: - pargp_df.loc[:, "pargp"] = pargp_df.pargp.apply(lambda x: group_names.pop(x, x)) - pargp_df.columns = pargp_df.columns.map(lambda x: labels[x]) - - preamble = '\\documentclass{article}\n\\usepackage{booktabs}\n'+ \ - '\\usepackage{pdflscape}\n\\usepackage{longtable}\n' + \ - '\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n' - - if filename is None: - filename = self.filename.replace(".pst",".par.tex") - - with open(filename,'w') as f: - f.write(preamble) - f.write("\\begin{center}\nParameter Summary\n\\end{center}\n") - f.write("\\begin{center}\n\\begin{landscape}\n") - pargp_df.to_latex(f, index=False, longtable=True) - f.write("\\end{landscape}\n") - f.write("\\end{center}\n") - f.write("\\end{document}\n")
    - -
    [docs] def write_obs_summary_table(self,filename=None,group_names=None): - """write a stand alone observation summary latex table - - - Parameters - ---------- - filename : str - latex filename. If None, use <case>.par.tex. Default is None - group_names: dict - par group names : table names for example {"w0":"well stress period 1"}. - Default is None - - Returns - ------- - None - """ - - ffmt = lambda x: "{0:5G}".format(x) - obs = self.observation_data.copy() - obsgp = obs.groupby(obs.obgnme).groups - cols = ["obgnme","obsval","nzcount","zcount","weight","stdev","pe"] - - labels = {"obgnme":"group","obsval":"value","nzcount":"non-zero weight", - "zcount":"zero weight","weight":"weight","stdev":"standard deviation", - "pe":"percent error"} - - obs.loc[:,"stdev"] = 1.0 / obs.weight - obs.loc[:,"pe"] = 100.0 * (obs.stdev / obs.obsval.apply(np.abs)) - obs = obs.replace([np.inf,-np.inf],np.NaN) - - data = {c: [] for c in cols} - for og, onames in obsgp.items(): - obs_g = obs.loc[onames, :] - data["obgnme"].append(og) - data["nzcount"].append(obs_g.loc[obs_g.weight > 0.0,:].shape[0]) - data["zcount"].append(obs_g.loc[obs_g.weight == 0.0,:].shape[0]) - for col in cols: - if col in ["obgnme","nzcount","zcount"]: - continue - - #print(col) - mn = obs_g.loc[:, col].min() - mx = obs_g.loc[:, col].max() - if np.isnan(mn) or np.isnan(mx): - data[col].append("NA") - elif mn == mx: - data[col].append(ffmt(mn)) - else: - data[col].append("{0} to {1}".format(ffmt(mn), ffmt(mx))) - - - obsg_df = pd.DataFrame(data=data, index=list(obsgp.keys())) - obsg_df = obsg_df.loc[:, cols] - if group_names is not None: - obsg_df.loc[:, "obgnme"] = obsg_df.obgnme.apply(lambda x: group_names.pop(x, x)) - obsg_df.sort_values(by="obgnme",inplace=True,ascending=True) - obsg_df.columns = obsg_df.columns.map(lambda x: labels[x]) - - preamble = '\\documentclass{article}\n\\usepackage{booktabs}\n' + \ - '\\usepackage{pdflscape}\n\\usepackage{longtable}\n' + \ - '\\usepackage{booktabs}\n\\usepackage{nopageno}\n\\begin{document}\n' - - if filename is None: - filename = self.filename.replace(".pst", ".obs.tex") - - with open(filename, 'w') as f: - - f.write(preamble) - - f.write("\\begin{center}\nObservation Summary\n\\end{center}\n") - f.write("\\begin{center}\n\\begin{landscape}\n") - f.write("\\setlength{\\LTleft}{-4.0cm}\n") - obsg_df.to_latex(f, index=False, longtable=True) - f.write("\\end{landscape}\n") - f.write("\\end{center}\n") - f.write("\\end{document}\n")
    - - -
    [docs] def run(self,exe_name="pestpp",cwd=None): - """run a command related to the pst instance. If - write() has been called, then the filename passed to write - is in the command, otherwise the original constructor - filename is used - - exe_name : str - the name of the executable to call. Default is "pestpp" - cwd : str - the directory to execute the command in. If None, - os.path.split(self.filename) is used to find - cwd. Default is None - - - """ - filename = self.filename - if self.new_filename is not None: - filename = self.new_filename - cmd_line = "{0} {1}".format(exe_name,os.path.split(filename)[-1]) - if cwd is None: - cwd = os.path.join(*os.path.split(filename)[:-1]) - if cwd == '': - cwd = '.' - print("executing {0} in dir {1}".format(cmd_line, cwd)) - pyemu.utils.os_utils.run(cmd_line,cwd=cwd)
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/pst/pst_utils.html b/docs/_build/html/_modules/pyemu/pst/pst_utils.html deleted file mode 100644 index 2e6c1532f..000000000 --- a/docs/_build/html/_modules/pyemu/pst/pst_utils.html +++ /dev/null @@ -1,1010 +0,0 @@ - - - - - - - - pyemu.pst.pst_utils — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.pst.pst_utils

    -"""This module contains helpers and default values that support
    -the pyemu.Pst object.
    -"""
    -from __future__ import print_function, division
    -import os, sys
    -import stat
    -import warnings
    -from datetime import datetime
    -import numpy as np
    -import pandas as pd
    -pd.options.display.max_colwidth = 100
    -
    -import pyemu
    -from ..pyemu_warnings import PyemuWarning
    -#formatters
    -#SFMT = lambda x: "{0:>20s}".format(str(x.decode()))
    -
    [docs]def SFMT(item): - try: - s = "{0:<20s} ".format(item.decode()) - except: - s = "{0:<20s} ".format(str(item)) - return s
    - -SFMT_LONG = lambda x: "{0:<50s} ".format(str(x)) -IFMT = lambda x: "{0:<10d} ".format(int(x)) -FFMT = lambda x: "{0:<20.10E} ".format(float(x)) - -
    [docs]def str_con(item): - if len(item) == 0: - return np.NaN - return item.lower().strip()
    - -pst_config = {} - -# parameter stuff -pst_config["tied_dtype"] = np.dtype([("parnme", "U20"), ("partied","U20")]) -pst_config["tied_fieldnames"] = ["parnme","partied"] -pst_config["tied_format"] = {"parnme":SFMT,"partied":SFMT} -pst_config["tied_converters"] = {"parnme":str_con,"partied":str_con} -pst_config["tied_defaults"] = {"parnme":"dum","partied":"dum"} - -pst_config["par_dtype"] = np.dtype([("parnme", "U20"), ("partrans","U20"), - ("parchglim","U20"),("parval1", np.float64), - ("parlbnd",np.float64),("parubnd",np.float64), - ("pargp","U20"),("scale", np.float64), - ("offset", np.float64),("dercom",np.int)]) -pst_config["par_fieldnames"] = "PARNME PARTRANS PARCHGLIM PARVAL1 PARLBND PARUBND " +\ - "PARGP SCALE OFFSET DERCOM" -pst_config["par_fieldnames"] = pst_config["par_fieldnames"].lower().strip().split() -pst_config["par_format"] = {"parnme": SFMT, "partrans": SFMT, - "parchglim": SFMT, "parval1": FFMT, - "parlbnd": FFMT, "parubnd": FFMT, - "pargp": SFMT, "scale": FFMT, - "offset": FFMT, "dercom": IFMT} -pst_config["par_converters"] = {"parnme": str_con, "pargp": str_con, - "parval1":np.float,"parubnd":np.float, - "parlbnd":np.float,"scale":np.float, - "offset":np.float} -pst_config["par_defaults"] = {"parnme":"dum","partrans":"log","parchglim":"factor", - "parval1":1.0,"parlbnd":1.1e-10,"parubnd":1.1e+10, - "pargp":"pargp","scale":1.0,"offset":0.0,"dercom":1} - - -# parameter group stuff -pst_config["pargp_dtype"] = np.dtype([("pargpnme", "U20"), ("inctyp","U20"), - ("derinc", np.float64), - ("derinclb",np.float64),("forcen","U20"), - ("derincmul",np.float64),("dermthd", "U20"), - ("splitthresh", np.float64),("splitreldiff",np.float64), - ("splitaction","U20")]) -pst_config["pargp_fieldnames"] = "PARGPNME INCTYP DERINC DERINCLB FORCEN DERINCMUL " +\ - "DERMTHD SPLITTHRESH SPLITRELDIFF SPLITACTION" -pst_config["pargp_fieldnames"] = pst_config["pargp_fieldnames"].lower().strip().split() - -pst_config["pargp_format"] = {"pargpnme":SFMT,"inctyp":SFMT,"derinc":FFMT,"forcen":SFMT, - "derincmul":FFMT,"dermthd":SFMT,"splitthresh":FFMT, - "splitreldiff":FFMT,"splitaction":SFMT} - -pst_config["pargp_converters"] = {"pargpnme":str_con,"inctype":str_con, - "dermethd":str_con, - "splitaction":str_con} -pst_config["pargp_defaults"] = {"pargpnme":"pargp","inctyp":"relative","derinc":0.01, - "derinclb":0.0,"forcen":"switch","derincmul":2.0, - "dermthd":"parabolic","splitthresh":1.0e-5, - "splitreldiff":0.5,"splitaction":"smaller"} - - -# observation stuff -pst_config["obs_fieldnames"] = "OBSNME OBSVAL WEIGHT OBGNME".lower().split() -pst_config["obs_dtype"] = np.dtype([("obsnme","U20"),("obsval",np.float64), - ("weight",np.float64),("obgnme","U20")]) -pst_config["obs_format"] = {"obsnme": SFMT, "obsval": FFMT, - "weight": FFMT, "obgnme": SFMT} -pst_config["obs_converters"] = {"obsnme": str_con, "obgnme": str_con, - "weight":np.float,"obsval":np.float} -pst_config["obs_defaults"] = {"obsnme":"dum","obsval":1.0e+10, - "weight":1.0,"obgnme":"obgnme"} - - -# prior info stuff -pst_config["null_prior"] = pd.DataFrame({"pilbl": None, - "obgnme": None}, index=[]) -pst_config["prior_format"] = {"pilbl": SFMT, "equation": SFMT_LONG, - "weight": FFMT, "obgnme": SFMT} -pst_config["prior_fieldnames"] = ["pilbl","equation", "weight", "obgnme"] - - -# other containers -pst_config["model_command"] = [] -pst_config["template_files"] = [] -pst_config["input_files"] = [] -pst_config["instruction_files"] = [] -pst_config["output_files"] = [] -pst_config["other_lines"] = [] -pst_config["tied_lines"] = [] -pst_config["regul_lines"] = [] -pst_config["pestpp_options"] = {} - - -
    [docs]def read_resfile(resfile): - """load a residual file into a pandas.DataFrame - - Parameters - ---------- - resfile : str - residual file name - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - - """ - assert os.path.exists(resfile),"read_resfile() error: resfile " +\ - "{0} not found".format(resfile) - converters = {"name": str_con, "group": str_con} - f = open(resfile, 'r') - while True: - line = f.readline() - if line == '': - raise Exception("Pst.get_residuals: EOF before finding "+ - "header in resfile: " + resfile) - if "name" in line.lower(): - header = line.lower().strip().split() - break - res_df = pd.read_csv(f, header=None, names=header, sep="\s+", - converters=converters) - res_df.index = res_df.name - f.close() - return res_df
    - - -
    [docs]def read_parfile(parfile): - """load a pest-compatible .par file into a pandas.DataFrame - - Parameters - ---------- - parfile : str - pest parameter file name - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - - """ - assert os.path.exists(parfile), "Pst.parrep(): parfile not found: " +\ - str(parfile) - f = open(parfile, 'r') - header = f.readline() - par_df = pd.read_csv(f, header=None, - names=["parnme", "parval1", "scale", "offset"], - sep="\s+") - par_df.index = par_df.parnme - return par_df
    - -
    [docs]def write_parfile(df,parfile): - """ write a pest parameter file from a dataframe - - Parameters - ---------- - df : (pandas.DataFrame) - dataframe with column names that correspond to the entries - in the parameter data section of a pest control file - parfile : str - name of the parameter file to write - - """ - columns = ["parnme","parval1","scale","offset"] - formatters = {"parnme":lambda x:"{0:20s}".format(x), - "parval1":lambda x:"{0:20.7E}".format(x), - "scale":lambda x:"{0:20.7E}".format(x), - "offset":lambda x:"{0:20.7E}".format(x)} - - for col in columns: - assert col in df.columns,"write_parfile() error: " +\ - "{0} not found in df".format(col) - with open(parfile,'w') as f: - f.write("single point\n") - f.write(df.to_string(col_space=0, - columns=columns, - formatters=formatters, - justify="right", - header=False, - index=False, - index_names=False) + '\n')
    - -
    [docs]def parse_tpl_file(tpl_file): - """ parse a pest template file to get the parameter names - - Parameters - ---------- - tpl_file : str - template file name - - Returns - ------- - par_names : list - list of parameter names - - """ - par_names = set() - with open(tpl_file,'r') as f: - try: - header = f.readline().strip().split() - assert header[0].lower() in ["ptf","jtf"],\ - "template file error: must start with [ptf,jtf], not:" +\ - str(header[0]) - assert len(header) == 2,\ - "template file error: header line must have two entries: " +\ - str(header) - - marker = header[1] - assert len(marker) == 1,\ - "template file error: marker must be a single character, not:" +\ - str(marker) - for line in f: - par_line = set(line.lower().strip().split(marker)[1::2]) - par_names.update(par_line) - #par_names.extend(par_line) - #for p in par_line: - # if p not in par_names: - # par_names.append(p) - except Exception as e: - raise Exception("error processing template file " +\ - tpl_file+" :\n" + str(e)) - #par_names = [pn.strip().lower() for pn in par_names] - #seen = set() - #seen_add = seen.add - #return [x for x in par_names if not (x in seen or seen_add(x))] - return [p.strip() for p in list(par_names)]
    - - -
    [docs]def write_input_files(pst): - """write parameter values to a model input files using a template files with - current parameter values (stored in Pst.parameter_data.parval1). - This is a simple implementation of what PEST does. It does not - handle all the special cases, just a basic function...user beware - - Parameters - ---------- - pst : (pyemu.Pst) - a Pst instance - - """ - par = pst.parameter_data - par.loc[:,"parval1_trans"] = (par.parval1 * par.scale) + par.offset - for tpl_file,in_file in zip(pst.template_files,pst.input_files): - write_to_template(pst.parameter_data.parval1_trans,tpl_file,in_file)
    - -
    [docs]def write_to_template(parvals,tpl_file,in_file): - """ write parameter values to model input files using template files - - Parameters - ---------- - parvals : dict or pandas.Series - a way to look up parameter values using parameter names - tpl_file : str - template file - in_file : str - input file - - """ - f_in = open(in_file,'w') - f_tpl = open(tpl_file,'r') - header = f_tpl.readline().strip().split() - assert header[0].lower() in ["ptf", "jtf"], \ - "template file error: must start with [ptf,jtf], not:" + \ - str(header[0]) - assert len(header) == 2, \ - "template file error: header line must have two entries: " + \ - str(header) - - marker = header[1] - assert len(marker) == 1, \ - "template file error: marker must be a single character, not:" + \ - str(marker) - for line in f_tpl: - if marker not in line: - f_in.write(line) - else: - line = line.rstrip() - par_names = line.lower().split(marker)[1::2] - par_names = [name.strip() for name in par_names] - start,end = get_marker_indices(marker,line) - assert len(par_names) == len(start) - new_line = line[:start[0]] - between = [line[e:s] for s,e in zip(start[1:],end[:-1])] - for i,name in enumerate(par_names): - s,e = start[i],end[i] - w = e - s - if w > 15: - d = 6 - else: - d = 3 - fmt = "{0:" + str(w)+"."+str(d)+"E}" - val_str = fmt.format(parvals[name]) - new_line += val_str - if i != len(par_names) - 1: - new_line += between[i] - new_line += line[end[-1]:] - f_in.write(new_line+'\n') - f_tpl.close() - f_in.close()
    - - -
    [docs]def get_marker_indices(marker,line): - """ method to find the start and end parameter markers - on a template file line. Used by write_to_template() - - Parameters - ---------- - marker : str - template file marker char - line : str - template file line - - Returns - ------- - indices : list - list of start and end indices (zero based) - - """ - indices = [i for i, ltr in enumerate(line) if ltr == marker] - start = indices[0:-1:2] - end = [i+1 for i in indices[1::2]] - assert len(start) == len(end) - return start,end
    - - -
    [docs]def parse_ins_file(ins_file): - """parse a pest instruction file to get observation names - - Parameters - ---------- - ins_file : str - instruction file name - - Returns - ------- - list of observation names - - """ - - obs_names = [] - with open(ins_file,'r') as f: - header = f.readline().strip().split() - assert header[0].lower() in ["pif","jif"],\ - "instruction file error: must start with [pif,jif], not:" +\ - str(header[0]) - marker = header[1] - assert len(marker) == 1,\ - "instruction file error: marker must be a single character, not:" +\ - str(marker) - for line in f: - line = line.lower() - if marker in line: - raw = line.lower().strip().split(marker) - for item in raw[::2]: - obs_names.extend(parse_ins_string(item)) - else: - obs_names.extend(parse_ins_string(line.strip())) - #obs_names = [on.strip().lower() for on in obs_names] - return obs_names
    - - -
    [docs]def parse_ins_string(string): - """ split up an instruction file line to get the observation names - - Parameters - ---------- - string : str - instruction file line - - Returns - ------- - obs_names : list - list of observation names - - """ - istart_markers = ["[","(","!"] - iend_markers = ["]",")","!"] - - obs_names = [] - - idx = 0 - while True: - if idx >= len(string) - 1: - break - char = string[idx] - if char in istart_markers: - em = iend_markers[istart_markers.index(char)] - # print("\n",idx) - # print(string) - # print(string[idx+1:]) - # print(string[idx+1:].index(em)) - # print(string[idx+1:].index(em)+idx+1) - eidx = min(len(string),string[idx+1:].index(em)+idx+1) - obs_name = string[idx+1:eidx] - if obs_name.lower() != "dum": - obs_names.append(obs_name) - idx = eidx + 1 - else: - idx += 1 - return obs_names
    - - -
    [docs]def populate_dataframe(index,columns, default_dict, dtype): - """ helper function to populate a generic Pst dataframe attribute. This - function is called as part of constructing a generic Pst instance - - Parameters - ---------- - index : (varies) - something to use as the dataframe index - columns: (varies) - something to use as the dataframe columns - default_dict : (dict) - dictionary of default values for columns - dtype : numpy.dtype - dtype used to cast dataframe columns - - Returns - ------- - new_df : pandas.DataFrame - - """ - new_df = pd.DataFrame(index=index,columns=columns) - for fieldname,dt in zip(columns,dtype.descr): - default = default_dict[fieldname] - new_df.loc[:,fieldname] = default - new_df.loc[:,fieldname] = new_df.loc[:,fieldname].astype(dt[1]) - return new_df
    - - -
    [docs]def generic_pst(par_names=["par1"],obs_names=["obs1"],addreg=False): - """generate a generic pst instance. This can used to later fill in - the Pst parts programatically. - - Parameters - ---------- - par_names : (list) - parameter names to setup - obs_names : (list) - observation names to setup - - Returns - ------- - new_pst : pyemu.Pst - - """ - if not isinstance(par_names,list): - par_names = list(par_names) - if not isinstance(obs_names,list): - obs_names = list(obs_names) - new_pst = pyemu.Pst("pest.pst",load=False) - pargp_data = populate_dataframe(["pargp"], new_pst.pargp_fieldnames, - new_pst.pargp_defaults, new_pst.pargp_dtype) - new_pst.parameter_groups = pargp_data - - par_data = populate_dataframe(par_names,new_pst.par_fieldnames, - new_pst.par_defaults,new_pst.par_dtype) - par_data.loc[:,"parnme"] = par_names - par_data.index = par_names - par_data.sort_index(inplace=True) - new_pst.parameter_data = par_data - obs_data = populate_dataframe(obs_names,new_pst.obs_fieldnames, - new_pst.obs_defaults,new_pst.obs_dtype) - obs_data.loc[:,"obsnme"] = obs_names - obs_data.index = obs_names - obs_data.sort_index(inplace=True) - new_pst.observation_data = obs_data - - new_pst.template_files = ["file.tpl"] - new_pst.input_files = ["file.in"] - new_pst.instruction_files = ["file.ins"] - new_pst.output_files = ["file.out"] - new_pst.model_command = ["model.bat"] - - new_pst.prior_information = new_pst.null_prior - - #new_pst.other_lines = ["* singular value decomposition\n","1\n", - # "{0:d} {1:15.6E}\n".format(new_pst.npar_adj,1.0E-6), - # "1 1 1\n"] - if addreg: - new_pst.zero_order_tikhonov() - - return new_pst
    - - -
    [docs]def pst_from_io_files(tpl_files,in_files,ins_files,out_files,pst_filename=None): - """ generate a new pyemu.Pst instance from model interface files. This - function is emulated in the Pst.from_io_files() class method. - - Parameters - ---------- - tpl_files : (list) - template file names - in_files : (list) - model input file names - ins_files : (list) - instruction file names - out_files : (list) - model output file names - pst_filename : str - filename to save new pyemu.Pst. If None, Pst is not written. - default is None - - Returns - ------- - new_pst : pyemu.Pst - - """ - - warnings.warn("pst_from_io_files has moved to pyemu.helpers and is also "+\ - "now avaiable as a Pst class method (Pst.from_io_files())",PyemuWarning) - from pyemu import helpers - return helpers.pst_from_io_files(tpl_files=tpl_files,in_files=in_files, - ins_files=ins_files,out_files=out_files, - pst_filename=pst_filename)
    - - -
    [docs]def try_run_inschek(pst): - """ attempt to run INSCHEK for each instruction file, model output - file pair in a pyemu.Pst. If the run is successful, the INSCHEK written - .obf file is used to populate the pst.observation_data.obsval attribute - - Parameters - ---------- - pst : (pyemu.Pst) - - """ - for ins_file,out_file in zip(pst.instruction_files,pst.output_files): - df = _try_run_inschek(ins_file,out_file) - if df is not None: - pst.observation_data.loc[df.index, "obsval"] = df.obsval
    - - -
    [docs]def try_process_ins_file(ins_file,out_file=None): - assert os.path.exists(ins_file),"instruction file {0} not found".format(ins_file) - try: - obs_names = parse_ins_file(ins_file) - except Exception as e: - raise Exception("error parsing ins file {0} for obs names: {1}".format(ins_file,str(e))) - - if out_file is None: - out_file = ins_file.replace(".ins","") - if not os.path.exists(out_file): - print("out file {0} not found".format(out_file)) - return pd.DataFrame({"obsnme":obs_names},index="obsnme") - try: - f_ins = open(ins_file,'r') - f_out = open(out_file,'r') - - header = f_ins.readline().lower().strip().split() - assert header[0] == "pif" - marker = header[1] - icount,ocount = 1,0 - obsnme,obsval = [],[] - for iline in f_ins: - iraw = iline.lower().strip().split() - if iraw[0][0] != 'l': - raise Exception("not support instruction:{0}".format(iraw[0])) - num_lines = int(iraw[0][1:]) - for i in range(num_lines): - oline = f_out.readline().lower().strip() - ocount += 1 - if '(' in oline: - raise Exception("semi-fixed obs index instructions not supported") - elif '[' in oline: - raise Exception("fixed obs index instructions not supported") - elif marker in oline: - raise Exception("marker-based file seeking not supported") - oraw = oline.strip().split() - if oline[0] in [' ',' ']: - oraw.insert(0,'') - oc = 0 - for ir in iraw[1:]: - if ir == 'w': - oc += 1 - if oc > len(oraw): - raise Exception("out file line {0} too short".format(ocount)) - elif '!' in ir: - n = ir.replace('!','') - try: - v = float(oraw[oc]) - except Exception as e: - raise Exception("error processing ins {0} for obs {1}, string: {2} on line {3} (ins line {4}):{5}". - format(ir,n,oline,ocount,icount,str(e))) - obsnme.append(n) - obsval.append(v) - oc += 1 - - f_ins.close() - f_out.close() - df = pd.DataFrame({"obsnme":obsnme,"obsval":obsval},index=obsnme) - - return df - except Exception as e: - print("error processing ins file {0}: {1}".format(ins_file,str(e))) - return pd.DataFrame({"obsnme":obs_names},index=obs_names)
    - - - -def _try_run_inschek(ins_file,out_file): - - try: - # os.system("inschek {0} {1}".format(ins_file,out_file)) - pyemu.helpers.run("inschek {0} {1}".format(ins_file, out_file)) - obf_file = ins_file.replace(".ins", ".obf") - df = pd.read_csv(obf_file, delim_whitespace=True, - skiprows=0, index_col=0, names=["obsval"]) - df.index = df.index.map(str.lower) - return df - except Exception as e: - print("error using inschek for instruction file {0}:{1}". - format(ins_file, str(e))) - print("observations in this instruction file will have" + - "generic values.") - return None - - -
    [docs]def get_phi_comps_from_recfile(recfile): - """read the phi components from a record file by iteration - - Parameters - ---------- - recfile : str - pest record file name - - Returns - ------- - iters : dict - nested dictionary of iteration number, {group,contribution} - - """ - iiter = 1 - iters = {} - f = open(recfile,'r') - while True: - line = f.readline() - if line == '': - break - if "starting phi for this iteration" in line.lower() or \ - "final phi" in line.lower(): - contributions = {} - while True: - line = f.readline() - if line == '': - break - if "contribution to phi" not in line.lower(): - iters[iiter] = contributions - iiter += 1 - break - raw = line.strip().split() - val = float(raw[-1]) - group = raw[-3].lower().replace('\"', '') - contributions[group] = val - return iters
    - -
    [docs]def smp_to_ins(smp_filename,ins_filename=None,use_generic_names=False, - gwutils_compliant=False, datetime_format=None,prefix=''): - """ create an instruction file for an smp file - - Parameters - ---------- - smp_filename : str - existing smp file - ins_filename: str - instruction file to create. If None, create - an instruction file using the smp filename - with the ".ins" suffix - use_generic_names : bool - flag to force observations names to use a generic - int counter instead of trying to use a datetime str - gwutils_compliant : bool - flag to use instruction set that is compliant with the - pest gw utils (fixed format instructions). If false, - use free format (with whitespace) instruction set - datetime_format : str - str to pass to datetime.strptime in the smp_to_dataframe() function - prefix : str - a prefix to add to the front of the obsnmes. Default is '' - - - Returns - ------- - df : pandas.DataFrame - dataframe instance of the smp file with the observation names and - instruction lines as additional columns - - """ - if ins_filename is None: - ins_filename = smp_filename+".ins" - df = smp_to_dataframe(smp_filename,datetime_format=datetime_format) - df.loc[:,"ins_strings"] = None - df.loc[:,"observation_names"] = None - name_groups = df.groupby("name").groups - for name,idxs in name_groups.items(): - if not use_generic_names and len(name) <= 11: - onames = df.loc[idxs,"datetime"].apply(lambda x: prefix+name+'_'+x.strftime("%d%m%Y")).values - else: - onames = [prefix+name+"_{0:d}".format(i) for i in range(len(idxs))] - if False in (map(lambda x :len(x) <= 20,onames)): - long_names = [oname for oname in onames if len(oname) > 20] - raise Exception("observation names longer than 20 chars:\n{0}".format(str(long_names))) - if gwutils_compliant: - ins_strs = ["l1 ({0:s})39:46".format(on) for on in onames] - else: - ins_strs = ["l1 w w w !{0:s}!".format(on) for on in onames] - df.loc[idxs,"observation_names"] = onames - df.loc[idxs,"ins_strings"] = ins_strs - - counts = df.observation_names.value_counts() - dup_sites = [name for name in counts.index if counts[name] > 1] - if len(dup_sites) > 0: - raise Exception("duplicate observation names found:{0}"\ - .format(','.join(dup_sites))) - - with open(ins_filename,'w') as f: - f.write("pif ~\n") - [f.write(ins_str+"\n") for ins_str in df.loc[:,"ins_strings"]] - return df
    - - -
    [docs]def dataframe_to_smp(dataframe,smp_filename,name_col="name", - datetime_col="datetime",value_col="value", - datetime_format="dd/mm/yyyy", - value_format="{0:15.6E}", - max_name_len=12): - """ write a dataframe as an smp file - - Parameters - ---------- - dataframe : pandas.DataFrame - smp_filename : str - smp file to write - name_col: str - the column in the dataframe the marks the site namne - datetime_col: str - the column in the dataframe that is a datetime instance - value_col: str - the column in the dataframe that is the values - datetime_format: str - either 'dd/mm/yyyy' or 'mm/dd/yyy' - value_format: str - a python float-compatible format - - """ - formatters = {"name":lambda x:"{0:<20s}".format(str(x)[:max_name_len]), - "value":lambda x:value_format.format(x)} - if datetime_format.lower().startswith("d"): - dt_fmt = "%d/%m/%Y %H:%M:%S" - elif datetime_format.lower().startswith("m"): - dt_fmt = "%m/%d/%Y %H:%M:%S" - else: - raise Exception("unrecognized datetime_format: " +\ - "{0}".format(str(datetime_format))) - - for col in [name_col,datetime_col,value_col]: - assert col in dataframe.columns - - dataframe.loc[:,"datetime_str"] = dataframe.loc[:,"datetime"].\ - apply(lambda x:x.strftime(dt_fmt)) - if isinstance(smp_filename,str): - smp_filename = open(smp_filename,'w') - # need this to remove the leading space that pandas puts in front - s = dataframe.loc[:,[name_col,"datetime_str",value_col]].\ - to_string(col_space=0, - formatters=formatters, - justify=None, - header=False, - index=False) - for ss in s.split('\n'): - smp_filename.write("{0:<s}\n".format(ss.strip())) - dataframe.pop("datetime_str")
    - - -
    [docs]def date_parser(items): - """ datetime parser to help load smp files - - Parameters - ---------- - items : iterable - something or somethings to try to parse into datetimes - - Returns - ------- - dt : iterable - the cast datetime things - """ - try: - dt = datetime.strptime(items,"%d/%m/%Y %H:%M:%S") - except Exception as e: - try: - dt = datetime.strptime(items,"%m/%d/%Y %H:%M:%S") - except Exception as ee: - raise Exception("error parsing datetime string" +\ - " {0}: \n{1}\n{2}".format(str(items),str(e),str(ee))) - return dt
    - - -
    [docs]def smp_to_dataframe(smp_filename,datetime_format=None): - """ load an smp file into a pandas dataframe (stacked in wide format) - - Parameters - ---------- - smp_filename : str - smp filename to load - datetime_format : str - should be either "%m/%d/%Y %H:%M:%S" or "%d/%m/%Y %H:%M:%S" - If None, then we will try to deduce the format for you, which - always dangerous - - Returns - ------- - df : pandas.DataFrame - - """ - - if datetime_format is not None: - date_func = lambda x: datetime.strptime(x,datetime_format) - else: - date_func = date_parser - df = pd.read_csv(smp_filename, delim_whitespace=True, - parse_dates={"datetime":["date","time"]}, - header=None,names=["name","date","time","value"], - dtype={"name":object,"value":np.float64}, - na_values=["dry"], - date_parser=date_func) - return df
    - -
    [docs]def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name)
    - -
    [docs]def start_slaves(slave_dir,exe_rel_path,pst_rel_path,num_slaves=None,slave_root="..", - port=4004,rel_path=None): - - - warnings.warn("deprecation warning:start_slaves() has moved to the utils.helpers module",PyemuWarning) - from pyemu.utils import start_slaves - start_slaves(slave_dir,exe_rel_path,pst_rel_path,num_slaves=num_slaves,slave_root=slave_root, - port=port,rel_path=rel_path)
    - -
    [docs]def res_from_obseravtion_data(observation_data): - """create a generic residual dataframe filled with np.NaN for - missing information - - Parameters - ---------- - observation_data : pandas.DataFrame - pyemu.Pst.observation_data - - Returns - ------- - res_df : pandas.DataFrame - - """ - res_df = observation_data.copy() - res_df.loc[:, "name"] = res_df.pop("obsnme") - res_df.loc[:, "measured"] = res_df.pop("obsval") - res_df.loc[:, "group"] = res_df.pop("obgnme") - res_df.loc[:, "modelled"] = np.NaN - res_df.loc[:, "residual"] = np.NaN - return res_df
    - -
    [docs]def clean_missing_exponent(pst_filename,clean_filename="clean.pst"): - """fixes the issue where some terrible fortran program may have - written a floating point format without the 'e' - like 1.0-3, really?! - - Parameters - ---------- - pst_filename : str - the pest control file - clean_filename : str - the new pest control file to write. Default is "clean.pst" - - Returns - ------- - None - - - """ - lines = [] - with open(pst_filename,'r') as f: - for line in f: - line = line.lower().strip() - if '+' in line: - raw = line.split('+') - for i,r in enumerate(raw[:-1]): - if r[-1] != 'e': - r = r + 'e' - raw[i] = r - lines.append('+'.join(raw)) - else: - lines.append(line) - with open(clean_filename,'w') as f: - for line in lines: - f.write(line+'\n')
    - - - - - - - - - - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/sc.html b/docs/_build/html/_modules/pyemu/sc.html deleted file mode 100644 index 51e48a8c8..000000000 --- a/docs/_build/html/_modules/pyemu/sc.html +++ /dev/null @@ -1,1093 +0,0 @@ - - - - - - - - pyemu.sc — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.sc

    -"""module for FOSM-based uncertainty analysis using a
    -linearized form of Bayes equation known as the Schur compliment
    -"""
    -
    -from __future__ import print_function, division
    -import numpy as np
    -import pandas as pd
    -from pyemu.la import LinearAnalysis
    -from pyemu.mat import Cov, Matrix
    -
    -
    [docs]class Schur(LinearAnalysis): - """derived type for prior and posterior uncertainty and data-worth - analysis using Schur compliment - - Parameters - ---------- - **kwargs : dict - keyword arguments to pass to the LinearAnalysis constructor. See - LinearAnalysis definition for argument types - - Note - ---- - Same call signature as the base LinearAnalysis class - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - """ - def __init__(self,jco,**kwargs): - self.__posterior_prediction = None - self.__posterior_parameter = None - super(Schur,self).__init__(jco,**kwargs) - - - @property - def pandas(self): - """get a pandas dataframe of prior and posterior for all predictions - - Returns: - pandas.DataFrame : pandas.DataFrame - a dataframe with prior and posterior uncertainty estimates - for all forecasts (predictions) - """ - names,prior,posterior = [],[],[] - for iname,name in enumerate(self.posterior_parameter.row_names): - names.append(name) - posterior.append(np.sqrt(float( - self.posterior_parameter[iname, iname]. x))) - iprior = self.parcov.row_names.index(name) - prior.append(np.sqrt(float(self.parcov[iprior, iprior].x))) - for pred_name, pred_var in self.posterior_prediction.items(): - names.append(pred_name) - posterior.append(np.sqrt(pred_var)) - prior.append(self.prior_prediction[pred_name]) - return pd.DataFrame({"posterior": posterior, "prior": prior}, - index=names) - - @property - def posterior_parameter(self): - """get the posterior parameter covariance matrix. If Schur.__posterior_parameter - is None, the posterior parameter covariance matrix is calculated via - Schur compliment before returning - - Returns - ------- - posterior_parameter : pyemu.Cov - the posterior parameter covariance matrix - - Note - ---- - returns a reference - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``>>>post_cov = sc.posterior_parameter`` - - ``>>>post_cov.to_ascii("post.cov")`` - - - """ - if self.__posterior_parameter is not None: - return self.__posterior_parameter - else: - self.clean() - self.log("Schur's complement") - try: - pinv = self.parcov.inv - r = self.xtqx + pinv - r = r.inv - except Exception as e: - self.xtqx.to_binary("xtqx.err.jcb") - pinv.to_ascii("parcov_inv.err.cov") - self.logger.warn("error forming schur's complement: {0}". - format(str(e))) - self.logger.warn("problemtic xtqx saved to xtqx.err.jcb") - self.logger.warn("problematic inverse parcov saved to parcov_inv.err.cov") - raise Exception("error forming schur's complement: {0}". - format(str(e))) - assert r.row_names == r.col_names - self.__posterior_parameter = Cov(r.x, row_names=r.row_names, - col_names=r.col_names) - self.log("Schur's complement") - return self.__posterior_parameter - - @property - def map_parameter_estimate(self): - """ get the posterior expectation for parameters using Bayes linear - estimation - - Returns - ------- - post_expt : pandas.DataFrame - a dataframe with prior and posterior parameter expectations - - """ - res = self.pst.res - assert res is not None - # build the prior expectation parameter vector - prior_expt = self.pst.parameter_data.loc[:,["parval1"]].copy() - islog = self.pst.parameter_data.partrans == "log" - prior_expt.loc[islog] = prior_expt.loc[islog].apply(np.log10) - prior_expt = Matrix.from_dataframe(prior_expt) - prior_expt.col_names = ["prior_expt"] - # build the residual vector - res_vec = Matrix.from_dataframe(res.loc[:,["residual"]]) - - # form the terms of Schur's complement - b = self.parcov * self.jco.T - c = ((self.jco * self.parcov * self.jco.T) + self.obscov).inv - bc = Matrix((b * c).x, row_names=b.row_names, col_names=c.col_names) - - # calc posterior expectation - upgrade = bc * res_vec - upgrade.col_names = ["prior_expt"] - post_expt = prior_expt + upgrade - - # post processing - back log transform - post_expt = pd.DataFrame(data=post_expt.x,index=post_expt.row_names, - columns=["post_expt"]) - post_expt.loc[:,"prior_expt"] = prior_expt.x.flatten() - post_expt.loc[islog,:] = 10.0**post_expt.loc[islog,:] - # unc_sum = self.get_parameter_summary() - # post_expt.loc[:,"standard_deviation"] = unc_sum.post_var.apply(np.sqrt) - post_expt.sort_index(inplace=True) - return post_expt - - @property - def map_forecast_estimate(self): - """ get the prior and posterior forecast (prediction) expectations. - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - dataframe with prior and posterior forecast expected values - - """ - assert self.forecasts is not None - islog = self.pst.parameter_data.partrans == "log" - par_map = self.map_parameter_estimate - par_map.loc[islog,:] = np.log10(par_map.loc[islog,:]) - par_map = Matrix.from_dataframe(par_map.loc[:,["post_expt"]]) - posts,priors = [],[] - post_expt = (self.predictions.T * par_map).to_dataframe() - for fname in self.forecast_names: - #fname = forecast.col_names[0] - pr = self.pst.res.loc[fname,"modelled"] - priors.append(pr) - posts.append(pr + post_expt.loc[fname,"post_expt"]) - return pd.DataFrame(data=np.array([priors,posts]).transpose(), - columns=["prior_expt","post_expt"], - index=self.forecast_names) - - @property - def posterior_forecast(self): - """thin wrapper around posterior_prediction - """ - return self.posterior_prediction - - @property - def posterior_prediction(self): - """get posterior forecast (prediction) variances - - Returns - ------- - dict : dict - a dictionary of forecast names, posterior variance pairs - - Note - ---- - This method is not as easy to use as Schur.get_forecast_summary(), please - use it instead - - """ - if self.__posterior_prediction is not None: - return self.__posterior_prediction - else: - if self.predictions is not None: - self.log("propagating posterior to predictions") - - post_cov = self.predictions.T *\ - self.posterior_parameter * self.predictions - self.__posterior_prediction = {n:v for n,v in - zip(post_cov.row_names, - np.diag(post_cov.x))} - self.log("propagating posterior to predictions") - else: - self.__posterior_prediction = {} - return self.__posterior_prediction - -
    [docs] def get_parameter_summary(self,include_map=False): - """get a summary of the parameter uncertainty - - Parameters - ---------- - include_map : bool - if True, add the prior and posterior expectations - and report standard deviation instead of variance - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - dataframe of prior,posterior variances and percent - uncertainty reduction of each parameter - - Note - ---- - this is the primary method for accessing parameter uncertainty - estimates - use this! - - Example - ------- - ``>>>import matplotlib.pyplot as plt`` - - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb",forecasts=["fore1","fore2"])`` - - ``>>>par_sum = sc.get_parameter_summary()`` - - ``>>>par_sum.plot(kind="bar")`` - - ``>>>plt.show()`` - - """ - prior_mat = self.parcov.get(self.posterior_parameter.col_names) - if prior_mat.isdiagonal: - prior = prior_mat.x.flatten() - else: - prior = np.diag(prior_mat.x) - post = np.diag(self.posterior_parameter.x) - if include_map: - par_data = self.map_parameter_estimate - prior = pd.DataFrame(data=prior,index=prior_mat.col_names) - islog = self.pst.parameter_data.partrans == "log" - par_data.loc[islog,:] = np.log10(par_data.loc[islog,:]) - par_data.loc[:,"prior_stdev"] = prior - post = pd.DataFrame(data=post,index=prior.index) - par_data.loc[:,"post_stdev"] = post - par_data.loc[:,"is_log"] = islog - return par_data - else: - ureduce = 100.0 * (1.0 - (post / prior)) - - return pd.DataFrame({"prior_var":prior,"post_var":post, - "percent_reduction":ureduce}, - index=self.posterior_parameter.col_names)
    - -
    [docs] def get_forecast_summary(self, include_map=False): - """get a summary of the forecast uncertainty - - Parameters - ---------- - include_map : bool - if True, add the prior and posterior expectations - and report standard deviation instead of variance - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - dataframe of prior,posterior variances and percent - uncertainty reduction of each parameter - - Note - ---- - this is the primary method for accessing forecast uncertainty - estimates - use this! - - Example - ------- - ``>>>import matplotlib.pyplot as plt`` - - ``>>>import pyemu`` - - This usage assumes you have set the ``++forecasts()`` argument in the - control file: - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - or, you can pass the forecasts directly, assuming the forecasts are - names of zero-weight observations: - - ``>>>sc = pyemu.Schur(jco="pest.jcb",forecasts=["fore1","fore2"])`` - - ``>>>fore_sum = sc.get_forecast_summary()`` - - ``>>>fore_sum.plot(kind="bar")`` - - ``>>>plt.show()`` - - """ - sum = {"prior_var":[], "post_var":[], "percent_reduction":[]} - for forecast in self.prior_forecast.keys(): - pr = self.prior_forecast[forecast] - pt = self.posterior_forecast[forecast] - ur = 100.0 * (1.0 - (pt/pr)) - sum["prior_var"].append(pr) - sum["post_var"].append(pt) - sum["percent_reduction"].append(ur) - df = pd.DataFrame(sum,index=self.prior_forecast.keys()) - - if include_map: - df.loc[:,"prior_stdev"] = df.pop("prior_var").apply(np.sqrt) - df.loc[:,"post_stdev"] = df.pop("post_var").apply(np.sqrt) - df.pop("percent_reduction") - forecast_map = self.map_forecast_estimate - df.loc[:,"prior_expt"] = forecast_map.prior_expt - df.loc[:,"post_expt"] = forecast_map.post_expt - return df - return pd.DataFrame(sum,index=self.prior_forecast.keys())
    - - def __contribution_from_parameters(self, parameter_names): - """private method get the prior and posterior uncertainty reduction as a result of - some parameter becoming perfectly known - - Parameters - ---------- - parameter_names : list - parameter that are perfectly known - - Returns - ------- - dict : dict - dictionary of forecast name, [prior uncertainty w/o parameter_names, - % posterior uncertainty w/o parameter names] - - Note - ---- - this method is used by get_parameter_contribution() method - don't - call this method directly - - """ - - - #get the prior and posterior for the base case - bprior,bpost = self.prior_prediction, self.posterior_prediction - #get the prior and posterior for the conditioned case - la_cond = self.get_conditional_instance(parameter_names) - cprior,cpost = la_cond.prior_prediction, la_cond.posterior_prediction - return cprior,cpost - -
    [docs] def get_conditional_instance(self, parameter_names): - """ get a new Schur instance that includes conditional update from - some parameters becoming known perfectly - - Parameters - ---------- - parameter_names : list - parameters that are to be treated as notionally perfectly - known - - Returns - ------- - la_cond : Schur - a new Schur instance conditional on perfect knowledge - of some parameters - - Note - ---- - this method is used by the get_parameter_contribution() method - - don't call this method directly - - """ - if not isinstance(parameter_names, list): - parameter_names = [parameter_names] - - for iname, name in enumerate(parameter_names): - name = str(name).lower() - parameter_names[iname] = name - assert name in self.jco.col_names,\ - "contribution parameter " + name + " not found jco" - keep_names = [] - for name in self.jco.col_names: - if name not in parameter_names: - keep_names.append(name) - if len(keep_names) == 0: - raise Exception("Schur.contribution_from_Parameters " + - "atleast one parameter must remain uncertain") - #get the reduced predictions - if self.predictions is None: - raise Exception("Schur.contribution_from_Parameters " + - "no predictions have been set") - # cond_preds = [] - # for pred in self.predictions: - # cond_preds.append(pred.get(keep_names, pred.col_names)) - cond_preds = self.predictions.get(row_names=keep_names) - la_cond = Schur(jco=self.jco.get(self.jco.row_names, keep_names), - parcov=self.parcov.condition_on(parameter_names), - obscov=self.obscov, predictions=cond_preds,verbose=False) - return la_cond
    - -
    [docs] def get_par_contribution(self,parlist_dict=None): - """get a dataframe the prior and posterior uncertainty - reduction as a result of some parameter becoming perfectly known - - Parameters - ---------- - parlist_dict : dict - a nested dictionary-list of groups of parameters - that are to be treated as perfectly known. key values become - row labels in returned dataframe. If None, each adjustable parameter - is sequentially treated as known and the returned dataframe - has row labels for each adjustable parameter - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - a dataframe that summarizes the parameter contribution analysis. - The dataframe has index (row labels) of the keys in parlist_dict - and a column labels of forecast names. The values in the dataframe - are the posterior variance of the forecast conditional on perfect - knowledge of the parameters in the values of parlist_dict - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``>>>df = sc.get_par_contribution()`` - - """ - self.log("calculating contribution from parameters") - if parlist_dict is None: - parlist_dict = {}#dict(zip(self.pst.adj_par_names,self.pst.adj_par_names)) - # make sure all of the adjustable pars are in the jco - for pname in self.pst.adj_par_names: - if pname in self.jco.col_names: - parlist_dict[pname] = pname - else: - if type(parlist_dict) == list: - parlist_dict = dict(zip(parlist_dict,parlist_dict)) - - results = {} - names = ["base"] - for forecast in self.prior_forecast.keys(): - pr = self.prior_forecast[forecast] - pt = self.posterior_forecast[forecast] - #reduce = 100.0 * ((pr - pt) / pr) - results[(forecast,"prior")] = [pr] - results[(forecast,"post")] = [pt] - #results[(forecast,"percent_reduce")] = [reduce] - for case_name,par_list in parlist_dict.items(): - if len(par_list) == 0: - continue - names.append(case_name) - self.log("calculating contribution from: " + str(par_list)) - case_prior,case_post = self.__contribution_from_parameters(par_list) - self.log("calculating contribution from: " + str(par_list)) - for forecast in case_prior.keys(): - pr = case_prior[forecast] - pt = case_post[forecast] - #reduce = 100.0 * ((pr - pt) / pr) - results[(forecast, "prior")].append(pr) - results[(forecast, "post")].append(pt) - #results[(forecast, "percent_reduce")].append(reduce) - - df = pd.DataFrame(results,index=names) - #base = df.loc["base",df.columns.get_level_values(1)=="post"] - #df = 1.0 - (df.loc[:,df.columns.get_level_values(1)=="post"] / base) - df = df.xs("post",level=1,drop_level=True,axis=1) - self.log("calculating contribution from parameters") - return df
    - -
    [docs] def get_par_group_contribution(self): - """get the forecast uncertainty contribution from each parameter - group. Just some sugar for get_contribution_dataframe() - this method - automatically constructs the parlist_dict argument where the keys are the - group names and the values are the adjustable parameters in the groups - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - a dataframe that summarizes the parameter contribution analysis. - The dataframe has index (row labels) that are the parameter groups - and a column labels of forecast names. The values in the dataframe - are the posterior variance of the forecast conditional on perfect - knowledge of the adjustable parameters in each parameter groups - - """ - pargrp_dict = {} - par = self.pst.parameter_data - groups = par.groupby("pargp").groups - for grp,idxs in groups.items(): - #pargrp_dict[grp] = list(par.loc[idxs,"parnme"]) - pargrp_dict[grp] = [pname for pname in list(par.loc[idxs,"parnme"]) - if pname in self.jco.col_names and pname in self.parcov.row_names] - return self.get_par_contribution(pargrp_dict)
    - -
    [docs] def get_added_obs_importance(self,obslist_dict=None,base_obslist=None, - reset_zero_weight=False): - """get a dataframe fo the posterior uncertainty - as a results of added some observations - - Parameters - ---------- - obslist_dict : dict - a nested dictionary-list of groups of observations - that are to be treated as gained. key values become - row labels in returned dataframe. If None, then every zero-weighted - observation is tested sequentially. Default is None - base_obslist : list - observation names to treat as the "existing" observations. - The values of obslist_dict will be added to this list during - each test. If None, then the values in obslist_dict will - be treated as the entire calibration dataset. That is, there - are no existing data. Default is None. Standard practice would - be to pass this argument as Schur.pst.nnz_obs_names. - reset_zero_weight : (boolean or float) - a flag to reset observations with zero weight in either - obslist_dict or base_obslist. The value of reset_zero_weights - can be cast to a float,then that value will be assigned to - zero weight obs. Otherwise, zero weight obs will be given a - weight of 1.0. Default is False. - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - dataframe with row labels (index) of obslist_dict.keys() and - columns of forecast_name. The values in the dataframe are the - posterior variance of the forecasts resulting from notional inversion - using the observations in obslist_dict[key value] plus the observations - in base_obslist (if any) - - Note - ---- - all observations listed in obslist_dict and base_obslist with zero - weights will be dropped unless reset_zero_weight is set - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``>>>df = sc.get_added_obs_importance(base_obslist=sc.pst.nnz_obs_names,reset_zero=True)`` - - - """ - - if obslist_dict is not None: - if type(obslist_dict) == list: - obslist_dict = dict(zip(obslist_dict,obslist_dict)) - - reset = False - if reset_zero_weight is not False: - if not self.obscov.isdiagonal: - raise NotImplementedError("cannot reset weights for non-"+\ - "diagonal obscov") - reset = True - try: - weight = float(reset_zero_weight) - except: - weight = 1.0 - self.logger.statement("resetting zero weights to {0}".format(weight)) - # make copies of the original obscov and pst - #org_obscov = self.obscov.get(self.obscov.row_names) - org_obscov = self.obscov.copy() - org_pst = self.pst.get() - - obs = self.pst.observation_data - obs.index = obs.obsnme - - # if we don't care about grouping obs, then just reset all weights at once - if base_obslist is None and obslist_dict is None and reset: - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.obs_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight - - # if needed reset the zero-weight obs in base_obslist - if base_obslist is not None and reset: - # check for zero - self.log("resetting zero weight obs in base_obslist") - self.pst.adjust_weights_by_list(base_obslist, weight) - self.log("resetting zero weight obs in base_obslist") - - if base_obslist is None: - base_obslist = [] - else: - if type(base_obslist) != list: - self.logger.lraise("Schur.get_added_obs)_importance: base_obslist must be" + - " type 'list', not {0}".format(str(type(base_obslist)))) - # if needed reset the zero-weight obs in obslist_dict - if obslist_dict is not None and reset: - z_obs = [] - for case,obslist in obslist_dict.items(): - if not isinstance(obslist,list): - obslist_dict[case] = [obslist] - obslist = [obslist] - inboth = set(base_obslist).intersection(set(obslist)) - if len(inboth) > 0: - raise Exception("observation(s) listed in both "+\ - "base_obslist and obslist_dict: "+\ - ','.join(inboth)) - z_obs.extend(obslist) - self.log("resetting zero weight obs in obslist_dict") - self.pst.adjust_weights_by_list(z_obs, weight) - self.log("resetting zero weight obs in obslist_dict") - - # for a comprehensive obslist_dict - if obslist_dict is None and reset: - obs = self.pst.observation_data - obs.index = obs.obsnme - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.obs_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight - - if obslist_dict is None: - obslist_dict = {name:name for name in self.pst.nnz_obs_names if name\ - in self.jco.obs_names and name in self.obscov.row_names} - - # reset the obs cov from the newly adjusted weights - if reset: - self.log("resetting self.obscov") - self.reset_obscov(self.pst) - self.log("resetting self.obscov") - - results = {} - names = ["base"] - - if base_obslist is None or len(base_obslist) == 0: - self.logger.statement("no base observation passed, 'base' case"+ - " is just the prior of the forecasts") - for forecast,pr in self.prior_forecast.items(): - results[forecast] = [pr] - # reset base obslist for use later - base_obslist = [] - - else: - base_posterior = self.get(par_names=self.jco.par_names, - obs_names=base_obslist).posterior_forecast - for forecast,pt in base_posterior.items(): - results[forecast] = [pt] - - for case_name,obslist in obslist_dict.items(): - names.append(case_name) - if not isinstance(obslist,list): - obslist = [obslist] - self.log("calculating importance of observations by adding: " + - str(obslist) + '\n') - # this case is the combination of the base obs plus whatever unique - # obs names in obslist - case_obslist = list(base_obslist) - dedup_obslist = [oname for oname in obslist if oname not in case_obslist] - case_obslist.extend(dedup_obslist) - #print(self.pst.observation_data.loc[case_obslist,:]) - case_post = self.get(par_names=self.jco.par_names, - obs_names=case_obslist).posterior_forecast - for forecast,pt in case_post.items(): - results[forecast].append(pt) - self.log("calculating importance of observations by adding: " + - str(obslist) + '\n') - df = pd.DataFrame(results,index=names) - - - if reset: - self.reset_obscov(org_obscov) - self.reset_pst(org_pst) - - return df
    - -
    [docs] def get_removed_obs_importance(self,obslist_dict=None, - reset_zero_weight=False): - """get a dataframe the posterior uncertainty - as a result of losing some observations - - Parameters - ---------- - obslist_dict : dict - dictionary of groups of observations - that are to be treated as lost. key values become - row labels in returned dataframe. If None, then test every - (nonzero weight - see reset_zero_weight) observation - reset_zero_weight : bool or float - a flag to reset observations with zero weight in obslist_dict. - If the value of reset_zero_weights can be cast to a float, - then that value will be assigned to zero weight obs. Otherwise, - zero weight obs will be given a weight of 1.0 - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - a dataframe with index of obslist_dict.keys() and columns - of forecast names. The values in the dataframe are the posterior - variances of the forecasts resulting from losing the information - contained in obslist_dict[key value] - - Note - ---- - all observations listed in obslist_dict with zero - weights will be dropped unless reset_zero_weight is set - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``df = sc.get_removed_obs_importance()`` - - """ - - - if obslist_dict is not None: - if type(obslist_dict) == list: - obslist_dict = dict(zip(obslist_dict,obslist_dict)) - - elif reset_zero_weight is False and self.pst.nnz_obs == 0: - raise Exception("not resetting weights and there are no non-zero weight obs to remove") - - reset = False - if reset_zero_weight is not False: - if not self.obscov.isdiagonal: - raise NotImplementedError("cannot reset weights for non-"+\ - "diagonal obscov") - reset = True - try: - weight = float(reset_zero_weight) - except: - weight = 1.0 - self.logger.statement("resetting zero weights to {0}".format(weight)) - # make copies of the original obscov and pst - org_obscov = self.obscov.get(self.obscov.row_names) - org_pst = self.pst.get() - - self.log("calculating importance of observations") - if reset and obslist_dict is None: - obs = self.pst.observation_data - onames = [name for name in self.pst.zero_weight_obs_names - if name in self.jco.obs_names and name in self.obscov.row_names] - obs.loc[onames,"weight"] = weight - - if obslist_dict is None: - obslist_dict = dict(zip(self.pst.nnz_obs_names, - self.pst.nnz_obs_names)) - - - elif reset: - self.pst.observation_data.index = self.pst.observation_data.obsnme - for name,obslist in obslist_dict.items(): - self.log("resetting weights in obs in group {0}".format(name)) - self.pst.adjust_weights_by_list(obslist,weight) - self.log("resetting weights in obs in group {0}".format(name)) - - for case,obslist in obslist_dict.items(): - if not isinstance(obslist,list): - obslist = [obslist] - obslist_dict[case] = obslist - - - if reset: - self.log("resetting self.obscov") - self.reset_obscov(self.pst) - self.log("resetting self.obscov") - - results = {} - names = ["base"] - for forecast,pt in self.posterior_forecast.items(): - results[forecast] = [pt] - for case_name,obslist in obslist_dict.items(): - if not isinstance(obslist,list): - obslist = [obslist] - names.append(case_name) - self.log("calculating importance of observations by removing: " + - str(obslist) + '\n') - # check for missing names - missing_onames = [oname for oname in obslist if oname not in self.jco.obs_names] - if len(missing_onames) > 0: - raise Exception("case {0} has observation names ".format(case_name) + \ - "not found: " + ','.join(missing_onames)) - # find the set difference between obslist and jco obs names - #diff_onames = [oname for oname in self.jco.obs_names if oname not in obslist] - diff_onames = [oname for oname in self.nnz_obs_names if oname not in obslist and oname not in self.forecast_names] - - - # calculate the increase in forecast variance by not using the obs - # in obslist - case_post = self.get(par_names=self.jco.par_names, - obs_names=diff_onames).posterior_forecast - - for forecast,pt in case_post.items(): - results[forecast].append(pt) - df = pd.DataFrame(results,index=names) - self.log("calculating importance of observations by removing: " + - str(obslist) + '\n') - - if reset: - self.reset_obscov(org_obscov) - self.reset_pst(org_pst) - return df
    - -
    [docs] def obs_group_importance(self): - obsgrp_dict = {} - obs = self.pst.observation_data - obs.index = obs.obsnme - obs = obs.loc[self.jco.row_names,:] - groups = obs.groupby("obgnme").groups - for grp, idxs in groups.items(): - obsgrp_dict[grp] = list(obs.loc[idxs,"obsnme"]) - return obsgrp_dict
    - -
    [docs] def get_removed_obs_group_importance(self): - return self.get_removed_obs_importance(self.obs_group_importance())
    - -
    [docs] def get_added_obs_group_importance(self): - return self.get_added_obs_importance(self.obs_group_importance())
    - -
    [docs] def next_most_important_added_obs(self,forecast=None,niter=3, obslist_dict=None, - base_obslist=None, - reset_zero_weight=False): - """find the most important observation(s) by sequentially evaluating - the importance of the observations in obslist_dict. The most important observations - from each iteration is added to base_obslist and removed obslist_dict for the - next iteration. In this way, the added observation importance values include - the conditional information from the last iteration. - - Parameters - ---------- - forecast : str - name of the forecast to use in the ranking process. If - more than one forecast has been listed, this argument is required - niter : int - number of sequential iterations - obslist_dict dict: - nested dictionary-list of groups of observations - that are to be treated as gained. key values become - row labels in result dataframe. If None, then test every observation - individually - base_obslist : list - observation names to treat as the "existing" observations. - The values of obslist_dict will be added to this list during testing. - If None, then each list in the values of obslist_dict will be - treated as an individual calibration dataset. - reset_zero_weight : (boolean or float) - a flag to reset observations with zero weight in either - obslist_dict or base_obslist. If the value of reset_zero_weights - can be cast to a float,then that value will be assigned to - zero weight obs. Otherwise, zero weight obs will be given a weight of 1.0 - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - DataFrame with columns of best obslist_dict key for each iteration. - Columns of forecast variance percent reduction for this iteration, - (percent reduction compared to initial base case) - - - Example - ------- - ``>>>import pyemu`` - - ``>>>sc = pyemu.Schur(jco="pest.jcb")`` - - ``>>>df = sc.next_most_added_importance_obs(forecast="fore1",`` - - ``>>> base_obslist=sc.pst.nnz_obs_names,reset_zero=True`` - """ - - - if forecast is None: - assert self.forecasts.shape[1] == 1,"forecast arg list one and only one" +\ - " forecast" - forecast = self.forecasts[0].col_names[0] - #elif forecast not in self.prediction_arg: - # raise Exception("forecast {0} not found".format(forecast)) - - else: - forecast = forecast.lower() - found = False - for fore in self.forecasts.col_names: - if fore == forecast: - found = True - break - if not found: - raise Exception("forecast {0} not found".format(forecast)) - - - - - if base_obslist: - obs_being_used = list(base_obslist) - else: - obs_being_used = [] - - best_case, best_results = [],[] - for iiter in range(niter): - self.log("next most important added obs iteration {0}".format(iiter+1)) - df = self.get_added_obs_importance(obslist_dict=obslist_dict, - base_obslist=obs_being_used, - reset_zero_weight=reset_zero_weight) - - if iiter == 0: - init_base = df.loc["base",forecast].copy() - fore_df = df.loc[:,forecast] - fore_diff_df = fore_df - fore_df.loc["base"] - fore_diff_df.sort_values(inplace=True) - iter_best_name = fore_diff_df.index[0] - iter_best_result = df.loc[iter_best_name,forecast] - iter_base_result = df.loc["base",forecast] - diff_percent_init = 100.0 * (init_base - - iter_best_result) / init_base - diff_percent_iter = 100.0 * (iter_base_result - - iter_best_result) / iter_base_result - self.log("next most important added obs iteration {0}".format(iiter+1)) - - - best_results.append([iter_best_name,iter_best_result, - diff_percent_iter,diff_percent_init]) - best_case.append(iter_best_name) - - if iter_best_name.lower() == "base": - break - - if obslist_dict is None: - onames = [iter_best_name] - else: - onames = obslist_dict.pop(iter_best_name) - if not isinstance(onames,list): - onames = [onames] - obs_being_used.extend(onames) - columns = ["best_obs",forecast+"_variance", - "unc_reduce_iter_base","unc_reduce_initial_base"] - return pd.DataFrame(best_results,index=best_case,columns=columns)
    - -
    [docs] def next_most_par_contribution(self,niter=3,forecast=None,parlist_dict=None): - """find the largest parameter(s) contribution for prior and posterior - forecast by sequentially evaluating the contribution of parameters in - parlist_dict. The largest contributing parameters from each iteration are - treated as known perfectly for the remaining iterations. In this way, the - next iteration seeks the next most influential group of parameters. - - Parameters - ---------- - forecast : str - name of the forecast to use in the ranking process. If - more than one forecast has been listed, this argument is required - parlist_dict : dict - a nested dictionary-list of groups of parameters - that are to be treated as perfectly known. key values become - row labels in dataframe - - Returns - ------- - pandas.DataFrame : pandas.DataFrame - a dataframe with index of iteration number and columns - of parlist_dict keys. The values are the results of the knowing - each parlist_dict entry expressed as posterior variance reduction - - """ - if forecast is None: - assert len(self.forecasts) == 1,"forecast arg list one and only one" +\ - " forecast" - elif forecast not in self.prediction_arg: - raise Exception("forecast {0} not found".format(forecast)) - org_parcov = self.parcov.get(row_names=self.parcov.row_names) - if parlist_dict is None: - parlist_dict = dict(zip(self.pst.adj_par_names,self.pst.adj_par_names)) - - base_prior,base_post = self.prior_forecast,self.posterior_forecast - iter_results = [base_post[forecast].copy()] - iter_names = ["base"] - for iiter in range(niter): - iter_contrib = {forecast:[base_post[forecast]]} - iter_case_names = ["base"] - self.log("next most par iteration {0}".format(iiter+1)) - - for case,parlist in parlist_dict.items(): - iter_case_names.append(case) - la_cond = self.get_conditional_instance(parlist) - iter_contrib[forecast].append(la_cond.posterior_forecast[forecast]) - df = pd.DataFrame(iter_contrib,index=iter_case_names) - df.sort_values(by=forecast,inplace=True) - iter_best = df.index[0] - self.logger.statement("next best iter {0}: {1}".format(iiter+1,iter_best)) - self.log("next most par iteration {0}".format(iiter+1)) - if iter_best.lower() == "base": - break - iter_results.append(df.loc[iter_best,forecast]) - iter_names.append(iter_best) - self.reset_parcov(self.parcov.condition_on(parlist_dict.pop(iter_best))) - - self.reset_parcov(org_parcov) - return pd.DataFrame(iter_results,index=iter_names)
    - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/smoother.html b/docs/_build/html/_modules/pyemu/smoother.html deleted file mode 100644 index c5651a180..000000000 --- a/docs/_build/html/_modules/pyemu/smoother.html +++ /dev/null @@ -1,1266 +0,0 @@ - - - - - - - - pyemu.smoother — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.smoother

    -"""this is a prototype ensemble smoother based on the LM-EnRML
    -algorithm of Chen and Oliver 2013.  It requires the pest++ "sweep" utility
    -to propagate the ensemble forward.
    -"""
    -from __future__ import print_function, division
    -import os
    -from datetime import datetime
    -import shutil
    -import threading
    -import time
    -import warnings
    -
    -import numpy as np
    -import pandas as pd
    -import pyemu
    -from pyemu.en import ParameterEnsemble,ObservationEnsemble
    -from pyemu.mat import Cov,Matrix
    -
    -from pyemu.pst import Pst
    -from .logger import Logger
    -
    -
    -
    [docs]class Phi(object): - """Container class for dealing with ensemble residuals and phi vectors - - Parameters - ---------- - em : EnsembleMethod - """ - def __init__(self,em): - assert isinstance(em,EnsembleMethod) - self.em = em - self.phi_files = {} - self._tags = ["composite","meas","actual","reg"] - self._prepare_output_files() - self.obs0_matrix = self.em.obsensemble_0.nonzero.as_pyemu_matrix() - if self.em.parensemble.istransformed: - self.par0_matrix = self.em.parensemble_0.as_pyemu_matrix() - else: - self.par0_matrix = self.em.parensemble_0._transform(inplace=False).as_pyemu_matrix() - self.par0_matrix = self.par0_matrix.get(col_names=self.em.parcov.row_names) - self.em.logger.log("inverting parcov for regul phi calcs") - self.inv_parcov = self.em.parcov.pseudo_inv(eigthresh=self.em.pst.svd_data.eigthresh) - self.em.logger.log("inverting parcov for regul phi calcs") - self.update() - - def _prepare_output_files(self): - num_reals = self.em.obsensemble.shape[0] - for tag in self._tags: - f = open(self.em.pst.filename + ".iobj.{0}.csv".format(tag), 'w') - f.write("iter_num,total_runs,lambda,min,max,mean,median,std,") - f.write(','.join(["{0:010d}".format(i + 1) for i in range(num_reals)])) - f.write('\n') - self.phi_files[tag] = f - -
    [docs] def write(self,cur_lam=0.0): - for t,pv in self.phi_vec_dict.items(): - self._phi_report(self.phi_files[t],pv,cur_lam)
    - - @property - def phi_vec_dict(self): - d = {t:pv for t,pv in zip(self._tags,[self.comp_phi,self.meas_phi,self.meas_phi_actual,self.reg_phi])} - return d - - def _phi_report(self,phi_csv,phi_vec,cur_lam=0.0): - phi_csv.write("{0},{1},{2},{3},{4},{5},{6},{7},".format(self.em.iter_num, - self.em.total_runs, - cur_lam, - phi_vec.min(), - phi_vec.max(), - phi_vec.mean(), - np.median(phi_vec), - phi_vec.std())) - #[print(phi) for phi in phi_vec] - phi_csv.write(",".join(["{0:20.8}".format(phi) for phi in phi_vec])) - phi_csv.write("\n") - phi_csv.flush() - -
    [docs] def get_meas_and_regul_phi(self,obsensemble,parensemble): - assert obsensemble.shape[0] == parensemble.shape[0] - meas_phi = self._calc_meas_phi(obsensemble) - reg_phi = self._calc_regul_phi(parensemble) - return meas_phi,reg_phi
    - - - -
    [docs] def update(self): - #assert obsensemble.shape[0] == parensemble.shape[0] - #self.cur_lam = cur_lam - self.meas_phi = self._calc_meas_phi(self.em.obsensemble) - self.meas_phi_actual = self._calc_meas_phi_actual(self.em.obsensemble) - self.reg_phi = self._calc_regul_phi(self.em.parensemble) - self.comp_phi = self.meas_phi + (self.reg_phi * self.em.regul_factor)
    - - - # dynamic updating is too costly when lots of pars are being used... - # @property - # def meas_phi(self): - # #print('calc meas phi') - # return self._calc_meas_phi(self.em.obsensemble) - # - # @property - # def meas_phi_actual(self): - # #print('calc actual phi') - # return self._calc_meas_phi_actual(self.em.obsensemble) - # - # @property - # def reg_phi(self): - # #print('calc regul phi') - # return self._calc_regul_phi(self.em.parensemble) - # - # @property - # def comp_phi(self): - # #print('calc comp phi') - # return self.meas_phi + (self.reg_phi * self.em.regul_factor) - -
    [docs] def report(self,cur_lam=0.0): - self.write(cur_lam) - ls = self.em.logger.statement - pvd = self.phi_vec_dict - ls("**** phi summary ****") - ls("{0:12s}: {1:>15.6G}".format("regul factor",self.em.regul_factor)) - for t in self._tags: - ls("{0:9s} mean: {1:>15.6G}, std: {2:>15.6G}".\ - format(t,pvd[t].mean(),pvd[t].std())) - ls("*********************")
    - - def _calc_meas_phi(self,obsensemble): - obs_diff = self.get_residual_obs_matrix(obsensemble) - - q = np.diagonal(self.em.obscov_inv_sqrt.get(row_names=obs_diff.col_names,col_names=obs_diff.col_names).x) - phi_vec = [] - for i in range(obs_diff.shape[0]): - o = obs_diff.x[i,:] - phi_vec.append(((obs_diff.x[i,:] * q)**2).sum()) - - return np.array(phi_vec) - - - def _calc_regul_phi(self,parensemble): - if isinstance(parensemble,pd.DataFrame): - parensemble = pyemu.ParameterEnsemble.from_dataframe(pst=self.em.pst,df=parensemble) - par_diff = self.get_residual_par_matrix(parensemble) - #reg_phi_vec = np.diag(np.dot(np.dot(par_diff.x,self.inv_parcov.x),par_diff.x.transpose())) - reg_phi_vec = np.diag((par_diff * self.inv_parcov * par_diff.T).x) - return reg_phi_vec - - def _calc_meas_phi_actual(self,obsensemble): - return obsensemble.phi_vector - -
    [docs] def get_residual_obs_matrix(self, obsensemble): - obs_matrix = obsensemble.nonzero.as_pyemu_matrix() - - res_mat = obs_matrix - self.obs0_matrix.get(col_names=obs_matrix.col_names, - row_names=obs_matrix.row_names) - self._apply_inequality_constraints(res_mat) - return res_mat
    - -
    [docs] def get_residual_par_matrix(self, parensemble): - # these should already be transformed - # if parensemble.istransformed: - # par_matrix = parensemble.as_pyemu_matrix() - # else: - # par_matrix = parensemble._transform(inplace=False).as_pyemu_matrix() - par_matrix = parensemble.as_pyemu_matrix() - #res_mat = par_matrix.get(col_names=self.em.pst.adj_par_names) - \ - # self.par0_matrix.get(col_names=self.em.pst.adj_par_names) - res_mat = par_matrix.get(col_names=self.par0_matrix.col_names) - self.par0_matrix - return res_mat
    - - def _apply_inequality_constraints(self,res_mat): - obs = self.em.pst.observation_data.loc[res_mat.col_names] - gt_names = obs.loc[obs.obgnme.apply(lambda x: x.startswith("g_") or x.startswith("less")), "obsnme"] - lt_names = obs.loc[obs.obgnme.apply(lambda x: x.startswith("l_") or x.startswith("greater")), "obsnme"] - if gt_names.shape[0] == 0 and lt_names.shape[0] == 0: - return res_mat - res_df = res_mat.to_dataframe() - if gt_names.shape[0] > 0: - for gt_name in gt_names: - #print(res_df.loc[:,gt_name]) - #if the residual is greater than zero, this means the ineq is satisified - res_df.loc[res_df.loc[:,gt_name] > 0,gt_name] = 0.0 - #print(res_df.loc[:,gt_name]) - #print() - - - if lt_names.shape[0] > 0: - for lt_name in lt_names: - #print(res_df.loc[:,lt_name]) - #f the residual is less than zero, this means the ineq is satisfied - res_df.loc[res_df.loc[:,lt_name] < 0,lt_name] = 0.0
    - #print(res_df.loc[:,lt_name]) - #print() - - -
    [docs]class EnsembleMethod(object): - """Base class for ensemble-type methods. Should not be instantiated directly - - Parameters - ---------- - pst : pyemu.Pst or str - a control file instance or filename - parcov : pyemu.Cov or str - a prior parameter covariance matrix or filename. If None, - parcov is constructed from parameter bounds (diagonal) - obscov : pyemu.Cov or str - a measurement noise covariance matrix or filename. If None, - obscov is constructed from observation weights. - num_slaves : int - number of slaves to use in (local machine) parallel evaluation of the parmaeter - ensemble. If 0, serial evaluation is used. Ignored if submit_file is not None - submit_file : str - the name of a HTCondor submit file. If not None, HTCondor is used to - evaluate the parameter ensemble in parallel by issuing condor_submit - as a system command - port : int - the TCP port number to communicate on for parallel run management - slave_dir : str - path to a directory with a complete set of model files and PEST - interface files - - """ - - def __init__(self,pst,parcov=None,obscov=None,num_slaves=0,use_approx_prior=True, - submit_file=None,verbose=False,port=4004,slave_dir="template"): - self.logger = Logger(verbose) - if verbose is not False: - self.logger.echo = True - self.num_slaves = int(num_slaves) - if submit_file is not None: - if not os.path.exists(submit_file): - self.logger.lraise("submit_file {0} not found".format(submit_file)) - elif num_slaves > 0: - if not os.path.exists(slave_dir): - self.logger.lraise("template dir {0} not found".format(slave_dir)) - - self.slave_dir = slave_dir - self.submit_file = submit_file - self.port = int(port) - self.paren_prefix = ".parensemble.{0:04d}.csv" - self.obsen_prefix = ".obsensemble.{0:04d}.csv" - - if isinstance(pst,str): - pst = Pst(pst) - assert isinstance(pst,Pst) - self.pst = pst - self.sweep_in_csv = pst.pestpp_options.get("sweep_parameter_csv_file","sweep_in.csv") - self.sweep_out_csv = pst.pestpp_options.get("sweep_output_csv_file","sweep_out.csv") - if parcov is not None: - assert isinstance(parcov,Cov) - else: - parcov = Cov.from_parameter_data(self.pst) - if obscov is not None: - assert isinstance(obscov,Cov) - else: - obscov = Cov.from_observation_data(pst) - - self.parcov = parcov - self.obscov = obscov - - self.__initialized = False - self.iter_num = 0 - self.raw_sweep_out = None - -
    [docs] def initialize(self,*args,**kwargs): - raise Exception("EnsembleMethod.initialize() must be implemented by the derived types")
    - - def _calc_delta(self,ensemble,scaling_matrix=None): - ''' - calc the scaled ensemble differences from the mean - ''' - mean = np.array(ensemble.mean(axis=0)) - delta = ensemble.as_pyemu_matrix() - for i in range(ensemble.shape[0]): - delta.x[i,:] -= mean - if scaling_matrix is not None: - delta = scaling_matrix * delta.T - delta *= (1.0 / np.sqrt(float(ensemble.shape[0] - 1.0))) - return delta - - def _calc_obs(self,parensemble): - self.logger.log("removing existing sweep in/out files") - try: - os.remove(self.sweep_in_csv) - except Exception as e: - self.logger.warn("error removing existing sweep in file:{0}".format(str(e))) - try: - os.remove(self.sweep_out_csv) - except Exception as e: - self.logger.warn("error removing existing sweep out file:{0}".format(str(e))) - self.logger.log("removing existing sweep in/out files") - - if parensemble.isnull().values.any(): - parensemble.to_csv("_nan.csv") - self.logger.lraise("_calc_obs() error: NaNs in parensemble (written to '_nan.csv')") - - if self.submit_file is None: - self._calc_obs_local(parensemble) - else: - self._calc_obs_condor(parensemble) - - # make a copy of sweep out for restart purposes - # sweep_out = str(self.iter_num)+"_raw_"+self.sweep_out_csv - # if os.path.exists(sweep_out): - # os.remove(sweep_out) - # shutil.copy2(self.sweep_out_csv,sweep_out) - - self.logger.log("reading sweep out csv {0}".format(self.sweep_out_csv)) - failed_runs,obs = self._load_obs_ensemble(self.sweep_out_csv) - self.logger.log("reading sweep out csv {0}".format(self.sweep_out_csv)) - self.total_runs += obs.shape[0] - self.logger.statement("total runs:{0}".format(self.total_runs)) - return failed_runs,obs - - def _load_obs_ensemble(self,filename): - if not os.path.exists(filename): - self.logger.lraise("obsensemble file {0} does not exists".format(filename)) - obs = pd.read_csv(filename) - obs.columns = [item.lower() for item in obs.columns] - self.raw_sweep_out = obs.copy() # save this for later to support restart - assert "input_run_id" in obs.columns,\ - "'input_run_id' col missing...need newer version of sweep" - obs.index = obs.input_run_id - failed_runs = None - if 1 in obs.failed_flag.values: - failed_runs = obs.loc[obs.failed_flag == 1].index.values - self.logger.warn("{0} runs failed (indices: {1})".\ - format(len(failed_runs),','.join([str(f) for f in failed_runs]))) - obs = ObservationEnsemble.from_dataframe(df=obs.loc[:,self.obscov.row_names], - pst=self.pst) - if obs.isnull().values.any(): - self.logger.lraise("_calc_obs() error: NaNs in obsensemble") - return failed_runs, obs - - def _get_master_thread(self): - master_stdout = "_master_stdout.dat" - master_stderr = "_master_stderr.dat" - def master(): - try: - #os.system("sweep {0} /h :{1} 1>{2} 2>{3}". \ - # format(self.pst.filename, self.port, master_stdout, master_stderr)) - pyemu.helpers.run("pestpp-swp {0} /h :{1} 1>{2} 2>{3}". \ - format(self.pst.filename, self.port, master_stdout, master_stderr)) - - except Exception as e: - self.logger.lraise("error starting condor master: {0}".format(str(e))) - with open(master_stderr, 'r') as f: - err_lines = f.readlines() - if len(err_lines) > 0: - self.logger.warn("master stderr lines: {0}". - format(','.join([l.strip() for l in err_lines]))) - - master_thread = threading.Thread(target=master) - master_thread.start() - time.sleep(2.0) - return master_thread - - def _calc_obs_condor(self,parensemble): - self.logger.log("evaluating ensemble of size {0} with htcondor".\ - format(parensemble.shape[0])) - - parensemble.to_csv(self.sweep_in_csv) - master_thread = self._get_master_thread() - condor_temp_file = "_condor_submit_stdout.dat" - condor_err_file = "_condor_submit_stderr.dat" - self.logger.log("calling condor_submit with submit file {0}".format(self.submit_file)) - try: - os.system("condor_submit {0} 1>{1} 2>{2}".\ - format(self.submit_file,condor_temp_file,condor_err_file)) - except Exception as e: - self.logger.lraise("error in condor_submit: {0}".format(str(e))) - self.logger.log("calling condor_submit with submit file {0}".format(self.submit_file)) - time.sleep(2.0) #some time for condor to submit the job and echo to stdout - condor_submit_string = "submitted to cluster" - with open(condor_temp_file,'r') as f: - lines = f.readlines() - self.logger.statement("condor_submit stdout: {0}".\ - format(','.join([l.strip() for l in lines]))) - with open(condor_err_file,'r') as f: - err_lines = f.readlines() - if len(err_lines) > 0: - self.logger.warn("stderr from condor_submit:{0}".\ - format([l.strip() for l in err_lines])) - cluster_number = None - for line in lines: - if condor_submit_string in line.lower(): - cluster_number = int(float(line.split(condor_submit_string)[-1])) - if cluster_number is None: - self.logger.lraise("couldn't find cluster number...") - self.logger.statement("condor cluster: {0}".format(cluster_number)) - master_thread.join() - self.logger.statement("condor master thread exited") - self.logger.log("calling condor_rm on cluster {0}".format(cluster_number)) - os.system("condor_rm cluster {0}".format(cluster_number)) - self.logger.log("calling condor_rm on cluster {0}".format(cluster_number)) - self.logger.log("evaluating ensemble of size {0} with htcondor".\ - format(parensemble.shape[0])) - - - def _calc_obs_local(self,parensemble): - ''' - propagate the ensemble forward using sweep. - ''' - self.logger.log("evaluating ensemble of size {0} locally with sweep".\ - format(parensemble.shape[0])) - parensemble.to_csv(self.sweep_in_csv) - if self.num_slaves > 0: - master_thread = self._get_master_thread() - pyemu.utils.start_slaves(self.slave_dir,"pestpp-swp",self.pst.filename, - self.num_slaves,slave_root='..',port=self.port) - master_thread.join() - else: - os.system("pestpp-swp {0}".format(self.pst.filename)) - - self.logger.log("evaluating ensemble of size {0} locally with sweep".\ - format(parensemble.shape[0])) - - -
    [docs] def update(self,lambda_mults=[1.0],localizer=None,run_subset=None,use_approx=True): - raise Exception("EnsembleMethod.update() must be implemented by the derived types")
    - - -
    [docs]class EnsembleSmoother(EnsembleMethod): - """an implementation of the GLM iterative ensemble smoother - - Parameters - ---------- - pst : pyemu.Pst or str - a control file instance or filename - parcov : pyemu.Cov or str - a prior parameter covariance matrix or filename. If None, - parcov is constructed from parameter bounds (diagonal) - obscov : pyemu.Cov or str - a measurement noise covariance matrix or filename. If None, - obscov is constructed from observation weights. - num_slaves : int - number of slaves to use in (local machine) parallel evaluation of the parmaeter - ensemble. If 0, serial evaluation is used. Ignored if submit_file is not None - use_approx_prior : bool - a flag to use the MLE (approx) upgrade solution. If True, a MAP - solution upgrade is used - submit_file : str - the name of a HTCondor submit file. If not None, HTCondor is used to - evaluate the parameter ensemble in parallel by issuing condor_submit - as a system command - port : int - the TCP port number to communicate on for parallel run management - slave_dir : str - path to a directory with a complete set of model files and PEST - interface files - drop_bad_reals : float - drop realizations with phi greater than drop_bad_reals. If None, all - realizations are kept. Default is None - - Example - ------- - ``>>>import pyemu`` - - ``>>>es = pyemu.EnsembleSmoother(pst="pest.pst")`` - """ - - def __init__(self,pst,parcov=None,obscov=None,num_slaves=0,submit_file=None,verbose=False, - port=4004,slave_dir="template",drop_bad_reals=None,save_mats=False): - - - - super(EnsembleSmoother,self).__init__(pst=pst,parcov=parcov,obscov=obscov,num_slaves=num_slaves, - submit_file=submit_file,verbose=verbose,port=port, - slave_dir=slave_dir) - self.logger.warn("pyemu's EnsembleSmoother is for prototyping only. Use PESTPP-IES for a production " + - "implementation of iterative ensemble smoother") - #self.use_approx_prior = bool(use_approx_prior) - self.parcov_inv_sqrt = None - self.half_obscov_diag = None - self.delta_par_prior = None - self.drop_bad_reals = drop_bad_reals - self.save_mats = save_mats - # @classmethod - # def from_pestpp_args(cls,pst): - # if isinstance(pst,str): - # pst = pyemu.Pst(pst) - # pp = pst.pestpp_options - # parcov = pp.pop("parcov_filename",pyemu.Cov.from_parameter_data(pst,sigma_range=6)) - # obscov = pyemu.Cov.from_observation_data(pst) - # par_csv = pp.pop("ies_par_csv") - # obs_csv = pp.pop("ies_obs_csv") - # restart_csv = pp.pop("ies_obs_restart_csv",None) - # - # use_approx = False - # tags = ["ies_use_approx","ies_use_approximate_solution"] - # for tag in tags: - # if tag in pp.keys() and pp[tag].startswith('t'): - # - # use_approx = True - # pp.pop(tag) - # - # lambda_mults = pp.pop("lambda",[1.0]) - # init_lambda = pp.pop("ies_init_lam",pp.pop("ies_initial_lambda",None)) - # subset = pp.pop("ies_subset_size",None) - - - - -
    [docs] def initialize(self,num_reals=1,init_lambda=None,enforce_bounds="reset", - parensemble=None,obsensemble=None,restart_obsensemble=None, - regul_factor=0.0,use_approx_prior=True,build_empirical_prior=False): - """Initialize the iES process. Depending on arguments, draws or loads - initial parameter observations ensembles and runs the initial parameter - ensemble - - Parameters - ---------- - num_reals : int - the number of realizations to draw. Ignored if parensemble/obsensemble - are not None - init_lambda : float - the initial lambda to use. During subsequent updates, the lambda is - updated according to upgrade success - enforce_bounds : str - how to enfore parameter bound transgression. options are - reset, drop, or None - parensemble : pyemu.ParameterEnsemble or str - a parameter ensemble or filename to use as the initial - parameter ensemble. If not None, then obsenemble must not be - None - obsensemble : pyemu.ObservationEnsemble or str - an observation ensemble or filename to use as the initial - observation ensemble. If not None, then parensemble must - not be None - restart_obsensemble : pyemu.ObservationEnsemble or str - an observation ensemble or filename to use as an - evaluated observation ensemble. If not None, this will skip the initial - parameter ensemble evaluation - user beware! - regul_factor : float - the regularization penalty fraction of the composite objective. The - Prurist, MAP solution would be regul_factor = 1.0, yielding equal - parts measurement and regularization to the composite objective function. - Default is 0.0, which means only seek to minimize the measurement objective - function - use_approx_prior : bool - a flag to use the inverse, square root of the prior ccovariance matrix - for scaling the upgrade calculation. If True, this matrix is not used. - Default is True - build_empirical_prior : bool - flag to build the prior parameter covariance matrix from an existing parensemble. - If True and parensemble is None, an exception is raised - - - Example - ------- - ``>>>import pyemu`` - - ``>>>es = pyemu.EnsembleSmoother(pst="pest.pst")`` - - ``>>>es.initialize(num_reals=100)`` - - """ - ''' - (re)initialize the process - ''' - # initialize the phi report csv - self.enforce_bounds = enforce_bounds - - self.regul_factor = float(regul_factor) - - self.total_runs = 0 - # this matrix gets used a lot, so only calc once and store - self.obscov_inv_sqrt = self.obscov.get(self.pst.nnz_obs_names).inv.sqrt - - if use_approx_prior: - self.logger.statement("using approximate parcov in solution") - self.parcov_inv_sqrt = 1.0 - else: - self.logger.statement("using full parcov in solution") - # Chen and Oliver use a low rank approx here, but so far, - # I haven't needed it - not using enough parameters yet - self.logger.log("forming inverse sqrt parcov matrix") - self.parcov_inv_sqrt = self.parcov.inv.sqrt - self.logger.log("forming inverse sqrt parcov matrix") - - if parensemble is not None and obsensemble is not None: - self.logger.log("initializing with existing ensembles") - if isinstance(parensemble,str): - self.logger.log("loading parensemble from file") - if not os.path.exists(obsensemble): - self.logger.lraise("can not find parensemble file: {0}".\ - format(parensemble)) - df = pd.read_csv(parensemble,index_col=0) - #df.index = [str(i) for i in df.index] - self.parensemble_0 = ParameterEnsemble.from_dataframe(df=df,pst=self.pst) - self.logger.log("loading parensemble from file") - - elif isinstance(parensemble,ParameterEnsemble): - self.parensemble_0 = parensemble.copy() - else: - raise Exception("unrecognized arg type for parensemble, " +\ - "should be filename or ParameterEnsemble" +\ - ", not {0}".format(type(parensemble))) - self.parensemble = self.parensemble_0.copy() - if isinstance(obsensemble,str): - self.logger.log("loading obsensemble from file") - if not os.path.exists(obsensemble): - self.logger.lraise("can not find obsensemble file: {0}".\ - format(obsensemble)) - df = pd.read_csv(obsensemble,index_col=0).loc[:,self.pst.nnz_obs_names] - #df.index = [str(i) for i in df.index] - self.obsensemble_0 = ObservationEnsemble.from_dataframe(df=df,pst=self.pst) - self.logger.log("loading obsensemble from file") - - elif isinstance(obsensemble,ObservationEnsemble): - self.obsensemble_0 = obsensemble.copy() - else: - raise Exception("unrecognized arg type for obsensemble, " +\ - "should be filename or ObservationEnsemble" +\ - ", not {0}".format(type(obsensemble))) - - assert self.parensemble_0.shape[0] == self.obsensemble_0.shape[0] - #self.num_reals = self.parensemble_0.shape[0] - num_reals = self.parensemble.shape[0] - self.logger.log("initializing with existing ensembles") - - if build_empirical_prior: - - self.reset_parcov(self.parensemble.covariance_matrix()) - if self.save_mats: - self.parcov.to_binary(self.pst.filename+".empcov.jcb") - - else: - if build_empirical_prior: - self.logger.lraise("can't use build_emprirical_prior without parensemble...") - self.logger.log("initializing smoother with {0} realizations".format(num_reals)) - self.logger.log("initializing parensemble") - self.parensemble_0 = pyemu.ParameterEnsemble.from_gaussian_draw(self.pst, - self.parcov,num_reals=num_reals) - self.parensemble_0.enforce(enforce_bounds=enforce_bounds) - self.logger.log("initializing parensemble") - self.parensemble = self.parensemble_0.copy() - self.parensemble_0.to_csv(self.pst.filename +\ - self.paren_prefix.format(0)) - self.logger.log("initializing parensemble") - self.logger.log("initializing obsensemble") - self.obsensemble_0 = pyemu.ObservationEnsemble.from_id_gaussian_draw(self.pst, - num_reals=num_reals) - #self.obsensemble = self.obsensemble_0.copy() - - # save the base obsensemble - self.obsensemble_0.to_csv(self.pst.filename +\ - self.obsen_prefix.format(-1)) - self.logger.log("initializing obsensemble") - self.logger.log("initializing smoother with {0} realizations".format(num_reals)) - - - if use_approx_prior: - self.logger.statement("using approximate parcov in solution") - self.parcov_inv_sqrt = 1.0 - else: - self.logger.statement("using full parcov in solution") - # Chen and Oliver use a low rank approx here, but so far, - # I haven't needed it - not using enough parameters yet - self.logger.log("forming inverse sqrt parcov matrix") - self.parcov_inv_sqrt = self.parcov.inv.sqrt - self.logger.log("forming inverse sqrt parcov matrix") - - # self.obs0_matrix = self.obsensemble_0.nonzero.as_pyemu_matrix() - # self.par0_matrix = self.parensemble_0.as_pyemu_matrix() - self.enforce_bounds = enforce_bounds - - if restart_obsensemble is not None: - self.logger.log("loading restart_obsensemble {0}".format(restart_obsensemble)) - failed_runs,self.obsensemble = self._load_obs_ensemble(restart_obsensemble) - assert self.obsensemble.shape[0] == self.obsensemble_0.shape[0] - assert list(self.obsensemble.columns) == list(self.obsensemble_0.columns) - self.logger.log("loading restart_obsensemble {0}".format(restart_obsensemble)) - - else: - # run the initial parameter ensemble - self.logger.log("evaluating initial ensembles") - failed_runs, self.obsensemble = self._calc_obs(self.parensemble) - self.obsensemble.to_csv(self.pst.filename +\ - self.obsen_prefix.format(0)) - if self.raw_sweep_out is not None: - self.raw_sweep_out.to_csv(self.pst.filename + "_sweepraw0.csv") - self.logger.log("evaluating initial ensembles") - - if failed_runs is not None: - self.logger.warn("dropping failed realizations") - #failed_runs_str = [str(f) for f in failed_runs] - #self.parensemble = self.parensemble.drop(failed_runs) - #self.obsensemble = self.obsensemble.drop(failed_runs) - self.parensemble.loc[failed_runs,:] = np.NaN - self.parensemble = self.parensemble.dropna() - self.obsensemble.loc[failed_runs,:] = np.NaN - self.obsensemble = self.obsensemble.dropna() - - if not self.parensemble.istransformed: - self.parensemble._transform(inplace=True) - if not self.parensemble_0.istransformed: - self.parensemble_0._transform(inplace=True) - - self.phi = Phi(self) - - if self.drop_bad_reals is not None: - #drop_idx = np.argwhere(self.current_phi_vec > self.drop_bad_reals).flatten() - #comp_phi = self.phi.comp_phi - #drop_idx = np.argwhere(self.phi.comp_phi > self.drop_bad_reals).flatten() - #meas_phi = self.phi.meas_phi - drop_idx = np.argwhere(self.phi.meas_phi > self.drop_bad_reals).flatten() - run_ids = self.obsensemble.index.values - drop_idx = run_ids[drop_idx] - if len(drop_idx) == self.obsensemble.shape[0]: - raise Exception("dropped all realizations as 'bad'") - if len(drop_idx) > 0: - self.logger.warn("{0} realizations dropped as 'bad' (indices :{1})".\ - format(len(drop_idx),','.join([str(d) for d in drop_idx]))) - self.parensemble.loc[drop_idx,:] = np.NaN - self.parensemble = self.parensemble.dropna() - self.obsensemble.loc[drop_idx,:] = np.NaN - self.obsensemble = self.obsensemble.dropna() - - self.phi.update() - - self.phi.report(cur_lam=0.0) - - self.last_best_mean = self.phi.comp_phi.mean() - self.last_best_std = self.phi.comp_phi.std() - - #self.logger.statement("initial phi (mean, std): {0:15.6G},{1:15.6G}".\ - # format(self.last_best_mean,self.last_best_std)) - if init_lambda is not None: - self.current_lambda = float(init_lambda) - else: - #following chen and oliver - x = self.last_best_mean / (2.0 * float(self.obsensemble.shape[1])) - self.current_lambda = 10.0**(np.floor(np.log10(x))) - - self.logger.statement("current lambda:{0:15.6g}".format(self.current_lambda)) - - self.delta_par_prior = self._calc_delta_par(self.parensemble_0) - u,s,v = self.delta_par_prior.pseudo_inv_components(eigthresh=self.pst.svd_data.eigthresh) - self.Am = u * s.inv - if self.save_mats: - np.savetxt(self.pst.filename.replace(".pst",'.') + "0.prior_par_diff.dat", self.delta_par_prior.x, fmt="%15.6e") - np.savetxt(self.pst.filename.replace(".pst",'.') + "0.am_u.dat",u.x,fmt="%15.6e") - np.savetxt(self.pst.filename.replace(".pst", '.') + "0.am_v.dat", v.x, fmt="%15.6e") - np.savetxt(self.pst.filename.replace(".pst",'.') + "0.am_s_inv.dat", s.inv.as_2d, fmt="%15.6e") - np.savetxt(self.pst.filename.replace(".pst",'.') + "0.am.dat", self.Am.x, fmt="%15.6e") - - self.__initialized = True
    - -
    [docs] def get_localizer(self): - """ get an empty/generic localizer matrix that can be filled - - Returns - ------- - localizer : pyemu.Matrix - matrix with nnz obs names for rows and adj par names for columns - - """ - onames = self.pst.nnz_obs_names - pnames = self.pst.adj_par_names - localizer = Matrix(x=np.ones((len(onames),len(pnames))),row_names=onames,col_names=pnames) - return localizer
    - - def _calc_delta_par(self,parensemble): - ''' - calc the scaled parameter ensemble differences from the mean - ''' - return self._calc_delta(parensemble, self.parcov_inv_sqrt) - - def _calc_delta_obs(self,obsensemble): - ''' - calc the scaled observation ensemble differences from the mean - ''' - return self._calc_delta(obsensemble.nonzero, self.obscov_inv_sqrt) - - -
    [docs] def update(self,lambda_mults=[1.0],localizer=None,run_subset=None,use_approx=True, - calc_only=False): - """update the iES one GLM cycle - - Parameters - ---------- - lambda_mults : list - a list of lambda multipliers to test. Each lambda mult value will require - evaluating (a subset of) the parameter ensemble. - localizer : pyemu.Matrix - a jacobian localizing matrix - run_subset : int - the number of realizations to test for each lambda_mult value. For example, - if run_subset = 30 and num_reals=100, the first 30 realizations will be run (in - parallel) for each lambda_mult value. Then the best lambda_mult is selected and the - remaining 70 realizations for that lambda_mult value are run (in parallel). - use_approx : bool - a flag to use the MLE or MAP upgrade solution. True indicates use MLE solution - calc_only : bool - a flag to calculate the upgrade matrix only (not run the ensemble). This is mostly for - debugging and testing on travis. Default is False - - Example - ------- - - ``>>>import pyemu`` - - ``>>>es = pyemu.EnsembleSmoother(pst="pest.pst")`` - - ``>>>es.initialize(num_reals=100)`` - - ``>>>es.update(lambda_mults=[0.1,1.0,10.0],run_subset=30)`` - - """ - - #if not self.parensemble.istransformed: - # self.parensemble._transform(inplace=False) - - if run_subset is not None: - if run_subset >= self.obsensemble.shape[0]: - self.logger.warn("run_subset ({0}) >= num of active reals ({1})...ignoring ".\ - format(run_subset,self.obsensemble.shape[0])) - run_subset = None - - self.iter_num += 1 - mat_prefix = self.pst.filename.replace('.pst','')+".{0}".format(self.iter_num) - self.logger.log("iteration {0}".format(self.iter_num)) - self.logger.statement("{0} active realizations".format(self.obsensemble.shape[0])) - if self.obsensemble.shape[0] < 2: - self.logger.lraise("at least active 2 realizations (really like 300) are needed to update") - if not self.__initialized: - #raise Exception("must call initialize() before update()") - self.logger.lraise("must call initialize() before update()") - - self.logger.log("calculate scaled delta obs") - scaled_delta_obs = self._calc_delta_obs(self.obsensemble) - self.logger.log("calculate scaled delta obs") - self.logger.log("calculate scaled delta par") - scaled_delta_par = self._calc_delta_par(self.parensemble) - self.logger.log("calculate scaled delta par") - - self.logger.log("calculate pseudo inv comps") - u,s,v = scaled_delta_obs.pseudo_inv_components(eigthresh=self.pst.svd_data.eigthresh) - self.logger.log("calculate pseudo inv comps") - - self.logger.log("calculate obs diff matrix") - #obs_diff = self.obscov_inv_sqrt * self._get_residual_obs_matrix(self.obsensemble).T - obs_diff = self.obscov_inv_sqrt * self.phi.get_residual_obs_matrix(self.obsensemble).T - self.logger.log("calculate obs diff matrix") - - if self.save_mats: - np.savetxt(mat_prefix + ".obs_diff.dat", scaled_delta_obs.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".par_diff.dat", scaled_delta_par.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".u.dat", u.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".s.dat", s.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".v.dat", v.x, fmt="%15.6e") - # here is the math part...calculate upgrade matrices - mean_lam,std_lam,paren_lam,obsen_lam = [],[],[],[] - lam_vals = [] - for ilam,cur_lam_mult in enumerate(lambda_mults): - - parensemble_cur_lam = self.parensemble.copy() - #print(parensemble_cur_lam.isnull().values.any()) - - cur_lam = self.current_lambda * cur_lam_mult - lam_vals.append(cur_lam) - self.logger.log("calcs for lambda {0}".format(cur_lam_mult)) - scaled_ident = Cov.identity_like(s) * (cur_lam+1.0) - scaled_ident += s**2 - scaled_ident = scaled_ident.inv - - # build up this matrix as a single element so we can apply - # localization - self.logger.log("building upgrade_1 matrix") - upgrade_1 = -1.0 * (self.parcov_inv_sqrt * scaled_delta_par) *\ - v * s * scaled_ident * u.T - if self.save_mats: - np.savetxt(mat_prefix+".ivec.dat".format(self.iter_num), scaled_ident.x, fmt="%15.6e") - self.logger.log("building upgrade_1 matrix") - - # apply localization - if localizer is not None: - self.logger.log("applying localization") - upgrade_1.hadamard_product(localizer) - self.logger.log("applying localization") - - # apply residual information - self.logger.log("applying residuals") - upgrade_1 *= obs_diff - self.logger.log("applying residuals") - - self.logger.log("processing upgrade_1") - if self.save_mats: - np.savetxt(mat_prefix + ".upgrade_1.dat", upgrade_1.T.x, fmt="%15.6e") - upgrade_1 = upgrade_1.to_dataframe() - upgrade_1.index.name = "parnme" - upgrade_1 = upgrade_1.T - upgrade_1.index = [int(i) for i in upgrade_1.index] - upgrade_1.to_csv(self.pst.filename+".upgrade_1.{0:04d}.csv".\ - format(self.iter_num)) - if upgrade_1.isnull().values.any(): - self.logger.lraise("NaNs in upgrade_1") - self.logger.log("processing upgrade_1") - - #print(upgrade_1.isnull().values.any()) - #print(parensemble_cur_lam.index) - #print(upgrade_1.index) - parensemble_cur_lam += upgrade_1 - - # parameter-based upgrade portion - if not use_approx and self.iter_num > 1: - #if True: - self.logger.log("building upgrade_2 matrix") - par_diff = (self.parensemble - self.parensemble_0.loc[self.parensemble.index,:]).\ - as_pyemu_matrix().T - x4 = self.Am.T * self.parcov_inv_sqrt * par_diff - x5 = self.Am * x4 - x6 = scaled_delta_par.T * x5 - x7 = v * scaled_ident * v.T * x6 - ug2_mat = -1.0 * (self.parcov_inv_sqrt * - scaled_delta_par * x7) - upgrade_2 = ug2_mat.to_dataframe() - upgrade_2.index.name = "parnme" - upgrade_2 = upgrade_2.T - upgrade_2.to_csv(self.pst.filename+".upgrade_2.{0:04d}.csv".\ - format(self.iter_num)) - upgrade_2.index = [int(i) for i in upgrade_2.index] - - if self.save_mats: - np.savetxt(mat_prefix + ".scaled_par_resid.dat", par_diff.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".x4.dat", x4.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".x5.dat", x5.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".x6.dat", x6.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".x7.dat", x7.x, fmt="%15.6e") - np.savetxt(mat_prefix + ".upgrade_2.dat", ug2_mat.T.x, fmt="%15.6e") - - if upgrade_2.isnull().values.any(): - self.logger.lraise("NaNs in upgrade_2") - - parensemble_cur_lam += upgrade_2 - self.logger.log("building upgrade_2 matrix") - self.logger.log("enforcing bounds") - parensemble_cur_lam.enforce(self.enforce_bounds) - self.logger.log("enforcing bounds") - - self.logger.log("filling fixed parameters") - #fill in fixed pars with initial values - fi = parensemble_cur_lam.fixed_indexer - li = parensemble_cur_lam.log_indexer - log_values = self.pst.parameter_data.loc[:,"parval1"].copy() - log_values.loc[li] = log_values.loc[li].apply(np.log10) - fixed_vals = log_values.loc[fi] - - for fname, fval in zip(fixed_vals.index, fixed_vals.values): - # if fname not in df.columns: - # continue - # print(fname) - parensemble_cur_lam.loc[:, fname] = fval - self.logger.log("filling fixed parameters") - # this is for testing failed runs on upgrade testing - # works with the 10par_xsec smoother test - #parensemble_cur_lam.iloc[:,:] = -1000000.0 - - # some hackery - we lose track of the transform flag here, but just - # know it is transformed. Need to create dataframe here because - # pd.concat doesn't like par ensembles later - paren_lam.append(pd.DataFrame(parensemble_cur_lam.loc[:,:])) - self.logger.log("calcs for lambda {0}".format(cur_lam_mult)) - - if calc_only: - return - - - # subset if needed - # and combine lambda par ensembles into one par ensemble for evaluation - if run_subset is not None and run_subset < self.parensemble.shape[0]: - #subset_idx = ["{0:d}".format(i) for i in np.random.randint(0,self.parensemble.shape[0]-1,run_subset)] - subset_idx = self.parensemble.iloc[:run_subset,:].index.values - self.logger.statement("subset idxs: " + ','.join([str(s) for s in subset_idx])) - - # more tracking of transformed - just know it! Creating dataframes... - paren_lam_subset = [pe.loc[subset_idx,:] for pe in paren_lam] - paren_combine = pd.concat(paren_lam_subset,ignore_index=True) - paren_lam_subset = None - else: - subset_idx = self.parensemble.index.values - paren_combine = pd.concat(paren_lam,ignore_index=True) - - - self.logger.log("evaluating ensembles for lambdas : {0}".\ - format(','.join(["{0:8.3E}".format(l) for l in lam_vals]))) - # back to par ensemble and know it is transformed - paren_combine = ParameterEnsemble.from_dataframe(df=paren_combine,pst=self.pst,istransformed=True) - failed_runs, obsen_combine = self._calc_obs(paren_combine) - self.logger.log("evaluating ensembles for lambdas : {0}".\ - format(','.join(["{0:8.3E}".format(l) for l in lam_vals]))) - paren_combine = None - - if failed_runs is not None and len(failed_runs) == obsen_combine.shape[0]: - self.logger.lraise("all runs failed - cannot continue") - - - # unpack lambda obs ensembles from combined obs ensemble - nrun_per_lam = self.obsensemble.shape[0] - if run_subset is not None: - nrun_per_lam = run_subset - obsen_lam = [] - - for i in range(len(lam_vals)): - sidx = i * nrun_per_lam - eidx = sidx + nrun_per_lam - oe = ObservationEnsemble.from_dataframe(df=obsen_combine.iloc[sidx:eidx,:].copy(), - pst=self.pst) - oe.index = subset_idx - # check for failed runs in this set - drop failed runs from obs ensembles - if failed_runs is not None: - failed_runs_this = np.array([f for f in failed_runs if f >= sidx and f < eidx]) - sidx - if len(failed_runs_this) > 0: - if len(failed_runs_this) == oe.shape[0]: - self.logger.warn("all runs failed for lambda {0}".format(lam_vals[i])) - else: - self.logger.warn("{0} run failed for lambda {1}".\ - format(len(failed_runs_this),lam_vals[i])) - oe.iloc[failed_runs_this,:] = np.NaN - oe = oe.dropna() - paren_lam[i].iloc[failed_runs_this,:] = np.NaN - paren_lam[i] = ParameterEnsemble.from_dataframe(df=paren_lam[i].dropna(),pst=self.pst) - paren_lam[i].__instransformed = True - - # don't drop bad reals here, instead, mask bad reals in the lambda - # selection and drop later - # if self.drop_bad_reals is not None: - # assert isinstance(drop_bad_reals, float) - # drop_idx = np.argwhere(self.current_phi_vec > self.drop_bad_reals).flatten() - # run_ids = self.obsensemble.index.values - # drop_idx = run_ids[drop_idx] - # if len(drop_idx) == self.obsensemble.shape[0]: - # raise Exception("dropped all realizations as 'bad'") - # if len(drop_idx) > 0: - # self.logger.warn("{0} realizations dropped as 'bad' (indices :{1})". \ - # format(len(drop_idx), ','.join([str(d) for d in drop_idx]))) - # self.parensemble.loc[drop_idx, :] = np.NaN - # self.parensemble = self.parensemble.dropna() - # self.obsensemble.loc[drop_idx, :] = np.NaN - # self.obsensemble = self.obsensemble.dropna() - # - # self.current_phi_vec = self._calc_phi_vec(self.obsensemble) - - obsen_lam.append(oe) - obsen_combine = None - - # here is where we need to select out the "best" lambda par and obs - # ensembles - - #phi_vecs = [self._calc_phi_vec(obsen) for obsen in obsen_lam] - #phi_vecs_reg = [self._calc_regul_phi_vec(paren) for paren in paren_lam] - #if self.regul_factor > 0.0: - # for i,(pv,prv) in enumerate(zip(phi_vecs,phi_vecs_reg)): - # phi_vecs[i] = pv + (prv * self.regul_factor) - self.logger.log("calc lambda phi vectors") - phi_vecs = [self.phi.get_meas_and_regul_phi(oe,pe.loc[oe.index,:]) for oe,pe in zip(obsen_lam,paren_lam)] - self.logger.log("calc lambda phi vectors") - if self.drop_bad_reals is not None: - for i,(meas_pv,regul_pv) in enumerate(phi_vecs): - #for testing the drop_bad_reals functionality - #pv[[0,3,7]] = self.drop_bad_reals + 1.0 - regul_pv = regul_pv.copy() - regul_pv[meas_pv>self.drop_bad_reals] = np.NaN - regul_pv = regul_pv[~np.isnan(regul_pv)] - meas_pv[meas_pv>self.drop_bad_reals] = np.NaN - meas_pv = meas_pv[~np.isnan(meas_pv)] - if len(meas_pv) == 0: - #raise Exception("all realization for lambda {0} dropped as 'bad'".\ - # format(lam_vals[i])) - self.logger.warn("all realizations for lambda {0} marked as 'bad'") - meas_pv = np.zeros_like(obsen_lam[0].shape[0]) + 1.0e+30 - regul_pv = np.zeros_like(obsen_lam[0].shape[0]) + 1.0e+30 - phi_vecs[i] = (meas_pv,regul_pv) - mean_std_meas = [(pv[0].mean(),pv[0].std()) for pv in phi_vecs] - mean_std_regul = [(pv[1].mean(), pv[1].std()) for pv in phi_vecs] - update_pars = False - update_lambda = False - self.logger.statement("**************************") - # self.logger.statement(str(datetime.now())) - self.logger.statement("lambda testing summary") - self.logger.statement("total runs:{0}".format(self.total_runs)) - self.logger.statement("iteration: {0}".format(self.iter_num)) - self.logger.statement("current lambda:{0:15.6G}, mean:{1:15.6G}, std:{2:15.6G}". \ - format(self.current_lambda, - self.last_best_mean, self.last_best_std)) - - # accept a new best if its within 10% - best_mean = self.last_best_mean * 1.1 - best_std = self.last_best_std * 1.1 - best_i = 0 - for i,((mm,ms),(rm,rs)) in enumerate(zip(mean_std_meas,mean_std_regul)): - self.logger.statement(" tested lambda:{0:15.6G}, meas mean:{1:15.6G}, meas std:{2:15.6G}". - format(self.current_lambda * lambda_mults[i],mm,ms)) - self.logger.statement("{0:30s}regul mean:{1:15.6G}, regul std:{2:15.6G}".\ - format(' ',rm,rs)) - m = mm + (self.regul_factor * rm) - s = ms + (self.regul_factor * rs) - if m < best_mean: - update_pars = True - best_mean = m - best_i = i - if s < best_std: - update_lambda = True - best_std = s - if np.isnan(best_mean): - self.logger.lraise("best mean = NaN") - if np.isnan(best_std): - self.logger.lraise("best std = NaN") - - if not update_pars: - self.current_lambda *= max(lambda_mults) * 10.0 - self.current_lambda = min(self.current_lambda,100000) - self.logger.statement("not accepting iteration, increased lambda:{0}".\ - format(self.current_lambda)) - else: - #more transformation status hard coding - ugly - self.parensemble = ParameterEnsemble.from_dataframe(df=paren_lam[best_i],pst=self.pst,istransformed=True) - if run_subset is not None: - failed_runs, self.obsensemble = self._calc_obs(self.parensemble) - if failed_runs is not None: - self.logger.warn("dropping failed realizations") - self.parensemble.loc[failed_runs, :] = np.NaN - self.parensemble = self.parensemble.dropna() - self.obsensemble.loc[failed_runs, :] = np.NaN - self.obsensemble = self.obsensemble.dropna() - - self.phi.update() - best_mean = self.phi.comp_phi.mean() - best_std = self.phi.comp_phi.std() - else: - self.obsensemble = obsen_lam[best_i] - # reindex parensemble in case failed runs - self.parensemble = ParameterEnsemble.from_dataframe(df=self.parensemble.loc[self.obsensemble.index], - pst=self.pst, - istransformed=self.parensemble.istransformed) - self.phi.update() - if self.drop_bad_reals is not None: - # for testing drop_bad_reals functionality - # self.current_phi_vec[::2] = self.drop_bad_reals + 1.0 - #drop_idx = np.argwhere(self.current_phi_vec > self.drop_bad_reals).flatten() - drop_idx = np.argwhere(self.phi.comp_phi > self.drop_bad_reals).flatten() - run_ids = self.obsensemble.index.values - drop_idx = run_ids[drop_idx] - if len(drop_idx) > self.obsensemble.shape[0] - 3: - raise Exception("dropped too many realizations as 'bad'") - if len(drop_idx) > 0: - self.logger.warn("{0} realizations dropped as 'bad' (indices :{1})". \ - format(len(drop_idx), ','.join([str(d) for d in drop_idx]))) - self.parensemble.loc[drop_idx, :] = np.NaN - self.parensemble = self.parensemble.dropna() - self.obsensemble.loc[drop_idx, :] = np.NaN - self.obsensemble = self.obsensemble.dropna() - - self.phi.update() - best_mean = self.phi.comp_phi.mean() - best_std = self.phi.comp_phi.std() - - self.phi.report(cur_lam=self.current_lambda * lambda_mults[best_i]) - - self.logger.statement(" best lambda:{0:15.6G}, mean:{1:15.6G}, std:{2:15.6G}".\ - format(self.current_lambda*lambda_mults[best_i], - best_mean,best_std)) - #self.logger.statement(" actual mean phi: {0:15.6G}".format(float(self.current_actual_phi.mean()))) - self.last_best_mean = best_mean - self.last_best_std = best_std - - - if update_lambda: - # be aggressive - self.current_lambda *= (lambda_mults[best_i] * 0.75) - # but don't let lambda get too small - self.current_lambda = max(self.current_lambda,0.00001) - self.logger.statement("updating lambda: {0:15.6G}".\ - format(self.current_lambda )) - - self.logger.statement("**************************\n") - self.parensemble.to_csv(self.pst.filename+self.paren_prefix.\ - format(self.iter_num)) - self.obsensemble.to_csv(self.pst.filename+self.obsen_prefix.\ - format(self.iter_num)) - if self.raw_sweep_out is not None: - self.raw_sweep_out.to_csv(self.pst.filename+"_sweepraw{0}.csv".\ - format(self.iter_num)) - self.logger.log("iteration {0}".format(self.iter_num))
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/utils/geostats.html b/docs/_build/html/_modules/pyemu/utils/geostats.html deleted file mode 100644 index fa6bc762b..000000000 --- a/docs/_build/html/_modules/pyemu/utils/geostats.html +++ /dev/null @@ -1,2118 +0,0 @@ - - - - - - - - pyemu.utils.geostats — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.utils.geostats

    -"""Geostatistical analyses within the pyemu framework.
    -Support for Ordinary Kriging as well as construction of
    -covariance matrices from (nested) geostistical structures.
    -Also support for reading GSLIB and SGEMS files
    -"""
    -from __future__ import print_function
    -import os
    -import copy
    -from datetime import datetime
    -import multiprocessing as mp
    -import warnings
    -import numpy as np
    -import scipy.sparse
    -import pandas as pd
    -from pyemu.mat.mat_handler import Cov,SparseMatrix
    -from pyemu.utils.pp_utils import pp_file_to_dataframe
    -from ..pyemu_warnings import PyemuWarning
    -
    -EPSILON = 1.0e-7
    -
    -# class KrigeFactors(pd.DataFrame):
    -#     def __init__(self,*args,**kwargs):
    -#         super(KrigeFactors,self).__init__(*args,**kwargs)
    -#
    -#     def to_factors(self,filename,nrow,ncol,
    -#                    points_file="points.junk",
    -#                    zone_file="zone.junk"):
    -#         with open(filename,'w') as f:
    -#             f.write(points_file+'\n')
    -#             f.write(zone_file+'\n')
    -#             f.write("{0} {1}\n".format(ncol,nrow))
    -#             f.write("{0}\n".format(self.shape[0]))
    -#
    -#
    -#
    -#     def from_factors(self,filename):
    -#         raise NotImplementedError()
    -
    -
    -
    -
    [docs]class GeoStruct(object): - """a geostatistical structure object. The object contains - variograms and (optionally) nugget information. - - Parameters - ---------- - nugget : (float) - nugget contribution - variograms : (list or Vario2d instance) - variogram(s) associated with this GeoStruct instance - name : (str) - name to assign the structure. Default is "struct1". - transform : (str) - the transformation to apply to the GeoStruct. Can be - "none" or "log", depending on the transformation of the - property being represented by the GeoStruct. - Default is "none - - Attributes - ---------- - variograms : list - the Vario2d objects associated with the GeoStruct - name : str - the name of the GeoStruct - transform : str - the transform of the GeoStruct - - Example - ------- - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)`` - - ``>>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)`` - - """ - def __init__(self,nugget=0.0,variograms=[],name="struct1", - transform="none"): - self.name = name - self.nugget = float(nugget) - if not isinstance(variograms,list): - variograms = [variograms] - for vario in variograms: - assert isinstance(vario,Vario2d) - self.variograms = variograms - transform = transform.lower() - assert transform in ["none","log"] - self.transform = transform - -
    [docs] def to_struct_file(self, f): - """ write a PEST-style structure file - - Parameters - ---------- - f : (str or file handle) - file to write the GeoStruct information to - - """ - if isinstance(f, str): - f = open(f,'w') - f.write("STRUCTURE {0}\n".format(self.name)) - f.write(" NUGGET {0}\n".format(self.nugget)) - f.write(" NUMVARIOGRAM {0}\n".format(len(self.variograms))) - for v in self.variograms: - f.write(" VARIOGRAM {0} {1}\n".format(v.name,v.contribution)) - f.write(" TRANSFORM {0}\n".format(self.transform)) - f.write("END STRUCTURE\n\n") - for v in self.variograms: - v.to_struct_file(f)
    - -
    [docs] def sparse_covariance_matrix(self,x,y,names): - """build a pyemu.Cov instance from GeoStruct - - Parameters - ---------- - x : (iterable of floats) - x-coordinate locations - y : (iterable of floats) - y-coordinate locations - names : (iterable of str) - (parameter) names of locations. - - Returns - ------- - sparse : pyemu.SparseMatrix - the sparse covariance matrix implied by this GeoStruct for the x,y pairs. - - Example - ------- - ``>>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")`` - - ``>>>cov = gs.covariance_matrix(pp_df.x,pp_df.y,pp_df.name)`` - - - """ - - if not isinstance(x, np.ndarray): - x = np.array(x) - if not isinstance(y, np.ndarray): - y = np.array(y) - assert x.shape[0] == y.shape[0] - assert x.shape[0] == len(names) - - iidx = [i for i in range(len(names))] - jidx = list(iidx) - data = list(np.zeros(x.shape[0])+self.nugget) - - for v in self.variograms: - v.add_sparse_covariance_matrix(x,y,names,iidx,jidx,data) - coo = scipy.sparse.coo_matrix((data,(iidx,jidx)),shape=(len(names),len(names))) - coo.eliminate_zeros() - coo.sum_duplicates() - return SparseMatrix(coo,row_names=names,col_names=names)
    - - -
    [docs] def covariance_matrix(self,x,y,names=None,cov=None): - """build a pyemu.Cov instance from GeoStruct - - Parameters - ---------- - x : (iterable of floats) - x-coordinate locations - y : (iterable of floats) - y-coordinate locations - names : (iterable of str) - names of location. If None, cov must not be None. Default is None. - cov : (pyemu.Cov) instance - an existing Cov instance. The contribution of this GeoStruct is added - to cov. If cov is None, names must not be None. Default is None - - Returns - ------- - cov : pyemu.Cov - the covariance matrix implied by this GeoStruct for the x,y pairs. - cov has row and column names supplied by the names argument unless - the "cov" argument was passed. - - Note - ---- - either "names" or "cov" must be passed. If "cov" is passed, cov.shape - must equal len(x) and len(y). - - Example - ------- - ``>>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")`` - - ``>>>cov = gs.covariance_matrix(pp_df.x,pp_df.y,pp_df.name)`` - - - """ - - if not isinstance(x,np.ndarray): - x = np.array(x) - if not isinstance(y,np.ndarray): - y = np.array(y) - assert x.shape[0] == y.shape[0] - - if names is not None: - assert x.shape[0] == len(names) - c = np.zeros((len(names),len(names))) - np.fill_diagonal(c,self.nugget) - cov = Cov(x=c,names=names) - elif cov is not None: - assert cov.shape[0] == x.shape[0] - names = cov.row_names - c = np.zeros((len(names),1)) - c += self.nugget - cont = Cov(x=c,names=names,isdiagonal=True) - cov += cont - - else: - raise Exception("GeoStruct.covariance_matrix() requires either " + - "names or cov arg") - for v in self.variograms: - v.covariance_matrix(x,y,cov=cov) - return cov
    - -
    [docs] def covariance(self,pt0,pt1): - """get the covariance between two points implied by the GeoStruct. - This is used during the ordinary kriging process to get the RHS - - Parameters - ---------- - pt0 : (iterable length 2 of floats) - pt1 : (iterable length 2 of floats) - - Returns - ------- - covariance : float - the covariance between pt0 and pt1 implied by the GeoStruct - - """ - #raise Exception() - cov = self.nugget - for vario in self.variograms: - cov += vario.covariance(pt0,pt1) - return cov
    - -
    [docs] def covariance_points(self,x0,y0,xother,yother): - """ Get the covariance between point x0,y0 and the points - contained in xother, yother. - - Parameters - ---------- - x0 : (float) - x-coordinate - y0 : (float) - y-coordinate - xother : (iterable of floats) - x-coordinate of other points - yother : (iterable of floats) - y-coordinate of other points - - Returns - ------- - cov : numpy.ndarray - a 1-D array of covariance between point x0,y0 and the - points contained in xother, yother. len(cov) = len(xother) = - len(yother) - - """ - - cov = np.zeros((len(xother))) + self.nugget - for v in self.variograms: - cov += v.covariance_points(x0,y0,xother,yother) - return cov
    - - @property - def sill(self): - """ get the sill of the GeoStruct - - Return - ------ - sill : float - the sill of the (nested) GeoStruct, including nugget and contribution - from each variogram - """ - sill = self.nugget - for v in self.variograms: - sill += v.contribution - return sill - - -
    [docs] def plot(self,**kwargs): - """ make a cheap plot of the GeoStruct - - Parameters - ---------- - **kwargs : (dict) - keyword arguments to use for plotting. - - Returns - ------- - ax : matplotlib.pyplot.axis - the axis with the GeoStruct plot - - Note - ---- - optional arguments include "ax" (an existing axis), - "individuals" (plot each variogram on a separate axis), - "legend" (add a legend to the plot(s)). All other kwargs - are passed to matplotlib.pyplot.plot() - - """ - # - if "ax" in kwargs: - ax = kwargs.pop("ax") - else: - import matplotlib.pyplot as plt - ax = plt.subplot(111) - legend = kwargs.pop("legend",False) - individuals = kwargs.pop("individuals",False) - xmx = max([v.a*3.0 for v in self.variograms]) - x = np.linspace(0,xmx,100) - y = np.zeros_like(x) - for v in self.variograms: - yv = v.inv_h(x) - if individuals: - ax.plot(x,yv,label=v.name,**kwargs) - y += yv - y += self.nugget - ax.plot(x,y,label=self.name,**kwargs) - if legend: - ax.legend() - ax.set_xlabel("distance") - ax.set_ylabel("$\gamma$") - return ax
    - - def __str__(self): - """ the str representation of the GeoStruct - - Returns - ------- - str : str - the str representation of the GeoStruct - """ - s = '' - s += 'name:{0},nugget:{1},structures:\n'.format(self.name,self.nugget) - for v in self.variograms: - s += str(v) - return s
    - - -# class LinearUniversalKrige(object): -# def __init__(self,geostruct,point_data): -# if isinstance(geostruct,str): -# geostruct = read_struct_file(geostruct) -# assert isinstance(geostruct,GeoStruct),"need a GeoStruct, not {0}".\ -# format(type(geostruct)) -# self.geostruct = geostruct -# if isinstance(point_data,str): -# point_data = pp_file_to_dataframe(point_data) -# assert isinstance(point_data,pd.DataFrame) -# assert 'name' in point_data.columns,"point_data missing 'name'" -# assert 'x' in point_data.columns, "point_data missing 'x'" -# assert 'y' in point_data.columns, "point_data missing 'y'" -# assert "value" in point_data.columns,"point_data missing 'value'" -# self.point_data = point_data -# self.point_data.index = self.point_data.name -# self.interp_data = None -# self.spatial_reference = None -# #X, Y = np.meshgrid(point_data.x,point_data.y) -# #self.point_data_dist = pd.DataFrame(data=np.sqrt((X - X.T) ** 2 + (Y - Y.T) ** 2), -# # index=point_data.name,columns=point_data.name) -# self.point_cov_df = self.geostruct.covariance_matrix(point_data.x, -# point_data.y, -# point_data.name).to_dataframe() -# #for name in self.point_cov_df.index: -# # self.point_cov_df.loc[name,name] -= self.geostruct.nugget -# -# -# def estimate_grid(self,spatial_reference,zone_array=None,minpts_interp=1, -# maxpts_interp=20,search_radius=1.0e+10,verbose=False, -# var_filename=None): -# -# self.spatial_reference = spatial_reference -# self.interp_data = None -# #assert isinstance(spatial_reference,SpatialReference) -# try: -# x = self.spatial_reference.xcentergrid.copy() -# y = self.spatial_reference.ycentergrid.copy() -# except Exception as e: -# raise Exception("spatial_reference does not have proper attributes:{0}"\ -# .format(str(e))) -# -# if var_filename is not None: -# arr = np.zeros((self.spatial_reference.nrow, -# self.spatial_reference.ncol)) - 1.0e+30 -# -# df = self.estimate(x.ravel(),y.ravel(), -# minpts_interp=minpts_interp, -# maxpts_interp=maxpts_interp, -# search_radius=search_radius, -# verbose=verbose) -# if var_filename is not None: -# arr = df.err_var.values.reshape(x.shape) -# np.savetxt(var_filename,arr,fmt="%15.6E") -# arr = df.estimate.values.reshape(x.shape) -# return arr -# -# -# def estimate(self,x,y,minpts_interp=1,maxpts_interp=20, -# search_radius=1.0e+10,verbose=False): -# assert len(x) == len(y) -# -# # find the point data to use for each interp point -# sqradius = search_radius**2 -# df = pd.DataFrame(data={'x':x,'y':y}) -# inames,idist,ifacts,err_var = [],[],[],[] -# estimates = [] -# sill = self.geostruct.sill -# pt_data = self.point_data -# ptx_array = pt_data.x.values -# pty_array = pt_data.y.values -# ptnames = pt_data.name.values -# #if verbose: -# print("starting interp point loop for {0} points".format(df.shape[0])) -# start_loop = datetime.now() -# for idx,(ix,iy) in enumerate(zip(df.x,df.y)): -# if np.isnan(ix) or np.isnan(iy): #if nans, skip -# inames.append([]) -# idist.append([]) -# ifacts.append([]) -# err_var.append(np.NaN) -# continue -# if verbose: -# istart = datetime.now() -# print("processing interp point:{0} of {1}".format(idx,df.shape[0])) -# # if verbose == 2: -# # start = datetime.now() -# # print("calc ipoint dist...",end='') -# -# # calc dist from this interp point to all point data...slow -# dist = pd.Series((ptx_array-ix)**2 + (pty_array-iy)**2,ptnames) -# dist.sort_values(inplace=True) -# dist = dist.loc[dist <= sqradius] -# -# # if too few points were found, skip -# if len(dist) < minpts_interp: -# inames.append([]) -# idist.append([]) -# ifacts.append([]) -# err_var.append(sill) -# estimates.append(np.NaN) -# continue -# -# # only the maxpts_interp points -# dist = dist.iloc[:maxpts_interp].apply(np.sqrt) -# pt_names = dist.index.values -# # if one of the points is super close, just use it and skip -# if dist.min() <= EPSILON: -# ifacts.append([1.0]) -# idist.append([EPSILON]) -# inames.append([dist.idxmin()]) -# err_var.append(self.geostruct.nugget) -# estimates.append(self.point_data.loc[dist.idxmin(),"value"]) -# continue -# # if verbose == 2: -# # td = (datetime.now()-start).total_seconds() -# # print("...took {0}".format(td)) -# # start = datetime.now() -# # print("extracting pt cov...",end='') -# -# #vextract the point-to-point covariance matrix -# point_cov = self.point_cov_df.loc[pt_names,pt_names] -# # if verbose == 2: -# # td = (datetime.now()-start).total_seconds() -# # print("...took {0}".format(td)) -# # print("forming ipt-to-point cov...",end='') -# -# # calc the interp point to points covariance -# ptx = self.point_data.loc[pt_names,"x"] -# pty = self.point_data.loc[pt_names,"y"] -# interp_cov = self.geostruct.covariance_points(ix,iy,ptx,pty) -# -# if verbose == 2: -# td = (datetime.now()-start).total_seconds() -# print("...took {0}".format(td)) -# print("forming lin alg components...",end='') -# -# # form the linear algebra parts and solve -# d = len(pt_names) + 3 # +1 for lagrange mult + 2 for x and y coords -# npts = len(pt_names) -# A = np.ones((d,d)) -# A[:npts,:npts] = point_cov.values -# A[npts,npts] = 0.0 #unbiaised constraint -# A[-2,:npts] = ptx #x coords for linear trend -# A[:npts,-2] = ptx -# A[-1,:npts] = pty #y coords for linear trend -# A[:npts,-1] = pty -# A[npts:,npts:] = 0 -# print(A) -# rhs = np.ones((d,1)) -# rhs[:npts,0] = interp_cov -# rhs[-2,0] = ix -# rhs[-1,0] = iy -# # if verbose == 2: -# # td = (datetime.now()-start).total_seconds() -# # print("...took {0}".format(td)) -# # print("solving...",end='') -# # # solve -# facs = np.linalg.solve(A,rhs) -# assert len(facs) - 3 == len(dist) -# estimate = facs[-3] + (ix * facs[-2]) + (iy * facs[-1]) -# estimates.append(estimate[0]) -# err_var.append(float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)]))) -# inames.append(pt_names) -# -# idist.append(dist.values) -# ifacts.append(facs[:-1,0]) -# # if verbose == 2: -# # td = (datetime.now()-start).total_seconds() -# # print("...took {0}".format(td)) -# if verbose: -# td = (datetime.now()-istart).total_seconds() -# print("point took {0}".format(td)) -# df["idist"] = idist -# df["inames"] = inames -# df["ifacts"] = ifacts -# df["err_var"] = err_var -# df["estimate"] = estimates -# self.interp_data = df -# td = (datetime.now() - start_loop).total_seconds() -# print("took {0}".format(td)) -# return df - - -
    [docs]class OrdinaryKrige(object): - """ Ordinary Kriging using Pandas and Numpy. - - Parameters - ---------- - geostruct : (GeoStruct) - a pyemu.geostats.GeoStruct to use for the kriging - point_data : (pandas.DataFrame) - the conditioning points to use for kriging. point_data must contain - columns "name", "x", "y". - - Note - ---- - if point_data is an str, then it is assumed to be a pilot points file - and is loaded as such using pyemu.pp_utils.pp_file_to_dataframe() - - If zoned interpolation is used for grid-based interpolation, then - point_data must also contain a "zone" column - - - Example - ------- - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)`` - - ``>>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)`` - - ``>>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")`` - - ``>>>ok = pyemu.utils.geostats.OrdinaryKrige(gs,pp_df)`` - """ - - def __init__(self,geostruct,point_data): - if isinstance(geostruct,str): - geostruct = read_struct_file(geostruct) - assert isinstance(geostruct,GeoStruct),"need a GeoStruct, not {0}".\ - format(type(geostruct)) - self.geostruct = geostruct - if isinstance(point_data,str): - point_data = pp_file_to_dataframe(point_data) - assert isinstance(point_data,pd.DataFrame) - assert 'name' in point_data.columns,"point_data missing 'name'" - assert 'x' in point_data.columns, "point_data missing 'x'" - assert 'y' in point_data.columns, "point_data missing 'y'" - #check for duplicates in point data - unique_name = point_data.name.unique() - if len(unique_name) != point_data.shape[0]: - warnings.warn("duplicates detected in point_data..attempting to rectify",PyemuWarning) - ux_std = point_data.groupby(point_data.name).std()['x'] - if ux_std.max() > 0.0: - raise Exception("duplicate point_info entries with name {0} have different x values" - .format(uname)) - uy_std = point_data.groupby(point_data.name).std()['y'] - if uy_std.max() > 0.0: - raise Exception("duplicate point_info entries with name {0} have different y values" - .format(uname)) - - self.point_data = point_data.drop_duplicates(subset=["name"]) - else: - self.point_data = point_data.copy() - self.point_data.index = self.point_data.name - self.check_point_data_dist() - self.interp_data = None - self.spatial_reference = None - #X, Y = np.meshgrid(point_data.x,point_data.y) - #self.point_data_dist = pd.DataFrame(data=np.sqrt((X - X.T) ** 2 + (Y - Y.T) ** 2), - # index=point_data.name,columns=point_data.name) - self.point_cov_df = self.geostruct.covariance_matrix(self.point_data.x, - self.point_data.y, - self.point_data.name).to_dataframe() - #for name in self.point_cov_df.index: - # self.point_cov_df.loc[name,name] -= self.geostruct.nugget - -
    [docs] def check_point_data_dist(self, rectify=False): - """ check for point_data entries that are closer than - EPSILON distance - this will cause a singular kriging matrix. - - Parameters - ---------- - rectify : (boolean) - flag to fix the problems with point_data - by dropping additional points that are - closer than EPSILON distance. Default is False - - Note - ---- - this method will issue warnings for points that are closer - than EPSILON distance - - """ - - ptx_array = self.point_data.x.values - pty_array = self.point_data.y.values - ptnames = self.point_data.name.values - drop = [] - for i in range(self.point_data.shape[0]): - ix,iy,iname = ptx_array[i],pty_array[i],ptnames[i] - dist = pd.Series((ptx_array[i+1:] - ix) ** 2 + (pty_array[i+1:] - iy) ** 2, ptnames[i+1:]) - if dist.min() < EPSILON**2: - print(iname,ix,iy) - warnings.warn("points {0} and {1} are too close. This will cause a singular kriging matrix ".\ - format(iname,dist.idxmin()),PyemuWarning) - drop_idxs = dist.loc[dist<=EPSILON**2] - drop.extend([pt for pt in list(drop_idxs.index) if pt not in drop]) - if rectify and len(drop) > 0: - print("rectifying point data by removing the following points: {0}".format(','.join(drop))) - print(self.point_data.shape) - self.point_data = self.point_data.loc[self.point_data.index.map(lambda x: x not in drop),:] - print(self.point_data.shape)
    - - #def prep_for_ppk2fac(self,struct_file="structure.dat",pp_file="points.dat",): - # pass - - - -
    [docs] def calc_factors_grid(self,spatial_reference,zone_array=None,minpts_interp=1, - maxpts_interp=20,search_radius=1.0e+10,verbose=False, - var_filename=None, forgive=False): - """ calculate kriging factors (weights) for a structured grid. - - Parameters - ---------- - spatial_reference : (flopy.utils.reference.SpatialReference) - a spatial reference that describes the orientation and - spatail projection of the the structured grid - zone_array : (numpy.ndarray) - an integer array of zones to use for kriging. If not None, - then point_data must also contain a "zone" column. point_data - entries with a zone value not found in zone_array will be skipped. - If None, then all point_data will (potentially) be used for - interpolating each grid node. Default is None - minpts_interp : (int) - minimum number of point_data entires to use for interpolation at - a given grid node. grid nodes with less than minpts_interp - point_data found will be skipped (assigned np.NaN). Defaut is 1 - maxpts_interp : (int) - maximum number of point_data entries to use for interpolation at - a given grid node. A larger maxpts_interp will yield "smoother" - interplation, but using a large maxpts_interp will slow the - (already) slow kriging solution process and may lead to - memory errors. Default is 20. - search_radius : (float) - the size of the region around a given grid node to search for - point_data entries. Default is 1.0e+10 - verbose : (boolean) - a flag to echo process to stdout during the interpolatino process. - Default is False - var_filename : (str) - a filename to save the kriging variance for each interpolated grid node. - Default is None. - forgive : (boolean) - flag to continue if inversion of the kriging matrix failes at one or more - grid nodes. Inversion usually fails if the kriging matrix is singular, - resulting from point_data entries closer than EPSILON distance. If True, - warnings are issued for each failed inversion. If False, an exception - is raised for failed matrix inversion. - - Returns - ------- - df : pandas.DataFrame - a dataframe with information summarizing the ordinary kriging - process for each grid node - - Note - ---- - this method calls OrdinaryKrige.calc_factors() - - - Example - ------- - ``>>>import flopy`` - - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)`` - - ``>>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)`` - - ``>>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")`` - - ``>>>ok = pyemu.utils.geostats.OrdinaryKrige(gs,pp_df)`` - - ``>>>m = flopy.modflow.Modflow.load("mymodel.nam")`` - - ``>>>df = ok.calc_factors_grid(m.sr,zone_array=m.bas6.ibound[0].array,`` - - ``>>> var_filename="ok_var.dat")`` - - ``>>>ok.to_grid_factor_file("factors.dat")`` - - """ - - self.spatial_reference = spatial_reference - self.interp_data = None - #assert isinstance(spatial_reference,SpatialReference) - try: - x = self.spatial_reference.xcentergrid.copy() - y = self.spatial_reference.ycentergrid.copy() - except Exception as e: - raise Exception("spatial_reference does not have proper attributes:{0}"\ - .format(str(e))) - - if var_filename is not None: - arr = np.zeros((self.spatial_reference.nrow, - self.spatial_reference.ncol)) - 1.0e+30 - - # the simple case of no zone array: ignore point_data zones - if zone_array is None: - df = self.calc_factors(x.ravel(),y.ravel(), - minpts_interp=minpts_interp, - maxpts_interp=maxpts_interp, - search_radius=search_radius, - verbose=verbose, forgive=forgive) - if var_filename is not None: - arr = df.err_var.values.reshape(x.shape) - np.savetxt(var_filename,arr,fmt="%15.6E") - - if zone_array is not None: - assert zone_array.shape == x.shape - if "zone" not in self.point_data.columns: - warnings.warn("'zone' columns not in point_data, assigning generic zone",PyemuWarning) - self.point_data.loc[:,"zone"] = 1 - pt_data_zones = self.point_data.zone.unique() - dfs = [] - for pt_data_zone in pt_data_zones: - if pt_data_zone not in zone_array: - warnings.warn("pt zone {0} not in zone array {1}, skipping".\ - format(pt_data_zone,np.unique(zone_array)),PyemuWarning) - continue - xzone,yzone = x.copy(),y.copy() - xzone[zone_array!=pt_data_zone] = np.NaN - yzone[zone_array!=pt_data_zone] = np.NaN - df = self.calc_factors(xzone.ravel(),yzone.ravel(), - minpts_interp=minpts_interp, - maxpts_interp=maxpts_interp, - search_radius=search_radius, - verbose=verbose,pt_zone=pt_data_zone, - forgive=forgive) - dfs.append(df) - if var_filename is not None: - a = df.err_var.values.reshape(x.shape) - na_idx = np.isfinite(a) - arr[na_idx] = a[na_idx] - if self.interp_data is None or self.interp_data.dropna().shape[0] == 0: - raise Exception("no interpolation took place...something is wrong") - df = pd.concat(dfs) - if var_filename is not None: - np.savetxt(var_filename,arr,fmt="%15.6E") - return df
    - -
    [docs] def calc_factors(self,x,y,minpts_interp=1,maxpts_interp=20, - search_radius=1.0e+10,verbose=False, - pt_zone=None,forgive=False): - """ calculate ordinary kriging factors (weights) for the points - represented by arguments x and y - - Parameters - ---------- - x : (iterable of floats) - x-coordinates to calculate kriging factors for - y : (iterable of floats) - y-coordinates to calculate kriging factors for - minpts_interp : (int) - minimum number of point_data entires to use for interpolation at - a given x,y interplation point. interpolation points with less - than minpts_interp point_data found will be skipped - (assigned np.NaN). Defaut is 1 - maxpts_interp : (int) - maximum number of point_data entries to use for interpolation at - a given x,y interpolation point. A larger maxpts_interp will - yield "smoother" interplation, but using a large maxpts_interp - will slow the (already) slow kriging solution process and may - lead to memory errors. Default is 20. - search_radius : (float) - the size of the region around a given x,y interpolation point to search for - point_data entries. Default is 1.0e+10 - verbose : (boolean) - a flag to echo process to stdout during the interpolatino process. - Default is False - forgive : (boolean) - flag to continue if inversion of the kriging matrix failes at one or more - interpolation points. Inversion usually fails if the kriging matrix is singular, - resulting from point_data entries closer than EPSILON distance. If True, - warnings are issued for each failed inversion. If False, an exception - is raised for failed matrix inversion. - - Returns - ------- - df : pandas.DataFrame - a dataframe with information summarizing the ordinary kriging - process for each interpolation points - - - """ - - assert len(x) == len(y) - - # find the point data to use for each interp point - sqradius = search_radius**2 - df = pd.DataFrame(data={'x':x,'y':y}) - inames,idist,ifacts,err_var = [],[],[],[] - sill = self.geostruct.sill - if pt_zone is None: - ptx_array = self.point_data.x.values - pty_array = self.point_data.y.values - ptnames = self.point_data.name.values - else: - pt_data = self.point_data - ptx_array = pt_data.loc[pt_data.zone==pt_zone,"x"].values - pty_array = pt_data.loc[pt_data.zone==pt_zone,"y"].values - ptnames = pt_data.loc[pt_data.zone==pt_zone,"name"].values - #if verbose: - print("starting interp point loop for {0} points".format(df.shape[0])) - start_loop = datetime.now() - for idx,(ix,iy) in enumerate(zip(df.x,df.y)): - if np.isnan(ix) or np.isnan(iy): #if nans, skip - inames.append([]) - idist.append([]) - ifacts.append([]) - err_var.append(np.NaN) - continue - if verbose: - istart = datetime.now() - print("processing interp point:{0} of {1}".format(idx,df.shape[0])) - # if verbose == 2: - # start = datetime.now() - # print("calc ipoint dist...",end='') - - # calc dist from this interp point to all point data...slow - dist = pd.Series((ptx_array-ix)**2 + (pty_array-iy)**2,ptnames) - dist.sort_values(inplace=True) - dist = dist.loc[dist <= sqradius] - - # if too few points were found, skip - if len(dist) < minpts_interp: - inames.append([]) - idist.append([]) - ifacts.append([]) - err_var.append(sill) - continue - - # only the maxpts_interp points - dist = dist.iloc[:maxpts_interp].apply(np.sqrt) - pt_names = dist.index.values - # if one of the points is super close, just use it and skip - if dist.min() <= EPSILON: - ifacts.append([1.0]) - idist.append([EPSILON]) - inames.append([dist.idxmin()]) - err_var.append(self.geostruct.nugget) - continue - # if verbose == 2: - # td = (datetime.now()-start).total_seconds() - # print("...took {0}".format(td)) - # start = datetime.now() - # print("extracting pt cov...",end='') - - #vextract the point-to-point covariance matrix - point_cov = self.point_cov_df.loc[pt_names,pt_names] - # if verbose == 2: - # td = (datetime.now()-start).total_seconds() - # print("...took {0}".format(td)) - # print("forming ipt-to-point cov...",end='') - - # calc the interp point to points covariance - interp_cov = self.geostruct.covariance_points(ix,iy,self.point_data.loc[pt_names,"x"], - self.point_data.loc[pt_names,"y"]) - - if verbose == 2: - td = (datetime.now()-start).total_seconds() - print("...took {0} seconds".format(td)) - print("forming lin alg components...",end='') - - # form the linear algebra parts and solve - d = len(pt_names) + 1 # +1 for lagrange mult - A = np.ones((d,d)) - A[:-1,:-1] = point_cov.values - A[-1,-1] = 0.0 #unbiaised constraint - rhs = np.ones((d,1)) - rhs[:-1,0] = interp_cov - # if verbose == 2: - # td = (datetime.now()-start).total_seconds() - # print("...took {0}".format(td)) - # print("solving...",end='') - # # solve - try: - facs = np.linalg.solve(A,rhs) - except Exception as e: - print("error solving for factors: {0}".format(str(e))) - print("point:",ix,iy) - print("dist:",dist) - print("A:", A) - print("rhs:", rhs) - if forgive: - inames.append([]) - idist.append([]) - ifacts.append([]) - err_var.append(np.NaN) - continue - else: - raise Exception("error solving for factors:{0}".format(str(e))) - assert len(facs) - 1 == len(dist) - - err_var.append(float(sill + facs[-1] - sum([f*c for f,c in zip(facs[:-1],interp_cov)]))) - inames.append(pt_names) - - idist.append(dist.values) - ifacts.append(facs[:-1,0]) - # if verbose == 2: - # td = (datetime.now()-start).total_seconds() - # print("...took {0}".format(td)) - if verbose: - td = (datetime.now()-istart).total_seconds() - print("point took {0}".format(td)) - df["idist"] = idist - df["inames"] = inames - df["ifacts"] = ifacts - df["err_var"] = err_var - if pt_zone is None: - self.interp_data = df - else: - if self.interp_data is None: - self.interp_data = df - else: - self.interp_data = self.interp_data.append(df) - td = (datetime.now() - start_loop).total_seconds() - print("took {0} seconds".format(td)) - return df
    - -
    [docs] def to_grid_factors_file(self, filename,points_file="points.junk", - zone_file="zone.junk"): - """ write a grid-based PEST-style factors file. This file can be used with - the fac2real() method to write an interpolated structured array - - Parameters - ---------- - filename : (str) - factor filename - points_file : (str) - points filename to add to the header of the factors file. - Not used by fac2real() method. Default is "points.junk" - zone_file : (str) - zone filename to add to the header of the factors file. - Not used by fac2real() method. Default is "zone.junk" - - Note - ---- - this method should be called after OrdinaryKirge.calc_factors_grid() - - """ - if self.interp_data is None: - raise Exception("ok.interp_data is None, must call calc_factors_grid() first") - if self.spatial_reference is None: - raise Exception("ok.spatial_reference is None, must call calc_factors_grid() first") - with open(filename, 'w') as f: - f.write(points_file + '\n') - f.write(zone_file + '\n') - f.write("{0} {1}\n".format(self.spatial_reference.ncol, self.spatial_reference.nrow)) - f.write("{0}\n".format(self.point_data.shape[0])) - [f.write("{0}\n".format(name)) for name in self.point_data.name] - t = 0 - if self.geostruct.transform == "log": - t = 1 - pt_names = list(self.point_data.name) - for idx,names,facts in zip(self.interp_data.index,self.interp_data.inames,self.interp_data.ifacts): - if len(facts) == 0: - continue - n_idxs = [pt_names.index(name) for name in names] - f.write("{0} {1} {2} {3:8.5e} ".format(idx+1, t, len(names), 0.0)) - [f.write("{0} {1:12.8g} ".format(i+1, w)) for i, w in zip(n_idxs, facts)] - f.write("\n")
    - - -
    [docs]class Vario2d(object): - """base class for 2-D variograms. - - Parameters - ---------- - contribution : (float) - sill of the variogram - a : (float) - (practical) range of correlation - anisotropy : (float) - Anisotropy ratio. Default is 1.0 - bearing : (float) - angle in degrees East of North corresponding to anisotropy ellipse. - Default is 0.0 - name : (str) - name of the variogram. Default is "var1" - - Returns - ------- - Vario2d : Vario2d - - Note - ---- - This base class should not be instantiated directly as it does not implement - an h_function() method. - - """ - - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - self.name = name - self.epsilon = EPSILON - self.contribution = float(contribution) - assert self.contribution > 0.0 - self.a = float(a) - assert self.a > 0.0 - self.anisotropy = float(anisotropy) - assert self.anisotropy > 0.0 - self.bearing = float(bearing) - -
    [docs] def to_struct_file(self, f): - """ write the Vario2d to a PEST-style structure file - - Parameters - ---------- - f : (str or file handle) - item to write to - - """ - if isinstance(f, str): - f = open(f,'w') - f.write("VARIOGRAM {0}\n".format(self.name)) - f.write(" VARTYPE {0}\n".format(self.vartype)) - f.write(" A {0}\n".format(self.a)) - f.write(" ANISOTROPY {0}\n".format(self.anisotropy)) - f.write(" BEARING {0}\n".format(self.bearing)) - f.write("END VARIOGRAM\n\n")
    - - @property - def bearing_rads(self): - """ get the bearing of the Vario2d in radians - - Returns - ------- - bearing_rads : float - the Vario2d bearing in radians - """ - return (np.pi / 180.0 ) * (90.0 - self.bearing) - - @property - def rotation_coefs(self): - """ get the rotation coefficents in radians - - Returns - ------- - rotation_coefs : list - the rotation coefficients implied by Vario2d.bearing - - - """ - return [np.cos(self.bearing_rads), - np.sin(self.bearing_rads), - -1.0*np.sin(self.bearing_rads), - np.cos(self.bearing_rads)] - -
    [docs] def inv_h(self,h): - """ the inverse of the h_function. Used for plotting - - Parameters - ---------- - h : (float) - the value of h_function to invert - - Returns - ------- - inv_h : float - the inverse of h - - """ - return self.contribution - self._h_function(h)
    - -
    [docs] def plot(self,**kwargs): - """ get a cheap plot of the Vario2d - - Parameters - ---------- - **kwargs : (dict) - keyword arguments to use for plotting - - Returns - ------- - ax : matplotlib.pyplot.axis - - Note - ---- - optional arguments in kwargs include - "ax" (existing matplotlib.pyplot.axis). Other - kwargs are passed to matplotlib.pyplot.plot() - - """ - import matplotlib.pyplot as plt - ax = kwargs.pop("ax",plt.subplot(111)) - x = np.linspace(0,self.a*3,100) - y = self.inv_h(x) - ax.set_xlabel("distance") - ax.set_ylabel("$\gamma$") - ax.plot(x,y,**kwargs) - return ax
    - - -
    [docs] def add_sparse_covariance_matrix(self,x,y,names,iidx,jidx,data): - - """build a pyemu.SparseMatrix instance implied by Vario2d - - Parameters - ---------- - x : (iterable of floats) - x-coordinate locations - y : (iterable of floats) - y-coordinate locations - names : (iterable of str) - names of locations. If None, cov must not be None - iidx : 1-D ndarray - i row indices - jidx : 1-D ndarray - j col indices - data : 1-D ndarray - nonzero entries - - - Returns - ------- - None - - """ - if not isinstance(x, np.ndarray): - x = np.array(x) - if not isinstance(y, np.ndarray): - y = np.array(y) - assert x.shape[0] == y.shape[0] - - - assert x.shape[0] == len(names) - # c = np.zeros((len(names), len(names))) - # np.fill_diagonal(c, self.contribution) - # cov = Cov(x=c, names=names) - # elif cov is not None: - # assert cov.shape[0] == x.shape[0] - # names = cov.row_names - # c = np.zeros((len(names), 1)) + self.contribution - # cont = Cov(x=c, names=names, isdiagonal=True) - # cov += cont - # - # else: - # raise Exception("Vario2d.covariance_matrix() requires either" + - # "names or cov arg") - # rc = self.rotation_coefs - for i,name in enumerate(names): - iidx.append(i) - jidx.append(i) - data.append(self.contribution) - - for i1, (n1, x1, y1) in enumerate(zip(names, x, y)): - dx = x1 - x[i1 + 1:] - dy = y1 - y[i1 + 1:] - dxx, dyy = self._apply_rotation(dx, dy) - h = np.sqrt(dxx * dxx + dyy * dyy) - - h[h < 0.0] = 0.0 - cv = self._h_function(h) - if np.any(np.isnan(cv)): - raise Exception("nans in cv for i1 {0}".format(i1)) - #cv[h>self.a] = 0.0 - j = list(np.arange(i1+1,x.shape[0])) - i = [i1] * len(j) - iidx.extend(i) - jidx.extend(j) - data.extend(list(cv)) - # replicate across the diagonal - iidx.extend(j) - jidx.extend(i) - data.extend(list(cv))
    - - - -
    [docs] def covariance_matrix(self,x,y,names=None,cov=None): - """build a pyemu.Cov instance implied by Vario2d - - Parameters - ---------- - x : (iterable of floats) - x-coordinate locations - y : (iterable of floats) - y-coordinate locations - names : (iterable of str) - names of locations. If None, cov must not be None - cov : (pyemu.Cov) - an existing Cov instance. Vario2d contribution is added to cov - - Returns - ------- - cov : pyemu.Cov - - Note - ---- - either names or cov must not be None. - - """ - if not isinstance(x,np.ndarray): - x = np.array(x) - if not isinstance(y,np.ndarray): - y = np.array(y) - assert x.shape[0] == y.shape[0] - - if names is not None: - assert x.shape[0] == len(names) - c = np.zeros((len(names),len(names))) - np.fill_diagonal(c,self.contribution) - cov = Cov(x=c,names=names) - elif cov is not None: - assert cov.shape[0] == x.shape[0] - names = cov.row_names - c = np.zeros((len(names),1)) + self.contribution - cont = Cov(x=c,names=names,isdiagonal=True) - cov += cont - - else: - raise Exception("Vario2d.covariance_matrix() requires either" + - "names or cov arg") - rc = self.rotation_coefs - for i1,(n1,x1,y1) in enumerate(zip(names,x,y)): - dx = x1 - x[i1+1:] - dy = y1 - y[i1+1:] - dxx,dyy = self._apply_rotation(dx,dy) - h = np.sqrt(dxx*dxx + dyy*dyy) - - h[h<0.0] = 0.0 - h = self._h_function(h) - if np.any(np.isnan(h)): - raise Exception("nans in h for i1 {0}".format(i1)) - cov.x[i1,i1+1:] += h - for i in range(len(names)): - cov.x[i+1:,i] = cov.x[i,i+1:] - return cov
    - - def _apply_rotation(self,dx,dy): - """ private method to rotate points - according to Vario2d.bearing and Vario2d.anisotropy - - Parameters - ---------- - dx : (float or numpy.ndarray) - x-coordinates to rotate - dy : (float or numpy.ndarray) - y-coordinates to rotate - - Returns - ------- - dxx : (float or numpy.ndarray) - rotated x-coordinates - dyy : (float or numpy.ndarray) - rotated y-coordinates - - """ - if self.anisotropy == 1.0: - return dx,dy - rcoefs = self.rotation_coefs - dxx = (dx * rcoefs[0]) +\ - (dy * rcoefs[1]) - dyy = ((dx * rcoefs[2]) +\ - (dy * rcoefs[3])) *\ - self.anisotropy - return dxx,dyy - -
    [docs] def covariance_points(self,x0,y0,xother,yother): - """ get the covariance between base point x0,y0 and - other points xother,yother implied by Vario2d - - Parameters - ---------- - x0 : (float) - x-coordinate of base point - y0 : (float) - y-coordinate of base point - xother : (float or numpy.ndarray) - x-coordinates of other points - yother : (float or numpy.ndarray) - y-coordinates of other points - - Returns - ------- - cov : numpy.ndarray - covariance between base point and other points implied by - Vario2d. - - Note - ---- - len(cov) = len(xother) = len(yother) - - """ - dx = x0 - xother - dy = y0 - yother - dxx,dyy = self._apply_rotation(dx,dy) - h = np.sqrt(dxx*dxx + dyy*dyy) - return self._h_function(h)
    - -
    [docs] def covariance(self,pt0,pt1): - """ get the covarince between two points implied by Vario2d - - Parameters - ---------- - pt0 : (iterable of len 2) - first point x and y - pt1 : (iterable of len 2) - second point x and y - - Returns - ------- - cov : float - covariance between pt0 and pt1 - - """ - - x = np.array([pt0[0],pt1[0]]) - y = np.array([pt0[1],pt1[1]]) - names = ["n1","n2"] - return self.covariance_matrix(x,y,names=names).x[0,1]
    - - - def __str__(self): - """ get the str representation of Vario2d - - Returns - ------- - str : str - """ - s = "name:{0},contribution:{1},a:{2},anisotropy:{3},bearing:{4}\n".\ - format(self.name,self.contribution,self.a,\ - self.anisotropy,self.bearing) - return s
    - -
    [docs]class ExpVario(Vario2d): - """ Exponetial variogram derived type - - Parameters - ---------- - contribution : (float) - sill of the variogram - a : (float) - (practical) range of correlation - anisotropy : (float) - Anisotropy ratio. Default is 1.0 - bearing : (float) - angle in degrees East of North corresponding to anisotropy ellipse. - Default is 0.0 - name : (str) - name of the variogram. Default is "var1" - - Returns - ------- - ExpVario : ExpVario - - Example - ------- - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)`` - - """ - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(ExpVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) - self.vartype = 2 - - def _h_function(self,h): - """ private method exponential variogram "h" function - - Parameters - ---------- - h : (float or numpy.ndarray) - distance(s) - - Returns - ------- - h_function : float or numpy.ndarray - the value of the "h" function implied by the ExpVario - - """ - return self.contribution * np.exp(-1.0 * h / self.a)
    - -
    [docs]class GauVario(Vario2d): - """Gaussian variogram derived type - - Parameters - ---------- - contribution : (float) - sill of the variogram - a : (float) - (practical) range of correlation - anisotropy : (float) - Anisotropy ratio. Default is 1.0 - bearing : (float) - angle in degrees East of North corresponding to anisotropy ellipse. - Default is 0.0 - name : (str) - name of the variogram. Default is "var1" - - Returns - ------- - GauVario : GauVario - - Note - ---- - the Gaussian variogram can be unstable (not invertible) for long ranges. - - Example - ------- - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.GauVario(a=1000,contribution=1.0)`` - - """ - - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(GauVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) - self.vartype = 3 - - def _h_function(self,h): - """ private method for the gaussian variogram "h" function - - Parameters - ---------- - h : (float or numpy.ndarray) - distance(s) - - Returns - ------- - h_function : float or numpy.ndarray - the value of the "h" function implied by the GauVario - - """ - - hh = -1.0 * (h * h) / (self.a * self.a) - return self.contribution * np.exp(hh)
    - -
    [docs]class SphVario(Vario2d): - """Spherical variogram derived type - - Parameters - ---------- - contribution : (float) - sill of the variogram - a : (float) - (practical) range of correlation - anisotropy : (float) - Anisotropy ratio. Default is 1.0 - bearing : (float) - angle in degrees East of North corresponding to anisotropy ellipse. - Default is 0.0 - name : (str) - name of the variogram. Default is "var1" - - Returns - ------- - SphVario : SphVario - - Example - ------- - ``>>>import pyemu`` - - ``>>>v = pyemu.utils.geostats.SphVario(a=1000,contribution=1.0)`` - - """ - - def __init__(self,contribution,a,anisotropy=1.0,bearing=0.0,name="var1"): - super(SphVario,self).__init__(contribution,a,anisotropy=anisotropy, - bearing=bearing,name=name) - self.vartype = 1 - - def _h_function(self,h): - """ private method for the spherical variogram "h" function - - Parameters - ---------- - h : (float or numpy.ndarray) - distance(s) - - Returns - ------- - h_function : float or numpy.ndarray - the value of the "h" function implied by the SphVario - - """ - - hh = h / self.a - h = self.contribution * (1.0 - (hh * (1.5 - (0.5 * hh * hh)))) - h[hh > 1.0] = 0.0 - return h
    - # try: - # h[hh < 1.0] = 0.0 - # - # except TypeError: - # if hh > 0.0: - # h = 0.0 - #return h - # if hh < 1.0: - # return self.contribution * (1.0 - (hh * (1.5 - (0.5 * hh * hh)))) - # else: - # return 0.0 - - - - - -
    [docs]def read_struct_file(struct_file,return_type=GeoStruct): - """read an existing PEST-type structure file into a GeoStruct instance - - Parameters - ---------- - struct_file : (str) - existing pest-type structure file - return_type : (object) - the instance type to return. Default is GeoStruct - - Returns - ------- - GeoStruct : list or GeoStruct - - Note - ---- - if only on structure is listed in struct_file, then return type - is GeoStruct. Otherwise, return type is a list of GeoStruct - - Example - ------- - ``>>>import pyemu`` - - ``>>>gs = pyemu.utils.geostats.reads_struct_file("struct.dat")`` - - - """ - - VARTYPE = {1:SphVario,2:ExpVario,3:GauVario,4:None} - assert os.path.exists(struct_file) - structures = [] - variograms = [] - with open(struct_file,'r') as f: - while True: - line = f.readline() - if line == '': - break - line = line.strip().lower() - if line.startswith("structure"): - name = line.strip().split()[1] - nugget,transform,variogram_info = _read_structure_attributes(f) - s = return_type(nugget=nugget,transform=transform,name=name) - s.variogram_info = variogram_info - # not sure what is going on, but if I don't copy s here, - # all the structures end up sharing all the variograms later - structures.append(copy.deepcopy(s)) - elif line.startswith("variogram"): - name = line.strip().split()[1].lower() - vartype,bearing,a,anisotropy = _read_variogram(f) - if name in variogram_info: - v = VARTYPE[vartype](variogram_info[name],a,anisotropy=anisotropy, - bearing=bearing,name=name) - variograms.append(v) - - for i,st in enumerate(structures): - for vname in st.variogram_info: - vfound = None - for v in variograms: - if v.name == vname: - vfound = v - break - if vfound is None: - raise Exception("variogram {0} not found for structure {1}".\ - format(vname,s.name)) - - st.variograms.append(vfound) - if len(structures) == 1: - return structures[0] - return structures
    - - - -def _read_variogram(f): - """Function to instantiate a Vario2d from a PEST-style structure file - - Parameters - ---------- - f : (file handle) - file handle opened for reading - - Returns - ------- - Vario2d : Vario2d - Vario2d derived type - - """ - - line = '' - vartype = None - bearing = 0.0 - a = None - anisotropy = 1.0 - while "end variogram" not in line: - line = f.readline() - if line == '': - raise Exception("EOF while read variogram") - line = line.strip().lower().split() - if line[0].startswith('#'): - continue - if line[0] == "vartype": - vartype = int(line[1]) - elif line[0] == "bearing": - bearing = float(line[1]) - elif line[0] == "a": - a = float(line[1]) - elif line[0] == "anisotropy": - anisotropy = float(line[1]) - elif line[0] == "end": - break - else: - raise Exception("unrecognized arg in variogram:{0}".format(line[0])) - return vartype,bearing,a,anisotropy - - -def _read_structure_attributes(f): - """ function to read information from a PEST-style structure file - - Parameters - ---------- - f : (file handle) - file handle open for reading - - Returns - ------- - nugget : float - the GeoStruct nugget - transform : str - the GeoStruct transformation - variogram_info : dict - dictionary of structure-level variogram information - - """ - - line = '' - variogram_info = {} - while "end structure" not in line: - line = f.readline() - if line == '': - raise Exception("EOF while reading structure") - line = line.strip().lower().split() - if line[0].startswith('#'): - continue - if line[0] == "nugget": - nugget = float(line[1]) - elif line[0] == "transform": - transform = line[1] - elif line[0] == "numvariogram": - numvariograms = int(line[1]) - elif line[0] == "variogram": - variogram_info[line[1]] = float(line[2]) - elif line[0] == "end": - break - elif line[0] == "mean": - warning.warn("'mean' attribute not supported, skipping",PyemuWarning) - else: - raise Exception("unrecognized line in structure definition:{0}".\ - format(line[0])) - assert numvariograms == len(variogram_info) - return nugget,transform,variogram_info - - -
    [docs]def read_sgems_variogram_xml(xml_file,return_type=GeoStruct): - """ function to read an SGEMS-type variogram XML file into - a GeoStruct - - Parameters - ---------- - xml_file : (str) - SGEMS variogram XML file - return_type : (object) - the instance type to return. Default is GeoStruct - - Returns - ------- - GeoStruct : GeoStruct - - - Example - ------- - ``>>>import pyemu`` - - ``>>>gs = pyemu.utils.geostats.read_sgems_variogram_xml("sgems.xml")`` - - """ - try: - import xml.etree.ElementTree as ET - - except Exception as e: - print("error import elementtree, skipping...") - VARTYPE = {1: SphVario, 2: ExpVario, 3: GauVario, 4: None} - assert os.path.exists(xml_file) - tree = ET.parse(xml_file) - gs_model = tree.getroot() - structures = [] - variograms = [] - nugget = 0.0 - num_struct = 0 - for key,val in gs_model.items(): - #print(key,val) - if str(key).lower() == "nugget": - if len(val) > 0: - nugget = float(val) - if str(key).lower() == "structures_count": - num_struct = int(val) - if num_struct == 0: - raise Exception("no structures found") - if num_struct != 1: - raise NotImplementedError() - for structure in gs_model: - vtype, contribution = None, None - mx_range,mn_range = None, None - x_angle,y_angle = None,None - #struct_name = structure.tag - for key,val in structure.items(): - key = str(key).lower() - if key == "type": - vtype = str(val).lower() - if vtype.startswith("sph"): - vtype = SphVario - elif vtype.startswith("exp"): - vtype = ExpVario - elif vtype.startswith("gau"): - vtype = GauVario - else: - raise Exception("unrecognized variogram type:{0}".format(vtype)) - - elif key == "contribution": - contribution = float(val) - for item in structure: - if item.tag.lower() == "ranges": - mx_range = float(item.attrib["max"]) - mn_range = float(item.attrib["min"]) - elif item.tag.lower() == "angles": - x_angle = float(item.attrib["x"]) - y_angle = float(item.attrib["y"]) - - assert contribution is not None - assert mn_range is not None - assert mx_range is not None - assert x_angle is not None - assert y_angle is not None - assert vtype is not None - v = vtype(contribution=contribution,a=mx_range, - anisotropy=mx_range/mn_range,bearing=(180.0/np.pi)*np.arctan2(x_angle,y_angle), - name=structure.tag) - return GeoStruct(nugget=nugget,variograms=[v])
    - - -
    [docs]def gslib_2_dataframe(filename,attr_name=None,x_idx=0,y_idx=1): - """ function to read a GSLIB point data file into a pandas.DataFrame - - Parameters - ---------- - filename : (str) - GSLIB file - attr_name : (str) - the column name in the dataframe for the attribute. If None, GSLIB file - can have only 3 columns. attr_name must be in the GSLIB file header - x_idx : (int) - the index of the x-coordinate information in the GSLIB file. Default is - 0 (first column) - y_idx : (int) - the index of the y-coordinate information in the GSLIB file. - Default is 1 (second column) - - Returns - ------- - df : pandas.DataFrame - - Raises - ------ - exception if attr_name is None and GSLIB file has more than 3 columns - - Note - ---- - assigns generic point names ("pt0, pt1, etc) - - Example - ------- - ``>>>import pyemu`` - - ``>>>df = pyemu.utiils.geostats.gslib_2_dataframe("prop.gslib",attr_name="hk")`` - - - """ - with open(filename,'r') as f: - title = f.readline().strip() - num_attrs = int(f.readline().strip()) - attrs = [f.readline().strip() for _ in range(num_attrs)] - if attr_name is not None: - assert attr_name in attrs,"{0} not in attrs:{1}".format(attr_name,','.join(attrs)) - else: - assert len(attrs) == 3,"propname is None but more than 3 attrs in gslib file" - attr_name = attrs[2] - assert len(attrs) > x_idx - assert len(attrs) > y_idx - a_idx = attrs.index(attr_name) - x,y,a = [],[],[] - while True: - line = f.readline() - if line == '': - break - raw = line.strip().split() - try: - x.append(float(raw[x_idx])) - y.append(float(raw[y_idx])) - a.append(float(raw[a_idx])) - except Exception as e: - raise Exception("error paring line {0}: {1}".format(line,str(e))) - df = pd.DataFrame({"x":x,"y":y,"value":a}) - df.loc[:,"name"] = ["pt{0}".format(i) for i in range(df.shape[0])] - df.index = df.name - return df
    - - -#class ExperimentalVariogram(object): -# def __init__(self,na) - -
    [docs]def load_sgems_exp_var(filename): - """ read an SGEM experimental variogram into a sequence of - pandas.DataFrames - - Parameters - ---------- - filename : (str) - an SGEMS experimental variogram XML file - - Returns - ------- - dfs : list - a list of pandas.DataFrames of x, y, pairs for each - division in the experimental variogram - - """ - - assert os.path.exists(filename) - import xml.etree.ElementTree as etree - tree = etree.parse(filename) - root = tree.getroot() - dfs = {} - for variogram in root: - #print(variogram.tag) - for attrib in variogram: - - #print(attrib.tag,attrib.text) - if attrib.tag == "title": - title = attrib.text.split(',')[0].split('=')[-1] - elif attrib.tag == "x": - x = [float(i) for i in attrib.text.split()] - elif attrib.tag == "y": - y = [float(i) for i in attrib.text.split()] - elif attrib.tag == "pairs": - pairs = [int(i) for i in attrib.text.split()] - - for item in attrib: - print(item,item.tag) - df = pd.DataFrame({"x":x,"y":y,"pairs":pairs}) - df.loc[df.y<0.0,"y"] = np.NaN - dfs[title] = df - return dfs
    - - - -
    [docs]def fac2real(pp_file=None,factors_file="factors.dat",out_file="test.ref", - upper_lim=1.0e+30,lower_lim=-1.0e+30,fill_value=1.0e+30): - """A python replication of the PEST fac2real utility for creating a - structure grid array from previously calculated kriging factors (weights) - - Parameters - ---------- - pp_file : (str) - PEST-type pilot points file - factors_file : (str) - PEST-style factors file - out_file : (str) - filename of array to write. If None, array is returned, else - value of out_file is returned. Default is "test.ref". - upper_lim : (float) - maximum interpolated value in the array. Values greater than - upper_lim are set to fill_value - lower_lim : (float) - minimum interpolated value in the array. Values less than lower_lim - are set to fill_value - fill_value : (float) - the value to assign array nodes that are not interpolated - - - Returns - ------- - arr : numpy.ndarray - if out_file is None - out_file : str - if out_file it not None - - Example - ------- - ``>>>import pyemu`` - - ``>>>pyemu.utils.geostats.fac2real("hkpp.dat",out_file="hk_layer_1.ref")`` - - """ - - if pp_file is not None and isinstance(pp_file,str): - assert os.path.exists(pp_file) - # pp_data = pd.read_csv(pp_file,delim_whitespace=True,header=None, - # names=["name","parval1"],usecols=[0,4]) - pp_data = pp_file_to_dataframe(pp_file) - pp_data.loc[:,"name"] = pp_data.name.apply(lambda x: x.lower()) - elif pp_file is not None and isinstance(pp_file,pd.DataFrame): - assert "name" in pp_file.columns - assert "parval1" in pp_file.columns - pp_data = pp_file - else: - raise Exception("unrecognized pp_file arg: must be str or pandas.DataFrame, not {0}"\ - .format(type(pp_file))) - assert os.path.exists(factors_file) - f_fac = open(factors_file,'r') - fpp_file = f_fac.readline() - if pp_file is None and pp_data is None: - pp_data = pp_file_to_dataframe(fpp_file) - pp_data.loc[:, "name"] = pp_data.name.apply(lambda x: x.lower()) - - fzone_file = f_fac.readline() - ncol,nrow = [int(i) for i in f_fac.readline().strip().split()] - npp = int(f_fac.readline().strip()) - pp_names = [f_fac.readline().strip().lower() for _ in range(npp)] - - # check that pp_names is sync'd with pp_data - diff = set(list(pp_data.name)).symmetric_difference(set(pp_names)) - if len(diff) > 0: - raise Exception("the following pilot point names are not common " +\ - "between the factors file and the pilot points file " +\ - ','.join(list(diff))) - - arr = np.zeros((nrow,ncol),dtype=np.float) + fill_value - pp_dict = {int(name):val for name,val in zip(pp_data.index,pp_data.parval1)} - try: - pp_dict_log = {name:np.log10(val) for name,val in zip(pp_data.index,pp_data.parval1)} - except: - pp_dict_log = {} - #for i in range(nrow): - # for j in range(ncol): - while True: - line = f_fac.readline() - if len(line) == 0: - #raise Exception("unexpected EOF in factors file") - break - try: - inode,itrans,fac_data = parse_factor_line(line) - except Exception as e: - raise Exception("error parsing factor line {0}:{1}".format(line,str(e))) - #fac_prods = [pp_data.loc[pp,"value"]*fac_data[pp] for pp in fac_data] - if itrans == 0: - fac_sum = sum([pp_dict[pp] * fac_data[pp] for pp in fac_data]) - else: - fac_sum = sum([pp_dict_log[pp] * fac_data[pp] for pp in fac_data]) - if itrans != 0: - fac_sum = 10**fac_sum - #col = ((inode - 1) // nrow) + 1 - #row = inode - ((col - 1) * nrow) - row = ((inode-1) // ncol) + 1 - col = inode - ((row - 1) * ncol) - #arr[row-1,col-1] = np.sum(np.array(fac_prods)) - arr[row - 1, col - 1] = fac_sum - arr[arr<lower_lim] = lower_lim - arr[arr>upper_lim] = upper_lim - - #print(out_file,arr.min(),pp_data.parval1.min(),lower_lim) - - if out_file is not None: - np.savetxt(out_file,arr,fmt="%15.6E",delimiter='') - return out_file - return arr
    - -
    [docs]def parse_factor_line(line): - """ function to parse a factor file line. Used by fac2real() - - Parameters - ---------- - line : (str) - a factor line from a factor file - - Returns - ------- - inode : int - the inode of the grid node - itrans : int - flag for transformation of the grid node - fac_data : dict - a dictionary of point number, factor - - """ - - raw = line.strip().split() - inode,itrans,nfac = [int(i) for i in raw[:3]] - fac_data = {int(raw[ifac])-1:float(raw[ifac+1]) for ifac in range(4,4+nfac*2,2)} - # fac_data = {} - # for ifac in range(4,4+nfac*2,2): - # pnum = int(raw[ifac]) - 1 #zero based to sync with pandas - # fac = float(raw[ifac+1]) - # fac_data[pnum] = fac - return inode,itrans,fac_data
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/utils/gw_utils.html b/docs/_build/html/_modules/pyemu/utils/gw_utils.html deleted file mode 100644 index d5279ef30..000000000 --- a/docs/_build/html/_modules/pyemu/utils/gw_utils.html +++ /dev/null @@ -1,2063 +0,0 @@ - - - - - - - - pyemu.utils.gw_utils — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.utils.gw_utils

    -""" module of utilities for groundwater modeling
    -"""
    -
    -import os
    -import copy
    -import csv
    -from datetime import datetime
    -import shutil
    -import warnings
    -import numpy as np
    -import pandas as pd
    -import re
    -pd.options.display.max_colwidth = 100
    -from pyemu.pst.pst_utils import SFMT,IFMT,FFMT,pst_config,_try_run_inschek,\
    -    parse_tpl_file,try_process_ins_file
    -from pyemu.utils.os_utils import run
    -from pyemu.utils.helpers import write_df_tpl
    -from ..pyemu_warnings import PyemuWarning
    -PP_FMT = {"name": SFMT, "x": FFMT, "y": FFMT, "zone": IFMT, "tpl": SFMT,
    -          "parval1": FFMT}
    -PP_NAMES = ["name","x","y","zone","parval1"]
    -
    -
    -
    [docs]def modflow_pval_to_template_file(pval_file,tpl_file=None): - """write a template file for a modflow parameter value file. - Uses names in the first column in the pval file as par names. - - Parameters - ---------- - pval_file : str - parameter value file - tpl_file : str, optional - template file to write. If None, use <pval_file>.tpl. - Default is None - - Returns - ------- - df : pandas.DataFrame - pandas DataFrame with control file parameter information - """ - - if tpl_file is None: - tpl_file = pval_file + ".tpl" - pval_df = pd.read_csv(pval_file,delim_whitespace=True, - header=None,skiprows=2, - names=["parnme","parval1"]) - pval_df.index = pval_df.parnme - pval_df.loc[:,"tpl"] = pval_df.parnme.apply(lambda x: " ~ {0:15s} ~".format(x)) - with open(tpl_file,'w') as f: - f.write("ptf ~\n#pval template file from pyemu\n") - f.write("{0:10d} #NP\n".format(pval_df.shape[0])) - f.write(pval_df.loc[:,["parnme","tpl"]].to_string(col_space=0, - formatters=[SFMT,SFMT], - index=False, - header=False, - justify="left")) - return pval_df
    - -
    [docs]def modflow_hob_to_instruction_file(hob_file): - """write an instruction file for a modflow head observation file - - Parameters - ---------- - hob_file : str - modflow hob file - - Returns - ------- - df : pandas.DataFrame - pandas DataFrame with control file observation information - """ - - hob_df = pd.read_csv(hob_file,delim_whitespace=True,skiprows=1, - header=None,names=["simval","obsval","obsnme"]) - - hob_df.loc[:,"ins_line"] = hob_df.obsnme.apply(lambda x:"l1 !{0:s}!".format(x)) - hob_df.loc[0,"ins_line"] = hob_df.loc[0,"ins_line"].replace('l1','l2') - - ins_file = hob_file + ".ins" - f_ins = open(ins_file, 'w') - f_ins.write("pif ~\n") - f_ins.write(hob_df.loc[:,["ins_line"]].to_string(col_space=0, - columns=["ins_line"], - header=False, - index=False, - formatters=[SFMT]) + '\n') - hob_df.loc[:,"weight"] = 1.0 - hob_df.loc[:,"obgnme"] = "obgnme" - f_ins.close() - return hob_df
    - -
    [docs]def modflow_hydmod_to_instruction_file(hydmod_file): - """write an instruction file for a modflow hydmod file - - Parameters - ---------- - hydmod_file : str - modflow hydmod file - - - Returns - ------- - df : pandas.DataFrame - pandas DataFrame with control file observation information - - Note - ---- - calls modflow_read_hydmod_file() - """ - - hydmod_df, hydmod_outfile = modflow_read_hydmod_file(hydmod_file) - - - hydmod_df.loc[:,"ins_line"] = hydmod_df.obsnme.apply(lambda x:"l1 w !{0:s}!".format(x)) - - ins_file = hydmod_outfile + ".ins" - - with open(ins_file, 'w') as f_ins: - f_ins.write("pif ~\nl1\n") - f_ins.write(hydmod_df.loc[:,["ins_line"]].to_string(col_space=0, - columns=["ins_line"], - header=False, - index=False, - formatters=[SFMT]) + '\n') - hydmod_df.loc[:,"weight"] = 1.0 - hydmod_df.loc[:,"obgnme"] = "obgnme" - - try: - os.system("inschek {0}.ins {0}".format(hydmod_outfile)) - except: - print("error running inschek") - - obs_obf = hydmod_outfile + ".obf" - if os.path.exists(obs_obf): - df = pd.read_csv(obs_obf,delim_whitespace=True,header=None,names=["obsnme","obsval"]) - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: x[:-9]) - df.to_csv("_setup_"+os.path.split(hydmod_outfile)[-1]+'.csv',index=False) - df.index = df.obsnme - return df - - - return hydmod_df
    - -
    [docs]def modflow_read_hydmod_file(hydmod_file, hydmod_outfile=None): - """ read in a binary hydmod file and return a dataframe of the results - - Parameters - ---------- - hydmod_file : str - modflow hydmod binary file - hydmod_outfile : str - output file to write. If None, use <hydmod_file>.dat. - Default is None - - Returns - ------- - df : pandas.DataFrame - pandas DataFrame with hymod_file values - - Note - ---- - requires flopy - """ - try: - import flopy.utils as fu - except Exception as e: - print('flopy is not installed - cannot read {0}\n{1}'.format(hydmod_file, e)) - return - #print('Starting to read HYDMOD data from {0}'.format(hydmod_file)) - obs = fu.HydmodObs(hydmod_file) - hyd_df = obs.get_dataframe() - - hyd_df.columns = [i[2:] if i.lower() != 'totim' else i for i in hyd_df.columns] - #hyd_df.loc[:,"datetime"] = hyd_df.index - hyd_df['totim'] = hyd_df.index.map(lambda x: x.strftime("%Y%m%d")) - - hyd_df.rename(columns={'totim': 'datestamp'}, inplace=True) - - - # reshape into a single column - hyd_df = pd.melt(hyd_df, id_vars='datestamp') - - hyd_df.rename(columns={'value': 'obsval'}, inplace=True) - - hyd_df['obsnme'] = [i.lower() + '_' + j.lower() for i, j in zip(hyd_df.variable, hyd_df.datestamp)] - - vc = hyd_df.obsnme.value_counts().sort_values() - vc = list(vc.loc[vc>1].index.values) - if len(vc) > 0: - hyd_df.to_csv("hyd_df.duplciates.csv") - obs.get_dataframe().to_csv("hyd_org.duplicates.csv") - raise Exception("duplicates in obsnme:{0}".format(vc)) - #assert hyd_df.obsnme.value_counts().max() == 1,"duplicates in obsnme" - - if not hydmod_outfile: - hydmod_outfile = hydmod_file + '.dat' - hyd_df.to_csv(hydmod_outfile, columns=['obsnme','obsval'], sep=' ',index=False) - #hyd_df = hyd_df[['obsnme','obsval']] - return hyd_df[['obsnme','obsval']], hydmod_outfile
    - - -
    [docs]def setup_pilotpoints_grid(ml=None,sr=None,ibound=None,prefix_dict=None, - every_n_cell=4, - use_ibound_zones=False, - pp_dir='.',tpl_dir='.', - shapename="pp.shp"): - """ setup regularly-spaced (gridded) pilot point parameterization - - Parameters - ---------- - ml : flopy.mbase - a flopy mbase dervied type. If None, sr must not be None. - sr : flopy.utils.reference.SpatialReference - a spatial reference use to locate the model grid in space. If None, - ml must not be None. Default is None - ibound : numpy.ndarray - the modflow ibound integer array. Used to set pilot points only in active areas. - If None and ml is None, then pilot points are set in all rows and columns according to - every_n_cell. Default is None. - prefix_dict : dict - a dictionary of pilot point parameter prefix, layer pairs. example : {"hk":[0,1,2,3]} would - setup pilot points with the prefix "hk" for model layers 1 - 4 (zero based). If None, a generic set - of pilot points with the "pp" prefix are setup for a generic nrowXncol grid. Default is None - use_ibound_zones : bool - a flag to use the greater-than-zero values in the ibound as pilot point zones. If False,ibound - values greater than zero are treated as a single zone. Default is False. - pp_dir : str - directory to write pilot point files to. Default is '.' - tpl_dir : str - directory to write pilot point template file to. Default is '.' - shapename : str - name of shapefile to write that containts pilot point information. Default is "pp.shp" - - Returns - ------- - pp_df : pandas.DataFrame - a dataframe summarizing pilot point information (same information - written to shapename - - """ - from . import pp_utils - warnings.warn("setup_pilotpoint_grid has moved to pp_utils...",PyemuWarning) - return pp_utils.setup_pilotpoints_grid(ml=ml,sr=sr,ibound=ibound, - prefix_dict=prefix_dict, - every_n_cell=every_n_cell, - use_ibound_zones=use_ibound_zones, - pp_dir=pp_dir,tpl_dir=tpl_dir, - shapename=shapename)
    - - -
    [docs]def pp_file_to_dataframe(pp_filename): - - - from . import pp_utils - warnings.warn("pp_file_to_dataframe has moved to pp_utils",PyemuWarning) - return pp_utils.pp_file_to_dataframe(pp_filename)
    - -
    [docs]def pp_tpl_to_dataframe(tpl_filename): - - from . import pp_utils - warnings.warn("pp_tpl_to_dataframe has moved to pp_utils",PyemuWarning) - return pp_utils.pp_tpl_to_dataframe(tpl_filename)
    - -
    [docs]def write_pp_shapfile(pp_df,shapename=None): - from . import pp_utils - warnings.warn("write_pp_shapefile has moved to pp_utils",PyemuWarning) - pp_utils.write_pp_shapfile(pp_df,shapename=shapename)
    - - -
    [docs]def write_pp_file(filename,pp_df): - from . import pp_utils - warnings.warn("write_pp_file has moved to pp_utils",PyemuWarning) - return pp_utils.write_pp_file(filename,pp_df)
    - -
    [docs]def pilot_points_to_tpl(pp_file,tpl_file=None,name_prefix=None): - from . import pp_utils - warnings.warn("pilot_points_to_tpl has moved to pp_utils",PyemuWarning) - return pp_utils.pilot_points_to_tpl(pp_file,tpl_file=tpl_file, - name_prefix=name_prefix)
    - -
    [docs]def fac2real(pp_file=None,factors_file="factors.dat",out_file="test.ref", - upper_lim=1.0e+30,lower_lim=-1.0e+30,fill_value=1.0e+30): - from . import geostats as gs - warnings.warn("fac2real has moved to geostats",PyemuWarning) - return gs.fac2real(pp_file=pp_file,factors_file=factors_file, - out_file=out_file,upper_lim=upper_lim, - lower_lim=lower_lim,fill_value=fill_value)
    - - -
    [docs]def setup_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat",sw_filename="mtlist_sw.dat", - start_datetime="1-1-1970",gw_prefix='gw',sw_prefix="sw", - save_setup_file=False): - """ setup observations of gw (and optionally sw) mass budgets from mt3dusgs list file. writes - an instruction file and also a _setup_.csv to use when constructing a pest - control file - - Parameters - ---------- - list_filename : str - modflow list file - gw_filename : str - output filename that will contain the gw budget observations. Default is - "mtlist_gw.dat" - sw_filename : str - output filename that will contain the sw budget observations. Default is - "mtlist_sw.dat" - start_datetime : str - an str that can be parsed into a pandas.TimeStamp. used to give budget - observations meaningful names - gw_prefix : str - a prefix to add to the GW budget observations. Useful if processing - more than one list file as part of the forward run process. Default is 'gw'. - sw_prefix : str - a prefix to add to the SW budget observations. Useful if processing - more than one list file as part of the forward run process. Default is 'sw'. - save_setup_file : (boolean) - a flag to save _setup_<list_filename>.csv file that contains useful - control file information - - Returns - ------- - frun_line, ins_filenames, df :str, list(str), pandas.DataFrame - the command to add to the forward run script, the names of the instruction - files and a dataframe with information for constructing a control file. If INSCHEK fails - to run, df = None - - Note - ---- - This function uses INSCHEK to get observation values; the observation values are - the values of the list file list_filename. If INSCHEK fails to run, the obseravtion - values are set to 1.0E+10 - - the instruction files are named <out_filename>.ins - - It is recommended to use the default value for gw_filename or sw_filename. - - """ - gw,sw = apply_mtlist_budget_obs(list_filename, gw_filename, sw_filename, start_datetime) - gw_ins = gw_filename + ".ins" - _write_mtlist_ins(gw_ins, gw, gw_prefix) - ins_files = [gw_ins] - try: - run("inschek {0}.ins {0}".format(gw_filename)) - except: - print("error running inschek") - if sw is not None: - sw_ins = sw_filename + ".ins" - _write_mtlist_ins(sw_ins, sw, sw_prefix) - ins_files.append(sw_ins) - try: - run("inschek {0}.ins {0}".format(sw_filename)) - except: - print("error running inschek") - frun_line = "pyemu.gw_utils.apply_mtlist_budget_obs('{0}')".format(list_filename) - gw_obf = gw_filename + ".obf" - df_gw = None - if os.path.exists(gw_obf): - df_gw = pd.read_csv(gw_obf, delim_whitespace=True, header=None, names=["obsnme", "obsval"]) - df_gw.loc[:, "obgnme"] = df_gw.obsnme.apply(lambda x: x[:-9]) - sw_obf = sw_filename + ".obf" - if os.path.exists(sw_obf): - df_sw = pd.read_csv(sw_obf, delim_whitespace=True, header=None, names=["obsnme", "obsval"]) - df_sw.loc[:, "obgnme"] = df_sw.obsnme.apply(lambda x: x[:-9]) - df_gw = df_gw.append(df_sw) - - if save_setup_file: - df_gw.to_csv("_setup_" + os.path.split(list_filename)[-1] + '.csv', index=False) - df_gw.index = df_gw.obsnme - return frun_line,ins_files,df_gw
    - -def _write_mtlist_ins(ins_filename,df,prefix): - """ write an instruction file for a MODFLOW list file - - Parameters - ---------- - ins_filename : str - name of the instruction file to write - df : pandas.DataFrame - the dataframe of list file entries - prefix : str - the prefix to add to the column names to form - obseravtions names - - """ - try: - dt_str = df.index.map(lambda x: x.strftime("%Y%m%d")) - except: - dt_str = df.index.map(lambda x: "{0:08.1f}".format(x).strip()) - if prefix == '': - name_len = 11 - else: - name_len = 11 - (len(prefix)+1) - with open(ins_filename,'w') as f: - f.write('pif ~\nl1\n') - - for dt in dt_str: - f.write("l1 ") - for col in df.columns: - col = col.replace("(",'').replace(")",'') - raw = col.split('_') - name = ''.join([r[:2] for r in raw[:-2]])[:6] + raw[-2] + raw[-1][0] - #raw[0] = raw[0][:6] - #name = ''.join(raw) - if prefix == '': - obsnme = "{1}_{2}".format(prefix,name[:name_len],dt) - else: - obsnme = "{0}_{1}_{2}".format(prefix, name[:name_len], dt) - f.write(" w !{0}!".format(obsnme)) - f.write("\n") - -
    [docs]def apply_mtlist_budget_obs(list_filename,gw_filename="mtlist_gw.dat", - sw_filename="mtlist_sw.dat", - start_datetime="1-1-1970"): - """ process an MT3D list file to extract mass budget entries. - - Parameters - ---------- - list_filename : str - the mt3d list file - gw_filename : str - the name of the output file with gw mass budget information. - Default is "mtlist_gw.dat" - sw_filename : str - the name of the output file with sw mass budget information. - Default is "mtlist_sw.dat" - start_datatime : str - an str that can be cast to a pandas.TimeStamp. Used to give - observations a meaningful name - - Returns - ------- - gw : pandas.DataFrame - the gw mass dataframe - sw : pandas.DataFrame (optional) - the sw mass dataframe - - Note - ---- - requires flopy - - if SFT is not active, no SW mass budget will be returned - - """ - try: - import flopy - except Exception as e: - raise Exception("error import flopy: {0}".format(str(e))) - mt = flopy.utils.MtListBudget(list_filename) - gw,sw = mt.parse(start_datetime=start_datetime,diff=True) - gw.to_csv(gw_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - if sw is not None: - sw.to_csv(sw_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - return gw, sw
    - -
    [docs]def setup_mflist_budget_obs(list_filename,flx_filename="flux.dat", - vol_filename="vol.dat",start_datetime="1-1'1970",prefix='', - save_setup_file=False): - """ setup observations of budget volume and flux from modflow list file. writes - an instruction file and also a _setup_.csv to use when constructing a pest - control file - - Parameters - ---------- - list_filename : str - modflow list file - flx_filename : str - output filename that will contain the budget flux observations. Default is - "flux.dat" - vol_filename : str) - output filename that will contain the budget volume observations. Default - is "vol.dat" - start_datetime : str - an str that can be parsed into a pandas.TimeStamp. used to give budget - observations meaningful names - prefix : str - a prefix to add to the water budget observations. Useful if processing - more than one list file as part of the forward run process. Default is ''. - save_setup_file : (boolean) - a flag to save _setup_<list_filename>.csv file that contains useful - control file information - - Returns - ------- - df : pandas.DataFrame - a dataframe with information for constructing a control file. If INSCHEK fails - to run, reutrns None - - Note - ---- - This function uses INSCHEK to get observation values; the observation values are - the values of the list file list_filename. If INSCHEK fails to run, the obseravtion - values are set to 1.0E+10 - - the instruction files are named <flux_file>.ins and <vol_file>.ins, respectively - - It is recommended to use the default values for flux_file and vol_file. - - - """ - - - - flx,vol = apply_mflist_budget_obs(list_filename,flx_filename,vol_filename, - start_datetime) - _write_mflist_ins(flx_filename+".ins",flx,prefix+"flx") - _write_mflist_ins(vol_filename+".ins",vol, prefix+"vol") - - #run("inschek {0}.ins {0}".format(flx_filename)) - #run("inschek {0}.ins {0}".format(vol_filename)) - - try: - #os.system("inschek {0}.ins {0}".format(flx_filename)) - #os.system("inschek {0}.ins {0}".format(vol_filename)) - run("inschek {0}.ins {0}".format(flx_filename)) - run("inschek {0}.ins {0}".format(vol_filename)) - - except: - print("error running inschek") - return None - flx_obf = flx_filename+".obf" - vol_obf = vol_filename + ".obf" - if os.path.exists(flx_obf) and os.path.exists(vol_obf): - df = pd.read_csv(flx_obf,delim_whitespace=True,header=None,names=["obsnme","obsval"]) - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: x[:-9]) - df2 = pd.read_csv(vol_obf, delim_whitespace=True, header=None, names=["obsnme", "obsval"]) - df2.loc[:, "obgnme"] = df2.obsnme.apply(lambda x: x[:-9]) - df = df.append(df2) - if save_setup_file: - df.to_csv("_setup_"+os.path.split(list_filename)[-1]+'.csv',index=False) - df.index = df.obsnme - return df
    - -
    [docs]def apply_mflist_budget_obs(list_filename,flx_filename="flux.dat", - vol_filename="vol.dat", - start_datetime="1-1-1970"): - """ process a MODFLOW list file to extract flux and volume water budget entries. - - Parameters - ---------- - list_filename : str - the modflow list file - flx_filename : str - the name of the output file with water budget flux information. - Default is "flux.dat" - vol_filename : str - the name of the output file with water budget volume information. - Default is "vol.dat" - start_datatime : str - an str that can be cast to a pandas.TimeStamp. Used to give - observations a meaningful name - - Returns - ------- - flx : pandas.DataFrame - the flux dataframe - vol : pandas.DataFrame - the volume dataframe - - Note - ---- - requires flopy - - """ - try: - import flopy - except Exception as e: - raise Exception("error import flopy: {0}".format(str(e))) - mlf = flopy.utils.MfListBudget(list_filename) - flx,vol = mlf.get_dataframes(start_datetime=start_datetime,diff=True) - flx.to_csv(flx_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - vol.to_csv(vol_filename,sep=' ',index_label="datetime",date_format="%Y%m%d") - return flx,vol
    - - -def _write_mflist_ins(ins_filename,df,prefix): - """ write an instruction file for a MODFLOW list file - - Parameters - ---------- - ins_filename : str - name of the instruction file to write - df : pandas.DataFrame - the dataframe of list file entries - prefix : str - the prefix to add to the column names to form - obseravtions names - - """ - - dt_str = df.index.map(lambda x: x.strftime("%Y%m%d")) - name_len = 11 - (len(prefix)+1) - with open(ins_filename,'w') as f: - f.write('pif ~\nl1\n') - - for dt in dt_str: - f.write("l1 ") - for col in df.columns: - obsnme = "{0}_{1}_{2}".format(prefix,col[:name_len],dt) - f.write(" w !{0}!".format(obsnme)) - f.write("\n") - - -
    [docs]def setup_hds_timeseries(hds_file,kij_dict,prefix=None,include_path=False, - model=None): - """a function to setup extracting time-series from a binary modflow - head save (or equivalent format - ucn, sub, etc). Writes - an instruction file and a _set_ csv - - Parameters - ---------- - hds_file : str - binary filename - kij_dict : dict - dictionary of site_name: [k,i,j] pairs - prefix : str - string to prepend to site_name when forming obsnme's. Default is None - include_path : bool - flag to prepend hds_file path. Useful for setting up - process in separate directory for where python is running. - model : flopy.mbase - a flopy model. If passed, the observation names will have the datetime of the - observation appended to them. If None, the observation names will have the - stress period appended to them. Default is None. - - Returns - ------- - - - - Note - ---- - This function writes hds_timeseries.config that must be in the same - dir where apply_hds_timeseries() is called during the forward run - - assumes model time units are days!!! - - """ - - try: - import flopy - except Exception as e: - print("error importing flopy, returning {0}".format(str(e))) - return - - assert os.path.exists(hds_file),"head save file not found" - if hds_file.lower().endswith(".ucn"): - try: - hds = flopy.utils.UcnFile(hds_file) - except Exception as e: - raise Exception("error instantiating UcnFile:{0}".format(str(e))) - else: - try: - hds = flopy.utils.HeadFile(hds_file) - except Exception as e: - raise Exception("error instantiating HeadFile:{0}".format(str(e))) - - nlay,nrow,ncol = hds.nlay,hds.nrow,hds.ncol - - #if include_path: - # pth = os.path.join(*[p for p in os.path.split(hds_file)[:-1]]) - # config_file = os.path.join(pth,"{0}_timeseries.config".format(hds_file)) - #else: - config_file = "{0}_timeseries.config".format(hds_file) - print("writing config file to {0}".format(config_file)) - - f_config = open(config_file,'w') - if model is not None: - if model.dis.itmuni != 4: - warnings.warn("setup_hds_timeseries only supports 'days' time units...",PyemuWarning) - f_config.write("{0},{1},d\n".format(os.path.split(hds_file)[-1],model.start_datetime)) - start = pd.to_datetime(model.start_datetime) - else: - f_config.write("{0},none,none\n".format(os.path.split(hds_file)[-1])) - f_config.write("site,k,i,j\n") - dfs = [] - - for site,(k,i,j) in kij_dict.items(): - assert k >= 0 and k < nlay, k - assert i >= 0 and i < nrow, i - assert j >= 0 and j < ncol, j - site = site.lower().replace(" ",'') - df = pd.DataFrame(data=hds.get_ts((k,i,j)),columns=["totim",site]) - - if model is not None: - dts = start + pd.to_timedelta(df.totim,unit='d') - df.loc[:,"totim"] = dts - #print(df) - f_config.write("{0},{1},{2},{3}\n".format(site,k,i,j)) - df.index = df.pop("totim") - dfs.append(df) - - f_config.close() - df = pd.concat(dfs,axis=1) - df.to_csv(hds_file+"_timeseries.processed",sep=' ') - if model is not None: - t_str = df.index.map(lambda x: x.strftime("%Y%m%d")) - else: - t_str = df.index.map(lambda x: "{0:08.2f}".format(x)) - - ins_file = hds_file+"_timeseries.processed.ins" - print("writing instruction file to {0}".format(ins_file)) - with open(ins_file,'w') as f: - f.write('pif ~\n') - f.write("l1 \n") - for t in t_str: - f.write("l1 w ") - for site in df.columns: - if prefix is not None: - obsnme = "{0}_{1}_{2}".format(prefix,site,t) - else: - obsnme = "{0}_{1}".format(site, t) - f.write(" !{0}!".format(obsnme)) - f.write('\n') - - - bd = '.' - if include_path: - bd = os.getcwd() - pth = os.path.join(*[p for p in os.path.split(hds_file)[:-1]]) - os.chdir(pth) - config_file = os.path.split(config_file)[-1] - try: - df = apply_hds_timeseries(config_file) - except Exception as e: - os.chdir(bd) - raise Exception("error in apply_sfr_obs(): {0}".format(str(e))) - os.chdir(bd) - - #df = _try_run_inschek(ins_file,ins_file.replace(".ins","")) - df = try_process_ins_file(ins_file,ins_file.replace(".ins","")) - if df is not None: - df.loc[:,"weight"] = 0.0 - if prefix is not None: - df.loc[:,"obgnme"] = df.index.map(lambda x: '_'.join(x.split('_')[:2])) - else: - df.loc[:, "obgnme"] = df.index.map(lambda x: x.split('_')[0]) - frun_line = "pyemu.gw_utils.apply_hds_timeseries('{0}')\n".format(config_file) - return frun_line,df
    - - -
    [docs]def apply_hds_timeseries(config_file=None): - - import flopy - - if config_file is None: - config_file = "hds_timeseries.config" - - assert os.path.exists(config_file), config_file - with open(config_file,'r') as f: - line = f.readline() - hds_file,start_datetime,time_units = line.strip().split(',') - site_df = pd.read_csv(f) - - #print(site_df) - - assert os.path.exists(hds_file), "head save file not found" - if hds_file.lower().endswith(".ucn"): - try: - hds = flopy.utils.UcnFile(hds_file) - except Exception as e: - raise Exception("error instantiating UcnFile:{0}".format(str(e))) - else: - try: - hds = flopy.utils.HeadFile(hds_file) - except Exception as e: - raise Exception("error instantiating HeadFile:{0}".format(str(e))) - - nlay, nrow, ncol = hds.nlay, hds.nrow, hds.ncol - - dfs = [] - for site,k,i,j in zip(site_df.site,site_df.k,site_df.i,site_df.j): - assert k >= 0 and k < nlay - assert i >= 0 and i < nrow - assert j >= 0 and j < ncol - df = pd.DataFrame(data=hds.get_ts((k,i,j)),columns=["totim",site]) - df.index = df.pop("totim") - dfs.append(df) - df = pd.concat(dfs,axis=1) - #print(df) - df.to_csv(hds_file+"_timeseries.processed",sep=' ') - return df
    - - -
    [docs]def setup_hds_obs(hds_file,kperk_pairs=None,skip=None,prefix="hds"): - """a function to setup using all values from a - layer-stress period pair for observations. Writes - an instruction file and a _setup_ csv used - construct a control file. - - Parameters - ---------- - hds_file : str - a MODFLOW head-save file. If the hds_file endswith 'ucn', - then the file is treated as a UcnFile type. - kperk_pairs : iterable - an iterable of pairs of kper (zero-based stress - period index) and k (zero-based layer index) to - setup observations for. If None, then a shit-ton - of observations may be produced! - skip : variable - a value or function used to determine which values - to skip when setting up observations. If np.scalar(skip) - is True, then values equal to skip will not be used. If not - np.scalar(skip), then skip will be treated as a lambda function that - returns np.NaN if the value should be skipped. - prefix : str - the prefix to use for the observation names. default is "hds". - - Returns - ------- - (forward_run_line, df) : str, pd.DataFrame - a python code str to add to the forward run script and the setup info for the observations - - Note - ---- - requires flopy - - writes <hds_file>.dat.ins instruction file - - writes _setup_<hds_file>.csv which contains much - useful information for construction a control file - - - """ - try: - import flopy - except Exception as e: - print("error importing flopy, returning {0}".format(str(e))) - return - - assert os.path.exists(hds_file),"head save file not found" - if hds_file.lower().endswith(".ucn"): - try: - hds = flopy.utils.UcnFile(hds_file) - except Exception as e: - raise Exception("error instantiating UcnFile:{0}".format(str(e))) - else: - try: - hds = flopy.utils.HeadFile(hds_file) - except Exception as e: - raise Exception("error instantiating HeadFile:{0}".format(str(e))) - - if kperk_pairs is None: - kperk_pairs = [] - for kstp,kper in hds.kstpkper: - kperk_pairs.extend([(kper-1,k) for k in range(hds.nlay)]) - if len(kperk_pairs) == 2: - try: - if len(kperk_pairs[0]) == 2: - pass - except: - kperk_pairs = [kperk_pairs] - - #if start_datetime is not None: - # start_datetime = pd.to_datetime(start_datetime) - # dts = start_datetime + pd.to_timedelta(hds.times,unit='d') - data = {} - kpers = [kper-1 for kstp,kper in hds.kstpkper] - for kperk_pair in kperk_pairs: - kper,k = kperk_pair - assert kper in kpers, "kper not in hds:{0}".format(kper) - assert k in range(hds.nlay), "k not in hds:{0}".format(k) - kstp = last_kstp_from_kper(hds,kper) - d = hds.get_data(kstpkper=(kstp,kper))[k,:,:] - - data["{0}_{1}".format(kper,k)] = d.flatten() - #data[(kper,k)] = d.flatten() - idx,iidx,jidx = [],[],[] - for _ in range(len(data)): - for i in range(hds.nrow): - iidx.extend([i for _ in range(hds.ncol)]) - jidx.extend([j for j in range(hds.ncol)]) - idx.extend(["i{0:04d}_j{1:04d}".format(i,j) for j in range(hds.ncol)]) - idx = idx[:hds.nrow*hds.ncol] - - df = pd.DataFrame(data,index=idx) - data_cols = list(df.columns) - data_cols.sort() - #df.loc[:,"iidx"] = iidx - #df.loc[:,"jidx"] = jidx - if skip is not None: - for col in data_cols: - if np.isscalar(skip): - df.loc[df.loc[:,col]==skip,col] = np.NaN - else: - df.loc[:,col] = df.loc[:,col].apply(skip) - - # melt to long form - df = df.melt(var_name="kperk",value_name="obsval") - # set row and col identifies - df.loc[:,"iidx"] = iidx - df.loc[:,"jidx"] = jidx - #drop nans from skip - df = df.dropna() - #set some additional identifiers - df.loc[:,"kper"] = df.kperk.apply(lambda x: int(x.split('_')[0])) - df.loc[:,"kidx"] = df.pop("kperk").apply(lambda x: int(x.split('_')[1])) - - # form obs names - #def get_kper_str(kper): - # if start_datetime is not None: - # return dts[int(kper)].strftime("%Y%m%d") - # else: - # return "kper{0:04.0f}".format(kper) - fmt = prefix + "_{0:02.0f}_{1:03.0f}_{2:03.0f}_{3:03.0f}" - # df.loc[:,"obsnme"] = df.apply(lambda x: fmt.format(x.kidx,x.iidx,x.jidx, - # get_kper_str(x.kper)),axis=1) - df.loc[:,"obsnme"] = df.apply(lambda x: fmt.format(x.kidx,x.iidx,x.jidx, - x.kper),axis=1) - - df.loc[:,"ins_str"] = df.obsnme.apply(lambda x: "l1 w !{0}!".format(x)) - df.loc[:,"obgnme"] = prefix - #write the instruction file - with open(hds_file+".dat.ins","w") as f: - f.write("pif ~\nl1\n") - df.ins_str.to_string(f,index=False,header=False) - - #write the corresponding output file - df.loc[:,["obsnme","obsval"]].to_csv(hds_file+".dat",sep=' ',index=False) - - hds_path = os.path.dirname(hds_file) - setup_file = os.path.join(hds_path,"_setup_{0}.csv".format(os.path.split(hds_file)[-1])) - df.to_csv(setup_file) - fwd_run_line = "pyemu.gw_utils.apply_hds_obs('{0}')\n".format(hds_file) - df.index = df.obsnme - return fwd_run_line, df
    - - -
    [docs]def last_kstp_from_kper(hds,kper): - """ function to find the last time step (kstp) for a - give stress period (kper) in a modflow head save file. - - - Parameters - ---------- - hds : flopy.utils.HeadFile - - kper : int - the zero-index stress period number - - Returns - ------- - kstp : int - the zero-based last time step during stress period - kper in the head save file - - - """ - #find the last kstp with this kper - kstp = -1 - for kkstp,kkper in hds.kstpkper: - if kkper == kper+1 and kkstp > kstp: - kstp = kkstp - if kstp == -1: - raise Exception("kstp not found for kper {0}".format(kper)) - kstp -= 1 - return kstp
    - - -
    [docs]def apply_hds_obs(hds_file): - """ process a modflow head save file. A companion function to - setup_hds_obs that is called during the forward run process - - Parameters - ---------- - hds_file : str - a modflow head save filename. if hds_file ends with 'ucn', - then the file is treated as a UcnFile type. - - Note - ---- - requires flopy - - writes <hds_file>.dat - - expects <hds_file>.dat.ins to exist - - uses pyemu.pst_utils.parse_ins_file to get observation names - - """ - - try: - import flopy - except Exception as e: - raise Exception("apply_hds_obs(): error importing flopy: {0}".\ - format(str(e))) - from .. import pst_utils - assert os.path.exists(hds_file) - out_file = hds_file+".dat" - ins_file = out_file + ".ins" - assert os.path.exists(ins_file) - df = pd.DataFrame({"obsnme":pst_utils.parse_ins_file(ins_file)}) - df.index = df.obsnme - - # populate metdata - items = ["k","i","j","kper"] - for i,item in enumerate(items): - df.loc[:,item] = df.obsnme.apply(lambda x: int(x.split('_')[i+1])) - - if hds_file.lower().endswith('ucn'): - hds = flopy.utils.UcnFile(hds_file) - else: - hds = flopy.utils.HeadFile(hds_file) - kpers = df.kper.unique() - df.loc[:,"obsval"] = np.NaN - for kper in kpers: - kstp = last_kstp_from_kper(hds,kper) - data = hds.get_data(kstpkper=(kstp,kper)) - #jwhite 15jan2018 fix for really large values that are getting some - #trash added to them... - data[data>1.0e+20] = 1.0e+20 - data[data<-1.0e+20] = -1.0e+20 - df_kper = df.loc[df.kper==kper,:] - df.loc[df_kper.index,"obsval"] = data[df_kper.k,df_kper.i,df_kper.j] - assert df.dropna().shape[0] == df.shape[0] - df.loc[:,["obsnme","obsval"]].to_csv(out_file,index=False,sep=" ")
    - - -
    [docs]def setup_sft_obs(sft_file,ins_file=None,start_datetime=None,times=None,ncomp=1): - """writes an instruction file for a mt3d-usgs sft output file - - Parameters - ---------- - sft_file : str - the sft output file (ASCII) - ins_file : str - the name of the instruction file to create. If None, the name - is <sft_file>.ins. Default is None - start_datetime : str - a pandas.to_datetime() compatible str. If not None, - then the resulting observation names have the datetime - suffix. If None, the suffix is the output totim. Default - is None - times : iterable - a container of times to make observations for. If None, all times are used. - Default is None. - ncomp : int - number of components in transport model. Default is 1. - - - Returns - ------- - df : pandas.DataFrame - a dataframe with obsnme and obsval for the sft simulated concentrations and flows. - If inschek was not successfully run, then returns None - - - Note - ---- - setups up observations for SW conc, GW conc and flowgw for all times and reaches. - """ - - df = pd.read_csv(sft_file,skiprows=1,delim_whitespace=True) - df.columns = [c.lower().replace("-","_") for c in df.columns] - if times is None: - times = df.time.unique() - missing = [] - utimes = df.time.unique() - for t in times: - if t not in utimes: - missing.append(str(t)) - if len(missing) > 0: - print(df.time) - raise Exception("the following times are missing:{0}".format(','.join(missing))) - with open("sft_obs.config",'w') as f: - f.write(sft_file+'\n') - [f.write("{0:15.6E}\n".format(t)) for t in times] - df = apply_sft_obs() - utimes = df.time.unique() - for t in times: - assert t in utimes,"time {0} missing in processed dataframe".format(t) - idx = df.time.apply(lambda x: x in times) - if start_datetime is not None: - start_datetime = pd.to_datetime(start_datetime) - df.loc[:,"time_str"] = pd.to_timedelta(df.time,unit='d') + start_datetime - df.loc[:,"time_str"] = df.time_str.apply(lambda x: datetime.strftime(x,"%Y%m%d")) - else: - df.loc[:,"time_str"] = df.time.apply(lambda x: "{0:08.2f}".format(x)) - df.loc[:,"ins_str"] = "l1\n" - # check for multiple components - df_times = df.loc[idx,:] - df.loc[:,"icomp"] = 1 - icomp_idx = list(df.columns).index("icomp") - for t in times: - df_time = df.loc[df.time==t,:] - vc = df_time.sfr_node.value_counts() - ncomp = vc.max() - assert np.all(vc.values==ncomp) - nstrm = df_time.shape[0] / ncomp - for icomp in range(ncomp): - s = int(nstrm*(icomp)) - e = int(nstrm*(icomp+1)) - idxs = df_time.iloc[s:e,:].index - #df_time.iloc[nstrm*(icomp):nstrm*(icomp+1),icomp_idx.loc["icomp"] = int(icomp+1) - df_time.loc[idxs,"icomp"] = int(icomp+1) - - df.loc[df_time.index,"ins_str"] = df_time.apply(lambda x: "l1 w w !sfrc{0}_{1}_{2}! !swgw{0}_{1}_{2}! !gwcn{0}_{1}_{2}!\n".\ - format(x.sfr_node,x.icomp,x.time_str),axis=1) - df.index = np.arange(df.shape[0]) - if ins_file is None: - ins_file = sft_file+".processed.ins" - - with open(ins_file,'w') as f: - f.write("pif ~\nl1\n") - [f.write(i) for i in df.ins_str] - #df = _try_run_inschek(ins_file,sft_file+".processed") - df = try_process_ins_file(ins_file,sft_file+".processed") - if df is not None: - return df - else: - return None
    - - -
    [docs]def apply_sft_obs(): - times = [] - with open("sft_obs.config") as f: - sft_file = f.readline().strip() - for line in f: - times.append(float(line.strip())) - df = pd.read_csv(sft_file,skiprows=1,delim_whitespace=True) - df.columns = [c.lower().replace("-", "_") for c in df.columns] - - #normalize - for c in df.columns: - df.loc[df.loc[:,c].apply(lambda x: x<1e-30),c] = 0.0 - df.loc[df.loc[:, c] > 1e+30, c] = 1.0e+30 - df.loc[:,"sfr_node"] = df.sfr_node.apply(np.int) - df = df.loc[df.time.apply(lambda x: x in times),:] - df.to_csv(sft_file+".processed",sep=' ',index=False) - return df
    - - -
    [docs]def setup_sfr_seg_parameters(nam_file,model_ws='.',par_cols=["flow","runoff","hcond1","hcond2", "pptsw"], - tie_hcond=True): - """Setup multiplier parameters for SFR segment data. Just handles the - standard input case, not all the cryptic SFR options. Loads the dis, bas, and sfr files - with flopy using model_ws. However, expects that apply_sfr_seg_parameters() will be called - from within model_ws at runtime. - - Parameters - ---------- - nam_file : str - MODFLOw name file. DIS, BAS, and SFR must be available as pathed in the nam_file - model_ws : str - model workspace for flopy to load the MODFLOW model from - OR - nam_file : flopy.modflow.mf.Modflow - flopy modflow model object - par_cols : list(str) - segment data entires to parameterize - tie_hcond : flag to use same mult par for hcond1 and hcond2 for a given segment. Default is True - - Returns - ------- - df : pandas.DataFrame - a dataframe with useful parameter setup information - - Note - ---- - the number (and numbering) of segment data entries must consistent across - all stress periods. - writes <nam_file>+"_backup_.sfr" as the backup of the original sfr file - skips values = 0.0 since multipliers don't work for these - - """ - - try: - import flopy - except Exception as e: - return - - if tie_hcond: - if "hcond1" not in par_cols or "hcond2" not in par_cols: - tie_hcond = False - - if isinstance(nam_file,flopy.modflow.mf.Modflow) and nam_file.sfr is not None: - m = nam_file - nam_file = m.namefile - model_ws = m.model_ws - else: - # load MODFLOW model # is this needed? could we just pass the model if it has already been read in? - m = flopy.modflow.Modflow.load(nam_file,load_only=["sfr"],model_ws=model_ws,check=False,forgive=False) - #make backup copy of sfr file - shutil.copy(os.path.join(model_ws,m.sfr.file_name[0]),os.path.join(model_ws,nam_file+"_backup_.sfr")) - - #get the segment data (dict) - segment_data = m.sfr.segment_data - shape = segment_data[list(segment_data.keys())[0]].shape - # check - for kper,seg_data in m.sfr.segment_data.items(): - assert seg_data.shape == shape,"cannot use: seg data must have the same number of entires for all kpers" - seg_data_col_order = list(seg_data.dtype.names) - # convert segment_data dictionary to multi index df - this could get ugly - reform = {(k, c): segment_data[k][c] for k in segment_data.keys() for c in segment_data[k].dtype.names} - seg_data_all_kper = pd.DataFrame.from_dict(reform) - seg_data_all_kper.columns.names = ['kper', 'col'] - - # extract the first seg data kper to a dataframe - seg_data = seg_data_all_kper[0].copy() # pd.DataFrame.from_records(seg_data) - - #make sure all par cols are found and search of any data in kpers - missing = [] - for par_col in par_cols: - if par_col not in seg_data.columns: - missing.append(par_col) - # look across all kper in multiindex df to check for values entry - fill with absmax should capture entries - seg_data.loc[:,par_col] = seg_data_all_kper.loc[:, (slice(None),par_col)].abs().max(level=1,axis=1) - if len(missing) > 0: - raise Exception("the following par_cols were not found: {0}".format(','.join(missing))) - - seg_data = seg_data[seg_data_col_order] # reset column orders to inital - seg_data_org = seg_data.copy() - seg_data.to_csv(os.path.join(model_ws, "sfr_seg_pars.dat"), sep=',') - - #the data cols not to parameterize - # better than a column indexer as pandas can change column orders - idx_cols=['nseg', 'icalc', 'outseg', 'iupseg', 'iprior', 'nstrpts'] - notpar_cols = [c for c in seg_data.columns if c not in par_cols+idx_cols] - - #process par cols - tpl_str,pvals = [],[] - for par_col in par_cols: - prefix = par_col - if tie_hcond and par_col == 'hcond2': - prefix = 'hcond1' - if seg_data.loc[:,par_col].sum() == 0.0: - print("all zeros for {0}...skipping...".format(par_col)) - #seg_data.loc[:,par_col] = 1 - else: - seg_data.loc[:,par_col] = seg_data.apply(lambda x: "~ {0}_{1:04d} ~". - format(prefix,int(x.nseg)) if float(x[par_col]) != 0.0\ - else "1.0",axis=1) - - org_vals = seg_data_org.loc[seg_data_org.loc[:,par_col] != 0.0,par_col] - pnames = seg_data.loc[org_vals.index,par_col] - pvals.extend(list(org_vals.values)) - tpl_str.extend(list(pnames.values)) - - pnames = [t.replace('~','').strip() for t in tpl_str] - df = pd.DataFrame({"parnme":pnames,"org_value":pvals,"tpl_str":tpl_str},index=pnames) - df.drop_duplicates(inplace=True) - #set not par cols to 1.0 - seg_data.loc[:,notpar_cols] = "1.0" - - #write the template file - #with open(os.path.join(model_ws,"sfr_seg_pars.dat.tpl"),'w') as f: - # f.write("ptf ~\n") - # seg_data.to_csv(f,sep=',') - write_df_tpl(os.path.join(model_ws,"sfr_seg_pars.dat.tpl"),seg_data,sep=',') - - #write the config file used by apply_sfr_pars() - with open(os.path.join(model_ws,"sfr_seg_pars.config"),'w') as f: - f.write("nam_file {0}\n".format(nam_file)) - f.write("model_ws {0}\n".format(model_ws)) - f.write("mult_file sfr_seg_pars.dat\n") - f.write("sfr_filename {0}".format(m.sfr.file_name[0])) - - #make sure the tpl file exists and has the same num of pars - parnme = parse_tpl_file(os.path.join(model_ws,"sfr_seg_pars.dat.tpl")) - assert len(parnme) == df.shape[0] - - #set some useful par info - df.loc[:,"pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) - df.loc[:,"parubnd"] = 1.25 - df.loc[:,"parlbnd"] = 0.75 - hpars = df.loc[df.pargp.apply(lambda x: x.startswith("hcond")),"parnme"] - df.loc[hpars,"parubnd"] = 100.0 - df.loc[hpars, "parlbnd"] = 0.01 - return df
    - -
    [docs]def setup_sfr_reach_parameters(nam_file,model_ws='.',par_cols=['strhc1']): - """Setup multiplier paramters for reach data, when reachinput option is specififed in sfr. - Similare to setup_sfr_seg_parameters() method will apply params to sfr reachdata - Can load the dis, bas, and sfr files with flopy using model_ws. Or can pass a model object (SFR loading can be slow) - However, expects that apply_sfr_reach_parameters() will be called - from within model_ws at runtime. - - Parameters - ---------- - nam_file : str - MODFLOw name file. DIS, BAS, and SFR must be available as pathed in the nam_file - model_ws : str - model workspace for flopy to load the MODFLOW model from - OR - nam_file : flopy.modflow.mf.Modflow - flopy modflow model object - - par_cols : list(str) - segment data entires to parameterize - - Returns - ------- - df : pandas.DataFrame - a dataframe with useful parameter setup information - - Note - ---- - skips values = 0.0 since multipliers don't work for these - - """ - try: - import flopy - except Exception as e: - return - - if isinstance(nam_file,flopy.modflow.mf.Modflow) and nam_file.sfr is not None: - # flopy MODFLOW model has been passed and has SFR loaded - m = nam_file - nam_file = m.namefile - model_ws = m.model_ws - else: - # if model has not been passed or SFR not loaded # load MODFLOW model - m = flopy.modflow.Modflow.load(nam_file,load_only=["sfr"],model_ws=model_ws,check=False,forgive=False) - # get reachdata as dataframe - reach_data = pd.DataFrame.from_records(m.sfr.reach_data) - # write inital reach_data as csv - reach_data_orig = reach_data.copy() - reach_data.to_csv(os.path.join(m.model_ws, "sfr_reach_pars.dat"), sep=' ') - - # generate template file with pars in par_cols - #process par cols - tpl_str,pvals = [],[] - par_cols=["strhc1"] - idx_cols=["node", "k", "i", "j", "iseg", "ireach", "reachID", "outreach"] - #the data cols not to parameterize - notpar_cols = [c for c in reach_data.columns if c not in par_cols+idx_cols] - for par_col in par_cols: - if par_col == "strhc1": - prefix = 'strk' # shorten par - else: - prefix = par_col - reach_data.loc[:, par_col] = reach_data.apply( - lambda x: "~ {0}_{1:04d} ~".format(prefix, int(x.reachID)) - if float(x[par_col]) != 0.0 else "1.0", axis=1) - org_vals = reach_data_orig.loc[reach_data_orig.loc[:, par_col] != 0.0, par_col] - pnames = reach_data.loc[org_vals.index, par_col] - pvals.extend(list(org_vals.values)) - tpl_str.extend(list(pnames.values)) - pnames = [t.replace('~','').strip() for t in tpl_str] - df = pd.DataFrame({"parnme":pnames,"org_value":pvals,"tpl_str":tpl_str},index=pnames) - df.drop_duplicates(inplace=True) - - # set not par cols to 1.0 - reach_data.loc[:, notpar_cols] = "1.0" - - # write the template file - #with open(os.path.join(model_ws, "sfr_reach_pars.dat.tpl"), 'w') as f: - # f.write("ptf ~\n") - # reach_data.to_csv(f, sep=',',quotechar=' ',quoting=1) - write_df_tpl(os.path.join(model_ws, "sfr_reach_pars.dat.tpl"),reach_data,sep=',') - - # write the config file used by apply_sfr_pars() - with open(os.path.join(model_ws, "sfr_reach_pars.config"), 'w') as f: - f.write("nam_file {0}\n".format(nam_file)) - f.write("model_ws {0}\n".format(model_ws)) - f.write("mult_file sfr_reach_pars.dat\n") - f.write("sfr_filename {0}".format(m.sfr.file_name[0])) - - # make sure the tpl file exists and has the same num of pars - parnme = parse_tpl_file(os.path.join(model_ws, "sfr_reach_pars.dat.tpl")) - assert len(parnme) == df.shape[0] - - # set some useful par info - df.loc[:, "pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) - df.loc[:, "parubnd"] = 1.25 - df.loc[:, "parlbnd"] = 0.75 - hpars = df.loc[df.pargp.apply(lambda x: x.startswith("strk")), "parnme"] - df.loc[hpars, "parubnd"] = 100.0 - df.loc[hpars, "parlbnd"] = 0.01 - return df
    - - -
    [docs]def apply_sfr_seg_parameters(reach_pars=False): - """apply the SFR segement multiplier parameters. Expected to be run in the same dir - as the model exists - - Parameters - ---------- - reach_pars : bool - if reach paramters need to be applied - - Returns - ------- - sfr : flopy.modflow.ModflowSfr instance - - Note - ---- - expects "sfr_seg_pars.config" to exist - expects <nam_file>+"_backup_.sfr" to exist - - - """ - import flopy - assert os.path.exists("sfr_seg_pars.config") - - with open("sfr_seg_pars.config",'r') as f: - pars = {} - for line in f: - line = line.strip().split() - pars[line[0]] = line[1] - bak_sfr_file = pars["nam_file"]+"_backup_.sfr" - #m = flopy.modflow.Modflow.load(pars["nam_file"],model_ws=pars["model_ws"],load_only=["sfr"],check=False) - m = flopy.modflow.Modflow.load(pars["nam_file"], load_only=[], check=False) - sfr = flopy.modflow.ModflowSfr2.load(os.path.join(bak_sfr_file),m) - - mlt_df = pd.read_csv(pars["mult_file"],delim_whitespace=False,index_col=0) - - idx_cols = ['nseg', 'icalc', 'outseg', 'iupseg', 'iprior', 'nstrpts'] - present_cols = [c for c in idx_cols if c in mlt_df.columns] - mlt_cols = mlt_df.columns.drop(present_cols) - for key,val in m.sfr.segment_data.items(): - df = pd.DataFrame.from_records(val) - df.loc[:,mlt_cols] *= mlt_df.loc[:,mlt_cols] - val = df.to_records(index=False) - sfr.segment_data[key] = val - if reach_pars: - assert os.path.exists("sfr_reach_pars.config") - - with open("sfr_reach_pars.config", 'r') as f: - r_pars = {} - for line in f: - line = line.strip().split() - r_pars[line[0]] = line[1] - r_mlt_df = pd.read_csv(r_pars["mult_file"],sep=',',index_col=0) - r_idx_cols = ["node", "k", "i", "j", "iseg", "ireach", "reachID", "outreach"] - r_mlt_cols = r_mlt_df.columns.drop(r_idx_cols) - r_df = pd.DataFrame.from_records(m.sfr.reach_data) - r_df.loc[:, r_mlt_cols] *= r_mlt_df.loc[:, r_mlt_cols] - sfr.reach_data = r_df.to_records(index=False) - #m.remove_package("sfr") - sfr.write_file(filename=pars["sfr_filename"]) - return sfr
    - -
    [docs]def apply_sfr_parameters(reach_pars=False): - sfr = apply_sfr_seg_parameters(reach_pars=reach_pars) - return sfr
    - -
    [docs]def setup_sfr_obs(sfr_out_file,seg_group_dict=None,ins_file=None,model=None, - include_path=False): - """setup observations using the sfr ASCII output file. Setups - the ability to aggregate flows for groups of segments. Applies - only flow to aquier and flow out. - - Parameters - ---------- - sft_out_file : str - the existing SFR output file - seg_group_dict : dict - a dictionary of SFR segements to aggregate together for a single obs. - the key value in the dict is the base observation name. If None, all segments - are used as individual observations. Default is None - model : flopy.mbase - a flopy model. If passed, the observation names will have the datetime of the - observation appended to them. If None, the observation names will have the - stress period appended to them. Default is None. - include_path : bool - flag to prepend sfr_out_file path to sfr_obs.config. Useful for setting up - process in separate directory for where python is running. - - - Returns - ------- - df : pd.DataFrame - dataframe of obsnme, obsval and obgnme if inschek run was successful. Else None - - Note - ---- - This function writes "sfr_obs.config" which must be kept in the dir where - "apply_sfr_obs()" is being called during the forward run - - """ - - sfr_dict = load_sfr_out(sfr_out_file) - kpers = list(sfr_dict.keys()) - kpers.sort() - - if seg_group_dict is None: - seg_group_dict = {"seg{0:04d}".format(s):s for s in sfr_dict[kpers[0]].segment} - - sfr_segs = set(sfr_dict[list(sfr_dict.keys())[0]].segment) - keys = ["sfr_out_file"] - if include_path: - values = [os.path.split(sfr_out_file)[-1]] - else: - values = [sfr_out_file] - for oname,segs in seg_group_dict.items(): - if np.isscalar(segs): - segs_set = {segs} - segs = [segs] - else: - segs_set = set(segs) - diff = segs_set.difference(sfr_segs) - if len(diff) > 0: - raise Exception("the following segs listed with oname {0} where not found: {1}". - format(oname,','.join([str(s) for s in diff]))) - for seg in segs: - keys.append(oname) - values.append(seg) - - df_key = pd.DataFrame({"obs_base":keys,"segment":values}) - if include_path: - pth = os.path.join(*[p for p in os.path.split(sfr_out_file)[:-1]]) - config_file = os.path.join(pth,"sfr_obs.config") - else: - config_file = "sfr_obs.config" - print("writing 'sfr_obs.config' to {0}".format(config_file)) - df_key.to_csv(config_file) - - bd = '.' - if include_path: - bd = os.getcwd() - os.chdir(pth) - try: - df = apply_sfr_obs() - except Exception as e: - os.chdir(bd) - raise Exception("error in apply_sfr_obs(): {0}".format(str(e))) - os.chdir(bd) - if model is not None: - dts = (pd.to_datetime(model.start_datetime) + pd.to_timedelta(np.cumsum(model.dis.perlen.array),unit='d')).date - df.loc[:,"datetime"] = df.kper.apply(lambda x: dts[x]) - df.loc[:,"time_str"] = df.datetime.apply(lambda x: x.strftime("%Y%m%d")) - else: - df.loc[:,"time_str"] = df.kper.apply(lambda x: "{0:04d}".format(x)) - df.loc[:,"flaqx_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fa",x.obs_base,x.time_str),axis=1) - df.loc[:,"flout_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fo",x.obs_base,x.time_str),axis=1) - - if ins_file is None: - ins_file = sfr_out_file + ".processed.ins" - - with open(ins_file,'w') as f: - f.write("pif ~\nl1\n") - for fla,flo in zip(df.flaqx_obsnme,df.flout_obsnme): - f.write("l1 w w !{0}! !{1}!\n".format(fla,flo)) - - df = None - pth = os.path.split(ins_file)[:-1] - pth = os.path.join(*pth) - if pth == '': - pth = '.' - bd = os.getcwd() - os.chdir(pth) - try: - #df = _try_run_inschek(os.path.split(ins_file)[-1],os.path.split(sfr_out_file+".processed")[-1]) - df = try_process_ins_file(os.path.split(ins_file)[-1],os.path.split(sfr_out_file+".processed")[-1]) - except Exception as e: - pass - os.chdir(bd) - if df is not None: - df.loc[:,"obsnme"] = df.index.values - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: "flaqx" if x.startswith("fa") else "flout") - return df
    - - -
    [docs]def apply_sfr_obs(): - """apply the sfr observation process - pairs with setup_sfr_obs(). - requires sfr_obs.config. Writes <sfr_out_file>.processed, where - <sfr_out_file> is defined in "sfr_obs.config" - - - Parameters - ---------- - None - - Returns - ------- - df : pd.DataFrame - a dataframe of aggregrated sfr segment aquifer and outflow - """ - assert os.path.exists("sfr_obs.config") - df_key = pd.read_csv("sfr_obs.config",index_col=0) - - assert df_key.iloc[0,0] == "sfr_out_file",df_key.iloc[0,:] - sfr_out_file = df_key.iloc[0,1] - df_key = df_key.iloc[1:,:] - df_key.loc[:, "segment"] = df_key.segment.apply(np.int) - df_key.index = df_key.segment - seg_group_dict = df_key.groupby(df_key.obs_base).groups - - sfr_kper = load_sfr_out(sfr_out_file) - kpers = list(sfr_kper.keys()) - kpers.sort() - #results = {o:[] for o in seg_group_dict.keys()} - results = [] - for kper in kpers: - df = sfr_kper[kper] - for obs_base,segs in seg_group_dict.items(): - agg = df.loc[segs.values,:].sum() - #print(obs_base,agg) - results.append([kper,obs_base,agg["flaqx"],agg["flout"]]) - df = pd.DataFrame(data=results,columns=["kper","obs_base","flaqx","flout"]) - df.sort_values(by=["kper","obs_base"],inplace=True) - df.to_csv(sfr_out_file+".processed",sep=' ',index=False) - return df
    - - -
    [docs]def load_sfr_out(sfr_out_file): - """load an ASCII SFR output file into a dictionary of kper: dataframes. aggregates - segments and only returns flow to aquifer and flow out. - - Parameters - ---------- - sfr_out_file : str - SFR ASCII output file - - Returns - ------- - sfr_dict : dict - dictionary of {kper:dataframe} - - """ - assert os.path.exists(sfr_out_file),"couldn't find sfr out file {0}".\ - format(sfr_out_file) - tag = " stream listing" - lcount = 0 - sfr_dict = {} - with open(sfr_out_file) as f: - while True: - line = f.readline().lower() - lcount += 1 - if line == '': - break - if line.startswith(tag): - raw = line.strip().split() - kper = int(raw[3]) - 1 - kstp = int(raw[5]) - 1 - [f.readline() for _ in range(4)] #skip to where the data starts - lcount += 4 - dlines = [] - while True: - dline = f.readline() - lcount += 1 - if dline.strip() == '': - break - draw = dline.strip().split() - dlines.append(draw) - df = pd.DataFrame(data=np.array(dlines)).iloc[:,[3,6,7]] - df.columns = ["segment","flaqx","flout"] - df.loc[:,"segment"] = df.segment.apply(np.int) - df.loc[:,"flaqx"] = df.flaqx.apply(np.float) - df.loc[:,"flout"] = df.flout.apply(np.float) - df.index = df.segment - df = df.groupby(df.segment).sum() - df.loc[:,"segment"] = df.index - if kper in sfr_dict.keys(): - print("multiple entries found for kper {0}, replacing...".format(kper)) - sfr_dict[kper] = df - return sfr_dict
    - -
    [docs]def modflow_sfr_gag_to_instruction_file(gage_output_file, ins_file=None): - """writes an instruction file for an SFR gage output file to read Flow only at all times - - Parameters - ---------- - gage_output_file : str - the gage output filename (ASCII) - - ins_file : str - the name of the instruction file to create. If None, the name - is <gage_output_file>.ins. Default is None - - Returns - ------- - df : pandas.DataFrame - a dataframe with obsnme and obsval for the sfr simulated flows. - If inschek was not successfully run, then returns None - ins_file : str - file name of instructions file relating to gage output. - obs_file : str - file name of processed gage output for all times - - - Note - ---- - setups up observations for gage outputs only for the Flow column. - - TODO : allow other observation types and align explicitly with times - now returns all values - """ - - if ins_file is None: - ins_file = gage_output_file + '.ins' - - # navigate the file to be sure the header makes sense - indat = [line.strip() for line in open(gage_output_file, 'r').readlines()] - header = [i for i in indat if i.startswith('"')] - # yank out the gage number to identify the observation names - gage_num = int(re.sub("[^0-9]", "", indat[0].lower().split("gage no.")[-1].strip().split()[0])) - - # get the column names - cols = [i for i in header if 'data' in i.lower()][0].lower().replace('"', '').replace('data:', '').split() - - # make sure "Flow" is included in the columns - if 'flow' not in cols: - raise Exception('Requested field "Flow" not in gage output columns') - - # find which column is for "Flow" - flowidx = np.where(np.array(cols) == 'flow')[0][0] - - # write out the instruction file lines - inslines = ['l1 ' + (flowidx + 1) * 'w ' + '!g{0:d}_{1:d}!'.format(gage_num, j) - for j in range(len(indat) - len(header))] - inslines[0] = inslines[0].replace('l1', 'l{0:d}'.format(len(header) + 1)) - - # write the instruction file - with open(ins_file, 'w') as ofp: - ofp.write('pif ~\n') - [ofp.write('{0}\n'.format(line)) for line in inslines] - - df = _try_run_inschek(ins_file, gage_output_file) - if df is not None: - return df, ins_file, gage_output_file - else: - return None
    - -
    [docs]def setup_gage_obs(gage_file,ins_file=None,start_datetime=None,times=None): - """writes an instruction file for a mt3d-usgs sft output file - - Parameters - ---------- - gage_file : str - the gage output file (ASCII) - ins_file : str - the name of the instruction file to create. If None, the name - is <gage_file>.processed.ins. Default is None - start_datetime : str - a pandas.to_datetime() compatible str. If not None, - then the resulting observation names have the datetime - suffix. If None, the suffix is the output totim. Default - is None - times : iterable - a container of times to make observations for. If None, all times are used. - Default is None. - - - Returns - ------- - df : pandas.DataFrame - a dataframe with obsnme and obsval for the sft simulated concentrations and flows. - If inschek was not successfully run, then returns None - ins_file : str - file name of instructions file relating to gage output. - obs_file : str - file name of processed gage output (processed according to times passed above.) - - - Note - ---- - setups up observations for gage outputs (all columns). - """ - - with open(gage_file, 'r') as f: - line1 = f.readline() - gage_num = int(re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0])) - gage_type = line1.split("GAGE No.")[-1].strip().split()[1].lower() - obj_num = int(line1.replace('"', '').strip().split()[-1]) - line2 = f.readline() - df = pd.read_csv(f, delim_whitespace=True, names=line2.replace('"', '').split()[1:]) - - df.columns = [c.lower().replace("-", "_").replace('.', '_').strip('_') for c in df.columns] - # get unique observation ids - obs_ids = {col:"" for col in df.columns[1:]} # empty dictionary for observation ids - for col in df.columns[1:]: # exclude column 1 (TIME) - colspl = col.split('_') - if len(colspl) > 1: - # obs name built out of "g"(for gage) "s" or "l"(for gage type) 2 chars from column name - date added later - obs_ids[col] = "g{0}{1}{2}".format(gage_type[0], colspl[0][0],colspl[-1][0]) - else: - obs_ids[col] = "g{0}{1}".format(gage_type[0], col[0:2]) - with open("_gage_obs_ids.csv", "w") as f: # write file relating obs names to meaningfull keys! - [f.write('{0},{1}\n'.format(key, obs)) for key, obs in obs_ids.items()] - # find passed times in df - if times is None: - times = df.time.unique() - missing = [] - utimes = df.time.unique() - for t in times: - if not np.isclose(t, utimes).any(): - missing.append(str(t)) - if len(missing) > 0: - print(df.time) - raise Exception("the following times are missing:{0}".format(','.join(missing))) - # write output times to config file - with open("gage_obs.config", 'w') as f: - f.write(gage_file+'\n') - [f.write("{0:15.10E}\n".format(t)) for t in times] - # extract data for times: returns dataframe and saves a processed df - read by pest - df, obs_file = apply_gage_obs(return_obs_file=True) - utimes = df.time.unique() - for t in times: - assert np.isclose(t, utimes).any(), "time {0} missing in processed dataframe".format(t) - idx = df.time.apply(lambda x: np.isclose(x, times).any()) # boolean selector of desired times in df - if start_datetime is not None: - # convert times to usable observation times - start_datetime = pd.to_datetime(start_datetime) - df.loc[:, "time_str"] = pd.to_timedelta(df.time, unit='d') + start_datetime - df.loc[:, "time_str"] = df.time_str.apply(lambda x: datetime.strftime(x, "%Y%m%d")) - else: - df.loc[:, "time_str"] = df.time.apply(lambda x: "{0:08.2f}".format(x)) - # set up instructions (line feed for lines without obs (not in time) - df.loc[:, "ins_str"] = "l1\n" - df_times = df.loc[idx, :] # Slice by desired times - # TODO include GAGE No. in obs name (if permissible) - df.loc[df_times.index, "ins_str"] = df_times.apply(lambda x: "l1 w {}\n".format( - ' w '.join(["!{0}{1}!".format(obs, x.time_str) for key,obs in obs_ids.items()])), axis=1) - df.index = np.arange(df.shape[0]) - if ins_file is None: - ins_file = gage_file+".processed.ins" - - with open(ins_file, 'w') as f: - f.write("pif ~\nl1\n") - [f.write(i) for i in df.ins_str] - df = _try_run_inschek(ins_file, gage_file+".processed") - if df is not None: - return df, ins_file, obs_file - else: - return None
    - - -
    [docs]def apply_gage_obs(return_obs_file=False): - times = [] - with open("gage_obs.config") as f: - gage_file = f.readline().strip() - for line in f: - times.append(float(line.strip())) - obs_file = gage_file+".processed" - with open(gage_file, 'r') as f: - line1 = f.readline() - gage_num = int(re.sub("[^0-9]", "", line1.split("GAGE No.")[-1].strip().split()[0])) - gage_type = line1.split("GAGE No.")[-1].strip().split()[1].lower() - obj_num = int(line1.replace('"', '').strip().split()[-1]) - line2 = f.readline() - df = pd.read_csv(f, delim_whitespace=True, names=line2.replace('"', '').split()[1:]) - df.columns = [c.lower().replace("-", "_").replace('.', '_') for c in df.columns] - df = df.loc[df.time.apply(lambda x: np.isclose(x, times).any()), :] - df.to_csv(obs_file, sep=' ', index=False) - if return_obs_file: - return df, obs_file - else: - return df
    - - -
    [docs]def write_hfb_zone_multipliers_template(m): - """write a template file for an hfb using multipliers per zone (double yuck!) - - Parameters - ---------- - m : flopy.modflow.Modflow instance with an HFB file - - Returns - ------- - (hfb_mults, tpl_filename) : (dict, str) - a dictionary with original unique HFB conductivity values and their corresponding parameter names - and the name of the template file - - """ - assert m.hfb6 is not None - # find the model file - hfb_file = os.path.join(m.model_ws, m.hfb6.file_name[0]) - - # this will use multipliers, so need to copy down the original - if not os.path.exists('hfb6_org'): - os.mkdir('hfb6_org') - # copy down the original file - shutil.copy2(os.path.join(m.model_ws, m.hfb6.file_name[0]), os.path.join('hfb6_org', m.hfb6.file_name[0])) - - if not os.path.exists('hfb6_mlt'): - os.mkdir('hfb6_mlt') - - # read in the model file - hfb_file_contents = open(hfb_file, 'r').readlines() - - # navigate the header - skiprows = sum([1 if i.strip().startswith('#') else 0 for i in hfb_file_contents]) + 1 - header = hfb_file_contents[:skiprows] - - # read in the data - names = ['lay', 'irow1','icol1','irow2','icol2', 'hydchr'] - hfb_in = pd.read_csv(hfb_file, skiprows=skiprows, delim_whitespace=True, names=names).dropna() - for cn in names[:-1]: - hfb_in[cn] = hfb_in[cn].astype(np.int) - - # set up a multiplier for each unique conductivity value - unique_cond = hfb_in.hydchr.unique() - hfb_mults = dict(zip(unique_cond, ['hbz_{0:04d}'.format(i) for i in range(len(unique_cond))])) - # set up the TPL line for each parameter and assign - hfb_in['tpl'] = 'blank' - for cn, cg in hfb_in.groupby('hydchr'): - hfb_in.loc[hfb_in.hydchr == cn, 'tpl'] = '~{0:^10s}~'.format(hfb_mults[cn]) - - assert 'blank' not in hfb_in.tpl - - # write out the TPL file - tpl_file = "hfb6.mlt.tpl" - with open(tpl_file, 'w') as ofp: - ofp.write('ptf ~\n') - [ofp.write('{0}\n'.format(line.strip())) for line in header] - ofp.flush() - hfb_in[['lay', 'irow1','icol1','irow2','icol2', 'tpl']].to_csv(ofp, sep=' ', quotechar=' ', - header=None, index=None, mode='a') - - # make a lookup for lining up the necessary files to perform multiplication with the - # helpers.apply_hfb_pars() function which must be added to the forward run script - with open('hfb6_pars.csv', 'w') as ofp: - ofp.write('org_file,mlt_file,model_file\n') - ofp.write('{0},{1},{2}\n'.format(os.path.join('hfb6_org', m.hfb6.file_name[0]), - os.path.join('hfb6_mlt', tpl_file.replace('.tpl','')), - hfb_file)) - - return hfb_mults, tpl_file
    - - -
    [docs]def write_hfb_template(m): - """write a template file for an hfb (yuck!) - - Parameters - ---------- - m : flopy.modflow.Modflow instance with an HFB file - - Returns - ------- - (tpl_filename, df) : (str, pandas.DataFrame) - the name of the template file and a dataframe with useful info. - - """ - - assert m.hfb6 is not None - hfb_file = os.path.join(m.model_ws,m.hfb6.file_name[0]) - assert os.path.exists(hfb_file),"couldn't find hfb_file".format(hfb_file) - f_in = open(hfb_file,'r') - tpl_file = hfb_file+".tpl" - f_tpl = open(tpl_file,'w') - f_tpl.write("ptf ~\n") - parnme,parval1,xs,ys = [],[],[],[] - iis,jjs,kks = [],[],[] - xc = m.sr.xcentergrid - yc = m.sr.ycentergrid - - while True: - line = f_in.readline() - if line == "": - break - f_tpl.write(line) - if not line.startswith("#"): - raw = line.strip().split() - nphfb = int(raw[0]) - mxfb = int(raw[1]) - nhfbnp = int(raw[2]) - if nphfb > 0 or mxfb > 0: - raise Exception("not supporting terrible HFB pars") - for i in range(nhfbnp): - line = f_in.readline() - if line == "": - raise Exception("EOF") - raw = line.strip().split() - k = int(raw[0]) - 1 - i = int(raw[1]) - 1 - j = int(raw[2]) - 1 - pn = "hb{0:02}{1:04d}{2:04}".format(k,i,j) - pv = float(raw[5]) - raw[5] = "~ {0} ~".format(pn) - line = ' '.join(raw)+'\n' - f_tpl.write(line) - parnme.append(pn) - parval1.append(pv) - xs.append(xc[i,j]) - ys.append(yc[i,j]) - iis.append(i) - jjs.append(j) - kks.append(k) - - break - - f_tpl.close() - f_in.close() - df = pd.DataFrame({"parnme":parnme,"parval1":parval1,"x":xs,"y":ys, - "i":iis,"j":jjs,"k":kks},index=parnme) - df.loc[:,"pargp"] = "hfb_hydfac" - df.loc[:,"parubnd"] = df.parval1.max() * 10.0 - df.loc[:,"parlbnd"] = df.parval1.min() * 0.1 - return tpl_file,df
    - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/utils/helpers.html b/docs/_build/html/_modules/pyemu/utils/helpers.html deleted file mode 100644 index 4ae23d122..000000000 --- a/docs/_build/html/_modules/pyemu/utils/helpers.html +++ /dev/null @@ -1,3964 +0,0 @@ - - - - - - - - pyemu.utils.helpers — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.utils.helpers

    -""" module with high-level functions to help
    -perform complex tasks
    -"""
    -
    -from __future__ import print_function, division
    -import os
    -import multiprocessing as mp
    -import warnings
    -from datetime import datetime
    -import struct
    -import shutil
    -import copy
    -import numpy as np
    -import scipy.sparse
    -import pandas as pd
    -pd.options.display.max_colwidth = 100
    -from ..pyemu_warnings import PyemuWarning
    -try:
    -    import flopy
    -except:
    -    pass
    -
    -import pyemu
    -from pyemu.utils.os_utils import run, start_slaves
    -
    -
    [docs]def remove_readonly(func, path, excinfo): - """remove readonly dirs, apparently only a windows issue - add to all rmtree calls: shutil.rmtree(*,onerror=remove_readonly), wk - - Parameters - ---------- - func : object - func - path : str - path - excinfo : object - info - - """ - os.chmod(path, 128) #stat.S_IWRITE==128==normal - func(path)
    - -
    [docs]def run(cmd_str,cwd='.',verbose=False): - """ an OS agnostic function to execute command - - Parameters - ---------- - cmd_str : str - the str to execute with os.system() - - cwd : str - the directory to execute the command in - - verbose : bool - flag to echo to stdout complete cmd str - - Note - ---- - uses platform to detect OS and adds .exe or ./ as appropriate - - for Windows, if os.system returns non-zero, raises exception - - Example - ------- - ``>>>import pyemu`` - - ``>>>pyemu.helpers.run("pestpp pest.pst")`` - - """ - warnings.warn("run() has moved to pyemu.os_utils",PyemuWarning) - pyemu.os_utils.run(cmd_str=cmd_str,cwd=cwd,verbose=verbose)
    - - -
    [docs]def geostatistical_draws(pst, struct_dict,num_reals=100,sigma_range=4,verbose=True): - """ a helper function to construct a parameter ensenble from a full prior covariance matrix - implied by the geostatistical structure(s) in struct_dict. This function is much more efficient - for problems with lots of pars (>200K). - - Parameters - ---------- - pst : pyemu.Pst - a control file (or the name of control file) - struct_dict : dict - a python dict of GeoStruct (or structure file), and list of pp tpl files pairs - If the values in the dict are pd.DataFrames, then they must have an - 'x','y', and 'parnme' column. If the filename ends in '.csv', - then a pd.DataFrame is loaded, otherwise a pilot points file is loaded. - num_reals : int - number of realizations to draw. Default is 100 - sigma_range : float - a float representing the number of standard deviations implied by parameter bounds. - Default is 4.0, which implies 95% confidence parameter bounds. - verbose : bool - flag for stdout. - - Returns - ------- - - par_ens : pyemu.ParameterEnsemble - - - Example - ------- - ``>>>import pyemu`` - - ``>>>pst = pyemu.Pst("pest.pst")`` - - ``>>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}`` - - ``>>>pe = pyemu.helpers.geostatistical_draws(pst,struct_dict=sd,num_reals=100)`` - - ``>>>pe.to_csv("par_ensemble.csv")`` - - """ - - if isinstance(pst,str): - pst = pyemu.Pst(pst) - assert isinstance(pst,pyemu.Pst),"pst arg must be a Pst instance, not {0}".\ - format(type(pst)) - if verbose: print("building diagonal cov") - - full_cov = pyemu.Cov.from_parameter_data(pst, sigma_range=sigma_range) - full_cov_dict = {n: float(v) for n, v in zip(full_cov.col_names, full_cov.x)} - - par_org = pst.parameter_data.copy - par = pst.parameter_data - par_ens = [] - pars_in_cov = set() - for gs,items in struct_dict.items(): - if verbose: print("processing ",gs) - if isinstance(gs,str): - gss = pyemu.geostats.read_struct_file(gs) - if isinstance(gss,list): - warnings.warn("using first geostat structure in file {0}".\ - format(gs),PyemuWarning) - gs = gss[0] - else: - gs = gss - if not isinstance(items,list): - items = [items] - for item in items: - if isinstance(item,str): - assert os.path.exists(item),"file {0} not found".\ - format(item) - if item.lower().endswith(".tpl"): - df = pyemu.pp_utils.pp_tpl_to_dataframe(item) - elif item.lower.endswith(".csv"): - df = pd.read_csv(item) - else: - df = item - if df.columns.contains('pargp'): - if verbose: print("working on pargroups {0}".format(df.pargp.unique().tolist())) - for req in ['x','y','parnme']: - if req not in df.columns: - raise Exception("{0} is not in the columns".format(req)) - missing = df.loc[df.parnme.apply( - lambda x : x not in par.parnme),"parnme"] - if len(missing) > 0: - warnings.warn("the following parameters are not " + \ - "in the control file: {0}".\ - format(','.join(missing)),PyemuWarning) - df = df.loc[df.parnme.apply(lambda x: x not in missing)] - if "zone" not in df.columns: - df.loc[:,"zone"] = 1 - zones = df.zone.unique() - for zone in zones: - df_zone = df.loc[df.zone==zone,:].copy() - df_zone.sort_values(by="parnme",inplace=True) - if verbose: print("build cov matrix") - cov = gs.covariance_matrix(df_zone.x,df_zone.y,df_zone.parnme) - if verbose: print("done") - - if verbose: print("getting diag var cov",df_zone.shape[0]) - #tpl_var = np.diag(full_cov.get(list(df_zone.parnme)).x).max() - tpl_var = max([full_cov_dict[pn] for pn in df_zone.parnme]) - - if verbose: print("scaling full cov by diag var cov") - cov *= tpl_var - # no fixed values here - pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst=pst,cov=cov,num_reals=num_reals, - group_chunks=False,fill_fixed=False) - #df = pe.iloc[:,:] - par_ens.append(pd.DataFrame(pe)) - pars_in_cov.update(set(pe.columns)) - - if verbose: print("adding remaining parameters to diagonal") - fset = set(full_cov.row_names) - diff = list(fset.difference(pars_in_cov)) - if (len(diff) > 0): - name_dict = {name:i for i,name in enumerate(full_cov.row_names)} - vec = np.atleast_2d(np.array([full_cov.x[name_dict[d]] for d in diff])) - cov = pyemu.Cov(x=vec,names=diff,isdiagonal=True) - #cov = full_cov.get(diff,diff) - # here we fill in the fixed values - pe = pyemu.ParameterEnsemble.from_gaussian_draw(pst,cov,num_reals=num_reals, - fill_fixed=True) - par_ens.append(pd.DataFrame(pe)) - par_ens = pd.concat(par_ens,axis=1) - par_ens = pyemu.ParameterEnsemble.from_dataframe(df=par_ens,pst=pst) - return par_ens
    - - -
    [docs]def pilotpoint_prior_builder(pst, struct_dict,sigma_range=4): - warnings.warn("'pilotpoint_prior_builder' has been renamed to "+\ - "'geostatistical_prior_builder'",PyemuWarning) - return geostatistical_prior_builder(pst=pst,struct_dict=struct_dict, - sigma_range=sigma_range)
    - -
    [docs]def sparse_geostatistical_prior_builder(pst, struct_dict,sigma_range=4,verbose=False): - """ a helper function to construct a full prior covariance matrix using - a mixture of geostastical structures and parameter bounds information. - The covariance of parameters associated with geostatistical structures is defined - as a mixture of GeoStruct and bounds. That is, the GeoStruct is used to construct a - pyemu.Cov, then the entire pyemu.Cov is scaled by the uncertainty implied by the bounds and - sigma_range. Sounds complicated... - - Parameters - ---------- - pst : pyemu.Pst - a control file (or the name of control file) - struct_dict : dict - a python dict of GeoStruct (or structure file), and list of pp tpl files pairs - If the values in the dict are pd.DataFrames, then they must have an - 'x','y', and 'parnme' column. If the filename ends in '.csv', - then a pd.DataFrame is loaded, otherwise a pilot points file is loaded. - sigma_range : float - a float representing the number of standard deviations implied by parameter bounds. - Default is 4.0, which implies 95% confidence parameter bounds. - verbose : bool - flag for stdout. - - Returns - ------- - Cov : pyemu.SparseMatrix - a sparse covariance matrix that includes all adjustable parameters in the control - file. - - Example - ------- - ``>>>import pyemu`` - - ``>>>pst = pyemu.Pst("pest.pst")`` - - ``>>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}`` - - ``>>>cov = pyemu.helpers.sparse_geostatistical_prior_builder(pst,struct_dict=sd)`` - - ``>>>cov.to_coo("prior.jcb")`` - - """ - - if isinstance(pst,str): - pst = pyemu.Pst(pst) - assert isinstance(pst,pyemu.Pst),"pst arg must be a Pst instance, not {0}".\ - format(type(pst)) - if verbose: print("building diagonal cov") - full_cov = pyemu.Cov.from_parameter_data(pst,sigma_range=sigma_range) - - full_cov_dict = {n:float(v) for n,v in zip(full_cov.col_names,full_cov.x)} - - full_cov = None - par = pst.parameter_data - for gs,items in struct_dict.items(): - if verbose: print("processing ",gs) - if isinstance(gs,str): - gss = pyemu.geostats.read_struct_file(gs) - if isinstance(gss,list): - warnings.warn("using first geostat structure in file {0}".\ - format(gs),PyemuWarning) - gs = gss[0] - else: - gs = gss - if not isinstance(items,list): - items = [items] - for item in items: - if isinstance(item,str): - assert os.path.exists(item),"file {0} not found".\ - format(item) - if item.lower().endswith(".tpl"): - df = pyemu.pp_utils.pp_tpl_to_dataframe(item) - elif item.lower.endswith(".csv"): - df = pd.read_csv(item) - else: - df = item - for req in ['x','y','parnme']: - if req not in df.columns: - raise Exception("{0} is not in the columns".format(req)) - missing = df.loc[df.parnme.apply( - lambda x : x not in par.parnme),"parnme"] - if len(missing) > 0: - warnings.warn("the following parameters are not " + \ - "in the control file: {0}".\ - format(','.join(missing)),PyemuWarning) - df = df.loc[df.parnme.apply(lambda x: x not in missing)] - if "zone" not in df.columns: - df.loc[:,"zone"] = 1 - zones = df.zone.unique() - for zone in zones: - df_zone = df.loc[df.zone==zone,:].copy() - df_zone.sort_values(by="parnme",inplace=True) - if verbose: print("build cov matrix") - cov = gs.sparse_covariance_matrix(df_zone.x,df_zone.y,df_zone.parnme) - if verbose: print("done") - - if verbose: print("getting diag var cov",df_zone.shape[0]) - #tpl_var = np.diag(full_cov.get(list(df_zone.parnme)).x).max() - tpl_var = max([full_cov_dict[pn] for pn in df_zone.parnme]) - - if verbose: print("scaling full cov by diag var cov") - cov.x.data *= tpl_var - - if full_cov is None: - full_cov = cov - else: - if verbose: print("extending SparseMatix") - full_cov.block_extend_ip(cov) - - - if verbose: print("adding remaining parameters to diagonal") - fset = set(full_cov.row_names) - pset = set(pst.par_names) - diff = list(pset.difference(fset)) - diff.sort() - vals = np.array([full_cov_dict[d] for d in diff]) - i = np.arange(vals.shape[0]) - coo = scipy.sparse.coo_matrix((vals,(i,i)),shape=(vals.shape[0],vals.shape[0])) - cov = pyemu.SparseMatrix(x=coo,row_names=diff,col_names=diff) - full_cov.block_extend_ip(cov) - - return full_cov
    - -
    [docs]def geostatistical_prior_builder(pst, struct_dict,sigma_range=4, - par_knowledge_dict=None,verbose=False): - """ a helper function to construct a full prior covariance matrix using - a mixture of geostastical structures and parameter bounds information. - The covariance of parameters associated with geostatistical structures is defined - as a mixture of GeoStruct and bounds. That is, the GeoStruct is used to construct a - pyemu.Cov, then the entire pyemu.Cov is scaled by the uncertainty implied by the bounds and - sigma_range. Sounds complicated... - - Parameters - ---------- - pst : pyemu.Pst - a control file (or the name of control file) - struct_dict : dict - a python dict of GeoStruct (or structure file), and list of pp tpl files pairs - If the values in the dict are pd.DataFrames, then they must have an - 'x','y', and 'parnme' column. If the filename ends in '.csv', - then a pd.DataFrame is loaded, otherwise a pilot points file is loaded. - sigma_range : float - a float representing the number of standard deviations implied by parameter bounds. - Default is 4.0, which implies 95% confidence parameter bounds. - par_knowledge_dict : dict - used to condition on existing knowledge about parameters. This functionality is - currently in dev - don't use it. - verbose : bool - stdout flag - Returns - ------- - Cov : pyemu.Cov - a covariance matrix that includes all adjustable parameters in the control - file. - - Example - ------- - ``>>>import pyemu`` - - ``>>>pst = pyemu.Pst("pest.pst")`` - - ``>>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}`` - - ``>>>cov = pyemu.helpers.geostatistical_prior_builder(pst,struct_dict=sd)`` - - ``>>>cov.to_ascii("prior.cov")`` - - """ - - if isinstance(pst,str): - pst = pyemu.Pst(pst) - assert isinstance(pst,pyemu.Pst),"pst arg must be a Pst instance, not {0}".\ - format(type(pst)) - if verbose: print("building diagonal cov") - full_cov = pyemu.Cov.from_parameter_data(pst,sigma_range=sigma_range) - - full_cov_dict = {n:float(v) for n,v in zip(full_cov.col_names,full_cov.x)} - #full_cov = None - par = pst.parameter_data - for gs,items in struct_dict.items(): - if verbose: print("processing ",gs) - if isinstance(gs,str): - gss = pyemu.geostats.read_struct_file(gs) - if isinstance(gss,list): - warnings.warn("using first geostat structure in file {0}".\ - format(gs),PyemuWarning) - gs = gss[0] - else: - gs = gss - if not isinstance(items,list): - items = [items] - for item in items: - if isinstance(item,str): - assert os.path.exists(item),"file {0} not found".\ - format(item) - if item.lower().endswith(".tpl"): - df = pyemu.pp_utils.pp_tpl_to_dataframe(item) - elif item.lower.endswith(".csv"): - df = pd.read_csv(item) - else: - df = item - for req in ['x','y','parnme']: - if req not in df.columns: - raise Exception("{0} is not in the columns".format(req)) - missing = df.loc[df.parnme.apply( - lambda x : x not in par.parnme),"parnme"] - if len(missing) > 0: - warnings.warn("the following parameters are not " + \ - "in the control file: {0}".\ - format(','.join(missing)),PyemuWarning) - df = df.loc[df.parnme.apply(lambda x: x not in missing)] - if "zone" not in df.columns: - df.loc[:,"zone"] = 1 - zones = df.zone.unique() - for zone in zones: - df_zone = df.loc[df.zone==zone,:].copy() - df_zone.sort_values(by="parnme",inplace=True) - if verbose: print("build cov matrix") - cov = gs.covariance_matrix(df_zone.x,df_zone.y,df_zone.parnme) - if verbose: print("done") - # find the variance in the diagonal cov - if verbose: print("getting diag var cov",df_zone.shape[0]) - #tpl_var = np.diag(full_cov.get(list(df_zone.parnme)).x).max() - tpl_var = max([full_cov_dict[pn] for pn in df_zone.parnme]) - #if np.std(tpl_var) > 1.0e-6: - # warnings.warn("pars have different ranges" +\ - # " , using max range as variance for all pars") - #tpl_var = tpl_var.max() - if verbose: print("scaling full cov by diag var cov") - cov *= tpl_var - if verbose: print("test for inversion") - try: - ci = cov.inv - except: - df_zone.to_csv("prior_builder_crash.csv") - raise Exception("error inverting cov {0}". - format(cov.row_names[:3])) - - if verbose: print('replace in full cov') - full_cov.replace(cov) - # d = np.diag(full_cov.x) - # idx = np.argwhere(d==0.0) - # for i in idx: - # print(full_cov.names[i]) - - if par_knowledge_dict is not None: - full_cov = condition_on_par_knowledge(full_cov, - par_knowledge_dict=par_knowledge_dict) - return full_cov
    - - - -
    [docs]def condition_on_par_knowledge(cov,par_knowledge_dict): - """ experimental function to include conditional prior information - for one or more parameters in a full covariance matrix - """ - - missing = [] - for parnme in par_knowledge_dict.keys(): - if parnme not in cov.row_names: - missing.append(parnme) - if len(missing): - raise Exception("par knowledge dict parameters not found: {0}".\ - format(','.join(missing))) - # build the selection matrix and sigma epsilon - #sel = cov.zero2d - #sel = pyemu.Matrix(x=np.zeros((cov.shape[0],1)),row_names=cov.row_names,col_names=['sel']) - sel = cov.zero2d - sigma_ep = cov.zero2d - for parnme,var in par_knowledge_dict.items(): - idx = cov.row_names.index(parnme) - #sel.x[idx,:] = 1.0 - sel.x[idx,idx] = 1.0 - sigma_ep.x[idx,idx] = var - #print(sigma_ep.x) - #q = sigma_ep.inv - #cov_inv = cov.inv - print(sel) - term2 = sel * cov * sel.T - #term2 += sigma_ep - #term2 = cov - print(term2) - term2 = term2.inv - term2 *= sel - term2 *= cov - - new_cov = cov - term2 - - return new_cov
    - - - -
    [docs]def kl_setup(num_eig,sr,struct,prefixes, - factors_file="kl_factors.dat",islog=True, basis_file=None, - tpl_dir="."): - """setup a karhuenen-Loeve based parameterization for a given - geostatistical structure. - - Parameters - ---------- - num_eig : int - number of basis vectors to retain in the reduced basis - sr : flopy.reference.SpatialReference - - struct : str or pyemu.geostats.Geostruct - geostatistical structure (or file containing one) - - array_dict : dict - a dict of arrays to setup as KL-based parameters. The key becomes the - parameter name prefix. The total number of parameters is - len(array_dict) * num_eig - - basis_file : str - the name of the PEST-format binary file where the reduced basis will be saved - - tpl_file : str - the name of the template file to make. The template - file is a csv file with the parameter names, the - original factor values,and the template entries. - The original values can be used to set the parval1 - entries in the control file - - Returns - ------- - back_array_dict : dict - a dictionary of back transformed arrays. This is useful to see - how much "smoothing" is taking place compared to the original - arrays. - - Note - ---- - requires flopy - - Example - ------- - ``>>>import flopy`` - - ``>>>import pyemu`` - - ``>>>m = flopy.modflow.Modflow.load("mymodel.nam")`` - - ``>>>a_dict = {"hk":m.lpf.hk[0].array}`` - - ``>>>ba_dict = pyemu.helpers.kl_setup(10,m.sr,"struct.dat",a_dict)`` - - """ - - try: - import flopy - except Exception as e: - raise Exception("error import flopy: {0}".format(str(e))) - assert isinstance(sr,flopy.utils.SpatialReference) - # for name,array in array_dict.items(): - # assert isinstance(array,np.ndarray) - # assert array.shape[0] == sr.nrow - # assert array.shape[1] == sr.ncol - # assert len(name) + len(str(num_eig)) <= 12,"name too long:{0}".\ - # format(name) - - if isinstance(struct,str): - assert os.path.exists(struct) - gs = pyemu.utils.read_struct_file(struct) - else: - gs = struct - names = [] - for i in range(sr.nrow): - names.extend(["i{0:04d}j{1:04d}".format(i,j) for j in range(sr.ncol)]) - - cov = gs.covariance_matrix(sr.xcentergrid.flatten(), - sr.ycentergrid.flatten(), - names=names) - - eig_names = ["eig_{0:04d}".format(i) for i in range(cov.shape[0])] - trunc_basis = cov.u - trunc_basis.col_names = eig_names - #trunc_basis.col_names = [""] - if basis_file is not None: - trunc_basis.to_binary(basis_file) - trunc_basis = trunc_basis[:,:num_eig] - eig_names = eig_names[:num_eig] - - pp_df = pd.DataFrame({"name":eig_names},index=eig_names) - pp_df.loc[:,"x"] = -1.0 * sr.ncol - pp_df.loc[:,"y"] = -1.0 * sr.nrow - pp_df.loc[:,"zone"] = -999 - pp_df.loc[:,"parval1"] = 1.0 - pyemu.pp_utils.write_pp_file(os.path.join("temp.dat"),pp_df) - - - eigen_basis_to_factor_file(sr.nrow,sr.ncol,trunc_basis,factors_file=factors_file,islog=islog) - dfs = [] - for prefix in prefixes: - tpl_file = os.path.join(tpl_dir,"{0}.dat_kl.tpl".format(prefix)) - df = pyemu.pp_utils.pilot_points_to_tpl("temp.dat",tpl_file,prefix) - shutil.copy2("temp.dat",tpl_file.replace(".tpl","")) - df.loc[:,"tpl_file"] = tpl_file - df.loc[:,"in_file"] = tpl_file.replace(".tpl","") - df.loc[:,"prefix"] = prefix - df.loc[:,"pargp"] = "kl_{0}".format(prefix) - dfs.append(df) - #arr = pyemu.geostats.fac2real(df,factors_file=factors_file,out_file=None) - df = pd.concat(dfs) - df.loc[:,"parubnd"] = 10.0 - df.loc[:,"parlbnd"] = 0.1 - return pd.concat(dfs)
    - - # back_array_dict = {} - # f = open(tpl_file,'w') - # f.write("ptf ~\n") - # f.write("name,org_val,new_val\n") - # for name,array in array_dict.items(): - # mname = name+"mean" - # f.write("{0},{1:20.8E},~ {2} ~\n".format(mname,0.0,mname)) - # #array -= array.mean() - # array_flat = pyemu.Matrix(x=np.atleast_2d(array.flatten()).transpose() - # ,col_names=["flat"],row_names=names, - # isdiagonal=False) - # factors = trunc_basis * array_flat - # enames = ["{0}{1:04d}".format(name,i) for i in range(num_eig)] - # for n,val in zip(enames,factors.x): - # f.write("{0},{1:20.8E},~ {0} ~\n".format(n,val[0])) - # back_array_dict[name] = (factors.T * trunc_basis).x.reshape(array.shape) - # print(array_back) - # print(factors.shape) - # - # return back_array_dict - - -
    [docs]def eigen_basis_to_factor_file(nrow,ncol,basis,factors_file,islog=True): - assert nrow * ncol == basis.shape[0] - with open(factors_file,'w') as f: - f.write("junk.dat\n") - f.write("junk.zone.dat\n") - f.write("{0} {1}\n".format(ncol,nrow)) - f.write("{0}\n".format(basis.shape[1])) - [f.write(name+"\n") for name in basis.col_names] - t = 0 - if islog: - t = 1 - for i in range(nrow * ncol): - f.write("{0} {1} {2} {3:8.5e}".format(i+1,t,basis.shape[1],0.0)) - [f.write(" {0} {1:12.8g} ".format(i + 1, w)) for i, w in enumerate(basis.x[i,:])] - f.write("\n")
    - - -
    [docs]def kl_apply(par_file, basis_file,par_to_file_dict,arr_shape): - """ Applies a KL parameterization transform from basis factors to model - input arrays. Companion function to kl_setup() - - Parameters - ---------- - par_file : str - the csv file to get factor values from. Must contain - the following columns: name, new_val, org_val - basis_file : str - the binary file that contains the reduced basis - - par_to_file_dict : dict - a mapping from KL parameter prefixes to array file names. - - Note - ---- - This is the companion function to kl_setup. - - This function should be called during the forward run - - Example - ------- - ``>>>import pyemu`` - - ``>>>pyemu.helpers.kl_apply("kl.dat","basis.dat",{"hk":"hk_layer_1.dat",(100,100))`` - - - """ - df = pd.read_csv(par_file) - assert "name" in df.columns - assert "org_val" in df.columns - assert "new_val" in df.columns - - df.loc[:,"prefix"] = df.name.apply(lambda x: x[:-4]) - for prefix in df.prefix.unique(): - assert prefix in par_to_file_dict.keys(),"missing prefix:{0}".\ - format(prefix) - basis = pyemu.Matrix.from_binary(basis_file) - assert basis.shape[1] == arr_shape[0] * arr_shape[1] - arr_min = 1.0e-10 # a temp hack - - #means = df.loc[df.name.apply(lambda x: x.endswith("mean")),:] - #print(means) - df = df.loc[df.name.apply(lambda x: not x.endswith("mean")),:] - for prefix,filename in par_to_file_dict.items(): - factors = pyemu.Matrix.from_dataframe(df.loc[df.prefix==prefix,["new_val"]]) - factors.autoalign = False - basis_prefix = basis[:factors.shape[0],:] - arr = (factors.T * basis_prefix).x.reshape(arr_shape) - #arr += means.loc[means.prefix==prefix,"new_val"].values - arr[arr<arr_min] = arr_min - np.savetxt(filename,arr,fmt="%20.8E")
    - - -
    [docs]def zero_order_tikhonov(pst, parbounds=True,par_groups=None, - reset=True): - """setup preferred-value regularization - - Parameters - ---------- - pst : pyemu.Pst - the control file instance - parbounds : bool - flag to weight the prior information equations according - to parameter bound width - approx the KL transform. Default - is True - par_groups : list - parameter groups to build PI equations for. If None, all - adjustable parameters are used. Default is None - - reset : bool - flag to reset the prior_information attribute of the pst - instance. Default is True - - Example - ------- - ``>>>import pyemu`` - - ``>>>pst = pyemu.Pst("pest.pst")`` - - ``>>>pyemu.helpers.zero_order_tikhonov(pst)`` - - """ - - if par_groups is None: - par_groups = pst.par_groups - - pilbl, obgnme, weight, equation = [], [], [], [] - for idx, row in pst.parameter_data.iterrows(): - pt = row["partrans"].lower() - try: - pt = pt.decode() - except: - pass - if pt not in ["tied", "fixed"] and\ - row["pargp"] in par_groups: - pilbl.append(row["parnme"]) - weight.append(1.0) - ogp_name = "regul"+row["pargp"] - obgnme.append(ogp_name[:12]) - parnme = row["parnme"] - parval1 = row["parval1"] - if pt == "log": - parnme = "log(" + parnme + ")" - parval1 = np.log10(parval1) - eq = "1.0 * " + parnme + " ={0:15.6E}".format(parval1) - equation.append(eq) - - if reset: - pst.prior_information = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "obgnme": obgnme, - "weight": weight}) - else: - pi = pd.DataFrame({"pilbl": pilbl, - "equation": equation, - "obgnme": obgnme, - "weight": weight}) - pst.prior_information = pst.prior_information.append(pi) - if parbounds: - regweight_from_parbound(pst) - if pst.control_data.pestmode == "estimation": - pst.control_data.pestmode = "regularization"
    - - -
    [docs]def regweight_from_parbound(pst): - """sets regularization weights from parameter bounds - which approximates the KL expansion. Called by - zero_order_tikhonov(). - - Parameters - ---------- - pst : pyemu.Pst - a control file instance - - """ - - pst.parameter_data.index = pst.parameter_data.parnme - pst.prior_information.index = pst.prior_information.pilbl - for idx, parnme in enumerate(pst.prior_information.pilbl): - if parnme in pst.parameter_data.index: - row = pst.parameter_data.loc[parnme, :] - lbnd,ubnd = row["parlbnd"], row["parubnd"] - if row["partrans"].lower() == "log": - weight = 1.0 / (np.log10(ubnd) - np.log10(lbnd)) - else: - weight = 1.0 / (ubnd - lbnd) - pst.prior_information.loc[parnme, "weight"] = weight - else: - print("prior information name does not correspond" +\ - " to a parameter: " + str(parnme))
    - - -
    [docs]def first_order_pearson_tikhonov(pst,cov,reset=True,abs_drop_tol=1.0e-3): - """setup preferred-difference regularization from a covariance matrix. - The weights on the prior information equations are the Pearson - correlation coefficients implied by covariance matrix. - - Parameters - ---------- - pst : pyemu.Pst - pst instance - cov : pyemu.Cov - covariance matrix instance - reset : bool - drop all other pi equations. If False, append to - existing pi equations - abs_drop_tol : float - tolerance to control how many pi equations are written. - If the Pearson C is less than abs_drop_tol, the prior information - equation will not be included in the control file - - Example - ------- - ``>>>import pyemu`` - - ``>>>pst = pyemu.Pst("pest.pst")`` - - ``>>>cov = pyemu.Cov.from_ascii("prior.cov")`` - - ``>>>pyemu.helpers.first_order_pearson_tikhonov(pst,cov,abs_drop_tol=0.25)`` - - """ - assert isinstance(cov,pyemu.Cov) - cc_mat = cov.to_pearson() - #print(pst.parameter_data.dtypes) - try: - ptrans = pst.parameter_data.partrans.apply(lambda x:x.decode()).to_dict() - except: - ptrans = pst.parameter_data.partrans.to_dict() - pi_num = pst.prior_information.shape[0] + 1 - pilbl, obgnme, weight, equation = [], [], [], [] - for i,iname in enumerate(cc_mat.row_names): - if iname not in pst.adj_par_names: - continue - for j,jname in enumerate(cc_mat.row_names[i+1:]): - if jname not in pst.adj_par_names: - continue - #print(i,iname,i+j+1,jname) - cc = cc_mat.x[i,j+i+1] - if cc < abs_drop_tol: - continue - pilbl.append("pcc_{0}".format(pi_num)) - iiname = str(iname) - if str(ptrans[iname]) == "log": - iiname = "log("+iname+")" - jjname = str(jname) - if str(ptrans[jname]) == "log": - jjname = "log("+jname+")" - equation.append("1.0 * {0} - 1.0 * {1} = 0.0".\ - format(iiname,jjname)) - weight.append(cc) - obgnme.append("regul_cc") - pi_num += 1 - df = pd.DataFrame({"pilbl": pilbl,"equation": equation, - "obgnme": obgnme,"weight": weight}) - df.index = df.pilbl - if reset: - pst.prior_information = df - else: - pst.prior_information = pst.prior_information.append(df) - - if pst.control_data.pestmode == "estimation": - pst.control_data.pestmode = "regularization"
    - -
    [docs]def simple_tpl_from_pars(parnames, tplfilename='model.input.tpl'): - """ - Make a template file just assuming a list of parameter names the values of which should be - listed in order in a model input file - Args: - parnames: list of names from which to make a template file - tplfilename: filename for TPL file (default: model.input.tpl) - - Returns: - writes a file <tplfilename> with each parameter name on a line - - """ - with open(tplfilename, 'w') as ofp: - ofp.write('ptf ~\n') - [ofp.write('~{0:^12}~\n'.format(cname)) for cname in parnames]
    - - -
    [docs]def simple_ins_from_obs(obsnames, insfilename='model.output.ins'): - """ - writes an instruction file that assumes wanting to read the values names in obsnames in order - one per line from a model output file - Args: - obsnames: list of obsnames to read in - insfilename: filename for INS file (default: model.output.ins) - - Returns: - writes a file <insfilename> with each observation read off a line - - """ - with open(insfilename, 'w') as ofp: - ofp.write('pif ~\n') - [ofp.write('!{0}!\n'.format(cob)) for cob in obsnames]
    - -
    [docs]def pst_from_parnames_obsnames(parnames, obsnames, - tplfilename='model.input.tpl', insfilename='model.output.ins'): - """ - Creates a Pst object from a list of parameter names and a list of observation names. - Default values are provided for the TPL and INS - Args: - parnames: list of names from which to make a template file - obsnames: list of obsnames to read in - tplfilename: filename for TPL file (default: model.input.tpl) - insfilename: filename for INS file (default: model.output.ins) - - Returns: - Pst object - - """ - simple_tpl_from_pars(parnames, tplfilename) - simple_ins_from_obs(obsnames, insfilename) - - modelinputfilename = tplfilename.replace('.tpl','') - modeloutputfilename = insfilename.replace('.ins','') - - return pyemu.Pst.from_io_files(tplfilename, modelinputfilename, insfilename, modeloutputfilename)
    - - - -
    [docs]def start_slaves(slave_dir,exe_rel_path,pst_rel_path,num_slaves=None,slave_root="..", - port=4004,rel_path=None,local=True,cleanup=True,master_dir=None, - verbose=False,silent_master=False): - """ start a group of pest(++) slaves on the local machine - - Parameters - ---------- - slave_dir : str - the path to a complete set of input files - exe_rel_path : str - the relative path to the pest(++) executable from within the slave_dir - pst_rel_path : str - the relative path to the pst file from within the slave_dir - num_slaves : int - number of slaves to start. defaults to number of cores - slave_root : str - the root to make the new slave directories in - rel_path: str - the relative path to where pest(++) should be run from within the - slave_dir, defaults to the uppermost level of the slave dir - local: bool - flag for using "localhost" instead of hostname on slave command line - cleanup: bool - flag to remove slave directories once processes exit - master_dir: str - name of directory for master instance. If master_dir - exists, then it will be removed. If master_dir is None, - no master instance will be started - verbose : bool - flag to echo useful information to stdout - - Note - ---- - if all slaves (and optionally master) exit gracefully, then the slave - dirs will be removed unless cleanup is false - - Example - ------- - ``>>>import pyemu`` - - start 10 slaves using the directory "template" as the base case and - also start a master instance in a directory "master". - - ``>>>pyemu.helpers.start_slaves("template","pestpp","pest.pst",10,master_dir="master")`` - - """ - - warnings.warn("start_slaves has moved to pyemu.os_utils",PyemuWarning) - pyemu.os_utils.start_slaves(slave_dir=slave_dir,exe_rel_path=exe_rel_path,pst_rel_path=pst_rel_path - ,num_slaves=num_slaves,slave_root=slave_root,port=port,rel_path=rel_path, - local=local,cleanup=cleanup,master_dir=master_dir,verbose=verbose, - silent_master=silent_master)
    - - -
    [docs]def read_pestpp_runstorage(filename,irun=0): - """read pars and obs from a specific run in a pest++ serialized run storage file into - pandas.DataFrame(s) - - Parameters - ---------- - filename : str - the name of the run storage file - irun : int - the run id to process. Default is 0 - - Returns - ------- - par_df : pandas.DataFrame - parameter information - obs_df : pandas.DataFrame - observation information - - """ - - header_dtype = np.dtype([("n_runs",np.int64),("run_size",np.int64),("p_name_size",np.int64), - ("o_name_size",np.int64)]) - with open(filename,'rb') as f: - header = np.fromfile(f,dtype=header_dtype,count=1) - p_name_size,o_name_size = header["p_name_size"][0],header["o_name_size"][0] - par_names = struct.unpack('{0}s'.format(p_name_size), - f.read(p_name_size))[0].strip().lower().decode().split('\0')[:-1] - obs_names = struct.unpack('{0}s'.format(o_name_size), - f.read(o_name_size))[0].strip().lower().decode().split('\0')[:-1] - n_runs,run_size = header["n_runs"],header["run_size"][0] - assert irun <= n_runs - run_start = f.tell() - f.seek(run_start + (irun * run_size)) - r_status = np.fromfile(f,dtype=np.int8,count=1) - info_txt = struct.unpack("41s",f.read(41))[0].strip().lower().decode() - par_vals = np.fromfile(f,dtype=np.float64,count=len(par_names)+1)[1:] - obs_vals = np.fromfile(f,dtype=np.float64,count=len(obs_names)+1)[:-1] - par_df = pd.DataFrame({"parnme":par_names,"parval1":par_vals}) - - par_df.index = par_df.pop("parnme") - obs_df = pd.DataFrame({"obsnme":obs_names,"obsval":obs_vals}) - obs_df.index = obs_df.pop("obsnme") - return par_df,obs_df
    - - -
    [docs]def jco_from_pestpp_runstorage(rnj_filename,pst_filename): - """ read pars and obs from a pest++ serialized run storage file (e.g., .rnj) and return - pyemu.Jco. This can then be passed to Jco.to_binary or Jco.to_coo, etc., to write jco file - in a subsequent step to avoid memory resource issues associated with very large problems. - - Parameters - ---------- - rnj_filename : str - the name of the run storage file - pst_filename : str - the name of the pst file - - Returns - ------- - jco_cols : pyemu.Jco - - - Notes - ------- - TODO: - 0. Check rnj file contains transformed par vals (i.e., in model input space) - 1. Currently only returns pyemu.Jco; doesn't write jco file due to memory issues - associated with very large problems - 3. Compare rnj and jco from Freyberg problem in autotests - - """ - - header_dtype = np.dtype([("n_runs",np.int64),("run_size",np.int64),("p_name_size",np.int64), - ("o_name_size",np.int64)]) - - pst = pyemu.Pst(pst_filename) - par = pst.parameter_data - log_pars = set(par.loc[par.partrans=="log","parnme"].values) - with open(rnj_filename,'rb') as f: - header = np.fromfile(f,dtype=header_dtype,count=1) - - try: - base_par,base_obs = read_pestpp_runstorage(rnj_filename,irun=0) - except: - raise Exception("couldn't get base run...") - par = par.loc[base_par.index,:] - li = base_par.index.map(lambda x: par.loc[x,"partrans"]=="log") - base_par.loc[li] = base_par.loc[li].apply(np.log10) - jco_cols = {} - for irun in range(1,int(header["n_runs"])): - par_df,obs_df = read_pestpp_runstorage(rnj_filename,irun=irun) - par_df.loc[li] = par_df.loc[li].apply(np.log10) - obs_diff = base_obs - obs_df - par_diff = base_par - par_df - # check only one non-zero element per col(par) - if len(par_diff[par_diff.parval1 != 0]) > 1: - raise Exception("more than one par diff - looks like the file wasn't created during jco filling...") - parnme = par_diff[par_diff.parval1 != 0].index[0] - parval = par_diff.parval1.loc[parnme] - - # derivatives - jco_col = obs_diff / parval - # some tracking, checks - print("processing par {0}: {1}...".format(irun, parnme)) - print("%nzsens: {0}%...".format((jco_col[abs(jco_col.obsval)>1e-8].shape[0] / jco_col.shape[0])*100.)) - - jco_cols[parnme] = jco_col.obsval - - jco_cols = pd.DataFrame.from_records(data=jco_cols, index=list(obs_diff.index.values)) - - jco_cols = pyemu.Jco.from_dataframe(jco_cols) - - # write # memory considerations important here for very large matrices - break into chunks... - #jco_fnam = "{0}".format(filename[:-4]+".jco") - #jco_cols.to_binary(filename=jco_fnam, droptol=None, chunk=None) - - return jco_cols
    - - -
    [docs]def parse_dir_for_io_files(d): - """ a helper function to find template/input file pairs and - instruction file/output file pairs. the return values from this - function can be passed straight to pyemu.Pst.from_io_files() classmethod - constructor. Assumes the template file names are <input_file>.tpl and - instruction file names are <output_file>.ins. - - Parameters - ---------- - d : str - directory to search for interface files - - Returns - ------- - tpl_files : list - list of template files in d - in_files : list - list of input files in d - ins_files : list - list of instruction files in d - out_files : list - list of output files in d - - """ - - files = os.listdir(d) - tpl_files = [f for f in files if f.endswith(".tpl")] - in_files = [f.replace(".tpl","") for f in tpl_files] - ins_files = [f for f in files if f.endswith(".ins")] - out_files = [f.replace(".ins","") for f in ins_files] - return tpl_files,in_files,ins_files,out_files
    - - -
    [docs]def pst_from_io_files(tpl_files,in_files,ins_files,out_files,pst_filename=None): - """generate a Pst instance from the model io files. If 'inschek' - is available (either in the current directory or registered - with the system variables) and the model output files are available - , then the observation values in the control file will be set to the - values of the model-simulated equivalents to observations. This can be - useful for testing - - Parameters - ---------- - tpl_files : list - list of pest template files - in_files : list - list of corresponding model input files - ins_files : list - list of pest instruction files - out_files: list - list of corresponding model output files - pst_filename : str - name of file to write the control file to. If None, - control file is not written. Default is None - - Returns - ------- - pst : pyemu.Pst - - - Example - ------- - ``>>>import pyemu`` - - this will construct a new Pst instance from template and instruction files - found in the current directory, assuming that the naming convention follows - that listed in parse_dir_for_io_files() - - ``>>>pst = pyemu.helpers.pst_from_io_files(*pyemu.helpers.parse_dir_for_io_files('.'))`` - - """ - par_names = set() - if not isinstance(tpl_files,list): - tpl_files = [tpl_files] - if not isinstance(in_files,list): - in_files = [in_files] - assert len(in_files) == len(tpl_files),"len(in_files) != len(tpl_files)" - - for tpl_file in tpl_files: - assert os.path.exists(tpl_file),"template file not found: "+str(tpl_file) - #new_names = [name for name in pyemu.pst_utils.parse_tpl_file(tpl_file) if name not in par_names] - #par_names.extend(new_names) - new_names = pyemu.pst_utils.parse_tpl_file(tpl_file) - par_names.update(new_names) - - if not isinstance(ins_files,list): - ins_files = [ins_files] - if not isinstance(out_files,list): - out_files = [out_files] - assert len(ins_files) == len(out_files),"len(out_files) != len(out_files)" - - - obs_names = [] - for ins_file in ins_files: - assert os.path.exists(ins_file),"instruction file not found: "+str(ins_file) - obs_names.extend(pyemu.pst_utils.parse_ins_file(ins_file)) - - new_pst = pyemu.pst_utils.generic_pst(list(par_names),list(obs_names)) - - new_pst.template_files = tpl_files - new_pst.input_files = in_files - new_pst.instruction_files = ins_files - new_pst.output_files = out_files - - #try to run inschek to find the observtion values - pyemu.pst_utils.try_run_inschek(new_pst) - - if pst_filename: - new_pst.write(pst_filename,update_regul=True) - return new_pst
    - - -wildass_guess_par_bounds_dict = {"hk":[0.01,100.0],"vka":[0.1,10.0], - "sy":[0.25,1.75],"ss":[0.1,10.0], - "cond":[0.01,100.0],"flux":[0.25,1.75], - "rech":[0.9,1.1],"stage":[0.9,1.1], - } - -
    [docs]class PstFromFlopyModel(object): - """ a monster helper class to setup multiplier parameters for an - existing MODFLOW model. does all kinds of coolness like building a - meaningful prior, assigning somewhat meaningful parameter groups and - bounds, writes a forward_run.py script with all the calls need to - implement multiplier parameters, run MODFLOW and post-process. - - Parameters - ---------- - model : flopy.mbase - a loaded flopy model instance. If model is an str, it is treated as a - MODFLOW nam file (requires org_model_ws) - new_model_ws : str - a directory where the new version of MODFLOW input files and PEST(++) - files will be written - org_model_ws : str - directory to existing MODFLOW model files. Required if model argument - is an str. Default is None - pp_props : list - pilot point multiplier parameters for grid-based properties. - A nested list of grid-scale model properties to parameterize using - name, iterable pairs. For 3D properties, the iterable is zero-based - layer indices. For example, ["lpf.hk",[0,1,2,]] would setup pilot point multiplier - parameters for layer property file horizontal hydraulic conductivity for model - layers 1,2, and 3. For time-varying properties (e.g. recharge), the - iterable is for zero-based stress period indices. For example, ["rch.rech",[0,4,10,15]] - would setup pilot point multiplier parameters for recharge for stress - period 1,5,11,and 16. - const_props : list - constant (uniform) multiplier parameters for grid-based properties. - A nested list of grid-scale model properties to parameterize using - name, iterable pairs. For 3D properties, the iterable is zero-based - layer indices. For example, ["lpf.hk",[0,1,2,]] would setup constant (uniform) multiplier - parameters for layer property file horizontal hydraulic conductivity for model - layers 1,2, and 3. For time-varying properties (e.g. recharge), the - iterable is for zero-based stress period indices. For example, ["rch.rech",[0,4,10,15]] - would setup constant (uniform) multiplier parameters for recharge for stress - period 1,5,11,and 16. - temporal_bc_props : list - boundary condition stress-period level multiplier parameters. - A nested list of boundary condition elements to parameterize using - name, iterable pairs. The iterable is zero-based stress-period indices. - For example, to setup multipliers for WEL flux and for RIV conductance, - bc_props = [["wel.flux",[0,1,2]],["riv.cond",None]] would setup - multiplier parameters for well flux for stress periods 1,2 and 3 and - would setup one single river conductance multiplier parameter that is applied - to all stress periods - spatial_bc_props : list - boundary condition spatial multiplier parameters. - A nested list of boundary condition elements to parameterize using - names (e.g. [["riv.cond",0],["wel.flux",1] to setup up cell-based parameters for - each boundary condition element listed. These multipler parameters are applied across - all stress periods. For this to work, there must be the same number of entries - for all stress periods. If more than one BC of the same type is in a single - cell, only one parameter is used to multiply all BCs in the same cell. - grid_props : list - grid-based (every active model cell) multiplier parameters. - A nested list of grid-scale model properties to parameterize using - name, iterable pairs. For 3D properties, the iterable is zero-based - layer indices (e.g., ["lpf.hk",[0,1,2,]] would setup a multiplier - parameter for layer property file horizontal hydraulic conductivity for model - layers 1,2, and 3 in every active model cell). For time-varying properties (e.g. recharge), the - iterable is for zero-based stress period indices. For example, ["rch.rech",[0,4,10,15]] - would setup grid-based multiplier parameters in every active model cell - for recharge for stress period 1,5,11,and 16. - grid_geostruct : pyemu.geostats.GeoStruct - the geostatistical structure to build the prior parameter covariance matrix - elements for grid-based parameters. If None, a generic GeoStruct is created - using an "a" parameter that is 10 times the max cell size. Default is None - pp_space : int - number of grid cells between pilot points. If None, use the default - in pyemu.pp_utils.setup_pilot_points_grid. Default is None - zone_props : list - zone-based multiplier parameters. - A nested list of grid-scale model properties to parameterize using - name, iterable pairs. For 3D properties, the iterable is zero-based - layer indices (e.g., ["lpf.hk",[0,1,2,]] would setup a multiplier - parameter for layer property file horizontal hydraulic conductivity for model - layers 1,2, and 3 for unique zone values in the ibound array. - For time-varying properties (e.g. recharge), the iterable is for - zero-based stress period indices. For example, ["rch.rech",[0,4,10,15]] - would setup zone-based multiplier parameters for recharge for stress - period 1,5,11,and 16. - pp_geostruct : pyemu.geostats.GeoStruct - the geostatistical structure to use for building the prior parameter - covariance matrix for pilot point parameters. If None, a generic - GeoStruct is created using pp_space and grid-spacing information. - Default is None - par_bounds_dict : dict - a dictionary of model property/boundary condition name, upper-lower bound pairs. - For example, par_bounds_dict = {"hk":[0.01,100.0],"flux":[0.5,2.0]} would - set the bounds for horizontal hydraulic conductivity to - 0.001 and 100.0 and set the bounds for flux parameters to 0.5 and - 2.0. For parameters not found in par_bounds_dict, - pyemu.helpers.wildass_guess_par_bounds_dict is - used to set somewhat meaningful bounds. Default is None - temporal_bc_geostruct : pyemu.geostats.GeoStruct - the geostastical struture to build the prior parameter covariance matrix - for time-varying boundary condition multiplier parameters. This GeoStruct - express the time correlation so that the 'a' parameter is the length of - time that boundary condition multiplier parameters are correlated across. - If None, then a generic GeoStruct is created that uses an 'a' parameter - of 3 stress periods. Default is None - spatial_bc_geostruct : pyemu.geostats.GeoStruct - the geostastical struture to build the prior parameter covariance matrix - for spatially-varying boundary condition multiplier parameters. - If None, a generic GeoStruct is created using an "a" parameter that - is 10 times the max cell size. Default is None. - remove_existing : bool - a flag to remove an existing new_model_ws directory. If False and - new_model_ws exists, an exception is raised. If True and new_model_ws - exists, the directory is destroyed - user beware! Default is False. - k_zone_dict : dict - a dictionary of zero-based layer index, zone array pairs. Used to - override using ibound zones for zone-based parameterization. If None, - use ibound values greater than zero as zones. - use_pp_zones : bool - a flag to use ibound zones (or k_zone_dict, see above) as pilot - point zones. If False, ibound values greater than zero are treated as - a single zone for pilot points. Default is False - obssim_smp_pairs: list - a list of observed-simulated PEST-type SMP file pairs to get observations - from and include in the control file. Default is [] - external_tpl_in_pairs : list - a list of existing template file, model input file pairs to parse parameters - from and include in the control file. Default is [] - external_ins_out_pairs : list - a list of existing instruction file, model output file pairs to parse - observations from and include in the control file. Default is [] - extra_pre_cmds : list - a list of preprocessing commands to add to the forward_run.py script - commands are executed with os.system() within forward_run.py. Default - is None. - redirect_forward_output : bool - flag for whether to redirect forward model output to text files (True) or - allow model output to be directed to the screen (False) - Default is True - extra_post_cmds : list - a list of post-processing commands to add to the forward_run.py script. - Commands are executed with os.system() within forward_run.py. - Default is None. - tmp_files : list - a list of temporary files that should be removed at the start of the forward - run script. Default is []. - model_exe_name : str - binary name to run modflow. If None, a default from flopy is used, - which is dangerous because of the non-standard binary names - (e.g. MODFLOW-NWT_x64, MODFLOWNWT, mfnwt, etc). Default is None. - build_prior : bool - flag to build prior covariance matrix. Default is True - sfr_obs : bool - flag to include observations of flow and aquifer exchange from - the sfr ASCII output file - all_wells : bool [DEPRECATED - now use spatial_bc_pars] - hfb_pars : bool - add HFB parameters. uses pyemu.gw_utils.write_hfb_template(). the resulting - HFB pars have parval1 equal to the values in the original file and use the - spatial_bc_geostruct to build geostatistical covariates between parameters - - Returns - ------- - PstFromFlopyModel : PstFromFlopyModel - - Attributes - ---------- - pst : pyemu.Pst - - - Note - ---- - works a lot better if TEMPCHEK, INSCHEK and PESTCHEK are available in the - system path variable - - """ - - def __init__(self,model,new_model_ws,org_model_ws=None,pp_props=[],const_props=[], - temporal_bc_props=[],grid_props=[],grid_geostruct=None,pp_space=None, - zone_props=[],pp_geostruct=None,par_bounds_dict=None,sfr_pars=False, - temporal_bc_geostruct=None,remove_existing=False,k_zone_dict=None, - mflist_waterbudget=True,mfhyd=True,hds_kperk=[],use_pp_zones=False, - obssim_smp_pairs=None,external_tpl_in_pairs=None, - external_ins_out_pairs=None,extra_pre_cmds=None, - extra_model_cmds=None,extra_post_cmds=None,redirect_forward_output=True, - tmp_files=None,model_exe_name=None,build_prior=True, - sfr_obs=False, all_wells=False,bc_props=[], - spatial_bc_props=[],spatial_bc_geostruct=None, - hfb_pars=False, kl_props=None,kl_num_eig=100, kl_geostruct=None): - - self.logger = pyemu.logger.Logger("PstFromFlopyModel.log") - self.log = self.logger.log - - self.logger.echo = True - self.zn_suffix = "_zn" - self.gr_suffix = "_gr" - self.pp_suffix = "_pp" - self.cn_suffix = "_cn" - self.kl_suffix = "_kl" - self.arr_org = "arr_org" - self.arr_mlt = "arr_mlt" - self.bc_org = "bc_org" - self.bc_mlt = "bc_mlt" - self.forward_run_file = "forward_run.py" - - self.remove_existing = remove_existing - self.external_tpl_in_pairs = external_tpl_in_pairs - self.external_ins_out_pairs = external_ins_out_pairs - - self.setup_model(model, org_model_ws, new_model_ws) - self.add_external() - - self.arr_mult_dfs = [] - self.par_bounds_dict = par_bounds_dict - self.pp_props = pp_props - self.pp_space = pp_space - self.pp_geostruct = pp_geostruct - self.use_pp_zones = use_pp_zones - - self.const_props = const_props - - self.grid_props = grid_props - self.grid_geostruct = grid_geostruct - - self.zone_props = zone_props - - self.kl_props = kl_props - self.kl_geostruct = kl_geostruct - self.kl_num_eig = kl_num_eig - - if len(bc_props) > 0: - if len(temporal_bc_props) > 0: - self.logger.lraise("bc_props and temporal_bc_props. "+\ - "bc_props is deprecated and replaced by temporal_bc_props") - self.logger.warn("bc_props is deprecated and replaced by temporal_bc_props") - temporal_bc_props = bc_props - - self.temporal_bc_props = temporal_bc_props - self.temporal_bc_geostruct = temporal_bc_geostruct - self.spatial_bc_props = spatial_bc_props - self.spatial_bc_geostruct = spatial_bc_geostruct - - - self.obssim_smp_pairs = obssim_smp_pairs - self.hds_kperk = hds_kperk - self.sfr_obs = sfr_obs - self.frun_pre_lines = [] - self.frun_model_lines = [] - self.frun_post_lines = [] - self.tmp_files = [] - if tmp_files is not None: - if not isinstance(tmp_files,list): - tmp_files = [tmp_files] - self.tmp_files.extend(tmp_files) - - if k_zone_dict is None: - self.k_zone_dict = {k:self.m.bas6.ibound[k].array for k in np.arange(self.m.nlay)} - else: - for k,arr in k_zone_dict.items(): - if k not in np.arange(self.m.nlay): - self.logger.lraise("k_zone_dict layer index not in nlay:{0}". - format(k)) - if arr.shape != (self.m.nrow,self.m.ncol): - self.logger.lraise("k_zone_dict arr for k {0} has wrong shape:{1}". - format(k,arr.shape)) - self.k_zone_dict = k_zone_dict - - # add any extra commands to the forward run lines - - for alist,ilist in zip([self.frun_pre_lines,self.frun_model_lines,self.frun_post_lines], - [extra_pre_cmds,extra_model_cmds,extra_post_cmds]): - if ilist is None: - continue - - if not isinstance(ilist,list): - ilist = [ilist] - for cmd in ilist: - self.logger.statement("forward_run line:{0}".format(cmd)) - alist.append("pyemu.os_utils.run('{0}')\n".format(cmd)) - - # add the model call - - if model_exe_name is None: - model_exe_name = self.m.exe_name - self.logger.warn("using flopy binary to execute the model:{0}".format(model)) - if redirect_forward_output: - line = "pyemu.os_utils.run('{0} {1} 1>{1}.stdout 2>{1}.stderr')".format(model_exe_name,self.m.namefile) - else: - line = "pyemu.os_utils.run('{0} {1} ')".format(model_exe_name, self.m.namefile) - self.logger.statement("forward_run line:{0}".format(line)) - self.frun_model_lines.append(line) - - self.tpl_files,self.in_files = [],[] - self.ins_files,self.out_files = [],[] - self.all_wells = all_wells - self.setup_mult_dirs() - - self.mlt_files = [] - self.org_files = [] - self.m_files = [] - self.mlt_counter = {} - self.par_dfs = {} - self.mlt_dfs = [] - if self.all_wells: - #self.setup_all_wells() - self.logger.lraise("all_wells has been deprecated and replaced with"+\ - " spatial_bc_pars.") - self.setup_bc_pars() - - - self.setup_array_pars() - - if sfr_pars: - self.setup_sfr_pars() - - if hfb_pars: - self.setup_hfb_pars() - - self.mflist_waterbudget = mflist_waterbudget - self.setup_observations() - self.build_pst() - if build_prior: - self.parcov = self.build_prior() - else: - self.parcov = None - self.log("saving intermediate _setup_<> dfs into {0}". - format(self.m.model_ws)) - for tag,df in self.par_dfs.items(): - df.to_csv(os.path.join(self.m.model_ws,"_setup_par_{0}_{1}.csv". - format(tag.replace(" ",'_'),self.pst_name))) - for tag,df in self.obs_dfs.items(): - df.to_csv(os.path.join(self.m.model_ws,"_setup_obs_{0}_{1}.csv". - format(tag.replace(" ",'_'),self.pst_name))) - self.log("saving intermediate _setup_<> dfs into {0}". - format(self.m.model_ws)) - - self.logger.statement("all done") - - - - - -
    [docs] def setup_sfr_obs(self): - """setup sfr ASCII observations""" - if not self.sfr_obs: - return - - if self.m.sfr is None: - self.logger.lraise("no sfr package found...") - org_sfr_out_file = os.path.join(self.org_model_ws,"{0}.sfr.out".format(self.m.name)) - if not os.path.exists(org_sfr_out_file): - self.logger.lraise("setup_sfr_obs() error: could not locate existing sfr out file: {0}". - format(org_sfr_out_file)) - new_sfr_out_file = os.path.join(self.m.model_ws,os.path.split(org_sfr_out_file)[-1]) - shutil.copy2(org_sfr_out_file,new_sfr_out_file) - seg_group_dict = None - if isinstance(self.sfr_obs,dict): - seg_group_dict = self.sfr_obs - - df = pyemu.gw_utils.setup_sfr_obs(new_sfr_out_file,seg_group_dict=seg_group_dict, - model=self.m,include_path=True) - if df is not None: - self.obs_dfs["sfr"] = df - self.frun_post_lines.append("pyemu.gw_utils.apply_sfr_obs()")
    - - -
    [docs] def setup_sfr_pars(self): - """setup multiplier parameters for sfr segment data - Adding support for reachinput (and isfropt = 1)""" - assert self.m.sfr is not None,"can't find sfr package..." - par_dfs = {} - df = pyemu.gw_utils.setup_sfr_seg_parameters(self.m) # self.m.namefile,self.m.model_ws) # now just pass model - # self.par_dfs["sfr"] = df - par_dfs["sfr"] = [df] # may need df for both segs and reaches - self.tpl_files.append("sfr_seg_pars.dat.tpl") - self.in_files.append("sfr_seg_pars.dat") - if self.m.sfr.reachinput: # setup reaches - df = pyemu.gw_utils.setup_sfr_reach_parameters(self.m) - par_dfs["sfr"].append(df) - self.tpl_files.append("sfr_reach_pars.dat.tpl") - self.in_files.append("sfr_reach_pars.dat") - self.frun_pre_lines.append("pyemu.gw_utils.apply_sfr_parameters(reach_pars=True)") - else: - self.frun_pre_lines.append("pyemu.gw_utils.apply_sfr_seg_parameters()") - self.par_dfs["sfr"] = pd.concat(par_dfs["sfr"])
    - - -
    [docs] def setup_hfb_pars(self): - """setup non-mult parameters for hfb (yuck!) - - """ - if self.m.hfb6 is None: - self.logger.lraise("couldn't find hfb pak") - tpl_file,df = pyemu.gw_utils.write_hfb_template(self.m) - - self.in_files.append(os.path.split(tpl_file.replace(".tpl",""))[-1]) - self.tpl_files.append(os.path.split(tpl_file)[-1]) - self.par_dfs["hfb"] = df
    - -
    [docs] def setup_mult_dirs(self): - """ setup the directories to use for multiplier parameterization. Directories - are make within the PstFromFlopyModel.m.model_ws directory - - """ - # setup dirs to hold the original and multiplier model input quantities - set_dirs = [] -# if len(self.pp_props) > 0 or len(self.zone_props) > 0 or \ -# len(self.grid_props) > 0: - if self.pp_props is not None or \ - self.zone_props is not None or \ - self.grid_props is not None or\ - self.const_props is not None or \ - self.kl_props is not None: - set_dirs.append(self.arr_org) - set_dirs.append(self.arr_mlt) - # if len(self.bc_props) > 0: - if len(self.temporal_bc_props) > 0 or len(self.spatial_bc_props) > 0: - set_dirs.append(self.bc_org) - if len(self.spatial_bc_props): - set_dirs.append(self.bc_mlt) - - for d in set_dirs: - d = os.path.join(self.m.model_ws,d) - self.log("setting up '{0}' dir".format(d)) - if os.path.exists(d): - if self.remove_existing: - shutil.rmtree(d,onerror=remove_readonly) - else: - raise Exception("dir '{0}' already exists". - format(d)) - os.mkdir(d) - self.log("setting up '{0}' dir".format(d))
    - -
    [docs] def setup_model(self,model,org_model_ws,new_model_ws): - """ setup the flopy.mbase instance for use with multipler parameters. - Changes model_ws, sets external_path and writes new MODFLOW input - files - - Parameters - ---------- - model : flopy.mbase - flopy model instance - org_model_ws : str - the orginal model working space - new_model_ws : str - the new model working space - - """ - split_new_mws = [i for i in os.path.split(new_model_ws) if len(i) > 0] - if len(split_new_mws) != 1: - self.logger.lraise("new_model_ws can only be 1 folder-level deep:{0}". - format(str(split_new_mws))) - - if isinstance(model,str): - self.log("loading flopy model") - try: - import flopy - except: - raise Exception("from_flopy_model() requires flopy") - # prepare the flopy model - self.org_model_ws = org_model_ws - self.new_model_ws = new_model_ws - self.m = flopy.modflow.Modflow.load(model,model_ws=org_model_ws, - check=False,verbose=True,forgive=False) - self.log("loading flopy model") - else: - self.m = model - self.org_model_ws = str(self.m.model_ws) - self.new_model_ws = new_model_ws - - self.log("updating model attributes") - self.m.array_free_format = True - self.m.free_format_input = True - self.m.external_path = '.' - self.log("updating model attributes") - if os.path.exists(new_model_ws): - if not self.remove_existing: - self.logger.lraise("'new_model_ws' already exists") - else: - self.logger.warn("removing existing 'new_model_ws") - shutil.rmtree(new_model_ws,onerror=remove_readonly) - self.m.change_model_ws(new_model_ws,reset_external=True) - self.m.exe_name = self.m.exe_name.replace(".exe",'') - self.m.exe = self.m.version - self.log("writing new modflow input files") - self.m.write_input() - self.log("writing new modflow input files")
    - -
    [docs] def get_count(self,name): - """ get the latest counter for a certain parameter type. - - Parameters - ---------- - name : str - the parameter type - - Returns - ------- - count : int - the latest count for a parameter type - - Note - ---- - calling this function increments the counter for the passed - parameter type - - """ - if name not in self.mlt_counter: - self.mlt_counter[name] = 1 - c = 0 - else: - c = self.mlt_counter[name] - self.mlt_counter[name] += 1 - #print(name,c) - return c
    - -
    [docs] def prep_mlt_arrays(self): - """ prepare multipler arrays. Copies existing model input arrays and - writes generic (ones) multiplier arrays - - """ - par_props = [self.pp_props,self.grid_props, - self.zone_props,self.const_props, - self.kl_props] - par_suffixs = [self.pp_suffix,self.gr_suffix, - self.zn_suffix,self.cn_suffix, - self.kl_suffix] - - # Need to remove props and suffixes for which no info was provided (e.g. still None) - del_idx = [] - for i,cp in enumerate(par_props): - if cp is None: - del_idx.append(i) - for i in del_idx[::-1]: - del(par_props[i]) - del(par_suffixs[i]) - - mlt_dfs = [] - for par_prop,suffix in zip(par_props,par_suffixs): - if len(par_prop) == 2: - if not isinstance(par_prop[0],list): - par_prop = [par_prop] - if len(par_prop) == 0: - continue - for pakattr,k_org in par_prop: - attr_name = pakattr.split('.')[1] - pak,attr = self.parse_pakattr(pakattr) - ks = np.arange(self.m.nlay) - if isinstance(attr,flopy.utils.Transient2d): - ks = np.arange(self.m.nper) - try: - k_parse = self.parse_k(k_org,ks) - except Exception as e: - self.logger.lraise("error parsing k {0}:{1}". - format(k_org,str(e))) - org,mlt,mod,layer = [],[],[],[] - c = self.get_count(attr_name) - mlt_prefix = "{0}{1}".format(attr_name,c) - mlt_name = os.path.join(self.arr_mlt,"{0}.dat{1}" - .format(mlt_prefix,suffix)) - for k in k_parse: - # horrible kludge to avoid passing int64 to flopy - # this gift may give again... - if type(k) is np.int64: - k = int(k) - if isinstance(attr,flopy.utils.Util2d): - fname = self.write_u2d(attr) - - layer.append(k) - elif isinstance(attr,flopy.utils.Util3d): - fname = self.write_u2d(attr[k]) - layer.append(k) - elif isinstance(attr,flopy.utils.Transient2d): - fname = self.write_u2d(attr.transient_2ds[k]) - layer.append(0) #big assumption here - mod.append(os.path.join(self.m.external_path,fname)) - mlt.append(mlt_name) - org.append(os.path.join(self.arr_org,fname)) - df = pd.DataFrame({"org_file":org,"mlt_file":mlt,"model_file":mod,"layer":layer}) - df.loc[:,"suffix"] = suffix - df.loc[:,"prefix"] = mlt_prefix - mlt_dfs.append(df) - if len(mlt_dfs) > 0: - mlt_df = pd.concat(mlt_dfs,ignore_index=True) - return mlt_df
    - -
    [docs] def write_u2d(self, u2d): - """ write a flopy.utils.Util2D instance to an ASCII text file using the - Util2D filename - - Parameters - ---------- - u2d : flopy.utils.Util2D - - Returns - ------- - filename : str - the name of the file written (without path) - - """ - filename = os.path.split(u2d.filename)[-1] - np.savetxt(os.path.join(self.m.model_ws,self.arr_org,filename), - u2d.array,fmt="%15.6E") - return filename
    - -
    [docs] def write_const_tpl(self,name,tpl_file,zn_array): - """ write a template file a for a constant (uniform) multiplier parameter - - Parameters - ---------- - name : str - the base parameter name - tpl_file : str - the template file to write - zn_array : numpy.ndarray - an array used to skip inactive cells - - Returns - ------- - df : pandas.DataFrame - a dataframe with parameter information - - """ - parnme = [] - with open(os.path.join(self.m.model_ws,tpl_file),'w') as f: - f.write("ptf ~\n") - for i in range(self.m.nrow): - for j in range(self.m.ncol): - if zn_array[i,j] < 1: - pname = " 1.0 " - else: - pname = "{0}{1}".format(name,self.cn_suffix) - if len(pname) > 12: - self.logger.lraise("zone pname too long:{0}".\ - format(pname)) - parnme.append(pname) - pname = " ~ {0} ~".format(pname) - f.write(pname) - f.write("\n") - df = pd.DataFrame({"parnme":parnme},index=parnme) - #df.loc[:,"pargp"] = "{0}{1}".format(self.cn_suffixname) - df.loc[:,"pargp"] = "{0}_{1}".format(name,self.cn_suffix.replace('_','')) - df.loc[:,"tpl"] = tpl_file - return df
    - -
    [docs] def write_grid_tpl(self,name,tpl_file,zn_array): - """ write a template file a for grid-based multiplier parameters - - Parameters - ---------- - name : str - the base parameter name - tpl_file : str - the template file to write - zn_array : numpy.ndarray - an array used to skip inactive cells - - Returns - ------- - df : pandas.DataFrame - a dataframe with parameter information - - """ - parnme,x,y = [],[],[] - with open(os.path.join(self.m.model_ws,tpl_file),'w') as f: - f.write("ptf ~\n") - for i in range(self.m.nrow): - for j in range(self.m.ncol): - if zn_array[i,j] < 1: - pname = ' 1.0 ' - else: - pname = "{0}{1:03d}{2:03d}".format(name,i,j) - if len(pname) > 12: - self.logger.lraise("grid pname too long:{0}".\ - format(pname)) - parnme.append(pname) - pname = ' ~ {0} ~ '.format(pname) - x.append(self.m.sr.xcentergrid[i,j]) - y.append(self.m.sr.ycentergrid[i,j]) - f.write(pname) - f.write("\n") - df = pd.DataFrame({"parnme":parnme,"x":x,"y":y},index=parnme) - df.loc[:,"pargp"] = "{0}{1}".format(self.gr_suffix.replace('_',''),name) - df.loc[:,"tpl"] = tpl_file - return df
    - -
    [docs] @staticmethod - def write_zone_tpl(model, name, tpl_file, zn_array, zn_suffix, logger=None): - """ write a template file a for zone-based multiplier parameters - - Parameters - ---------- - model : flopy model object - model from which to obtain workspace information, nrow, and ncol - name : str - the base parameter name - tpl_file : str - the template file to write - zn_array : numpy.ndarray - an array used to skip inactive cells - - logger : a logger object - optional - a logger object to document errors, etc. - Returns - ------- - df : pandas.DataFrame - a dataframe with parameter information - - """ - parnme = [] - with open(os.path.join(model.model_ws, tpl_file), 'w') as f: - f.write("ptf ~\n") - for i in range(model.nrow): - for j in range(model.ncol): - if zn_array[i,j] < 1: - pname = " 1.0 " - else: - pname = "{0}_zn{1}".format(name, zn_array[i, j]) - if len(pname) > 12: - if logger is not None: - logger.lraise("zone pname too long:{0}".\ - format(pname)) - parnme.append(pname) - pname = " ~ {0} ~".format(pname) - f.write(pname) - f.write("\n") - df = pd.DataFrame({"parnme":parnme}, index=parnme) - df.loc[:, "pargp"] = "{0}{1}".format(zn_suffix.replace("_",''), name) - return df
    - -
    [docs] def grid_prep(self): - """ prepare grid-based parameterizations - - """ - if len(self.grid_props) == 0: - return - - if self.grid_geostruct is None: - self.logger.warn("grid_geostruct is None,"\ - " using ExpVario with contribution=1 and a=(max(delc,delr)*10") - dist = 10 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) - v = pyemu.geostats.ExpVario(contribution=1.0,a=dist) - self.grid_geostruct = pyemu.geostats.GeoStruct(variograms=v)
    - -
    [docs] def pp_prep(self, mlt_df): - """ prepare pilot point based parameterizations - - Parameters - ---------- - mlt_df : pandas.DataFrame - a dataframe with multiplier array information - - Note - ---- - calls pyemu.pp_utils.setup_pilot_points_grid() - - - """ - if len(self.pp_props) == 0: - return - if self.pp_space is None: - self.logger.warn("pp_space is None, using 10...\n") - self.pp_space=10 - if self.pp_geostruct is None: - self.logger.warn("pp_geostruct is None,"\ - " using ExpVario with contribution=1 and a=(pp_space*max(delr,delc))") - pp_dist = self.pp_space * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) - v = pyemu.geostats.ExpVario(contribution=1.0,a=pp_dist) - self.pp_geostruct = pyemu.geostats.GeoStruct(variograms=v) - - pp_df = mlt_df.loc[mlt_df.suffix==self.pp_suffix,:] - layers = pp_df.layer.unique() - pp_dict = {l:list(pp_df.loc[pp_df.layer==l,"prefix"].unique()) for l in layers} - # big assumption here - if prefix is listed more than once, use the lowest layer index - for i,l in enumerate(layers): - p = set(pp_dict[l]) - for ll in layers[i+1:]: - pp = set(pp_dict[ll]) - d = pp - p - pp_dict[ll] = list(d) - - - pp_array_file = {p:m for p,m in zip(pp_df.prefix,pp_df.mlt_file)} - self.logger.statement("pp_dict: {0}".format(str(pp_dict))) - - self.log("calling setup_pilot_point_grid()") - if self.use_pp_zones: - ib = self.k_zone_dict - else: - ib = {k:self.m.bas6.ibound[k].array for k in range(self.m.nlay)} - - for k,i in ib.items(): - if np.any(i<0): - u,c = np.unique(i[i>0], return_counts=True) - counts = dict(zip(u,c)) - mx = -1.0e+10 - imx = None - for u,c in counts.items(): - if c > mx: - mx = c - imx = u - self.logger.warn("resetting negative ibound values for PP zone"+ \ - "array in layer {0} : {1}".format(k+1,u)) - i[i<0] = u - pp_df = pyemu.pp_utils.setup_pilotpoints_grid(self.m, - ibound=ib, - use_ibound_zones=self.use_pp_zones, - prefix_dict=pp_dict, - every_n_cell=self.pp_space, - pp_dir=self.m.model_ws, - tpl_dir=self.m.model_ws, - shapename=os.path.join( - self.m.model_ws,"pp.shp")) - self.logger.statement("{0} pilot point parameters created". - format(pp_df.shape[0])) - self.logger.statement("pilot point 'pargp':{0}". - format(','.join(pp_df.pargp.unique()))) - self.log("calling setup_pilot_point_grid()") - - # calc factors for each layer - pargp = pp_df.pargp.unique() - pp_dfs_k = {} - fac_files = {} - pp_df.loc[:,"fac_file"] = np.NaN - for pg in pargp: - ks = pp_df.loc[pp_df.pargp==pg,"k"].unique() - if len(ks) == 0: - self.logger.lraise("something is wrong in fac calcs for par group {0}".format(pg)) - if len(ks) == 1: - - ib_k = ib[ks[0]] - if len(ks) != 1: - #self.logger.lraise("something is wrong in fac calcs for par group {0}".format(pg)) - self.logger.warn("multiple k values for {0},forming composite zone array...".format(pg)) - ib_k = np.zeros((self.m.nrow,self.m.ncol)) - for k in ks: - t = ib[k].copy() - t[t<1] = 0 - ib_k[t>0] = t[t>0] - k = int(ks[0]) - if k not in pp_dfs_k.keys(): - self.log("calculating factors for k={0}".format(k)) - - fac_file = os.path.join(self.m.model_ws,"pp_k{0}.fac".format(k)) - var_file = fac_file.replace(".fac",".var.dat") - self.logger.statement("saving krige variance file:{0}" - .format(var_file)) - self.logger.statement("saving krige factors file:{0}"\ - .format(fac_file)) - pp_df_k = pp_df.loc[pp_df.pargp==pg] - ok_pp = pyemu.geostats.OrdinaryKrige(self.pp_geostruct,pp_df_k) - ok_pp.calc_factors_grid(self.m.sr,var_filename=var_file, - zone_array=ib_k) - ok_pp.to_grid_factors_file(fac_file) - fac_files[k] = fac_file - self.log("calculating factors for k={0}".format(k)) - pp_dfs_k[k] = pp_df_k - - for k,fac_file in fac_files.items(): - #pp_files = pp_df.pp_filename.unique() - fac_file = os.path.split(fac_file)[-1] - pp_prefixes = pp_dict[k] - for pp_prefix in pp_prefixes: - self.log("processing pp_prefix:{0}".format(pp_prefix)) - if pp_prefix not in pp_array_file.keys(): - self.logger.lraise("{0} not in self.pp_array_file.keys()". - format(pp_prefix,','. - join(pp_array_file.keys()))) - - - out_file = os.path.join(self.arr_mlt,os.path.split(pp_array_file[pp_prefix])[-1]) - - pp_files = pp_df.loc[pp_df.pp_filename.apply(lambda x: pp_prefix in x),"pp_filename"] - if pp_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of pp_files found:{0}".format(','.join(pp_files))) - pp_file = os.path.split(pp_files.iloc[0])[-1] - pp_df.loc[pp_df.pargp==pp_prefix,"fac_file"] = fac_file - pp_df.loc[pp_df.pargp==pp_prefix,"pp_file"] = pp_file - pp_df.loc[pp_df.pargp==pp_prefix,"out_file"] = out_file - - pp_df.loc[:,"pargp"] = pp_df.pargp.apply(lambda x: "pp_{0}".format(x)) - out_files = mlt_df.loc[mlt_df.mlt_file. - apply(lambda x: x.endswith(self.pp_suffix)),"mlt_file"] - #mlt_df.loc[:,"fac_file"] = np.NaN - #mlt_df.loc[:,"pp_file"] = np.NaN - for out_file in out_files: - pp_df_pf = pp_df.loc[pp_df.out_file==out_file,:] - fac_files = pp_df_pf.fac_file - if fac_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of fac files:{0}".format(str(fac_files.unique()))) - fac_file = fac_files.iloc[0] - pp_files = pp_df_pf.pp_file - if pp_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of pp files:{0}".format(str(pp_files.unique()))) - pp_file = pp_files.iloc[0] - mlt_df.loc[mlt_df.mlt_file==out_file,"fac_file"] = fac_file - mlt_df.loc[mlt_df.mlt_file==out_file,"pp_file"] = pp_file - self.par_dfs[self.pp_suffix] = pp_df - - mlt_df.loc[mlt_df.suffix==self.pp_suffix,"tpl_file"] = np.NaN
    - - -
    [docs] def kl_prep(self,mlt_df): - """ prepare KL based parameterizations - - Parameters - ---------- - mlt_df : pandas.DataFrame - a dataframe with multiplier array information - - Note - ---- - calls pyemu.helpers.setup_kl() - - - """ - if len(self.kl_props) == 0: - return - - if self.kl_geostruct is None: - self.logger.warn("kl_geostruct is None,"\ - " using ExpVario with contribution=1 and a=(10.0*max(delr,delc))") - kl_dist = 10.0 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) - v = pyemu.geostats.ExpVario(contribution=1.0,a=kl_dist) - self.kl_geostruct = pyemu.geostats.GeoStruct(variograms=v) - - kl_df = mlt_df.loc[mlt_df.suffix==self.kl_suffix,:] - layers = kl_df.layer.unique() - #kl_dict = {l:list(kl_df.loc[kl_df.layer==l,"prefix"].unique()) for l in layers} - # big assumption here - if prefix is listed more than once, use the lowest layer index - #for i,l in enumerate(layers): - # p = set(kl_dict[l]) - # for ll in layers[i+1:]: - # pp = set(kl_dict[ll]) - # d = pp - p - # kl_dict[ll] = list(d) - kl_prefix = list(kl_df.loc[:,"prefix"]) - - kl_array_file = {p:m for p,m in zip(kl_df.prefix,kl_df.mlt_file)} - self.logger.statement("kl_prefix: {0}".format(str(kl_prefix))) - - fac_file = os.path.join(self.m.model_ws, "kl.fac") - - self.log("calling kl_setup() with factors file {0}".format(fac_file)) - - kl_df = kl_setup(self.kl_num_eig,self.m.sr,self.kl_geostruct,kl_prefix, - factors_file=fac_file,basis_file=fac_file+".basis.jcb", - tpl_dir=self.m.model_ws) - self.logger.statement("{0} kl parameters created". - format(kl_df.shape[0])) - self.logger.statement("kl 'pargp':{0}". - format(','.join(kl_df.pargp.unique()))) - - self.log("calling kl_setup() with factors file {0}".format(fac_file)) - kl_mlt_df = mlt_df.loc[mlt_df.suffix==self.kl_suffix] - for prefix in kl_df.prefix.unique(): - prefix_df = kl_df.loc[kl_df.prefix==prefix,:] - in_file = os.path.split(prefix_df.loc[:,"in_file"].iloc[0])[-1] - assert prefix in mlt_df.prefix.values,"{0}:{1}".format(prefix,mlt_df.prefix) - mlt_df.loc[mlt_df.prefix==prefix,"pp_file"] = in_file - mlt_df.loc[mlt_df.prefix==prefix,"fac_file"] = os.path.split(fac_file)[-1] - - print(kl_mlt_df) - mlt_df.loc[mlt_df.suffix == self.kl_suffix, "tpl_file"] = np.NaN - self.par_dfs[self.kl_suffix] = kl_df
    - # calc factors for each layer - - -
    [docs] def setup_array_pars(self): - """ main entry point for setting up array multipler parameters - - """ - mlt_df = self.prep_mlt_arrays() - if mlt_df is None: - return - mlt_df.loc[:,"tpl_file"] = mlt_df.mlt_file.apply(lambda x: os.path.split(x)[-1]+".tpl") - #mlt_df.loc[mlt_df.tpl_file.apply(lambda x:pd.notnull(x.pp_file)),"tpl_file"] = np.NaN - mlt_files = mlt_df.mlt_file.unique() - #for suffix,tpl_file,layer,name in zip(self.mlt_df.suffix, - # self.mlt_df.tpl,self.mlt_df.layer, - # self.mlt_df.prefix): - par_dfs = {} - for mlt_file in mlt_files: - suffixes = mlt_df.loc[mlt_df.mlt_file==mlt_file,"suffix"] - if suffixes.unique().shape[0] != 1: - self.logger.lraise("wrong number of suffixes for {0}"\ - .format(mlt_file)) - suffix = suffixes.iloc[0] - - tpl_files = mlt_df.loc[mlt_df.mlt_file==mlt_file,"tpl_file"] - if tpl_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of tpl_files for {0}"\ - .format(mlt_file)) - tpl_file = tpl_files.iloc[0] - layers = mlt_df.loc[mlt_df.mlt_file==mlt_file,"layer"] - #if layers.unique().shape[0] != 1: - # self.logger.lraise("wrong number of layers for {0}"\ - # .format(mlt_file)) - layer = layers.iloc[0] - names = mlt_df.loc[mlt_df.mlt_file==mlt_file,"prefix"] - if names.unique().shape[0] != 1: - self.logger.lraise("wrong number of names for {0}"\ - .format(mlt_file)) - name = names.iloc[0] - #ib = self.k_zone_dict[layer] - df = None - if suffix == self.cn_suffix: - self.log("writing const tpl:{0}".format(tpl_file)) - df = self.write_const_tpl(name,tpl_file,self.m.bas6.ibound[layer].array) - self.log("writing const tpl:{0}".format(tpl_file)) - - elif suffix == self.gr_suffix: - self.log("writing grid tpl:{0}".format(tpl_file)) - df = self.write_grid_tpl(name,tpl_file,self.m.bas6.ibound[layer].array) - self.log("writing grid tpl:{0}".format(tpl_file)) - - elif suffix == self.zn_suffix: - self.log("writing zone tpl:{0}".format(tpl_file)) - df = self.write_zone_tpl(self.m, name, tpl_file, self.k_zone_dict[layer], self.zn_suffix, self.logger) - self.log("writing zone tpl:{0}".format(tpl_file)) - - if df is None: - continue - if suffix not in par_dfs: - par_dfs[suffix] = [df] - else: - par_dfs[suffix].append(df) - for suf,dfs in par_dfs.items(): - self.par_dfs[suf] = pd.concat(dfs) - - if self.pp_suffix in mlt_df.suffix.values: - self.log("setting up pilot point process") - self.pp_prep(mlt_df) - self.log("setting up pilot point process") - - if self.gr_suffix in mlt_df.suffix.values: - self.log("setting up grid process") - self.grid_prep() - self.log("setting up grid process") - - if self.kl_suffix in mlt_df.suffix.values: - self.log("setting up kl process") - self.kl_prep(mlt_df) - self.log("setting up kl process") - - mlt_df.to_csv(os.path.join(self.m.model_ws,"arr_pars.csv")) - ones = np.ones((self.m.nrow,self.m.ncol)) - for mlt_file in mlt_df.mlt_file.unique(): - self.log("save test mlt array {0}".format(mlt_file)) - np.savetxt(os.path.join(self.m.model_ws,mlt_file), - ones,fmt="%15.6E") - self.log("save test mlt array {0}".format(mlt_file)) - tpl_files = mlt_df.loc[mlt_df.mlt_file == mlt_file, "tpl_file"] - if tpl_files.unique().shape[0] != 1: - self.logger.lraise("wrong number of tpl_files for {0}" \ - .format(mlt_file)) - tpl_file = tpl_files.iloc[0] - if pd.notnull(tpl_file): - self.tpl_files.append(tpl_file) - self.in_files.append(mlt_file) - - # for tpl_file,mlt_file in zip(mlt_df.tpl_file,mlt_df.mlt_file): - # if pd.isnull(tpl_file): - # continue - # self.tpl_files.append(tpl_file) - # self.in_files.append(mlt_file) - - os.chdir(self.m.model_ws) - try: - apply_array_pars() - except Exception as e: - os.chdir("..") - self.logger.lraise("error test running apply_array_pars():{0}". - format(str(e))) - os.chdir("..") - line = "pyemu.helpers.apply_array_pars()\n" - self.logger.statement("forward_run line:{0}".format(line)) - self.frun_pre_lines.append(line)
    - -
    [docs] def setup_observations(self): - """ main entry point for setting up observations - - """ - obs_methods = [self.setup_water_budget_obs,self.setup_hyd, - self.setup_smp,self.setup_hob,self.setup_hds, - self.setup_sfr_obs] - obs_types = ["mflist water budget obs","hyd file", - "external obs-sim smp files","hob","hds","sfr"] - self.obs_dfs = {} - for obs_method, obs_type in zip(obs_methods,obs_types): - self.log("processing obs type {0}".format(obs_type)) - obs_method() - self.log("processing obs type {0}".format(obs_type))
    - - - -
    [docs] def draw(self, num_reals=100, sigma_range=6): - """ draw like a boss! - - Parameters - ---------- - num_reals : int - number of realizations to generate. Default is 100 - sigma_range : float - number of standard deviations represented by the parameter bounds. Default - is 6. - - Returns - ------- - cov : pyemu.Cov - a full covariance matrix - - """ - - self.log("drawing realizations") - struct_dict = {} - if self.pp_suffix in self.par_dfs.keys(): - pp_df = self.par_dfs[self.pp_suffix] - pp_dfs = [] - for pargp in pp_df.pargp.unique(): - gp_df = pp_df.loc[pp_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - pp_dfs.append(p_df) - #pp_dfs = [pp_df.loc[pp_df.pargp==pargp,:].copy() for pargp in pp_df.pargp.unique()] - struct_dict[self.pp_geostruct] = pp_dfs - if self.gr_suffix in self.par_dfs.keys(): - gr_df = self.par_dfs[self.gr_suffix] - gr_dfs = [] - for pargp in gr_df.pargp.unique(): - gp_df = gr_df.loc[gr_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - gr_dfs.append(p_df) - #gr_dfs = [gr_df.loc[gr_df.pargp==pargp,:].copy() for pargp in gr_df.pargp.unique()] - struct_dict[self.grid_geostruct] = gr_dfs - if "temporal_bc" in self.par_dfs.keys(): - bc_df = self.par_dfs["temporal_bc"] - bc_df.loc[:,"y"] = 0 - bc_df.loc[:,"x"] = bc_df.timedelta.apply(lambda x: x.days) - bc_dfs = [] - for pargp in bc_df.pargp.unique(): - gp_df = bc_df.loc[bc_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - #print(p_df) - bc_dfs.append(p_df) - #bc_dfs = [bc_df.loc[bc_df.pargp==pargp,:].copy() for pargp in bc_df.pargp.unique()] - struct_dict[self.temporal_bc_geostruct] = bc_dfs - if "spatial_bc" in self.par_dfs.keys(): - bc_df = self.par_dfs["spatial_bc"] - bc_dfs = [] - for pargp in bc_df.pargp.unique(): - gp_df = bc_df.loc[bc_df.pargp==pargp,:] - #p_df = gp_df.drop_duplicates(subset="parnme") - #print(p_df) - bc_dfs.append(gp_df) - struct_dict[self.spatial_bc_geostruct] = bc_dfs - pe = geostatistical_draws(self.pst,struct_dict=struct_dict,num_reals=num_reals, - sigma_range=sigma_range) - - self.log("drawing realizations") - return pe
    - -
    [docs] def build_prior(self, fmt="ascii",filename=None,droptol=None, chunk=None, sparse=False, - sigma_range=6): - """ build a prior parameter covariance matrix. - - Parameters - ---------- - fmt : str - the format to save the cov matrix. Options are "ascii","binary","uncfile", "coo". - default is "ascii" - filename : str - the filename to save the prior cov matrix to. If None, the name is formed using - model nam_file name. Default is None. - droptol : float - tolerance for dropping near-zero values when writing compressed binary. - Default is None - chunk : int - chunk size to write in a single pass - for binary only - sparse : bool - flag to build a pyemu.SparseMatrix format cov matrix. Default is False - sigma_range : float - number of standard deviations represented by the parameter bounds. Default - is 6. - - Returns - ------- - cov : pyemu.Cov - a full covariance matrix - - """ - - fmt = fmt.lower() - acc_fmts = ["ascii","binary","uncfile","none","coo"] - if fmt not in acc_fmts: - self.logger.lraise("unrecognized prior save 'fmt':{0}, options are: {1}". - format(fmt,','.join(acc_fmts))) - - self.log("building prior covariance matrix") - struct_dict = {} - if self.pp_suffix in self.par_dfs.keys(): - pp_df = self.par_dfs[self.pp_suffix] - pp_dfs = [] - for pargp in pp_df.pargp.unique(): - gp_df = pp_df.loc[pp_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - pp_dfs.append(p_df) - #pp_dfs = [pp_df.loc[pp_df.pargp==pargp,:].copy() for pargp in pp_df.pargp.unique()] - struct_dict[self.pp_geostruct] = pp_dfs - if self.gr_suffix in self.par_dfs.keys(): - gr_df = self.par_dfs[self.gr_suffix] - gr_dfs = [] - for pargp in gr_df.pargp.unique(): - gp_df = gr_df.loc[gr_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - gr_dfs.append(p_df) - #gr_dfs = [gr_df.loc[gr_df.pargp==pargp,:].copy() for pargp in gr_df.pargp.unique()] - struct_dict[self.grid_geostruct] = gr_dfs - if "temporal_bc" in self.par_dfs.keys(): - bc_df = self.par_dfs["temporal_bc"] - bc_df.loc[:,"y"] = 0 - bc_df.loc[:,"x"] = bc_df.timedelta.apply(lambda x: x.days) - bc_dfs = [] - for pargp in bc_df.pargp.unique(): - gp_df = bc_df.loc[bc_df.pargp==pargp,:] - p_df = gp_df.drop_duplicates(subset="parnme") - #print(p_df) - bc_dfs.append(p_df) - #bc_dfs = [bc_df.loc[bc_df.pargp==pargp,:].copy() for pargp in bc_df.pargp.unique()] - struct_dict[self.temporal_bc_geostruct] = bc_dfs - if "spatial_bc" in self.par_dfs.keys(): - bc_df = self.par_dfs["spatial_bc"] - bc_dfs = [] - for pargp in bc_df.pargp.unique(): - gp_df = bc_df.loc[bc_df.pargp==pargp,:] - #p_df = gp_df.drop_duplicates(subset="parnme") - #print(p_df) - bc_dfs.append(gp_df) - struct_dict[self.spatial_bc_geostruct] = bc_dfs - if "hfb" in self.par_dfs.keys(): - if self.spatial_bc_geostruct in struct_dict.keys(): - struct_dict[self.spatial_bc_geostruct].append(self.par_dfs["hfb"]) - else: - struct_dict[self.spatial_bc_geostruct] = [self.par_dfs["hfb"]] - - if len(struct_dict) > 0: - if sparse: - cov = pyemu.helpers.sparse_geostatistical_prior_builder(self.pst, - struct_dict=struct_dict, - sigma_range=sigma_range) - - else: - cov = pyemu.helpers.geostatistical_prior_builder(self.pst, - struct_dict=struct_dict, - sigma_range=sigma_range) - else: - cov = pyemu.Cov.from_parameter_data(self.pst,sigma_range=sigma_range) - - if filename is None: - filename = os.path.join(self.m.model_ws,self.pst_name+".prior.cov") - if fmt != "none": - self.logger.statement("saving prior covariance matrix to file {0}".format(filename)) - if fmt == 'ascii': - cov.to_ascii(filename) - elif fmt == 'binary': - cov.to_binary(filename,droptol=droptol,chunk=chunk) - elif fmt == 'uncfile': - cov.to_uncfile(filename) - elif fmt == 'coo': - cov.to_coo(filename,droptol=droptol,chunk=chunk) - self.log("building prior covariance matrix") - return cov
    - -
    [docs] def build_pst(self,filename=None): - """ build the pest control file using the parameterizations and - observations. - - Parameters - ---------- - filename : str - the filename to save the pst to. If None, the name if formed from - the model namfile name. Default is None. - - Note - ---- - calls pyemu.Pst.from_io_files - - calls PESTCHEK - - """ - self.logger.statement("changing dir in to {0}".format(self.m.model_ws)) - os.chdir(self.m.model_ws) - tpl_files = copy.deepcopy(self.tpl_files) - in_files = copy.deepcopy(self.in_files) - try: - files = os.listdir('.') - new_tpl_files = [f for f in files if f.endswith(".tpl") and f not in tpl_files] - new_in_files = [f.replace(".tpl",'') for f in new_tpl_files] - tpl_files.extend(new_tpl_files) - in_files.extend(new_in_files) - ins_files = [f for f in files if f.endswith(".ins")] - out_files = [f.replace(".ins",'') for f in ins_files] - for tpl_file,in_file in zip(tpl_files,in_files): - if tpl_file not in self.tpl_files: - self.tpl_files.append(tpl_file) - self.in_files.append(in_file) - - for ins_file,out_file in zip(ins_files,out_files): - if ins_file not in self.ins_files: - self.ins_files.append(ins_file) - self.out_files.append(out_file) - self.log("instantiating control file from i/o files") - pst = pyemu.Pst.from_io_files(tpl_files=self.tpl_files, - in_files=self.in_files, - ins_files=self.ins_files, - out_files=self.out_files) - - self.log("instantiating control file from i/o files") - except Exception as e: - os.chdir("..") - self.logger.lraise("error build Pst:{0}".format(str(e))) - os.chdir('..') - # more customization here - par = pst.parameter_data - for name,df in self.par_dfs.items(): - if "parnme" not in df.columns: - continue - df.index = df.parnme - for col in par.columns: - if col in df.columns: - par.loc[df.parnme,col] = df.loc[:,col] - par.loc[:,"parubnd"] = 10.0 - par.loc[:,"parlbnd"] = 0.1 - - for name,df in self.par_dfs.items(): - if "parnme" not in df: - continue - df.index = df.parnme - for col in ["parubnd","parlbnd","pargp"]: - if col in df.columns: - par.loc[df.index,col] = df.loc[:,col] - - for tag,[lw,up] in wildass_guess_par_bounds_dict.items(): - par.loc[par.parnme.apply(lambda x: x.startswith(tag)),"parubnd"] = up - par.loc[par.parnme.apply(lambda x: x.startswith(tag)),"parlbnd"] = lw - - - if self.par_bounds_dict is not None: - for tag,[lw,up] in self.par_bounds_dict.items(): - par.loc[par.parnme.apply(lambda x: x.startswith(tag)),"parubnd"] = up - par.loc[par.parnme.apply(lambda x: x.startswith(tag)),"parlbnd"] = lw - - - - obs = pst.observation_data - for name,df in self.obs_dfs.items(): - if "obsnme" not in df.columns: - continue - df.index = df.obsnme - for col in df.columns: - if col in obs.columns: - obs.loc[df.obsnme,col] = df.loc[:,col] - - self.pst_name = self.m.name+".pst" - pst.model_command = ["python forward_run.py"] - pst.control_data.noptmax = 0 - self.log("writing forward_run.py") - self.write_forward_run() - self.log("writing forward_run.py") - - if filename is None: - filename = os.path.join(self.m.model_ws,self.pst_name) - self.logger.statement("writing pst {0}".format(filename)) - - pst.write(filename) - self.pst = pst - - self.log("running pestchek on {0}".format(self.pst_name)) - os.chdir(self.m.model_ws) - try: - run("pestchek {0} >pestchek.stdout".format(self.pst_name)) - except Exception as e: - self.logger.warn("error running pestchek:{0}".format(str(e))) - for line in open("pestchek.stdout"): - self.logger.statement("pestcheck:{0}".format(line.strip())) - os.chdir("..") - self.log("running pestchek on {0}".format(self.pst_name))
    - -
    [docs] def add_external(self): - """ add external (existing) template files and instrution files to the - Pst instance - - """ - if self.external_tpl_in_pairs is not None: - if not isinstance(self.external_tpl_in_pairs,list): - external_tpl_in_pairs = [self.external_tpl_in_pairs] - for tpl_file,in_file in self.external_tpl_in_pairs: - if not os.path.exists(tpl_file): - self.logger.lraise("couldn't find external tpl file:{0}".\ - format(tpl_file)) - self.logger.statement("external tpl:{0}".format(tpl_file)) - shutil.copy2(tpl_file,os.path.join(self.m.model_ws, - os.path.split(tpl_file)[-1])) - if os.path.exists(in_file): - shutil.copy2(in_file,os.path.join(self.m.model_ws, - os.path.split(in_file)[-1])) - - if self.external_ins_out_pairs is not None: - if not isinstance(self.external_ins_out_pairs,list): - external_ins_out_pairs = [self.external_ins_out_pairs] - for ins_file,out_file in self.external_ins_out_pairs: - if not os.path.exists(ins_file): - self.logger.lraise("couldn't find external ins file:{0}".\ - format(ins_file)) - self.logger.statement("external ins:{0}".format(ins_file)) - shutil.copy2(ins_file,os.path.join(self.m.model_ws, - os.path.split(ins_file)[-1])) - if os.path.exists(out_file): - shutil.copy2(out_file,os.path.join(self.m.model_ws, - os.path.split(out_file)[-1])) - self.logger.warn("obs listed in {0} will have values listed in {1}" - .format(ins_file,out_file)) - else: - self.logger.warn("obs listed in {0} will have generic values")
    - -
    [docs] def write_forward_run(self): - """ write the forward run script forward_run.py - - """ - with open(os.path.join(self.m.model_ws,self.forward_run_file),'w') as f: - f.write("import os\nimport numpy as np\nimport pandas as pd\nimport flopy\n") - f.write("import pyemu\n") - for tmp_file in self.tmp_files: - f.write("try:\n") - f.write(" os.remove('{0}')\n".format(tmp_file)) - f.write("except Exception as e:\n") - f.write(" print('error removing tmp file:{0}')\n".format(tmp_file)) - for line in self.frun_pre_lines: - f.write(line+'\n') - for line in self.frun_model_lines: - f.write(line+'\n') - for line in self.frun_post_lines: - f.write(line+'\n')
    - -
    [docs] def parse_k(self,k,vals): - """ parse the iterable from a property or boundary condition argument - - Parameters - ---------- - k : int or iterable int - the iterable - vals : iterable of ints - the acceptable values that k may contain - - Returns - ------- - k_vals : iterable of int - parsed k values - - """ - try: - k = int(k) - except: - pass - else: - assert k in vals,"k {0} not in vals".format(k) - return [k] - if k is None: - return vals - else: - try: - k_vals = vals[k] - except Exception as e: - raise Exception("error slicing vals with {0}:{1}". - format(k,str(e))) - return k_vals
    - -
    [docs] def parse_pakattr(self,pakattr): - """ parse package-iterable pairs from a property or boundary condition - argument - - Parameters - ---------- - pakattr : iterable len 2 - - - Returns - ------- - pak : flopy.PakBase - the flopy package from the model instance - attr : (varies) - the flopy attribute from pak. Could be Util2D, Util3D, - Transient2D, or MfList - attrname : (str) - the name of the attribute for MfList type. Only returned if - attr is MfList. For example, if attr is MfList and pak is - flopy.modflow.ModflowWel, then attrname can only be "flux" - - """ - - raw = pakattr.lower().split('.') - if len(raw) != 2: - self.logger.lraise("pakattr is wrong:{0}".format(pakattr)) - pakname = raw[0] - attrname = raw[1] - pak = self.m.get_package(pakname) - if pak is None: - if pakname == "extra": - self.logger.statement("'extra' pak detected:{0}".format(pakattr)) - ud = flopy.utils.Util3d(self.m,(self.m.nlay,self.m.nrow,self.m.ncol),np.float32,1.0,attrname) - return "extra",ud - - self.logger.lraise("pak {0} not found".format(pakname)) - if hasattr(pak,attrname): - attr = getattr(pak,attrname) - return pak,attr - elif hasattr(pak,"stress_period_data"): - dtype = pak.stress_period_data.dtype - if attrname not in dtype.names: - self.logger.lraise("attr {0} not found in dtype.names for {1}.stress_period_data".\ - format(attrname,pakname)) - attr = pak.stress_period_data - return pak,attr,attrname - # elif hasattr(pak,'hfb_data'): - # dtype = pak.hfb_data.dtype - # if attrname not in dtype.names: - # self.logger.lraise('attr {0} not found in dtypes.names for {1}.hfb_data. Thanks for playing.'.\ - # format(attrname,pakname)) - # attr = pak.hfb_data - # return pak, attr, attrname - else: - self.logger.lraise("unrecognized attr:{0}".format(attrname))
    - - -
    [docs] def setup_bc_pars(self): - tdf = self.setup_temporal_bc_pars() - sdf = self.setup_spatial_bc_pars() - if tdf is None and sdf is None: - return - os.chdir(self.m.model_ws) - try: - apply_bc_pars() - except Exception as e: - os.chdir("..") - self.logger.lraise("error test running apply_bc_pars():{0}".format(str(e))) - os.chdir('..') - line = "pyemu.helpers.apply_bc_pars()\n" - self.logger.statement("forward_run line:{0}".format(line)) - self.frun_pre_lines.append(line)
    - -
    [docs] def setup_temporal_bc_pars(self): - """ main entry point for setting up boundary condition multiplier - parameters - - """ - if len(self.temporal_bc_props) == 0: - return - self.log("processing temporal_bc_props") - # if not isinstance(self.bc_prop_dict,dict): - # self.logger.lraise("bc_prop_dict must be 'dict', not {0}". - # format(str(type(self.bc_prop_dict)))) - bc_filenames = [] - bc_cols = [] - bc_pak = [] - bc_k = [] - bc_dtype_names = [] - bc_parnme = [] - if len(self.temporal_bc_props) == 2: - if not isinstance(self.temporal_bc_props[0],list): - self.temporal_bc_props = [self.temporal_bc_props] - for pakattr,k_org in self.temporal_bc_props: - pak,attr,col = self.parse_pakattr(pakattr) - k_parse = self.parse_k(k_org,np.arange(self.m.nper)) - c = self.get_count(pakattr) - for k in k_parse: - bc_filenames.append(self.bc_helper(k,pak,attr,col)) - bc_cols.append(col) - pak_name = pak.name[0].lower() - bc_pak.append(pak_name) - bc_k.append(k) - bc_dtype_names.append(','.join(attr.dtype.names)) - - bc_parnme.append("{0}{1}_{2:03d}".format(pak_name,col,c)) - - df = pd.DataFrame({"filename":bc_filenames,"col":bc_cols, - "kper":bc_k,"pak":bc_pak, - "dtype_names":bc_dtype_names, - "parnme":bc_parnme}) - tds = pd.to_timedelta(np.cumsum(self.m.dis.perlen.array),unit='d') - dts = pd.to_datetime(self.m._start_datetime) + tds - df.loc[:,"datetime"] = df.kper.apply(lambda x: dts[x]) - df.loc[:,"timedelta"] = df.kper.apply(lambda x: tds[x]) - df.loc[:,"val"] = 1.0 - #df.loc[:,"kper"] = df.kper.apply(np.int) - #df.loc[:,"parnme"] = df.apply(lambda x: "{0}{1}_{2:03d}".format(x.pak,x.col,x.kper),axis=1) - df.loc[:,"tpl_str"] = df.parnme.apply(lambda x: "~ {0} ~".format(x)) - df.loc[:,"bc_org"] = self.bc_org - df.loc[:,"model_ext_path"] = self.m.external_path - df.loc[:,"pargp"] = df.parnme.apply(lambda x: x.split('_')[0]) - names = ["filename","dtype_names","bc_org","model_ext_path","col","kper","pak","val"] - df.loc[:,names].\ - to_csv(os.path.join(self.m.model_ws,"temporal_bc_pars.dat"),sep=' ') - df.loc[:,"val"] = df.tpl_str - tpl_name = os.path.join(self.m.model_ws,'temporal_bc_pars.dat.tpl') - #f_tpl = open(tpl_name,'w') - #f_tpl.write("ptf ~\n") - #f_tpl.flush() - # df.loc[:,names].to_csv(f_tpl,sep=' ',quotechar=' ') - #f_tpl.write("index ") - #f_tpl.write(df.loc[:,names].to_string(index_names=True)) - #f_tpl.close() - write_df_tpl(tpl_name,df.loc[:,names],sep=' ',index_label="index") - self.par_dfs["temporal_bc"] = df - - if self.temporal_bc_geostruct is None: - v = pyemu.geostats.ExpVario(contribution=1.0,a=180.0) # 180 correlation length - self.temporal_bc_geostruct = pyemu.geostats.GeoStruct(variograms=v) - self.log("processing temporal_bc_props") - return True
    - -
    [docs] def setup_spatial_bc_pars(self): - """helper to setup crazy numbers of well flux multipliers""" - if len(self.spatial_bc_props) == 0: - return - self.log("processing spatial_bc_props") - - bc_filenames = [] - bc_cols = [] - bc_pak = [] - bc_k = [] - bc_dtype_names = [] - bc_parnme = [] - if len(self.spatial_bc_props) == 2: - if not isinstance(self.spatial_bc_props[0], list): - self.spatial_bc_props = [self.spatial_bc_props] - for pakattr, k_org in self.spatial_bc_props: - pak, attr, col = self.parse_pakattr(pakattr) - k_parse = self.parse_k(k_org, np.arange(self.m.nlay)) - if len(k_parse) > 1: - self.logger.lraise("spatial_bc_pars error: each set of spatial bc pars can only be applied "+\ - "to a single layer (e.g. [wel.flux,0].\n"+\ - "You passed [{0},{1}], implying broadcasting to layers {2}". - format(pakattr,k_org,k_parse)) - # # horrible special case for HFB since it cannot vary over time - #if type(pak) != flopy.modflow.mfhfb.ModflowHfb: - for k in range(self.m.nper): - bc_filenames.append(self.bc_helper(k, pak, attr, col)) - bc_cols.append(col) - pak_name = pak.name[0].lower() - bc_pak.append(pak_name) - bc_k.append(k_parse[0]) - #bc_dtype_names.append(list(attr.dtype.names)) - bc_dtype_names.append(','.join(attr.dtype.names)) - # else: - # bc_filenames.append(self.bc_helper(k, pak, attr, col)) - # bc_cols.append(col) - # pak_name = pak.name[0].lower() - # bc_pak.append(pak_name) - # bc_k.append(k_parse[0]) - # bc_dtype_names.append(','.join(attr.dtype.names)) - - info_df = pd.DataFrame({"filename": bc_filenames, "col": bc_cols, - "k": bc_k, "pak": bc_pak, - "dtype_names": bc_dtype_names}) - info_df.loc[:,"bc_mlt"] = self.bc_mlt - info_df.loc[:,"bc_org"] = self.bc_org - info_df.loc[:,"model_ext_path"] = self.m.external_path - - # check that all files for a given package have the same number of entries - info_df.loc[:,"itmp"] = np.NaN - pak_dfs = {} - for pak in info_df.pak.unique(): - df_pak = info_df.loc[info_df.pak==pak,:] - itmp = [] - for filename in df_pak.filename: - names = df_pak.dtype_names.iloc[0].split(',') - - #mif pak != 'hfb6': - fdf = pd.read_csv(os.path.join(self.m.model_ws, filename), - delim_whitespace=True, header=None, names=names) - for c in ['k','i','j']: - fdf.loc[:,c] -= 1 - # else: - # # need to navigate the HFB file to skip both comments and header line - # skiprows = sum( - # [1 if i.strip().startswith('#') else 0 - # for i in open(os.path.join(self.m.model_ws, filename), 'r').readlines()]) + 1 - # fdf = pd.read_csv(os.path.join(self.m.model_ws, filename), - # delim_whitespace=True, header=None, names=names, skiprows=skiprows ).dropna() - # - # for c in ['k', 'irow1','icol1','irow2','icol2']: - # fdf.loc[:, c] -= 1 - - itmp.append(fdf.shape[0]) - pak_dfs[pak] = fdf - info_df.loc[info_df.pak==pak,"itmp"] = itmp - if np.unique(np.array(itmp)).shape[0] != 1: - info_df.to_csv("spatial_bc_trouble.csv") - self.logger.lraise("spatial_bc_pars() error: must have same number of "+\ - "entries for every stress period for {0}".format(pak)) - - # make the pak dfs have unique model indices - for pak,df in pak_dfs.items(): - #if pak != 'hfb6': - df.loc[:,"idx"] = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k,x.i,x.j),axis=1) - # else: - # df.loc[:, "idx"] = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}{2:04.0f}{2:04.0f}".format(x.k, x.irow1, x.icol1, - # x.irow2, x.icol2), axis=1) - if df.idx.unique().shape[0] != df.shape[0]: - self.logger.warn("duplicate entries in bc pak {0}...collapsing".format(pak)) - df.drop_duplicates(subset="idx",inplace=True) - df.index = df.idx - pak_dfs[pak] = df - - # write template files - find which cols are parameterized... - par_dfs = [] - for pak,df in pak_dfs.items(): - pak_df = info_df.loc[info_df.pak==pak,:] - # reset all non-index cols to 1.0 - for col in df.columns: - if col not in ['k','i','j','inode', 'irow1','icol1','irow2','icol2']: - df.loc[:,col] = 1.0 - in_file = os.path.join(self.bc_mlt,pak+".csv") - tpl_file = os.path.join(pak + ".csv.tpl") - # save an all "ones" mult df for testing - df.to_csv(os.path.join(self.m.model_ws,in_file), sep=' ') - parnme,pargp = [],[] - #if pak != 'hfb6': - x = df.apply(lambda x: self.m.sr.xcentergrid[int(x.i),int(x.j)],axis=1).values - y = df.apply(lambda x: self.m.sr.ycentergrid[int(x.i),int(x.j)],axis=1).values - # else: - # # note -- for HFB6, only row and col for node 1 - # x = df.apply(lambda x: self.m.sr.xcentergrid[int(x.irow1),int(x.icol1)],axis=1).values - # y = df.apply(lambda x: self.m.sr.ycentergrid[int(x.irow1),int(x.icol1)],axis=1).values - - for col in pak_df.col.unique(): - col_df = pak_df.loc[pak_df.col==col] - k_vals = col_df.k.unique() - npar = col_df.k.apply(lambda x: x in k_vals).shape[0] - if npar == 0: - continue - names = df.index.map(lambda x: "{0}{1}{2}".format(pak[0],col[0],x)) - - df.loc[:,col] = names.map(lambda x: "~ {0} ~".format(x)) - df.loc[df.k.apply(lambda x: x not in k_vals),col] = 1.0 - par_df = pd.DataFrame({"parnme": names,"x":x,"y":y,"k":df.k.values}, index=names) - par_df = par_df.loc[par_df.k.apply(lambda x: x in k_vals)] - if par_df.shape[0] == 0: - self.logger.lraise("no parameters found for spatial bc k,pak,attr {0}, {1}, {2}". - format(k_vals,pak,col)) - - par_df.loc[:,"pargp"] = df.k.apply(lambda x : "{0}{1}_k{2:02.0f}".format(pak,col,int(x))).values - par_df.loc[:,"tpl_file"] = tpl_file - par_df.loc[:,"in_file"] = in_file - par_dfs.append(par_df) - - - #with open(os.path.join(self.m.model_ws,tpl_file),'w') as f: - # f.write("ptf ~\n") - #f.flush() - #df.to_csv(f) - # f.write("index ") - # f.write(df.to_string(index_names=False)+'\n') - write_df_tpl(os.path.join(self.m.model_ws,tpl_file),df,sep=' ',index_label="index") - self.tpl_files.append(tpl_file) - self.in_files.append(in_file) - - par_df = pd.concat(par_dfs) - self.par_dfs["spatial_bc"] = par_df - info_df.to_csv(os.path.join(self.m.model_ws,"spatial_bc_pars.dat"),sep=' ') - if self.spatial_bc_geostruct is None: - dist = 10 * float(max(self.m.dis.delr.array.max(), - self.m.dis.delc.array.max())) - v = pyemu.geostats.ExpVario(contribution=1.0, a=dist) - self.spatial_bc_geostruct = pyemu.geostats.GeoStruct(variograms=v) - self.log("processing spatial_bc_props") - return True
    - - - # def setup_all_wells(self): - # """helper to setup crazy numbers of well flux multipliers""" - # self.log("setup all wells parameterization") - # if self.m.wel is None: - # self.logger.lraise("setup_all_wells() requires a wel pak") - # pak, attr, col = self.parse_pakattr("wel.flux") - # k_parse = self.parse_k(np.arange(self.m.nper), np.arange(self.m.nper)) - # bc_filenames = [] - # for k in k_parse: - # bc_filenames.append(self.bc_helper(k, pak, attr, col)) - # parnme = [] - # for bc_filename in bc_filenames: - # bc_filename = os.path.split(bc_filename)[-1] - # kper = int(bc_filename.split('.')[0].split('_')[1]) - # df = pd.read_csv(os.path.join(self.m.model_ws,self.bc_org,bc_filename), - # delim_whitespace=True,header=None,names=['l','r','c','flux']) - # mlt_file = os.path.join(self.m.model_ws, self.bc_mlt, bc_filename) - # df.loc[:,"flux"] = 1.0 - # df.to_csv(mlt_file,sep=' ',index=False,header=False) - # df.loc[:,"parnme"] = ["wf{0:04d}_{1:03d}".format(i,kper) for i in range(df.shape[0])] - # parnme.extend(list(df.parnme)) - # df.loc[:,"tpl_str"] = df.parnme.apply(lambda x: "~ {0} ~".format(x)) - # tpl_file = os.path.join(self.m.model_ws,bc_filename+".tpl") - # - # - # self.logger.statement("writing tpl file "+tpl_file) - # with open(tpl_file,'w') as f: - # f.write("ptf ~\n") - # f.write(df.loc[:,['l','r','c','tpl_str']].to_string(index=False,header=False)+'\n') - # self.tpl_files.append(os.path.split(tpl_file)[-1]) - # self.in_files.append(os.path.join(self.bc_mlt, bc_filename)) - # - # df = pd.DataFrame({"parnme":parnme}, index=parnme) - # df.loc[:,"parubnd"] = 1.2 - # df.loc[:,"parlbnd"] = 0.8 - # df.loc[:,"pargp"] = df.parnme.apply(lambda x: "wflux_{0}".format(x.split('_')[-1])) - # self.par_dfs["wel_flux"] = df - # self.frun_pre_lines.append("pyemu.helpers.apply_all_wells()") - # self.log("setup all wells parameterization") - - -
    [docs] def bc_helper(self,k,pak,attr,col): - """ helper to setup boundary condition multiplier parameters for a given - k, pak, attr set. - - Parameters - ---------- - k : int or iterable of int - the zero-based stress period indices - pak : flopy.PakBase= - the MODFLOW package - attr : MfList - the MfList instance - col : str - the column name in the MfList recarray to parameterize - - """ - # special case for horrible HFB6 exception - # if type(pak) == flopy.modflow.mfhfb.ModflowHfb: - # filename = pak.file_name[0] - # else: - filename = attr.get_filename(k) - filename_model = os.path.join(self.m.external_path,filename) - shutil.copy2(os.path.join(self.m.model_ws,filename_model), - os.path.join(self.m.model_ws,self.bc_org,filename)) - return filename_model
    - - -
    [docs] def setup_hds(self): - """ setup modflow head save file observations for given kper (zero-based - stress period index) and k (zero-based layer index) pairs using the - kperk argument. - - Note - ---- - this can setup a shit-ton of observations - - this is useful for dataworth analyses or for monitoring - water levels as forecasts - - - - """ - if self.hds_kperk is None or len(self.hds_kperk) == 0: - return - from .gw_utils import setup_hds_obs - # if len(self.hds_kperk) == 2: - # try: - # if len(self.hds_kperk[0] == 2): - # pass - # except: - # self.hds_kperk = [self.hds_kperk] - oc = self.m.get_package("OC") - if oc is None: - raise Exception("can't find OC package in model to setup hds grid obs") - if not oc.savehead: - raise Exception("OC not saving hds, can't setup grid obs") - hds_unit = oc.iuhead - hds_file = self.m.get_output(unit=hds_unit) - assert os.path.exists(os.path.join(self.org_model_ws,hds_file)),\ - "couldn't find existing hds file {0} in org_model_ws".format(hds_file) - shutil.copy2(os.path.join(self.org_model_ws,hds_file), - os.path.join(self.m.model_ws,hds_file)) - inact = None - if self.m.lpf is not None: - inact = self.m.lpf.hdry - elif self.m.upw is not None: - inact = self.m.upw.hdry - if inact is None: - skip = lambda x: np.NaN if x == self.m.bas6.hnoflo else x - else: - skip = lambda x: np.NaN if x == self.m.bas6.hnoflo or x == inact else x - print(self.hds_kperk) - frun_line, df = setup_hds_obs(os.path.join(self.m.model_ws,hds_file), - kperk_pairs=self.hds_kperk,skip=skip) - self.obs_dfs["hds"] = df - self.frun_post_lines.append("pyemu.gw_utils.apply_hds_obs('{0}')".format(hds_file)) - self.tmp_files.append(hds_file)
    - -
    [docs] def setup_smp(self): - """ setup observations from PEST-style SMP file pairs - - """ - if self.obssim_smp_pairs is None: - return - if len(self.obssim_smp_pairs) == 2: - if isinstance(self.obssim_smp_pairs[0],str): - self.obssim_smp_pairs = [self.obssim_smp_pairs] - for obs_smp,sim_smp in self.obssim_smp_pairs: - self.log("processing {0} and {1} smp files".format(obs_smp,sim_smp)) - if not os.path.exists(obs_smp): - self.logger.lraise("couldn't find obs smp: {0}".format(obs_smp)) - if not os.path.exists(sim_smp): - self.logger.lraise("couldn't find sim smp: {0}".format(sim_smp)) - new_obs_smp = os.path.join(self.m.model_ws, - os.path.split(obs_smp)[-1]) - shutil.copy2(obs_smp,new_obs_smp) - new_sim_smp = os.path.join(self.m.model_ws, - os.path.split(sim_smp)[-1]) - shutil.copy2(sim_smp,new_sim_smp) - pyemu.pst_utils.smp_to_ins(new_sim_smp)
    - -
    [docs] def setup_hob(self): - """ setup observations from the MODFLOW HOB package - - - """ - - if self.m.hob is None: - return - hob_out_unit = self.m.hob.iuhobsv - #hob_out_fname = os.path.join(self.m.model_ws,self.m.get_output_attribute(unit=hob_out_unit)) - hob_out_fname = os.path.join(self.org_model_ws,self.m.get_output_attribute(unit=hob_out_unit)) - - if not os.path.exists(hob_out_fname): - self.logger.warn("could not find hob out file: {0}...skipping".format(hob_out_fname)) - return - hob_df = pyemu.gw_utils.modflow_hob_to_instruction_file(hob_out_fname) - self.obs_dfs["hob"] = hob_df - self.tmp_files.append(os.path.split(hob_out_fname))
    - -
    [docs] def setup_hyd(self): - """ setup observations from the MODFLOW HYDMOD package - - - """ - if self.m.hyd is None: - return - org_hyd_out = os.path.join(self.org_model_ws,self.m.name+".hyd.bin") - if not os.path.exists(org_hyd_out): - self.logger.warn("can't find existing hyd out file:{0}...skipping". - format(org_hyd_out)) - return - new_hyd_out = os.path.join(self.m.model_ws,os.path.split(org_hyd_out)[-1]) - shutil.copy2(org_hyd_out,new_hyd_out) - df = pyemu.gw_utils.modflow_hydmod_to_instruction_file(new_hyd_out) - df.loc[:,"obgnme"] = df.obsnme.apply(lambda x: '_'.join(x.split('_')[:-1])) - line = "pyemu.gw_utils.modflow_read_hydmod_file('{0}')".\ - format(os.path.split(new_hyd_out)[-1]) - self.logger.statement("forward_run line: {0}".format(line)) - self.frun_post_lines.append(line) - self.obs_dfs["hyd"] = df - self.tmp_files.append(os.path.split(new_hyd_out)[-1])
    - -
    [docs] def setup_water_budget_obs(self): - """ setup observations from the MODFLOW list file for - volume and flux water buget information - - """ - if self.mflist_waterbudget: - org_listfile = os.path.join(self.org_model_ws,self.m.lst.file_name[0]) - if os.path.exists(org_listfile): - shutil.copy2(org_listfile,os.path.join(self.m.model_ws, - self.m.lst.file_name[0])) - else: - self.logger.warn("can't find existing list file:{0}...skipping". - format(org_listfile)) - return - list_file = os.path.join(self.m.model_ws,self.m.lst.file_name[0]) - flx_file = os.path.join(self.m.model_ws,"flux.dat") - vol_file = os.path.join(self.m.model_ws,"vol.dat") - df = pyemu.gw_utils.setup_mflist_budget_obs(list_file, - flx_filename=flx_file, - vol_filename=vol_file, - start_datetime=self.m.start_datetime) - if df is not None: - self.obs_dfs["wb"] = df - #line = "try:\n os.remove('{0}')\nexcept:\n pass".format(os.path.split(list_file)[-1]) - #self.logger.statement("forward_run line:{0}".format(line)) - #self.frun_pre_lines.append(line) - self.tmp_files.append(os.path.split(list_file)[-1]) - line = "pyemu.gw_utils.apply_mflist_budget_obs('{0}',flx_filename='{1}',vol_filename='{2}',start_datetime='{3}')".\ - format(os.path.split(list_file)[-1], - os.path.split(flx_file)[-1], - os.path.split(vol_file)[-1], - self.m.start_datetime) - self.logger.statement("forward_run line:{0}".format(line)) - self.frun_post_lines.append(line)
    - - -
    [docs]def apply_array_pars(arr_par_file="arr_pars.csv"): - """ a function to apply array-based multipler parameters. Used to implement - the parameterization constructed by PstFromFlopyModel during a forward run - - Parameters - ---------- - arr_par_file : str - path to csv file detailing parameter array multipliers - - Note - ---- - "arr_pars.csv" - is written by PstFromFlopy - - the function should be added to the forward_run.py script but can be called on any correctly formatted csv - - """ - df = pd.read_csv(arr_par_file) - # for fname in df.model_file: - # try: - # os.remove(fname) - # except: - # print("error removing mult array:{0}".format(fname)) - - if 'pp_file' in df.columns: - for pp_file,fac_file,mlt_file in zip(df.pp_file,df.fac_file,df.mlt_file): - if pd.isnull(pp_file): - continue - pyemu.geostats.fac2real(pp_file=pp_file,factors_file=fac_file, - out_file=mlt_file,lower_lim=1.0e-10) - - for model_file in df.model_file.unique(): - # find all mults that need to be applied to this array - df_mf = df.loc[df.model_file==model_file,:] - results = [] - org_file = df_mf.org_file.unique() - if org_file.shape[0] != 1: - raise Exception("wrong number of org_files for {0}". - format(model_file)) - org_arr = np.loadtxt(org_file[0]) - - for mlt in df_mf.mlt_file: - org_arr *= np.loadtxt(mlt) - if "upper_bound" in df.columns: - ub_vals = df_mf.upper_bound.value_counts().dropna().to_dict() - if len(ub_vals) == 0: - pass - elif len(ub_vals) > 1: - raise Exception("different upper bound values for {0}".format(org_file)) - else: - ub = list(ub_vals.keys())[0] - org_arr[org_arr>ub] = ub - if "lower_bound" in df.columns: - lb_vals = df_mf.lower_bound.value_counts().dropna().to_dict() - if len(lb_vals) == 0: - pass - elif len(lb_vals) > 1: - raise Exception("different lower bound values for {0}".format(org_file)) - else: - lb = list(lb_vals.keys())[0] - org_arr[org_arr < lb] = lb - - np.savetxt(model_file,org_arr,fmt="%15.6E",delimiter='')
    - -
    [docs]def apply_bc_pars(): - """ a function to apply boundary condition multiplier parameters. Used to implement - the parameterization constructed by PstFromFlopyModel during a forward run - - Note - ---- - requires "bc_pars.csv" - - should be added to the forward_run.py script - - """ - temp_file = "temporal_bc_pars.dat" - spat_file = "spatial_bc_pars.dat" - - temp_df,spat_df = None,None - if os.path.exists(temp_file): - temp_df = pd.read_csv(temp_file, delim_whitespace=True) - temp_df.loc[:,"split_filename"] = temp_df.filename.apply(lambda x: os.path.split(x)[-1]) - org_dir = temp_df.bc_org.iloc[0] - model_ext_path = temp_df.model_ext_path.iloc[0] - if os.path.exists(spat_file): - spat_df = pd.read_csv(spat_file, delim_whitespace=True) - spat_df.loc[:,"split_filename"] = spat_df.filename.apply(lambda x: os.path.split(x)[-1]) - mlt_dir = spat_df.bc_mlt.iloc[0] - org_dir = spat_df.bc_org.iloc[0] - model_ext_path = spat_df.model_ext_path.iloc[0] - if temp_df is None and spat_df is None: - raise Exception("apply_bc_pars() - no key dfs found, nothing to do...") - # load the spatial mult dfs - sp_mlts = {} - if spat_df is not None: - - for f in os.listdir(mlt_dir): - pak = f.split(".")[0].lower() - df = pd.read_csv(os.path.join(mlt_dir,f),index_col=0, delim_whitespace=True) - #if pak != 'hfb6': - df.index = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k,x.i,x.j),axis=1) - # else: - # df.index = df.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}{2:04.0f}{2:04.0f}".format(x.k, x.irow1, x.icol1, - # x.irow2, x.icol2), axis = 1) - if pak in sp_mlts.keys(): - raise Exception("duplicate multplier csv for pak {0}".format(pak)) - if df.shape[0] == 0: - raise Exception("empty dataframe for spatial bc file: {0}".format(f)) - sp_mlts[pak] = df - - org_files = os.listdir(org_dir) - #for fname in df.filename.unique(): - for fname in org_files: - # need to get the PAK name to handle stupid horrible expceptions for HFB... - # try: - # pakspat = sum([True if fname in i else False for i in spat_df.filename]) - # if pakspat: - # pak = spat_df.loc[spat_df.filename.str.contains(fname)].pak.values[0] - # else: - # pak = 'notHFB' - # except: - # pak = "notHFB" - - names = None - if temp_df is not None and fname in temp_df.split_filename.values: - temp_df_fname = temp_df.loc[temp_df.split_filename==fname,:] - if temp_df_fname.shape[0] > 0: - names = temp_df_fname.dtype_names.iloc[0].split(',') - if spat_df is not None and fname in spat_df.split_filename.values: - spat_df_fname = spat_df.loc[spat_df.split_filename == fname, :] - if spat_df_fname.shape[0] > 0: - names = spat_df_fname.dtype_names.iloc[0].split(',') - if names is not None: - - # if pak == 'hfb6': - # # ugh - need to yank out the header rows if HFB6 - # tmpfn = spat_df.loc[spat_df.pak == pak, :].filename.values[0] - # skiprows = sum( - # [1 if i.strip().startswith('#') else 0 - # for i in open(os.path.join(org_dir, tmpfn), 'r').readlines()]) + 1 - # headrows = ['{}\n'.format(i.strip()) for i in open(os.path.join(org_dir, tmpfn), 'r').readlines()[:skiprows]] - # df_list = pd.read_csv(os.path.join(org_dir, fname), - # delim_whitespace=True, header=None, names=names, skiprows=skiprows).dropna() - # df_list.loc[:, "idx"] = df_list.apply( - # lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}{2:04.0f}{2:04.0f}".format(x.k, x.irow1, x.icol1, - # x.irow2, x.icol2), axis=1) - # footrows = '{}\n'.format(open(os.path.join(org_dir, fname), 'r').readlines()[-1].strip()) - # else: - df_list = pd.read_csv(os.path.join(org_dir, fname), - delim_whitespace=True, header=None, names=names) - df_list.loc[:, "idx"] = df_list.apply(lambda x: "{0:02.0f}{1:04.0f}{2:04.0f}".format(x.k-1, x.i-1, x.j-1), axis=1) - - - df_list.index = df_list.idx - pak_name = fname.split('_')[0].lower() - if pak_name in sp_mlts: - mlt_df = sp_mlts[pak_name] - mlt_df_ri = mlt_df.reindex(df_list.index) - for col in df_list.columns: - if col in ["k","i","j","inode",'irow1','icol1','irow2','icol2','idx']: - continue - if col in mlt_df.columns: - # print(mlt_df.loc[mlt_df.index.duplicated(),:]) - # print(df_list.loc[df_list.index.duplicated(),:]) - df_list.loc[:,col] *= mlt_df_ri.loc[:,col].values - - if temp_df is not None and fname in temp_df.split_filename.values: - temp_df_fname = temp_df.loc[temp_df.split_filename == fname, :] - for col,val in zip(temp_df_fname.col,temp_df_fname.val): - df_list.loc[:,col] *= val - #fmts = {} - fmts = '' - for name in names: - if name in ["i","j","k","inode",'irow1','icol1','irow2','icol2']: - #fmts[name] = pyemu.pst_utils.IFMT - #fmts[name] = lambda x: " {0:>9.0f}".format(x) - fmts += " %9d" - else: - #fmts[name] = pyemu.pst_utils.FFMT - #fmts[name] = lambda x: " {0:>9G}".format(x) - fmts += " %9G" - # if pak == 'hfb6': - # np.savetxt(os.path.join(model_ext_path, fname), df_list.loc[:, names].as_matrix(), fmt=fmts, - # header=''.join(headrows), footer=footrows) - # else: - #np.savetxt(os.path.join(model_ext_path, fname), df_list.loc[:, names].as_matrix(), fmt=fmts) - np.savetxt(os.path.join(model_ext_path, fname), df_list.loc[:, names].values, fmt=fmts)
    - #with open(os.path.join(model_ext_path,fname),'w') as f: - # f.write(df_list.loc[:,names].to_string(header=False,index=False,formatters=fmts)+'\n') - #df_list.to_csv(os.path.join(model_ext_path,fname),index=False,header=False) - -# def apply_all_wells(): -# -# mlt_dir = "bc_mlt" -# org_dir = "bc_org" -# names = ["l","r","c","flux"] -# fmt = {"flux": pyemu.pst_utils.FFMT} -# for c in names[:-1]: -# fmt[c] = pyemu.pst_utils.IFMT -# if not os.path.exists(mlt_dir) or not os.path.exists(org_dir): -# return -# mlt_files = os.listdir(mlt_dir) -# for f in mlt_files: -# if not "wel" in f.lower(): -# continue -# org_file = os.path.join(org_dir,f) -# mlt_file = os.path.join(mlt_dir,f) -# df_org = pd.read_csv(org_file,header=None,delim_whitespace=True,names=names) -# df_mlt = pd.read_csv(mlt_file,header=None,delim_whitespace=True,names=names) -# assert df_org.shape == df_mlt.shape -# df_org.iloc[:,-1] *= df_mlt.iloc[:,-1] -# with open(f,'w') as fi: -# fi.write(df_org.to_string(index=False,header=False,formatters=fmt)+'\n') - -
    [docs]def apply_hfb_pars(): - """ a function to apply HFB multiplier parameters. Used to implement - the parameterization constructed by write_hfb_zone_multipliers_template() - - This is to account for the horrible HFB6 format that differs from other BCs making this a special case - - Note - ---- - requires "hfb_pars.csv" - - should be added to the forward_run.py script - """ - hfb_pars = pd.read_csv('hfb6_pars.csv') - - hfb_mults_contents = open(hfb_pars.mlt_file.values[0], 'r').readlines() - skiprows = sum([1 if i.strip().startswith('#') else 0 for i in hfb_mults_contents]) + 1 - header = hfb_mults_contents[:skiprows] - - # read in the multipliers - names = ['lay', 'irow1','icol1','irow2','icol2', 'hydchr'] - hfb_mults = pd.read_csv(hfb_pars.mlt_file.values[0], skiprows=skiprows, delim_whitespace=True, names=names).dropna() - for cn in names[:-1]: - hfb_mults[cn] = hfb_mults[cn].astype(np.int) - - # read in the original file - hfb_org = pd.read_csv(hfb_pars.org_file.values[0], skiprows=skiprows, delim_whitespace=True, names=names).dropna() - - # multiply it out - hfb_org.hydchr *= hfb_mults.hydchr - - # write the results - with open(hfb_pars.model_file.values[0], 'w') as ofp: - [ofp.write('{0}\n'.format(line.strip())) for line in header] - - hfb_org[['lay', 'irow1','icol1','irow2','icol2', 'hydchr']].to_csv(ofp, sep=' ', - header=None, index=None)
    - -
    [docs]def plot_flopy_par_ensemble(pst,pe,num_reals=None,model=None,fig_axes_generator=None, - pcolormesh_transform=None): - """function to plot ensemble of parameter values for a flopy/modflow model. Assumes - the FlopytoPst helper was used to setup the model and the forward run. - - Parameters - ---------- - pst : Pst instance - - pe : ParameterEnsemble instance - - num_reals : int - number of realizations to process. If None, all realizations are processed. - default is None - model : flopy.mbase - model instance used for masking inactive areas and also geo-locating plots. If None, - generic plots are made. Default is None. - fig_axes_generator : function - a function that returns a pyplot.figure and list of pyplot.axes instances for plots. - If None, a generic 8.5inX11in figure with 3 rows and 2 cols is used. - pcolormesh_transform : cartopy.csr.CRS - transform to map pcolormesh plot into correct location. Requires model arg - - - Note - ---- - Needs "arr_pars.csv" to run - - Todo - ---- - add better support for log vs nonlog- currently just logging everything - add support for cartopy basemap and stamen tiles - - """ - try: - import matplotlib.pyplot as plt - except Exception as e: - raise Exception("error import matplotlib: {0}".format(str(e))) - from matplotlib.backends.backend_pdf import PdfPages - assert os.path.exists("arr_pars.csv"),"couldn't find arr_pars.csv, can't continue" - df = pd.read_csv("arr_pars.csv") - if isinstance(pst,str): - pst = pyemu.Pst(pst) - if isinstance(pe,str): - pe = pd.read_csv(pe,index_col=0) - if num_reals is None: - num_reals = pe.shape[0] - arr_dict,arr_mx,arr_mn = {},{},{} - - islog = True - - ib,sr = None,None - if model is not None: - if isinstance(model,str): - model = flopy.modflow.Modflow.load(model,load_only=[],verbose=False,check=False) - if model.bas6 is not None: - ib = {k:model.bas6.ibound[k].array for k in range(model.nlay)} - sr = model.sr - if model.btn is not None: - raise NotImplementedError() - - for i in range(num_reals): - print(datetime.now(),"processing realization number {0} of {1}".format(i+1,num_reals)) - pst.parameter_data.loc[pe.columns,"parval1"] = pe.iloc[i,:] - pst.write_input_files() - apply_array_pars() - for model_file,k in zip(df.model_file,df.layer): - arr = np.loadtxt(model_file) - if islog: - arr = np.log10(arr) - - if ib is not None: - arr = np.ma.masked_where(ib[k]<1,arr) - arr = np.ma.masked_invalid(arr) - if model_file not in arr_dict.keys(): - arr_dict[model_file] = [] - arr_mx[model_file] = -1.0e+10 - arr_mn[model_file] = 1.0e+10 - arr_mx[model_file] = max(arr_mx[model_file],arr.max()) - arr_mn[model_file] = min(arr_mn[model_file],arr.min()) - - arr_dict[model_file].append(arr) - - - - - def _get_fig_and_axes(): - nrow, ncol = 3, 2 - fig = plt.figure(figsize=(8.5, 11)) - axes = [plt.subplot(nrow, ncol, ii + 1) for ii in range(nrow * ncol)] - [ax.set_xticklabels([]) for ax in axes] - [ax.set_yticklabels([]) for ax in axes] - return fig,axes - - if fig_axes_generator is None: - fig_axes_generator = _get_fig_and_axes - - for model_file,arrs in arr_dict.items(): - k = df.loc[df.model_file==model_file,"layer"].values[0] - print("plotting arrays for {0}".format(model_file)) - mx,mn = arr_mx[model_file],arr_mn[model_file] - with PdfPages(model_file+".pdf") as pdf: - fig,axes = fig_axes_generator() - ax_count = 0 - for i,arr in enumerate(arrs): - - if ax_count >= len(axes): - - pdf.savefig() - plt.close(fig) - fig, axes = fig_axes_generator() - ax_count = 0 - #ax = plt.subplot(111) - if sr is not None: - p = axes[ax_count].pcolormesh(sr.xcentergrid,sr.ycentergrid,arr,vmax=mx,vmin=mn, - transform=pcolormesh_transform) - else: - p = axes[ax_count].imshow(arr,vmax=mx,vmin=mn) - plt.colorbar(p,ax=axes[ax_count]) - if islog: - arr = 10.*arr - axes[ax_count].set_title("real index:{0}, max:{1:5.2E}, min:{2:5.2E}".\ - format(i,arr.max(),arr.min()),fontsize=6) - - ax_count += 1 - stack = np.array(arrs) - arr_dict = {} - for lab,arr in zip(["mean","std"],[np.nanmean(stack,axis=0),np.nanstd(stack,axis=0)]): - if ib is not None: - arr = np.ma.masked_where(ib[k]<1,arr) - arr = np.ma.masked_invalid(arr) - arr_dict[lab] = arr - if ax_count >= len(axes): - - pdf.savefig() - plt.close(fig) - fig, axes = fig_axes_generator() - ax_count = 0 - if sr is not None: - p = axes[ax_count].pcolormesh(sr.xcentergrid,sr.ycentergrid,arr, - transform=pcolormesh_transform) - else: - p = axes[ax_count].imshow(arr) - plt.colorbar(p,ax=axes[ax_count]) - axes[ax_count].set_title("{0}, max:{1:5.2E}, min:{2:5.2E}".format(lab,arr.max(),arr.min()),fontsize=6) - ax_count += 1 - pdf.savefig() - plt.close("all") - if sr is not None: - for i,arr in enumerate(arrs): - arr_dict[str(i)] = arr - flopy.export.shapefile_utils.write_grid_shapefile(model_file+".shp",sr,array_dict=arr_dict) - - return
    - - -def _istextfile(filename, blocksize=512): - - - """ - Function found from: - https://eli.thegreenplace.net/2011/10/19/perls-guess-if-file-is-text-or-binary-implemented-in-python - - Returns True if file is most likely a text file - Returns False if file is most likely a binary file - - Uses heuristics to guess whether the given file is text or binary, - by reading a single block of bytes from the file. - If more than 30% of the chars in the block are non-text, or there - are NUL ('\x00') bytes in the block, assume this is a binary file. - """ - - import sys - PY3 = sys.version_info[0] == 3 - - # A function that takes an integer in the 8-bit range and returns - # a single-character byte object in py3 / a single-character string - # in py2. - # - int2byte = (lambda x: bytes((x,))) if PY3 else chr - - _text_characters = ( - b''.join(int2byte(i) for i in range(32, 127)) + - b'\n\r\t\f\b') - block = open(filename,'rb').read(blocksize) - if b'\x00' in block: - # Files with null bytes are binary - return False - elif not block: - # An empty file is considered a valid text file - return True - - # Use translate's 'deletechars' argument to efficiently remove all - # occurrences of _text_characters from the block - nontext = block.translate(None, _text_characters) - return float(len(nontext)) / len(block) <= 0.30 - - - -
    [docs]def plot_summary_distributions(df,ax=None,label_post=False,label_prior=False, - subplots=False,figsize=(11,8.5),pt_color='b'): - """ helper function to plot gaussian distrbutions from prior and posterior - means and standard deviations - - Parameters - ---------- - df : pandas.DataFrame - a dataframe and csv file. Must have columns named: - 'prior_mean','prior_stdev','post_mean','post_stdev'. If loaded - from a csv file, column 0 is assumed to tbe the index - ax: matplotlib.pyplot.axis - If None, and not subplots, then one is created - and all distributions are plotted on a single plot - label_post: bool - flag to add text labels to the peak of the posterior - label_prior: bool - flag to add text labels to the peak of the prior - subplots: (boolean) - flag to use subplots. If True, then 6 axes per page - are used and a single prior and posterior is plotted on each - figsize: tuple - matplotlib figure size - - Returns - ------- - figs : list - list of figures - axes : list - list of axes - - Note - ---- - This is useful for demystifying FOSM results - - if subplots is False, a single axis is returned - - Example - ------- - ``>>>import matplotlib.pyplot as plt`` - - ``>>>import pyemu`` - - ``>>>pyemu.helpers.plot_summary_distributions("pest.par.usum.csv")`` - - ``>>>plt.show()`` - """ - warnings.warn("pyemu.helpers.plot_summary_distributions() has moved to plot_utils",PyemuWarning) - from pyemu import plot_utils - return plot_utils.plot_summary_distributions(df=df,ax=ax,label_post=label_post, - label_prior=label_prior,subplots=subplots, - figsize=figsize,pt_color=pt_color)
    - - -
    [docs]def gaussian_distribution(mean, stdev, num_pts=50): - """ get an x and y numpy.ndarray that spans the +/- 4 - standard deviation range of a gaussian distribution with - a given mean and standard deviation. useful for plotting - - Parameters - ---------- - mean : float - the mean of the distribution - stdev : float - the standard deviation of the distribution - num_pts : int - the number of points in the returned ndarrays. - Default is 50 - - Returns - ------- - x : numpy.ndarray - the x-values of the distribution - y : numpy.ndarray - the y-values of the distribution - - """ - warnings.warn("pyemu.helpers.gaussian_distribution() has moved to plot_utils",PyemuWarning) - from pyemu import plot_utils - return plot_utils.gaussian_distribution(mean=mean,stdev=stdev,num_pts=num_pts)
    - - -
    [docs]def build_jac_test_csv(pst,num_steps,par_names=None,forward=True): - """ build a dataframe of jactest inputs for use with sweep - - Parameters - ---------- - pst : pyemu.Pst - - num_steps : int - number of pertubation steps for each parameter - par_names : list - names of pars to test. If None, all adjustable pars are used - Default is None - forward : bool - flag to start with forward pertubations. Default is True - - Returns - ------- - df : pandas.DataFrame - the index of the dataframe is par name and the parval used. - - """ - if isinstance(pst,str): - pst = pyemu.Pst(pst) - #pst.add_transform_columns() - pst.build_increments() - incr = pst.parameter_data.increment.to_dict() - irow = 0 - par = pst.parameter_data - if par_names is None: - par_names = pst.adj_par_names - total_runs = num_steps * len(par_names) + 1 - idx = ["base"] - for par_name in par_names: - idx.extend(["{0}_{1}".format(par_name,i) for i in range(num_steps)]) - df = pd.DataFrame(index=idx, columns=pst.par_names) - li = par.partrans == "log" - lbnd = par.parlbnd.copy() - ubnd = par.parubnd.copy() - lbnd.loc[li] = lbnd.loc[li].apply(np.log10) - ubnd.loc[li] = ubnd.loc[li].apply(np.log10) - lbnd = lbnd.to_dict() - ubnd = ubnd.to_dict() - - org_vals = par.parval1.copy() - org_vals.loc[li] = org_vals.loc[li].apply(np.log10) - if forward: - sign = 1.0 - else: - sign = -1.0 - - # base case goes in as first row, no perturbations - df.loc["base",pst.par_names] = par.parval1.copy() - irow = 1 - full_names = ["base"] - for jcol, par_name in enumerate(par_names): - org_val = org_vals.loc[par_name] - last_val = org_val - for step in range(num_steps): - vals = org_vals.copy() - i = incr[par_name] - - - val = last_val + (sign * incr[par_name]) - if val > ubnd[par_name]: - sign = -1.0 - val = org_val + (sign * incr[par_name]) - if val < lbnd[par_name]: - raise Exception("parameter {0} went out of bounds". - format(par_name)) - elif val < lbnd[par_name]: - sign = 1.0 - val = org_val + (sign * incr[par_name]) - if val > ubnd[par_name]: - raise Exception("parameter {0} went out of bounds". - format(par_name)) - - vals.loc[par_name] = val - vals.loc[li] = 10**vals.loc[li] - df.loc[idx[irow],pst.par_names] = vals - full_names.append("{0}_{1:<15.6E}".format(par_name,vals.loc[par_name]).strip()) - - irow += 1 - last_val = val - df.index = full_names - return df
    - - -
    [docs]def write_df_tpl(filename,df,sep=',',tpl_marker='~',**kwargs): - """function write a pandas dataframe to a template file. - Parameters - ---------- - filename : str - template filename - df : pandas.DataFrame - dataframe to write - sep : char - separate to pass to df.to_csv(). default is ',' - tpl_marker : char - template file marker. default is '~' - kwargs : dict - additional keyword args to pass to df.to_csv() - - Returns - ------- - None - - Note - ---- - If you don't use this function, make sure that you flush the - file handle before df.to_csv() and you pass mode='a' to to_csv() - - """ - with open(filename,'w') as f: - f.write("ptf {0}\n".format(tpl_marker)) - f.flush() - df.to_csv(f,sep=sep,mode='a',**kwargs)
    - - - - - - - - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/utils/optimization.html b/docs/_build/html/_modules/pyemu/utils/optimization.html deleted file mode 100644 index 462f37046..000000000 --- a/docs/_build/html/_modules/pyemu/utils/optimization.html +++ /dev/null @@ -1,487 +0,0 @@ - - - - - - - - pyemu.utils.optimization — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.utils.optimization

    -from __future__ import print_function, division
    -import os, sys
    -import numpy as np
    -import pandas as pd
    -from pyemu import Matrix,Pst,Schur,Cov
    -
    -OPERATOR_WORDS = ["l","g","n","e"]
    -OPERATOR_SYMBOLS = ["<=",">=","=","="]
    -
    -# self.prior_information = pd.DataFrame({"pilbl": pilbl,
    -#                                        "equation": equation,
    -#                                        "weight": weight,
    -#                                        "obgnme": obgnme})
    -
    -
    [docs]def add_pi_obj_func(pst,obj_func_dict=None,out_pst_name=None): - if not isinstance(pst,Pst): - pst = Pst(pst) - if obj_func_dict is None: - obj_func_dict = {name:1.0 for name in pst.adj_par_names} - pi_equation = '' - for name,coef in obj_func_dict.items(): - assert(name in pst.adj_par_names),"obj func component not in adjustable pars:"+name - if coef < 0.0: - pi_equation += ' - {0}*{1}'.format(coef,name) - else: - pi_equation += ' + {0}*{1}'.format(coef, name) - pi_equation += ' = 0.0' - pilbl = "pi_obj_func" - pi_df = pd.DataFrame({"pilbl":pilbl,"equation":pi_equation,"weight":0.0,"obgnme":pilbl},index=[pilbl]) - - if pst.prior_information.shape[0] == 0: - pst.prior_information = pi_df - else: - assert pilbl not in pst.prior_information.index - # append by enlargement - pst.prior_information.loc[pilbl,:] = pi_df.loc[pilbl,:] - - if out_pst_name is not None: - pst.write(out_pst_name) - - return pst
    - - -# def get_added_obs_importance(pst,obslist_dict=None,base_obslist=None, -# reset_zero_weight=1.0): -# """get a dataframe fo the objective function -# as a results of added some observations -# Parameters -# ---------- -# obslist_dict (dict of list of str) : groups of observations -# that are to be treated as added the implied calibration. key values become -# row labels in result dataframe. If None, then test every obs -# base_obslist (list of str) : observation names to treat as -# the "existing" observations. The values of obslist_dict -# will be added to this list. If None, then each list in the -# values of obslist_dict will be treated as an individual -# calibration dataset -# reset_zero_weight : (bool or float) a flag to reset observations -# with zero weight in either obslist_dict or base_obslist. -# If the value of reset_zero_weights can be cast to a float, -# then that value will be assigned to zero weight obs. Otherwise, -# zero weight obs will be given a weight of 1.0 -# Returns -# ------- -# dataframe[obslist_dict.keys(),(forecast_name,post) -# multiindex dataframe of Schur's complement results for each -# group of observations in obslist_dict values. -# Note: -# ---- -# all observations listed in obslist_dict and base_obslist with zero -# weights will be dropped unless reset_zero_weight is set -# """ -# -# if not isinstance(pst,Pst): -# pst = Pst(pst) -# assert "hotstart_resfile" in pst.pestpp_options.keys() -# assert "opt_skip_final" in pst.pestpp_options.keys() -# assert "base_jacobian" in pst.pestpp_options.keys() -# assert "opt_risk" in pst.pestpp_options.keys() -# assert pst.pestpp_options["opt_risk"] != 0.5 -# assert pst.control_data.noptmax == 1 -# -# obscov = Cov.from_observation_data(pst) -# -# if obslist_dict is not None: -# if type(obslist_dict) == list: -# obslist_dict = dict(zip(obslist_dict,obslist_dict)) -# -# try: -# weight = float(reset_zero_weight) -# except: -# weight = 1.0 -# -# if obslist_dict is None: -# -# zero_weight_names = [n for n,w in zip(pst.observation_data.obsnme, -# pst.observation_data.weight) -# if w == 0.0] -# obslist_dict = dict(zip(zero_weight_names,zero_weight_names)) -# names = ["base"] -# -# results = [get_obj_func(pst)] -# #print(len(pst.nnz_obs_names)) -# for case_name,obslist in obslist_dict.items(): -# names.append(case_name) -# case_pst = pst.get() -# case_pst.observation_data.loc[obslist,"weight"] = weight -# #print(len(case_pst.nnz_obs_names)) -# results.append(get_obj_func(case_pst)) -# -# -# df = pd.DataFrame(results,index=names) -# return df -# -# -# def get_obj_func(pst): -# pst_name = "temp_" + pst.filename -# pst.write(pst_name) -# print(pst.template_files) -# os.system("{0} {1}".format("pestpp-opt",pst_name)) -# rec_file = pst_name[:-4]+".rec" -# with open(rec_file) as f: -# for line in f: -# if "iteration 1 objective function value" in line: -# val = float(line.strip().split()[-2]) -# return val -# raise Exception("unable to find objective function in {0}".\ -# format(rec_file)) -# -# -# def to_mps(jco,obj_func=None,obs_constraint_sense=None,pst=None, -# decision_var_names=None,mps_filename=None, -# risk=0.5): -# """helper utility to write an mps file from pest-style -# jacobian matrix. Requires corresponding pest control -# file. -# -# Parameters -# jco : pyemu.Matrix or str (filename of matrix) -# obj_func : optional. If None, an obs group must exist -# named 'n' and must have one one member. Can be a str, which -# is the name of an observation to treat as the objective function -# or can be a dict, which is keyed on decision var names and valued -# with objective function coeffs. -# obs_constraint_sense : optional. If None, obs groups are sought that -# have names "l","g", or "e" - members of these groups are treated -# as constraints. Otherwise, must be a dict keyed on constraint -# (obs) names with values of "l","g", or "e". -# pst : optional. If None, a pest control file is sought with -# filename <case>.pst. Otherwise, must be a pyemu.Pst instance or -# a filename of a pest control file. The control must have an -# associated .res or .rei file - this is needed for the RHS of the -# constraints. -# decision_var_names: optional. If None, all parameters are treated as -# decision vars. Otherwise, must be a list of str of parameter names -# to use as decision vars -# mps_filename : optional. If None, then <case>.mps is written. -# Otherwise, must be a str. -# risk : float -# the level of risk tolerance/aversion in the chance constraints. -# Values other then 0.50 require at least one parameter (non decision -# var) in the jco. Ranges from 0.0,1.0 -# """ -# -# #if jco arg is a string, load a jco from binary -# if isinstance(jco,str): -# pst_name = jco.lower().replace('.jcb',".pst").replace(".jco",".pst") -# jco = Matrix.from_binary(jco) -# assert isinstance(jco,Matrix) -# -# # try to find a pst -# if pst is None: -# if os.path.exists(pst_name): -# pst = Pst(pst_name) -# else: -# raise Exception("could not find pst file {0} and pst argument is None, a ".format(pst_name) +\ -# "pst instance is required for setting decision variable bound constraints") -# else: -# assert len(set(jco.row_names).difference(pst.observation_data.index)) == 0 -# assert len(set(jco.col_names).difference(pst.parameter_data.index)) == 0 -# -# #make sure the pst has an associate res -# assert pst.res is not None," could find a residuals file (.res or .rei) for" +\ -# " for control file {0}".format(pst.filename) -# -# #if no decision_var_names where passed, use all columns in the jco -# if decision_var_names is None: -# decision_var_names = jco.col_names -# -# #otherwise, do some error checking and processing -# else: -# if not isinstance(decision_var_names,list): -# decision_var_names = [decision_var_names] -# for i,dv in enumerate(decision_var_names): -# dv = dv.lower() -# decision_var_names[i] = dv -# assert dv in jco.col_names,"decision var {0} not in jco column names".format(dv) -# assert dv in pst.parameter_data.index,"decision var {0} not in pst parameter names".format(dv) -# -# #if no obs_constraint_sense, try to build one from the obs group info -# if obs_constraint_sense is None: -# const_groups = [grp for grp in pst.obs_groups if grp.lower() in OPERATOR_WORDS] -# if len(const_groups) == 0: -# raise Exception("to_mps(): obs_constraint_sense is None and no "+\ -# "obseravtion groups in {0}".format(','.join(pst.obs_groups))) -# obs_constraint_sense = {} -# obs_groups = pst.observation_data.groupby(pst.observation_data.obgnme).groups -# for og,obs_names in obs_groups.items(): -# if og == 'n': -# continue -# if og in const_groups: -# for oname in obs_names: -# obs_constraint_sense[oname] = og -# -# assert isinstance(obs_constraint_sense,dict) -# assert len(obs_constraint_sense) > 0,"no obs_constraints..." -# -# #build up a dict of (in)equality operators for the constraints -# operators = {} -# for obs_name,operator in obs_constraint_sense.items(): -# obs_name = obs_name.lower() -# assert obs_name in pst.obs_names,"obs constraint {0} not in pst observation names" -# assert obs_name in pst.res.name," obs constraint {0} not in pst.res names" -# assert obs_name in jco.row_names,"obs constraint {0} not in jco row names".format(obs_name) -# if operator.lower() not in OPERATOR_WORDS: -# if operator not in OPERATOR_SYMBOLS: -# raise Exception("operator {0} not in [{1}] or [{2}]".\ -# format(operator,','.join(OPERATOR_WORDS),','\ -# .join(OPERATOR_SYMBOLS))) -# op = OPERATOR_WORDS[OPERATOR_SYMBOLS.index(operator)] -# else: -# op = operator.lower() -# operators[obs_name] = op -# obs_constraint_sense[obs_name.lower()] = obs_constraint_sense.\ -# pop(obs_name) -# -# #build a list of constaint names in order WRT jco row order -# # order_obs_constraints = [name for name in jco.row_names if name in -# # obs_constraint_sense] -# -# order_obs_constraints = list(obs_constraint_sense.keys()) -# order_obs_constraints.sort() -# -# #build a list of decision var names in order WRT jco col order -# #order_dec_var = [name for name in jco.col_names if name in -# # decision_var_names] -# -# order_dec_var = list(decision_var_names) -# order_dec_var.sort() -# -# #shorten constraint names if needed -# new_const_count = 0 -# new_constraint_names = {} -# for name in order_obs_constraints: -# if len(name) > 8: -# new_name = name[:7]+"{0}".format(new_const_count) -# print("to_mps(): shortening constraint name {0} to {1}\n".format(name,new_name)) -# new_constraint_names[name] = new_name -# new_const_count += 1 -# else: -# new_constraint_names[name] = name -# -# #shorten decision var names if needed -# new_dec_count = 0 -# new_decision_names = {} -# for name in order_dec_var: -# if len(name) > 8: -# new_name = name[:7]+"{0}".format(new_dec_count) -# print("to_mps(): shortening decision var name {0} to {1}\n".format(name,new_name)) -# new_decision_names[name] = new_name -# new_dec_count += 1 -# else: -# new_decision_names[name] = name -# -# # if no obj_func, try to make one -# if obj_func is None: -# # look for an obs group named 'n' with a single member -# og = pst.obs_groups -# if 'n' not in pst.obs_groups: -# raise Exception("to_mps(): obj_func is None but no "+\ -# "obs group named 'n'") -# grps = pst.observation_data.groupby(pst.observation_data.obgnme).groups -# assert len(grps["n"]) == 1,"to_mps(): 'n' obj_func group has more " +\ -# " than one member, mps only support one objf " -# obj_name = grps['n'][0] -# obj_iidx = jco.row_names.index(obj_name) -# obj = {} -# for name in order_dec_var: -# jco_jidx = jco.col_names.index(name) -# obj[name] = jco.x[obj_iidx,jco_jidx] -# -# #otherwise, parse what was passed -# elif isinstance(obj_func,str): -# obj_func = obj_func.lower() -# assert obj_func in jco.row_names,\ -# "obj_func {0} not in jco.row_names".format(obj_func) -# assert obj_func in pst.observation_data.obsnme,\ -# "obj_func {0} not in pst observations".format(obj_func) -# -# obj_iidx = jco.row_names.index(obj_func) -# obj = {} -# for name in order_dec_var: -# jco_jidx = jco.col_names.index(name) -# obj[name] = jco.x[obj_iidx,jco_jidx] -# obj_name = str(obj_func) -# -# elif isinstance(obj_func,dict): -# obj = {} -# for name,value in obj_func.items(): -# assert name in jco.col_names,"to_mps(): obj_func key "+\ -# "{0} not ".format(name) +\ -# "in jco col names" -# obj[name] = float(value) -# obj_name = "obj_func" -# else: -# raise NotImplementedError("unsupported obj_func arg type {0}".format(\ -# type(obj_func))) -# -# if risk != 0.5: -# try: -# from scipy.special import erfinv -# except Exception as e: -# raise Exception("to_mps() error importing erfinv from scipy.special: "+\ -# "{0}".format(str(e))) -# -# par_names = [name for name in jco.col_names if name not in decision_var_names] -# if len(par_names) == 0: -# raise Exception("to_mps() error: risk != 0.5, but no "+\ -# "non-decision vars parameters ") -# unc_jco = jco.get(col_names=par_names) -# unc_pst = pst.get(par_names=par_names) -# sc = Schur(jco=unc_jco,pst=unc_pst,forecasts=order_obs_constraints) -# constraint_std = sc.get_forecast_summary().loc[:,"post_var"].apply(np.sqrt) -# rhs = {} -# -# # the probit value for a given risk...using the inverse -# # error function -# probit_val = np.sqrt(2.0) * erfinv((2.0 * risk) - 1.0) -# for name in order_obs_constraints: -# mu = unc_pst.res.loc[name,"residual"] -# std = constraint_std.loc[name] -# #if this is a less than constraint, then we want -# # to subtract -# if operators[name] == 'l': -# prob_val = mu - (probit_val * std) -# #if this is a greater than constraint, then we want -# # to add -# elif operators[name] == "g": -# prob_val = mu + (probit_val * std) -# else: -# raise NotImplementedError("chance constraints only " +\ -# "implemented for 'l' or 'g' " +\ -# "type constraints, not " +\ -# "{0}".format(operators[name])) -# rhs[name] = prob_val -# else: -# rhs = {n:pst.res.loc[n,"residual"] for n in order_obs_constraints} -# -# if mps_filename is None: -# mps_filename = pst.filename.replace(".pst",".mps") -# -# with open(mps_filename,'w') as f: -# f.write("NAME {0}\n".format("pest_opt")) -# f.write("ROWS\n") -# for name in order_obs_constraints: -# f.write(" {0} {1}\n".format(operators[name], -# new_constraint_names[name])) -# f.write(" {0} {1}\n".format('n',obj_name)) -# -# f.write("COLUMNS\n") -# for dname in order_dec_var: -# jco_jidx = jco.col_names.index(dname) -# for cname in order_obs_constraints: -# jco_iidx = jco.row_names.index(cname) -# v = jco.x[jco_iidx,jco_jidx] -# f.write(" {0:8} {1:8} {2:10G}\n".\ -# format(new_decision_names[dname], -# new_constraint_names[cname], -# v)) -# # f.write(" {0:8} {1:8} {2:10G}\n".\ -# # format(new_decision_names[dname], -# # obj_name,pst.parameter_data.loc[dname,"parval1"])) -# f.write(" {0:8} {1:8} {2:10G}\n".\ -# format(new_decision_names[dname], -# obj_name,obj_func[dname])) -# -# -# -# -# f.write("RHS\n") -# for iname,name in enumerate(order_obs_constraints): -# f.write(" {0:8} {1:8} {2:10G}\n". -# format("rhs",new_constraint_names[name], -# rhs[name])) -# f.write("BOUNDS\n") -# for name in order_dec_var: -# up,lw = pst.parameter_data.loc[name,"parubnd"],\ -# pst.parameter_data.loc[name,"parlbnd"] -# f.write(" {0:2} {1:8} {2:8} {3:10G}\n".\ -# format("UP","BOUND",name,up)) -# f.write(" {0:2} {1:8} {2:8} {3:10G}\n".\ -# format("LO","BOUND",name,lw)) -# f.write("ENDATA\n") - - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/pyemu/utils/pp_utils.html b/docs/_build/html/_modules/pyemu/utils/pp_utils.html deleted file mode 100644 index eb03e67be..000000000 --- a/docs/_build/html/_modules/pyemu/utils/pp_utils.html +++ /dev/null @@ -1,470 +0,0 @@ - - - - - - - - pyemu.utils.pp_utils — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -

    Source code for pyemu.utils.pp_utils

    -import os
    -import copy
    -import numpy as np
    -import pandas as pd
    -pd.options.display.max_colwidth = 100
    -from pyemu.pst.pst_utils import SFMT,IFMT,FFMT,pst_config
    -from pyemu.utils.helpers import run
    -from ..pyemu_warnings import PyemuWarning
    -PP_FMT = {"name": SFMT, "x": FFMT, "y": FFMT, "zone": IFMT, "tpl": SFMT,
    -          "parval1": FFMT}
    -PP_NAMES = ["name","x","y","zone","parval1"]
    -
    -
    -
    [docs]def setup_pilotpoints_grid(ml=None,sr=None,ibound=None,prefix_dict=None, - every_n_cell=4, - use_ibound_zones=False, - pp_dir='.',tpl_dir='.', - shapename="pp.shp"): - """setup grid-based pilot points. Uses the ibound to determine - where to set pilot points. pilot points are given generic ``pp_`` - names unless ``prefix_dict`` is passed. Write template files and a - shapefile as well...hopefully this is useful to someone... - - Parameters - ---------- - ml : flopy.mbase - a flopy mbase dervied type. If None, sr must not be None. - sr : flopy.utils.reference.SpatialReference - a spatial reference use to locate the model grid in space. If None, - ml must not be None. Default is None - ibound : numpy.ndarray - the modflow ibound integer array. Used to set pilot points only in active areas (!=0). - If None and ml is None, then pilot points are set in all rows and columns according to - every_n_cell. Default is None. - prefix_dict : dict - a dictionary of layer index, pilot point parameter prefixes. - Example : ``{0:["hk_"],1:["vka_"]}`` would setup pilot points with - the prefix ``"hk"`` for model layers 1 and ``"vka"`` for model layer 2 (zero based). - If None, a generic set of pilot points with the "pp" prefix are setup - for a generic nrowXncol grid. Default is None. - use_ibound_zones : bool - a flag to use the greater-than-zero values in the ibound as pilot point zones. If False,ibound - values greater than zero are treated as a single zone. Default is False. - pp_dir : str - directory to write pilot point files to. Default is '.' - tpl_dir : str - directory to write pilot point template file to. Default is '.' - shapename : str - name of shapefile to write that contains pilot point information. Default is "pp.shp" - - Returns - ------- - pp_df : pandas.DataFrame - a dataframe summarizing pilot point information (same information - written to shapename - Example - ------- - ``>>>import flopy`` - - ``>>>from pyemu.utils import setup_pilotpoints_grid`` - - ``>>>m = flopy.modflow.Modfow.load("mymodel.nam")`` - - ``>>>setup_pilotpoints_grid(m,prefix_dict={0:['hk_'],1:['vka_']},`` - - ``>>> every_n_cell=3,shapename='layer1_pp.shp')`` - """ - - import flopy - - if ml is not None: - assert isinstance(ml,flopy.modflow.Modflow) - sr = ml.sr - if ibound is None: - ibound = ml.bas6.ibound.array - else: - assert sr is not None,"if 'ml' is not passed, 'sr' must be passed" - if ibound is None: - - print("ibound not passed, using array of ones") - ibound = {k:np.ones((sr.nrow,sr.ncol)) for k in prefix_dict.keys()} - #assert ibound is not None,"if 'ml' is not pass, 'ibound' must be passed" - - try: - xcentergrid = sr.xcentergrid - ycentergrid = sr.ycentergrid - except Exception as e: - raise Exception("error getting xcentergrid and/or ycentergrid from 'sr':{0}".\ - format(str(e))) - start = int(float(every_n_cell) / 2.0) - - #build a generic prefix_dict - if prefix_dict is None: - prefix_dict = {k:["pp_{0:02d}_".format(k)] for k in range(ml.nlay)} - - #check prefix_dict - for k, prefix in prefix_dict.items(): - assert k < len(ibound),"layer index {0} > nlay {1}".format(k,len(ibound)) - if not isinstance(prefix,list): - prefix_dict[k] = [prefix] - - #try: - #ibound = ml.bas6.ibound.array - #except Exception as e: - # raise Exception("error getting model.bas6.ibound:{0}".format(str(e))) - par_info = [] - pp_files,tpl_files = [],[] - pp_names = copy.copy(PP_NAMES) - pp_names.extend(["k","i","j"]) - for k in range(len(ibound)): - pp_df = None - ib = ibound[k] - assert ib.shape == xcentergrid.shape,"ib.shape != xcentergrid.shape for k {0}".\ - format(k) - pp_count = 0 - #skip this layer if not in prefix_dict - if k not in prefix_dict.keys(): - continue - #cycle through rows and cols - for i in range(start,ib.shape[0]-start,every_n_cell): - for j in range(start,ib.shape[1]-start,every_n_cell): - # skip if this is an inactive cell - if ib[i,j] == 0: - continue - - # get the attributes we need - x = xcentergrid[i,j] - y = ycentergrid[i,j] - name = "pp_{0:04d}".format(pp_count) - parval1 = 1.0 - - #decide what to use as the zone - zone = 1 - if use_ibound_zones: - zone = ib[i,j] - #stick this pilot point into a dataframe container - - if pp_df is None: - data = {"name": name, "x": x, "y": y, "zone": zone, - "parval1": parval1, "k":k, "i":i, "j":j} - pp_df = pd.DataFrame(data=data,index=[0],columns=pp_names) - else: - data = [name, x, y, zone, parval1, k, i, j] - pp_df.loc[pp_count,:] = data - pp_count += 1 - #if we found some acceptable locs... - if pp_df is not None: - for prefix in prefix_dict[k]: - base_filename = prefix+"pp.dat" - pp_filename = os.path.join(pp_dir, base_filename) - # write the base pilot point file - write_pp_file(pp_filename, pp_df) - - tpl_filename = os.path.join(tpl_dir, base_filename + ".tpl") - #write the tpl file - pilot_points_to_tpl(pp_df, tpl_filename, - name_prefix=prefix) - pp_df.loc[:,"tpl_filename"] = tpl_filename - pp_df.loc[:,"pp_filename"] = pp_filename - pp_df.loc[:,"pargp"] = prefix - #save the parameter names and parval1s for later - par_info.append(pp_df.copy()) - #save the pp_filename and tpl_filename for later - pp_files.append(pp_filename) - tpl_files.append(tpl_filename) - - par_info = pd.concat(par_info) - for key,default in pst_config["par_defaults"].items(): - if key in par_info.columns: - continue - par_info.loc[:,key] = default - - if shapename is not None: - try: - import shapefile - except Exception as e: - print("error importing shapefile, try pip install pyshp...{0}"\ - .format(str(e))) - return par_info - shp = shapefile.Writer(shapeType=shapefile.POINT) - for name,dtype in par_info.dtypes.iteritems(): - if dtype == object: - shp.field(name=name,fieldType='C',size=50) - elif dtype in [int,np.int,np.int64,np.int32]: - shp.field(name=name, fieldType='N', size=50, decimal=0) - elif dtype in [float,np.float,np.float32,np.float64]: - shp.field(name=name, fieldType='N', size=50, decimal=10) - else: - raise Exception("unrecognized field type in par_info:{0}:{1}".format(name,dtype)) - - #some pandas awesomeness.. - par_info.apply(lambda x:shp.poly([[[x.x,x.y]]],shapeType=shapefile.POINT), axis=1) - par_info.apply(lambda x:shp.record(*x),axis=1) - - shp.save(shapename) - shp = shapefile.Reader(shapename) - assert shp.numRecords == par_info.shape[0] - return par_info
    - - -
    [docs]def pp_file_to_dataframe(pp_filename): - - """ read a pilot point file to a pandas Dataframe - - Parameters - ---------- - pp_filename : str - pilot point file - - Returns - ------- - df : pandas.DataFrame - a dataframe with pp_utils.PP_NAMES for columns - - """ - - df = pd.read_csv(pp_filename, delim_whitespace=True, - header=None, names=PP_NAMES,usecols=[0,1,2,3,4]) - df.loc[:,"name"] = df.name.apply(str).apply(str.lower) - return df
    - -
    [docs]def pp_tpl_to_dataframe(tpl_filename): - """ read a pilot points template file to a pandas dataframe - - Parameters - ---------- - tpl_filename : str - pilot points template file - - Returns - ------- - df : pandas.DataFrame - a dataframe with "parnme" included - - """ - inlines = open(tpl_filename, 'r').readlines() - header = inlines.pop(0) - marker = header.strip().split()[1] - assert len(marker) == 1 - usecols = [0,1,2,3] - df = pd.read_csv(tpl_filename, delim_whitespace=True,skiprows=1, - header=None, names=PP_NAMES[:-1],usecols=usecols) - df.loc[:,"name"] = df.name.apply(str).apply(str.lower) - df["parnme"] = [i.split(marker)[1].strip() for i in inlines] - - - return df
    - -
    [docs]def write_pp_shapfile(pp_df,shapename=None): - """write pilot points dataframe to a shapefile - - Parameters - ---------- - pp_df : pandas.DataFrame or str - pilot point dataframe or a pilot point filename. Dataframe - must include "x" and "y" - shapename : str - shapefile name. If None, pp_df must be str and shapefile - is saved as <pp_df>.shp - - Note - ---- - requires pyshp - - """ - try: - import shapefile - except Exception as e: - raise Exception("error importing shapefile: {0}, \ntry pip install pyshp...".format(str(e))) - - if not isinstance(pp_df,list): - pp_df = [pp_df] - dfs = [] - for pp in pp_df: - if isinstance(pp,pd.DataFrame): - dfs.append(pp) - elif isinstance(pp,str): - dfs.append(pp_file_to_dataframe(pp)) - else: - raise Exception("unsupported arg type:{0}".format(type(pp))) - - if shapename is None: - shapename = "pp_locs.shp" - - shp = shapefile.Writer(shapeType=shapefile.POINT) - for name, dtype in dfs[0].dtypes.iteritems(): - if dtype == object: - shp.field(name=name, fieldType='C', size=50) - elif dtype in [int, np.int, np.int64, np.int32]: - shp.field(name=name, fieldType='N', size=50, decimal=0) - elif dtype in [float, np.float, np.float32, np.float32]: - shp.field(name=name, fieldType='N', size=50, decimal=8) - else: - raise Exception("unrecognized field type in pp_df:{0}:{1}".format(name, dtype)) - - - # some pandas awesomeness.. - for df in dfs: - df.apply(lambda x: shp.poly([[[x.x, x.y]]]), axis=1) - df.apply(lambda x: shp.record(*x), axis=1) - - shp.save(shapename)
    - - - -
    [docs]def write_pp_file(filename,pp_df): - """write a pilot points dataframe to a pilot points file - - Parameters - ---------- - filename : str - pilot points file to write - pp_df : pandas.DataFrame - a dataframe that has columns "x","y","zone", and "value" - - """ - with open(filename,'w') as f: - f.write(pp_df.to_string(col_space=0, - columns=PP_NAMES, - formatters=PP_FMT, - justify="right", - header=False, - index=False) + '\n')
    - - -
    [docs]def pilot_points_to_tpl(pp_file,tpl_file=None,name_prefix=None): - """write a template file for a pilot points file - - Parameters - ---------- - pp_file : str - pilot points file - tpl_file : str - template file name to write. If None, append ".tpl" to - the pp_file arg. Default is None - name_prefix : str - name to prepend to parameter names for each pilot point. For example, - if ``name_prefix = "hk_"``, then each pilot point parameter will be named - "hk_0001","hk_0002", etc. If None, parameter names from pp_df.name - are used. Default is None. - - Returns - ------- - pp_df : pandas.DataFrame - a dataframe with pilot point information (name,x,y,zone,parval1) - with the parameter information (parnme,tpl_str) - """ - - if isinstance(pp_file,pd.DataFrame): - pp_df = pp_file - assert tpl_file is not None - else: - assert os.path.exists(pp_file) - pp_df = pd.read_csv(pp_file, delim_whitespace=True, - header=None, names=PP_NAMES) - - if tpl_file is None: - tpl_file = pp_file + ".tpl" - - if name_prefix is not None: - digits = str(len(str(pp_df.shape[0]))) - fmt = "{0:0"+digits+"d}" - names = [name_prefix+fmt.format(i) for i in range(pp_df.shape[0])] - else: - names = pp_df.name.copy() - - too_long = [] - for name in names: - if len(name) > 12: - too_long.append(name) - if len(too_long) > 0: - raise Exception("the following parameter names are too long:" +\ - ",".join(too_long)) - - tpl_entries = ["~ {0} ~".format(name) for name in names] - pp_df.loc[:,"tpl"] = tpl_entries - pp_df.loc[:,"parnme"] = names - - - f_tpl = open(tpl_file,'w') - f_tpl.write("ptf ~\n") - f_tpl.write(pp_df.to_string(col_space=0, - columns=["name","x","y","zone","tpl"], - formatters=PP_FMT, - justify="left", - header=False, - index=False) + '\n') - - return pp_df
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt index b95fcb632..61b7c052a 100644 --- a/docs/_build/html/_sources/index.rst.txt +++ b/docs/_build/html/_sources/index.rst.txt @@ -1,86 +1,16 @@ -.. pyemu documentation master file, created by - sphinx-quickstart on Thu Sep 14 11:12:03 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. -################################# -Welcome to pyEMU's documentation! -################################# - -pyEMU [WFD16]_ is a set of python modules for performing linear and non-linear -uncertainty analysis including parameter and forecast analyses, data-worth -analysis, and error-variance analysis. These python modules can interact with -the PEST [DOH15]_ and PEST++ [WWHD15]_ suites and use terminology consistent -with them. pyEMU is written in an object-oriented programming style, and -thus expects that users will write, or adapt, client code in python to -implement desired analysis. :doc:`source/oop` are provided in this documentation. - -pyEMU is available via github_ . - -.. _github: https://github.com/jtwhite79/pyemu -.. _Notes: +Welcome to pyEMU's documentation! +================================= -Contents -******** +This documentation is more-or-less complete. However, the example notebooks in the repo +provide a more complete picture of how the various components of pyEMU function. -.. toctree:: - :maxdepth: 1 - source/Monte_carlo_page - source/oop - source/glossary - -.. _Technical: -Technical Documentation -*********************** +Indices and tables +================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` - - -References -********** - -.. [BPL+16] Bakker, M., Post, V., Langevin, C. D., Hughes, J. D., White, J. T., - Starn, J. J. and Fienen, M. N., 2016, Scripting MODFLOW model development using - Python and FloPy: Groundwater, v. 54, p. 733-739, https://doi.org/10.1111/gwat.12413. - -.. [DOH15] Doherty, John, 2015. Calibration and - Uncertainty Analysis for Complex Environmental Models: Brisbane, Australia, Watermark Numerical - Computing, http://www.pesthomepage.org/Home.php . - -.. [FB16] Fienen, M.N., and Bakker, Mark, 2016, HESS Opinions: Repeatable research--- what hydrologists can learn - from the Duke cancer research scandal: Hydrology and Earth System Sciences, v. 20, no. 9, pg. 3739-3743, - https://doi.org/10.5194/hess-20-3739-2016 . - -.. [KB09] Beven, Keith, 2009, Environmental modelling--- an uncertain future?: London, Routledge, 310 p. - -.. [KRP+16] Kluyver, Thomas; Ragan-Kelley, Benjamin; Perez, Fernado; Granger, Brian; Bussonnier, Matthias; - Federic, Jonathan; Kelley, Kyle; Hamrick, Jessica; Grout, Jason; Corlay, Sylvain; Ivanov, Paul; Avila, Damian; - Aballa, Safia; Willing, Carol; and Jupyter Development Team, 2016, Jupyter Notebooks-- a publishing - format for reproducible computational workflows: in Positioning and Power in Academic - Publishing-- Players, Agents, and Agendas. F. Loizides and B. Schmidt (eds). - IOS Press, https://doi.org/10.3233/978-1-61499-649-1-87 and https://jupyter.org . - -.. [MCK10] McKinney, Wes, 2010, Data structures for statistical computing in python: - in Proceedings of the 9th Python in Science Conference, Stefan van - der Walt and Jarrod Millman (eds.), p. 51-56, https://pandas.pydata.org/index.html . - -.. [PSF18] The Python Software Foundation, 2018, Documentation, The python tutorial, - 9. Classes: https://docs.python.org/3.6/tutorial/classes.html . - -.. [RS16] Reges, Stuart, and Stepp, Marty, 2016, Building Java Programs--- A Back to Basics - Approach, Fourth Edition: Boston, Pearson, 1194 p. - -.. [WFD16] White, J.T., Fienen, M.N., and Doherty, J.E., 2016, A python framework - for environmental model uncertainty analysis: Environmental Modeling & - Software, v. 85, pg. 217-228, https://doi.org/10.1016/j.envsoft.2016.08.017 . - -.. [WWHD15] Welter, D.E., White, J.T., Hunt, R.J., and Doherty, J.E., 2015, - Approaches in highly parameterized inversion: PEST++ Version 3, a Parameter - ESTimation and uncertainty analysis software suite optimized for large - environmental models: U.S. Geological Survey Techniques and Methods, book 7, - section C12, 54 p., https://doi.org/10.3133/tm7C12 . \ No newline at end of file diff --git a/docs/_build/html/_sources/pst_demo.ipynb.txt b/docs/_build/html/_sources/pst_demo.ipynb.txt deleted file mode 100644 index 9adc1bbd4..000000000 --- a/docs/_build/html/_sources/pst_demo.ipynb.txt +++ /dev/null @@ -1,1724 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# The Pst class\n", - "\n", - "The `pst_handler` module contains the `Pst` class for dealing with pest control files. It relies heavily on `pandas` to deal with tabular sections, such as parameters, observations, and prior information. " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import print_function\n", - "import os\n", - "import numpy as np\n", - "import pyemu\n", - "from pyemu import Pst" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We need to pass the name of a pest control file to instantiate:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "pst_name = os.path.join(\"henry\",\"pest.pst\")\n", - "p = Pst(pst_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "All of the relevant parts of the pest control file are attributes of the `pst` class with the same name:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercom
    parnme
    global_kglobal_klogfactor200.0150.00250.00m1.00.01
    mult1mult1logfactor1.00.751.25m1.00.01
    mult2mult2logfactor1.00.502.00m1.00.01
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 200.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom \n", - "parnme \n", - "global_k 0.0 1 \n", - "mult1 0.0 1 \n", - "mult2 0.0 1 \n", - "kr01c01 0.0 1 \n", - "kr01c02 0.0 1 " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnme
    obsnme
    h_obs01_1h_obs01_10.051396152.1458head
    h_obs01_2h_obs01_20.0221560.0000head
    h_obs02_1h_obs02_10.046879152.1458head
    h_obs02_2h_obs02_20.0208530.0000head
    h_obs03_1h_obs03_10.036584152.1458head
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 152.1458 head\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A residual file (`.rei` or `res`) can also be passed to the `resfile` argument at instantiation to enable some simple residual analysis and weight adjustments. If the residual file is in the same directory as the pest control file and has the same base name, it will be accessed automatically:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    namegroupmeasuredmodelledresidualweight
    name
    h_obs01_1h_obs01_1head0.0513960.080402-0.029006152.1458
    h_obs01_2h_obs01_2head0.0221560.036898-0.0147420.0000
    h_obs02_1h_obs02_1head0.0468790.069121-0.022241152.1458
    h_obs02_2h_obs02_2head0.0208530.034311-0.0134580.0000
    h_obs03_1h_obs03_1head0.0365840.057722-0.021138152.1458
    h_obs03_2h_obs03_2head0.0195020.031408-0.0119050.0000
    h_obs04_1h_obs04_1head0.0275420.045770-0.018229152.1458
    h_obs04_2h_obs04_2head0.0169460.027337-0.0103910.0000
    h_obs05_1h_obs05_1head0.0263820.031442-0.005060152.1458
    h_obs05_2h_obs05_2head0.0075460.020240-0.0126950.0000
    h_obs06_1h_obs06_1head0.0528100.080413-0.027603152.1458
    h_obs06_2h_obs06_2head0.0238190.036928-0.0131090.0000
    h_obs07_1h_obs07_1head0.0412820.069172-0.027890152.1458
    h_obs07_2h_obs07_2head0.0236840.034452-0.0107680.0000
    h_obs08_1h_obs08_1head0.0366790.057965-0.021286152.1458
    h_obs08_2h_obs08_2head0.0213960.032000-0.0106040.0000
    h_obs09_1h_obs09_1head0.0320560.046879-0.014823152.1458
    h_obs09_2h_obs09_2head0.0091570.028272-0.0191160.0000
    h_obs10_1h_obs10_1head0.0175370.034988-0.017451152.1458
    h_obs10_2h_obs10_2head-0.0019470.011799-0.0137460.0000
    h_obs11_1h_obs11_1head0.0583760.080421-0.022045152.1458
    h_obs11_2h_obs11_2head0.0252910.036952-0.0116610.0000
    h_obs12_1h_obs12_1head0.0416970.069212-0.027515152.1458
    h_obs12_2h_obs12_2head0.0093850.034571-0.0251860.0000
    h_obs13_1h_obs13_1head0.0440800.058160-0.014079152.1458
    h_obs13_2h_obs13_2head-0.0043310.032735-0.0370660.0000
    h_obs14_1h_obs14_1head0.0145620.047917-0.033355152.1458
    h_obs14_2h_obs14_2head-0.0071730.001399-0.0085710.0000
    h_obs15_1h_obs15_1head-0.0073510.008326-0.015677152.1458
    h_obs15_2h_obs15_2head-0.0033610.004449-0.0078100.0000
    .....................
    kr10c31kr10c31regul_p0.0000000.0000000.0000001.0000
    kr10c32kr10c32regul_p0.0000000.0000000.0000001.0000
    kr10c33kr10c33regul_p0.0000000.0000000.0000001.0000
    kr10c34kr10c34regul_p0.0000000.0000000.0000001.0000
    kr10c35kr10c35regul_p0.0000000.0000000.0000001.0000
    kr10c36kr10c36regul_p0.0000000.0000000.0000001.0000
    kr10c37kr10c37regul_p0.0000000.0000000.0000001.0000
    kr10c38kr10c38regul_p0.0000000.0000000.0000001.0000
    kr10c39kr10c39regul_p0.0000000.0000000.0000001.0000
    kr10c40kr10c40regul_p0.0000000.0000000.0000001.0000
    kr10c41kr10c41regul_p0.0000000.0000000.0000001.0000
    kr10c42kr10c42regul_p0.0000000.0000000.0000001.0000
    kr10c43kr10c43regul_p0.0000000.0000000.0000001.0000
    kr10c44kr10c44regul_p0.0000000.0000000.0000001.0000
    kr10c45kr10c45regul_p0.0000000.0000000.0000001.0000
    kr10c46kr10c46regul_p0.0000000.0000000.0000001.0000
    kr10c47kr10c47regul_p0.0000000.0000000.0000001.0000
    kr10c48kr10c48regul_p0.0000000.0000000.0000001.0000
    kr10c49kr10c49regul_p0.0000000.0000000.0000001.0000
    kr10c50kr10c50regul_p0.0000000.0000000.0000001.0000
    kr10c51kr10c51regul_p0.0000000.0000000.0000001.0000
    kr10c52kr10c52regul_p0.0000000.0000000.0000001.0000
    kr10c53kr10c53regul_p0.0000000.0000000.0000001.0000
    kr10c54kr10c54regul_p0.0000000.0000000.0000001.0000
    kr10c55kr10c55regul_p0.0000000.0000000.0000001.0000
    kr10c56kr10c56regul_p0.0000000.0000000.0000001.0000
    kr10c57kr10c57regul_p0.0000000.0000000.0000001.0000
    kr10c58kr10c58regul_p0.0000000.0000000.0000001.0000
    kr10c59kr10c59regul_p0.0000000.0000000.0000001.0000
    kr10c60kr10c60regul_p0.0000000.0000000.0000001.0000
    \n", - "

    676 rows × 6 columns

    \n", - "
    " - ], - "text/plain": [ - " name group measured modelled residual weight\n", - "name \n", - "h_obs01_1 h_obs01_1 head 0.051396 0.080402 -0.029006 152.1458\n", - "h_obs01_2 h_obs01_2 head 0.022156 0.036898 -0.014742 0.0000\n", - "h_obs02_1 h_obs02_1 head 0.046879 0.069121 -0.022241 152.1458\n", - "h_obs02_2 h_obs02_2 head 0.020853 0.034311 -0.013458 0.0000\n", - "h_obs03_1 h_obs03_1 head 0.036584 0.057722 -0.021138 152.1458\n", - "h_obs03_2 h_obs03_2 head 0.019502 0.031408 -0.011905 0.0000\n", - "h_obs04_1 h_obs04_1 head 0.027542 0.045770 -0.018229 152.1458\n", - "h_obs04_2 h_obs04_2 head 0.016946 0.027337 -0.010391 0.0000\n", - "h_obs05_1 h_obs05_1 head 0.026382 0.031442 -0.005060 152.1458\n", - "h_obs05_2 h_obs05_2 head 0.007546 0.020240 -0.012695 0.0000\n", - "h_obs06_1 h_obs06_1 head 0.052810 0.080413 -0.027603 152.1458\n", - "h_obs06_2 h_obs06_2 head 0.023819 0.036928 -0.013109 0.0000\n", - "h_obs07_1 h_obs07_1 head 0.041282 0.069172 -0.027890 152.1458\n", - "h_obs07_2 h_obs07_2 head 0.023684 0.034452 -0.010768 0.0000\n", - "h_obs08_1 h_obs08_1 head 0.036679 0.057965 -0.021286 152.1458\n", - "h_obs08_2 h_obs08_2 head 0.021396 0.032000 -0.010604 0.0000\n", - "h_obs09_1 h_obs09_1 head 0.032056 0.046879 -0.014823 152.1458\n", - "h_obs09_2 h_obs09_2 head 0.009157 0.028272 -0.019116 0.0000\n", - "h_obs10_1 h_obs10_1 head 0.017537 0.034988 -0.017451 152.1458\n", - "h_obs10_2 h_obs10_2 head -0.001947 0.011799 -0.013746 0.0000\n", - "h_obs11_1 h_obs11_1 head 0.058376 0.080421 -0.022045 152.1458\n", - "h_obs11_2 h_obs11_2 head 0.025291 0.036952 -0.011661 0.0000\n", - "h_obs12_1 h_obs12_1 head 0.041697 0.069212 -0.027515 152.1458\n", - "h_obs12_2 h_obs12_2 head 0.009385 0.034571 -0.025186 0.0000\n", - "h_obs13_1 h_obs13_1 head 0.044080 0.058160 -0.014079 152.1458\n", - "h_obs13_2 h_obs13_2 head -0.004331 0.032735 -0.037066 0.0000\n", - "h_obs14_1 h_obs14_1 head 0.014562 0.047917 -0.033355 152.1458\n", - "h_obs14_2 h_obs14_2 head -0.007173 0.001399 -0.008571 0.0000\n", - "h_obs15_1 h_obs15_1 head -0.007351 0.008326 -0.015677 152.1458\n", - "h_obs15_2 h_obs15_2 head -0.003361 0.004449 -0.007810 0.0000\n", - "... ... ... ... ... ... ...\n", - "kr10c31 kr10c31 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c32 kr10c32 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c33 kr10c33 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c34 kr10c34 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c35 kr10c35 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c36 kr10c36 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c37 kr10c37 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c38 kr10c38 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c39 kr10c39 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c40 kr10c40 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c41 kr10c41 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c42 kr10c42 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c43 kr10c43 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c44 kr10c44 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c45 kr10c45 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c46 kr10c46 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c47 kr10c47 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c48 kr10c48 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c49 kr10c49 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c50 kr10c50 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c51 kr10c51 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c52 kr10c52 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c53 kr10c53 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c54 kr10c54 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c55 kr10c55 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c56 kr10c56 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c57 kr10c57 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c58 kr10c58 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c59 kr10c59 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "kr10c60 kr10c60 regul_p 0.000000 0.000000 0.000000 1.0000\n", - "\n", - "[676 rows x 6 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.res" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `pst` class has some `@decorated` convience methods related to the residuals:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1855.6874378297073 {'conc': 197.05822096106502, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 1658.6292168686423}\n" - ] - } - ], - "source": [ - "print(p.phi,p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some additional `@decorated` convience methods:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "603 75 601\n" - ] - } - ], - "source": [ - "print(p.npar,p.nobs,p.nprior)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['m', 'p'] ['conc', 'head']\n" - ] - } - ], - "source": [ - "print(p.par_groups,p.obs_groups)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "print(type(p.par_names)) # all parameter names\n", - "print(type(p.adj_par_names)) # adjustable parameter names\n", - "print(type(p.obs_names)) # all observation names\n", - "print(type(p.nnz_obs_names)) # non-zero weight observations" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The \"control_data\" section of the pest control file is accessible in the `Pst.control_data` attribute:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "jacupdate = 999\n", - "numlam = 10\n", - "numlam has been changed to --> 100\n" - ] - } - ], - "source": [ - "print('jacupdate = {0}'.format(p.control_data.jacupdate))\n", - "print('numlam = {0}'.format(p.control_data.numlam))\n", - "p.control_data.numlam = 100\n", - "print('numlam has been changed to --> {0}'.format(p.control_data.numlam))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Pst` class also exposes a method to get a new `Pst` instance with a subset of parameters and or obseravtions. Note this method does not propogate prior information to the new instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " equation obgnme pilbl weight names\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0 [mult1]\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0 [kr01c01]\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0 [kr01c02]\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0 [kr01c03]\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0 [kr01c04]\n", - "kr01c05 1.0 * log(kr01c05) = 0.0 regul_p kr01c05 1.0 [kr01c05]\n", - "kr01c06 1.0 * log(kr01c06) = 0.0 regul_p kr01c06 1.0 [kr01c06]\n", - "kr01c07 1.0 * log(kr01c07) = 0.0 regul_p kr01c07 1.0 [kr01c07]\n" - ] - } - ], - "source": [ - "pnew = p.get(p.par_names[:10],p.obs_names[-10:])\n", - "print(pnew.prior_information)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also write a pest control file with altered parameters, observations, and/or prior information:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/jwhite/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py:337: SettingWithCopyWarning: \n", - "A value is trying to be set on a copy of a slice from a DataFrame.\n", - "Try using .loc[row_indexer,col_indexer] = value instead\n", - "\n", - "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", - " self.obj[key] = _infer_fill_value(value)\n", - "/Users/jwhite/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py:517: SettingWithCopyWarning: \n", - "A value is trying to be set on a copy of a slice from a DataFrame.\n", - "Try using .loc[row_indexer,col_indexer] = value instead\n", - "\n", - "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", - " self.obj[item] = s\n" - ] - } - ], - "source": [ - "pnew.write(\"test.pst\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some other methods in `Pst` include:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    global_k1.0 * log(global_k) = 2.301030E+00regulmglobal_k4.507576
    mult11.0 * log(mult1) = 0.000000E+00regulmmult14.507576
    mult21.0 * log(mult2) = 0.000000E+00regulmmult21.660964
    kr01c011.0 * log(kr01c01) = 0.000000E+00regulpkr01c010.500000
    kr01c021.0 * log(kr01c02) = 0.000000E+00regulpkr01c020.500000
    kr01c031.0 * log(kr01c03) = 0.000000E+00regulpkr01c030.500000
    kr01c041.0 * log(kr01c04) = 0.000000E+00regulpkr01c040.500000
    kr01c051.0 * log(kr01c05) = 0.000000E+00regulpkr01c050.500000
    kr01c061.0 * log(kr01c06) = 0.000000E+00regulpkr01c060.500000
    kr01c071.0 * log(kr01c07) = 0.000000E+00regulpkr01c070.500000
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "global_k 1.0 * log(global_k) = 2.301030E+00 regulm global_k 4.507576\n", - "mult1 1.0 * log(mult1) = 0.000000E+00 regulm mult1 4.507576\n", - "mult2 1.0 * log(mult2) = 0.000000E+00 regulm mult2 1.660964\n", - "kr01c01 1.0 * log(kr01c01) = 0.000000E+00 regulp kr01c01 0.500000\n", - "kr01c02 1.0 * log(kr01c02) = 0.000000E+00 regulp kr01c02 0.500000\n", - "kr01c03 1.0 * log(kr01c03) = 0.000000E+00 regulp kr01c03 0.500000\n", - "kr01c04 1.0 * log(kr01c04) = 0.000000E+00 regulp kr01c04 0.500000\n", - "kr01c05 1.0 * log(kr01c05) = 0.000000E+00 regulp kr01c05 0.500000\n", - "kr01c06 1.0 * log(kr01c06) = 0.000000E+00 regulp kr01c06 0.500000\n", - "kr01c07 1.0 * log(kr01c07) = 0.000000E+00 regulp kr01c07 0.500000" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add preferred value regularization with weights proportional to parameter bounds\n", - "pyemu.utils.helpers.zero_order_tikhonov(pnew)\n", - "pnew.prior_information" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    01.0 * log(global_k) = 2.301030E+00regulmglobal_k1.0
    11.0 * log(mult1) = 0.000000E+00regulmmult11.0
    21.0 * log(mult2) = 0.000000E+00regulmmult21.0
    31.0 * log(kr01c01) = 0.000000E+00regulpkr01c011.0
    41.0 * log(kr01c02) = 0.000000E+00regulpkr01c021.0
    51.0 * log(kr01c03) = 0.000000E+00regulpkr01c031.0
    61.0 * log(kr01c04) = 0.000000E+00regulpkr01c041.0
    71.0 * log(kr01c05) = 0.000000E+00regulpkr01c051.0
    81.0 * log(kr01c06) = 0.000000E+00regulpkr01c061.0
    91.0 * log(kr01c07) = 0.000000E+00regulpkr01c071.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "0 1.0 * log(global_k) = 2.301030E+00 regulm global_k 1.0\n", - "1 1.0 * log(mult1) = 0.000000E+00 regulm mult1 1.0\n", - "2 1.0 * log(mult2) = 0.000000E+00 regulm mult2 1.0\n", - "3 1.0 * log(kr01c01) = 0.000000E+00 regulp kr01c01 1.0\n", - "4 1.0 * log(kr01c02) = 0.000000E+00 regulp kr01c02 1.0\n", - "5 1.0 * log(kr01c03) = 0.000000E+00 regulp kr01c03 1.0\n", - "6 1.0 * log(kr01c04) = 0.000000E+00 regulp kr01c04 1.0\n", - "7 1.0 * log(kr01c05) = 0.000000E+00 regulp kr01c05 1.0\n", - "8 1.0 * log(kr01c06) = 0.000000E+00 regulp kr01c06 1.0\n", - "9 1.0 * log(kr01c07) = 0.000000E+00 regulp kr01c07 1.0" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add preferred value regularization with unity weights\n", - "pyemu.utils.helpers.zero_order_tikhonov(pnew,parbounds=False)\n", - "pnew.prior_information" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some more `res` functionality" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1855.6874378297073 36 {'conc': 197.05822096106502, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 1658.6292168686423}\n", - "36.0 36 {'conc': 15.000000000000004, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 21.0}\n" - ] - } - ], - "source": [ - "# adjust observation weights to account for residual phi components\n", - "#pnew = p.get()\n", - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "p.adjust_weights_resfile()\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "adjust observation weights by an arbitrary amount by groups:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "36.0 36 {'conc': 15.000000000000004, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 21.0}\n", - "114.99999999999997 36 {'conc': 15.000000000000004, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 99.99999999999997}\n" - ] - } - ], - "source": [ - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "grp_dict = {\"head\":100}\n", - "p.adjust_weights(obsgrp_dict=grp_dict)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "adjust observation weights by an arbitrary amount by individual observations:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "114.99999999999997 36 {'conc': 15.000000000000004, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 99.99999999999997}\n", - "138.82580701146588 36 {'conc': 15.000000000000004, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 123.82580701146588}\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/jwhite/Dev/python/pyemu/pyemu/pst/pst_handler.py:1012: FutureWarning: 'name' is both a column name and an index level.\n", - "Defaulting to column but this will raise an ambiguity error in a future version\n", - " res_groups = self.res.groupby(\"name\").groups\n", - "/Users/jwhite/Dev/python/pyemu/pyemu/pst/pst_handler.py:1013: FutureWarning: 'obsnme' is both a column name and an index level.\n", - "Defaulting to column but this will raise an ambiguity error in a future version\n", - " obs_groups = self.observation_data.groupby(\"obsnme\").groups\n" - ] - } - ], - "source": [ - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "obs_dict = {\"h_obs01_1\":25}\n", - "p.adjust_weights(obs_dict=obs_dict)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "setup weights inversely proportional to the observation values" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "36.0 36 {'conc': 14.999999999999998, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 21.0}\n", - "222.96996562151 36 {'conc': 194.30909581453392, 'regul_m': 0.0, 'regul_p': 0.0, 'head': 28.66086980697609}\n" - ] - } - ], - "source": [ - "p.adjust_weights_resfile()\n", - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "p.proportional_weights(fraction_stdev=0.1,wmax=20.0)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [Root]", - "language": "python", - "name": "Python [Root]" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/docs/_build/html/_sources/source/Monte_carlo.rst.txt b/docs/_build/html/_sources/source/Monte_carlo.rst.txt deleted file mode 100644 index 341e5a97c..000000000 --- a/docs/_build/html/_sources/source/Monte_carlo.rst.txt +++ /dev/null @@ -1,20 +0,0 @@ -Run Monte Carlo Simulations from PEST Object --------------------------------------------- - -pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -*SWEEP.EXE* to generate results. - -.. rubric:: Workflow Overview - -blah blah blah - - -.. rubric:: Module Documentation - -.. automodule:: mc - :members: - - \ No newline at end of file diff --git a/docs/_build/html/_sources/source/Monte_carlo_page.rst.txt b/docs/_build/html/_sources/source/Monte_carlo_page.rst.txt deleted file mode 100644 index d68021cc0..000000000 --- a/docs/_build/html/_sources/source/Monte_carlo_page.rst.txt +++ /dev/null @@ -1,24 +0,0 @@ -Run Monte Carlo Simulations from PEST Object --------------------------------------------- - -pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -*SWEEP.EXE* to generate results. - -.. rubric:: Workflow Overview - -blah blah blah Uses :ref:`Ensembles`. - -.. rubric:: Inheritance - -.. inheritance-diagram:: pyemu.mc - -.. rubric:: Module Documentation - -.. automodule:: pyemu.mc - :members: - :show-inheritance: - :noindex: - \ No newline at end of file diff --git a/docs/_build/html/_sources/source/ensembles.rst.txt b/docs/_build/html/_sources/source/ensembles.rst.txt deleted file mode 100644 index 2d1084b95..000000000 --- a/docs/_build/html/_sources/source/ensembles.rst.txt +++ /dev/null @@ -1,17 +0,0 @@ -.. _Ensembles: - -Ensembles ---------- - -pyEMU ensembles is a base class with derived..bbb - -.. rubric:: Inheritance - -.. inheritance-diagram:: pyemu.en - -.. rubric:: Module Documentation - -.. automodule:: pyemu.en - :members: - :show-inheritance: - :noindex: \ No newline at end of file diff --git a/docs/_build/html/_sources/source/glossary.rst.txt b/docs/_build/html/_sources/source/glossary.rst.txt deleted file mode 100644 index 60b28e2b7..000000000 --- a/docs/_build/html/_sources/source/glossary.rst.txt +++ /dev/null @@ -1,330 +0,0 @@ -Glossary --------- - -.. glossary:: - :sorted: - - object - instance - A programming entity that may store internal data (:term:`state`), have - functionality (:term:`behavior`) or both [RS16]_ . - - class - Classes [PSF18]_ [RS16]_ are the building blocks for object-oriented programs. The class - defines both attributes (:term:`state`) and functionality (:term:`behavior`) of an :term:`object`. - - client code - In the case of pyEMU, client code is a python script that uses the class definitions to - make a desired :term:`instance` (also know as an :term:`object`) and to use the :term:`attributes` - and :term:`instance methods` to build the desired analysis. The client code - can be developed as a traditional python script or within a Jupyter notebook [KRP+16]_. - Jupyter notebooks are used extensively in this documentation to illustrate features of - pyEMU. - - state - The state of the object refers to the current value of all the internal data - stored in and object [RS16]_ . - - instance methods - Functions defined in a class that become associated with a particular - instance or object after :term:`instantiation`. - - instantiation - Make a class instance, also known as an object, from a class. - - behavior - Object behavior refers to the set of actions an object can perform often reporting - or modifying its internal :term:`state` [RS16]_ . - - attributes - Internal data stored by an object. - - static methods - helper methods - Functions that are defined for the python module and available to the script. They - are not tied to specified objects. pyEMU has a number of helper methods including - functions to plot results, read or write specific data types, and interact with the - :term:`FloPY` module in analysis of MODFLOW models. - - FloPY - Object-oriented python module to build, run, and process MODFLOW models [BPL+16]_ . Because - of the similar modeling structure, the combination of flopy and pyEMU can be - very effective in constructing, documenting, and analyzing groundwater-flow models. - - inheritance - A object-oriented programming technique where one class, referred to as the - :term:`derived class` or subclass, extends a second class, known as the :term:`base class` - or superclass. The subclass has all the defined attributes and behavior - of the superclass and typically adds additional attributes or methods. Many - derived classes :term:`override` methods of the superclass to perform specific - functions [RS16]_. - - override - Implement a new version of a method within a derived class that would have - otherwise been inherited from the base class customizing the behavior - of the method for the derived class [RS16]_. Overriding allows objects to - call a method by the same name but have behavior specific to the - object's type. - - base class - A base class, or superclass, is a class that is extended by another class - in a technique called :term:`inheritance`. Inheritance can make programming - efficient by having one version of base attributes and methods that can be - tested and then extended by a :term:`derived class`. - - derived class - A derived class, or subclass, inherits attributes and methods from its - :term:`base class`. Derived classes then add attributes and methods as - needed. For example, in pyEMU, the linear analysis base class is inherited by the - Monte Carlo derived class. - - GLUE - Generalized Likelihood Uncertainty Estimation [KB09]_ . - - parameters - Variable input values for models, typically representing system properties - and forcings. Values to be estimated in the history matching process. - - observation - Measured system state values. These values are used to compare with model - outputs collocated in space and time. The term is often used to mean - *both* field measurements and outputs from the model. These are denoted - by :math:`y` for a scalar value or :math:`\bf{y}` - for a vector of values in this documentation. - - modeled equivalent - simulated equivalent - A modeled value collocated to correspond in time and space with an observation. - To make things confusing, they are often *also* called - "observations"! These are denoted by :math:`f(x)` for a scalar value or :math:`f\left( \bf{x} \right)` - for a vector of values in this documentation. Note that in addition to - :math:`f(x)`, authors use several different alternate - notations to distinguish an observation from a - corresponding modeled equivalent including subscripts, :math:`y` and :math:`y_m`, - superscripts, :math:`y` and :math:`y^\prime`, or diacritic marks, :math:`y` and :math:`\hat{y}`. - - forecasts - Model outputs for which field observations are not available. Typically these - values are simulated under an uncertain future condition. - - Phi - For pyEMU and consistent with PEST and PEST++, Phi refers to the :term:`objective function`, - defined as the weighted sum of squares of residuals. Phi, :math:`\Phi`, is typically calculated as - - .. math:: - \begin{array}{ccc} - \Phi=\sum_{i=1}^{n}\left(\frac{y_{i}-f\left(x_{i}\right)}{w_{i}}\right)^{2} & or & \Phi=\left(\mathbf{y}-\mathbf{Jx}\right)^{T}\mathbf{Q}^{-1}\left(\mathbf{y}-\mathbf{Jx}\right) - \end{array} - - When regularization is included, an additional term is added, - quantifying a penalty assessed for parameter sets that violate the preferred - conditions regarding the parameter values. - In such a case, the value of :math:`\Phi` as stated above is - renamed :math:`\Phi_m` for "measurement Phi" and the additional regularization - term is named :math:`\Phi_r`. A scalar, :math:`\gamma`, parameter controls the - tradeoff between these two dual components of the total objective function :math:`\Phi_t`. - - .. math:: - \Phi_t = \Phi_m + \gamma \Phi_r - - weight - epistemic uncertainty - A value by which a residual is divided by when constructing the sum of - squared residuals. In principal, :math:`w_i\approx\frac{1}{\sigma_i}` where :math:`\sigma_i` is - an approximation of the expected error between model output and collocated - observation values. While the symbol :math:`\sigma` implies a standard deviation, - it is important to note that measurement error only makes up a portion of - this error. Other aspects such as structural error (e.g. inadequacy inherent - in all models to perfectly simulate the natural world) also contribute to - this expected level of error. The reciprocal of weights are also called - Epistemic Uncertainty terms. - - residuals - - The difference between observation values and modeled equivalents - :math:`r_i=y_i-f\left(x_i\right)`. - - sensitivity - The incremental change of an observation (actually the modeled equivalent) - due to an incremental change in a parameter. Typically expressed as a - finite-difference approximation of a partial derivative, :math:`\frac{\partial - f(x)}{\partial x}`. - - Jacobian matrix - A matrix of the sensitivity of all observations in an inverse model to all - parameters. This is often shown as a matrix by various names :math:`\mathbf{X}`, - :math:`\mathbf{J}`, or :math:`\mathbf{H}`. Each element of the matrix is a single - sensitivity value :math:`\frac{\partial f(x_i)}{\partial x_j}` for :math:`i\in NOBS`, :math:`j - \in NPAR`. - - *NOBS* - For PEST and PEST++, number of observations. - - *NPAR* - For PEST and PEST++, number of parameters. - - regularization - A preferred condition pertaining to parameters, the deviation from which, - elicits a penalty added to the objective function. This serves as a - balance between the level of fit or "measurement Phi" - :math:`(\mathbf{\Phi_M})` and the coherence with soft knowledge/previous - conditions/prior knowledge/regularization :math:`(\mathbf{\Phi_R})`. These terms - can also be interpreted as the likelihood function and prior distribution - in Bayes theorem. - - PHIMLIM - A PEST input parameter the governs the strength with which regularization - is applied to the objective function. A high value of PHIMLIM indicates a - strong penalty for deviation from preferred parameter conditions while a - low value of PHIMLIM indicates a weak penalty. The reason this "dial"" is - listed as a function of PHIM (e.g. :math:`\mathbf{\Phi_M}`) is because it can - then be interpreted as a limit on how well we want to fit the observation - data. PHIMLIM is actually controlling the value :math:`\gamma` appearing in the - definition for :term:`Phi` and formally trading off :math:`\Phi_m` asnd :math:`\Phi_r`. - - objective function - Equation quantifying how well as simulation matches observations. Inverse - modeling is the process of calibrating a numerical model by mathematically - seeking the minimum of an objective function. (see :term:`Phi`) - - Gaussian (multivariate) - The equation for Gaussian (Normal) distribution for a single variable (:math:`x`) is - - .. math:: - \begin{equation} - f(x|\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\frac{\left(x-\mu\right)^2}{\sigma^2}} - \end{equation} - - where :math:`\mu` is the mean and :math:`\sigma` is the standard deviation - The equation for a multivariate Gaussian for a vector of :math:`k` variables (:math:`\mathbf{x}`) is - - .. math:: - \begin{equation} - f(\mathbf{x} | \mathbf{\mu},\mathbf{\Sigma})=\frac{1}{\sqrt{(2\pi)^k\left|\mathbf{\Sigma}\right|}}e^{-\frac{1}{2}\left( \left(\mathbf{x}-\mathbf{\mu} \right)^T \mathbf{\Sigma}^{-1}\left(\mathbf{x}-\mathbf{\mu} \right)\right)} - \end{equation} - - where :math:`\mu` is a :math:`k`-length vector of mean values, :math:`\mathbf{\Sigma}` is - the covariance matrix, and :math:`\left|\mathbf{\Sigma}\right|` is the determinant of - the covariance matrix. These quantities are often abbreviated - as :math:`\mathcal{N}\left( \mu, \sigma \right)` - and :math:`\mathcal{N}\left(\boldsymbol{\mu}, \boldsymbol{\Sigma} \right)` - for univariate and multivariate Gaussian distributions, respectively. - - weight covariance matrix (correlation matrix) - In practice, this is usually a :math:`NOBS\times NOBS` diagonal matrix with - values of weights on the diagonal representing the inverse of the - observation covariance. This implies a lack of correlation among the - observations. A full covariance matrix would indicate correlation among - the observations which, in reality, is present but, in practice, is rarely - characterized. The weight matrix is often identified as :math:`\mathbf{Q}^{-1}` - or :math:`\mathbf{\Sigma_\epsilon}^{-1}`. - - parameter covariance matrix - The uncertainty of parameters can be expressed as a matrix as well. This - is formed also as a diagonal matrix from the bounds around parameter - values (assuming that the range between the bounds indicates :math:`4\sigma` - (e.g. 95 percent of a normal distribution).) In pyEMU, some functions - accept a *sigma\_range* argument which can override the :math:`4\sigma` - assumption. In many cases of our applications, parameters are spatially - distributed (e.g. hydraulic conductivity fields) so a covariance matrix - with off-diagonal terms can be formed to characterize not only their - variance but also their correlation/covariance. We often use - geostatistical variograms to characterize the covariance of parameters. - The parameter covariance matrix is often identified as :math:`C(\mathbf{p})`, - :math:`\mathbf{\Sigma_\theta}`, or :math:`\mathbf{R}`. - - measurement noise/error - Measurement noise is a contribution to Epistemic Uncertainty. This is the - expected error of repeated measurements due to things like instrument - error and also can be compounded by error of surveying a datum, location - of an observation on a map, and other factors. - - structural (model) error - Epistemic uncertainty is actually dominated by structural error relative - to measurement noise. The structural error is the expected misfit between - measured and modeled values at observation locations due to model - inadequacy (including everything from model simplification due to the - necessity of discretizing the domain, processes that are missing from the - model, etc.). - - Monte Carlo parameter realization - A set of parameter values, often but not required to be a multi-Gaussian - distribution, sampled from the mean values of specified parameter values - (either starting values or, in some cases, optimal values following - parameter estimation) with covariance from a set of variance values, or a - covariance matrix. Can be identified as :math:`\mathbf{\theta}`. - - Monte Carlo observation realization - A set of observation values, often but not required to be a multi-Gaussian - distribution, sampled using the mean values of measured observations and - variance from the observation weight covariance matrix. Can be identified - as :math:`\boldsymbol{d_{obs}}`. - - - Monte Carlo ensemble - A group of realizations of parameters, :math:`\mathbf{\Theta}`, observations, - :math:`\mathbf{D_{obs}}`, and the simulated equivalent values, - :math:`\mathbf{D_{sim}}`. Note that these three matrices are made up of column - vectors representing all of the :math:`\boldsymbol{\theta}`, :math:`\mathbf{d_{obs}}`, - and :math:`\mathbf{d_{sim}}` vectors. - - Bayes' Theorem - .. math:: - \begin{equation} - P\left(\boldsymbol{\theta}|\boldsymbol{d}\right) = - \frac{\mathcal{L}\left(\textbf{d}|\boldsymbol{\theta}\right) P\left(\boldsymbol{\theta}\right)} - {P\left(\textbf{d}\right)} \ldots - \underbrace{P\left(\boldsymbol{\theta}|\textbf{d}\right)}_{\text{posterior pdf}} \propto - \underbrace{\mathcal{L}\left(\boldsymbol{d}|\boldsymbol{\theta}\right)}_{\text{likelihood function}} - \quad - \underbrace{P\left(\boldsymbol{\theta}\right)}_{\text{prior pdf}} - \end{equation} - - where :math:`\boldsymbol{\theta}` is a vector of parameters, - and :math:`\mathbf{d}` is a vector of observations It is computationally - expedient to assume that these quantities can be characterized by - multivariate Gaussian distributions and, thus, characterized only by their - first two moments --- mean and covariance. - - posterior (multivariate distribution) - The posterior distribution is the updated distribution (mean and - covariance) of parameter values :math:`\boldsymbol{\theta}` updated from their - prior by an experiment (encapsulated in the likelihood function). In other - words, information gleaned from observations :math:`\mathbf{d}` is used to - update the initial values of the parameters. - - prior (multivariate distribution) - This distribution represents what we know about parameter values prior to - any modeling. It is also called "soft knowledge" or "expert - knowledge". This information is more than just starting values, but also - encapsulates the understanding of uncertainty (characterized through the - covariance) based on direct estimation of parameters (e.g. pumping tests, - geologic maps, and grain size analysis, for example). In one - interpretation of the objective function, this is also where the - regularization information is contained. - - likelihood (multivariate distribution) - This is a function describing how much is learned from the model. It is - characterized by the misfit between modeled equivalents and observations. - - FOSM - linear uncertainty analysis - First Order Second Moment (FOSM) is a technique to use an assumption of Gaussian - distributions to, analytically, calculate the covariance of model outputs - considering both the prior covariance and the likelihood function. In - other words, it's an analytical calculation of the posterior covariance of - parameters using Bayes' Theoerem. The equation for this calculation is the - Schur Complement. The key advantage of this is that we really only need a - few quantities --- a Jacobian Matrix :math:`\mathbf{J}`, the prior covariance of - parameters :math:`\boldsymbol{\Sigma_\theta}`, and the observation covariance - :math:`\boldsymbol{\Sigma_\epsilon}`. - - Schur complement - The formula used to propagate uncertainty from a prior through a - "notional" calibration (via the Jacobian) to the posterior update. - - .. math:: - \begin{equation} - \underbrace{\overline{\boldsymbol{\Sigma}}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{know now}}} = \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{knew}}} - \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T\left[\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T + \boldsymbol{\Sigma}_{\boldsymbol{\epsilon}}\right]^{-1}\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\text{what we learned}} - \end{equation} - \ No newline at end of file diff --git a/docs/_build/html/_sources/source/introduction.rst.txt b/docs/_build/html/_sources/source/introduction.rst.txt deleted file mode 100644 index 1f768ed16..000000000 --- a/docs/_build/html/_sources/source/introduction.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -Introduction ------------- - -pyEMU is a set of python modules for programmatically interacting with the PEST -and PESTPP suites. It adopts much of the terminology from the PEST ecosystem, -but implements the functionality in a pythonic, easy-to-use interface. pyEMU is -available via github. diff --git a/docs/_build/html/_sources/source/modules.rst.txt b/docs/_build/html/_sources/source/modules.rst.txt deleted file mode 100644 index 23825801c..000000000 --- a/docs/_build/html/_sources/source/modules.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -pyemu -===== - -.. toctree:: - :maxdepth: 4 - - pyemu diff --git a/docs/_build/html/_sources/source/oop.rst.txt b/docs/_build/html/_sources/source/oop.rst.txt deleted file mode 100644 index e19764ef0..000000000 --- a/docs/_build/html/_sources/source/oop.rst.txt +++ /dev/null @@ -1,50 +0,0 @@ -Notes on Object-Oriented Programming ------------------------------------- - -Analysis using PEST and PEST++ were traditionally run by writing a series of -input files (template, instruction, and contol files) and running a desired -program from either suite. pyEMU provides a programmatic way to interact with -PEST and PEST++ that allows for easier creation of necessary input files and -interpretation of the output generated by the suite of programs. The -programmatic approach also assists in creation of reproducible research [FB16]_ -by documenting choices made by the analyst in the generation of input files, in -steps of the analysis, or in the interpretation of results. pyEMU also extends -the functionality of PEST and PEST++ especially for performing linear and non- -linear uncertainty analysis. Users familiar with PEST and PEST++ input files -will find the terminology used in pyEMU familiar, the main difference between -using pyEMU and traditional methods is in the writing of :term:`client code` to -perform the desired analysis. - -The general workflow for pyEMU is to first create an :term:`instance` (:term:`object`) -of a class. The simplest object will only have attributes, -or data, associated with it and may be thought of as a data structure. Many -pyEMU objects also have :term:`instance methods` associated with them which -allow the user to either manipulate attributes of the object or use the current -attributes, also known as the :term:`state` of the object, to perform tasks. - -.. rubric:: For example - -The jupyter notebook linked below uses the pyEMU -Pst Class (in the pyemu.pst module, pst_handler submodule) to create an object called *p*. - -Attributes of the object -can then be accessed using *p.attribute*, for example, the parameter_data -are stored in the object as a pandas [MCK10]_ dataframe and can be accessed -using:: - - parm_data_df = p.parameter_data - -Methods are invoked using the dot notation, *p.methodname(parameters)*. -One of the methods of the object used in the example notebook is -the get() method which will generate a new Pst instance using -a subset of the parameters or observations from an existing -object. To get a new object with the first 10 parameters and observations:: - - pnew = p.get(p.par_names[:10], p.obs_names[:10]) - -The attributes and methods associated with all the classes of pyEMU and -the helper methods are summarized in the :ref:`Technical` section. - -.. toctree:: - - pst_demo \ No newline at end of file diff --git a/docs/_build/html/_sources/source/pst_demo.ipynb.txt b/docs/_build/html/_sources/source/pst_demo.ipynb.txt deleted file mode 100644 index 20a832102..000000000 --- a/docs/_build/html/_sources/source/pst_demo.ipynb.txt +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example Object-Oriented Access to the PEST Control File\n", - "\n", - "The `pst_handler` module with `pyemu.pst` contains the `Pst` class for dealing with pest control files. It relies heavily on `pandas` to deal with tabular sections, such as parameters, observations, and prior information. This jupyter notebook shows how to create a control-file object (instantiate the class or make an instance of the class), how to access attributes of the class, and how to call an instance method." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import os\n", - "import numpy as np\n", - "import pyemu\n", - "from pyemu import Pst" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A PEST control file is required to make the object, and we need to pass the name of the PEST control file as a parameter to the __init__ method for the class. The class instance (or object)\n", - "is assigned to the variable *p*." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "pst_name = os.path.join(\"..\", \"..\", \"examples\", \"henry\",\"pest.pst\")\n", - "p = Pst(pst_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now all of the relevant parts of the pest control file are attributes of the object.\n", - "For example, the parameter_data, observation data, and prior information are available as pandas dataframes.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor200.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 200.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.051396152.1458headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 152.1458 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The client-code can be used to change values in the dataframes that can be written to \n", - "a new or updated control file using the `write()` method as shown at the end of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 225.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.loc['global_k', 'parval1'] = 225\n", - "p.parameter_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A residual file (`.rei` or `res`) can also be passed to the `resfile` argument at instantiation to enable some simple residual analysis and evaluate if weight adjustments are needed. If `resfile = False`, or not supplied, and if the residual file is in the same directory as the pest control file and has the same base name, it will be accessed automatically:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    namegroupmeasuredmodelledresidualweight
    name
    h_obs01_1h_obs01_1head0.0513960.080402-0.029006152.1458
    h_obs01_2h_obs01_2head0.0221560.036898-0.0147420.0000
    h_obs02_1h_obs02_1head0.0468790.069121-0.022241152.1458
    h_obs02_2h_obs02_2head0.0208530.034311-0.0134580.0000
    h_obs03_1h_obs03_1head0.0365840.057722-0.021138152.1458
    \n", - "
    " - ], - "text/plain": [ - " name group measured modelled residual weight\n", - "name \n", - "h_obs01_1 h_obs01_1 head 0.051396 0.080402 -0.029006 152.1458\n", - "h_obs01_2 h_obs01_2 head 0.022156 0.036898 -0.014742 0.0000\n", - "h_obs02_1 h_obs02_1 head 0.046879 0.069121 -0.022241 152.1458\n", - "h_obs02_2 h_obs02_2 head 0.020853 0.034311 -0.013458 0.0000\n", - "h_obs03_1 h_obs03_1 head 0.036584 0.057722 -0.021138 152.1458" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.res.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The weights can be updated by changing values in the observation dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.loc['h_obs01_1', 'weight'] = 25.0\n", - "p.observation_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Pst` class exposes a method, `get()`, to create a new `Pst` instance with a subset of parameters and or observations. For example, make a new PEST control-file object using the first 10 entries from the \n", - "parameter and observation dataframes. Note this method does not propogate prior information to the new instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweightnames
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0[mult1]
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0[kr01c01]
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0[kr01c02]
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0[kr01c03]
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0[kr01c04]
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight names\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0 [mult1]\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0 [kr01c01]\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0 [kr01c02]\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0 [kr01c03]\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0 [kr01c04]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew = p.get(p.par_names[:10],p.obs_names[:10])\n", - "pnew.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the parameter_data and observation_data dataframes for the new object, note that\n", - "the updated values for `global_k` `parval1` and `h_obs01_1` `weight` are in these dataframes." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 225.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew.observation_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `write(filename)` method allows you to write a PEST control file with the current state of the object: that is, make a new PEST control file with the current information contained in an object." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "pnew.write(\"test.pst\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/docs/_build/html/_sources/source/pyemu.mat.rst.txt b/docs/_build/html/_sources/source/pyemu.mat.rst.txt deleted file mode 100644 index 3711f0da4..000000000 --- a/docs/_build/html/_sources/source/pyemu.mat.rst.txt +++ /dev/null @@ -1,22 +0,0 @@ -pyemu.mat package -================= - -Submodules ----------- - -pyemu.mat.mat_handler module ----------------------------- - -.. automodule:: pyemu.mat.mat_handler - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.mat - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/pyemu.pst.rst.txt b/docs/_build/html/_sources/source/pyemu.pst.rst.txt deleted file mode 100644 index 7483d9722..000000000 --- a/docs/_build/html/_sources/source/pyemu.pst.rst.txt +++ /dev/null @@ -1,38 +0,0 @@ -pyemu.pst package -================= - -Submodules ----------- - -pyemu.pst.pst_controldata module --------------------------------- - -.. automodule:: pyemu.pst.pst_controldata - :members: - :undoc-members: - :show-inheritance: - -pyemu.pst.pst_handler module ----------------------------- - -.. automodule:: pyemu.pst.pst_handler - :members: - :undoc-members: - :show-inheritance: - -pyemu.pst.pst_utils module --------------------------- - -.. automodule:: pyemu.pst.pst_utils - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.pst - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/pyemu.rst.txt b/docs/_build/html/_sources/source/pyemu.rst.txt deleted file mode 100644 index 02c7d08f2..000000000 --- a/docs/_build/html/_sources/source/pyemu.rst.txt +++ /dev/null @@ -1,79 +0,0 @@ -pyemu package -============= - -Subpackages ------------ - -.. toctree:: - - pyemu.mat - pyemu.pst - pyemu.utils - -Submodules ----------- - -pyemu.en module ---------------- - -.. automodule:: pyemu.en - :members: - :undoc-members: - :show-inheritance: - -pyemu.ev module ---------------- - -.. automodule:: pyemu.ev - :members: - :undoc-members: - :show-inheritance: - -pyemu.la module ---------------- - -.. automodule:: pyemu.la - :members: - :undoc-members: - :show-inheritance: - -pyemu.logger module -------------------- - -.. automodule:: pyemu.logger - :members: - :undoc-members: - :show-inheritance: - -pyemu.mc module ---------------- - -.. automodule:: pyemu.mc - :members: - :undoc-members: - :show-inheritance: - -pyemu.sc module ---------------- - -.. automodule:: pyemu.sc - :members: - :undoc-members: - :show-inheritance: - -pyemu.smoother module ---------------------- - -.. automodule:: pyemu.smoother - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/pyemu.utils.rst.txt b/docs/_build/html/_sources/source/pyemu.utils.rst.txt deleted file mode 100644 index 2e93a0887..000000000 --- a/docs/_build/html/_sources/source/pyemu.utils.rst.txt +++ /dev/null @@ -1,54 +0,0 @@ -pyemu.utils package -=================== - -Submodules ----------- - -pyemu.utils.geostats module ---------------------------- - -.. automodule:: pyemu.utils.geostats - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.gw_utils module ---------------------------- - -.. automodule:: pyemu.utils.gw_utils - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.helpers module --------------------------- - -.. automodule:: pyemu.utils.helpers - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.optimization module -------------------------------- - -.. automodule:: pyemu.utils.optimization - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.pp_utils module ---------------------------- - -.. automodule:: pyemu.utils.pp_utils - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_build/html/_static/ajax-loader.gif b/docs/_build/html/_static/ajax-loader.gif deleted file mode 100644 index 61faf8cab23993bd3e1560bff0668bd628642330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN diff --git a/docs/_build/html/_static/alabaster.css b/docs/_build/html/_static/alabaster.css deleted file mode 100644 index 4ee0e05f3..000000000 --- a/docs/_build/html/_static/alabaster.css +++ /dev/null @@ -1,684 +0,0 @@ -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Georgia, serif; - font-size: 17px; - background-color: #fff; - color: #000; - margin: 0; - padding: 0; -} - - -div.document { - width: 940px; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 220px; -} - -div.sphinxsidebar { - width: 220px; - font-size: 14px; - line-height: 1.5; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #fff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -div.body > .section { - text-align: left; -} - -div.footer { - width: 940px; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -p.caption { - font-family: inherit; - font-size: inherit; -} - - - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0; - margin: -10px 0 0 0px; - text-align: center; -} - -div.sphinxsidebarwrapper h1.logo { - margin-top: -10px; - text-align: center; - margin-bottom: 5px; - text-align: left; -} - -div.sphinxsidebarwrapper h1.logo-name { - margin-top: 0px; -} - -div.sphinxsidebarwrapper p.blurb { - margin-top: 0; - font-style: normal; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Georgia, serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar ul li.toctree-l1 > a { - font-size: 120%; -} - -div.sphinxsidebar ul li.toctree-l2 > a { - font-size: 110%; -} - -div.sphinxsidebar input { - border: 1px solid #CCC; - font-family: Georgia, serif; - font-size: 1em; -} - -div.sphinxsidebar hr { - border: none; - height: 1px; - color: #AAA; - background: #AAA; - - text-align: left; - margin-left: 0; - width: 50%; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Georgia, serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #DDD; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #EAEAEA; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - margin: 20px 0px; - padding: 10px 30px; - background-color: #EEE; - border: 1px solid #CCC; -} - -div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fafafa; -} - -div.admonition p.admonition-title { - font-family: Georgia, serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: #fff; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.warning { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.danger { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.error { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.caution { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.attention { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.important { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.note { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.tip { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.hint { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.seealso { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.topic { - background-color: #EEE; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -.hll { - background-color: #FFC; - margin: 0 -12px; - padding: 0 12px; - display: block; -} - -img.screenshot { -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.95em; -} - -tt.descname, code.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #EEE; - background: #FDFDFD; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.field-list p { - margin-bottom: 0.8em; -} - -/* Cloned from - * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 - */ -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -table.footnote td.label { - width: .1px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - /* Matches the 30px from the narrow-screen "li > ul" selector below */ - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #EEE; - padding: 7px 30px; - margin: 15px 0px; - line-height: 1.3em; -} - -div.viewcode-block:target { - background: #ffd; -} - -dl pre, blockquote pre, li pre { - margin-left: 0; - padding-left: 30px; -} - -tt, code { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, code.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fff; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -/* Don't put an underline on images */ -a.image-reference, a.image-reference:hover { - border-bottom: none; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt, a:hover code { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - li > ul { - /* Matches the 30px from the "ul, ol" selector above */ - margin-left: 30px; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: #fff; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: #FFF; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: #fff; - } - - div.sphinxsidebar a { - color: #AAA; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* misc. */ - -.revsys-inline { - display: none!important; -} - -/* Make nested-list/multi-paragraph items look better in Releases changelog - * pages. Without this, docutils' magical list fuckery causes inconsistent - * formatting between different release sub-lists. - */ -div#changelog > div.section > ul > li > p:only-child { - margin-bottom: 0; -} - -/* Hide fugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - - -/* relbar */ - -.related { - line-height: 30px; - width: 100%; - font-size: 0.9rem; -} - -.related.top { - border-bottom: 1px solid #EEE; - margin-bottom: 20px; -} - -.related.bottom { - border-top: 1px solid #EEE; -} - -.related ul { - padding: 0; - margin: 0; - list-style: none; -} - -.related li { - display: inline; -} - -nav#rellinks { - float: right; -} - -nav#rellinks li+li:before { - content: "|"; -} - -nav#breadcrumbs li+li:before { - content: "\00BB"; -} - -/* Hide certain items when printing */ -@media print { - div.related { - display: none; - } -} \ No newline at end of file diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css index 19ced1057..24bc73e7f 100644 --- a/docs/_build/html/_static/basic.css +++ b/docs/_build/html/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -15,6 +15,12 @@ div.clearer { clear: both; } +div.section::after { + display: block; + content: ''; + clear: left; +} + /* -- relbar ---------------------------------------------------------------- */ div.related { @@ -81,6 +87,10 @@ div.sphinxsidebar input { font-size: 1em; } +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + div.sphinxsidebar #searchbox input[type="text"] { float: left; width: 80%; @@ -227,6 +237,16 @@ a.headerlink { visibility: hidden; } +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -275,6 +295,12 @@ img.align-center, .figure.align-center, object.align-center { margin-right: auto; } +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + .align-left { text-align: left; } @@ -283,6 +309,10 @@ img.align-center, .figure.align-center, object.align-center { text-align: center; } +.align-default { + text-align: center; +} + .align-right { text-align: right; } @@ -292,21 +322,27 @@ img.align-center, .figure.align-center, object.align-center { div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; - padding: 7px 7px 0 7px; + padding: 7px; background-color: #ffe; width: 40%; float: right; + clear: right; + overflow-x: auto; } p.sidebar-title { font-weight: bold; } +div.admonition, div.topic, blockquote { + clear: left; +} + /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; - padding: 7px 7px 0 7px; + padding: 7px; margin: 10px 0 10px 0; } @@ -328,10 +364,6 @@ div.admonition dt { font-weight: bold; } -div.admonition dl { - margin-bottom: 0; -} - p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; @@ -342,9 +374,28 @@ div.body p.centered { margin-top: 25px; } +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + /* -- tables ---------------------------------------------------------------- */ table.docutils { + margin-top: 10px; + margin-bottom: 10px; border: 0; border-collapse: collapse; } @@ -354,6 +405,11 @@ table.align-center { margin-right: auto; } +table.align-default { + margin-left: auto; + margin-right: auto; +} + table caption span.caption-number { font-style: italic; } @@ -387,6 +443,16 @@ table.citation td { border-bottom: none; } +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + /* -- figures --------------------------------------------------------------- */ div.figure { @@ -427,6 +493,17 @@ table.field-list td, table.field-list th { hyphens: manual; } +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + + /* -- other body styles ----------------------------------------------------- */ ol.arabic { @@ -449,11 +526,78 @@ ol.upperroman { list-style: upper-roman; } +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + dl { margin-bottom: 15px; } -dd p { +dd > :first-child { margin-top: 0px; } @@ -467,6 +611,11 @@ dd { margin-left: 30px; } +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + dt:target, span.highlighted { background-color: #fbe54e; } @@ -526,6 +675,12 @@ dl.glossary dt { font-style: oblique; } +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + abbr, acronym { border-bottom: dotted 1px; cursor: help; @@ -538,6 +693,10 @@ pre { overflow-y: hidden; /* fixes display issues on Chrome browsers */ } +pre, div[class*="highlight-"] { + clear: both; +} + span.pre { -moz-hyphens: none; -ms-hyphens: none; @@ -545,22 +704,57 @@ span.pre { hyphens: none; } +div[class*="highlight-"] { + margin: 1em 0; +} + td.linenos pre { - padding: 5px 0px; border: 0; background-color: transparent; color: #aaa; } table.highlighttable { - margin-left: 0.5em; + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; } table.highlighttable td { - padding: 0 0.5em 0 0.5em; + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; } div.code-block-caption { + margin-top: 1em; padding: 2px 5px; font-size: small; } @@ -569,8 +763,9 @@ div.code-block-caption code { background-color: transparent; } -div.code-block-caption + div > div.highlight > pre { - margin-top: 0; +table.highlighttable td.linenos, +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; } div.code-block-caption span.caption-number { @@ -582,11 +777,7 @@ div.code-block-caption span.caption-text { } div.literal-block-wrapper { - padding: 1em 1em 0; -} - -div.literal-block-wrapper div.highlight { - margin: 0; + margin: 1em 0; } code.descname { @@ -637,8 +828,7 @@ span.eqno { } span.eqno a.headerlink { - position: relative; - left: 0px; + position: absolute; z-index: 1; } diff --git a/docs/_build/html/_static/comment-bright.png b/docs/_build/html/_static/comment-bright.png deleted file mode 100644 index 15e27edb12ac25701ac0ac21b97b52bb4e45415e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 756 zcmVgfIX78 z$8Pzv({A~p%??+>KickCb#0FM1rYN=mBmQ&Nwp<#JXUhU;{|)}%&s>suq6lXw*~s{ zvHx}3C%<;wE5CH!BR{p5@ml9ws}y)=QN-kL2?#`S5d*6j zk`h<}j1>tD$b?4D^N9w}-k)bxXxFg>+#kme^xx#qg6FI-%iv2U{0h(Y)cs%5a|m%Pn_K3X_bDJ>EH#(Fb73Z zfUt2Q3B>N+ot3qb*DqbTZpFIn4a!#_R-}{?-~Hs=xSS6p&$sZ-k1zDdtqU`Y@`#qL z&zv-~)Q#JCU(dI)Hf;$CEnK=6CK50}q7~wdbI->?E07bJ0R;!GSQTs5Am`#;*WHjvHRvY?&$Lm-vq1a_BzocI^ULXV!lbMd%|^B#fY;XX)n<&R^L z=84u1e_3ziq;Hz-*k5~zwY3*oDKt0;bM@M@@89;@m*4RFgvvM_4;5LB!@OB@^WbVT zjl{t;a8_>od-~P4 m{5|DvB&z#xT;*OnJqG}gk~_7HcNkCr0000W zanA~u9RIXo;n7c96&U)YLgs-FGlx~*_c{Jgvesu1E5(8YEf&5wF=YFPcRe@1=MJmi zag(L*xc2r0(slpcN!vC5CUju;vHJkHc*&70_n2OZsK%O~A=!+YIw z7zLLl7~Z+~RgWOQ=MI6$#0pvpu$Q43 zP@36QAmu6!_9NPM?o<1_!+stoVRRZbW9#SPe!n;#A_6m8f}|xN1;H{`0RoXQ2LM47 zt(g;iZ6|pCb@h2xk&(}S3=EVBUO0e90m2Lp5CB<(SPIaB;n4))3JB87Or#XPOPcum z?<^(g+m9}VNn4Y&B`g8h{t_$+RB1%HKRY6fjtd-<7&EsU;vs0GM(Lmbhi%Gwcfs0FTF}T zL{_M6Go&E0Eg8FuB*(Yn+Z*RVTBE@10eIOb3El^MhO`GabDll(V0&FlJi2k^;q8af zkENdk2}x2)_KVp`5OAwXZM;dG0?M-S)xE1IKDi6BY@5%Or?#aZ9$gcX)dPZ&wA1a< z$rFXHPn|TBf`e?>Are8sKtKrKcjF$i^lp!zkL?C|y^vlHr1HXeVJd;1I~g&Ob-q)& z(fn7s-KI}G{wnKzg_U5G(V%bX6uk zIa+<@>rdmZYd!9Y=C0cuchrbIjuRB_Wq{-RXlic?flu1*_ux}x%(HDH&nT`k^xCeC ziHi1!ChH*sQ6|UqJpTTzX$aw8e(UfcS^f;6yBWd+(1-70zU(rtxtqR%j z-lsH|CKQJXqD{+F7V0OTv8@{~(wp(`oIP^ZykMWgR>&|RsklFMCnOo&Bd{le} zV5F6424Qzl;o2G%oVvmHgRDP9!=rK8fy^!yV8y*4p=??uIRrrr0?>O!(z*g5AvL2!4z0{sq%vhG*Po}`a<6%kTK5TNhtC8}rXNu&h^QH4A&Sk~Autm*s~45(H7+0bi^MraaRVzr05hQ3iK?j` zR#U@^i0WhkIHTg29u~|ypU?sXCQEQgXfObPW;+0YAF;|5XyaMAEM0sQ@4-xCZe=0e z7r$ofiAxn@O5#RodD8rh5D@nKQ;?lcf@tg4o+Wp44aMl~c47azN_(im0N)7OqdPBC zGw;353_o$DqGRDhuhU$Eaj!@m000000NkvXXu0mjfjZ7Z_ diff --git a/docs/_build/html/_static/custom.css b/docs/_build/html/_static/custom.css deleted file mode 100644 index 2a924f1d6..000000000 --- a/docs/_build/html/_static/custom.css +++ /dev/null @@ -1 +0,0 @@ -/* This file intentionally left blank. */ diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js index d8928926b..daccd209d 100644 --- a/docs/_build/html/_static/doctools.js +++ b/docs/_build/html/_static/doctools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -87,14 +87,13 @@ jQuery.fn.highlightText = function(text, className) { node.nextSibling)); node.nodeValue = val.substr(0, pos); if (isInSVG) { - var bbox = span.getBBox(); var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - rect.x.baseVal.value = bbox.x; + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; rect.y.baseVal.value = bbox.y; rect.width.baseVal.value = bbox.width; rect.height.baseVal.value = bbox.height; rect.setAttribute('class', className); - var parentOfText = node.parentNode.parentNode; addItems.push({ "parent": node.parentNode, "target": rect}); @@ -150,7 +149,9 @@ var Documentation = { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); - + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } }, /** @@ -282,10 +283,11 @@ var Documentation = { }, initOnKeyListeners: function() { - $(document).keyup(function(event) { + $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); @@ -310,4 +312,4 @@ _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); -}); \ No newline at end of file +}); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js index b3bca8379..2f08d25ff 100644 --- a/docs/_build/html/_static/documentation_options.js +++ b/docs/_build/html/_static/documentation_options.js @@ -1,9 +1,12 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.3', + VERSION: '1.0.0', LANGUAGE: 'None', COLLAPSE_INDEX: false, + BUILDER: 'html', FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt' + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false }; \ No newline at end of file diff --git a/docs/_build/html/_static/down-pressed.png b/docs/_build/html/_static/down-pressed.png deleted file mode 100644 index 5756c8cad8854722893dc70b9eb4bb0400343a39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`OFdm2Ln;`PZ^+1>KjR?B@S0W7 z%OS_REiHONoJ6{+Ks@6k3590|7k9F+ddB6!zw3#&!aw#S`x}3V3&=A(a#84O-&F7T z^k3tZB;&iR9siw0|F|E|DAL<8r-F4!1H-;1{e*~yAKZN5f0|Ei6yUmR#Is)EM(Po_ zi`qJR6|P<~+)N+kSDgL7AjdIC_!O7Q?eGb+L+qOjm{~LLinM4NHn7U%HcK%uoMYO5 VJ~8zD2B3o(JYD@<);T3K0RV0%P>BEl diff --git a/docs/_build/html/_static/down.png b/docs/_build/html/_static/down.png deleted file mode 100644 index 1b3bdad2ceffae91cee61b32f3295f9bbe646e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6CVIL!hEy=F?b*7pIY7kW{q%Rg zx!yQ<9v8bmJwa`TQk7YSw}WVQ()mRdQ;TC;* diff --git a/docs/_build/html/_static/jquery-3.2.1.js b/docs/_build/html/_static/jquery-3.2.1.js deleted file mode 100644 index d2d8ca479..000000000 --- a/docs/_build/html/_static/jquery-3.2.1.js +++ /dev/null @@ -1,10253 +0,0 @@ -/*! - * jQuery JavaScript Library v3.2.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2017-03-20T18:59Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.2.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.3 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-08-08 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Simple selector that can be filtered directly, removing non-Elements - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - // Complex selector, compare the two sets, removing non-Elements - qualifier = jQuery.filter( qualifier, elements ); - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; - } ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( jQuery.isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ jQuery.camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ jQuery.camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( jQuery.camelCase ); - } else { - key = jQuery.camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - jQuery.contains( elem.ownerDocument, elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - initialInUnit = initialInUnit / scale; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); - -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE <=9 only - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
    " ], - col: [ 2, "", "
    " ], - tr: [ 2, "", "
    " ], - td: [ 3, "", "
    " ], - - _default: [ 0, "", "" ] -}; - -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: jQuery.isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( ">tbody", elem )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rmargin = ( /^margin/ ); - -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + - "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - - // Support: Android 4.0 - 4.3 only - // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelMarginRight: function() { - computeStyleTests(); - return pixelMarginRightVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. -function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; - } - return ret; -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; - - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with computed style - var valueIsBorderBox, - styles = getStyles( elem ), - val = curCSS( elem, name, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { - return val; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Fall back to offsetWidth/Height when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - if ( val === "auto" ) { - val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; - } - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - "float": "cssFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - } ) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ); - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ name ] = value; - value = jQuery.css( elem, name ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = jQuery.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 13 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( type === "string" ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, isFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = jQuery.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 13 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available, append data to url - if ( s.data ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - + + + + + + + + + Index — pyEMU 1.0.0 documentation + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    -
    -
    -
    + + + + +
    + +
    + -
    + + + + + + + + + + + + + + +
    + +
      + +
    • Docs »
    • + +
    • Index
    • + + +
    • + + + +
    • + +
    + + +
    +
    +
    +

    Index

    - A + _ + | A | B | C | D @@ -61,1444 +170,5350 @@

    Index

    | V | W | X + | Y | Z
    -

    A

    +

    _

    - -
    +
  • __contribution_from_parameters() (pyemu.sc.Schur method) + +
  • +
  • __eq__() (pyemu.utils.helpers.SpatialReference method) + +
  • +
  • __fromfile() (pyemu.la.LinearAnalysis method) + +
  • +
  • __getattr__() (pyemu.en.Ensemble method) + +
  • +
  • __getitem__() (pyemu.en.Iloc method) + +
  • +
  • __gt__() (pyemu.utils.geostats.GeoStruct method) + +
  • +
  • __init() (pyemu.Jco method) + +
  • +
  • __load_jco() (pyemu.la.LinearAnalysis method) + +
  • +
  • __load_obscov() (pyemu.la.LinearAnalysis method) + +
  • +
  • __load_omitted_jco() (pyemu.ErrVar method) + +
  • +
  • __load_omitted_parcov() (pyemu.ErrVar method) + +
  • +
  • __load_omitted_predictions() (pyemu.ErrVar method) + +
  • +
  • __load_parcov() (pyemu.la.LinearAnalysis method) + +
  • +
  • __load_predictions() (pyemu.la.LinearAnalysis method) + +
  • +
  • +
  • __load_pst() (pyemu.la.LinearAnalysis method) -

    B

    - - - -
    +
  • __pow__() (pyemu.en.Ensemble method) + +
  • +
  • +
  • __repr__() (pyemu.en.Ensemble method) -

    C

    - - - -
    - -

    D

    - - - -
    +
  • __sub__() (pyemu.en.Ensemble method)
  • -
    +
  • _adjust_weights_by_list() (pyemu.prototypes.Pst method) -

    E

    - - - -
    +
  • _apply_rotation() (pyemu.utils.geostats.Vario2d method) + +
  • +
  • _archive() (pyemu.prototypes.EvolAlg method) + +
  • +
  • _calc_delta() (pyemu.prototypes.ensemble_method.EnsembleMethod method) + +
  • +
  • _calc_factors_mp() (pyemu.utils.geostats.OrdinaryKrige method) + +
  • +
  • _calc_factors_org() (pyemu.utils.geostats.OrdinaryKrige method) + +
  • +
  • _calc_obs() (pyemu.prototypes.ensemble_method.EnsembleMethod method) + +
  • - -

    F

    - - - + +
    +
  • _calc_obs_condor() (pyemu.prototypes.ensemble_method.EnsembleMethod method)
  • -
  • fehalf (pyemu.la.LinearAnalysis attribute) -
  • -
  • FFMT() (in module pyemu.pst.pst_controldata) +
  • _calc_obs_local() (pyemu.prototypes.ensemble_method.EnsembleMethod method)
  • -
  • find_rowcol_indices() (pyemu.mat.mat_handler.Matrix static method) -
  • -
  • first_forecast() (pyemu.ev.ErrVar method) -
  • -
  • first_order_pearson_tikhonov() (in module pyemu.utils.helpers) +
  • _cast_df_from_lines() (pyemu.prototypes.Pst static method) + +
  • +
  • _cast_prior_df_from_lines() (pyemu.prototypes.Pst method) + +
  • +
  • _check_diff() (in module pyemu.utils)
  • -
  • forecasts +
  • _check_var_len() (in module pyemu.utils)
  • -
  • forecasts_iter (pyemu.la.LinearAnalysis attribute) -
  • -
  • formatted_values (pyemu.pst.pst_controldata.ControlData attribute) -
  • -
  • FOSM +
  • _condition_on_par_knowledge() (in module pyemu.utils) + +
  • +
  • _cov_points() (pyemu.utils.geostats.OrdinaryKrige method) + +
  • +
  • _date_parser() (in module pyemu.utils)
  • +
  • _df (pyemu.en.Ensemble attribute) + +
  • +
  • _dist_calcs() (pyemu.utils.geostats.OrdinaryKrige method) + +
  • -
    + +

    A

    + + + +
    + +

    B

    + + + +
    + +

    C

    + + + +
    + +

    D

    + + + +
    + +

    E

    + + + +
    + +

    F

    + + + +
    + +

    G

    + + + +
    + +

    H

    + + + +
    + +

    I

    + + + +
    + +

    J

    + + + +
    + +

    K

    + + + +
    + +

    L

    + + + +
    + +

    M

    + + + +
    + +

    N

    + + + +
    + +

    O

    + + + +
    + +

    P

    + + -
    +
  • phi_vector() (pyemu.en.ObservationEnsemble property), [1] -

    G

    - - -
    +
  • +
  • pst (pyemu.en.Ensemble attribute), [1] -

    H

    - - - -
    +
  • pst() (pyemu.la.LinearAnalysis property), [1] + +
  • +
  • +
  • pst_config (in module pyemu.pst.pst_utils) -

    I

    - - - -
    +
  • +
  • + pyemu.prototypes -

    J

    - - -
    +
  • + pyemu.prototypes.ensemble_method + +
  • +
  • + pyemu.prototypes.moouu + +
  • +
  • + pyemu.pst + +
  • +
  • + pyemu.pst.pst_controldata + +
  • +
  • + pyemu.pst.pst_handler + +
  • +
  • + pyemu.pst.pst_utils + +
  • +
  • + pyemu.pyemu_warnings + +
  • +
  • + pyemu.sc + +
  • +
  • + pyemu.utils + +
  • +
  • + pyemu.utils.geostats + +
  • +
  • + pyemu.utils.gw_utils + +
  • +
  • + pyemu.utils.helpers + +
  • +
  • + pyemu.utils.optimization + +
  • +
  • + pyemu.utils.os_utils + +
  • +
  • + pyemu.utils.pp_utils + +
  • +
  • + pyemu.utils.pst_from + +
  • +
  • + pyemu.utils.smp_utils + +
  • +
  • PyemuWarning, [1], [2], [3], [4], [5], [6], [7], [8]
  • -

    K

    +

    Q

    -

    L

    +

    R

    - -
    +
  • read_ins_file() (pyemu.pst.pst_utils.InstructionFile method), [1]
  • -
  • log() (pyemu.logger.Logger method) +
  • read_output_file() (pyemu.pst.pst_utils.InstructionFile method), [1]
  • -
  • log_indexer (pyemu.en.ParameterEnsemble attribute) +
  • read_parfile() (in module pyemu.pst.pst_utils), [1]
  • -
  • Logger (class in pyemu.logger) +
  • read_pestpp_runstorage() (in module pyemu.utils) + +
  • +
  • read_resfile() (in module pyemu.pst.pst_utils), [1]
  • -
    +
  • read_sgems_variogram_xml() (in module pyemu.utils) -

    M

    - - - -
    +
  • reduce_stack_with_risk_shift() (pyemu.prototypes.moouu.ParetoObjFunc method), [1] + +
  • +
  • reg_data (pyemu.prototypes.Pst attribute) + +
  • +
  • REG_DEFAULT_LINES (in module pyemu.pst.pst_controldata)
  • -
  • Monte Carlo ensemble +
  • REG_VARIABLE_LINES (in module pyemu.pst.pst_controldata)
  • -
  • Monte Carlo observation realization +
  • RegData (class in pyemu.pst.pst_controldata), [1]
  • -
  • Monte Carlo parameter realization +
  • register_vcs_handler() (in module pyemu._version)
  • -
  • MonteCarlo (class in pyemu.mc) +
  • render() (in module pyemu._version)
  • -
  • mult_isaligned() (pyemu.mat.mat_handler.Matrix method) +
  • render_git_describe() (in module pyemu._version)
  • - -

    N

    - - -
    +
  • res() (pyemu.prototypes.Pst property) + +
  • +
  • res_1to1() (in module pyemu.plot.plot_utils), [1]
  • -
  • next_most_par_contribution() (pyemu.sc.Schur method) +
  • res_from_en() (in module pyemu.pst.pst_utils), [1]
  • -
  • nnz_obs (pyemu.pst.pst_handler.Pst attribute) +
  • res_from_obseravtion_data() (in module pyemu.pst.pst_utils), [1]
  • -
  • nnz_obs_groups (pyemu.pst.pst_handler.Pst attribute) +
  • res_phi_pie() (in module pyemu.plot.plot_utils), [1]
  • -
    +
  • +
  • reset_x() (pyemu.mat.mat_handler.Matrix method), [1] -

    O

    - - +
    + +

    S

    + -
    +
  • +
  • save_coo() (in module pyemu.mat) -

    P

    - - + - +
    + +

    T

    + + -
    +
  • +
  • to_csv() (pyemu.en.Ensemble method), [1] -

    Q

    - - -
    +
  • to_dataframe() (pyemu.mat.mat_handler.Matrix method), [1] -

    R

    - - -
    +
  • transform (pyemu.utils.geostats.GeoStruct attribute), [1] + +
  • +
  • transform() (pyemu.en.Ensemble method), [1] + +
  • +
  • transpose() (pyemu.mat.mat_handler.Matrix property), [1] + +
  • +
  • try_parse_name_metadata() (pyemu.prototypes.Pst method) + +
  • +
  • try_process_output_file() (in module pyemu.pst.pst_utils), [1]
  • +
  • try_process_output_pst() (in module pyemu.pst.pst_utils), [1] +
  • -

    S

    +

    U

    + +
    + +

    V

    + + +
    + +

    W

    + + -
    - -

    T

    - -
    +
  • write_par_summary_table() (pyemu.prototypes.Pst method) + +
  • +
  • write_parfile() (in module pyemu.pst.pst_utils), [1]
  • -
  • to_struct_file() (pyemu.utils.geostats.GeoStruct method) +
  • write_pp_file() (in module pyemu.utils)
  • -
  • to_uncfile() (pyemu.mat.mat_handler.Cov method) -
  • -
  • transform (pyemu.utils.geostats.GeoStruct attribute) +
  • write_pp_shapfile() (in module pyemu.utils) + +
  • +
  • write_psts() (pyemu.mc.MonteCarlo method), [1]
  • -
  • try_process_ins_file() (in module pyemu.pst.pst_utils) +
  • write_to_template() (in module pyemu.pst.pst_utils), [1]
  • -
  • try_run_inschek() (in module pyemu.pst.pst_utils) +
  • write_zone_tpl() (in module pyemu.utils) + +
  • -

    U

    +

    X

    - -
    +
  • xcenter (pyemu.utils.helpers.SpatialReference attribute), [1]
  • -
    +
  • xcenter() (pyemu.utils.helpers.SpatialReference property), [1] -

    V

    - - - -
    +
  • xcentergrid() (pyemu.utils.helpers.SpatialReference property), [1] + +
  • +
  • xedge (pyemu.utils.helpers.SpatialReference attribute), [1] + +
  • - -

    W

    - +
    + +

    Y

    + +
    -

    X

    +

    Z

    - -
    +
  • (pyemu.prototypes.Cov property) +
  • +
  • (pyemu.utils.Cov property) +
  • + +
  • zero2d() (pyemu.mat.mat_handler.Matrix property), [1] -

    Z

    - -
    + + - - - - -
    - - - + + + + + + + + + - + + + \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index c5a6c713e..b6e312896 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -1,213 +1,204 @@ - - - - - - - Welcome to pyEMU’s documentation! — pyEMU 0.3 documentation - - - - - - - - - - - - + + + + + + + + + + Welcome to pyEMU’s documentation! — pyEMU 1.0.0 documentation + + + + - - + + + + + + + + + + + + -
    -
    -
    + + + + + + + + + + + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    +
    + +
    +

    Welcome to pyEMU’s documentation!

    +

    This documentation is more-or-less complete. However, the example notebooks in the repo +provide a more complete picture of how the various components of pyEMU function.

    -
    -

    Technical Documentation

    + -
    -

    References

    - - - - - -
    [BPL+16]Bakker, M., Post, V., Langevin, C. D., Hughes, J. D., White, J. T., -Starn, J. J. and Fienen, M. N., 2016, Scripting MODFLOW model development using -Python and FloPy: Groundwater, v. 54, p. 733-739, https://doi.org/10.1111/gwat.12413.
    - - - - - -
    [DOH15]Doherty, John, 2015. Calibration and -Uncertainty Analysis for Complex Environmental Models: Brisbane, Australia, Watermark Numerical -Computing, http://www.pesthomepage.org/Home.php .
    - - - - - -
    [FB16]Fienen, M.N., and Bakker, Mark, 2016, HESS Opinions: Repeatable research— what hydrologists can learn -from the Duke cancer research scandal: Hydrology and Earth System Sciences, v. 20, no. 9, pg. 3739-3743, -https://doi.org/10.5194/hess-20-3739-2016 .
    - - - - - -
    [KB09]Beven, Keith, 2009, Environmental modelling— an uncertain future?: London, Routledge, 310 p.
    - - - - - -
    [KRP+16]Kluyver, Thomas; Ragan-Kelley, Benjamin; Perez, Fernado; Granger, Brian; Bussonnier, Matthias; -Federic, Jonathan; Kelley, Kyle; Hamrick, Jessica; Grout, Jason; Corlay, Sylvain; Ivanov, Paul; Avila, Damian; -Aballa, Safia; Willing, Carol; and Jupyter Development Team, 2016, Jupyter Notebooks– a publishing -format for reproducible computational workflows: in Positioning and Power in Academic -Publishing– Players, Agents, and Agendas. F. Loizides and B. Schmidt (eds). -IOS Press, https://doi.org/10.3233/978-1-61499-649-1-87 and https://jupyter.org .
    - - - - - -
    [MCK10]McKinney, Wes, 2010, Data structures for statistical computing in python: -in Proceedings of the 9th Python in Science Conference, Stefan van -der Walt and Jarrod Millman (eds.), p. 51-56, https://pandas.pydata.org/index.html .
    - - - - - -
    [PSF18]The Python Software Foundation, 2018, Documentation, The python tutorial, -9. Classes: https://docs.python.org/3.6/tutorial/classes.html .
    - - - - - -
    [RS16]Reges, Stuart, and Stepp, Marty, 2016, Building Java Programs— A Back to Basics -Approach, Fourth Edition: Boston, Pearson, 1194 p.
    - - - - - -
    [WFD16]White, J.T., Fienen, M.N., and Doherty, J.E., 2016, A python framework -for environmental model uncertainty analysis: Environmental Modeling & -Software, v. 85, pg. 217-228, https://doi.org/10.1016/j.envsoft.2016.08.017 .
    - - - - - -
    [WWHD15]Welter, D.E., White, J.T., Hunt, R.J., and Doherty, J.E., 2015, -Approaches in highly parameterized inversion: PEST++ Version 3, a Parameter -ESTimation and uncertainty analysis software suite optimized for large -environmental models: U.S. Geological Survey Techniques and Methods, book 7, -section C12, 54 p., https://doi.org/10.3133/tm7C12 .
    -
    +
    +
    - -
    -
    - -
    -
    - - + + +
    + + + + + + - + + + \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index 362349c54846283d2f93286b18edcff9bf7d00bf..509fad54bbbf6568db3d366adaf5088807c7415a 100644 GIT binary patch delta 12619 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 - - - - - - Python Module Index — pyEMU 0.3 documentation - - - - - - - - - - - + + + + + + + + + Python Module Index — pyEMU 1.0.0 documentation + + + + - + + + + + + + + + + + + - + + + + + + + + + -
    -
    -
    + + + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + +
    + +
      + +
    • Docs »
    • + +
    • Python Module Index
    • + + +
    • + +
    • + +
    + + +
    +
    + - -
    -
    - -
    -
    - - + +
    + + + + + + + - + + + \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html index ddcb4dc8a..cbefa53d5 100644 --- a/docs/_build/html/search.html +++ b/docs/_build/html/search.html @@ -1,96 +1,205 @@ - - - - - - - Search — pyEMU 0.3 documentation - - - - - - - - - - - + + + + + + - + - - + Search — pyEMU 1.0.0 documentation + + + + + + + + - + + + + + + + + + + - + + + + + + + + + + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + +
    + +
      + +
    • Docs »
    • + +
    • Search
    • + + +
    • + + + +
    • + +
    + + +
    +
    +
    +
    + + +
    +
    +
    - -
    -
    - -
    -
    - - + +
    + + + + + + + - + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index 3cac033e0..85f79272f 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["index","source/Monte_carlo_page","source/ensembles","source/glossary","source/modules","source/oop","source/pst_demo","source/pyemu","source/pyemu.mat","source/pyemu.pst","source/pyemu.utils"],envversion:53,filenames:["index.rst","source\\Monte_carlo_page.rst","source\\ensembles.rst","source\\glossary.rst","source\\modules.rst","source\\oop.rst","source\\pst_demo.ipynb","source\\pyemu.rst","source\\pyemu.mat.rst","source\\pyemu.pst.rst","source\\pyemu.utils.rst"],objects:{"":{pyemu:[7,0,0,"-"]},"pyemu.en":{Ensemble:[7,1,1,""],ObservationEnsemble:[7,1,1,""],ParameterEnsemble:[7,1,1,""]},"pyemu.en.Ensemble":{as_pyemu_matrix:[7,2,1,""],copy:[7,2,1,""],covariance_matrix:[7,2,1,""],draw:[7,2,1,""],drop:[7,2,1,""],dropna:[7,2,1,""],from_dataframe:[7,3,1,""],plot:[7,2,1,""],reseed:[7,4,1,""]},"pyemu.en.ObservationEnsemble":{add_base:[7,2,1,""],copy:[7,2,1,""],draw:[7,2,1,""],from_binary:[7,3,1,""],from_id_gaussian_draw:[7,3,1,""],mean_values:[7,5,1,""],names:[7,5,1,""],nonzero:[7,5,1,""],phi_vector:[7,5,1,""],to_binary:[7,2,1,""]},"pyemu.en.ParameterEnsemble":{add_base:[7,2,1,""],adj_names:[7,5,1,""],copy:[7,2,1,""],draw:[7,2,1,""],dropna:[7,2,1,""],enforce:[7,2,1,""],enforce_drop:[7,2,1,""],enforce_reset:[7,2,1,""],enfore_scale:[7,2,1,""],fixed_indexer:[7,5,1,""],from_binary:[7,3,1,""],from_gaussian_draw:[7,3,1,""],from_parfiles:[7,3,1,""],from_sparse_gaussian_draw:[7,3,1,""],from_uniform_draw:[7,3,1,""],istransformed:[7,5,1,""],lbnd:[7,5,1,""],log_indexer:[7,5,1,""],mean_values:[7,5,1,""],names:[7,5,1,""],project:[7,2,1,""],read_parfiles:[7,2,1,""],read_parfiles_prefix:[7,2,1,""],to_binary:[7,2,1,""],to_csv:[7,2,1,""],to_parfiles:[7,2,1,""],ubnd:[7,5,1,""]},"pyemu.ev":{ErrVar:[7,1,1,""]},"pyemu.ev.ErrVar":{G:[7,2,1,""],I_minus_R:[7,2,1,""],R:[7,2,1,""],first_forecast:[7,2,1,""],first_parameter:[7,2,1,""],first_prediction:[7,2,1,""],get_errvar_dataframe:[7,2,1,""],get_identifiability_dataframe:[7,2,1,""],omitted_jco:[7,5,1,""],omitted_parcov:[7,5,1,""],omitted_predictions:[7,5,1,""],second_forecast:[7,2,1,""],second_parameter:[7,2,1,""],second_prediction:[7,2,1,""],third_forecast:[7,2,1,""],third_parameter:[7,2,1,""],third_prediction:[7,2,1,""],variance_at:[7,2,1,""]},"pyemu.la":{LinearAnalysis:[7,1,1,""]},"pyemu.la.LinearAnalysis":{adj_par_names:[7,2,1,""],adjust_obscov_resfile:[7,2,1,""],apply_karhunen_loeve_scaling:[7,2,1,""],clean:[7,2,1,""],drop_prior_information:[7,2,1,""],fehalf:[7,5,1,""],forecast_names:[7,5,1,""],forecasts:[7,5,1,""],forecasts_iter:[7,5,1,""],get:[7,2,1,""],get_cso_dataframe:[7,2,1,""],get_par_css_dataframe:[7,2,1,""],jco:[7,5,1,""],mle_covariance:[7,5,1,""],mle_parameter_estimate:[7,5,1,""],nnz_obs_names:[7,5,1,""],obscov:[7,5,1,""],parcov:[7,5,1,""],predictions:[7,5,1,""],predictions_iter:[7,5,1,""],prior_forecast:[7,5,1,""],prior_parameter:[7,5,1,""],prior_prediction:[7,5,1,""],pst:[7,5,1,""],qhalf:[7,5,1,""],qhalfx:[7,5,1,""],reset_obscov:[7,2,1,""],reset_parcov:[7,2,1,""],reset_pst:[7,2,1,""],xtqx:[7,5,1,""]},"pyemu.logger":{Logger:[7,1,1,""]},"pyemu.logger.Logger":{items:[7,5,1,""],log:[7,2,1,""],lraise:[7,2,1,""],statement:[7,2,1,""],warn:[7,2,1,""]},"pyemu.mat":{mat_handler:[8,0,0,"-"]},"pyemu.mat.mat_handler":{Cov:[8,1,1,""],Jco:[8,1,1,""],Matrix:[8,1,1,""],SparseMatrix:[8,1,1,""],concat:[8,6,1,""],get_common_elements:[8,6,1,""],save_coo:[8,6,1,""]},"pyemu.mat.mat_handler.Cov":{condition_on:[8,2,1,""],draw:[8,2,1,""],from_observation_data:[8,3,1,""],from_obsweights:[8,3,1,""],from_parameter_data:[8,3,1,""],from_parbounds:[8,3,1,""],from_uncfile:[8,3,1,""],get_uncfile_dimensions:[8,4,1,""],identity:[8,5,1,""],identity_like:[8,3,1,""],names:[8,5,1,""],replace:[8,2,1,""],to_pearson:[8,2,1,""],to_uncfile:[8,2,1,""],zero:[8,5,1,""]},"pyemu.mat.mat_handler.Jco":{from_pst:[8,3,1,""],nobs:[8,5,1,""],npar:[8,5,1,""],obs_names:[8,5,1,""],par_names:[8,5,1,""],replace_cols:[8,2,1,""]},"pyemu.mat.mat_handler.Matrix":{"char":[8,5,1,""],"double":[8,5,1,""],T:[8,5,1,""],align:[8,2,1,""],as_2d:[8,5,1,""],binary_header_dt:[8,5,1,""],binary_rec_dt:[8,5,1,""],coo_rec_dt:[8,5,1,""],copy:[8,2,1,""],df:[8,2,1,""],drop:[8,2,1,""],element_isaligned:[8,2,1,""],extend:[8,2,1,""],extract:[8,2,1,""],find_rowcol_indices:[8,4,1,""],from_ascii:[8,3,1,""],from_binary:[8,3,1,""],from_dataframe:[8,3,1,""],from_fortranfile:[8,3,1,""],from_names:[8,3,1,""],full_s:[8,5,1,""],get:[8,2,1,""],get_diagonal_vector:[8,2,1,""],get_maxsing:[8,2,1,""],hadamard_product:[8,2,1,""],indices:[8,2,1,""],integer:[8,5,1,""],inv:[8,5,1,""],mult_isaligned:[8,2,1,""],ncol:[8,5,1,""],new_obs_length:[8,5,1,""],new_par_length:[8,5,1,""],newx:[8,5,1,""],nrow:[8,5,1,""],obs_length:[8,5,1,""],old_indices:[8,2,1,""],par_length:[8,5,1,""],pseudo_inv:[8,2,1,""],pseudo_inv_components:[8,2,1,""],read_ascii:[8,4,1,""],read_binary:[8,4,1,""],reset_x:[8,2,1,""],s:[8,5,1,""],shape:[8,5,1,""],sqrt:[8,5,1,""],to_ascii:[8,2,1,""],to_binary:[8,2,1,""],to_coo:[8,2,1,""],to_dataframe:[8,2,1,""],to_sparse:[8,2,1,""],transpose:[8,5,1,""],u:[8,5,1,""],v:[8,5,1,""],x:[8,5,1,""],zero2d:[8,5,1,""]},"pyemu.mat.mat_handler.SparseMatrix":{block_extend_ip:[8,2,1,""],from_binary:[8,3,1,""],from_coo:[8,3,1,""],from_matrix:[8,3,1,""],get_matrix:[8,2,1,""],get_sparse_matrix:[8,2,1,""],shape:[8,5,1,""],to_coo:[8,2,1,""],to_matrix:[8,2,1,""]},"pyemu.mc":{MonteCarlo:[7,1,1,""]},"pyemu.mc.MonteCarlo":{draw:[7,2,1,""],get_nsing:[7,2,1,""],get_null_proj:[7,2,1,""],num_reals:[7,5,1,""],obsensemble:[7,5,1,""],parensemble:[7,5,1,""],project_parensemble:[7,2,1,""],write_psts:[7,2,1,""]},"pyemu.pst":{pst_controldata:[9,0,0,"-"],pst_handler:[9,0,0,"-"],pst_utils:[9,0,0,"-"]},"pyemu.pst.pst_controldata":{ControlData:[9,1,1,""],FFMT:[9,6,1,""],IFMT:[9,6,1,""],RegData:[9,1,1,""],SFMT:[9,6,1,""],SFMT_LONG:[9,6,1,""],SvdData:[9,1,1,""]},"pyemu.pst.pst_controldata.ControlData":{copy:[9,2,1,""],formatted_values:[9,5,1,""],get_dataframe:[9,4,1,""],parse_values_from_lines:[9,2,1,""],write:[9,2,1,""]},"pyemu.pst.pst_controldata.RegData":{write:[9,2,1,""]},"pyemu.pst.pst_controldata.SvdData":{parse_values_from_lines:[9,2,1,""],write:[9,2,1,""]},"pyemu.pst.pst_handler":{Pst:[9,1,1,""]},"pyemu.pst.pst_handler.Pst":{add_observations:[9,2,1,""],add_parameters:[9,2,1,""],add_pi_equation:[9,2,1,""],add_transform_columns:[9,2,1,""],adj_par_names:[9,5,1,""],adjust_weights:[9,2,1,""],adjust_weights_by_list:[9,2,1,""],adjust_weights_recfile:[9,2,1,""],adjust_weights_resfile:[9,2,1,""],build_increments:[9,2,1,""],calculate_pertubations:[9,2,1,""],control_data:[9,5,1,""],enforce_bounds:[9,2,1,""],estimation:[9,5,1,""],flex_load:[9,2,1,""],forecast_names:[9,5,1,""],from_io_files:[9,3,1,""],from_par_obs_names:[9,3,1,""],get:[9,2,1,""],get_res_stats:[9,2,1,""],load:[9,2,1,""],nnz_obs:[9,5,1,""],nnz_obs_groups:[9,5,1,""],nnz_obs_names:[9,5,1,""],nobs:[9,5,1,""],npar:[9,5,1,""],npar_adj:[9,5,1,""],nprior:[9,5,1,""],obs_groups:[9,5,1,""],obs_names:[9,5,1,""],observation_data:[9,5,1,""],par_groups:[9,5,1,""],par_names:[9,5,1,""],parameter_data:[9,5,1,""],parrep:[9,2,1,""],phi:[9,5,1,""],phi_components:[9,5,1,""],phi_components_normalized:[9,5,1,""],plot:[9,2,1,""],prior_groups:[9,5,1,""],prior_information:[9,5,1,""],prior_names:[9,5,1,""],proportional_weights:[9,2,1,""],rectify_pgroups:[9,2,1,""],rectify_pi:[9,2,1,""],reg_data:[9,5,1,""],res:[9,5,1,""],run:[9,2,1,""],sanity_checks:[9,2,1,""],set_res:[9,2,1,""],svd_data:[9,5,1,""],tied:[9,5,1,""],write:[9,2,1,""],write_input_files:[9,2,1,""],write_obs_summary_table:[9,2,1,""],write_par_summary_table:[9,2,1,""],zero_order_tikhonov:[9,2,1,""],zero_weight_obs_names:[9,5,1,""]},"pyemu.pst.pst_utils":{FFMT:[9,6,1,""],IFMT:[9,6,1,""],SFMT:[9,6,1,""],SFMT_LONG:[9,6,1,""],clean_missing_exponent:[9,6,1,""],dataframe_to_smp:[9,6,1,""],date_parser:[9,6,1,""],del_rw:[9,6,1,""],generic_pst:[9,6,1,""],get_marker_indices:[9,6,1,""],get_phi_comps_from_recfile:[9,6,1,""],parse_ins_file:[9,6,1,""],parse_ins_string:[9,6,1,""],parse_tpl_file:[9,6,1,""],populate_dataframe:[9,6,1,""],pst_from_io_files:[9,6,1,""],read_parfile:[9,6,1,""],read_resfile:[9,6,1,""],res_from_obseravtion_data:[9,6,1,""],smp_to_dataframe:[9,6,1,""],smp_to_ins:[9,6,1,""],start_slaves:[9,6,1,""],str_con:[9,6,1,""],try_process_ins_file:[9,6,1,""],try_run_inschek:[9,6,1,""],write_input_files:[9,6,1,""],write_parfile:[9,6,1,""],write_to_template:[9,6,1,""]},"pyemu.sc":{Schur:[7,1,1,""]},"pyemu.sc.Schur":{get_added_obs_group_importance:[7,2,1,""],get_added_obs_importance:[7,2,1,""],get_conditional_instance:[7,2,1,""],get_forecast_summary:[7,2,1,""],get_par_contribution:[7,2,1,""],get_par_group_contribution:[7,2,1,""],get_parameter_summary:[7,2,1,""],get_removed_obs_group_importance:[7,2,1,""],get_removed_obs_importance:[7,2,1,""],map_forecast_estimate:[7,5,1,""],map_parameter_estimate:[7,5,1,""],next_most_important_added_obs:[7,2,1,""],next_most_par_contribution:[7,2,1,""],obs_group_importance:[7,2,1,""],pandas:[7,5,1,""],posterior_forecast:[7,5,1,""],posterior_parameter:[7,5,1,""],posterior_prediction:[7,5,1,""]},"pyemu.smoother":{EnsembleMethod:[7,1,1,""],EnsembleSmoother:[7,1,1,""],Phi:[7,1,1,""]},"pyemu.smoother.EnsembleMethod":{initialize:[7,2,1,""],update:[7,2,1,""]},"pyemu.smoother.EnsembleSmoother":{get_localizer:[7,2,1,""],initialize:[7,2,1,""],update:[7,2,1,""]},"pyemu.smoother.Phi":{get_meas_and_regul_phi:[7,2,1,""],get_residual_obs_matrix:[7,2,1,""],get_residual_par_matrix:[7,2,1,""],phi_vec_dict:[7,5,1,""],report:[7,2,1,""],update:[7,2,1,""],write:[7,2,1,""]},"pyemu.utils":{geostats:[10,0,0,"-"],gw_utils:[10,0,0,"-"],helpers:[10,0,0,"-"],optimization:[10,0,0,"-"],pp_utils:[10,0,0,"-"]},"pyemu.utils.geostats":{ExpVario:[10,1,1,""],GauVario:[10,1,1,""],GeoStruct:[10,1,1,""],OrdinaryKrige:[10,1,1,""],SphVario:[10,1,1,""],Vario2d:[10,1,1,""],fac2real:[10,6,1,""],gslib_2_dataframe:[10,6,1,""],load_sgems_exp_var:[10,6,1,""],parse_factor_line:[10,6,1,""],read_sgems_variogram_xml:[10,6,1,""],read_struct_file:[10,6,1,""]},"pyemu.utils.geostats.GeoStruct":{covariance:[10,2,1,""],covariance_matrix:[10,2,1,""],covariance_points:[10,2,1,""],name:[10,5,1,""],plot:[10,2,1,""],sill:[10,5,1,""],sparse_covariance_matrix:[10,2,1,""],to_struct_file:[10,2,1,""],transform:[10,5,1,""],variograms:[10,5,1,""]},"pyemu.utils.geostats.OrdinaryKrige":{calc_factors:[10,2,1,""],calc_factors_grid:[10,2,1,""],check_point_data_dist:[10,2,1,""],to_grid_factors_file:[10,2,1,""]},"pyemu.utils.geostats.Vario2d":{add_sparse_covariance_matrix:[10,2,1,""],bearing_rads:[10,5,1,""],covariance:[10,2,1,""],covariance_matrix:[10,2,1,""],covariance_points:[10,2,1,""],inv_h:[10,2,1,""],plot:[10,2,1,""],rotation_coefs:[10,5,1,""],to_struct_file:[10,2,1,""]},"pyemu.utils.gw_utils":{apply_gage_obs:[10,6,1,""],apply_hds_obs:[10,6,1,""],apply_hds_timeseries:[10,6,1,""],apply_mflist_budget_obs:[10,6,1,""],apply_mtlist_budget_obs:[10,6,1,""],apply_sfr_obs:[10,6,1,""],apply_sfr_parameters:[10,6,1,""],apply_sfr_seg_parameters:[10,6,1,""],apply_sft_obs:[10,6,1,""],fac2real:[10,6,1,""],last_kstp_from_kper:[10,6,1,""],load_sfr_out:[10,6,1,""],modflow_hob_to_instruction_file:[10,6,1,""],modflow_hydmod_to_instruction_file:[10,6,1,""],modflow_pval_to_template_file:[10,6,1,""],modflow_read_hydmod_file:[10,6,1,""],modflow_sfr_gag_to_instruction_file:[10,6,1,""],pilot_points_to_tpl:[10,6,1,""],pp_file_to_dataframe:[10,6,1,""],pp_tpl_to_dataframe:[10,6,1,""],setup_gage_obs:[10,6,1,""],setup_hds_obs:[10,6,1,""],setup_hds_timeseries:[10,6,1,""],setup_mflist_budget_obs:[10,6,1,""],setup_mtlist_budget_obs:[10,6,1,""],setup_pilotpoints_grid:[10,6,1,""],setup_sfr_obs:[10,6,1,""],setup_sfr_reach_parameters:[10,6,1,""],setup_sfr_seg_parameters:[10,6,1,""],setup_sft_obs:[10,6,1,""],write_hfb_template:[10,6,1,""],write_hfb_zone_multipliers_template:[10,6,1,""],write_pp_file:[10,6,1,""],write_pp_shapfile:[10,6,1,""]},"pyemu.utils.helpers":{PstFromFlopyModel:[10,1,1,""],apply_array_pars:[10,6,1,""],apply_bc_pars:[10,6,1,""],apply_hfb_pars:[10,6,1,""],build_jac_test_csv:[10,6,1,""],condition_on_par_knowledge:[10,6,1,""],eigen_basis_to_factor_file:[10,6,1,""],first_order_pearson_tikhonov:[10,6,1,""],gaussian_distribution:[10,6,1,""],geostatistical_draws:[10,6,1,""],geostatistical_prior_builder:[10,6,1,""],jco_from_pestpp_runstorage:[10,6,1,""],kl_apply:[10,6,1,""],kl_setup:[10,6,1,""],parse_dir_for_io_files:[10,6,1,""],pilotpoint_prior_builder:[10,6,1,""],plot_flopy_par_ensemble:[10,6,1,""],plot_summary_distributions:[10,6,1,""],pst_from_io_files:[10,6,1,""],pst_from_parnames_obsnames:[10,6,1,""],read_pestpp_runstorage:[10,6,1,""],regweight_from_parbound:[10,6,1,""],remove_readonly:[10,6,1,""],run:[10,6,1,""],simple_ins_from_obs:[10,6,1,""],simple_tpl_from_pars:[10,6,1,""],sparse_geostatistical_prior_builder:[10,6,1,""],start_slaves:[10,6,1,""],write_df_tpl:[10,6,1,""],zero_order_tikhonov:[10,6,1,""]},"pyemu.utils.helpers.PstFromFlopyModel":{add_external:[10,2,1,""],bc_helper:[10,2,1,""],build_prior:[10,2,1,""],build_pst:[10,2,1,""],draw:[10,2,1,""],get_count:[10,2,1,""],grid_prep:[10,2,1,""],kl_prep:[10,2,1,""],parse_k:[10,2,1,""],parse_pakattr:[10,2,1,""],pp_prep:[10,2,1,""],prep_mlt_arrays:[10,2,1,""],pst:[10,5,1,""],setup_array_pars:[10,2,1,""],setup_bc_pars:[10,2,1,""],setup_hds:[10,2,1,""],setup_hfb_pars:[10,2,1,""],setup_hob:[10,2,1,""],setup_hyd:[10,2,1,""],setup_model:[10,2,1,""],setup_mult_dirs:[10,2,1,""],setup_observations:[10,2,1,""],setup_sfr_obs:[10,2,1,""],setup_sfr_pars:[10,2,1,""],setup_smp:[10,2,1,""],setup_spatial_bc_pars:[10,2,1,""],setup_temporal_bc_pars:[10,2,1,""],setup_water_budget_obs:[10,2,1,""],write_const_tpl:[10,2,1,""],write_forward_run:[10,2,1,""],write_grid_tpl:[10,2,1,""],write_u2d:[10,2,1,""],write_zone_tpl:[10,4,1,""]},"pyemu.utils.optimization":{add_pi_obj_func:[10,6,1,""]},"pyemu.utils.pp_utils":{pilot_points_to_tpl:[10,6,1,""],pp_file_to_dataframe:[10,6,1,""],pp_tpl_to_dataframe:[10,6,1,""],setup_pilotpoints_grid:[10,6,1,""],write_pp_file:[10,6,1,""],write_pp_shapfile:[10,6,1,""]},pyemu:{en:[7,0,0,"-"],ev:[7,0,0,"-"],la:[7,0,0,"-"],logger:[7,0,0,"-"],mat:[8,0,0,"-"],mc:[7,0,0,"-"],pst:[9,0,0,"-"],sc:[7,0,0,"-"],smoother:[7,0,0,"-"],utils:[10,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","classmethod","Python class method"],"4":["py","staticmethod","Python static method"],"5":["py","attribute","Python attribute"],"6":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:classmethod","4":"py:staticmethod","5":"py:attribute","6":"py:function"},terms:{"000000e":[],"1e6":[2,7],"1to1":9,"200k":10,"301030e":[],"5inx11in":10,"9th":0,"boolean":[7,9,10],"break":[2,7],"case":[2,3,7,9,10],"char":[2,7,8,9,10],"class":[0,1,2,3,5,6,7,8,9,10],"default":[1,2,7,8,9,10],"final":[1,7],"float":[1,2,7,8,9,10],"function":[2,3,5,7,8,9,10],"import":[1,3,6,7,10],"int":[1,2,7,8,9,10],"long":10,"new":[1,2,3,5,6,7,8,9,10],"null":[1,2,7],"return":[1,2,7,8,9,10],"static":[2,3,7,8,9,10],"super":7,"true":[1,2,7,8,9,10],"try":9,"var":7,"while":[2,3,7],Adding:10,BAS:10,BCs:10,CRS:10,DIS:10,EXE:1,For:[3,5,6,7,8,10],INS:10,IOS:0,Not:10,One:5,RHS:10,That:[7,10],The:[0,1,2,3,5,6,7,8,9,10],Then:7,There:7,These:[0,2,3,7,8,9,10],Used:[2,7,9,10],Useful:10,Uses:[1,2,7,10],Using:[],Wes:0,Willing:0,__future__:[],__init__:[],__istransform:[2,7],__jco:7,__mult__:8,__obscov:7,__omitted_jco:7,__omitted_parcov:7,__omitted_predict:7,__parcov:7,__posterior_paramet:7,__predict:7,__pst:7,__re:9,__x:8,_backup_:10,_github:[],_infer_fill_valu:[],_jj:7,_set_:10,_setup_:10,a_dict:10,aballa:0,abbrevi:3,abil:10,about:[3,8,10],abov:[1,3,7,10],abs_drop_tol:10,absolut:8,academ:0,accept:[2,3,7,10],access:[5,7],accommod:8,accord:[2,7,10],account:[2,7,10],across:10,action:[3,9],activ:[7,10],actual:[3,9],adapt:0,add:[1,2,3,7,9,10],add_bas:[2,7],add_extern:10,add_observ:9,add_paramet:9,add_pi_equ:9,add_pi_obj_func:10,add_sparse_covariance_matrix:10,add_transform_column:9,added:[3,7,9,10],addit:[3,7,9,10],addreg:9,adj:7,adj_nam:[2,7],adj_par_nam:[7,9],adjust:[2,6,7,9,10],adjust_obscov_resfil:7,adjust_weight:9,adjust_weights_by_list:9,adjust_weights_recfil:9,adjust_weights_resfil:[7,9],adopt:[],advanc:[2,7],advantag:3,after:[2,3,7,9,10],against:9,agenda:0,agent:0,aggreg:10,aggregr:10,agnost:10,aka:7,algebra:8,algorithm:7,alia:8,align:[8,10],all:[2,3,5,6,7,8,9,10],all_wel:10,allow:[3,5,6,9,10],alon:9,alreadi:[2,7,10],also:[1,3,5,6,7,9,10],alter:[],altern:3,alwai:9,amath:[],ambigu:[],among:3,amount:[2,7,9],anaconda:[],analys:[0,1,7,9,10],analysi:[0,1,3,5,6,7,9],analyst:5,analyt:3,analyz:3,andor:8,angl:10,ani:[3,7,8,9,10],anisotropi:10,anoth:[3,8],appar:10,appear:3,append:[9,10],appli:[2,3,7,8,10],applic:3,apply_array_par:10,apply_bc_par:10,apply_gage_ob:10,apply_hds_ob:10,apply_hds_timeseri:10,apply_hfb_par:10,apply_karhunen_loeve_sc:7,apply_mflist_budget_ob:10,apply_mtlist_budget_ob:10,apply_sfr_ob:10,apply_sfr_paramet:10,apply_sfr_reach_paramet:10,apply_sfr_seg_paramet:10,apply_sft_ob:10,approach:[0,5],appropri:10,approx:[7,10],approxim:[2,3,7,8,10],aquier:10,aquif:10,arbitrari:[],area:10,arg:[2,7,9,10],argument:[1,2,3,6,7,8,9,10],around:[2,3,7,8,10],arr:10,arr_par:10,arr_par_fil:10,arr_shap:10,arrai:10,array_dict:10,as_2d:8,as_pyemu_matrix:[2,7],ascii:[8,10],asnd:3,aspect:3,assert:9,assess:3,assign:[6,7,9,10],assist:[1,5],associ:[1,3,5,7,10],assum:[2,3,7,10],assumpt:[3,7],astyp:7,attempt:9,attr:10,attr_nam:10,attribu:[],attribut:[2,3,5,6,7,8,9,10],attrnam:10,australia:0,author:3,auto:8,autoalign:8,autodetect:8,automat:[6,7,9],autotest:10,avail:[0,3,6,10],avila:0,avoid:10,axes:[8,10],axi:[8,10],ba_dict:10,back:[0,2,7,10],back_array_dict:10,backup:10,bakker:0,balanc:3,bar:7,bas6:10,bas:10,base:[1,2,3,6,7,8,9,10],base_obslist:7,basemap:10,basi:10,basic:[0,7,9],basis_fil:10,bay:[3,7],bbb:2,bc_helper:10,bc_par:10,bc_prop:10,bear:10,bearing_rad:10,becaus:[2,3,7,9,10],becom:[3,7,8,10],been:[1,2,3,7,9],befor:[2,7,8,10],begin:[],behavior:3,being:[7,9,10],below:5,benjamin:0,best:7,better:10,between:[1,3,5,7,10],beven:0,bewar:[7,9,10],big:8,bin:[2,7],binari:[2,7,8,10],binary_header_dt:8,binary_rec_dt:8,bizarr:8,blah:1,block:[3,8],block_extend_ip:8,boldsymbol:[],book:0,bool:[1,2,7,8,9,10],boss:10,boston:0,both:[3,7,8],bound:[1,2,3,7,8,9,10],bound_tol:[2,7],boundari:10,bpl:[0,3],brian:0,bring:[2,7],brisban:0,budget:10,buget:10,build:[0,3,7,10],build_empirical_prior:7,build_incr:9,build_jac_test_csv:10,build_prior:10,build_pst:10,bussonni:0,c12:0,calc:[7,8],calc_factor:10,calc_factors_grid:10,calc_onli:7,calcul:[2,3,7,8,9,10],calculate_pertub:9,calibr:[0,3,7],call:[1,2,3,5,6,7,8,9,10],can:[0,1,2,3,5,6,7,9,10],cancer:0,caption:[],carlo:[0,2,3,7],carol:0,carri:[2,7],cartopi:10,cast:[7,9,10],caus:[7,9,10],caveat:[],ccovari:7,cell:10,certain:10,chang:[2,3,6,7,9,10],character:3,chart:9,cheap:10,check:[6,8,9,10],check_point_data_dist:10,chen:7,choic:5,chunk:[8,10],classmethod:[2,7,8,9,10],clean:[7,9],clean_filenam:9,clean_missing_expon:9,cleanup:10,clear:[7,8],client:[0,3,5,6],close:7,closer:10,cmd:10,cmd_str:10,code:[0,3,5,6,8,10],coef_dict:9,coeffic:10,coeffici:[8,9,10],cofactor:7,coher:3,col:[8,10],col_index:[],col_nam:8,collect:7,colloc:3,color:[2,7],column:[2,3,7,8,9,10],com:[],combin:[3,8],command:[7,9,10],common:[8,9],commun:7,companion:10,compar:[3,7,10],compat:[8,9,10],complement:[3,7,8],complet:[1,7,10],complex:[0,10],compliant:9,complic:10,compliment:7,compon:[1,3,7,8,9,10],composit:[7,9],compound:3,compress:[8,10],comput:0,computation:3,conc:10,concat:8,concaten:8,concentr:10,concept:[2,7],cond:10,condit:[3,7,8,10],condition_on:8,condition_on_par_knowledg:10,conditioning_el:8,condor_submit:7,conduct:[3,10],confer:0,confid:[7,8,9,10],config:10,config_fil:10,confus:3,consid:3,consist:[0,3,10],const_prop:10,constant:10,construct:[1,3,7,8,9,10],constructor:[2,7,9,10],contain:[2,3,6,7,8,9,10],containt:10,content:4,continu:10,contol:5,contribut:[3,7,9,10],control:[1,2,3,5,7,8,9,10],control_data:9,controldata:9,convent:10,convert:8,convienc:[],coo:[8,10],coo_rec_dt:8,cool:10,coordin:10,copi:[2,7,8,9,10],core:[2,7,10],corlai:0,correct:10,correctli:10,correl:[3,8,10],correspond:[3,8,9,10],could:10,count:10,counter:[2,7,9,10],cov:[1,2,7,8,10],covari:[2,3,7,8,10],covariance_matrix:[2,7,10],covariance_point:10,covarinc:10,covmat_fil:8,crazi:10,creat:[2,5,6,7,8,9,10],creation:5,cryptic:10,cso:7,cso_j:7,csr:10,css:7,csv:[2,7,10],cur_lam:7,current:[2,3,5,6,7,9,10],custom:3,cwd:[9,10],cycl:7,dai:10,damian:0,danger:[9,10],dat:10,data:[0,3,5,6,7,8,9,10],datafram:[1,2,5,6,7,8,9,10],dataframe_to_smp:9,dataset:7,dataworth:10,date_pars:9,datetim:[7,9,10],datetime_col:9,datetime_format:9,datum:3,deal:[6,7,9],debug:7,decomposit:[8,9],decor:[2,7,8],dedic:10,deduc:9,deep:[2,7],defaul:8,default_dict:9,defaut:10,defin:[3,7,8,10],definit:[1,3,7,9],degre:10,del_rw:9,delta:7,demystifi:10,denot:3,dens:8,depend:[7,9,10],deprec:10,der:0,dercom:6,deriv:[1,2,3,7,10],dervi:10,describ:[2,3,7,10],design:[7,8],desir:[0,3,5],destroi:10,detail:10,detect:10,determin:[1,3,7,8,10],dev:10,develop:[0,3],deviat:[3,7,8,9,10],dfs:10,diacrit:3,diag:8,diagon:[3,7,8],dial:3,dict:[1,2,7,9,10],dictionari:[1,7,9,10],differ:[3,5,7,9,10],differenc:[2,7],dimens:[1,7,8],dir:10,direct:[3,10],directli:[2,7,10],directori:[6,7,9,10],dis:10,discret:3,displai:7,distanc:10,distinguish:3,distrbut:10,distribut:[1,2,3,7,8,10],divid:[3,7],divis:10,doc:0,document:[1,2,3,5,7,10],dodgerblue1:[],doe:[2,6,7,9,10],doesn:10,doh15:0,doh_15:[],doherti:0,doi:0,doing:[2,7],domain:3,domin:3,don:[7,8,9,10],done:[],dot:[5,8],doubl:[8,10],draw:[1,2,7,8,10],drawn:[2,7],drop:[1,2,7,8,10],drop_bad_r:7,drop_prior_inform:7,dropna:[2,7],droptol:[8,10],dtemp:8,dtype:[8,9],dual:3,due:[3,10],duke:0,dure:[7,8,9,10],dynam:7,each:[3,7,8,9,10],earth:0,easi:[1,7,8],easier:5,east:10,echo:[7,10],ecosystem:[],edit:0,eds:0,effect:[3,7,9],effici:[3,10],egg:[],eigen_basis_to_factor_fil:10,eigthresh:8,either:[3,5,7,8,9,10],elaps:7,element:[3,7,8,10],element_isalign:8,elicit:3,ellips:10,els:10,emph:[],empti:[7,8],emul:9,enabl:6,encapsul:[3,7,8,9],end:[6,7,9,10],endswith:10,enfor:7,enforc:[1,2,7,9],enforce_bound:[1,2,7,9],enforce_drop:[2,7],enforce_hound:9,enforce_reset:[2,7],enfore_scal:[2,7],enkf:[2,7],enrml:7,ensembl:[1,3,7,10],ensemble_help:[2,7],ensemblemethod:7,ensemblesmooth:7,ensenbl:10,entir:[7,8,10],entiti:3,entri:[2,6,7,8,9,10],enumer:8,env:[],environment:[0,7],envsoft:0,epistem:3,epsilon:[1,7,10],equal:[7,8,9,10],equat:[3,6,7,9,10],equival:[3,10],err:7,error:[0,3,7,9,10],errorvari:7,errvar:7,especi:5,estim:[0,3,7,9],estmat:9,etc:[3,10],evalu:[2,6,7],event:7,everi:[7,10],every_n_cel:10,everyth:[3,10],exactli:8,exampl:[3,5,8,9],exc:9,except:[7,8,9,10],exchang:10,excinfo:10,exe:10,exe_nam:9,exe_rel_path:[9,10],execut:[9,10],exist:[1,5,7,9,10],existing_jco:[1,7],exit:10,expans:10,expect:[0,2,3,7,10],expedi:3,experi:3,experiement:[2,7],experiment:[9,10],expert:3,explicitli:[2,7,9,10],exponeti:10,expos:[1,6,7],express:[3,7,10],expvario:10,extend:[3,5,8],extens:[3,7],extern:10,external_ins_out_pair:10,external_path:10,external_tpl_in_pair:10,extra:[2,6,7],extra_model_cmd:10,extra_post_cmd:10,extra_pre_cmd:10,extract:[7,8,10],fac2real:10,fac_data:10,facecolor:[2,7],factor:[2,3,6,7,10],factors_fil:10,fail:10,fals:[1,2,6,7,8,9,10],familiar:5,faster:[2,7,8],fb16:[0,5],fb__16:[],featur:3,feder:0,fehalf:7,fernado:0,few:[2,3,7],ffmt:9,field:[3,9],fienen:0,fig:10,fig_axes_gener:10,figsiz:10,figur:10,file:[1,2,5,7,8,9,10],filenam:[1,2,6,7,8,9,10],fill:[1,2,7,8,9],fill_fix:[2,7],fill_valu:10,find:[2,5,7,8,9,10],find_rowcol_indic:8,finit:[3,9],first:[1,3,5,6,7,8,10],first_forecast:7,first_order_pearson_tikhonov:10,first_paramet:7,first_predict:7,first_term:7,fit:3,fix:[2,7,9,10],fixed_index:[2,7],flag:[2,7,8,9,10],fleft:[],flex:9,flex_load:9,float64:8,flopi:[0,3,10],flopytopst:10,flow:[3,10],flowgw:10,flush:10,flux:10,flux_fil:10,flx:10,flx_filenam:10,fmt:10,follow:[3,10],fontsiz:[],forc:[3,9],fore1:7,fore2:7,fore_sum:7,forecast:[0,3,7,9,10],forecast_nam:[7,9],forecasts_it:7,forgiv:10,form:[1,3,7,8,9,10],formal:3,format:[0,8,9,10],formatted_valu:9,formul:7,formula:3,fortran:[8,9],forward:[7,10],forward_run:10,forward_run_lin:10,fosm:[3,7,10],found:10,foundat:0,fourth:0,frac:[],fraction:[2,7,9],fraction_stdev:9,frame:[2,7],framework:[0,10],free:[1,7,9],freyberg:10,from:[0,2,3,5,6,7,8,9,10],from_ascii:[8,10],from_binari:[2,7,8],from_coo:8,from_datafram:[2,7,8],from_fortranfil:8,from_gaussian_draw:[2,7],from_id_gaussian_draw:[2,7],from_io_fil:[9,10],from_matrix:8,from_nam:8,from_observation_data:8,from_obsweight:8,from_par_obs_nam:9,from_parameter_data:8,from_parbound:8,from_parfil:[2,7],from_pst:8,from_sparse_gaussian_draw:[2,7],from_uncfil:8,from_uniform_draw:[2,7],front:9,frozen:[2,7],frun_lin:10,full:[2,3,7,8,10],full_:8,fulli:[2,7],func:10,func_dict:[2,7],futur:[0,3],futurewarn:[],gage:10,gage_fil:10,gage_output_fil:10,gain:7,gamma:[],gaussian:[1,2,3,7,10],gaussian_distribut:10,gauvario:10,gener:[1,2,3,5,7,9,10],generic_pst:9,geo:10,geolog:[0,3],geostast:10,geostat:[4,7],geostatist:[3,10],geostatistical_draw:10,geostatistical_prior_build:10,geostist:10,geostruct:10,get:[1,2,5,6,7,8,9,10],get_added_obs_group_import:7,get_added_obs_import:7,get_common_el:8,get_conditional_inst:7,get_contribution_datafram:7,get_count:10,get_cso_datafram:7,get_datafram:9,get_diagonal_vector:8,get_errvar_datafram:7,get_forecast_summari:7,get_identifiability_datafram:7,get_loc:7,get_marker_indic:9,get_matrix:8,get_maxs:8,get_meas_and_regul_phi:7,get_ns:[1,7],get_null_proj:[1,7],get_par_contribut:7,get_par_css_datafram:7,get_par_group_contribut:7,get_parameter_contribut:7,get_parameter_summari:7,get_phi_comps_from_recfil:9,get_removed_obs_group_import:7,get_removed_obs_import:7,get_res_stat:9,get_residual_obs_matrix:7,get_residual_par_matrix:7,get_sparse_matrix:8,get_uncfile_dimens:8,github:0,github_:[],give:10,given:[1,7,10],glean:3,glm:7,global:[2,7],global_k:6,glossari:0,glue:[1,3,7],govern:3,gracefulli:10,grain:3,granger:0,greater:[7,8,10],grid:10,grid_geostruct:10,grid_prep:10,grid_prop:10,groundwat:[0,3,10],group:[2,3,6,7,9,10],group_chunk:[2,7],group_nam:9,groupbi:[],grout:0,grown:[2,7],grp_dict:[],gslib:10,gslib_2_datafram:10,guard:[2,7],gw_filenam:10,gw_prefix:10,gw_util:[4,7],gwat:0,gwutils_compli:9,h_function:10,h_obs01_1:6,h_obs01_2:6,h_obs02_1:6,h_obs02_2:6,h_obs03_1:6,h_obs03_2:[],h_obs04_1:[],h_obs04_2:[],h_obs05_1:[],h_obs05_2:[],h_obs06_1:[],h_obs06_2:[],h_obs07_1:[],h_obs07_2:[],h_obs08_1:[],h_obs08_2:[],h_obs09_1:[],h_obs09_2:[],h_obs10_1:[],h_obs10_2:[],h_obs11_1:[],h_obs11_2:[],h_obs12_1:[],h_obs12_2:[],h_obs13_1:[],h_obs13_2:[],h_obs14_1:[],h_obs14_2:[],h_obs15_1:[],h_obs15_2:[],hadamard_product:8,half:7,hamrick:0,hand:9,handl:[2,7,8,9,10],happen:7,hard:7,has:[2,3,6,7,9,10],have:[1,2,3,5,7,8,9,10],hcond1:10,hcond2:10,hds:10,hds_file:10,hds_kperk:10,hds_timeseri:10,head:[6,10],header:[8,10],headfil:10,heavi:[7,8],heavili:6,height:[],help:[2,7,9,10],helper:[3,4,5,7,9],henri:6,here:[3,9],hess:0,hessian:7,hfb6:10,hfb:10,hfb_mult:10,hfb_par:10,high:[3,10],highli:0,hill:7,histogram:[2,7,9],histori:3,hk_0001:10,hk_0002:10,hk_:10,hk_layer_1:10,hkpp:10,hob:10,hob_fil:10,hold:7,home:[0,2,7],homegrown:[2,7],hopefulli:10,horizont:10,horribl:10,hostnam:10,how:[1,2,3,6,7,10],howev:[2,7,10],htcondor:7,html:0,http:0,hugh:0,hunt:0,hydmod:10,hydmod_fil:10,hydmod_outfil:10,hydraul:[3,10],hydrolog:0,hydrologist:0,hymod_fil:10,iES:7,i_minus_r:7,ibound:10,icod:8,icount:8,ident:[7,8],identifi:[3,7],identity_lik:8,ifmt:9,ignor:[2,7,8],iidx:10,illustr:3,implement:[0,2,3,7,8,9,10],impli:[2,3,7,8,9,10],in_fil:[9,10],inact:10,inadequaci:3,includ:[0,3,7,9,10],include_map:7,include_path:10,incorrect:9,increment:[3,9,10],independ:[2,7,10],index:[0,2,7,9,10],indic:[2,3,7,8,9,10],individu:[2,7,9,10],influenti:7,info:[8,9,10],inform:[1,3,6,7,9,10],inher:3,inherit:[1,2,3,7],inheritance_node_attr:[],init:6,init_lambda:7,initi:[3,7],inod:10,inplac:[1,2,7,8],input:[1,3,5,9,10],input_fil:10,ins:[9,10],ins_fil:[9,10],ins_filenam:[9,10],inscheck:9,inschek:[9,10],insfilenam:10,instanc:[1,2,3,5,6,7,8,9,10],instani:8,instanti:[2,3,6,7,8,10],instead:[7,9,10],instruct:[5,9,10],instrument:3,instrut:10,int32:8,integ:[2,7,8,10],intend:8,interact:[0,3,5],interfac:[7,9,10],interit:[],intern:3,interpl:10,interpol:10,interpolatino:10,interpret:[3,5],introduct:[],intuit:8,inv:8,inv_h:10,invers:[0,3,7,8,9,10],invert:10,invok:5,ipynb:[],irun:10,isbn:[],isdiagon:8,isdiaon:8,isdigon:8,isfropt:10,islog:10,issu:[7,9,10],istransform:[2,7],item:[2,7,9,10],itemp1:8,itemp2:8,iter:[2,7,8,9,10],itran:10,its:[3,9],ivanov:0,jacobian:[1,3,7,8],jactest:10,jacupd:[],jarrod:0,jason:0,java:0,jcb:[1,7,10],jco:[1,2,7,8,10],jco_col:10,jco_from_pestpp_runstorag:10,jessica:0,jidx:10,john:0,join:6,jonathan:0,jtwhite79:[],junk:10,jupyt:[0,3,5,6],just:[3,7,9,10],jwhite:[],k_val:10,k_zone_dict:10,karhuen:7,karhuenen:10,kb09:[0,3],kei:[2,3,7,9,10],keith:0,kellei:0,kept:[7,10],keyword:[1,2,7,10],kij_dict:10,kind:[7,9,10],kl_appli:10,kl_factor:10,kl_geostruct:10,kl_num_eig:10,kl_prep:10,kl_prop:10,kl_setup:10,kluyver:0,know:[2,3,7,8,9],knowledg:[3,7,10],known:[3,5,7],kper:10,kperk:10,kperk_pair:10,kr01c01:6,kr01c02:6,kr01c03:6,kr01c04:6,kr01c05:[],kr01c06:[],kr01c07:[],kr10c31:[],kr10c32:[],kr10c33:[],kr10c34:[],kr10c35:[],kr10c36:[],kr10c37:[],kr10c38:[],kr10c39:[],kr10c40:[],kr10c41:[],kr10c42:[],kr10c43:[],kr10c44:[],kr10c45:[],kr10c46:[],kr10c47:[],kr10c48:[],kr10c49:[],kr10c50:[],kr10c51:[],kr10c52:[],kr10c53:[],kr10c54:[],kr10c55:[],kr10c56:[],kr10c57:[],kr10c58:[],kr10c59:[],kr10c60:[],krige:10,krp:[0,3],kstp:10,kwarg:[1,2,7,9,10],kwrag:[2,7],kwwarg:[],kyle:0,la_cond:7,label:[7,10],label_post:10,label_prior:10,lack:3,lambda:[7,10],lambda_mult:7,langevin:0,larg:[0,2,7,10],larger:10,largest:[1,7,8],last:[7,10],last_kstp_from_kp:10,later:9,latest:10,latex:9,layer1_pp:10,layer:10,lbnd:[2,7],lead:[9,10],learn:[0,3],least:9,leav:9,leave_zero:9,left:8,legend:10,len:10,length:[3,8,10],less:[2,7,8,10],level:[3,10],lib:[],like:[3,8,9,10],likelihood:[3,7],limit:3,line:[9,10],linear:[0,3,5,7,8,9],linearanalsi:7,linearanalysi:[1,7],link:5,list1:8,list2:8,list:[2,3,7,8,9,10],list_filenam:10,load:[2,7,8,9,10],load_sfr_out:10,load_sgems_exp_var:10,loc:6,local:[2,7,10],localhost:10,locat:[1,3,7,10],loev:[7,10],log10:[2,7],log:[2,6,7,10],log_index:[2,7],logger:[2,4,10],loizid:0,london:0,look:9,lose:7,lost:7,lot:[9,10],low:3,lower:[2,7,8,10],lower_lim:10,lpf:10,lrais:7,machin:[7,10],made:[3,5,10],mai:[3,5,9,10],main:[5,10],make:[2,3,6,7,8,10],manag:7,mani:[3,5,8,10],manipul:[5,9],map:[3,7,10],map_forecast_estim:7,map_parameter_estim:7,mark:[0,3,9],marker:[9,10],marti:0,martric:8,mask:10,mass:10,master:10,master_dir:10,mat:[2,4,7],mat_handl:[2,4,7],match:[3,9],math:[],mathbf:[],mathcal:[],mathemat:3,matplotlib:[7,10],matric:[3,8,10],matrix:[1,2,3,7,8,10],matthia:0,max:[9,10],max_name_len:9,maxdepth:[],maximum:[7,9,10],maxpts_interp:10,maxs:8,mbase:10,mc_:[1,7],mck10:[0,5],mckinnei:0,mean:[1,2,3,7,8,10],mean_valu:[2,7],meaning:[9,10],measur:[1,3,6,7],mechan:9,memori:10,messag:7,met:9,method:[0,1,2,3,5,6,7,8,9,10],methodnam:5,mfhyd:10,mflist:10,mflist_waterbudget:10,mfnwt:10,might:8,millman:0,min:[7,9],minim:7,minimum:[3,7,10],minpts_interp:10,minu:7,misfit:3,miss:[3,9],mixtur:10,mle:7,mle_covari:7,mle_parameter_estim:7,mlt_df:10,mode:10,model:[0,1,3,6,7,9,10],model_exe_nam:10,model_w:10,modflow:[0,3,10],modflow_hob_to_instruction_fil:10,modflow_hydmod_to_instruction_fil:10,modflow_pval_to_template_fil:10,modflow_read_hydmod_fil:10,modflow_sfr_gag_to_instruction_fil:10,modflownwt:10,modflowsfr:10,modflowwel:10,modfow:10,modifi:3,modul:[0,1,2,3,4,5,6],moment:3,monitor:10,monster:10,mont:[0,2,3,7],montecarlo:[1,7],more:[2,3,7,8,9,10],most:[2,7,8,9],mostli:7,move:[2,7],mt3d:10,mt3dusg:10,mthod:[],mtlist_gw:10,mtlist_sw:10,much:[2,3,7,10],mult1:6,mult2:6,mult:[7,10],mult_isalign:8,multi:[3,7],multipag:[2,7,9],multipl:[8,10],multipli:[7,8,10],multivari:[2,3,7],must:[1,2,7,8,10],mymodel:10,nadj_par:[1,7],nam:10,nam_fil:10,name:[2,3,6,7,8,9,10],name_col:9,name_prefix:10,namfil:10,namn:9,nan:[2,6,7,9,10],natur:3,ncol:[8,10],ncomp:10,ndarrai:[7,8,10],ndframe:2,necess:3,necessari:[5,7],need:[1,2,3,6,7,9,10],need_omit:7,nentri:8,nest:[7,9,10],never:9,new_df:9,new_filenam:9,new_model_w:10,new_obs_data:9,new_obs_length:8,new_par_data:9,new_par_length:8,new_pst:9,new_val:10,newlin:[],newx:8,next:7,next_most_added_importance_ob:7,next_most_important_added_ob:7,next_most_par_contribut:7,niter:7,nnnn:[],nnz:7,nnz_ob:[7,9],nnz_obs_group:9,nnz_obs_nam:[2,7,9],nob:[3,8,9],node:10,nois:[1,2,3,7],non:[0,2,5,7,8,9,10],none:[1,2,7,8,9,10],nonlog:10,nont:[1,7],nonzero:[2,7,9,10],noptmax:[1,7],norm:[2,7],normal:[2,3,7,8,9],north:10,notat:[3,5],note:[0,2,3,6,7,8,10],notebook:[0,3,5,6],notimplementederror:[2,7],notion:[3,7],now:[6,10],npar:[3,7,8,9],npar_adj:9,nparrai:8,nprior:9,nrow:[8,10],nrowxncol:10,nsing:[1,7],nsmc:[1,7],nugget:10,num_eig:10,num_pt:10,num_real:[1,2,7,10],num_slav:[7,9,10],num_step:10,number:[1,2,3,7,8,9,10],numer:[0,3,8],numlam:[],numpi:[2,6,7,8,9,10],nwt_x64:10,obf:9,obgnm:[6,10],obj:[],obj_func_dict:10,object:[0,3,7,8,9,10],obs1:9,obs:[1,2,7,9,10],obs_df:10,obs_dict:9,obs_fil:10,obs_group:9,obs_group_import:7,obs_length:8,obs_nam:[5,6,7,8,9],obs_v_sim:9,obscov:7,obsembl:[2,7],obsenembl:7,obsensembl:[1,7],obseravt:10,observ:[1,2,3,5,6,7,8,9,10],observation_data:[6,8,9],observationensembl:[1,2,7],obsgrp_dict:9,obslist:9,obslist_dict:7,obsnam:10,obsnm:[6,9,10],obssim_smp_pair:10,obsval:[6,9,10],obsviou:9,obtain:[8,10],off:[2,3,7,9,10],offend:[2,7],offset:[6,7,8,9],often:3,ok_var:10,old_indic:8,oliv:7,omit:7,omitted_jco:7,omitted_par:7,omitted_paramet:7,omitted_parcov:7,omitted_predict:7,onc:10,one:[2,3,7,8,9,10],onerror:10,ones:10,onli:[2,3,5,7,9,10],onlyus:7,oop:[],oopnot:[],open:[7,9],oper:[1,2,7,8],opinion:0,optim:[0,3,4,7],option:[1,2,7,8,9,10],order:[3,8,9,10],ordinari:10,ordinarykirg:10,ordinarykrig:10,org:0,org_model_w:10,org_val:10,orgin:10,orient:[0,3,10],origin:[9,10],other:[3,7,8,10],otherwis:[1,3,7,8,9,10],our:3,out:[6,7,9,10],out_fil:[9,10],out_filenam:[8,10],out_pst_nam:10,outflow:10,output:[3,5,7,8,9,10],output_fil:10,overbar:[],overload:[2,7,8],overrid:[3,10],overview:1,overwrit:[1,7],packag:4,page:[0,10],pair:[7,9,10],pak:10,pakattr:10,pakbas:10,panda:[0,1,2,5,6,7,8,9,10],pandasobject:2,par1:9,par:[1,2,7,9,10],par_bounds_dict:10,par_col:10,par_df:10,par_en:[1,7,10],par_ensembl:10,par_fil:[1,7,10],par_group:[9,10],par_knowledge_dict:10,par_length:8,par_nam:[5,6,7,8,9,10],par_sum:7,par_to_file_dict:10,paragraph:[],parallel:7,param:10,paramet:[0,1,2,3,5,6,7,8,9,10],parameter:[0,10],parameter_data:[1,2,5,6,7,8,9],parameter_nam:7,parameterensembl:[1,2,7,10],paramt:10,parbound:[9,10],parchglim:6,parcov:7,parensembl:[1,7],parfil:[2,7,9],parfile_nam:[2,7],pargp:6,parlbnd:6,parlist_dict:7,parm_data_df:5,parmaet:7,parnam:[8,10],parnm:[2,6,7,10],parrep:9,pars:[9,10],parse_dir_for_io_fil:10,parse_factor_lin:10,parse_ins_fil:[9,10],parse_ins_str:9,parse_k:10,parse_pakattr:10,parse_tpl_fil:9,parse_values_from_lin:9,parser:9,part:[6,7,9,10],partial:3,particular:3,partran:6,parubnd:6,parval1:[1,2,6,7,9,10],parval1_tran:9,parval:[9,10],pass:[2,6,7,8,9,10],path:[1,6,7,9,10],paul:0,pcolormesh:10,pcolormesh_transform:10,pdf:[2,7,9],peak:10,pearson:[0,8,10],penalti:[3,7],per:10,percent:[3,7],perez:0,perfect:7,perfectli:[3,7],perform:[0,1,3,5,7,10],period:[9,10],pertain:3,pertub:[9,10],pest:[0,2,3,5,7,8,9,10],pestchek:10,pesthomepag:0,pestmod:9,pestpp:[9,10],phi:[2,3,7,9],phi_compon:9,phi_components_norm:9,phi_m:[],phi_pi:9,phi_r:[],phi_t:[],phi_vec_dict:7,phi_vector:[2,7],phim:3,phimlim:3,php:0,phrase:7,pi_obgnm:9,pie:9,pilbl:[6,9],pilot:10,pilot_points_to_tpl:10,pilotpoint_prior_build:10,place:[8,10],platform:10,player:0,pleas:7,plot:[2,3,7,9,10],plot_col:[2,7],plot_flopy_par_ensembl:10,plot_summary_distribut:10,plot_util:[2,7],plt:[7,10],plu:7,pnew:[5,6],pnulpar:[2,7],point:[2,7,9,10],point_data:10,points_fil:10,pop:7,popul:9,populate_datafram:9,port:[7,9,10],portion:[3,9],posit:[0,2,7],possibl:[2,7],post:[0,7,10],post_cov:7,post_expt:7,post_mean:10,post_stdev:10,posterior:[3,7,10],posterior_forecast:7,posterior_paramet:7,posterior_predict:7,potenti:10,power:0,pp_:10,pp_df:10,pp_dir:10,pp_file:10,pp_file_to_datafram:10,pp_filenam:10,pp_geostruct:10,pp_name:10,pp_prep:10,pp_prop:10,pp_space:10,pp_tpl_to_datafram:10,pp_util:[4,7],pptsw:10,practic:[3,7,10],precondit:7,predict:7,prediction_nam:7,predictions_it:7,prefer:[3,9,10],prefix:[1,2,7,9,10],prefix_dict:10,prep_mlt_arrai:10,prepar:10,prepend:[9,10],preprocess:10,present:3,press:0,previou:3,previous:10,primari:[7,8,9],primarili:[2,7],princip:3,print:[],print_funct:[],prior:[2,3,6,7,9,10],prior_forecast:7,prior_group:9,prior_inform:[6,9,10],prior_mean:10,prior_nam:9,prior_paramet:7,prior_predict:7,prior_stdev:10,privat:[7,8,9],problem:10,proceed:0,process:[3,7,10],produc:10,product:8,program:[0,1,3,9],programat:9,programmat:5,progress:[2,7],project:[1,2,7,10],project_parensembl:[1,7],projection_matrix:[2,7],prop:10,propag:[3,7,8],properti:[2,3,7,8,10],propog:6,proport:9,proportional_weight:9,prototyp:7,provid:[0,5,9,10],prurist:7,pseudo:8,pseudo_inv:8,pseudo_inv_compon:8,psf18:[0,3],psf_18:[],pst:[1,2,4,5,6,7,8,10],pst_controldata:[4,7],pst_demo:[],pst_file:8,pst_filenam:[9,10],pst_from_io_fil:[9,10],pst_from_parnames_obsnam:10,pst_handler:[4,5,6,7],pst_name:6,pst_path:9,pst_rel_path:[9,10],pst_util:[4,7,10],pstfromflopi:10,pstfromflopymodel:10,pt0:10,pt1:10,pt_color:10,pt_zone:10,publish:0,pump:3,purpos:8,put:[],pval:10,pval_fil:10,py18:[],py36:[],py3:[],pydata:0,pyemu:[1,2,3,5,6],pyplot:[7,10],pyshp:10,python27:[],python3:[],python:[0,3,7,9,10],qhalf:7,qhalfx:7,quantifi:3,quantiti:3,quickli:8,radian:10,ragan:0,rail:[2,7],rais:[2,7,8,9,10],random:[2,7,8],rang:[3,7,8,9,10],rank:7,rare:3,rather:7,ratio:[1,7,8,10],rch:10,reach:10,reach_par:10,reachdata:10,reachinput:10,read:[2,3,7,8,9,10],read_ascii:8,read_binari:8,read_parfil:[2,7,9],read_parfiles_prefix:[2,7],read_pestpp_runstorag:10,read_resfil:9,read_sgems_variogram_xml:10,read_struct_fil:10,readonli:10,reads_struct_fil:10,real_nam:[2,7],realiti:3,realiz:[1,2,3,7,10],realli:[2,3,7,9],reason:3,recalibr:[2,7],recarrai:10,recfil:9,rech:10,recharg:10,reciproc:3,recommend:10,record:[8,9],rectifi:[9,10],rectify_pgroup:9,rectify_pi:9,redirect:10,redirect_forward_output:10,reduc:10,reduct:7,ref:10,ref_var:7,refer:[3,7,8,10],reg_data:9,regard:3,regdata:9,rege:0,region:10,regist:10,regul_factor:7,regul_m:6,regul_p:6,regular:[3,7,9,10],regularli:10,regulm:[],regulp:[],regweight_from_parbound:10,rei:6,rel:[3,10],rel_path:[9,10],relat:[9,10],relev:[1,6,7],reli:6,remain:7,remov:[2,7,8,9,10],remove_exist:10,remove_readonli:10,renam:3,reorder:8,repeat:[0,3],replac:[8,9],replace_col:8,replic:[9,10],report:[3,7],repres:[3,7,8,9,10],represent:8,reproduc:[0,5],request:9,requir:[3,6,7,10],res:[6,9],res_df:9,res_fil:7,res_from_obseravtion_data:9,res_group:[],rese:[2,7],research:[0,5],resepct:[],reset:[1,2,7,8,9,10],reset_obscov:7,reset_parcov:7,reset_pst:7,reset_x:8,reset_zero:7,reset_zero_weight:7,resfil:[6,7,9],residu:[3,6,7,9],resolut:7,resourc:10,respect:[2,3,7,10],restart_obsensembl:7,result:[1,3,5,7,9,10],retain:10,return_obs_fil:10,return_typ:10,reutrn:10,rhs:9,right:[8,9],rigid:8,riv:10,river:10,rmse:9,rmtree:10,rnj:10,rnj_filenam:10,root:[7,8,10],rotat:10,rotation_coef:10,round:9,routledg:0,row:[7,8,10],row_index:[],row_nam:8,rs16:[0,3],rs17:[],run:[0,3,5,7,9,10],run_subset:7,runoff:10,runtim:10,s_1:7,safia:0,same:[3,6,7,8,10],sampl:[3,8],sanity_check:9,save:[2,7,8,9,10],save_coo:8,save_mat:7,save_setup_fil:10,scalar:[3,8,10],scale:[2,6,7,8,9,10],scale_offset:[7,8],scandal:0,schmidt:0,schur:[3,7,8,9],scienc:0,scipi:8,screen:[7,10],script:[0,3,10],seamlessli:7,search:[0,8,10],search_radiu:10,second:[3,7,8,10],second_forecast:7,second_paramet:7,second_predict:7,section:[0,5,6,8,9],see:[1,3,7,10],seed:[2,7],seek:[3,7],seem:9,seen:[1,7],seg_group_dict:10,segement:10,segment:10,select:7,selectionmixin:2,self:[2,7,8,9],sensit:[3,7],seo:7,sep:10,separ:10,sequenc:10,sequenti:7,seri:[1,2,5,7,9,10],serial:[7,10],serv:3,set:[0,1,2,3,7,8,9,10],set_r:9,settingwithcopywarn:[],setup:[7,9,10],setup_array_par:10,setup_bc_par:10,setup_gage_ob:10,setup_hd:10,setup_hds_ob:10,setup_hds_timeseri:10,setup_hfb_par:10,setup_hob:10,setup_hyd:10,setup_kl:10,setup_mflist_budget_ob:10,setup_model:10,setup_mtlist_budget_ob:10,setup_mult_dir:10,setup_observ:10,setup_pilot_points_grid:10,setup_pilotpoints_grid:10,setup_sfr_ob:10,setup_sfr_par:10,setup_sfr_reach_paramet:10,setup_sfr_seg_paramet:10,setup_sft_ob:10,setup_smp:10,setup_spatial_bc_par:10,setup_temporal_bc_par:10,setup_water_budget_ob:10,sever:[3,7,9],sfmt:9,sfmt_long:9,sfr:10,sfr_dict:10,sfr_ob:10,sfr_out_fil:10,sfr_par:10,sfr_seg_par:10,sft:10,sft_file:10,sft_out_fil:10,sgem:10,shape:[8,10],shapefil:10,shapenam:10,share:8,shit:10,should:[2,7,9,10],shouldn:[2,7,9],show:[6,7,10],shown:[2,3,6,7],shp:10,shutil:10,side:[8,9],sigma:[7,8],sigma_:7,sigma_rang:[3,7,8,9,10],sigma_theta:7,signatur:7,silent_mast:10,sill:10,sim:9,similar:[3,10],similarli:9,simpl:[6,9],simple_ins_from_ob:10,simple_tpl_from_par:10,simplest:5,simpli:[2,7,8],simplif:3,simul:[0,3,10],sinc:[1,7,8,9,10],singl:[2,3,7,8,10],singluar:7,singular:[1,7,8,9,10],singular_valu:7,site:9,site_nam:10,size:[2,3,7,10],skip:[7,10],slave:[7,10],slave_dir:[7,9,10],slave_root:[9,10],slice:[],slight:9,slightli:7,slow:10,smaller:8,smallest:[1,7,8],smooth:10,smoother:[4,10],smp:[9,10],smp_filenam:9,smp_to_datafram:9,smp_to_in:9,soft:3,softwar:0,solut:[1,7,10],some:[3,6,7,8,9],someon:10,someth:[7,9],somewhat:[9,10],sought:[7,9],sound:10,sourc:[1,2,7,8,9,10],space:[1,2,3,7,10],span:10,spars:[2,7,8,10],sparse_covariance_matrix:10,sparse_geostatistical_prior_build:10,sparsematrix:[2,7,8,10],spatail:10,spatial:[3,10],spatial_bc_geostruct:10,spatial_bc_par:10,spatial_bc_priop:[],spatial_bc_prop:10,spatial_refer:10,spatialrefer:10,special:[9,10],specif:[2,3,7,10],specifi:[3,7,8,9],specifif:10,spectrum:7,speed:[2,7],speedup:8,spheric:10,sphvario:10,split:9,sqrt:8,squar:[3,7,8,9],stabl:[],stack:9,stamen:10,stand:9,standard:[3,7,8,9,10],standard_devi:7,starn:0,start:[1,3,7,9,10],start_datatim:10,start_datetim:10,start_slav:[9,10],stat:9,state:[3,5,6,9],statement:7,statist:[0,9],statu:[2,7],stdev:10,stdout:10,stefan:0,step:[5,10],stepp:0,stle:8,stochast:[1,7],storag:[8,10],store:[3,5,7,9],str:[1,2,7,8,9,10],str_con:9,straight:10,straightforward:[],strategi:[2,7],strength:3,stress:[9,10],strhc1:10,string:[7,9,10],stringmixin:2,strong:3,strptime:9,struct1:10,struct:10,struct_dict:10,struct_fil:10,structur:[0,2,3,5,7,10],strutur:10,stuart:0,stuff:[],style:[0,2,7,8,10],sub:10,subclass:[3,8],submit:7,submit_fil:7,submodul:[4,5],subpackag:4,subplot:10,subscript:3,subsequ:[2,7,10],subset:[2,5,6,7,9],subspac:[1,7],success:[7,9,10],successfulli:10,suffix:[9,10],sugar:[7,9],suit:[0,5],sum:[3,9],sum_:[],summar:[5,7,10],summari:[7,9],superclass:3,superscript:3,suppli:[6,10],support:[1,2,7,8,9,10],sure:10,survei:[0,3],svd:[8,9],svd_data:9,svddata:9,sw_filenam:10,sw_prefix:10,sweep:[1,7,10],sylvain:0,symbol:3,synchron:9,synonym:7,syntat:9,system:[0,3,7,10],tabl:9,tabular:6,take:10,task:[5,10],tbe:10,tcp:7,team:0,technic:5,techniqu:[0,3],teh:9,tempchek:10,templat:[5,7,9,10],template_fil:9,temporal_bc_geostruct:10,temporal_bc_prop:10,temporari:10,term:[3,7,8],terminolog:[0,5],terribl:9,test:[3,6,7,10],tex:9,text:[7,10],than:[2,3,7,8,10],thei:[2,3,7,9,10],them:[0,5,10],theoerem:3,theorem:3,theta:[],thi:[0,1,2,3,6,7,8,9,10],thin:[2,7,8],thing:[3,7,8,9],third:7,third_forecast:7,third_paramet:7,third_predict:7,thoma:0,thought:5,three:[3,7],threshold:[1,7],through:3,thu:[0,3],tie_hcond:10,tied:[3,9],tikhonov:9,tile:10,time:[3,7,9,10],timestamp:10,tm7c12:0,tmp_file:10,to_ascii:[7,8,10],to_binari:[2,7,8,10],to_coo:[8,10],to_csv:[2,7,10],to_datafram:8,to_datetim:10,to_grid_factor_fil:10,to_grid_factors_fil:10,to_matrix:8,to_parfil:[2,7],to_pearson:8,to_spars:8,to_struct_fil:10,to_uncfil:8,toctre:[],todo:8,togeth:10,toler:[8,10],ton:10,total:[3,7,9,10],totim:10,tpl:[9,10],tpl_dir:10,tpl_file:[9,10],tpl_filenam:10,tpl_marker:10,tpl_str:10,tplfilenam:10,trade:3,tradeoff:3,tradit:[3,5],tradition:5,transfer:7,transform:[2,7,9,10],transgress:[2,7],transient2d:10,transport:10,transpos:[2,7,8],trash:8,travi:7,treat:[2,7,9,10],tri:[7,8,9],triue:[],trunc:8,truncat:[7,8],try_process_ins_fil:9,try_run_inschek:9,tupl:[8,10],tutori:0,two:[3,8,10],typ:[2,7],type:[1,2,3,7,8,9,10],typic:[2,3,7],u2d:10,u_1:7,ubnd:[2,7],ucn:10,ucnfil:10,uint8:8,unc_fil:8,uncertain:[0,3],uncertainti:[0,3,5,7,8,10],uncfil:10,under:3,understand:3,uniform:[1,2,7,10],uniqu:[9,10],unit:10,uniti:[],univari:3,unless:[7,10],unstabl:10,until:7,updat:[2,3,6,7,9],update_regul:9,upgrad:[1,2,7],upper:[2,7,8,10],upper_lim:10,uppermost:10,usag:7,use:[0,1,2,3,5,7,8,9,10],use_approx:7,use_approx_prior:7,use_generic_nam:9,use_homegrown:[2,7],use_ibound_zon:10,use_pp_zon:10,used:[1,2,3,5,6,7,8,9,10],useful:[1,2,7,9,10],user:[0,2,5,7,9,10],uses:[2,3,5,7,8,10],usg:10,using:[0,1,2,3,5,6,7,8,9,10],usual:[3,10],usum:10,utiil:10,util2d:10,util3d:10,util:[4,7,9],v2_proj:[1,7],v2v2:[1,7],v_1:7,val:[2,7,9,10],valu:[1,2,3,6,7,8,9,10],value_col:9,value_format:9,van:0,var1:10,var_filenam:10,var_mult:8,varainc:8,vari:[7,9,10],variabl:[2,3,6,7,10],varianc:[0,3,7,8,10],variance_at:7,vario2d:10,variogram:[3,10],variou:[3,9],vector:[2,3,7,8,10],verbos:[7,10],veri:[2,3,7,10],version:[0,3,10],versu:[],via:[0,3,7],view:[],violat:[2,3,7,9],vka:10,vka_:10,vol:10,vol_fil:10,vol_filenam:10,volum:[2,7,10],wai:[5,7,9],walt:0,want:[3,8,10],warn:[2,7,10],water:10,watermark:0,weak:3,weight:[1,2,3,6,7,8,9,10],wel:10,well:[3,9,10],welter:0,were:[5,9],wfd16:0,wfd_16:[],what:[0,2,3,7,9],when:[3,7,10],where:[1,3,7,9,10],whether:10,which:[3,5,7,9,10],white:0,whitespac:9,wide:9,width:10,wildass_guess_par_bounds_dict:10,window:10,wise:[7,8],within:[2,3,7,10],without:[9,10],wmax:9,word:3,work:[7,10],workflow:[0,1,5],workspac:10,world:3,worth:[0,7,9],would:[3,7,10],wrap:10,wrapper:[2,7,8],write:[0,1,2,3,5,6,7,8,9,10],write_const_tpl:10,write_df_tpl:10,write_forward_run:10,write_grid_tpl:10,write_hfb_templ:10,write_hfb_zone_multipliers_templ:10,write_input_fil:9,write_obs_summary_t:9,write_par_summary_t:9,write_parfil:9,write_pp_fil:10,write_pp_shapfil:10,write_pst:[1,7],write_to_templ:9,write_u2d:10,write_zone_tpl:10,written:[0,6,7,8,9,10],wrt:7,wwhd15:0,www:0,x_idx:10,xml:10,xml_file:10,xother:10,xtqt:7,xtqx:[1,7],y_idx:10,yet:7,yield:[7,10],yother:10,you:[2,6,7,9,10],yuck:10,yyi:9,yyyi:9,zero2d:8,zero:[2,7,8,9,10],zero_order_tikhonov:[9,10],zero_weight_obs_nam:9,zn_arrai:10,zn_suffix:10,zone:10,zone_arrai:10,zone_fil:10,zone_prop:10},titles:["Welcome to pyEMU\u2019s documentation!","Run Monte Carlo Simulations from PEST Object","Ensembles","Glossary","pyemu","Notes on Object-Oriented Programming","Example Object-Oriented Access to the PEST Control File","pyemu package","pyemu.mat package","pyemu.pst package","pyemu.utils package"],titleterms:{"class":[],For:[],The:[],access:6,carlo:1,content:[0,7,8,9,10],control:6,document:0,ensembl:2,exampl:[1,6,7,10],file:6,from:1,geostat:10,glossari:3,gw_util:10,helper:10,indic:[],introduct:[],logger:7,mat:8,mat_handl:8,modul:[7,8,9,10],mont:1,note:5,object:[1,5,6],optim:10,orient:[5,6],packag:[7,8,9,10],pest:[1,6],pp_util:10,program:5,pst:9,pst_controldata:9,pst_handler:9,pst_util:9,pyemu:[0,4,7,8,9,10],refer:0,run:1,simul:1,smoother:7,submodul:[7,8,9,10],subpackag:7,tabl:[],technic:0,todo:10,util:10,welcom:0}}) \ No newline at end of file +Search.setIndex({docnames:["_autosummary/pyemu","_autosummary/pyemu.ErrVar","_autosummary/pyemu.ParameterEnsemble","_autosummary/pyemu.Pst","_autosummary/pyemu.Schur","autoapi/index","autoapi/pyemu/_version/index","autoapi/pyemu/en/index","autoapi/pyemu/ev/index","autoapi/pyemu/index","autoapi/pyemu/la/index","autoapi/pyemu/logger/index","autoapi/pyemu/mat/index","autoapi/pyemu/mat/mat_handler/index","autoapi/pyemu/mc/index","autoapi/pyemu/plot/index","autoapi/pyemu/plot/plot_utils/index","autoapi/pyemu/prototypes/da/index","autoapi/pyemu/prototypes/ensemble_method/index","autoapi/pyemu/prototypes/index","autoapi/pyemu/prototypes/moouu/index","autoapi/pyemu/pst/index","autoapi/pyemu/pst/pst_controldata/index","autoapi/pyemu/pst/pst_handler/index","autoapi/pyemu/pst/pst_utils/index","autoapi/pyemu/pyemu_warnings/index","autoapi/pyemu/sc/index","autoapi/pyemu/utils/geostats/index","autoapi/pyemu/utils/gw_utils/index","autoapi/pyemu/utils/helpers/index","autoapi/pyemu/utils/index","autoapi/pyemu/utils/optimization/index","autoapi/pyemu/utils/os_utils/index","autoapi/pyemu/utils/pp_utils/index","autoapi/pyemu/utils/pst_from/index","autoapi/pyemu/utils/smp_utils/index","index","index1","modules","pyemu","pyemu.mat","pyemu.plot","pyemu.prototypes","pyemu.pst","pyemu.utils"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,sphinx:56},filenames:["_autosummary/pyemu.rst","_autosummary/pyemu.ErrVar.rst","_autosummary/pyemu.ParameterEnsemble.rst","_autosummary/pyemu.Pst.rst","_autosummary/pyemu.Schur.rst","autoapi/index.rst","autoapi/pyemu/_version/index.rst","autoapi/pyemu/en/index.rst","autoapi/pyemu/ev/index.rst","autoapi/pyemu/index.rst","autoapi/pyemu/la/index.rst","autoapi/pyemu/logger/index.rst","autoapi/pyemu/mat/index.rst","autoapi/pyemu/mat/mat_handler/index.rst","autoapi/pyemu/mc/index.rst","autoapi/pyemu/plot/index.rst","autoapi/pyemu/plot/plot_utils/index.rst","autoapi/pyemu/prototypes/da/index.rst","autoapi/pyemu/prototypes/ensemble_method/index.rst","autoapi/pyemu/prototypes/index.rst","autoapi/pyemu/prototypes/moouu/index.rst","autoapi/pyemu/pst/index.rst","autoapi/pyemu/pst/pst_controldata/index.rst","autoapi/pyemu/pst/pst_handler/index.rst","autoapi/pyemu/pst/pst_utils/index.rst","autoapi/pyemu/pyemu_warnings/index.rst","autoapi/pyemu/sc/index.rst","autoapi/pyemu/utils/geostats/index.rst","autoapi/pyemu/utils/gw_utils/index.rst","autoapi/pyemu/utils/helpers/index.rst","autoapi/pyemu/utils/index.rst","autoapi/pyemu/utils/optimization/index.rst","autoapi/pyemu/utils/os_utils/index.rst","autoapi/pyemu/utils/pp_utils/index.rst","autoapi/pyemu/utils/pst_from/index.rst","autoapi/pyemu/utils/smp_utils/index.rst","index.rst","index1.rst","modules.rst","pyemu.rst","pyemu.mat.rst","pyemu.plot.rst","pyemu.prototypes.rst","pyemu.pst.rst","pyemu.utils.rst"],objects:{"":{pyemu:[9,0,0,"-"]},"pyemu.Cov":{_get_uncfile_dimensions:[9,2,1,"id36"],condition_on:[9,2,1,"id27"],from_observation_data:[9,2,1,"id32"],from_obsweights:[9,2,1,"id31"],from_parameter_data:[9,2,1,"id34"],from_parbounds:[9,2,1,"id33"],from_uncfile:[9,2,1,"id35"],identity:[9,2,1,"id25"],identity_like:[9,2,1,"id37"],names:[9,2,1,"id28"],replace:[9,2,1,"id29"],to_pearson:[9,2,1,"id38"],to_uncfile:[9,2,1,"id30"],zero:[9,2,1,"id26"]},"pyemu.Ensemble":{__add__:[9,2,1,""],__getattr__:[9,2,1,""],__mul__:[9,2,1,""],__pow__:[9,2,1,""],__repr__:[9,2,1,""],__str__:[9,2,1,""],__sub__:[9,2,1,""],__truediv__:[9,2,1,""],_df:[9,3,1,""],_gaussian_draw:[9,2,1,""],_get_eigen_projection_matrix:[9,2,1,""],_get_svd_projection_matrix:[9,2,1,""],as_pyemu_matrix:[9,2,1,""],back_transform:[9,2,1,""],copy:[9,2,1,""],covariance_matrix:[9,2,1,""],dropna:[9,2,1,""],from_binary:[9,2,1,""],from_csv:[9,2,1,""],from_dataframe:[9,2,1,""],get_deviations:[9,2,1,""],istransformed:[9,2,1,""],plot:[9,2,1,""],pst:[9,3,1,""],reseed:[9,2,1,""],to_binary:[9,2,1,""],to_csv:[9,2,1,""],transform:[9,2,1,""]},"pyemu.ErrVar":{G:[9,2,1,""],I_minus_R:[9,2,1,""],R:[9,2,1,""],__load_omitted_jco:[9,2,1,""],__load_omitted_parcov:[9,2,1,""],__load_omitted_predictions:[9,2,1,""],first_forecast:[9,2,1,""],first_parameter:[9,2,1,""],first_prediction:[9,2,1,""],get_errvar_dataframe:[9,2,1,""],get_identifiability_dataframe:[9,2,1,""],get_null_proj:[9,2,1,""],omitted_jco:[9,2,1,""],omitted_parcov:[9,2,1,""],omitted_predictions:[9,2,1,""],second_forecast:[9,2,1,""],second_parameter:[9,2,1,""],second_prediction:[9,2,1,""],third_forecast:[9,2,1,""],third_parameter:[9,2,1,""],third_prediction:[9,2,1,""],variance_at:[9,2,1,""]},"pyemu.Jco":{__init:[9,2,1,""],from_pst:[9,2,1,""],nobs:[9,2,1,""],npar:[9,2,1,""],obs_names:[9,2,1,""],par_names:[9,2,1,""]},"pyemu.LinearAnalysis":{__fromfile:[9,2,1,""],__load_jco:[9,2,1,""],__load_obscov:[9,2,1,""],__load_parcov:[9,2,1,""],__load_predictions:[9,2,1,""],__load_pst:[9,2,1,""],adj_par_names:[9,2,1,""],adjust_obscov_resfile:[9,2,1,""],apply_karhunen_loeve_scaling:[9,2,1,""],clean:[9,2,1,""],drop_prior_information:[9,2,1,""],fehalf:[9,2,1,""],forecast_names:[9,2,1,""],forecasts:[9,2,1,""],forecasts_iter:[9,2,1,""],get:[9,2,1,""],get_cso_dataframe:[9,2,1,""],get_obs_competition_dataframe:[9,2,1,""],get_par_css_dataframe:[9,2,1,""],jco:[9,2,1,""],mle_covariance:[9,2,1,""],mle_parameter_estimate:[9,2,1,""],nnz_obs_names:[9,2,1,""],obscov:[9,2,1,""],parcov:[9,2,1,""],predictions:[9,2,1,""],predictions_iter:[9,2,1,""],prior_forecast:[9,2,1,""],prior_parameter:[9,2,1,""],prior_prediction:[9,2,1,""],pst:[9,2,1,""],qhalf:[9,2,1,""],qhalfx:[9,2,1,""],reset_obscov:[9,2,1,""],reset_parcov:[9,2,1,""],reset_pst:[9,2,1,""],xtqx:[9,2,1,""]},"pyemu.Matrix":{"char":[9,3,1,"id42"],"double":[9,3,1,"id41"],T:[9,2,1,"id69"],__add__:[9,2,1,"id55"],__getitem__:[9,2,1,"id52"],__mul__:[9,2,1,"id57"],__pow__:[9,2,1,"id53"],__rmul__:[9,2,1,"id58"],__set_svd:[9,2,1,"id59"],__str__:[9,2,1,"id51"],__sub__:[9,2,1,"id54"],align:[9,2,1,"id84"],as_2d:[9,2,1,"id64"],binary_header_dt:[9,3,1,"id43"],binary_rec_dt:[9,3,1,"id44"],coo_rec_dt:[9,3,1,"id45"],copy:[9,2,1,"id86"],df:[9,2,1,"id98"],drop:[9,2,1,"id87"],element_isaligned:[9,2,1,"id61"],extend:[9,2,1,"id102"],extract:[9,2,1,"id88"],find_rowcol_indices:[9,2,1,"id82"],from_ascii:[9,2,1,"id96"],from_binary:[9,2,1,"id92"],from_dataframe:[9,2,1,"id99"],from_fortranfile:[9,2,1,"id94"],from_names:[9,2,1,"id100"],full_s:[9,2,1,"id77"],get:[9,2,1,"id85"],get_diagonal_vector:[9,2,1,"id89"],get_maxsing:[9,2,1,"id73"],get_maxsing_from_s:[9,2,1,"id72"],hadamard_product:[9,2,1,"id56"],indices:[9,2,1,"id83"],integer:[9,3,1,"id40"],inv:[9,2,1,"id71"],mult_isaligned:[9,2,1,"id60"],ncol:[9,2,1,"id67"],new_obs_length:[9,3,1,"id49"],new_par_length:[9,3,1,"id48"],newx:[9,2,1,"id62"],nrow:[9,2,1,"id68"],obs_length:[9,3,1,"id47"],par_length:[9,3,1,"id46"],pseudo_inv:[9,2,1,"id75"],pseudo_inv_components:[9,2,1,"id74"],read_ascii:[9,2,1,"id97"],read_binary:[9,2,1,"id93"],reset_x:[9,2,1,"id50"],s:[9,2,1,"id78"],shape:[9,2,1,"id66"],sqrt:[9,2,1,"id76"],to_2d:[9,2,1,"id65"],to_ascii:[9,2,1,"id95"],to_binary:[9,2,1,"id91"],to_coo:[9,2,1,"id90"],to_dataframe:[9,2,1,"id101"],transpose:[9,2,1,"id70"],u:[9,2,1,"id79"],v:[9,2,1,"id80"],x:[9,2,1,"id63"],zero2d:[9,2,1,"id81"]},"pyemu.ObservationEnsemble":{add_base:[9,2,1,"id22"],from_gaussian_draw:[9,2,1,"id20"],nonzero:[9,2,1,"id23"],phi_vector:[9,2,1,"id21"]},"pyemu.ParameterEnsemble":{_enforce_drop:[9,2,1,"id17"],_enforce_reset:[9,2,1,"id18"],_enforce_scale:[9,2,1,"id16"],add_base:[9,2,1,"id8"],adj_names:[9,2,1,"id9"],back_transform:[9,2,1,"id6"],enforce:[9,2,1,"id15"],fixed_indexer:[9,2,1,"id13"],from_gaussian_draw:[9,2,1,"id1"],from_mixed_draws:[9,2,1,"id4"],from_parfiles:[9,2,1,"id5"],from_triangular_draw:[9,2,1,"id2"],from_uniform_draw:[9,2,1,"id3"],lbnd:[9,2,1,"id11"],log_indexer:[9,2,1,"id12"],project:[9,2,1,"id14"],transform:[9,2,1,"id7"],ubnd:[9,2,1,"id10"]},"pyemu.Pst":{__reset_weights:[9,2,1,"id161"],__setattr__:[9,2,1,"id110"],_adjust_weights_by_list:[9,2,1,"id162"],_adjust_weights_by_phi_components:[9,2,1,"id160"],_cast_df_from_lines:[9,2,1,"id141"],_cast_prior_df_from_lines:[9,2,1,"id142"],_is_greater_const:[9,2,1,"id186"],_is_less_const:[9,2,1,"id183"],_load_version2:[9,2,1,"id143"],_parse_external_line:[9,2,1,"id139"],_parse_path_agnostic:[9,2,1,"id140"],_parse_pestpp_line:[9,2,1,"id145"],_parse_pi_par_names:[9,2,1,"id148"],_read_df:[9,2,1,"id136"],_read_line_comments:[9,2,1,"id137"],_read_section_comments:[9,2,1,"id138"],_stats_mae:[9,2,1,"id177"],_stats_mean:[9,2,1,"id176"],_stats_nrmse:[9,2,1,"id179"],_stats_rmse:[9,2,1,"id178"],_stats_rss:[9,2,1,"id175"],_update_control_section:[9,2,1,"id146"],_write_df:[9,2,1,"id151"],_write_version1:[9,2,1,"id155"],_write_version2:[9,2,1,"id153"],add_observations:[9,2,1,"id171"],add_parameters:[9,2,1,"id170"],add_pi_equation:[9,2,1,"id149"],add_transform_columns:[9,2,1,"id167"],adj_par_groups:[9,2,1,"id125"],adj_par_names:[9,2,1,"id130"],adjust_weights:[9,2,1,"id163"],adjust_weights_discrepancy:[9,2,1,"id159"],bounds_report:[9,2,1,"id156"],build_increments:[9,2,1,"id166"],calculate_pertubations:[9,2,1,"id165"],control_data:[9,3,1,"id107"],enforce_bounds:[9,2,1,"id168"],estimation:[9,2,1,"id134"],forecast_names:[9,2,1,"id122"],from_io_files:[9,2,1,"id169"],from_par_obs_names:[9,2,1,"id111"],get:[9,2,1,"id157"],get_adj_pars_at_bounds:[9,2,1,"id190"],get_par_change_limits:[9,2,1,"id189"],get_res_stats:[9,2,1,"id174"],greater_than_obs_constraints:[9,2,1,"id187"],greater_than_pi_constraints:[9,2,1,"id188"],less_than_obs_constraints:[9,2,1,"id184"],less_than_pi_constraints:[9,2,1,"id185"],load:[9,2,1,"id144"],nnz_obs:[9,2,1,"id118"],nnz_obs_groups:[9,2,1,"id124"],nnz_obs_names:[9,2,1,"id132"],nobs:[9,2,1,"id119"],npar:[9,2,1,"id121"],npar_adj:[9,2,1,"id120"],nprior:[9,2,1,"id117"],obs_groups:[9,2,1,"id123"],obs_names:[9,2,1,"id131"],observation_data:[9,3,1,"id105"],par_groups:[9,2,1,"id126"],par_names:[9,2,1,"id129"],parameter_data:[9,3,1,"id104"],parrep:[9,2,1,"id158"],phi:[9,2,1,"id112"],phi_components:[9,2,1,"id113"],phi_components_normalized:[9,2,1,"id114"],plot:[9,2,1,"id180"],prior_groups:[9,2,1,"id127"],prior_information:[9,3,1,"id106"],prior_names:[9,2,1,"id128"],process_output_files:[9,2,1,"id173"],proportional_weights:[9,2,1,"id164"],rectify_pgroups:[9,2,1,"id147"],rectify_pi:[9,2,1,"id150"],reg_data:[9,3,1,"id109"],res:[9,2,1,"id116"],sanity_checks:[9,2,1,"id152"],set_res:[9,2,1,"id115"],svd_data:[9,3,1,"id108"],tied:[9,2,1,"id135"],try_parse_name_metadata:[9,2,1,"id191"],write:[9,2,1,"id154"],write_input_files:[9,2,1,"id172"],write_obs_summary_table:[9,2,1,"id182"],write_par_summary_table:[9,2,1,"id181"],zero_weight_obs_names:[9,2,1,"id133"]},"pyemu.Schur":{__contribution_from_parameters:[9,2,1,""],get_added_obs_group_importance:[9,2,1,""],get_added_obs_importance:[9,2,1,""],get_conditional_instance:[9,2,1,""],get_forecast_summary:[9,2,1,""],get_obs_group_dict:[9,2,1,""],get_par_contribution:[9,2,1,""],get_par_group_contribution:[9,2,1,""],get_parameter_summary:[9,2,1,""],get_removed_obs_group_importance:[9,2,1,""],get_removed_obs_importance:[9,2,1,""],next_most_important_added_obs:[9,2,1,""],next_most_par_contribution:[9,2,1,""],posterior_forecast:[9,2,1,""],posterior_parameter:[9,2,1,""],posterior_prediction:[9,2,1,""]},"pyemu._version":{HANDLERS:[6,4,1,""],LONG_VERSION_PY:[6,4,1,""],NotThisMethod:[6,5,1,""],VersioneerConfig:[6,1,1,""],get_config:[6,6,1,""],get_keywords:[6,6,1,""],get_versions:[6,6,1,""],git_get_keywords:[6,6,1,""],git_pieces_from_vcs:[6,6,1,""],git_versions_from_keywords:[6,6,1,""],plus_or_dot:[6,6,1,""],register_vcs_handler:[6,6,1,""],render:[6,6,1,""],render_git_describe:[6,6,1,""],render_git_describe_long:[6,6,1,""],render_pep440:[6,6,1,""],render_pep440_old:[6,6,1,""],render_pep440_post:[6,6,1,""],render_pep440_pre:[6,6,1,""],run_command:[6,6,1,""],versions_from_parentdir:[6,6,1,""]},"pyemu.en":{Ensemble:[7,1,1,""],Iloc:[7,1,1,""],Loc:[7,1,1,""],ObservationEnsemble:[7,1,1,""],ParameterEnsemble:[7,1,1,""],SEED:[7,4,1,""]},"pyemu.en.Ensemble":{__add__:[7,2,1,""],__getattr__:[7,2,1,""],__mul__:[7,2,1,""],__pow__:[7,2,1,""],__repr__:[7,2,1,""],__str__:[7,2,1,""],__sub__:[7,2,1,""],__truediv__:[7,2,1,""],_df:[7,3,1,""],_gaussian_draw:[7,2,1,""],_get_eigen_projection_matrix:[7,2,1,""],_get_svd_projection_matrix:[7,2,1,""],as_pyemu_matrix:[7,2,1,""],back_transform:[7,2,1,""],copy:[7,2,1,""],covariance_matrix:[7,2,1,""],dropna:[7,2,1,""],from_binary:[7,2,1,""],from_csv:[7,2,1,""],from_dataframe:[7,2,1,""],get_deviations:[7,2,1,""],istransformed:[7,2,1,""],plot:[7,2,1,""],pst:[7,3,1,""],reseed:[7,2,1,""],to_binary:[7,2,1,""],to_csv:[7,2,1,""],transform:[7,2,1,""]},"pyemu.en.Iloc":{__getitem__:[7,2,1,""],__setitem__:[7,2,1,""]},"pyemu.en.Loc":{__getitem__:[7,2,1,""],__setitem__:[7,2,1,""]},"pyemu.en.ObservationEnsemble":{add_base:[7,2,1,""],from_gaussian_draw:[7,2,1,""],nonzero:[7,2,1,""],phi_vector:[7,2,1,""]},"pyemu.en.ParameterEnsemble":{_enforce_drop:[7,2,1,""],_enforce_reset:[7,2,1,""],_enforce_scale:[7,2,1,""],add_base:[7,2,1,""],adj_names:[7,2,1,""],back_transform:[7,2,1,""],enforce:[7,2,1,""],fixed_indexer:[7,2,1,""],from_gaussian_draw:[7,2,1,""],from_mixed_draws:[7,2,1,""],from_parfiles:[7,2,1,""],from_triangular_draw:[7,2,1,""],from_uniform_draw:[7,2,1,""],lbnd:[7,2,1,""],log_indexer:[7,2,1,""],project:[7,2,1,""],transform:[7,2,1,""],ubnd:[7,2,1,""]},"pyemu.ev":{ErrVar:[8,1,1,""]},"pyemu.ev.ErrVar":{G:[8,2,1,""],I_minus_R:[8,2,1,""],R:[8,2,1,""],__load_omitted_jco:[8,2,1,""],__load_omitted_parcov:[8,2,1,""],__load_omitted_predictions:[8,2,1,""],first_forecast:[8,2,1,""],first_parameter:[8,2,1,""],first_prediction:[8,2,1,""],get_errvar_dataframe:[8,2,1,""],get_identifiability_dataframe:[8,2,1,""],get_null_proj:[8,2,1,""],omitted_jco:[8,2,1,""],omitted_parcov:[8,2,1,""],omitted_predictions:[8,2,1,""],second_forecast:[8,2,1,""],second_parameter:[8,2,1,""],second_prediction:[8,2,1,""],third_forecast:[8,2,1,""],third_parameter:[8,2,1,""],third_prediction:[8,2,1,""],variance_at:[8,2,1,""]},"pyemu.la":{LinearAnalysis:[10,1,1,""]},"pyemu.la.LinearAnalysis":{__fromfile:[10,2,1,""],__load_jco:[10,2,1,""],__load_obscov:[10,2,1,""],__load_parcov:[10,2,1,""],__load_predictions:[10,2,1,""],__load_pst:[10,2,1,""],adj_par_names:[10,2,1,""],adjust_obscov_resfile:[10,2,1,""],apply_karhunen_loeve_scaling:[10,2,1,""],clean:[10,2,1,""],drop_prior_information:[10,2,1,""],fehalf:[10,2,1,""],forecast_names:[10,2,1,""],forecasts:[10,2,1,""],forecasts_iter:[10,2,1,""],get:[10,2,1,""],get_cso_dataframe:[10,2,1,""],get_obs_competition_dataframe:[10,2,1,""],get_par_css_dataframe:[10,2,1,""],jco:[10,2,1,""],mle_covariance:[10,2,1,""],mle_parameter_estimate:[10,2,1,""],nnz_obs_names:[10,2,1,""],obscov:[10,2,1,""],parcov:[10,2,1,""],predictions:[10,2,1,""],predictions_iter:[10,2,1,""],prior_forecast:[10,2,1,""],prior_parameter:[10,2,1,""],prior_prediction:[10,2,1,""],pst:[10,2,1,""],qhalf:[10,2,1,""],qhalfx:[10,2,1,""],reset_obscov:[10,2,1,""],reset_parcov:[10,2,1,""],reset_pst:[10,2,1,""],xtqx:[10,2,1,""]},"pyemu.logger":{Logger:[11,1,1,""]},"pyemu.logger.Logger":{log:[11,2,1,""],lraise:[11,2,1,""],statement:[11,2,1,""],warn:[11,2,1,""]},"pyemu.mat":{Cov:[12,1,1,""],Jco:[12,1,1,""],Matrix:[12,1,1,""],concat:[12,6,1,""],mat_handler:[13,0,0,"-"],save_coo:[12,6,1,""]},"pyemu.mat.Cov":{_get_uncfile_dimensions:[12,2,1,""],condition_on:[12,2,1,""],from_observation_data:[12,2,1,""],from_obsweights:[12,2,1,""],from_parameter_data:[12,2,1,""],from_parbounds:[12,2,1,""],from_uncfile:[12,2,1,""],identity:[12,2,1,""],identity_like:[12,2,1,""],names:[12,2,1,""],replace:[12,2,1,""],to_pearson:[12,2,1,""],to_uncfile:[12,2,1,""],zero:[12,2,1,""]},"pyemu.mat.Jco":{__init:[12,2,1,""],from_pst:[12,2,1,""],nobs:[12,2,1,""],npar:[12,2,1,""],obs_names:[12,2,1,""],par_names:[12,2,1,""]},"pyemu.mat.Matrix":{"char":[12,3,1,""],"double":[12,3,1,""],T:[12,2,1,""],__add__:[12,2,1,""],__getitem__:[12,2,1,""],__mul__:[12,2,1,""],__pow__:[12,2,1,""],__rmul__:[12,2,1,""],__set_svd:[12,2,1,""],__str__:[12,2,1,""],__sub__:[12,2,1,""],align:[12,2,1,""],as_2d:[12,2,1,""],binary_header_dt:[12,3,1,""],binary_rec_dt:[12,3,1,""],coo_rec_dt:[12,3,1,""],copy:[12,2,1,""],df:[12,2,1,""],drop:[12,2,1,""],element_isaligned:[12,2,1,""],extend:[12,2,1,""],extract:[12,2,1,""],find_rowcol_indices:[12,2,1,""],from_ascii:[12,2,1,""],from_binary:[12,2,1,""],from_dataframe:[12,2,1,""],from_fortranfile:[12,2,1,""],from_names:[12,2,1,""],full_s:[12,2,1,""],get:[12,2,1,""],get_diagonal_vector:[12,2,1,""],get_maxsing:[12,2,1,""],get_maxsing_from_s:[12,2,1,""],hadamard_product:[12,2,1,""],indices:[12,2,1,""],integer:[12,3,1,""],inv:[12,2,1,""],mult_isaligned:[12,2,1,""],ncol:[12,2,1,""],new_obs_length:[12,3,1,""],new_par_length:[12,3,1,""],newx:[12,2,1,""],nrow:[12,2,1,""],obs_length:[12,3,1,""],par_length:[12,3,1,""],pseudo_inv:[12,2,1,""],pseudo_inv_components:[12,2,1,""],read_ascii:[12,2,1,""],read_binary:[12,2,1,""],reset_x:[12,2,1,""],s:[12,2,1,""],shape:[12,2,1,""],sqrt:[12,2,1,""],to_2d:[12,2,1,""],to_ascii:[12,2,1,""],to_binary:[12,2,1,""],to_coo:[12,2,1,""],to_dataframe:[12,2,1,""],transpose:[12,2,1,""],u:[12,2,1,""],v:[12,2,1,""],x:[12,2,1,""],zero2d:[12,2,1,""]},"pyemu.mat.mat_handler":{Cov:[13,1,1,""],Jco:[13,1,1,""],Matrix:[13,1,1,""],concat:[13,6,1,""],get_common_elements:[13,6,1,""],save_coo:[13,6,1,""]},"pyemu.mat.mat_handler.Cov":{_get_uncfile_dimensions:[13,2,1,""],condition_on:[13,2,1,""],from_observation_data:[13,2,1,""],from_obsweights:[13,2,1,""],from_parameter_data:[13,2,1,""],from_parbounds:[13,2,1,""],from_uncfile:[13,2,1,""],identity:[13,2,1,""],identity_like:[13,2,1,""],names:[13,2,1,""],replace:[13,2,1,""],to_pearson:[13,2,1,""],to_uncfile:[13,2,1,""],zero:[13,2,1,""]},"pyemu.mat.mat_handler.Jco":{__init:[13,2,1,""],from_pst:[13,2,1,""],nobs:[13,2,1,""],npar:[13,2,1,""],obs_names:[13,2,1,""],par_names:[13,2,1,""]},"pyemu.mat.mat_handler.Matrix":{"char":[13,3,1,""],"double":[13,3,1,""],T:[13,2,1,""],__add__:[13,2,1,""],__getitem__:[13,2,1,""],__mul__:[13,2,1,""],__pow__:[13,2,1,""],__rmul__:[13,2,1,""],__set_svd:[13,2,1,""],__str__:[13,2,1,""],__sub__:[13,2,1,""],align:[13,2,1,""],as_2d:[13,2,1,""],binary_header_dt:[13,3,1,""],binary_rec_dt:[13,3,1,""],coo_rec_dt:[13,3,1,""],copy:[13,2,1,""],df:[13,2,1,""],drop:[13,2,1,""],element_isaligned:[13,2,1,""],extend:[13,2,1,""],extract:[13,2,1,""],find_rowcol_indices:[13,2,1,""],from_ascii:[13,2,1,""],from_binary:[13,2,1,""],from_dataframe:[13,2,1,""],from_fortranfile:[13,2,1,""],from_names:[13,2,1,""],full_s:[13,2,1,""],get:[13,2,1,""],get_diagonal_vector:[13,2,1,""],get_maxsing:[13,2,1,""],get_maxsing_from_s:[13,2,1,""],hadamard_product:[13,2,1,""],indices:[13,2,1,""],integer:[13,3,1,""],inv:[13,2,1,""],mult_isaligned:[13,2,1,""],ncol:[13,2,1,""],new_obs_length:[13,3,1,""],new_par_length:[13,3,1,""],newx:[13,2,1,""],nrow:[13,2,1,""],obs_length:[13,3,1,""],par_length:[13,3,1,""],pseudo_inv:[13,2,1,""],pseudo_inv_components:[13,2,1,""],read_ascii:[13,2,1,""],read_binary:[13,2,1,""],reset_x:[13,2,1,""],s:[13,2,1,""],shape:[13,2,1,""],sqrt:[13,2,1,""],to_2d:[13,2,1,""],to_ascii:[13,2,1,""],to_binary:[13,2,1,""],to_coo:[13,2,1,""],to_dataframe:[13,2,1,""],transpose:[13,2,1,""],u:[13,2,1,""],v:[13,2,1,""],x:[13,2,1,""],zero2d:[13,2,1,""]},"pyemu.mc":{MonteCarlo:[14,1,1,""]},"pyemu.mc.MonteCarlo":{draw:[14,2,1,""],get_nsing:[14,2,1,""],get_null_proj:[14,2,1,""],num_reals:[14,2,1,""],obsensemble:[14,3,1,""],parensemble:[14,3,1,""],project_parensemble:[14,2,1,""],write_psts:[14,2,1,""]},"pyemu.plot":{plot_utils:[16,0,0,"-"]},"pyemu.plot.plot_utils":{_get_page_axes:[16,6,1,""],_process_ensemble_arg:[16,6,1,""],abet:[16,4,1,""],ensemble_change_summary:[16,6,1,""],ensemble_helper:[16,6,1,""],ensemble_res_1to1:[16,6,1,""],figsize:[16,4,1,""],font:[16,4,1,""],gaussian_distribution:[16,6,1,""],phi_progress:[16,6,1,""],plot_id_bar:[16,6,1,""],plot_jac_test:[16,6,1,""],plot_summary_distributions:[16,6,1,""],pst_helper:[16,6,1,""],pst_prior:[16,6,1,""],res_1to1:[16,6,1,""],res_phi_pie:[16,6,1,""]},"pyemu.prototypes":{Assimilator:[19,1,1,""],Cov:[19,1,1,""],EliteDiffEvol:[19,1,1,""],EnsembleKalmanFilter:[19,1,1,""],EnsembleMethod:[19,1,1,"id9"],EvolAlg:[19,1,1,""],Logger:[19,1,1,""],Matrix:[19,1,1,""],ObservationEnsemble:[19,1,1,""],ParameterEnsemble:[19,1,1,""],ParetoObjFunc:[19,1,1,""],Pst:[19,1,1,""],da:[17,0,0,"-"],ensemble_method:[18,0,0,"-"],moouu:[20,0,0,"-"],sm:[19,4,1,""]},"pyemu.prototypes.Assimilator":{enkf:[19,2,1,""],enks:[19,2,1,""],forcast:[19,2,1,""],generate_priors:[19,2,1,""],model_evalutions:[19,2,1,""],model_temporal_evolotion:[19,2,1,""],run:[19,2,1,""],smoother:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.Cov":{_get_uncfile_dimensions:[19,2,1,""],condition_on:[19,2,1,""],from_observation_data:[19,2,1,""],from_obsweights:[19,2,1,""],from_parameter_data:[19,2,1,""],from_parbounds:[19,2,1,""],from_uncfile:[19,2,1,""],identity:[19,2,1,""],identity_like:[19,2,1,""],names:[19,2,1,""],replace:[19,2,1,""],to_pearson:[19,2,1,""],to_uncfile:[19,2,1,""],zero:[19,2,1,""]},"pyemu.prototypes.EliteDiffEvol":{_drop_by_crowd:[19,2,1,""],iter_report:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.EnsembleKalmanFilter":{analysis:[19,2,1,""],analysis_evensen:[19,2,1,""],forecast:[19,2,1,""],initialize:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.EnsembleMethod":{_calc_delta:[19,2,1,"id11"],_calc_obs:[19,2,1,"id12"],_calc_obs_condor:[19,2,1,"id15"],_calc_obs_local:[19,2,1,"id16"],_get_master_thread:[19,2,1,"id14"],_load_obs_ensemble:[19,2,1,"id13"],initialize:[19,2,1,"id10"],update:[19,2,1,"id17"]},"pyemu.prototypes.EvolAlg":{_archive:[19,2,1,""],_calc_obs:[19,2,1,""],_drop_failed:[19,2,1,""],initialize:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.Logger":{log:[19,2,1,""],lraise:[19,2,1,""],statement:[19,2,1,""],warn:[19,2,1,""]},"pyemu.prototypes.Matrix":{"char":[19,3,1,""],"double":[19,3,1,""],T:[19,2,1,""],__add__:[19,2,1,""],__getitem__:[19,2,1,""],__mul__:[19,2,1,""],__pow__:[19,2,1,""],__rmul__:[19,2,1,""],__set_svd:[19,2,1,""],__str__:[19,2,1,""],__sub__:[19,2,1,""],align:[19,2,1,""],as_2d:[19,2,1,""],binary_header_dt:[19,3,1,""],binary_rec_dt:[19,3,1,""],coo_rec_dt:[19,3,1,""],copy:[19,2,1,""],df:[19,2,1,""],drop:[19,2,1,""],element_isaligned:[19,2,1,""],extend:[19,2,1,""],extract:[19,2,1,""],find_rowcol_indices:[19,2,1,""],from_ascii:[19,2,1,""],from_binary:[19,2,1,""],from_dataframe:[19,2,1,""],from_fortranfile:[19,2,1,""],from_names:[19,2,1,""],full_s:[19,2,1,""],get:[19,2,1,""],get_diagonal_vector:[19,2,1,""],get_maxsing:[19,2,1,""],get_maxsing_from_s:[19,2,1,""],hadamard_product:[19,2,1,""],indices:[19,2,1,""],integer:[19,3,1,""],inv:[19,2,1,""],mult_isaligned:[19,2,1,""],ncol:[19,2,1,""],new_obs_length:[19,3,1,""],new_par_length:[19,3,1,""],newx:[19,2,1,""],nrow:[19,2,1,""],obs_length:[19,3,1,""],par_length:[19,3,1,""],pseudo_inv:[19,2,1,""],pseudo_inv_components:[19,2,1,""],read_ascii:[19,2,1,""],read_binary:[19,2,1,""],reset_x:[19,2,1,""],s:[19,2,1,""],shape:[19,2,1,""],sqrt:[19,2,1,""],to_2d:[19,2,1,""],to_ascii:[19,2,1,""],to_binary:[19,2,1,""],to_coo:[19,2,1,""],to_dataframe:[19,2,1,""],transpose:[19,2,1,""],u:[19,2,1,""],v:[19,2,1,""],x:[19,2,1,""],zero2d:[19,2,1,""]},"pyemu.prototypes.ObservationEnsemble":{add_base:[19,2,1,""],from_gaussian_draw:[19,2,1,""],nonzero:[19,2,1,""],phi_vector:[19,2,1,""]},"pyemu.prototypes.ParameterEnsemble":{_enforce_drop:[19,2,1,""],_enforce_reset:[19,2,1,""],_enforce_scale:[19,2,1,""],add_base:[19,2,1,""],adj_names:[19,2,1,""],back_transform:[19,2,1,""],enforce:[19,2,1,""],fixed_indexer:[19,2,1,""],from_gaussian_draw:[19,2,1,""],from_mixed_draws:[19,2,1,""],from_parfiles:[19,2,1,""],from_triangular_draw:[19,2,1,""],from_uniform_draw:[19,2,1,""],lbnd:[19,2,1,""],log_indexer:[19,2,1,""],project:[19,2,1,""],transform:[19,2,1,""],ubnd:[19,2,1,""]},"pyemu.prototypes.ParetoObjFunc":{crowd_distance:[19,2,1,""],dominates:[19,2,1,""],get_risk_shifted_value:[19,2,1,""],is_feasible:[19,2,1,""],is_nondominated_continuous:[19,2,1,""],is_nondominated_kung:[19,2,1,""],is_nondominated_pathetic:[19,2,1,""],obs_obj_signs:[19,2,1,""],reduce_stack_with_risk_shift:[19,2,1,""]},"pyemu.prototypes.Pst":{__reset_weights:[19,2,1,""],__setattr__:[19,2,1,""],_adjust_weights_by_list:[19,2,1,""],_adjust_weights_by_phi_components:[19,2,1,""],_cast_df_from_lines:[19,2,1,""],_cast_prior_df_from_lines:[19,2,1,""],_is_greater_const:[19,2,1,""],_is_less_const:[19,2,1,""],_load_version2:[19,2,1,""],_parse_external_line:[19,2,1,""],_parse_path_agnostic:[19,2,1,""],_parse_pestpp_line:[19,2,1,""],_parse_pi_par_names:[19,2,1,""],_read_df:[19,2,1,""],_read_line_comments:[19,2,1,""],_read_section_comments:[19,2,1,""],_stats_mae:[19,2,1,""],_stats_mean:[19,2,1,""],_stats_nrmse:[19,2,1,""],_stats_rmse:[19,2,1,""],_stats_rss:[19,2,1,""],_update_control_section:[19,2,1,""],_write_df:[19,2,1,""],_write_version1:[19,2,1,""],_write_version2:[19,2,1,""],add_observations:[19,2,1,""],add_parameters:[19,2,1,""],add_pi_equation:[19,2,1,""],add_transform_columns:[19,2,1,""],adj_par_groups:[19,2,1,""],adj_par_names:[19,2,1,""],adjust_weights:[19,2,1,""],adjust_weights_discrepancy:[19,2,1,""],bounds_report:[19,2,1,""],build_increments:[19,2,1,""],calculate_pertubations:[19,2,1,""],control_data:[19,3,1,""],enforce_bounds:[19,2,1,""],estimation:[19,2,1,""],forecast_names:[19,2,1,""],from_io_files:[19,2,1,""],from_par_obs_names:[19,2,1,""],get:[19,2,1,""],get_adj_pars_at_bounds:[19,2,1,""],get_par_change_limits:[19,2,1,""],get_res_stats:[19,2,1,""],greater_than_obs_constraints:[19,2,1,""],greater_than_pi_constraints:[19,2,1,""],less_than_obs_constraints:[19,2,1,""],less_than_pi_constraints:[19,2,1,""],load:[19,2,1,""],nnz_obs:[19,2,1,""],nnz_obs_groups:[19,2,1,""],nnz_obs_names:[19,2,1,""],nobs:[19,2,1,""],npar:[19,2,1,""],npar_adj:[19,2,1,""],nprior:[19,2,1,""],obs_groups:[19,2,1,""],obs_names:[19,2,1,""],observation_data:[19,3,1,""],par_groups:[19,2,1,""],par_names:[19,2,1,""],parameter_data:[19,3,1,""],parrep:[19,2,1,""],phi:[19,2,1,""],phi_components:[19,2,1,""],phi_components_normalized:[19,2,1,""],plot:[19,2,1,""],prior_groups:[19,2,1,""],prior_information:[19,3,1,""],prior_names:[19,2,1,""],process_output_files:[19,2,1,""],proportional_weights:[19,2,1,""],rectify_pgroups:[19,2,1,""],rectify_pi:[19,2,1,""],reg_data:[19,3,1,""],res:[19,2,1,""],sanity_checks:[19,2,1,""],set_res:[19,2,1,""],svd_data:[19,3,1,""],tied:[19,2,1,""],try_parse_name_metadata:[19,2,1,""],write:[19,2,1,""],write_input_files:[19,2,1,""],write_obs_summary_table:[19,2,1,""],write_par_summary_table:[19,2,1,""],zero_weight_obs_names:[19,2,1,""]},"pyemu.prototypes.da":{Assimilator:[17,1,1,""],EnsembleKalmanFilter:[17,1,1,""],sm:[17,4,1,""]},"pyemu.prototypes.da.Assimilator":{enkf:[17,2,1,""],enks:[17,2,1,""],forcast:[17,2,1,""],generate_priors:[17,2,1,""],model_evalutions:[17,2,1,""],model_temporal_evolotion:[17,2,1,""],run:[17,2,1,""],smoother:[17,2,1,""],update:[17,2,1,""]},"pyemu.prototypes.da.EnsembleKalmanFilter":{analysis:[17,2,1,""],analysis_evensen:[17,2,1,""],forecast:[17,2,1,""],initialize:[17,2,1,""],update:[17,2,1,""]},"pyemu.prototypes.ensemble_method":{EnsembleMethod:[18,1,1,""]},"pyemu.prototypes.ensemble_method.EnsembleMethod":{_calc_delta:[18,2,1,""],_calc_obs:[18,2,1,""],_calc_obs_condor:[18,2,1,""],_calc_obs_local:[18,2,1,""],_get_master_thread:[18,2,1,""],_load_obs_ensemble:[18,2,1,""],initialize:[18,2,1,""],update:[18,2,1,""]},"pyemu.prototypes.moouu":{EliteDiffEvol:[20,1,1,""],EvolAlg:[20,1,1,""],ParetoObjFunc:[20,1,1,""]},"pyemu.prototypes.moouu.EliteDiffEvol":{_drop_by_crowd:[20,2,1,""],iter_report:[20,2,1,""],update:[20,2,1,""]},"pyemu.prototypes.moouu.EvolAlg":{_archive:[20,2,1,""],_calc_obs:[20,2,1,""],_drop_failed:[20,2,1,""],initialize:[20,2,1,""],update:[20,2,1,""]},"pyemu.prototypes.moouu.ParetoObjFunc":{crowd_distance:[20,2,1,""],dominates:[20,2,1,""],get_risk_shifted_value:[20,2,1,""],is_feasible:[20,2,1,""],is_nondominated_continuous:[20,2,1,""],is_nondominated_kung:[20,2,1,""],is_nondominated_pathetic:[20,2,1,""],obs_obj_signs:[20,2,1,""],reduce_stack_with_risk_shift:[20,2,1,""]},"pyemu.pst":{ControlData:[21,1,1,""],Pst:[21,1,1,""],pst_controldata:[22,0,0,"-"],pst_handler:[23,0,0,"-"],pst_utils:[24,0,0,"-"]},"pyemu.pst.ControlData":{__getattr__:[21,2,1,""],__setattr__:[21,2,1,""],_parse_value:[21,2,1,""],copy:[21,2,1,""],formatted_values:[21,2,1,""],get_dataframe:[21,2,1,""],parse_values_from_lines:[21,2,1,""],write:[21,2,1,""],write_keyword:[21,2,1,""]},"pyemu.pst.Pst":{__reset_weights:[21,2,1,""],__setattr__:[21,2,1,""],_adjust_weights_by_list:[21,2,1,""],_adjust_weights_by_phi_components:[21,2,1,""],_cast_df_from_lines:[21,2,1,""],_cast_prior_df_from_lines:[21,2,1,""],_is_greater_const:[21,2,1,""],_is_less_const:[21,2,1,""],_load_version2:[21,2,1,""],_parse_external_line:[21,2,1,""],_parse_path_agnostic:[21,2,1,""],_parse_pestpp_line:[21,2,1,""],_parse_pi_par_names:[21,2,1,""],_read_df:[21,2,1,""],_read_line_comments:[21,2,1,""],_read_section_comments:[21,2,1,""],_stats_mae:[21,2,1,""],_stats_mean:[21,2,1,""],_stats_nrmse:[21,2,1,""],_stats_rmse:[21,2,1,""],_stats_rss:[21,2,1,""],_update_control_section:[21,2,1,""],_write_df:[21,2,1,""],_write_version1:[21,2,1,""],_write_version2:[21,2,1,""],add_observations:[21,2,1,""],add_parameters:[21,2,1,""],add_pi_equation:[21,2,1,""],add_transform_columns:[21,2,1,""],adj_par_groups:[21,2,1,""],adj_par_names:[21,2,1,""],adjust_weights:[21,2,1,""],adjust_weights_discrepancy:[21,2,1,""],bounds_report:[21,2,1,""],build_increments:[21,2,1,""],calculate_pertubations:[21,2,1,""],control_data:[21,3,1,""],enforce_bounds:[21,2,1,""],estimation:[21,2,1,""],forecast_names:[21,2,1,""],from_io_files:[21,2,1,""],from_par_obs_names:[21,2,1,""],get:[21,2,1,""],get_adj_pars_at_bounds:[21,2,1,""],get_par_change_limits:[21,2,1,""],get_res_stats:[21,2,1,""],greater_than_obs_constraints:[21,2,1,""],greater_than_pi_constraints:[21,2,1,""],less_than_obs_constraints:[21,2,1,""],less_than_pi_constraints:[21,2,1,""],load:[21,2,1,""],nnz_obs:[21,2,1,""],nnz_obs_groups:[21,2,1,""],nnz_obs_names:[21,2,1,""],nobs:[21,2,1,""],npar:[21,2,1,""],npar_adj:[21,2,1,""],nprior:[21,2,1,""],obs_groups:[21,2,1,""],obs_names:[21,2,1,""],observation_data:[21,3,1,""],par_groups:[21,2,1,""],par_names:[21,2,1,""],parameter_data:[21,3,1,""],parrep:[21,2,1,""],phi:[21,2,1,""],phi_components:[21,2,1,""],phi_components_normalized:[21,2,1,""],plot:[21,2,1,""],prior_groups:[21,2,1,""],prior_information:[21,3,1,""],prior_names:[21,2,1,""],process_output_files:[21,2,1,""],proportional_weights:[21,2,1,""],rectify_pgroups:[21,2,1,""],rectify_pi:[21,2,1,""],reg_data:[21,3,1,""],res:[21,2,1,""],sanity_checks:[21,2,1,""],set_res:[21,2,1,""],svd_data:[21,3,1,""],tied:[21,2,1,""],try_parse_name_metadata:[21,2,1,""],write:[21,2,1,""],write_input_files:[21,2,1,""],write_obs_summary_table:[21,2,1,""],write_par_summary_table:[21,2,1,""],zero_weight_obs_names:[21,2,1,""]},"pyemu.pst.pst_controldata":{CONTROL_DEFAULT_LINES:[22,4,1,""],CONTROL_VARIABLE_LINES:[22,4,1,""],ControlData:[22,1,1,""],FFMT:[22,4,1,""],IFMT:[22,4,1,""],REG_DEFAULT_LINES:[22,4,1,""],REG_VARIABLE_LINES:[22,4,1,""],RegData:[22,1,1,""],SFMT:[22,4,1,""],SFMT_LONG:[22,4,1,""],SvdData:[22,1,1,""],max_colwidth:[22,4,1,""]},"pyemu.pst.pst_controldata.ControlData":{__getattr__:[22,2,1,""],__setattr__:[22,2,1,""],_parse_value:[22,2,1,""],copy:[22,2,1,""],formatted_values:[22,2,1,""],get_dataframe:[22,2,1,""],parse_values_from_lines:[22,2,1,""],write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_controldata.RegData":{write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_controldata.SvdData":{parse_values_from_lines:[22,2,1,""],write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_handler":{Pst:[23,1,1,""],max_colwidth:[23,4,1,""]},"pyemu.pst.pst_handler.Pst":{__reset_weights:[23,2,1,""],__setattr__:[23,2,1,""],_adjust_weights_by_list:[23,2,1,""],_adjust_weights_by_phi_components:[23,2,1,""],_cast_df_from_lines:[23,2,1,""],_cast_prior_df_from_lines:[23,2,1,""],_is_greater_const:[23,2,1,""],_is_less_const:[23,2,1,""],_load_version2:[23,2,1,""],_parse_external_line:[23,2,1,""],_parse_path_agnostic:[23,2,1,""],_parse_pestpp_line:[23,2,1,""],_parse_pi_par_names:[23,2,1,""],_read_df:[23,2,1,""],_read_line_comments:[23,2,1,""],_read_section_comments:[23,2,1,""],_stats_mae:[23,2,1,""],_stats_mean:[23,2,1,""],_stats_nrmse:[23,2,1,""],_stats_rmse:[23,2,1,""],_stats_rss:[23,2,1,""],_update_control_section:[23,2,1,""],_write_df:[23,2,1,""],_write_version1:[23,2,1,""],_write_version2:[23,2,1,""],add_observations:[23,2,1,""],add_parameters:[23,2,1,""],add_pi_equation:[23,2,1,""],add_transform_columns:[23,2,1,""],adj_par_groups:[23,2,1,""],adj_par_names:[23,2,1,""],adjust_weights:[23,2,1,""],adjust_weights_discrepancy:[23,2,1,""],bounds_report:[23,2,1,""],build_increments:[23,2,1,""],calculate_pertubations:[23,2,1,""],control_data:[23,3,1,""],enforce_bounds:[23,2,1,""],estimation:[23,2,1,""],forecast_names:[23,2,1,""],from_io_files:[23,2,1,""],from_par_obs_names:[23,2,1,""],get:[23,2,1,""],get_adj_pars_at_bounds:[23,2,1,""],get_par_change_limits:[23,2,1,""],get_res_stats:[23,2,1,""],greater_than_obs_constraints:[23,2,1,""],greater_than_pi_constraints:[23,2,1,""],less_than_obs_constraints:[23,2,1,""],less_than_pi_constraints:[23,2,1,""],load:[23,2,1,""],nnz_obs:[23,2,1,""],nnz_obs_groups:[23,2,1,""],nnz_obs_names:[23,2,1,""],nobs:[23,2,1,""],npar:[23,2,1,""],npar_adj:[23,2,1,""],nprior:[23,2,1,""],obs_groups:[23,2,1,""],obs_names:[23,2,1,""],observation_data:[23,3,1,""],par_groups:[23,2,1,""],par_names:[23,2,1,""],parameter_data:[23,3,1,""],parrep:[23,2,1,""],phi:[23,2,1,""],phi_components:[23,2,1,""],phi_components_normalized:[23,2,1,""],plot:[23,2,1,""],prior_groups:[23,2,1,""],prior_information:[23,3,1,""],prior_names:[23,2,1,""],process_output_files:[23,2,1,""],proportional_weights:[23,2,1,""],rectify_pgroups:[23,2,1,""],rectify_pi:[23,2,1,""],reg_data:[23,3,1,""],res:[23,2,1,""],sanity_checks:[23,2,1,""],set_res:[23,2,1,""],svd_data:[23,3,1,""],tied:[23,2,1,""],try_parse_name_metadata:[23,2,1,""],write:[23,2,1,""],write_input_files:[23,2,1,""],write_obs_summary_table:[23,2,1,""],write_par_summary_table:[23,2,1,""],zero_weight_obs_names:[23,2,1,""]},"pyemu.pst.pst_utils":{FFMT:[24,4,1,""],IFMT:[24,4,1,""],InstructionFile:[24,1,1,""],SFMT:[24,6,1,""],SFMT_LONG:[24,4,1,""],_get_marker_indices:[24,6,1,""],_parse_ins_string:[24,6,1,""],_populate_dataframe:[24,6,1,""],_try_run_inschek:[24,6,1,""],_write_chunk_to_template:[24,6,1,""],clean_missing_exponent:[24,6,1,""],csv_to_ins_file:[24,6,1,""],generic_pst:[24,6,1,""],get_phi_comps_from_recfile:[24,6,1,""],max_colwidth:[24,4,1,""],parse_ins_file:[24,6,1,""],parse_tpl_file:[24,6,1,""],process_output_files:[24,6,1,""],pst_config:[24,4,1,""],read_parfile:[24,6,1,""],read_resfile:[24,6,1,""],res_from_en:[24,6,1,""],res_from_obseravtion_data:[24,6,1,""],str_con:[24,6,1,""],try_process_output_file:[24,6,1,""],try_process_output_pst:[24,6,1,""],write_input_files:[24,6,1,""],write_parfile:[24,6,1,""],write_to_template:[24,6,1,""]},"pyemu.pst.pst_utils.InstructionFile":{_execute_ins_line:[24,2,1,""],_readline_ins:[24,2,1,""],_readline_output:[24,2,1,""],obs_name_set:[24,2,1,""],read_ins_file:[24,2,1,""],read_output_file:[24,2,1,""],throw_ins_error:[24,2,1,""],throw_ins_warning:[24,2,1,""],throw_out_error:[24,2,1,""]},"pyemu.pyemu_warnings":{PyemuWarning:[25,5,1,""]},"pyemu.sc":{Schur:[26,1,1,""]},"pyemu.sc.Schur":{__contribution_from_parameters:[26,2,1,""],get_added_obs_group_importance:[26,2,1,""],get_added_obs_importance:[26,2,1,""],get_conditional_instance:[26,2,1,""],get_forecast_summary:[26,2,1,""],get_obs_group_dict:[26,2,1,""],get_par_contribution:[26,2,1,""],get_par_group_contribution:[26,2,1,""],get_parameter_summary:[26,2,1,""],get_removed_obs_group_importance:[26,2,1,""],get_removed_obs_importance:[26,2,1,""],next_most_important_added_obs:[26,2,1,""],next_most_par_contribution:[26,2,1,""],posterior_forecast:[26,2,1,""],posterior_parameter:[26,2,1,""],posterior_prediction:[26,2,1,""]},"pyemu.utils":{Cov:[30,1,1,""],EPSILON:[30,4,1,""],ExpVario:[30,1,1,""],FFMT:[30,4,1,"id19"],GauVario:[30,1,1,""],GeoStruct:[30,1,1,""],IFMT:[30,4,1,"id18"],OrdinaryKrige:[30,1,1,""],PP_FMT:[30,4,1,"id24"],PP_NAMES:[30,4,1,"id25"],PstFrom:[30,1,1,""],PstFromFlopyModel:[30,1,1,""],PyemuWarning:[30,5,1,"id32"],SFMT:[30,6,1,"id17"],SpatialReference:[30,1,1,""],SpecSim2d:[30,1,1,""],SphVario:[30,1,1,""],Vario2d:[30,1,1,""],_apply_postprocess_hds_timeseries:[30,6,1,""],_check_diff:[30,6,1,""],_check_var_len:[30,6,1,""],_condition_on_par_knowledge:[30,6,1,""],_date_parser:[30,6,1,""],_eigen_basis_to_factor_file:[30,6,1,""],_get_datetime_from_str:[30,6,1,""],_get_tpl_or_ins_df:[30,6,1,""],_istextfile:[30,6,1,""],_l2_maha_worker:[30,6,1,""],_parse_factor_line:[30,6,1,""],_process_chunk_fac2real:[30,6,1,""],_process_chunk_model_files:[30,6,1,""],_process_model_file:[30,6,1,""],_read_structure_attributes:[30,6,1,""],_read_variogram:[30,6,1,""],_regweight_from_parbound:[30,6,1,""],_remove_readonly:[30,6,1,""],_rmse:[30,6,1,""],_setup_postprocess_hds_timeseries:[30,6,1,""],_write_df_tpl:[30,6,1,"id22"],_write_direct_df_tpl:[30,6,1,""],_write_mflist_ins:[30,6,1,""],_write_mtlist_ins:[30,6,1,""],apply_array_pars:[30,6,1,""],apply_gage_obs:[30,6,1,""],apply_genericlist_pars:[30,6,1,""],apply_hds_obs:[30,6,1,""],apply_hds_timeseries:[30,6,1,""],apply_hfb_pars:[30,6,1,""],apply_list_and_array_pars:[30,6,1,""],apply_list_pars:[30,6,1,""],apply_mflist_budget_obs:[30,6,1,""],apply_mtlist_budget_obs:[30,6,1,""],apply_sfr_obs:[30,6,1,""],apply_sfr_parameters:[30,6,1,""],apply_sfr_reach_obs:[30,6,1,""],apply_sfr_seg_parameters:[30,6,1,""],apply_sft_obs:[30,6,1,""],apply_temporal_diff_obs:[30,6,1,""],bin_path:[30,4,1,""],build_jac_test_csv:[30,6,1,""],calc_observation_ensemble_quantiles:[30,6,1,""],calc_rmse_ensemble:[30,6,1,""],dataframe_to_smp:[30,6,1,""],ext:[30,4,1,""],fac2real:[30,6,1,""],first_order_pearson_tikhonov:[30,6,1,""],geostatistical_draws:[30,6,1,""],geostatistical_prior_builder:[30,6,1,""],geostats:[27,0,0,"-"],get_maha_obs_summary:[30,6,1,""],gslib_2_dataframe:[30,6,1,""],gw_utils:[28,0,0,"-"],helpers:[29,0,0,"-"],jco_from_pestpp_runstorage:[30,6,1,""],kl_apply:[30,6,1,""],kl_setup:[30,6,1,""],last_kstp_from_kper:[30,6,1,""],load_sfr_out:[30,6,1,""],load_sgems_exp_var:[30,6,1,""],max_colwidth:[30,4,1,"id16"],modflow_hob_to_instruction_file:[30,6,1,""],modflow_hydmod_to_instruction_file:[30,6,1,""],modflow_pval_to_template_file:[30,6,1,""],modflow_read_hydmod_file:[30,6,1,""],modflow_sfr_gag_to_instruction_file:[30,6,1,""],optimization:[31,0,0,"-"],os_utils:[32,0,0,"-"],parse_dir_for_io_files:[30,6,1,""],parse_tpl_file:[30,6,1,""],pilot_points_to_tpl:[30,6,1,""],pp_file_to_dataframe:[30,6,1,"id15"],pp_tpl_to_dataframe:[30,6,1,""],pp_utils:[33,0,0,"-"],pst_config:[30,4,1,"id20"],pst_from:[34,0,0,"-"],pst_from_io_files:[30,6,1,""],pst_from_parnames_obsnames:[30,6,1,""],read_pestpp_runstorage:[30,6,1,""],read_sgems_variogram_xml:[30,6,1,""],read_struct_file:[30,6,1,""],run:[30,6,1,"id29"],setup_fake_forward_run:[30,6,1,""],setup_gage_obs:[30,6,1,""],setup_hds_obs:[30,6,1,""],setup_hds_timeseries:[30,6,1,""],setup_mflist_budget_obs:[30,6,1,""],setup_mtlist_budget_obs:[30,6,1,""],setup_pilotpoints_grid:[30,6,1,""],setup_sfr_obs:[30,6,1,""],setup_sfr_reach_obs:[30,6,1,""],setup_sfr_reach_parameters:[30,6,1,""],setup_sfr_seg_parameters:[30,6,1,""],setup_sft_obs:[30,6,1,""],setup_temporal_diff_obs:[30,6,1,""],simple_ins_from_obs:[30,6,1,""],simple_tpl_from_pars:[30,6,1,""],smp_to_dataframe:[30,6,1,""],smp_to_ins:[30,6,1,""],smp_utils:[35,0,0,"-"],srefhttp:[30,4,1,""],start_workers:[30,6,1,"id30"],try_process_output_file:[30,6,1,""],wildass_guess_par_bounds_dict:[30,4,1,""],write_array_tpl:[30,6,1,""],write_const_tpl:[30,6,1,""],write_grid_tpl:[30,6,1,""],write_hfb_template:[30,6,1,""],write_hfb_zone_multipliers_template:[30,6,1,""],write_list_tpl:[30,6,1,""],write_pp_file:[30,6,1,""],write_pp_shapfile:[30,6,1,""],write_zone_tpl:[30,6,1,""],zero_order_tikhonov:[30,6,1,""]},"pyemu.utils.Cov":{_get_uncfile_dimensions:[30,2,1,""],condition_on:[30,2,1,""],from_observation_data:[30,2,1,""],from_obsweights:[30,2,1,""],from_parameter_data:[30,2,1,""],from_parbounds:[30,2,1,""],from_uncfile:[30,2,1,""],identity:[30,2,1,""],identity_like:[30,2,1,""],names:[30,2,1,""],replace:[30,2,1,""],to_pearson:[30,2,1,""],to_uncfile:[30,2,1,""],zero:[30,2,1,""]},"pyemu.utils.ExpVario":{_h_function:[30,2,1,""]},"pyemu.utils.GauVario":{_h_function:[30,2,1,""]},"pyemu.utils.GeoStruct":{__gt__:[30,2,1,""],__lt__:[30,2,1,""],__str__:[30,2,1,""],covariance:[30,2,1,""],covariance_matrix:[30,2,1,""],covariance_points:[30,2,1,""],nugget:[30,3,1,""],plot:[30,2,1,""],same_as_other:[30,2,1,""],sill:[30,2,1,""],to_struct_file:[30,2,1,""],transform:[30,3,1,""],variograms:[30,3,1,""]},"pyemu.utils.OrdinaryKrige":{_calc_factors_mp:[30,2,1,""],_calc_factors_org:[30,2,1,""],_cov_points:[30,2,1,""],_dist_calcs:[30,2,1,""],_form:[30,2,1,""],_solve:[30,2,1,""],_worker:[30,2,1,""],calc_factors:[30,2,1,""],calc_factors_grid:[30,2,1,""],check_point_data_dist:[30,2,1,""],to_grid_factors_file:[30,2,1,""]},"pyemu.utils.PstFrom":{_flopy_mg_get_xy:[30,2,1,""],_flopy_sr_get_xy:[30,2,1,""],_generic_get_xy:[30,2,1,""],_load_listtype_file:[30,2,1,""],_next_count:[30,2,1,""],_par_prep:[30,2,1,""],_pivot_par_struct_dict:[30,2,1,""],_prep_arg_list_lengths:[30,2,1,""],_setup_dirs:[30,2,1,""],add_observations:[30,2,1,""],add_observations_from_ins:[30,2,1,""],add_parameters:[30,2,1,""],add_py_function:[30,2,1,""],build_prior:[30,2,1,""],build_pst:[30,2,1,""],draw:[30,2,1,""],initialize_spatial_reference:[30,2,1,""],parfile_relations:[30,2,1,""],parse_kij_args:[30,2,1,""],write_forward_run:[30,2,1,""]},"pyemu.utils.PstFromFlopyModel":{_add_external:[30,2,1,""],_get_count:[30,2,1,""],_grid_prep:[30,2,1,""],_kl_prep:[30,2,1,""],_list_helper:[30,2,1,""],_parse_k:[30,2,1,""],_parse_pakattr:[30,2,1,""],_pp_prep:[30,2,1,""],_prep_mlt_arrays:[30,2,1,""],_setup_array_pars:[30,2,1,""],_setup_hds:[30,2,1,""],_setup_hfb_pars:[30,2,1,""],_setup_hob:[30,2,1,""],_setup_hyd:[30,2,1,""],_setup_list_pars:[30,2,1,""],_setup_model:[30,2,1,""],_setup_mult_dirs:[30,2,1,""],_setup_observations:[30,2,1,""],_setup_sfr_obs:[30,2,1,""],_setup_sfr_pars:[30,2,1,""],_setup_smp:[30,2,1,""],_setup_spatial_list_pars:[30,2,1,""],_setup_temporal_list_pars:[30,2,1,""],_setup_water_budget_obs:[30,2,1,""],_write_const_tpl:[30,2,1,""],_write_grid_tpl:[30,2,1,""],_write_u2d:[30,2,1,""],build_prior:[30,2,1,""],build_pst:[30,2,1,""],draw:[30,2,1,""],write_forward_run:[30,2,1,""]},"pyemu.utils.SpatialReference":{__eq__:[30,2,1,""],__repr__:[30,2,1,""],__setattr__:[30,2,1,""],_parse_units_from_proj4:[30,2,1,""],_reset:[30,2,1,""],_set_vertices:[30,2,1,""],_set_xycentergrid:[30,2,1,""],_set_xygrid:[30,2,1,""],attribs_from_namfile_header:[30,2,1,""],attribute_dict:[30,2,1,""],bounds:[30,2,1,""],defaults:[30,3,1,""],epsg:[30,2,1,""],from_gridspec:[30,2,1,""],from_namfile:[30,2,1,""],get_extent:[30,2,1,""],get_grid_lines:[30,2,1,""],get_ij:[30,2,1,""],get_rc:[30,2,1,""],get_vertices:[30,2,1,""],get_xcenter_array:[30,2,1,""],get_xedge_array:[30,2,1,""],get_ycenter_array:[30,2,1,""],get_yedge_array:[30,2,1,""],length_multiplier:[30,2,1,"id0"],lenuni:[30,2,1,""],lenuni_text:[30,3,1,""],lenuni_values:[30,3,1,""],load:[30,2,1,""],model_length_units:[30,2,1,""],ncol:[30,2,1,""],nrow:[30,2,1,""],origin_loc:[30,3,1,""],proj4_str:[30,2,1,""],read_usgs_model_reference_file:[30,2,1,""],reset:[30,2,1,""],rotate:[30,2,1,""],rotation:[30,3,1,""],set_spatialreference:[30,2,1,""],theta:[30,2,1,""],transform:[30,2,1,""],units:[30,2,1,""],vertices:[30,2,1,"id9"],write_gridspec:[30,2,1,""],xcenter:[30,2,1,"id5"],xcentergrid:[30,2,1,"id8"],xedge:[30,2,1,"id1"],xgrid:[30,2,1,"id3"],xll:[30,2,1,""],xul:[30,2,1,""],ycenter:[30,2,1,"id6"],ycentergrid:[30,2,1,"id7"],yedge:[30,2,1,"id2"],ygrid:[30,2,1,"id4"],yll:[30,2,1,""],yul:[30,2,1,""]},"pyemu.utils.SpecSim2d":{draw_arrays:[30,2,1,""],draw_conditional:[30,2,1,""],grid_is_regular:[30,2,1,""],grid_par_ensemble_helper:[30,2,1,""],initialize:[30,2,1,""]},"pyemu.utils.SphVario":{_h_function:[30,2,1,""]},"pyemu.utils.Vario2d":{__str__:[30,2,1,""],_apply_rotation:[30,2,1,""],_specsim_grid_contrib:[30,2,1,""],bearing_rads:[30,2,1,""],covariance:[30,2,1,""],covariance_matrix:[30,2,1,""],covariance_points:[30,2,1,""],inv_h:[30,2,1,""],plot:[30,2,1,""],rotation_coefs:[30,2,1,""],same_as_other:[30,2,1,""],to_struct_file:[30,2,1,""]},"pyemu.utils.geostats":{EPSILON:[27,4,1,""],ExpVario:[27,1,1,""],GauVario:[27,1,1,""],GeoStruct:[27,1,1,""],OrdinaryKrige:[27,1,1,""],SpecSim2d:[27,1,1,""],SphVario:[27,1,1,""],Vario2d:[27,1,1,""],_parse_factor_line:[27,6,1,""],_read_structure_attributes:[27,6,1,""],_read_variogram:[27,6,1,""],fac2real:[27,6,1,""],gslib_2_dataframe:[27,6,1,""],load_sgems_exp_var:[27,6,1,""],read_sgems_variogram_xml:[27,6,1,""],read_struct_file:[27,6,1,""]},"pyemu.utils.geostats.ExpVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.GauVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.GeoStruct":{__gt__:[27,2,1,""],__lt__:[27,2,1,""],__str__:[27,2,1,""],covariance:[27,2,1,""],covariance_matrix:[27,2,1,""],covariance_points:[27,2,1,""],nugget:[27,3,1,""],plot:[27,2,1,""],same_as_other:[27,2,1,""],sill:[27,2,1,""],to_struct_file:[27,2,1,""],transform:[27,3,1,""],variograms:[27,3,1,""]},"pyemu.utils.geostats.OrdinaryKrige":{_calc_factors_mp:[27,2,1,""],_calc_factors_org:[27,2,1,""],_cov_points:[27,2,1,""],_dist_calcs:[27,2,1,""],_form:[27,2,1,""],_solve:[27,2,1,""],_worker:[27,2,1,""],calc_factors:[27,2,1,""],calc_factors_grid:[27,2,1,""],check_point_data_dist:[27,2,1,""],to_grid_factors_file:[27,2,1,""]},"pyemu.utils.geostats.SpecSim2d":{draw_arrays:[27,2,1,""],draw_conditional:[27,2,1,""],grid_is_regular:[27,2,1,""],grid_par_ensemble_helper:[27,2,1,""],initialize:[27,2,1,""]},"pyemu.utils.geostats.SphVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.Vario2d":{__str__:[27,2,1,""],_apply_rotation:[27,2,1,""],_specsim_grid_contrib:[27,2,1,""],bearing_rads:[27,2,1,""],covariance:[27,2,1,""],covariance_matrix:[27,2,1,""],covariance_points:[27,2,1,""],inv_h:[27,2,1,""],plot:[27,2,1,""],rotation_coefs:[27,2,1,""],same_as_other:[27,2,1,""],to_struct_file:[27,2,1,""]},"pyemu.utils.gw_utils":{PP_FMT:[28,4,1,""],PP_NAMES:[28,4,1,""],_apply_postprocess_hds_timeseries:[28,6,1,""],_setup_postprocess_hds_timeseries:[28,6,1,""],_write_mflist_ins:[28,6,1,""],_write_mtlist_ins:[28,6,1,""],apply_gage_obs:[28,6,1,""],apply_hds_obs:[28,6,1,""],apply_hds_timeseries:[28,6,1,""],apply_hfb_pars:[28,6,1,""],apply_mflist_budget_obs:[28,6,1,""],apply_mtlist_budget_obs:[28,6,1,""],apply_sfr_obs:[28,6,1,""],apply_sfr_parameters:[28,6,1,""],apply_sfr_reach_obs:[28,6,1,""],apply_sfr_seg_parameters:[28,6,1,""],apply_sft_obs:[28,6,1,""],last_kstp_from_kper:[28,6,1,""],load_sfr_out:[28,6,1,""],max_colwidth:[28,4,1,""],modflow_hob_to_instruction_file:[28,6,1,""],modflow_hydmod_to_instruction_file:[28,6,1,""],modflow_pval_to_template_file:[28,6,1,""],modflow_read_hydmod_file:[28,6,1,""],modflow_sfr_gag_to_instruction_file:[28,6,1,""],setup_gage_obs:[28,6,1,""],setup_hds_obs:[28,6,1,""],setup_hds_timeseries:[28,6,1,""],setup_mflist_budget_obs:[28,6,1,""],setup_mtlist_budget_obs:[28,6,1,""],setup_sfr_obs:[28,6,1,""],setup_sfr_reach_obs:[28,6,1,""],setup_sfr_reach_parameters:[28,6,1,""],setup_sfr_seg_parameters:[28,6,1,""],setup_sft_obs:[28,6,1,""],write_hfb_template:[28,6,1,""],write_hfb_zone_multipliers_template:[28,6,1,""]},"pyemu.utils.helpers":{PstFromFlopyModel:[29,1,1,""],SpatialReference:[29,1,1,""],_condition_on_par_knowledge:[29,6,1,""],_eigen_basis_to_factor_file:[29,6,1,""],_l2_maha_worker:[29,6,1,""],_process_chunk_fac2real:[29,6,1,""],_process_chunk_model_files:[29,6,1,""],_process_model_file:[29,6,1,""],_regweight_from_parbound:[29,6,1,""],_rmse:[29,6,1,""],_write_df_tpl:[29,6,1,""],apply_array_pars:[29,6,1,""],apply_genericlist_pars:[29,6,1,""],apply_list_and_array_pars:[29,6,1,""],apply_list_pars:[29,6,1,""],apply_temporal_diff_obs:[29,6,1,""],build_jac_test_csv:[29,6,1,""],calc_observation_ensemble_quantiles:[29,6,1,""],calc_rmse_ensemble:[29,6,1,""],first_order_pearson_tikhonov:[29,6,1,""],geostatistical_draws:[29,6,1,""],geostatistical_prior_builder:[29,6,1,""],get_maha_obs_summary:[29,6,1,""],jco_from_pestpp_runstorage:[29,6,1,""],kl_apply:[29,6,1,""],kl_setup:[29,6,1,""],max_colwidth:[29,4,1,""],parse_dir_for_io_files:[29,6,1,""],pst_from_io_files:[29,6,1,""],pst_from_parnames_obsnames:[29,6,1,""],read_pestpp_runstorage:[29,6,1,""],setup_fake_forward_run:[29,6,1,""],setup_temporal_diff_obs:[29,6,1,""],simple_ins_from_obs:[29,6,1,""],simple_tpl_from_pars:[29,6,1,""],srefhttp:[29,4,1,""],wildass_guess_par_bounds_dict:[29,4,1,""],write_const_tpl:[29,6,1,""],write_grid_tpl:[29,6,1,""],write_zone_tpl:[29,6,1,""],zero_order_tikhonov:[29,6,1,""]},"pyemu.utils.helpers.PstFromFlopyModel":{_add_external:[29,2,1,""],_get_count:[29,2,1,""],_grid_prep:[29,2,1,""],_kl_prep:[29,2,1,""],_list_helper:[29,2,1,""],_parse_k:[29,2,1,""],_parse_pakattr:[29,2,1,""],_pp_prep:[29,2,1,""],_prep_mlt_arrays:[29,2,1,""],_setup_array_pars:[29,2,1,""],_setup_hds:[29,2,1,""],_setup_hfb_pars:[29,2,1,""],_setup_hob:[29,2,1,""],_setup_hyd:[29,2,1,""],_setup_list_pars:[29,2,1,""],_setup_model:[29,2,1,""],_setup_mult_dirs:[29,2,1,""],_setup_observations:[29,2,1,""],_setup_sfr_obs:[29,2,1,""],_setup_sfr_pars:[29,2,1,""],_setup_smp:[29,2,1,""],_setup_spatial_list_pars:[29,2,1,""],_setup_temporal_list_pars:[29,2,1,""],_setup_water_budget_obs:[29,2,1,""],_write_const_tpl:[29,2,1,""],_write_grid_tpl:[29,2,1,""],_write_u2d:[29,2,1,""],build_prior:[29,2,1,""],build_pst:[29,2,1,""],draw:[29,2,1,""],write_forward_run:[29,2,1,""]},"pyemu.utils.helpers.SpatialReference":{__eq__:[29,2,1,""],__repr__:[29,2,1,""],__setattr__:[29,2,1,""],_parse_units_from_proj4:[29,2,1,""],_reset:[29,2,1,""],_set_vertices:[29,2,1,""],_set_xycentergrid:[29,2,1,""],_set_xygrid:[29,2,1,""],attribs_from_namfile_header:[29,2,1,""],attribute_dict:[29,2,1,""],bounds:[29,2,1,""],defaults:[29,3,1,""],epsg:[29,2,1,""],from_gridspec:[29,2,1,""],from_namfile:[29,2,1,""],get_extent:[29,2,1,""],get_grid_lines:[29,2,1,""],get_ij:[29,2,1,""],get_rc:[29,2,1,""],get_vertices:[29,2,1,""],get_xcenter_array:[29,2,1,""],get_xedge_array:[29,2,1,""],get_ycenter_array:[29,2,1,""],get_yedge_array:[29,2,1,""],length_multiplier:[29,2,1,"id0"],lenuni:[29,2,1,""],lenuni_text:[29,3,1,""],lenuni_values:[29,3,1,""],load:[29,2,1,""],model_length_units:[29,2,1,""],ncol:[29,2,1,""],nrow:[29,2,1,""],origin_loc:[29,3,1,""],proj4_str:[29,2,1,""],read_usgs_model_reference_file:[29,2,1,""],reset:[29,2,1,""],rotate:[29,2,1,""],rotation:[29,3,1,""],set_spatialreference:[29,2,1,""],theta:[29,2,1,""],transform:[29,2,1,""],units:[29,2,1,""],vertices:[29,2,1,"id9"],write_gridspec:[29,2,1,""],xcenter:[29,2,1,"id5"],xcentergrid:[29,2,1,"id8"],xedge:[29,2,1,"id1"],xgrid:[29,2,1,"id3"],xll:[29,2,1,""],xul:[29,2,1,""],ycenter:[29,2,1,"id6"],ycentergrid:[29,2,1,"id7"],yedge:[29,2,1,"id2"],ygrid:[29,2,1,"id4"],yll:[29,2,1,""],yul:[29,2,1,""]},"pyemu.utils.optimization":{OPERATOR_SYMBOLS:[31,4,1,""],OPERATOR_WORDS:[31,4,1,""],add_pi_obj_func:[31,6,1,""]},"pyemu.utils.os_utils":{_istextfile:[32,6,1,""],_remove_readonly:[32,6,1,""],bin_path:[32,4,1,"id1"],ext:[32,4,1,""],run:[32,6,1,""],start_workers:[32,6,1,""]},"pyemu.utils.pp_utils":{PP_FMT:[33,4,1,""],PP_NAMES:[33,4,1,""],max_colwidth:[33,4,1,""],pilot_points_to_tpl:[33,6,1,""],pp_file_to_dataframe:[33,6,1,""],pp_tpl_to_dataframe:[33,6,1,""],setup_pilotpoints_grid:[33,6,1,""],write_pp_file:[33,6,1,""],write_pp_shapfile:[33,6,1,""]},"pyemu.utils.pst_from":{PstFrom:[34,1,1,""],_check_diff:[34,6,1,""],_check_var_len:[34,6,1,""],_get_datetime_from_str:[34,6,1,""],_get_tpl_or_ins_df:[34,6,1,""],_write_direct_df_tpl:[34,6,1,""],write_array_tpl:[34,6,1,""],write_list_tpl:[34,6,1,""]},"pyemu.utils.pst_from.PstFrom":{_flopy_mg_get_xy:[34,2,1,""],_flopy_sr_get_xy:[34,2,1,""],_generic_get_xy:[34,2,1,""],_load_listtype_file:[34,2,1,""],_next_count:[34,2,1,""],_par_prep:[34,2,1,""],_pivot_par_struct_dict:[34,2,1,""],_prep_arg_list_lengths:[34,2,1,""],_setup_dirs:[34,2,1,""],add_observations:[34,2,1,""],add_observations_from_ins:[34,2,1,""],add_parameters:[34,2,1,""],add_py_function:[34,2,1,""],build_prior:[34,2,1,""],build_pst:[34,2,1,""],draw:[34,2,1,""],initialize_spatial_reference:[34,2,1,""],parfile_relations:[34,2,1,""],parse_kij_args:[34,2,1,""],write_forward_run:[34,2,1,""]},"pyemu.utils.smp_utils":{_date_parser:[35,6,1,""],dataframe_to_smp:[35,6,1,""],smp_to_dataframe:[35,6,1,""],smp_to_ins:[35,6,1,""]},pyemu:{Cov:[9,1,1,"id24"],Ensemble:[9,1,1,""],ErrVar:[9,1,1,""],Jco:[9,1,1,""],LinearAnalysis:[9,1,1,""],Matrix:[9,1,1,"id39"],ObservationEnsemble:[9,1,1,"id19"],ParameterEnsemble:[9,1,1,"id0"],Pst:[9,1,1,"id103"],Schur:[9,1,1,""],_version:[6,0,0,"-"],en:[7,0,0,"-"],ev:[8,0,0,"-"],la:[10,0,0,"-"],logger:[11,0,0,"-"],mat:[12,0,0,"-"],mc:[14,0,0,"-"],plot:[15,0,0,"-"],prototypes:[19,0,0,"-"],pst:[21,0,0,"-"],pyemu_warnings:[25,0,0,"-"],sc:[26,0,0,"-"],utils:[30,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","data","Python data"],"5":["py","exception","Python exception"],"6":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:data","5":"py:exception","6":"py:function"},terms:{"0001":[14,27,30,39,44],"001":[29,30,44],"100":[7,9,14,19,20,21,22,23,24,26,27,28,29,30,33,34,39,42,43,44],"1000":[14,27,30,39,44],"10000000000":[27,30,34,44],"1970":[28,30,44],"1to1":[9,16,19,21,23,39,41,43],"200":[9,12,13,19,39,40],"2003":[17,19,42],"2005":[29,30,44],"2011":[30,32],"2darrai":[29,30,44],"30k":[29,30,44],"358183147":7,"4004":[17,18,19,20,30,32,42,44],"512":[30,32],"695":[27,30,44],"boolean":[7,9,19,27,30,34,39,44],"byte":[30,32],"case":[9,10,12,13,16,19,21,23,24,26,28,29,30,32,34,39,40,41,43,44],"char":[9,12,13,19,22,29,30,32,34,38,39,40,43,44],"class":[1,2,3,4,25,39,40,42,43,44],"default":[1,3,4,6,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],"final":[9,14,19,21,23,39,43],"float":[1,4,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,34,35,39,40,41,42,43,44],"function":[7,9,17,19,20,21,22,23,36,37,39,40,41,42,43,44],"import":[9,10,14,16,26,27,30,34,39,41,44],"int":[7,8,9,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],"long":[6,27,29,30,35,44],"new":[3,7,9,10,12,13,14,19,21,23,24,26,29,30,32,34,39,40,43,44],"null":[7,8,9,14,19,39,42],"return":[6,7,8,9,10,12,13,14,16,17,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],"short":6,"static":[7,9,12,13,16,19,20,21,22,23,27,29,30,39,40,41,43,44],"super":[19,20,42],"throw":[16,24,41,43],"true":[1,3,4,7,8,9,10,12,13,14,16,18,19,20,21,23,24,26,27,28,29,30,32,34,39,40,41,42,43,44],"try":[6,9,17,19,21,23,24,27,30,34,35,39,42,43,44],"var":[8,9,29,30,34,39],"while":[7,9,19,21,23,39,43],Adding:[29,30],BAS:[28,30,44],BCs:[28,30,44],But:[7,9,19,39],DIS:[28,30,44],For:[1,4,7,8,9,10,12,13,19,21,23,26,28,29,30,32,33,34,39,40,43,44],IES:[24,43],NOT:[30,34,44],Not:[7,8,9,19,30,34,39],OBS:[28,30,44],One:[9,26,30,34,39,44],RHS:[27,30,44],That:[9,10,26,29,30,39,44],The:[6,7,9,10,11,12,13,14,17,19,21,23,26,27,28,29,30,34,35,39,40,42,43,44],These:[0,9,12,19,22,29,30,39,40,42,43,44],Use:[17,19,30,33,34,42,44],Used:[9,16,19,21,23,24,27,28,29,30,39,41,43,44],Useful:[9,26,28,30,39,44],Uses:[9,10,28,30,32,44],VCS:6,__add__:[7,9,12,13,19],__contribution_from_paramet:[9,26],__eq__:[29,30],__fromfil:[9,10],__getattr__:[7,9,21,22],__getitem__:[7,9,12,13,19],__gt__:[27,30],__init:[9,12,13],__load_jco:[9,10],__load_obscov:[9,10],__load_omitted_jco:[8,9],__load_omitted_parcov:[8,9],__load_omitted_predict:[8,9],__load_parcov:[9,10],__load_predict:[9,10],__load_pst:[9,10],__lt__:[27,30],__mul__:[7,9,12,13,19],__mult__:[9,12,13,19,39,40],__obscov:[9,10,39],__parcov:[9,10,39],__pow__:[7,9,12,13,19],__re:[9,19,21,23,39,43],__repr__:[7,9,29,30],__reset_weight:[9,19,21,23],__rmul__:[9,12,13,19],__set_svd:[9,12,13,19],__setattr__:[9,19,21,22,23,29,30],__setitem__:7,__str__:[7,9,12,13,19,27,30],__sub__:[7,9,12,13,19],__truediv__:[7,9],__x:[9,12,13,19,39,40],_add_extern:[29,30],_adjust_weights_by_list:[9,19,21,23],_adjust_weights_by_phi_compon:[9,19,21,23],_apply_postprocess_hds_timeseri:[28,30],_apply_rot:[27,30],_archiv:[19,20],_backup_:[28,30,44],_bak:[29,30,44],_calc_delta:[18,19],_calc_factors_mp:[27,30],_calc_factors_org:[27,30],_calc_ob:[18,19,20],_calc_obs_condor:[18,19],_calc_obs_loc:[18,19],_cast_df_from_lin:[9,19,21,23],_cast_prior_df_from_lin:[9,19,21,23],_check_diff:[30,34],_check_var_len:[30,34],_condition_on_par_knowledg:[29,30],_cov_point:[27,30],_date_pars:[30,35],_df:[7,9,39],_dist_calc:[27,30],_drop_by_crowd:[19,20],_drop_fail:[19,20],_eigen_basis_to_factor_fil:[29,30],_enforce_drop:[7,9,19],_enforce_reset:[7,9,19],_enforce_scal:[7,9,19],_execute_ins_lin:24,_flopy_mg_get_xi:[30,34],_flopy_sr_get_xi:[30,34],_form:[27,30],_gaussian_draw:[7,9],_generic_get_xi:[30,34],_get_count:[29,30],_get_datetime_from_str:[30,34],_get_eigen_projection_matrix:[7,9],_get_marker_indic:24,_get_master_thread:[18,19],_get_page_ax:16,_get_svd_projection_matrix:[7,9],_get_tpl_or_ins_df:[30,34],_get_uncfile_dimens:[9,12,13,19,30],_grid_prep:[29,30],_h_function:[27,30],_hp:[29,30,44],_ins_linecount:[24,43],_is_greater_const:[9,19,21,23],_is_less_const:[9,19,21,23],_istextfil:[30,32],_jj:[9,10,39],_kl_prep:[29,30],_l2_maha_work:[29,30],_list_help:[29,30],_load_listtype_fil:[30,34],_load_obs_ensembl:[18,19],_load_version2:[9,19,21,23],_next_count:[30,34],_num_at_lb:[9,19,21,23,39,43],_num_at_ub:[9,19,21,23,39,43],_par_prep:[30,34],_parse_external_lin:[9,19,21,23],_parse_factor_lin:[27,30],_parse_ins_str:24,_parse_k:[29,30],_parse_pakattr:[29,30],_parse_path_agnost:[9,19,21,23],_parse_pestpp_lin:[9,19,21,23],_parse_pi_par_nam:[9,19,21,23],_parse_units_from_proj4:[29,30],_parse_valu:[21,22],_pivot_par_struct_dict:[30,34],_populate_datafram:24,_pp_prep:[29,30],_prep_arg_list_length:[30,34],_prep_mlt_arrai:[29,30],_process_chunk_fac2r:[29,30],_process_chunk_model_fil:[29,30],_process_ensemble_arg:16,_process_model_fil:[29,30],_read_df:[9,19,21,23],_read_line_com:[9,19,21,23],_read_section_com:[9,19,21,23],_read_structure_attribut:[27,30],_read_variogram:[27,30],_readline_in:24,_readline_output:24,_regweight_from_parbound:[29,30],_remove_readonli:[30,32],_reset:[29,30],_rmse:[29,30],_set_vertic:[29,30],_set_xycentergrid:[29,30],_set_xygrid:[29,30],_setup_:[28,30,44],_setup_array_par:[29,30],_setup_dir:[30,34],_setup_hd:[29,30],_setup_hfb_par:[29,30],_setup_hob:[29,30],_setup_hyd:[29,30],_setup_list_par:[29,30],_setup_model:[29,30],_setup_mult_dir:[29,30],_setup_observ:[29,30],_setup_postprocess_hds_timeseri:[28,30],_setup_sfr_ob:[29,30],_setup_sfr_par:[29,30],_setup_smp:[29,30],_setup_spatial_list_par:[29,30],_setup_temporal_list_par:[29,30],_setup_water_budget_ob:[29,30],_solv:[27,30],_specsim_grid_contrib:[27,30],_stats_ma:[9,19,21,23],_stats_mean:[9,19,21,23],_stats_nrms:[9,19,21,23],_stats_rms:[9,19,21,23],_stats_rss:[9,19,21,23],_total_at_bound:[9,19,21,23,39,43],_transform:[7,9,39],_try_run_inschek:24,_update_control_sect:[9,19,21,23],_version:[5,9,19,21,23,39,43],_worker:[27,30],_write_chunk_to_templ:24,_write_const_tpl:[29,30],_write_df:[9,19,21,23],_write_df_tpl:[29,30],_write_direct_df_tpl:[30,34],_write_grid_tpl:[29,30],_write_mflist_in:[28,30],_write_mtlist_in:[28,30],_write_u2d:[29,30],_write_version1:[9,19,21,23],_write_version2:[9,19,21,23],abet:16,abil:[28,30,44],about:[9,17,19,25,26,29,30,39,42,44],abov:[14,27,28,29,30,39,44],abs_drop_tol:[29,30,44],absolut:[9,12,13,19,21,23,29,30,34,39,40,43,44],accept:[7,9,16,19,39,41],access:[9,17,19,21,22,23,26,29,30,39,42,43,44],accommod:[9,12,13,19,39,40],accord:[7,9,19,27,28,29,30,33,39,44],account:[9,19,21,23,26,28,29,30,39,43,44],across:[28,29,30,32,44],act:[29,30,44],activ:[1,8,9,19,21,23,28,29,30,33,39,43,44],actual:[21,22,30,32,43,44],add:[7,9,12,13,14,16,19,21,23,24,27,28,29,30,32,34,35,39,41,43,44],add_bas:[7,9,19,38,39],add_observ:[9,19,21,23,30,34,38,39,43,44],add_observations_from_in:[30,34,39,44],add_paramet:[9,19,21,23,30,34,38,39,43,44],add_pi_equ:[9,19,21,23,38,39,43],add_pi_obj_func:[31,39,44],add_py_funct:[30,34,39,44],add_transform_column:[9,19,21,23,38,39,43],added:[7,9,19,21,23,26,27,28,29,30,34,39,43,44],adding:[30,34,44],addit:[7,9,12,13,16,19,26,27,29,30,34,35,39,41,44],addition:[7,9,19,39],addkeyword:[7,9,39],addreg:[24,43],address:[30,32,44],adj_nam:[7,9,19,38,39],adj_par_group:[9,19,21,23,38,39,43],adj_par_nam:[9,10,19,21,23,38,39,43],adjust:[7,8,9,10,19,21,23,26,29,30,39,43,44],adjust_obscov_resfil:[9,10,38,39],adjust_weight:[9,19,21,23,38,39,43],adjust_weights_discrep:[9,19,21,23,38,39,43],adjust_weights_resfil:[9,10,39],adust:[9,26,39],advanc:[30,32,44],after:[9,19,21,23,27,29,30,34,39,43,44],against:[9,19,21,23,26,30,34,39,43,44],aggreg:[28,30,44],aggregr:[28,30,44],agnost:[30,32,44],aka:[9,10,39],algebra:[9,12,13,17,19,30,39,40,42],algin:[30,34],algorithm:[19,20,42],alia:[39,40],alias_map:[9,19,21,23],align:[9,12,13,17,19,29,30,34,38,39,40,42,44],all:[3,7,8,9,10,12,13,16,19,21,23,24,26,27,28,29,30,32,33,34,39,40,41,43,44],allow:[7,9,19,21,23,27,29,30,34,39,43,44],alon:[9,19,21,23,39,43],along:[7,9,12,13,19,29,30,39,40,44],alreadi:[6,9,19,21,23,27,30,34,39,43,44],also:[0,1,4,6,7,8,9,10,14,16,17,19,21,23,24,26,27,28,29,30,32,33,34,39,41,42,43,44],alt_inst_str:[30,34,44],alter:[1,8,9,39],altern:[29,30,34,44],alwai:[6,30,35,44],amount:[9,19,21,23,39,43],analys:[0,4,9,10,14,19,21,23,26,39,42],analysi:[1,4,8,9,10,11,14,17,19,26,39,42],analysis_evensen:[17,19,39,42],analyz:[1,4,8,9,26,39],andor:[9,12,13,19,39,40],angl:[27,30,44],ani:[7,9,16,17,19,21,23,26,29,30,34,39,41,42,43,44],anisotropi:[27,30,44],anlaysi:[17,19,42],anoth:[29,30],anyon:[16,41],anyth:[30,34,44],anywai:6,apart:[30,34,44],api:[],appar:[30,32],appear:6,append:[9,19,21,22,23,24,28,29,30,34,39,43,44],appl_array_par:[29,30,44],appli:[1,4,7,8,9,10,12,13,16,17,19,26,27,28,29,30,34,39,40,41,42,44],applic:[9,12,13,19,39,40],apply_array_par:[29,30,39,44],apply_gage_ob:[28,30,39,44],apply_genericlist_par:[29,30,39,44],apply_hds_ob:[28,30,39,44],apply_hds_timeseri:[28,30,39,44],apply_hfb_par:[28,30,39,44],apply_karhunen_loeve_sc:[9,10,38,39],apply_kl:[29,30,44],apply_list_and_array_par:[29,30,39,44],apply_list_par:[29,30,39,44],apply_mflist_budget_ob:[28,30,39,44],apply_mtlist_budget_ob:[28,30,39,44],apply_sfr_ob:[28,30,39,44],apply_sfr_paramet:[28,30,39,44],apply_sfr_reach_ob:[28,30,39,44],apply_sfr_reach_paramet:[28,30,44],apply_sfr_seg_paramet:[28,30,39,44],apply_sft_ob:[28,30,39,44],apply_temporal_diff_ob:[29,30,39,44],approach:[27,30,44],appropri:[6,30,32,44],approx:[29,30,44],approxim:[1,4,8,9,10,12,13,19,26,29,30,34,39,40,44],aquier:[28,30,44],aquif:[28,29,30,44],arang:[27,30,44],arbitrari:[29,30,44],archiv:6,area:[30,33,44],arg:[1,4,6,7,8,9,10,11,12,13,16,18,19,20,21,22,23,24,26,30,34,39,40,41,42,43,44],argument:[1,4,7,8,9,10,12,13,14,16,17,19,21,22,23,26,27,29,30,34,39,41,42,43,44],aris:[8,9,39],arithmat:[7,9,27,30,39,44],arithmet:[27,30,44],around:[7,8,9,12,13,16,17,19,26,27,28,29,30,39,40,41,42,44],arr_par:[29,30,44],arr_par_fil:[29,30,44],arr_shap:[29,30,44],arrai:[7,9,12,13,19,27,28,29,30,33,34,39,40,44],arrang:[27,30,44],as_2d:[9,12,13,19,38,39,40],as_pyemu_matrix:[7,9,38,39],ascii:[1,4,8,9,10,12,13,19,21,22,26,28,29,30,34,39,40,43,44],aspect:[15,41],assess:[9,26,39],assign:[7,9,10,19,21,23,26,27,29,30,34,39,43,44],assimil:[17,19,39,42],assist:[30,34,44],associ:[14,27,29,30,39,44],assum:[1,4,7,8,9,10,12,13,16,19,21,23,26,27,28,29,30,32,33,34,39,40,41,43,44],astyp:[9,10,39],async:[30,32,44],atleast:[9,19,21,23,39,43],atplotlib:[16,41],attempt:[21,22,24,29,30,34,43,44],attr:[29,30],attr_nam:[27,30,44],attribs_from_namfile_head:[29,30,39,44],attribut:[1,7,8,9,10,12,13,17,19,21,22,23,24,27,28,29,30,39,40,42,43,44],attribute_dict:[29,30,39,44],auto:[5,13,29,30,34,40,44],autoalign:[9,12,13,19,30,39,40],autoapi:5,autodetect:[9,12,13,19,39,40],automat:[9,22,26,39,43],avail:[7,9,16,17,19,21,23,28,29,30,39,41,42,43,44],avoid:[29,30,44],awai:[27,30,44],axes:[9,12,13,16,19,39,40,41],axi:[9,12,13,16,19,27,30,34,39,40,41,44],ayman:[17,19,42],back:[7,9,19,21,23,39,43],back_transform:[7,9,19,38,39],backup:[28,29,30,44],backward:6,bak_suffix:[29,30,44],balanced_group:[9,19,21,23,39,43],bar:[8,9,16,26,39,41],bas6:[27,30,44],bas:[28,30,44],base:[0,1,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,34,39,40,41,42,43,44],base_ensembl:[16,41],base_obslist:[9,26,39],base_values_fil:[27,30,44],basemodel:[28,30,44],basenam:[30,34,44],basi:[29,30,44],basic:[9,11,19,21,23,24,39,43],basis_fil:[29,30,44],bay:[26,39],bear:[27,30,44],bearing_rad:[27,30,39,44],becaus:[9,19,21,23,26,29,30,39,43,44],becom:[9,12,13,19,26,30,39,40],been:[6,7,9,10,14,19,21,22,23,26,30,34,39,43,44],befor:[1,7,8,9,12,13,19,29,30,34,39,40,44],behavior:[4,9,25,26,27,30,39,44],being:[7,8,9,16,19,21,22,23,26,27,28,29,30,34,39,41,43,44],better:[29,30,44],between:[7,9,12,13,14,16,19,27,29,30,34,39,40,41,44],bewar:[9,17,19,21,23,24,29,30,39,42,43,44],bin:[7,9,16,39,41],bin_fil:[28,30,44],bin_path:[30,32],binari:[1,4,7,8,9,10,12,13,19,26,28,29,30,32,34,39,40,44],binary_header_dt:[9,12,13,19,38,39,40],binary_rec_dt:[9,12,13,19,38,39,40],bizarr:[9,12,13,19,39,40],block:[30,32],blocksiz:[30,32],bool:[1,2,3,4,7,8,9,10,11,12,13,14,16,19,20,21,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],both:[6,7,9,10,12,13,16,19,26,29,30,39,40,41,44],bound:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,26,27,29,30,34,39,40,41,42,43,44],bound_report:[9,19,21,23,39,43],bound_tol:[7,9,19,39],boundari:[17,19,29,30,42,44],bounds_report:[9,19,21,23,38,39,43],box:[29,30,44],bring:[9,19,21,23,39,43],btn:[28,30,44],budget:[28,30,44],buget:[29,30],build:[6,27,29,30,34,44],build_incr:[9,19,21,23,38,39,43],build_jac_test_csv:[16,29,30,39,41,44],build_prior:[29,30,34,39,44],build_pst:[29,30,34,39,44],built:[30,34,44],by_group:[7,9,19,39],bygroup:[9,19,21,23,29,30,39,43,44],c_char:[30,34],calc:[8,9,12,13,18,19,39,40],calc_factor:[27,30,39,44],calc_factors_grid:[27,30,39,44],calc_factors_mp:[27,30,44],calc_factors_org:[27,30,44],calc_observation_ensemble_quantil:[29,30,39,44],calc_rmse_ensembl:[29,30,39,44],calcul:[1,4,8,9,10,11,12,13,19,20,21,23,26,27,29,30,34,39,40,42,43,44],calculate_pertub:[9,19,21,23,38,39,43],calibr:[9,26,39],call:[6,7,9,10,12,13,14,16,17,19,21,23,24,27,28,29,30,32,34,39,40,41,42,43,44],can:[0,1,4,7,8,9,10,12,13,14,16,17,19,21,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],candid:[19,20,42],captur:[27,30,44],care:[7,9,19,39],carlo:[7,8,9,14,19,39,42],cast:[1,4,8,9,10,21,22,24,26,28,30,39,43,44],caus:[9,11,19,21,23,27,30,39,43,44],caution:[28,30,44],cbb:[28,30,44],cell:[27,28,29,30,34,44],center:[7,9,19,27,29,30,39,44],center_on:[7,9,19,39],centimet:44,certain:[7,9,29,30,39],chang:[7,9,16,17,19,21,22,23,26,27,29,30,39,41,42,43,44],chart:[9,16,19,21,23,39,41,43],cheap:[9,19,21,23,27,30,39,43,44],check:[6,9,12,13,19,21,22,23,24,27,30,39,40,43,44],check_point_data_dist:[27,30,39,44],chi:[29,30,44],chile:[27,30,44],chunk:[9,12,13,19,24,29,30,34,39,40,44],chunk_len:[29,30,44],cinact:[28,30],cinit:[28,30,44],classic:[7,9,39],classmethod:[7,9,12,13,19,21,23,29,30,39,40,43,44],clean:[6,9,10,24,38,39,43],clean_filenam:[24,43],clean_missing_expon:[24,39,43],cleanup:[30,32,44],clear:[9,10,12,13,19,30,39,40],clockwis:[29,30,44],close:[11,19,39],closer:[27,30,44],cls:[7,9,12,13,19,21,23,29,30],cmd_str:[30,32,44],code:[9,12,13,19,29,30,39,40,44],coef:[9,12,13,19,30,39,40],coef_dict:[9,19,21,23,39,43],coeffic:[27,30,44],coeffici:[9,12,13,19,21,23,27,29,30,39,40,43,44],cofactor:[9,10,39],col:[9,12,13,16,17,19,29,30,34,39,40,41,42,44],col_:[9,12,13,19,39,40],col_nam:[9,10,12,13,19,30,39,40],collect:[1,4,8,9,10,16,26,39,41],coloc:[9,19,21,23,39,43],colon:[9,19,21,23,39,43],color:[7,9,39],colum:[9,12,13,19,39,40],column:[2,7,8,9,10,12,13,16,19,20,21,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],columnn:[16,41],combiat:[24,43],combin:[16,30,34,41,44],come:[9,12,13,19,39,40],command:[6,17,18,19,20,28,29,30,32,34,42,44],comment:[29,30,34,44],comment_char:[30,34,44],common:[9,12,13,19,21,23,30,34,39,40,43,44],commun:[17,18,19,20,42],companion:[28,29,30,44],compar:[9,26,39],compat:[9,12,13,19,24,28,30,35,39,40,43,44],competit:[9,10,39],complement:[9,10,12,13,19,30,39,40],complet:[9,14,17,18,19,20,21,23,30,32,34,36,37,39,42,43,44],complex:[29,30,44],compliant:[30,35,44],complic:[29,30,44],compliment:[26,39],compon:[8,9,10,12,13,14,16,17,19,21,23,24,28,30,34,36,37,39,40,41,42,43,44],composit:[9,10,19,21,23,39,43],compress:[29,30,44],concat:[12,13,29,30,39,40,44],concaten:[12,13,40],concentr:[28,30,44],conceptu:[9,26,39],cond:[29,30,44],condit:[9,12,13,17,19,26,27,29,30,39,40,42,44],condition_on:[9,12,13,19,30,38,39,40],conditional_factor:[27,30,44],conditioning_el:[9,12,13,19,30,39,40],condor_submit:[17,18,19,20,42],conduct:[28,29,30,44],confid:[1,4,8,9,10,12,13,19,21,23,26,29,30,34,39,40,43,44],config:[28,29,30,44],config_fil:[28,29,30,44],configur:[6,28,29,30,44],conjunct:[16,41],connect:[29,30,44],consid:[16,41],consider:[29,30,44],consist:[28,30,44],consolid:24,const_prop:[29,30,44],constant:[29,30,34,44],constant_head:[28,30,44],constraint:[9,19,20,21,23,39,42,43],construct:[1,3,4,7,8,9,10,12,13,14,17,18,19,20,21,23,24,26,28,29,30,32,34,39,40,42,43,44],constructor:[9,12,13,19,21,23,24,27,29,30,39,43,44],construtor:[9,19,21,23,39,43],constuctor:[9,12,13],contain:[5,6,7,9,12,13,15,16,19,21,22,23,24,26,27,28,29,30,33,34,35,39,40,41,43,44],content:38,continu:[19,20,27,30,42,44],contorl:[29,30,44],contribut:[8,9,19,21,23,24,26,27,30,39,43,44],control:[1,2,3,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,39,40,41,42,43,44],control_data:[3,9,19,21,23,29,30,38,39,43,44],control_default_lin:22,control_variable_lin:22,controldata:[9,19,21,22,23,39,43],contructor:[9,12,13,19,30,39,40],convention:6,convert:[9,12,13,19,21,23,29,30,39,40,44],coo:[12,13,29,30,34,40,44],coo_rec_dt:[9,12,13,19,38,39,40],cool:[29,30,44],coordin:[27,29,30,44],coorespond:[28,30,44],copi:[7,9,12,13,19,21,22,23,29,30,32,34,38,39,40,43,44],core:[30,32,44],corner:[29,30,44],correctli:[29,30,44],correl:[7,9,12,13,19,27,29,30,34,39,40,44],correspond:[3,6,7,9,12,13,17,19,21,23,24,27,28,29,30,39,40,42,43,44],could:[13,40],count:16,counter:[7,9,19,21,23,29,30,35,39,44],countless:[30,32,44],cov:[1,4,7,8,9,10,12,13,14,17,18,19,20,26,27,29,30,34,38,39,40,42,44],covari:[1,4,7,8,9,10,12,13,17,18,19,20,26,27,29,30,34,39,40,42,44],covariance_matrix:[7,9,17,19,27,30,38,39,42,44],covariance_point:[27,30,39,44],covarinc:[27,30,44],covmat_fil:[9,12,13,19,30,39,40],crazi:[30,32,44],creat:[5,6,7,9,10,11,12,13,16,19,21,22,23,24,27,28,29,30,35,39,40,41,43,44],critic:[29,30,44],cross_over_bas:[19,20,42],crowd:[19,20,42],crowd_dist:[19,20,39,42],crude:[9,12,13,19],cryptic:[28,30,44],cso_j:[9,10,39],csv:[7,9,12,13,16,19,24,28,29,30,34,39,40,41,43,44],csv_filenam:[24,43],csv_to_ins_fil:[24,30,34,39,43],csvin:[16,41],csvout:[16,41],current:[6,7,9,16,17,19,21,22,23,24,29,30,34,39,41,42,43,44],cwd:[6,24,30,32,44],cycl:[17,19,42],cycle_fil:[17,19,42],dai:[28,29,30,44],danger:[29,30,35,44],dat:[9,19,21,23,27,28,29,30,33,34,39,43,44],data:[0,4,9,12,13,19,21,22,23,24,26,27,28,29,30,34,39,40,43,44],datafram:[2,7,8,9,10,12,13,14,16,17,19,20,21,22,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],dataframe_to_smp:[30,35,39,44],datafran:[16,41],dataset:[9,26,39],datatim:[30,34,44],dataworth:[4,9,26,39],datetim:[9,19,21,23,28,29,30,34,35,39,43,44],datetime_col:[30,35,44],datetime_format:[30,35,44],deal:[3,9,19,21,22,23,27,30,39,43,44],debug:[29,30,32,44],decomposit:[9,12,13,19,21,22,23,39,40,43],decor:[6,9,10,12,13,19,39,40],decreas:[9,26,39],dedic:[30,44],deduc:[9,10,30,35,44],def:[30,34,44],defaul:[9,12,13,19,39,40],default_dict:24,defaut:[27,30,44],defin:[1,4,7,8,9,10,12,13,19,26,27,28,29,30,34,39,40,44],definit:[14,22,30,34,39,43,44],degre:[27,29,30,44],delc:[29,30,44],delfin:[27,30,44],deli:[27,30,44],delimit:[30,34,44],delr:[29,30,44],delx:[27,30,44],demystifi:[16,41],denot:[30,34,44],dens:[9,12,13,19,30,39,40],depart:[27,30,44],depend:[7,9,13,17,19,21,23,26,27,30,39,40,42,43,44],derinc:[9,19,21,23,39,43],deriv:[9,10,12,13,14,16,19,21,23,27,30,35,39,40,41,43,44],dervi:[30,33,44],describ:[6,7,9,19,27,30,39,44],descript:[29,30,44],design:[0,9,19,27,30,39,42,44],desir:[27,30,34,44],destroi:[29,30,34,44],detail:[29,30,44],detect:[30,32,44],deter_rang:[16,41],deter_v:[16,41],determin:[6,8,9,14,19,20,27,28,30,39,42,44],determinist:[16,41],dev0:6,devdist:6,develop:[16,18,29,30,41,42,44],deviat:[1,4,7,8,9,10,12,13,16,17,19,21,23,26,27,29,30,34,39,40,41,42,43,44],devnul:[30,32,44],dfault:[30,34,44],dfs:[30,34,44],diag:[9,12,13,19,39,40],diagon:[7,9,12,13,17,18,19,20,26,30,39,40,42],dict:[7,8,9,10,12,13,14,16,19,21,22,23,24,26,27,28,29,30,33,39,41,43,44],dictionari:[1,4,7,8,9,10,14,16,19,21,23,24,26,28,29,30,33,34,39,41,43,44],dif:[29,30,44],diff_df:[29,30,44],differ:[1,4,8,9,10,18,19,21,23,26,28,29,30,34,39,43,44],differen:[7,9,19,39],differenc:[9,26,29,30,39,44],dimens:[9,12,13,14,19,27,28,30,34,39,40,44],dimension:[0,9,29,30,39,44],dir:[28,29,30,32,44],direct:[29,30,34,44],directli:[0,9,10,12,13,16,17,18,19,20,21,23,27,30,34,39,41,42,44],directori:[6,9,16,17,18,19,20,21,23,28,29,30,32,33,34,39,41,42,43,44],dirti:[6,28,30],dis:[28,30,44],discrep:[9,19,21,23,39,43],discret:[29,30,44],displai:[11,19,39],dist_typ:[30,34,44],distanc:[6,7,9,19,20,21,23,27,29,30,39,42,43,44],distrbut:[16,41],distribut:[1,4,7,8,9,10,12,13,14,16,19,26,30,34,39,40,41,44],divid:[9,10,39],divis:[27,30,34,44],document:[5,9,10,14,39],doe:[7,9,19,21,23,24,27,29,30,39,43,44],doesnt:[9,19,21,23,39,43],domin:[19,20,39,42],don:[6,9,17,19,21,23,28,30,39,42,43,44],done:[28,30,44],dont:[30,32,44],dot:[9,12,13,19,39,40],doubl:[9,12,13,19,28,30,38,39,40,44],downstream:[28,30,44],draw:[7,9,14,17,19,27,29,30,34,38,39,42,44],draw_arrai:[27,30,39,44],draw_condit:[27,30,39,44],drawn:[7,9,19,39],drop:[7,9,10,12,13,14,17,19,27,29,30,38,39,40,42,44],drop_prior_inform:[9,10,38,39],dropna:[7,9,38,39],droptol:[9,12,13,19,29,30,34,39,40,44],dry:[28,30],dtemp:[39,40],dtype:[24,39,40,44],dubiou:[25,30],duplic:[9,19,21,23,24,39,43],dure:[9,11,12,13,19,21,23,26,27,28,29,30,39,40,43,44],dv_ensembl:[19,20,42],dv_name:[19,20,42],dynam:[17,19,42],each:[7,8,9,12,13,16,17,19,20,21,23,24,26,27,28,29,30,33,34,39,40,41,42,43,44],easi:[9,12,13,14,19,39,40],easiest:[7,9,16,19,39,41],east:[27,30,44],echo:[11,19,24,27,30,32,39,43,44],edg:[16,27,29,30,41,44],eexcept:6,effect:[7,9,10,19,21,23,26,27,30,39,43,44],eigen:[7,9,19,39],eigenvector:[29,30,44],eigthresh:[7,8,9,12,13,19,39,40],either:[9,12,13,19,26,27,29,30,34,35,39,40,44],elaps:[11,19,39],element:[9,10,12,13,19,28,29,30,34,39,40,44],element_isalign:[9,12,13,19,38,39,40],elementwis:[9,12,13,19],eli:[30,32],elitediffevol:[19,20,39,42],ellips:[27,30,44],els:[6,27,30,34,44],empir:[7,9,17,19,39,42],empti:[9,12,13,27,30,39,40,44],enbsembl:[9,19,21,23,39,43],encapsul:[9,10,12,13,19,21,22,39,40,43],encourag:[9,19,21,23,39,43],end:[24,28,29,30,44],endswith:[28,30,44],enfil:[24,43],enfor:[17,19,42],enforc:[7,9,14,19,21,23,38,39,43],enforce_bound:[7,9,14,17,19,21,23,38,39,42,43],engin:[29,30,44],enk:[17,19,39,42],enkf:[17,19,39,42],ens:[29,30,44],ensembl:[0,2,7,9,14,16,17,18,19,20,21,23,24,27,29,30,34,38,39,41,42,43,44],ensemble1:[16,41],ensemble2:[16,41],ensemble_change_summari:[16,39,41],ensemble_help:[7,9,16,39,41],ensemble_method:[5,9,17,19,20,38,39],ensemble_res_1to1:[16,39,41],ensemblekalmanfilt:[17,19,39,42],ensemblemethod:[17,18,19,20,39,42],ensemblesmooth:[10,39],ensur:[30,34,44],enter:[29,30,44],entir:[9,12,13,19,26,27,28,29,30,39,40,44],entri:[4,7,8,9,12,13,19,21,22,23,24,26,27,28,29,30,34,39,40,43,44],enumer:[16,41],env:6,environment:[0,9,19,39,42],epsg:[29,30,39,44],epsilon:[14,27,30,39,44],eqs:[9,19,21,23,39,43],equal:[7,9,10,12,13,19,21,23,27,28,29,30,34,39,40,43,44],equat:[9,10,19,21,23,24,26,27,29,30,39,43,44],equival:[28,30,44],err:[8,9,39],err_var:[27,30],error:[1,8,9,10,24,27,29,30,39,43,44],errvar:[0,7,8,9,10,16,19,38,39,41,42],especi:[29,30,44],estim:[9,10,19,21,23,26,27,30,38,39,43,44],estmat:[9,19,21,23,39,43],etc:[7,9,16,27,28,29,30,33,39,41,44],euclidean:[7,9,39],evalu:[7,9,17,18,19,20,21,23,26,29,30,39,42,43,44],even:[7,9,19,21,23,39,43],evensen:[17,19,42],event:[11,19,39],eventu:[30,34],everi:[9,26,27,29,30,32,34,39,44],every_n_cel:[30,33,44],evolalg:[19,20,39,42],exactli:[9,12,13,39,40],exampl:[1,2,3,4,7,8,9,10,12,13,14,16,19,21,23,24,26,27,28,29,30,32,33,34,35,36,37,39,40,41,43,44],exce:[29,30,44],except:[6,9,11,12,13,19,24,25,27,29,30,32,39,40,43,44],exchang:[29,30,44],excinfo:[30,32],exclus:[9,19,21,23,30,34,39,43,44],exe:[30,32,44],exe_rel_path:[30,32,44],execut:[28,29,30,32,34,44],exist:[1,3,4,7,8,9,10,14,19,21,23,24,26,27,28,29,30,32,33,34,35,39,43,44],existing_jco:[14,39],exit:[30,32,44],expand:6,expans:[29,30],expect:[21,22,28,30,34,43,44],experiment:[9,19,21,23,27,29,30,39,43,44],explicitli:[7,9,19,22,29,30,39,43,44],explod:[30,32,44],exponenti:[27,30],expos:[14,39],express:[9,26,29,30,39,44],expvario:[27,30,39,44],exst:[30,34,44],ext:[30,32],extend:[4,9,12,13,19,26,38,39,40],extens:[1,4,8,9,10,26,29,30,34,39,44],extent:[29,30,44],extern:[29,30],external_ins_out_pair:[29,30,44],external_path:[29,30],external_tpl_in_pair:[29,30,44],extra:[7,9,19,29,30,39,44],extra_model_cmd:[29,30,44],extra_post_cmd:[29,30,44],extra_pre_cmd:[29,30,44],extract:[1,6,8,9,12,13,19,24,28,30,34,38,39,40,43,44],fac2real:[27,29,30,39,44],fac_fil:[29,30,44],facecolor:[7,9,16,39,41],facili:[30,34,44],factor:[7,9,19,21,23,27,29,30,39,43,44],factors_fil:[27,29,30,44],fail:[24,27,30,43,44],failed_run:[19,20],failur:[28,30,44],fake:[29,30,44],fals:[1,2,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],famili:[29,30,44],fast:[9,12,13,19,27,29,30,39,40,44],faster:[7,9,12,13,19,39,40],feasibl:[19,20,42],feet:[29,30,44],fehalf:[9,10,38,39],few:[30,32,44],ffmt:[22,24,30,39,43],fft:[27,30,44],field:[9,19,21,23,27,30,39,43,44],fieldnam:[9,19,21,23],fig:[16,41],figsiz:[16,41],figur:[16,41],file:[1,2,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],file_nam:[30,34,44],fileanm:[16,41],filenam:[1,3,4,7,8,9,10,11,12,13,14,16,17,18,19,20,21,23,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],filetyp:[16,41],fill:[7,9,12,13,14,19,24,28,29,30,34,39,40,43,44],fill_valu:[27,29,30,34,44],filter:[7,9,39],find:[9,12,13,19,21,23,24,26,27,28,29,30,39,40,43,44],find_rowcol_indic:[9,12,13,19,38,39,40],finit:[9,19,21,23,39,43],first:[1,4,8,9,10,11,12,13,14,16,19,24,26,27,28,30,39,40,41,43,44],first_forecast:[8,9,38,39],first_order_pearson_tikhonov:[29,30,39,44],first_paramet:[8,9,38,39],first_predict:[8,9,38,39],fit:[29,30,44],fix:[7,9,19,21,23,24,27,30,35,39,43,44],fixed_index:[7,9,19,38,39],flag:[1,2,3,4,7,8,9,10,11,12,13,16,19,21,23,24,26,27,28,29,30,32,33,34,35,39,40,41,43,44],float1:[],float64:[39,40,44],flopi:[27,28,29,30,33,44],flow:[28,29,30,44],flush:[30,34,44],flux1:[9,26,39],flux2:[9,26,39],flux:[9,26,28,29,30,39,44],flux_fil:[28,30,44],flx_filenam:[28,30,44],fmt:[29,30,34,44],folder:[30,34,44],follow:[9,12,13,17,19,27,29,30,42,44],fom:[9,10,39],font:16,forc:[17,19,30,35,42,44],forcast:[17,19,39,42],fore1:[4,9,10,26,39],fore2:[4,9,10,26,39],forecast:[1,4,8,9,10,17,19,21,23,26,38,39,42,43],forecast_nam:[9,10,19,21,23,38,39,43],forecasts_it:[9,10,38,39],forgiv:[9,19,21,23,27,30,44],form:[0,1,7,8,9,10,12,13,14,16,19,21,23,24,26,27,28,29,30,34,35,39,40,41,42,43,44],format:[9,12,13,19,21,22,23,24,27,28,29,30,34,35,39,40,43,44],formatt:[9,19,21,23,30,34],formatted_valu:[21,22,39,43],former:[30,34,44],formul:[9,10,17,19,29,30,39,42,44],fortran:[9,12,13,19,24,39,40,43],forward:[17,18,19,28,29,30,34,42,44],forward_run:[28,29,30,44],fosm:[0,1,4,8,9,16,19,26,39,41,42],found:[1,4,8,9,10,19,21,23,24,26,27,28,29,30,32,39,43,44],frac_tol:[9,19,21,23,39,43],fraction:[9,19,21,23,39,43],fraction_stdev:[9,19,21,23,39,43],framework:[30,44],free:[14,29,30,34,35,39,44],freez:[29,30,44],freyberg_jac:[16,41],from:[1,4,6,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,39,40,41,42,43,44],from_ascii:[9,12,13,19,29,30,38,39,40,44],from_binari:[7,9,12,13,16,19,38,39,40,41],from_csv:[7,9,38,39],from_datafram:[7,9,12,13,19,38,39,40],from_fortranfil:[9,12,13,19,38,39,40],from_gaussian_draw:[2,7,9,19,38,39],from_gridspec:[29,30,39,44],from_io_fil:[9,19,21,23,29,30,38,39,43,44],from_mixed_draw:[7,9,19,38,39],from_nam:[9,12,13,19,38,39,40],from_namfil:[29,30,39,44],from_observation_data:[7,9,12,13,19,30,38,39,40],from_obsweight:[9,12,13,19,30,38,39,40],from_par_obs_nam:[9,19,21,23,38,39,43],from_parameter_data:[7,9,12,13,19,29,30,38,39,40,44],from_parbound:[9,12,13,19,30,38,39,40],from_parfil:[7,9,19,38,39],from_pst:[9,12,13,38,39,40],from_triangular_draw:[7,9,19,38,39],from_uncfil:[9,12,13,19,30,38,39,40],from_uniform_draw:[7,9,19,38,39],front:[9,19,21,23,24,29,30,34,35,39,43,44],full:[9,12,13,19,29,30,39,40,44],full_:[9,12,13,19,38,39,40],func:[30,32],func_dict:[7,9,16,39,41],function_nam:[30,34,44],gage:[28,30,44],gage_fil:[28,30,44],gage_num:[28,30,44],gage_output_fil:[28,30,44],gain:[9,26,39],gather:[9,26,30,34,39,44],gaussian:[7,9,14,16,19,27,30,34,39,41,44],gaussian_distribut:[16,39,41],gauvario:[27,30,39,44],gener:[0,5,7,9,14,16,17,19,21,22,23,24,27,29,30,33,34,35,39,41,42,43,44],generate_prior:[17,19,39,42],generic_pst:[24,39,43],geo:[30,34,44],geostast:[29,30,44],geostat:[5,9,29,30,34,38,39],geostatist:[27,29,30,44],geostatistical_draw:[29,30,39,44],geostatistical_prior_build:[29,30,39,44],geostatitical_draw:[29,30,44],geostruct:[27,29,30,34,39,44],get:[6,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,34,38,39,40,41,42,43,44],get_added_obs_group_import:[9,26,38,39],get_added_obs_import:[9,26,38,39],get_adj_pars_at_bound:[9,19,21,23,38,39,43],get_common_el:[13,39,40],get_conditional_inst:[9,26,38,39],get_config:6,get_contribution_datafram:[9,26,39],get_cso_datafram:[9,10,38,39],get_datafram:[21,22,39,43],get_devi:[7,9,17,19,38,39,42],get_diagonal_vector:[9,12,13,19,38,39,40],get_errvar_datafram:[1,8,9,38,39],get_ext:[29,30,39,44],get_forecast_summari:[4,9,26,38,39],get_grid_lin:[29,30,39,44],get_identifi:[16,41],get_identifiability_datafram:[8,9,16,38,39,41],get_ij:[29,30,39,44],get_keyword:6,get_maha_obs_summari:[29,30,39,44],get_maxs:[8,9,12,13,19,38,39,40],get_maxsing_from_:[9,12,13,19,38,39,40],get_ns:[14,38,39],get_null_proj:[7,8,9,14,19,38,39],get_obs_competition_datafram:[9,10,38,39],get_obs_group_dict:[9,26,38,39],get_par_change_limit:[9,19,21,23,38,39,43],get_par_contribut:[9,26,38,39],get_par_css_datafram:[9,10,38,39],get_par_group_contribut:[9,26,38,39],get_parameter_contribut:[4,9,26,39],get_parameter_summari:[9,26,38,39],get_phi_comps_from_recfil:[24,39,43],get_proj4:[29,30,44],get_rc:[29,30,39,44],get_removed_obs_group_import:[9,26,38,39],get_removed_obs_import:[9,26,38,39],get_res_stat:[9,19,21,23,38,39,43],get_risk_shifted_valu:[19,20,39,42],get_vers:6,get_vertic:[29,30,39,44],get_xcenter_arrai:[29,30,39,44],get_xedge_arrai:[29,30,39,44],get_xi:[30,34,44],get_ycenter_arrai:[29,30,39,44],get_yedge_arrai:[29,30,39,44],ghex:6,git:6,git_describ:6,git_get_keyword:6,git_pieces_from_vc:6,git_versions_from_keyword:6,give:[7,9,19,28,30,39,44],given:[6,8,9,12,13,14,16,19,26,27,28,29,30,32,39,40,41,44],glm:[16,41],global:[7,9,28,29,30,39,44],glue:[14,19,39,42],goal:6,good:[9,19,21,23,39,43],gov:[29,30,44],govern:[30,34],gpname:[24,30,34,43,44],gr_df:[27,30,44],gracefulli:[30,32,44],greater:[9,12,13,19,21,23,26,27,28,29,30,33,34,39,40,43,44],greater_than_obs_constraint:[9,19,21,23,38,39,43],greater_than_pi_constraint:[9,19,21,23,38,39,43],grid:[27,29,30,33,34,44],grid_geostruct:[29,30,44],grid_is_regular:[27,30,39,44],grid_par_ensemble_help:[27,30,39,44],grid_prop:[29,30,44],gridspec_fil:[29,30,44],groundwat:[9,19,21,23,39,43],group1:[9,19,21,23,39,43],group:[7,9,16,19,21,23,24,26,28,29,30,32,34,39,41,43,44],group_nam:[9,19,21,23,39,43],grouper:[7,9,16,41],grp:[9,19,21,23,39,43],gslib:[27,30,44],gslib_2_datafram:[27,30,39,44],guess:[30,32],gw_filenam:[28,30,44],gw_prefix:[28,30,44],gw_util:[5,9,29,30,38,39],gwutils_compli:[30,35,44],h_function:[27,30,44],hadamard_product:[9,12,13,19,38,39,40],half:[9,10,39],hand:[9,19,21,23,39,43],handl:[7,9,11,12,17,19,21,22,23,24,27,28,29,30,39,40,42,43,44],handler:6,handoff:[16,41],happen:[11,16,19,28,30,39,41,44],hard:[9,10,21,22,39,43],has:[7,9,10,17,19,21,23,26,27,29,30,33,39,42,43,44],hash:6,hasn:6,have:[6,7,9,12,13,14,16,17,19,21,22,23,24,27,28,29,30,32,33,34,39,40,41,42,43,44],hcond1:[28,30,44],hcond2:[28,30,44],hds:[9,19,21,23,26,28,30,39,43,44],hds_file:[28,29,30,44],hds_kperk:[29,30,44],hds_timeseri:[28,30,44],head1:[9,26,39],head2:[9,26,39],head:[28,29,30,44],head_lines_len:[24,43],head_row:[29,30,44],header:[24,27,29,30,34,43,44],headfil:[28,30,44],headsav:[28,30,44],heavi:[9,10,12,13,19,39,40],help:[7,9,19,29,30,35,39,44],helper:[5,9,15,16,19,21,23,24,30,32,34,38,39,41,43],here:[9,17,19,21,23,29,30,39,42,43,44],hessian:[8,9,39],heurist:[30,32],hex:6,hexbin:[16,41],hfb6:[28,30,44],hfb6_par:[28,30,44],hfb:[28,29,30,44],hfb_par:[28,29,30,44],hide_stderr:6,high:[0,9,29,39,44],highli:[30,34,44],hill:[9,10,39],hist:[24,43],histogram:[7,9,16,19,21,23,39,41,43],hk1:[9,26,39],hk2:[9,26,39],hk_0001:[30,33,44],hk_0002:[30,33,44],hk_:[30,33,44],hk_layer_1:[27,30,44],hkpp:[27,29,30,44],hob:[28,29,30,44],hob_fil:[28,30,44],hold:[16,41],horizont:[29,30,44],horribl:[28,30,44],hostnam:[30,32,44],how:[7,9,14,17,19,21,23,29,30,36,37,39,42,43,44],how_dict:[7,9,19,39],howev:[9,12,13,19,29,30,36,37,39,40,44],htcondor:[17,18,19,20,42],html:[29,30,44],http:[29,30,32,44],huge:[9,10,39],hydmod:[28,29,30,44],hydmod_fil:[28,30,44],hydmod_outfil:[28,30,44],hydraul:[29,30,44],hymod_fil:[28,30,44],i_minus_r:[8,9,38,39],ibound:[27,29,30,33,44],icod:[9,12,13,19,39,40],icount:[39,40],id_df:[16,41],idea:[9,19,21,23,39,43],ident:[8,9,10,12,13,19,30,38,39,40],identifi:[6,8,9,16,19,20,21,23,28,29,30,34,39,41,42,43,44],identifiabi:[8,9,39],identity_lik:[9,12,13,19,30,38,39,40],idiom:[29,30,44],idist:[27,30],idx:7,idx_str:[30,34],ies:[30,32,44],ifact:[27,30],ifmt:[22,24,30,39,43],ignor:[7,9,12,13,17,18,19,20,39,40,42],ij_in_idx:[30,34,44],iloc:[7,38,39],implement:[6,9,12,13,17,19,21,22,23,24,27,29,30,32,34,39,40,42,43,44],impli:[1,4,7,8,9,10,12,13,16,19,21,23,26,27,29,30,34,39,40,41,43,44],impliment:[30,34,44],in_fil:[9,19,21,23,24,29,30,39,43,44],in_filenam:[30,34],inact:[28,29,30,34,44],inact_abs_v:[28,30,44],inam:[27,30],includ:[0,6,7,9,10,12,13,16,17,19,21,23,24,26,27,29,30,33,34,39,40,41,42,43,44],include_path:[9,12,13,19,28,29,30,39,40,44],include_prior_result:[9,26,39],include_temporal_par:[28,29,30,44],include_zero:[16,41],include_zero_weight:[29,30,44],includes_head:[24,43],includes_index:[24,43],increas:[9,19,21,23,26,39,43],increment:[9,19,21,23,39,43],indent:[30,34,44],independ:[0,9,19,30,39,42,44],index:[2,7,8,9,12,13,16,19,20,21,23,24,26,27,28,29,30,33,34,35,36,37,39,40,41,42,43,44],index_col:[29,30,34,44],indic:[2,7,9,12,13,19,21,23,24,27,28,29,30,34,38,39,40,43,44],indici:[30,34,44],individu:[9,19,21,23,27,28,30,39,43,44],inequ:[9,19,21,23,39,43],influenti:[9,26,39],info:[9,12,13,19,21,23,24,27,28,29,30,33,34,39,40,43,44],inform:[6,9,10,14,19,21,22,23,24,26,27,28,29,30,32,33,34,35,39,43,44],inherit:[9,12,13,14,19,30,39,40],inhert:[7,9,39],initi:[7,9,17,18,19,20,26,27,30,34,39,42,44],initialize_spatial_refer:[30,34,39,44],inplac:[9,12,13,14,19,39,40],input:[9,16,17,19,21,22,23,24,28,29,30,32,34,39,41,42,43,44],input_fil:[29,30,44],input_filenam:[30,34,44],ins:[9,17,19,21,23,24,28,29,30,34,35,39,42,43,44],ins_fil:[9,19,21,23,24,28,29,30,34,39,43,44],ins_filenam:[24,28,30,35,43,44],ins_lcount:24,ins_lin:24,inschek:[9,19,21,23,24,29,30,34,39,43,44],insfil:[30,34,44],insfilenam:[29,30,44],insid:6,inst:[30,34,44],instanc:[1,2,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,39,40,41,42,43,44],instani:[9,12,13,19,30,39,40],instanti:[9,12,13,17,18,19,20,21,22,27,29,30,39,40,42,43,44],instead:[7,9,12,13,16,19,30,32,34,35,39,40,41,44],instruct:[9,17,19,21,23,24,28,29,30,34,35,39,42,43,44],instruction_set:[24,43],instructionfil:[9,19,21,23,24,30,34,39,43,44],int32:[39,40],integ:[7,9,12,13,19,27,30,33,38,39,40,44],interest:[9,10,19,21,23,28,30,44],interfac:[0,9,17,18,19,20,21,23,29,30,34,39,42,43,44],intergroup:[9,19,21,23,39,43],internet:[29,30,44],interp_cov:[27,30],interpl:[27,30,44],interpol:[27,29,30,44],interpolatino:[27,30,44],interpret:[9,12,13,19],intuit:[9,12,13,39,40],inv:[9,12,13,17,19,38,39,40,42],inv_h:[27,30,39,44],invers:[7,9,12,13,17,19,21,23,26,27,29,30,39,40,42,43,44],invert:[27,30,44],iobj:[16,41],irun:[29,30,44],is_domin:[19,20,42],is_feas:[19,20,39,42],is_nondominated_continu:[19,20,39,42],is_nondominated_kung:[19,20,39,42],is_nondominated_pathet:[19,20,39,42],is_pre_cmd:[30,34,44],isdiagon:[9,12,13,19,30,39,40],isdiaon:[9,12,13,19,30,39,40],isdigon:[9,12,13,19,30,39,40],isfropt:[29,30],iskeyword:[21,22,43],islog:[29,30,44],issu:[17,18,19,20,24,27,29,30,32,42,43,44],istransform:[2,7,9,19,38,39],item:[7,9,10,12,13,19,21,22,23,24,30,35,39,43],itemp1:[39,40],itemp2:[39,40],iter:[9,10,17,19,21,23,24,26,29,30,39,42,43,44],iter_report:[19,20,39,42],ithread:[27,30],its:[9,19,21,23,27,30,39,43,44],itself:[28,30,33,44],jacobian:[1,8,9,10,12,13,14,16,19,29,30,39,40,41,44],jactest:[16,29,30,41,44],jactest_in_fil:[16,41],jactest_out_fil:[16,41],jcb:[1,4,7,8,9,10,12,13,14,16,19,26,27,29,30,39,40,41,44],jco:[1,4,7,8,9,10,12,13,14,16,19,26,29,30,38,39,40,41,44],jco_from_pestpp_runstorag:[29,30,39,44],join:[9,16,19,21,23,30,32,34,39,41,43,44],junk:[27,30,44],just:[6,7,8,9,19,24,26,30,34,39,43,44],k_zone_dict:[29,30,44],kalman:[7,9,39],karg:[16,41],karhuen:[9,10,39],karhuenen:[29,30,44],karhunen:[1,8,9,10,29,30,39,44],keep:[8,9,19,21,23,39,43],kei:[7,9,16,19,21,22,23,26,28,29,30,34,39,41,43,44],kept:[28,30,44],keyword:[6,7,9,14,16,21,22,27,30,39,41,43,44],kij:[30,34,44],kij_dict:[28,30,44],kind:[8,9,16,19,21,23,24,26,29,30,39,41,43,44],kl_appli:[29,30,39,44],kl_factor:[29,30,44],kl_geostruct:[29,30,44],kl_num_eig:[29,30,44],kl_prop:[29,30,44],kl_setup:[29,30,39,44],know:[9,12,13,19,21,23,26,30,39,40,43],knowledg:[9,10,26,39],known:[9,12,13,19,26,30,39,40],kper:[28,29,30,44],kperk:[29,30],kperk_pair:[28,30,44],krige:[27,30,44],kstp:[28,30,44],kung:[19,20,42],kwarg:[1,4,7,8,9,10,12,13,14,16,18,19,20,21,22,23,26,27,29,30,34,39,41,42,43,44],l1_crit_val:[29,30,44],l2_crit_val:[29,30,44],label:[8,9,16,26,29,30,39,41,44],label_post:[16,41],label_prior:[16,41],lag:[24,43],lai:[29,30,44],lambda:[9,19,21,22,23,28,30,43,44],lambda_mult:[18,19,42],larg:[7,9,16,19,27,29,30,39,41,44],large_cov:[9,12,13,19,39,40],larger:[27,30,44],largest:[8,9,12,13,14,19,26,39,40],last:[7,9,19,24,26,28,30,39,43,44],last_kstp_from_kp:[28,30,39,44],later:[24,43],latest:[29,30],latex:[9,19,21,23,39,43],layer:[28,29,30,33,44],lbnd:[7,9,19,38,39],lbound:[30,34,44],lcount:[24,43],lead:[9,19,21,23,27,30,39,43,44],learn:[9,26,39],least:[9,19,21,23,30,33,39,43,44],leav:[9,19,21,23,39,43],leave_zero:[9,19,21,23,39,43],left:[9,12,13,19,29,30,34,39,40,44],legend:[27,30,44],len:[27,28,30,34,44],length:[9,12,13,19,29,30,39,40,44],length_multipli:[29,30,39,44],lenuni:[29,30,39,44],lenuni_text:[29,30,39,44],lenuni_valu:[29,30,39,44],less:[7,9,12,13,19,21,23,26,27,28,29,30,34,36,37,39,40,43,44],less_than_obs_constraint:[9,19,21,23,38,39,43],less_than_pi_constraint:[9,19,21,23,38,39,43],level:[6,9,19,21,23,29,30,32,34,39,43,44],lieu:[29,30,44],lift:[29,30,44],like:[6,9,12,13,19,24,29,30,32,34,39,40,43,44],likelihood:[9,10,39],limit:[9,19,21,23,30,34,39,43,44],linalg:[9,12,13,19,39,40],line:[9,16,19,21,22,23,24,27,28,29,30,32,34,35,39,41,43,44],linear:[9,10,11,12,13,17,19,26,30,39,40,42],linearanalsi:[1,8,9,39],linearanalysi:[1,4,8,9,10,14,26,38,39],list1:[13,40],list2:[13,40],list:[1,4,7,8,9,10,12,13,16,19,21,22,23,24,26,27,28,29,30,33,34,39,40,41,43,44],list_filenam:[28,30,44],load:[1,3,4,7,8,9,10,12,13,16,17,19,21,23,24,26,27,28,29,30,33,35,38,39,40,41,42,43,44],load_listtyp:[30,34],load_sfr_out:[28,30,39,44],load_sgems_exp_var:[27,30,39,44],loc:[7,9,19,21,23,26,38,39,43],local:[6,7,9,17,18,19,20,27,30,32,39,42,44],localhost:[30,32,44],locat:[9,12,13,14,19,21,23,27,28,29,30,33,34,39,40,43,44],lock:[27,30],loev:[1,8,9,10,29,30,39,44],log10:[7,9,16,39,41],log:[1,2,4,7,8,9,10,11,12,13,19,21,23,26,27,29,30,32,34,38,39,40,43,44],log_:[7,9,19,39],log_index:[7,9,19,38,39],logger:[5,7,9,16,19,20,27,30,38,41,42,44],london:[27,30,44],long_nam:[29,30,44],long_version_pi:6,longer:[29,30,34,44],longnam:[24,29,30,33,34,43,44],look:[6,9,12,13,19,24,28,29,30,39,40,43,44],loop:[17,19,42],lose:[9,26,39],lost:[9,26,30,34,39,44],lot:[16,28,29,30,41,44],low:[9,19,21,23,39,43],lower:[1,4,7,8,9,10,12,13,19,21,23,24,26,29,30,34,39,40,43,44],lower_bound:[29,30,34,44],lower_lim:[27,30,44],lpf:[29,30,44],lrais:[11,19,38,39],machin:[17,18,19,20,30,32,42,44],made:[16,27,30,32,41,44],magnitud:[9,26,39],mahalanobi:[29,30,44],mai:[9,12,13,17,19,21,23,24,27,28,30,33,34,39,40,42,43,44],main:[9,19,21,23,27,29,30,39,43,44],major:[29,30,44],make:[7,9,10,12,13,16,19,21,22,23,27,28,29,30,32,34,39,40,41,43,44],manag:[17,18,19,20,42],mani:[9,12,13,19,21,23,29,30,39,40,43,44],manipul:[3,9,19,21,23,39,43],manual:[9,19,21,23,39,43],map:[27,29,30,44],mark:[6,16,28,30,41,44],marker:[24,43],martrix:[9,12,13,19,39,40],mass:[28,30,44],master:[30,32,44],master_dir:[30,32,44],mat:[1,4,5,8,9,10,19,26,30,38,39],mat_handl:[5,9,12,19,30,38,39],mat_inv:[9,12,13,19,39,40],match:[21,22,27,30,43,44],matplotlib:[7,9,16,19,21,23,27,30,39,41,43,44],matric:[7,9,12,13,17,19,29,30,39,40,42,44],matrix:[1,4,7,8,9,10,12,13,14,17,18,19,20,26,27,29,30,34,38,39,40,42,44],matrx:[8,9,39],matter:[7,9,19,29,30,39,44],max:[9,12,13,19,21,23,29,30,39,40,43,44],max_colwidth:[22,23,24,28,29,30,33],max_name_len:[30,35,44],maximum:[9,10,16,19,21,23,27,28,30,39,41,43,44],maxoutputpag:[16,41],maxpts_interp:[27,30,44],maxs:[7,8,9,12,13,19,21,23,39,40,43],mayb:[29,30,44],mbase:[28,29,30,33,44],mc_:[14,39],mean:[6,7,9,14,16,17,18,19,24,27,29,30,39,41,42,43,44],mean_valu:[7,9,27,30,44],meaning:[9,19,21,23,28,29,30,39,43,44],measur:[14,17,18,19,20,24,29,30,39,42,43,44],mechan:[3,9,19,21,23,39,43],member:[9,19,21,23,29,30,39,43,44],memori:[7,9,12,13,19,27,29,30,39,40,44],meshgrid:[29,30,44],mess:[7,39],messag:[11,19,24,30,32,39,43,44],met:[9,19,21,23,39,43],meta:[9,19,21,23,39,43],metadata:[9,19,21,23,39,43],meter:[29,30,44],method:[6,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,34,39,40,41,42,43,44],mf_skip:[30,34,44],mfhyd:[29,30,44],mfile_fmt:[30,34,44],mfile_skip:[30,34,44],mflist_waterbudget:[29,30,44],mfnwt:[29,30,44],might:[13,40],mimic:[27,30,44],min:[8,9,12,13,19,21,23,39,40,43],min_dist:[19,20],minim:[24,43],minimum:[8,9,27,29,30,39,44],mininum:[28,30,44],minpts_interp:[27,30,44],minu:[8,9,39],miss:[7,9,19,21,23,24,28,30,35,39,43,44],mix:[9,10],mixtur:[7,9,19,29,30,39,44],mle_covari:[9,10,38,39],mle_parameter_estim:[9,10,38,39],mlt_df:[29,30],mlt_file:[29,30,44],mode:[17,19,30,32,42,44],model:[0,9,16,17,18,19,20,21,23,24,27,28,29,30,33,34,39,41,42,43,44],model_evalut:[17,19,39,42],model_exe_nam:[29,30,44],model_fil:[29,30,44],model_length_unit:[29,30,39,44],model_temporal_evolot:[17,19,39,42],model_w:[28,29,30,44],modflow:[27,28,29,30,33,34,44],modflow_hob_to_instruction_fil:[28,30,39,44],modflow_hydmod_to_instruction_fil:[28,30,39,44],modflow_pval_to_template_fil:[28,30,39,44],modflow_read_hydmod_fil:[28,30,39,44],modflow_sfr_gag_to_instruction_fil:[28,30,39,44],modflownwt:[29,30,44],modflowsfr:[28,30,44],modif:[9,19,21,23,39,43],modifi:[9,19,21,23,28,30,39,43,44],modul:[0,9,12,15,19,21,30,36,37,38],moment:[7,9,16,19,39,41],monster:[29,30,44],mont:[7,8,9,14,19,39,42],montecarlo:[10,14,38,39],month:[29,30,44],moouu:[5,9,19,38,39],more:[9,12,13,16,19,21,23,26,27,28,29,30,32,34,36,37,39,40,41,43,44],most:[7,9,12,19,21,23,26,29,30,32,34,39,40,43,44],move:[17,19,42],mozorov:[9,19,21,23,39,43],mt3d:[28,30,44],mt3dusg:[28,30,44],mtlist_gw:[28,30,44],mtlist_sw:[28,30,44],mult2model_info:[29,30,44],mult:[28,29,30,34,44],mult_isalign:[9,12,13,19,38,39,40],mult_well_funct:[30,34,44],multi:[8,9,16,26,39,41],multiobject:[19,20,42],multipag:[7,9,16,19,21,23,39,41,43],multipl:[7,9,12,13,19,29,30,34,39,40,44],multipli:[9,12,13,19,28,29,30,34,39,40,44],multiprocess:[24,27,29,30,43,44],multivari:[7,9,19,39],must:[7,9,12,13,14,16,17,19,21,23,26,27,28,29,30,33,34,39,40,41,42,43,44],mut_bas:[19,20,42],my_inv:[9,12,13,19,39,40],my_new:[3,9,19,21,23,30,34,39,43,44],my_p:[29,30,44],my_pp:[30,33,44],my_reg:[29,30,44],my_tri_p:[7,9,19,39],my_uni_p:[7,9,19,39],mymodel:[27,29,30,44],nadj_par:[8,9,14,39],nam:[27,29,30,33,44],nam_fil:[28,29,30,44],name:[1,2,3,4,6,7,8,9,10,12,13,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43,44],name_col:[30,35,44],name_prefix:[30,33,44],namefil:[29,30,44],namfil:[29,30,44],nan:[7,9,19,24,27,28,30,35,39,43,44],nane:[30,35,44],ncol:[9,12,13,19,29,30,33,38,39,40,44],ncomp:[28,30,44],ndarrai:[1,4,7,8,9,10,12,13,16,19,26,27,28,29,30,33,34,39,40,41,44],ndarri:[28,30,44],ndrop:[19,20],nearbi:[27,30],nearli:[7,9,19,39],necessari:[9,10,39],need:[1,4,6,7,8,9,10,14,16,19,22,24,26,28,29,30,32,34,39,41,43,44],nest:[9,24,26,27,29,30,39,43,44],net:[30,32],never:[9,19,21,23,39,43],new_cwd:[29,30,44],new_d:[30,34,44],new_filenam:[9,19,21,23,39,43],new_group:[9,19,21,23,39,43],new_model_w:[29,30,44],new_ob:[9,19,21,23,30,34,39,43,44],new_obs_length:[9,12,13,19,38,39,40],new_par:[9,19,21,23,39,43],new_par_length:[9,12,13,19,38,39,40],new_pst_nam:[29,30,44],new_val:[29,30,44],newx:[9,12,13,19,38,39,40],next:[9,24,26,39],next_most_important_added_ob:[9,26,38,39],next_most_par_contribut:[9,26,38,39],ninst:[30,33,44],niter:[9,26,39],nnz_ob:[8,9,19,21,23,38,39,43],nnz_obs_group:[9,19,21,23,38,39,43],nnz_obs_nam:[7,9,10,19,21,23,26,38,39,43],nob:[9,12,13,19,21,23,38,39,40,43],node:[27,30,44],nois:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,26,29,30,39,40,41,42,44],nomin:[8,9,39],non:[7,8,9,10,12,13,19,20,21,23,26,28,29,30,32,34,39,40,42,43,44],none:[1,3,4,6,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,31,32,33,34,35,39,40,41,42,43,44],nont:[14,39],nonzero:[7,9,19,21,23,38,39,43],noptmax:[3,9,14,19,21,23,29,30,39,43,44],norm:[7,9,19,39],normal:[1,4,8,9,10,12,13,19,21,23,26,30,39,40,43],north:[27,30,44],note:[1,4,6,8,9,11,12,13,19,21,22,23,26,27,29,30,32,33,34,39,43,44],notebook:[36,37],noth:[16,41],notion:[9,26,39],notthismethod:6,notus:[27,30,44],npar:[9,10,12,13,19,21,23,38,39,40,43],npar_adj:[9,19,21,23,38,39,43],nprior:[9,19,21,23,38,39,43],nrow:[9,12,13,19,21,23,29,30,33,38,39,40,44],nsing:[14,39],nsmc:[14,39],nsv:[16,41],nugget:[27,30,39,44],nul:[30,32],num_dv_real:[19,20,42],num_eig:[29,30,44],num_eig_kl:[30,34,44],num_par_r:[19,20,42],num_pt:[16,41],num_real:[7,9,14,17,19,20,27,29,30,34,38,39,42,44],num_step:[29,30,44],num_thread:[27,30,44],num_work:[17,18,19,20,30,32,42,44],number:[7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,32,33,34,39,40,41,42,43,44],numer:[7,9,12,13,19,21,23,30,39,40],numpi:[1,4,7,8,9,10,12,13,16,19,26,27,29,30,33,34,39,40,41,44],nwt_x64:[29,30,44],o2nam:[29,30],obf:24,obgnm:[9,19,21,23,39,43],obj_func_dict:[19,20,31,42,44],obj_function_dict:[19,20,42],object:[1,4,6,7,8,9,10,11,12,13,14,18,19,20,21,22,23,24,26,27,28,29,30,34,39,40,42,43,44],obs1:[9,19,21,23,24,39,43],obs2:[9,19,21,23,24,39,43],obs:[7,9,10,14,16,19,20,21,23,24,26,28,29,30,34,39,41,42,43,44],obs_base_dev:[7,9,39],obs_df:[19,20,42],obs_dict:[9,19,21,23,39,43],obs_ensembl:[19,20],obs_group:[9,19,21,23,38,39,43],obs_idx:[9,19,21,23],obs_length:[9,12,13,19,38,39,40],obs_nam:[9,10,12,13,19,21,23,24,38,39,40,43],obs_name_set:[24,39,43],obs_obj_sign:[19,20,39,42],obs_point:[27,30,44],obs_v_sim:[9,19,21,23,39,43],obscov:[1,4,8,9,10,12,13,17,18,19,20,26,30,38,39,40,42],obsenembl:[17,19,42],obsensembl:[14,17,19,38,39,42],obseravt:[9,17,19,21,23,39,42,43],observ:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,34,35,39,40,41,42,43,44],observation_data:[7,9,12,13,19,21,23,24,30,38,39,40,43],observationensembl:[7,9,14,17,19,29,30,38,39,42,44],obseur:[22,43],obsev:[1,4,8,9,10,26,39],obsgp:[30,34,44],obsgrp_dict:[9,19,21,23,26,39,43],obslist:[9,19,21,23],obslist_dict:[9,26,39],obsnam:[29,30,44],obsnm:[28,30,34,44],obssim_smp_pair:[29,30,44],obsval:[7,9,19,20,21,23,24,28,30,34,39,42,43,44],obsviou:[9,19,21,23,39,43],oe1:[7,9,19,39],oe2:[7,9,19,39],oe3:[7,9,19,39],oe_dev:[7,9,39],off:[9,19,21,23,29,30,34,39,43,44],offend:[7,9,19,39],offset:[1,4,7,8,9,10,12,13,19,21,23,24,26,29,30,34,39,40,43,44],ofile_sep:[30,34,44],ofile_skip:[30,34,44],ogw:[29,30,44],ok_var:[27,30,44],older:6,omit:[1,8,9,39],omitted_jco:[8,9,38,39],omitted_par:[8,9,39],omitted_paramet:[1,8,9,39],omitted_parcov:[1,8,9,38,39],omitted_predict:[1,8,9,38,39],onc:[9,12,13,17,19,30,32,34,39,40,42,44],one:[6,7,9,11,12,13,16,19,21,23,24,26,27,28,29,30,32,34,39,40,41,43,44],onerror:[30,32],ones:[29,30],onli:[1,4,6,7,8,9,10,12,13,16,19,21,22,23,24,26,27,28,29,30,32,33,34,39,40,41,43,44],only_col:[24,43],only_row:[24,43],onlyus:[1,4,8,9,10,26,39],open:[9,11,19,21,22,23,27,30,39,43,44],oper:[7,8,9,10,12,13,14,16,19,24,28,29,30,32,39,40,41,43,44],operator_symbol:31,operator_word:31,opt:[20,42],optim:[5,9,30,38,39],optino:[27,30,44],option:[1,3,4,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],opton:[16,41],ora:[27,30,44],order:[9,12,13,19,24,29,30,39,40,43,44],ordinari:[27,30,44],ordinarykirg:[27,30,44],ordinarykrig:[27,30,39,44],org:[29,30,44],org_arr:[30,34],org_cwd:[29,30,44],org_fil:[29,30,44],org_model_w:[29,30,44],org_val:[29,30,44],orgin:[30,34,44],orient:[27,30,44],orig:[29,30,44],origin:[28,29,30,34,44],origin_loc:[29,30,39,44],original_ceil:[9,19,21,23,39,43],original_d:[30,34,44],os_util:[5,9,30,38,39],other:[7,9,10,12,13,19,21,23,27,28,29,30,34,39,40,43,44],otherwis:[9,12,13,14,19,20,21,23,24,26,29,30,39,40,42,43,44],our:6,out:[6,7,9,11,12,13,19,21,23,28,29,30,34,39,40,43,44],out_fil:[9,19,21,23,24,27,29,30,34,39,43,44],out_filenam:[28,30,44],out_pst_nam:[31,44],outer:[30,34,44],outfil:[29,30,44],outflow:[28,30,44],output:[1,4,8,9,10,16,17,19,21,23,24,26,28,29,30,32,34,39,41,42,43,44],output_fil:[24,29,30,43,44],outputdirectori:[16,41],over:[17,19,29,30,42,44],overload:[9,12,13,19,39,40],overrid:[7,9,29,30,39,44],overwrit:[14,30,34,39,44],own:[30,32,44],packag:[28,29,38],page:[5,16,36,37,41],pair:[8,9,10,17,19,21,23,24,27,28,29,30,33,34,39,42,43,44],pak:[29,30],pakattr:[29,30],panda:[2,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],par1:[7,9,19,21,23,24,39,43],par2:[9,19,21,23,24,39,43],par:[7,9,10,14,16,19,21,23,24,28,29,30,34,39,41,43,44],par_:[9,12,13,19,30,39,40],par_bounds_dict:[29,30,44],par_col:[28,29,30,44],par_en:[14,39],par_ensembl:[19,20,42],par_fil:[14,28,29,30,39,44],par_group:[9,19,21,23,29,30,38,39,43,44],par_knowledge_dict:[29,30],par_length:[9,12,13,19,38,39,40],par_nam:[9,10,12,13,19,20,21,23,24,29,30,38,39,40,42,43,44],par_name_bas:[30,34,44],par_styl:[30,34,44],par_to_file_dict:[29,30,44],par_typ:[30,34,44],parallel:[17,18,19,20,42],param:[28,30,34,44],paramerteris:[30,34,44],paramet:[1,2,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],parameter:[28,29,30,33,34,44],parameter_data:[7,9,14,19,21,23,24,38,39,43],parameter_group:[9,19,21,23,39,43],parameter_nam:[9,26,39],parameterensembl:[7,9,14,16,17,19,27,29,30,34,38,39,41,42,44],parameteris:[30,34,44],paramt:[28,30,44],paranm:[30,34],parbound:[29,30,44],parchglim:[9,19,21,23,39,43],parcov:[1,4,8,9,10,17,18,19,20,26,38,39,42],paremet:[30,34],parensembl:[14,17,18,19,38,39,42],parensmebl:[17,19,42],parent:[6,9,10,39],parentdir_prefix:6,parenthes:[30,34,44],pareto:[19,20,42],paretoobjfunc:[19,20,39,42],parfil:[7,9,19,21,23,24,39,43],parfile_nam:[7,9,19,39],parfile_rel:[30,34,39,44],pargp:[9,19,21,23,27,30,34,39,43,44],pargroup:[30,34],parlbnd:[7,9,19,21,23,39,43],parlbnd_tran:[9,19,21,23,39,43],parlist_dict:[9,26,39],parmaet:[17,18,19,20,42],parnam:[29,30,34,44],parnm:[9,19,21,23,24,29,30,33,39,43,44],parrep:[9,19,21,23,38,39,43],pars:[16,22,24,27,28,29,30,34,41,43,44],parse_dir_for_io_fil:[29,30,39,44],parse_filenam:[28,30,44],parse_ins_fil:[24,39,43],parse_kij_arg:[30,34,39,44],parse_namefil:[28,30,44],parse_tpl_fil:[24,30,39,43],parse_values_from_lin:[21,22,39,43],parser:[30,35],part:[9,12,13,17,19,21,22,23,24,26,28,30,34,39,40,42,43,44],parti:[9,19,21,23,39,43],partial:[7,9,19,39],particular:6,partran:[2,7,9,19,21,23,39,43],parubnd:[7,9,19,21,23,39,43],parubnd_tran:[9,19,21,23,39,43],parval1:[7,9,14,19,21,23,24,27,28,29,30,33,39,43,44],parval1_tran:[9,19,21,23,39,43],parval:[24,43],pass:[1,4,7,8,9,10,11,12,13,16,19,21,22,23,26,27,28,29,30,34,35,39,40,41,43,44],path:[9,14,16,17,18,19,20,21,23,24,28,29,30,32,33,34,35,39,41,42,43,44],patheic:[19,20,42],pdf:[7,9,16,19,21,23,39,41,43],pe_proj:[7,9,19,39],peak:[16,41],pearson:[9,12,13,19,29,30,39,40,44],peic:[17,19,42],peopl:[30,32,44],per:[16,24,28,29,30,41,43,44],percent:[9,26,39],percent_reduct:[9,26,39],perfect:[9,26,39],perfectli:[9,26,39],perform:[1,8,9,14,16,17,19,29,39,41,42,44],period:[9,19,21,23,28,29,30,39,43,44],peripher:[24,43],perl:[30,32],pertub:[9,19,21,23,29,30,39,43,44],pest:[0,1,2,3,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,35,39,40,41,42,43,44],pest_control_fil:[16,41],pest_obj:[16,41],pest_object:[16,41],pestchek:[29,30,44],pestmod:[9,19,21,23,39,43],pestpp:[9,16,19,21,23,24,30,32,39,41,43,44],pestpp_opt:[1,4,8,9,16,26,39,41],phi:[7,9,10,16,19,21,23,24,38,39,41,43],phi_compon:[9,19,21,23,38,39,43],phi_components_norm:[9,19,21,23,38,39,43],phi_pi:[9,19,21,23,39,43],phi_progress:[16,39,41],phi_vector:[7,9,19,38,39],phimlim:[9,19,21,23,39,43],phrase:[11,19,39],physic:[30,34,44],pi_obgnm:[9,19,21,23,39,43],pictur:[36,37],pie:[9,16,19,21,23,39,41,43],piec:6,pilbl:[9,19,21,23,39,43],pilot:[27,29,30,33,34,44],pilot_point:[30,33,44],pilot_points_to_tpl:[30,33,39,44],pilotpoint:[30,34,44],pipe:[30,32,44],place:[1,7,8,9,12,13,19,27,30,39,40,44],placehold:[24,43],platform:[30,32,44],pleas:[24,43],plot:[5,7,8,9,19,21,23,24,26,27,30,38,39,43,44],plot_col:[7,9,16,39,41],plot_hexbin:[16,41],plot_id_bar:[16,39,41],plot_jac_test:[16,39,41],plot_summary_distribut:[16,39,41],plot_util:[5,7,9,15,38,39],plt:[9,16,26,39,41],plu:[9,26,39],plus_or_dot:6,point:[4,7,8,9,16,19,21,23,24,26,27,28,29,30,33,34,39,41,43,44],point_cov:[27,30],point_cov_df:[27,30],point_data:[27,30,44],point_pair:[27,30],points_fil:[27,30,44],polici:[29,30,44],poor:[24,43],popul:[6,9,19,21,23,24,29,30,34,39,43,44],port:[17,18,19,20,30,32,42,44],portion:[9,19,21,23,39,43],posit:[7,9,12,13,19,21,23,39],possibl:[9,19,21,23,39,43],post:[6,9,16,26,28,29,30,34,39,41,44],post_cov:[9,26,39],post_mean:[16,41],post_py_cmd:[30,34,44],post_stdev:[16,41],postdist:6,posterior:[9,10,16,26,39,41],posterior_forecast:[9,26,38,39],posterior_paramet:[9,26,38,39],posterior_predict:[9,26,38,39],postprocess_inact:[28,30,44],potenti:[9,26,27,30,39,44],pow:[7,9],power:[9,12,13,19],pp_df:[27,30,33,44],pp_dir:[30,33,44],pp_file:[27,29,30,33,44],pp_file_to_datafram:[27,30,33,39,44],pp_filenam:[30,33,44],pp_fmt:[28,30,33],pp_geostruct:[29,30,44],pp_name:[28,30,33,44],pp_prop:[29,30,44],pp_space:[29,30,34,44],pp_tpl_file_to_datafram:[30,33,44],pp_tpl_to_datafram:[30,33,39,44],pp_util:[5,9,27,29,30,38,39],practic:[9,26,27,30,39,44],pre:[29,30,34,44],pre_py_cmd:[30,34,44],precent_reduct:[9,26,39],precis:[28,30,44],precison:[28,30,44],precondit:[8,9,39],predict:[1,4,8,9,10,26,38,39],prediction_nam:[8,9,39],predictions_it:[9,10,38,39],predunc:[4,9,26,39],prefer:[29,30,44],prefix:[6,14,24,28,29,30,32,33,34,35,39,43,44],prefix_dict:[30,33,44],prepar:[9,17,19,21,23,27,29,30,39,42,43,44],prepend:[9,19,21,23,24,28,29,30,33,34,39,43,44],prepend_path:[29,30,44],preprocess:[29,30,34,44],present:[9,19,21,23,39,43],preserv:[9,19,21,23,29,30,34,39,43,44],prevent:[7,9,19,39],previou:[17,19,28,30,42,44],previous:[27,28,30,44],primari:[3,4,8,9,12,19,21,23,26,39,40,43],primarili:[9,26,39],princip:[9,19,21,23,39,43],print:[4,9,10,19,21,23,26,30,32,39,43,44],prior:[1,4,7,8,9,10,16,17,18,19,20,21,23,24,26,29,30,34,39,41,42,43,44],prior_forecast:[9,10,38,39],prior_group:[9,19,21,23,38,39,43],prior_inform:[9,19,21,23,38,39,43],prior_mean:[16,41],prior_nam:[9,19,21,23,38,39,43],prior_paramet:[9,10,38,39],prior_predict:[9,10,38,39],prior_stdev:[16,41],privat:[8,9,10,12,13,19,21,23,24,26,27,28,30,34,39,40,43],prj:[29,30,44],probabl:[7,9,19],problem:[27,29,30,44],process:[7,9,10,19,21,23,24,26,27,28,29,30,32,33,34,39,43,44],process_:[29,30,44],process_output_fil:[9,19,21,23,24,38,39,43],processor:[28,30,44],produc:[28,29,30,44],product:[9,12,13,19,39,40],program:[16,24,41,43],progress:[7,9,11,19,39],proj4:[29,30,44],proj4_str:[29,30,39,44],proj_par:[7,9,19,39],project:[6,7,8,9,14,19,27,30,38,39,44],project_parensembl:[14,38,39],projectin:[7,9,19,39],projection_matrix:[7,9,19,39],prop:[27,30,44],propag:[9,12,13,18,19,30,39,40],proper:[29,30,44],properti:[7,8,9,10,12,13,14,19,20,21,22,23,24,26,27,29,30,34,39,40,42,43,44],proport:[9,19,21,23,29,30,39,43,44],proportional_weight:[9,19,21,23,38,39,43],protect:[21,22,43],prototyp:[5,9,38,39],provid:[3,7,9,19,21,23,27,30,34,36,37,39,43,44],proxi:[9,12,13,19,30,39,40],pseudo:[9,12,13,19,39,40],pseudo_inv:[9,12,13,19,38,39,40],pseudo_inv_compon:[9,12,13,19,38,39,40],pst:[1,2,4,5,7,8,9,10,12,13,14,16,17,18,19,20,26,27,29,30,31,32,34,38,39,40,41,42,44],pst_config:[24,30],pst_controldata:[5,9,19,21,23,38,39],pst_file:[9,12,13,19,30,39,40],pst_filenam:[9,19,21,23,24,29,30,39,43,44],pst_from:[5,9,30,38,39],pst_from_io_fil:[9,19,21,23,29,30,39,43,44],pst_from_parnames_obsnam:[29,30,39,44],pst_handler:[5,9,21,38,39],pst_helper:[16,39,41],pst_path:[9,19,21,23,24,29,30,34,39,43,44],pst_prior:[16,39,41],pst_rel_path:[9,19,21,23,30,32,44],pst_util:[5,9,21,30,34,38,39],pstfrom:[9,19,21,23,29,30,34,39,43,44],pstfromflopi:[27,29,30,44],pstfromflopymodel:[29,30,39,44],pt0:[27,30,44],pt1:[27,30,44],pt_color:[16,41],pt_name:[27,30],pt_zone:[27,30,44],ptname:[27,30],ptx_arrai:[27,30],pty_arrai:[27,30],purpos:[7,9,12,13,19,30,39,40],put:[16,29,30,41,44],pval:[28,30,44],pval_fil:[28,30,44],py_import:[30,34,44],pyemi:[9,12,13,19,29,30,39,40,44],pyemu:5,pyemu_warn:[5,9,38],pyemupilot:[27,30,44],pyemuwarn:[24,25,30,38,39,43],pyplot:[16,27,30,41,44],pyshp:[30,33,44],python:[0,9,19,21,23,27,28,29,30,32,34,35,39,42,43,44],qhalf:[9,10,38,39],qhalfx:[9,10,38,39],quantifi:[29,30,44],quantil:[29,30,44],quantile_idx:[29,30,44],queri:[30,34,44],quickli:[9,12,13,19,30],r_factor:[27,30,44],radian:[27,30,44],rais:[6,9,11,12,13,19,24,27,29,30,32,39,40,43,44],random:[7,9,12,13,16,17,19,27,30,39,40,41,42,44],randomli:[7,9,19,39],rang:[1,4,8,9,10,12,13,16,19,21,23,26,27,29,30,39,40,41,43,44],rank:[9,26,39],rather:[9,10,39],ratio:[8,9,12,13,14,19,27,30,39,40,44],ravel:[29,30,44],raw:[21,22,43],rc11:[29,30,44],rch:[29,30,44],reach:[9,12,13,19,28,30,39,40,44],reach_par:[28,30,44],reachdata:[28,30,44],reachinput:[28,29,30,44],read:[9,12,13,19,21,23,24,27,28,29,30,32,33,34,39,40,43,44],read_ascii:[9,12,13,19,38,39,40],read_binari:[9,12,13,19,38,39,40],read_csv:[7,9,12,13,19,39,40],read_ins_fil:[24,39,43],read_output_fil:[24,39,43],read_parfil:[24,39,43],read_pestpp_runstorag:[29,30,39,44],read_resfil:[24,39,43],read_sgems_variogram_xml:[27,30,39,44],read_struct_fil:[27,30,39,44],read_usgs_model_reference_fil:[29,30,39,44],readonli:[30,32],reads_struct_fil:[27,30,44],real:[27,29,30,34,44],real_nam:[7,9,19,39],realist:[7,9,19],realiz:[2,7,9,14,17,19,20,24,27,29,30,34,39,42,43,44],realli:[9,19,21,23,24,29,30,39,43,44],realm:[2,7,9,12,13,19,27,32,39,40,44],realzat:[7,9,19,39],reappli:[30,34,44],rebuild_pst:[30,34,44],recfil:[24,43],rech1:[9,26,39],rech2:[9,26,39],rech:[9,26,29,30,39,44],recharg:[29,30,44],recommend:[28,30,34,44],record:[24,28,30,43,44],recreat:[27,30,44],rectifi:[9,19,21,23,27,30,39,43,44],rectify_group:[9,19,21,23,39,43],rectify_pgroup:[9,19,21,23,38,39,43],rectify_pi:[9,19,21,23,38,39,43],red:[16,41],redirect:[29,30,44],redirect_forward_output:[29,30,44],reduc:[29,30,44],reduce_stack_with_risk_shift:[19,20,39,42],reduct:[9,26,39],ref:[27,30,44],ref_var:[1,4,8,9,10,26,39],refer:[1,4,8,9,10,12,13,19,26,27,29,30,33,34,39,40,44],reffil:[29,30,44],reg_data:[9,19,21,23,38,39,43],reg_default_lin:22,reg_variable_lin:22,regdata:[9,19,21,22,23,39,43],region:[27,30,44],register_vcs_handl:6,registri:[29,30,44],regular:[9,10,19,21,22,23,27,29,30,39,43,44],regularli:[30,33,44],rel:[9,19,21,23,29,30,32,39,43,44],rel_path:[30,32,44],relat:[7,9,19,28,29,30,34,39,44],releas:6,relev:[14,39],reli:[7,9,19,39],remain:[9,26,39],remov:[7,9,12,13,19,21,23,26,29,30,32,39,40,43,44],remove_exist:[29,30,34,44],remove_readonli:[30,32],render:6,render_git_describ:6,render_git_describe_long:6,render_pep440:6,render_pep440_old:6,render_pep440_post:6,render_pep440_pr:6,reorder:[9,12,13,17,19,39,40,42],rep:[27,30],repeat:[27,30,44],repeatedli:[17,19,29,30,42,44],replac:[7,9,12,13,19,21,23,24,29,30,34,38,39,40,43,44],replic:[4,9,19,21,23,26,27,30,39,43,44],repo:[36,37],report:[9,19,21,23,29,30,39,43,44],repr:[7,9,29,30],repres:[1,4,7,8,9,10,12,13,19,21,23,26,27,29,30,34,39,40,43,44],represent:[9,12,13,19,27,30,34,39,40,44],repsect:[9,19,21,23,29,30,39,43,44],request:[6,9,16,19,21,23,29,30,39,41,43,44],requir:[9,12,13,16,19,21,23,26,28,29,30,33,34,39,40,41,43,44],requisit:[30,34,44],res:[9,19,21,23,38,39,43],res_1to1:[16,39,41],res_fil:[9,10,39],res_from_en:[24,39,43],res_from_obseravtion_data:[24,39,43],res_idx:[9,19,21,23],res_phi_pi:[16,39,41],rese:[7,9,38,39],reset:[7,9,10,12,13,14,17,19,21,23,26,27,28,29,30,39,40,42,43,44],reset_obscov:[9,10,38,39],reset_parcov:[9,10,38,39],reset_pst:[9,10,38,39],reset_x:[9,12,13,19,38,39,40],reset_zero_weight:[9,26,39],resfil:[3,9,10,19,21,23,24,39,43],resid:[9,19,21,23,24,29,30,34,39,43,44],residu:[3,9,10,16,19,21,23,24,39,41,43],resol:[9,12,13,19,39,40],resolut:[8,9,39],resolution_matrix:[9,12,13,19,39,40],resort:[24,43],resourc:[29,30,44],respec:[1,8,9,39],respect:[7,9,19,20,26,27,28,30,34,39,42,44],restart_obsensembl:[17,19,42],restrict:[30,34,44],resulat:[29,30,44],result:[7,9,12,13,16,17,19,21,23,24,26,27,28,29,30,39,40,41,42,43,44],retain:[9,12,13,19,29,30,39,40,44],return_obs_fil:[28,30,44],return_typ:[27,30,44],reuse_mast:[30,32,44],revers:[9,12,13,19],reweight:[9,19,21,23],rewrit:[30,34,44],rewritten:6,rhs:[9,19,21,23,27,30,39,43],right:[8,9,12,13,19,21,23,39,40,43],risk:[19,20,42],riv:[29,30,44],river:[29,30,44],rmse:[9,19,21,23,29,30,39,43,44],rmtree:[30,32],rnj:[29,30,44],rnj_filenam:[29,30,44],root:[6,9,10,12,13,19,29,30,32,39,40,44],rotat:[27,29,30,39,44],rotation_coef:[27,30,39,44],round:[9,19,21,23,39,43],rout:[29,30,44],routin:[28,29,30,44],row:[1,4,7,8,9,10,12,13,17,19,20,21,23,24,26,27,29,30,33,34,39,40,42,43,44],row_:[9,12,13,19,39,40],row_nam:[9,10,12,13,19,26,30,39,40],run:[7,9,14,16,17,18,19,20,21,23,24,27,28,29,30,32,34,39,41,42,43,44],run_command:6,run_subset:[18,19,42],runtim:[25,28,30,44],runtimewarn:[25,30,39],s_1:[8,9,12,13,19,39,40],same:[1,4,8,9,10,12,13,16,19,21,23,24,26,27,28,29,30,33,39,40,41,43,44],same_as_oth:[27,30,39,44],sampl:[7,9,19,35,39,44],sanity_check:[9,19,21,23,38,39,43],save:[9,11,12,13,16,19,27,28,29,30,33,34,39,40,41,44],save_coo:[12,13,39,40],save_setup_fil:[28,30,44],sbow:[16,41],scalar:[9,12,13,19,28,29,30,39,40,44],scale:[1,4,7,8,9,10,12,13,18,19,21,23,24,26,27,29,30,34,39,40,43,44],scale_offset:[1,4,8,9,10,12,13,19,26,29,30,34,39,40,44],scaling_matrix:[18,19],scatter:[16,41],scenario:6,schur:[0,9,10,12,13,19,21,23,26,30,38,39,40,42],scratch:[30,34,44],screen:[1,4,8,9,10,11,19,24,26,29,30,39,43,44],script:[28,29,30,34,44],sdt:[30,34],seamlessli:[0,9,19,39,42],search:[6,9,12,13,19,27,29,30,36,37,39,40,44],search_radiu:[27,30,44],second:[7,8,9,11,12,13,16,19,27,30,32,39,40,41,44],second_forecast:[8,9,38,39],second_paramet:[8,9,38,39],second_predict:[8,9,38,39],section:[9,12,13,19,21,22,23,24,30,39,40,43],see:[9,14,19,21,23,26,29,30,39,43,44],seed:[7,9,27,30,39,44],seek:[9,26,39],seem:[9,19,21,23,39,43],seen:[14,39],seg_group_dict:[28,30,44],seg_par:[28,30,44],seg_reach:[28,30,44],segement:[28,30,44],segment:[28,29,30,44],select:[16,28,29,30,34,41,44],self:[7,8,9,10,11,12,13,14,17,18,19,20,21,22,23,24,26,27,29,30,34,39,40,42,43,44],sensit:[1,4,8,9,10,26,29,30,39,44],sentiv:[9,10,39],seo:[9,10,39],sep:[24,29,30,34,43,44],separ:[9,16,19,21,23,27,28,29,30,34,39,41,43,44],sequenc:[27,29,30,44],sequenti:[9,26,30,34,39],seri:[7,9,10,14,19,20,21,22,23,24,28,30,39,42,43,44],serial:[17,18,19,20,29,30,42,44],set:[1,4,8,9,10,12,13,14,16,17,18,19,20,21,23,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],set_index:[30,34],set_r:[9,19,21,23,38,39,43],set_spatialrefer:[29,30,39,44],setattr:[9,19,21,22,23,29,30],setup:[0,9,19,21,23,28,29,30,33,34,39,42,43,44],setup_fake_forward_run:[29,30,39,44],setup_gage_ob:[28,30,39,44],setup_hds_ob:[28,30,39,44],setup_hds_timeseri:[28,30,39,44],setup_mflist_budget_ob:[28,30,39,44],setup_mtlist_budget_ob:[28,30,39,44],setup_pilot_points_grid:[29,30,44],setup_pilotpoints_grid:[30,33,39,44],setup_setup_temporal_diff_ob:[29,30,44],setup_sfr_ob:[28,30,39,44],setup_sfr_reach_ob:[28,30,39,44],setup_sfr_reach_paramet:[28,30,39,44],setup_sfr_seg_paramet:[28,30,39,44],setup_sft_ob:[28,30,39,44],setup_temporal_diff_ob:[29,30,39,44],sever:[0,9,19,22,39,42,43],sfmt:[22,24,30,39,43],sfmt_long:[22,24,39,43],sfr:[28,29,30,44],sfr_ob:[28,29,30,44],sfr_out_fil:[28,30,44],sfr_par:[29,30,44],sfr_reach_ob:[28,30,44],sfr_seg_par:[28,30,44],sfr_temporal_par:[29,30,44],sft:[28,30,44],sft_file:[28,30,44],sft_out_fil:[28,30,44],sgem:[27,30,44],shape:[9,12,13,19,27,29,30,34,38,39,40,44],shapefil:[30,33,44],shapenam:[30,33,44],share:[13,40],shell:[9,19,21,23,39,43],shift:[19,20,42],shit:[28,30,44],should:[2,7,8,9,12,13,17,18,19,20,21,23,26,27,28,29,30,32,34,35,39,40,42,43,44],shouldn:[6,7,9,19,22,39,43],show:[9,16,26,39,41],shp:[30,33,44],shutil:[30,32],side:[9,12,13,19,21,23,39,40,43],sigma:[1,4,8,9,10,12,13,19,26,30,39,40],sigma_:[8,9,39],sigma_rang:[1,4,7,8,9,10,12,13,19,21,23,26,27,29,30,34,39,40,43,44],sigma_theta:[8,9,39],silent_mast:[30,32,44],sill:[27,29,30,39,44],sim:[9,19,21,23,39,43],sim_en:[29,30,44],similar:[28,30,44],similarli:[9,19,21,23,39,43],similiar:[17,19,42],simpl:[24,29,30,32,43,44],simple_ins_from_ob:[29,30,39,44],simple_tpl_from_par:[29,30,39,44],simpli:[9,12,13,17,19,21,23,29,30,39,40,42,43,44],simul:[9,16,19,21,23,24,27,28,29,30,34,39,41,43,44],sinc:[7,9,12,13,14,19,21,22,23,28,30,33,39,40,43,44],singl:[7,9,10,12,13,16,19,21,23,26,28,29,30,32,33,34,39,40,41,43,44],singluar:[8,9,39],singular:[7,8,9,12,13,14,16,19,21,22,23,27,30,39,40,41,43,44],singular_valu:[8,9,16,39,41],site:[30,35,44],site_nam:[28,30,44],size:[16,27,29,30,32,41,44],skip:[9,17,19,21,23,27,28,29,30,34,39,42,43,44],skip_group:[16,41],skip_row:[30,34],slect:[29,30,44],slighlti:[29,30,44],slight:[9,19,21,23,39,43],slightli:[9,10,39],slow:[19,20,27,28,30,42,44],slower:[7,9,19,39],smaller:[9,12,13,19,27,30,34,39,40,44],smallest:[8,9,12,13,14,19,39,40],smoother:[7,9,17,19,27,30,39,42,44],smp:[29,30,35,44],smp_filenam:[30,35,44],smp_to_datafram:[30,35,39,44],smp_to_in:[30,35,39,44],smp_util:[5,9,30,38,39],softwar:6,sol1:[19,20,42],sol2:[19,20,42],solut:[8,9,14,19,20,27,30,39,42,44],solv:[17,19,42],some:[7,9,12,13,17,19,21,23,24,26,29,30,39,40,42,43,44],someth:[1,4,8,9,10,11,12,13,19,21,23,26,39,43],sometim:[27,30,44],somewhat:[9,19,21,23,29,30,39,43,44],sort:[6,9,12,13,19,29,30,39,40,44],sort_by_nam:[29,30,44],sought:[1,3,4,8,9,10,19,21,23,26,39,43],sound:[29,30,44],sourc:[6,29,30,34,44],space:[2,7,8,9,14,19,21,23,27,29,30,33,34,39,42,43,44],span:[16,41],spars:[12,13,40],spatail:[27,30,44],spatial:[27,28,29,30,33,34,44],spatial_bc_prop:[29,30,44],spatial_list_geostruct:[29,30,44],spatial_list_par:[29,30,44],spatial_list_prop:[29,30,44],spatial_refer:[27,29,30,34,44],spatialrefer:[27,29,30,33,34,39,44],spawn:[29,30,44],spec2dsim:[27,30,44],special:[24,28,30,43,44],specif:[7,9,29,30,34,39,44],specifi:[9,10,16,19,21,23,29,30,34,39,41,43,44],specifif:[28,30,44],specsim2d:[27,30,39,44],spectral:[27,29,30,34,44],spectrum:[8,9,12,13,19,39,40],speed:[27,29,30,44],speedup:[9,12,13,19,39,40],spheric:[27,30,44],sphinx:5,sphvario:[27,30,39,44],split:[9,19,21,23,24,29,30,34,39,43,44],spread:[30,32,44],sqradiu:[27,30],sqrt:[9,12,13,19,38,39,40],squar:[9,10,12,13,19,21,23,29,30,39,40,43,44],squential:[30,34],srefhttp:[29,30],stack:[16,41],stand:[9,19,21,23,39,43],standalon:[30,34,44],standard:[1,4,7,8,9,10,11,12,13,16,19,21,23,26,27,28,29,30,34,39,40,41,43,44],start:[9,11,14,19,21,23,24,29,30,32,34,39,43,44],start_datatim:[28,30,44],start_datetim:[28,30,34,44],start_work:[30,32,39,44],startsiwth:[9,19,21,23,39,43],stat:[9,10,19,21,23,29,30,39,43,44],state:[9,19,21,23,39,43],statement:[11,19,38,39],statist:[9,10,19,21,23,39,43],statu:[7,9,19,29,30,39,44],std:[16,41],std_window:[16,41],stdev:[16,41],stdout:[27,29,30,32,44],step:[17,19,27,28,29,30,42,44],still:[19,20,42],stochast:[7,9,14,17,19,39,42],storag:[9,12,13,19,29,30,39,40,44],store:[7,9,12,13,16,19,24,39,40,41,43],str:[1,3,4,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],str_con:[24,39,43],straight:[29,30,44],strang:[9,19,21,23,39,43],strategi:[7,9,19,39],stream:[29,30,44],stress:[9,17,19,21,23,28,29,30,39,42,43,44],strhc1:[28,30,44],string:[6,9,12,13,16,19,21,22,23,24,27,28,29,30,33,34,35,40,41,43,44],strptime:[30,35,44],struct1:[27,30,44],struct:[27,29,30,44],struct_dict:[29,30,44],struct_fil:[27,30,44],structur:[27,29,30,44],structuredgrid:[27,30,44],strutur:[29,30,44],style:[1,4,6,7,8,9,10,12,13,19,21,22,24,26,27,28,29,30,34,35,39,40,43,44],sub:[1,8,9,12,13,19,28,30,39,40,44],subdirectori:[30,32,44],submit:[17,18,19,20,42],submit_fil:[17,18,19,20,42],submodul:38,subpackag:38,subplot:[16,41],subsequ:[9,19,21,23,29,30,39,43,44],subset:[7,9,10,19,21,23,29,30,39,43,44],subset_obsgroup:[29,30,44],subset_obsnam:[29,30,44],subset_r:[29,30,44],subspac:[14,29,30,39,44],subst:6,subtract:[9,12,13,19,30,34,44],success:[9,19,21,23,30,34,39,43,44],successfulli:[24,43],suffix:[9,19,21,23,28,29,30,32,34,39,43,44],sum:[9,19,21,23,29,30,39,43,44],summar:[8,9,26,27,29,30,33,39,44],summari:[9,19,21,23,26,39,43],suppli:[27,30,44],support:[0,3,6,9,12,13,14,19,21,23,27,28,29,30,33,34,35,39,40,42,43,44],sure:[7,9,16,19,21,22,23,29,30,34,39,41,43,44],surfac:[27,30,44],svd:[7,9,12,13,17,19,22,39,40,42,43],svd_data:[9,19,21,23,38,39,43],svddata:[9,19,21,22,23,39,43],sw_filenam:[28,30,44],sw_prefix:[28,30,44],sweep:[16,18,19,29,30,41,44],sweep_output_csv_fil:[16,41],sweep_parameter_csv_fil:[16,41],sweet:[9,19,21,23,39,43],swp:[16,41],sync_bin:[16,41],synchron:[9,19,21,23,39,43],synonym:[9,10,39],system:[17,18,19,20,29,30,32,42,44],tabl:[9,19,21,23,39,43],tabular:[30,34,44],tag:[6,28,30,44],tag_prefix:6,take:[7,9,12,13,16,19,39,41],tarbal:6,target:[9,19,21,23,27,30,44],target_phi:[9,19,21,23],targetob:[16,41],task:[29,44],tbe:[16,41],tcp:[17,18,19,20,42],teh:[9,19,21,23,39,43],tempchek:[29,30,44],templat:[9,17,18,19,20,21,23,24,28,29,30,32,33,34,39,42,43,44],template_fil:[9,19,21,23,29,30,39,43,44],tempor:[17,19,30,34,42,44],temporal_bc_prop:[29,30,44],temporal_list_geostruct:[29,30,44],temporal_list_par:[29,30,44],temporal_list_prop:[29,30,44],temporal_sfr_par:[29,30,44],temporari:[29,30,44],term:[1,4,8,9,10,12,13,19,26,30,39,40],terribl:[24,43],test:[8,9,16,26,27,29,30,32,39,41,44],tex:[9,19,21,23,39,43],text:[16,28,29,30,32,41,44],tha:[16,41],than:[6,7,9,10,12,13,16,19,21,23,26,27,28,29,30,32,33,34,39,40,41,43,44],thegreenplac:[30,32],thei:[9,10,19,21,23,29,30,39,43,44],them:[28,29,30,44],therefor:[7,9,19,29,30,34,39,44],theta:[29,30,39,44],thi:[0,1,3,4,5,6,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,32,33,34,35,36,37,39,40,41,42,43,44],thin:[7,8,9,12,13,19,26,28,30,39,40,44],thing:[3,9,12,13,16,19,21,23,39,40,41,43],third:[8,9,39],third_forecast:[8,9,38,39],third_paramet:[8,9,38,39],third_predict:[8,9,38,39],those:[7,9,19,30,32,39,44],thread:[29,30,44],three:[8,9,39],threshold:[14,29,30,39,44],through:[29,30,34,44],throw_ins_error:[24,39,43],throw_ins_warn:[24,39,43],throw_out_error:[24,39,43],tie_hcond:[28,30,44],tied:[7,9,19,21,23,38,39,43],tikhonov:[24,43],time:[9,11,17,19,21,23,28,29,30,39,42,43,44],time_index:[17,19,42],timedelta:[30,34,44],timeseri:[28,30,44],timestamp:[28,30,44],tmp_file:[29,30,44],to_2d:[9,12,13,19,38,39,40],to_ascii:[9,12,13,19,26,38,39,40],to_binari:[7,9,12,13,19,27,29,30,38,39,40,44],to_coo:[9,12,13,19,29,30,38,39,40,44],to_csv:[7,9,19,29,30,38,39,44],to_datafram:[9,12,13,19,38,39,40],to_datetim:[28,30,44],to_grid_factor_fil:[27,30,44],to_grid_factors_fil:[27,30,39,44],to_pearson:[9,12,13,19,30,38,39,40],to_struct_fil:[27,30,39,44],to_uncfil:[9,12,13,19,30,38,39,40],todo:[30,34,44],togeth:[28,30,44],tol:[27,30,44],toler:[9,12,13,19,21,23,27,29,30,39,40,43,44],ton:[28,30,44],too:[29,30,44],total:[9,10,19,21,23,39,43],totim:[28,30,44],tpl:[9,17,19,21,23,24,28,29,30,33,34,39,42,43,44],tpl_dir:[29,30,33,44],tpl_file:[9,19,21,23,24,28,29,30,33,39,43,44],tpl_filenam:[30,33,34,44],tpl_marker:[29,30],tpl_str:[30,33,44],tplfilenam:[29,30,44],tradtion:[29,30,44],trail:[27,30,44],transfer:[9,10,29,30,39,44],transform:[7,9,19,21,23,27,29,30,34,38,39,43,44],transgress:[17,19,42],transpar:[16,41],transport:[28,30,44],transpos:[9,12,13,19,38,39,40],trash:[9,12,13,19,39,40],travi:[30,32,44],treat:[1,2,7,8,9,16,19,21,23,26,28,29,30,33,34,39,41,43,44],tree:6,tri:[9,10,12,13,19,21,23,24,29,30,39,40,43,44],triangular:[7,9,19,39],truncat:[8,9,12,13,19,39,40],try_parse_name_metadata:[9,19,21,23,38,39,43],try_process_output_fil:[24,30,39,43],try_process_output_pst:[24,39,43],tupl:[9,12,13,16,19,21,23,28,29,30,34,39,40,41,43,44],two:[6,9,12,13,16,17,19,27,28,29,30,39,40,41,42,44],txt:[29,30,44],typ:[7,9,30,34,39],type:[7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],typic:[16,41],u2d:[29,30],u_1:[8,9,39],ubnd:[7,9,19,38,39],ubound:[30,34,44],ucn:[28,30,44],ucnfil:[28,30,44],uint8:[39,40],ult_lbound:[30,34,44],ult_ubound:[30,34,44],ultim:[9,16,19,21,23,29,30,34,39,41,43,44],unabl:6,unari:[16,41],unc:[1,4,8,9,10,12,13,19,26,30,39,40],unc_fil:[9,12,13,19,30,39,40],uncert:[9,10],uncertainti:[0,1,4,8,9,10,12,13,19,20,26,27,29,30,39,40,42,44],uncfil:[29,30,44],uncondit:[6,27,30,44],undefin:[9,12,13,19,30,39,40,44],under:[1,4,8,9,20,26,39,42],underli:[7,9],underscor:[9,19,21,23,39,43],undertak:[27,30,44],unformat:[28,30,44],uniform:[7,9,14,19,29,30,39,44],union:[16,41],uniqu:[9,16,19,21,23,27,28,29,30,34,39,41,43,44],unit:[28,29,30,39,44],unknown:[27,30,44],unless:[1,4,8,9,19,21,23,26,27,30,32,39,43,44],unpack:6,unqiue_onli:[16,41],unstabl:[27,30,44],untag:6,until:[9,10,39],untransform:[7,9,19,27,30,39,44],updat:[7,9,17,18,19,20,21,23,26,30,34,39,42,43,44],upgrad:[14,39],upper:[1,4,7,8,9,10,12,13,19,21,23,26,29,30,34,39,40,43,44],upper_bound:[29,30,34,44],upper_lim:[27,30,44],uppermost:[30,32,44],upw:[29,30,44],use:[7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,32,33,34,35,39,40,41,42,43,44],use_approx:[18,19,42],use_approx_prior:[18,19,20,42],use_col:[29,30,34,44],use_generic_nam:[30,35,44],use_ibound_zon:[30,33,44],use_pp_zon:[29,30,34,44],use_pst_path:[9,19,21,23],use_row:[30,34,44],use_specsim:[29,30,34,44],used:[0,1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],useful:[9,14,16,17,19,21,23,28,29,30,32,34,39,41,42,43,44],usefulness:[9,19,21,23,39,43],user:[7,9,12,13,17,19,21,22,23,24,26,29,30,32,34,39,40,42,43,44],uses:[7,9,12,13,19,21,23,24,29,30,32,34,39,40,43,44],usg:[28,29,30,44],using:[1,4,7,8,9,10,12,13,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,39,40,41,42,43,44],usual:[9,10,16,19,21,23,27,30,32,39,41,44],usum:[16,41],utiil:[27,30,44],util2d:[29,30],util:[4,5,9,10,19,21,23,26,38,39,43],v2_proj:[14,39],v2v2:[8,9,14,39],v_1:[8,9,12,13,19,39,40],val:[7,9,19,21,23,29,30,34,39,43,44],valid:[6,7,9,19,30,34,39,44],valu:[2,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,33,34,35,39,40,41,42,43,44],value_col:[30,35,44],value_format:[30,35,44],var1:[27,30,44],var_filenam:[27,30,44],var_mult:[9,12,13,19,30,39,40],varainc:[9,12,13,19,30,39,40],vari:[1,4,8,9,10,16,26,28,29,30,34,39,41,44],variabl:[7,9,19,21,22,23,28,29,30,39,43,44],varianc:[1,4,8,9,10,12,13,19,26,27,29,30,34,39,40,44],variance_at:[8,9,38,39],variat:[27,30,44],vario2d:[27,30,39,44],variogram:[27,30,39,44],variou:[9,15,16,19,21,23,24,36,37,39,41,43],vcs:6,vec:[9,10],vector:[1,4,7,8,9,10,12,13,17,19,26,27,29,30,39,40,42,44],verbos:[1,4,6,8,9,10,17,18,19,20,24,26,27,29,30,32,39,42,43,44],veri:[9,10,12,13,19,29,30,39,44],version:[6,9,19,21,23,29,30,34,39,43,44],versioneerconfig:6,versionfile_ab:6,versions_from_parentdir:6,vertic:[16,29,30,39,41,44],via:[29,30,44],view:[16,41],violat:[7,9,19,21,23,39,43],vka:[29,30,44],vol:[28,30,44],vol_fil:[28,30,44],vol_filenam:[28,30,44],volum:[28,29,30,44],wai:[7,9,19,24,26,39,43],wait:[17,19,42],want:[1,4,8,9,26,29,30,32,39,44],warn:[11,16,19,24,25,27,29,30,34,38,39,41,43,44],water:[28,29,30,44],wave:[27,30,44],weigh:[9,26,39],weight:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,29,30,39,40,41,42,43,44],wel1:[1,8,9,28,30,39,44],wel2:[1,8,9,39],wel:[29,30,44],well:[3,9,17,19,21,23,29,30,39,42,43,44],were:[6,9,19,21,23,28,30,34,39,43,44],what:[9,19,21,23,24,39,43],whatev:[16,41],when:[1,4,8,9,10,24,26,28,29,30,34,39,43,44],where:[7,9,14,19,21,23,24,26,27,28,29,30,32,34,39,43,44],whether:[7,9,27,29,30,32,39,44],which:[7,9,10,12,13,16,19,20,21,23,27,28,29,30,34,35,39,40,41,42,43,44],whitespac:[24,30,35,44],who:[9,12,13,19,30,32,39,40,44],whole:[29,30,44],wholesal:[29,30,44],why:[16,41],wick:[29,30,44],width:[7,9,19,22,29,30,39,43,44],wildass_guess_par_bounds_dict:[29,30,44],wilei:[27,30,44],window:[16,17,19,30,32,41,42],wise:[8,9,10,12,13,19,39,40],with_metadata:[29,30,44],within:[16,29,30,32,34,41,44],without:[16,24,29,30,34,41,43,44],wmax:[9,19,21,23,39,43],work:[0,9,12,13,16,19,21,22,23,28,29,30,39,40,41,42,43,44],worker:[17,18,19,20,27,30,32,42,44],worker_dir:[17,18,19,20,30,32,42,44],worker_root:[30,32,44],workspac:[28,30,44],world:[29,30,34,44],worri:[17,19,42],worth:[0,4,9,19,21,23,26,39],would:[9,16,26,29,30,32,33,34,39,41,44],wrap:[30,44],wrapper:[7,8,9,12,13,19,26,27,28,30,34,39,40,44],write:[3,7,9,11,12,13,14,19,21,22,23,24,27,28,29,30,33,34,35,38,39,40,43,44],write_array_tpl:[30,34,39,44],write_const_tpl:[29,30,39,44],write_forward_run:[29,30,34,39,44],write_grid_tpl:[29,30,39,44],write_gridspec:[29,30,39,44],write_hfb_templ:[28,29,30,39,44],write_hfb_zone_multipliers_templ:[28,30,39,44],write_input_fil:[9,19,21,23,24,38,39,43],write_keyword:[21,22,39,43],write_list_tpl:[30,34,39,44],write_obs_summary_t:[9,19,21,23,38,39,43],write_par_summary_t:[9,19,21,23,38,39,43],write_parfil:[24,39,43],write_pp_fil:[30,33,39,44],write_pp_shapfil:[30,33,39,44],write_pst:[14,38,39],write_to_templ:[24,39,43],write_zone_tpl:[29,30,39,44],writen:[30,34,44],written:[1,4,8,9,10,11,12,13,19,21,23,24,26,28,29,30,33,39,40,43,44],writtendefault:[9,19,21,23,39,43],wrt:[13,40],www:[29,30,44],x_a:[27,30,44],x_idx:[27,30,44],xcenter:[29,30,39,44],xcentergrid:[29,30,39,44],xedg:[29,30,39,44],xgrid:[29,30,39,44],xlim:[16,41],xll:[29,30,39,44],xmax:[29,30,44],xmin:[29,30,44],xml:[27,30,44],xml_file:[27,30,44],xorigin:[29,30,44],xother:[27,30,44],xtqt:[8,9,39],xtqx:[8,9,10,14,38,39],xul:[29,30,39,44],xy_in_idx:[30,34,44],y_idx:[27,30,44],y_z:[27,30,44],ycenter:[29,30,39,44],ycentergrid:[29,30,39,44],yeah:[9,19,21,23,39,43],year:[29,30,44],yedg:[29,30,39,44],yet:[9,10,19,21,23,26,30,34,39,43,44],ygrid:[29,30,39,44],yield:[7,9,12,13,19,26,27,28,30,39,40,44],yll:[29,30,39,44],ymax:[29,30,44],ymin:[29,30,44],yorigin:[29,30,44],yother:[27,30,44],you:[1,4,6,8,9,17,19,21,23,26,29,30,32,35,39,42,43,44],your:[29,30,32,44],yuck:[28,29,30,44],yul:[29,30,39,44],yyi:[30,35,44],yyyi:[30,35,44],zero2d:[9,12,13,19,38,39,40],zero:[7,8,9,10,12,13,16,19,21,23,24,26,28,29,30,32,33,34,38,39,40,41,43,44],zero_bas:[30,34,44],zero_order_tikhonov:[29,30,39,44],zero_weight_obs_nam:[9,19,21,23,38,39,43],zn_arrai:[29,30,44],zone:[27,28,29,30,33,34,44],zone_arrai:[27,30,34,44],zone_fil:[27,30,44],zone_prop:[29,30,44],zval:[30,34]},titles:["pyemu","pyemu.ErrVar","pyemu.ParameterEnsemble","pyemu.Pst","pyemu.Schur","API Reference","pyemu._version","pyemu.en","pyemu.ev","pyemu","pyemu.la","pyemu.logger","pyemu.mat","pyemu.mat.mat_handler","pyemu.mc","pyemu.plot","pyemu.plot.plot_utils","pyemu.prototypes.da","pyemu.prototypes.ensemble_method","pyemu.prototypes","pyemu.prototypes.moouu","pyemu.pst","pyemu.pst.pst_controldata","pyemu.pst.pst_handler","pyemu.pst.pst_utils","pyemu.pyemu_warnings","pyemu.sc","pyemu.utils.geostats","pyemu.utils.gw_utils","pyemu.utils.helpers","pyemu.utils","pyemu.utils.optimization","pyemu.utils.os_utils","pyemu.utils.pp_utils","pyemu.utils.pst_from","pyemu.utils.smp_utils","Welcome to pyEMU\u2019s documentation!","Welcome to pyEMU\u2019s documentation!","pyemu","pyemu package","pyemu.mat package","pyemu.plot package","pyemu.prototypes package","pyemu.pst package","pyemu.utils package"],titleterms:{"class":[6,7,8,9,10,11,12,13,14,17,18,19,20,21,22,23,24,26,27,29,30,34],"function":[6,12,13,16,24,27,28,29,30,31,32,33,34,35],_version:6,api:5,content:[6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,39,40,41,42,43,44],document:[36,37],ensemble_method:[18,42],errvar:1,geostat:[27,44],gw_util:[28,44],helper:[29,44],indic:[36,37],logger:[11,39],mat:[12,13,40],mat_handl:[13,40],modul:[6,7,8,10,11,13,14,16,17,18,20,22,23,24,25,26,27,28,29,31,32,33,34,35,39,40,41,42,43,44],moouu:[20,42],optim:[31,44],os_util:[32,44],packag:[9,12,19,21,30,39,40,41,42,43,44],parameterensembl:2,plot:[15,16,41],plot_util:[16,41],pp_util:[33,44],prototyp:[17,18,19,20,42],pst:[3,21,22,23,24,43],pst_controldata:[22,43],pst_from:[34,44],pst_handler:[23,43],pst_util:[24,43],pyemu:[0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],pyemu_warn:[25,39],refer:5,schur:4,smp_util:[35,44],submodul:[9,12,15,19,21,30,39,40,41,42,43,44],subpackag:[9,39],tabl:[36,37],util:[27,28,29,30,31,32,33,34,35,44],welcom:[36,37]}}) \ No newline at end of file diff --git a/docs/_build/html/source/Monte_carlo.html b/docs/_build/html/source/Monte_carlo.html deleted file mode 100644 index 2261e4a68..000000000 --- a/docs/_build/html/source/Monte_carlo.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - - Run Monte Carlo Simulations from PEST Object — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Run Monte Carlo Simulations from PEST Object

    -

    pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -SWEEP.EXE to generate results.

    -

    Workflow Overview

    -

    blah blah blah

    -

    Module Documentation

    -

    pyEMU Monte Carlo module. Supports easy Monte Carlo -and GLUE analyses. The MonteCarlo class inherits from -pyemu.LinearAnalysis

    -
    -
    -class mc.MonteCarlo(**kwargs)[source]
    -

    LinearAnalysis derived type for monte carlo analysis

    - --- - - - -
    Parameters:**kwargs (dict) – dictionary of keyword arguments. See pyemu.LinearAnalysis for -complete definitions
    -
    -
    -parensemble
    -

    pyemu.ParameterEnsemble

    -
    - -
    -
    -obsensemble
    -

    pyemu.ObservationEnsemble

    -
    - - --- - - - - - -
    Returns:MonteCarlo
    Return type:MonteCarlo
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -
    -
    -num_reals
    -

    get the number of realizations in the parameter ensemble

    - --- - - - - - -
    Returns:num_real
    Return type:int
    -
    - -
    -
    -get_nsing(epsilon=0.0001)[source]
    -
    -
    get the number of solution space dimensions given
    -
    a ratio between the largest and smallest singular -values
    -
    - --- - - - - - - - -
    Parameters:epsilon (float) – singular value ratio
    Returns:nsing – number of singular components above the epsilon ratio threshold
    Return type:float
    -
    -

    Note

    -

    If nsing == nadj_par, then None is returned

    -
    -
    - -
    -
    -get_null_proj(nsing=None)[source]
    -

    get a null-space projection matrix of XTQX

    - --- - - - - - - - -
    Parameters:nsing (int) – optional number of singular components to use -If Nonte, then nsing is determined from -call to MonteCarlo.get_nsing()
    Returns:v2_proj – the null-space projection matrix (V2V2^T)
    Return type:pyemu.Matrix
    -
    - -
    -
    -draw(num_reals=1, par_file=None, obs=False, enforce_bounds=None, cov=None, how='gaussian')[source]
    -
    -
    draw stochastic realizations of parameters and
    -
    optionally observations, filling MonteCarlo.parensemble and -optionally MonteCarlo.obsensemble.
    -
    - --- - - - -
    Parameters:
      -
    • num_reals (int) – number of realization to generate
    • -
    • par_file (str) – parameter file to use as mean values. If None, -use MonteCarlo.pst.parameter_data.parval1. -Default is None
    • -
    • obs (bool) – add a realization of measurement noise to observation values, -forming MonteCarlo.obsensemble.Default is False
    • -
    • enforce_bounds (str) – enforce parameter bounds based on control file information. -options are ‘reset’, ‘drop’ or None. Default is None
    • -
    • how (str) – type of distribution to draw from. Must be in [“gaussian”,”uniform”] -default is “gaussian”.
    • -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -

    >>>mc.draw(1000)

    -
    - -
    -
    -project_parensemble(par_file=None, nsing=None, inplace=True, enforce_bounds='reset')[source]
    -

    perform the null-space projection operations for null-space monte carlo

    - --- - - - - - - - -
    Parameters:
      -
    • par_file (str) – an optional file of parameter values to use
    • -
    • nsing (int) – number of singular values to in forming null subspace matrix
    • -
    • inplace (bool) – overwrite the existing parameter ensemble with the -projected values
    • -
    • enforce_bounds (str) – how to enforce parameter bounds. can be None, ‘reset’, or ‘drop’. -Default is None
    • -
    -
    Returns:

    par_en – if inplace is False, otherwise None

    -
    Return type:

    pyemu.ParameterEnsemble

    -
    -
    -

    Note

    -

    to use this method, the MonteCarlo instance must have been constructed -with the jco argument.

    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000)

    -

    >>>mc.project_parensemble(par_file="final.par",nsing=100)

    -
    - -
    -
    -write_psts(prefix, existing_jco=None, noptmax=None)[source]
    -
    -
    write parameter and optionally observation realizations
    -
    to a series of pest control files
    -
    - --- - - - -
    Parameters:
      -
    • prefix (str) – pest control file prefix
    • -
    • existing_jco (str) – filename of an existing jacobian matrix to add to the -pest++ options in the control file. This is useful for -NSMC since this jco can be used to get the first set of -parameter upgrades for free! Needs to be the path the jco -file as seen from the location where pest++ will be run
    • -
    • noptmax (int) – value of NOPTMAX to set in new pest control files
    • -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000, obs=True)

    -

    >>>mc.write_psts("mc_", existing_jco="pest.jcb", noptmax=1)

    -
    - -
    - -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/Monte_carlo_page.html b/docs/_build/html/source/Monte_carlo_page.html deleted file mode 100644 index 2bdd03d8b..000000000 --- a/docs/_build/html/source/Monte_carlo_page.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - Run Monte Carlo Simulations from PEST Object — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Run Monte Carlo Simulations from PEST Object

    -

    pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -SWEEP.EXE to generate results.

    -

    Workflow Overview

    -

    blah blah blah Uses Ensembles.

    -

    Inheritance

    -Inheritance diagram of pyemu.mc - - - -

    Module Documentation

    -

    pyEMU Monte Carlo module. Supports easy Monte Carlo -and GLUE analyses. The MonteCarlo class inherits from -pyemu.LinearAnalysis

    -
    -
    -class pyemu.mc.MonteCarlo(**kwargs)[source]
    -

    Bases: pyemu.la.LinearAnalysis

    -

    LinearAnalysis derived type for monte carlo analysis

    - --- - - - -
    Parameters:**kwargs (dict) – dictionary of keyword arguments. See pyemu.LinearAnalysis for -complete definitions
    -
    -
    -parensemble
    -

    pyemu.ParameterEnsemble – pyemu object derived from a pandas dataframe, the ensemble -of parameters from the PEST control file with associated -starting value and bounds. Object also exposes methods -relevant to the dataframe and parameters– see documentation.

    -
    - -
    -
    -obsensemble
    -

    pyemu.ObservationEnsemble – pyemu object derived from a pandas dataframe, the ensemble -of observations from the PEST control file with associated -starting weights. Object also exposes methods -relevant to the dataframe and observations– see documentation.

    -
    - - --- - - - - - -
    Returns:pyEMU MonteCarlo object
    Return type:MonteCarlo
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -
    -
    -
    -num_reals
    -

    get the number of realizations in the parameter ensemble

    - --- - - - - - -
    Returns:num_real
    Return type:int
    -
    - -
    -
    -get_nsing(epsilon=0.0001)[source]
    -

    get the number of solution space dimensions given -a ratio between the largest and smallest singular values

    - --- - - - - - - - -
    Parameters:epsilon (float) – singular value ratio
    Returns:nsing – number of singular components above the epsilon ratio threshold
    Return type:float
    -
    -

    Note

    -

    If nsing == nadj_par, then None is returned

    -
    -
    - -
    -
    -get_null_proj(nsing=None)[source]
    -

    get a null-space projection matrix of XTQX

    - --- - - - - - - - -
    Parameters:nsing (int) – optional number of singular components to use -If Nonte, then nsing is determined from -call to MonteCarlo.get_nsing()
    Returns:v2_proj – the null-space projection matrix (V2V2^T)
    Return type:pyemu.Matrix
    -
    - -
    -
    -draw(num_reals=1, par_file=None, obs=False, enforce_bounds=None, cov=None, how='gaussian')[source]
    -
    -
    draw stochastic realizations of parameters and
    -
    optionally observations, filling MonteCarlo.parensemble and -optionally MonteCarlo.obsensemble.
    -
    - --- - - - -
    Parameters:
      -
    • num_reals (int) – number of realization to generate
    • -
    • par_file (str) – parameter file to use as mean values. If None, -use MonteCarlo.pst.parameter_data.parval1. -Default is None
    • -
    • obs (bool) – add a realization of measurement noise to observation values, -forming MonteCarlo.obsensemble.Default is False
    • -
    • enforce_bounds (str) – enforce parameter bounds based on control file information. -options are ‘reset’, ‘drop’ or None. Default is None
    • -
    • how (str) – type of distribution to draw from. Must be in [“gaussian”,”uniform”] -default is “gaussian”.
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -

    >>>mc.draw(1000)

    -
    -
    - -
    -
    -project_parensemble(par_file=None, nsing=None, inplace=True, enforce_bounds='reset')[source]
    -

    perform the null-space projection operations for null-space monte carlo

    - --- - - - - - - - -
    Parameters:
      -
    • par_file (str) – an optional file of parameter values to use
    • -
    • nsing (int) – number of singular values to in forming null subspace matrix
    • -
    • inplace (bool) – overwrite the existing parameter ensemble with the -projected values
    • -
    • enforce_bounds (str) – how to enforce parameter bounds. can be None, ‘reset’, or ‘drop’. -Default is None
    • -
    -
    Returns:

    par_en – if inplace is False, otherwise None

    -
    Return type:

    pyemu.ParameterEnsemble

    -
    -
    -

    Note

    -

    to use this method, the MonteCarlo instance must have been constructed -with the jco argument.

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000)

    -

    >>>mc.project_parensemble(par_file="final.par",nsing=100)

    -
    -
    - -
    -
    -write_psts(prefix, existing_jco=None, noptmax=None)[source]
    -
    -
    write parameter and optionally observation realizations
    -
    to a series of pest control files
    -
    - --- - - - -
    Parameters:
      -
    • prefix (str) – pest control file prefix
    • -
    • existing_jco (str) – filename of an existing jacobian matrix to add to the -pest++ options in the control file. This is useful for -NSMC since this jco can be used to get the first set of -parameter upgrades for free! Needs to be the path the jco -file as seen from the location where pest++ will be run
    • -
    • noptmax (int) – value of NOPTMAX to set in new pest control files
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000, obs=True)

    -

    >>>mc.write_psts("mc_", existing_jco="pest.jcb", noptmax=1)

    -
    -
    - -
    - -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/ensembles.html b/docs/_build/html/source/ensembles.html deleted file mode 100644 index b8c722aaa..000000000 --- a/docs/_build/html/source/ensembles.html +++ /dev/null @@ -1,1057 +0,0 @@ - - - - - - - - Ensembles — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Ensembles

    -

    pyEMU ensembles is a base class with derived..bbb

    -

    Inheritance

    -Inheritance diagram of pyemu.en - - - - -

    Module Documentation

    -
    -
    -class pyemu.en.Ensemble(*args, **kwargs)[source]
    -

    Bases: pandas.core.frame.DataFrame

    -
    -
    The base class type for handling parameter and observation ensembles.
    -
    It is directly derived from pandas.DataFrame. This class should not be -instantiated directly.
    -
    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame(). Must contain -‘columns’ and ‘mean_values’
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    -
    -as_pyemu_matrix(typ=<class 'pyemu.mat.mat_handler.Matrix'>)[source]
    -

    Create a pyemu.Matrix from the Ensemble.

    - --- - - - - - - - -
    Parameters:typ (pyemu.Matrix or derived type) – the type of matrix to return
    Returns:pyemu.Matrix
    Return type:pyemu.Matrix
    -
    - -
    -
    -drop(arg)[source]
    -

    overload of pandas.DataFrame.drop()

    - --- - - - - - - - -
    Parameters:arg (iterable) – argument to pass to pandas.DataFrame.drop()
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -dropna(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.dropna()

    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame.dropna()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame.dropna()
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    - -
    -
    -draw(cov, num_reals=1, names=None)[source]
    -
    -
    draw random realizations from a multivariate
    -
    Gaussian distribution
    -
    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance structure to draw from
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • names (list) – list of columns names to draw for. If None, values all names -are drawn
    • -
    -
    -
    - -
    -
    -plot(bins=10, facecolor='0.5', plot_cols=None, filename='ensemble.pdf', func_dict=None, **kwargs)[source]
    -

    plot ensemble histograms to multipage pdf

    - --- - - - - - - - -
    Parameters:
      -
    • bins (int) – number of bins
    • -
    • facecolor (str) – color
    • -
    • plot_cols (list of str) – subset of ensemble columns to plot. If None, all are plotted. -Default is None
    • -
    • filename (str) – pdf filename. Default is “ensemble.pdf”
    • -
    • func_dict (dict) – a dict of functions to apply to specific columns (e.g., np.log10)
    • -
    • **kwargs (dict) – keyword args to pass to plot_utils.ensemble_helper()
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -classmethod from_dataframe(**kwargs)[source]
    -

    class method constructor to create an Ensemble from -a pandas.DataFrame

    - --- - - - - - - - -
    Parameters:**kwargs (dict) – optional args to pass to the -Ensemble Constructor. Expects ‘df’ in kwargs.keys() -that must be a pandas.DataFrame instance
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -static reseed()[source]
    -

    method to reset the numpy.random seed using the pyemu.en -SEED global variable

    -
    - -
    -
    -copy()[source]
    -

    make a deep copy of self

    - --- - - - - - -
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -covariance_matrix(localizer=None)[source]
    -

    calculate the approximate covariance matrix implied by the ensemble using -mean-differencing operation at the core of EnKF

    - --- - - - - - - - -
    Parameters:localizer (pyemu.Matrix) – covariance localizer to apply
    Returns:cov – covariance matrix
    Return type:pyemu.Cov
    -
    - -
    - -
    -
    -class pyemu.en.ObservationEnsemble(pst, **kwargs)[source]
    -

    Bases: pyemu.en.Ensemble

    -

    Ensemble derived type for observations. This class is primarily used to -generate realizations of observation noise. These are typically generated from -the weights listed in the control file. However, a general covariance matrix can -be explicitly used.

    -
    -

    Note

    -

    Does not generate noise realizations for observations with zero weight

    -
    -
    -
    -copy()[source]
    -

    overload of Ensemble.copy()

    - --- - - - - - -
    Returns:ObservationEnsemble
    Return type:ObservationEnsemble
    -
    - -
    -
    -names
    -

    property decorated method to get current non-zero weighted -column names. Uses ObservationEnsemble.pst.nnz_obs_names

    - --- - - - - - -
    Returns:list – non-zero weight observation names
    Return type:list
    -
    - -
    -
    -mean_values
    -

    property decorated method to get mean values of observation noise. -This is a zero-valued pandas.Series

    - --- - - - - - -
    Returns:mean_values
    Return type:pandas Series
    -
    - -
    -
    -draw(cov, num_reals)[source]
    -

    draw realizations of observation noise and add to mean_values -Note: only draws noise realizations for non-zero weighted observations -zero-weighted observations are set to mean value for all realizations

    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance matrix that describes the support volume around the -mean values.
    • -
    • num_reals (int) – number of realizations to draw
    • -
    -
    -
    - -
    -
    -nonzero
    -

    property decorated method to get a new ObservationEnsemble -of only non-zero weighted observations

    - --- - - - - - -
    Returns:ObservationEnsemble
    Return type:ObservationEnsemble
    -
    - -
    -
    -classmethod from_id_gaussian_draw(pst, num_reals)[source]
    -

    this is an experiemental method to help speed up independent draws -for a really large (>1E6) ensemble sizes.

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • num_reals (int) – number of realizations to draw
    • -
    -
    Returns:

    ObservationEnsemble

    -
    Return type:

    ObservationEnsemble

    -
    -
    - -
    -
    -to_binary(filename)[source]
    -

    write the observation ensemble to a jco-style binary file. The -ensemble is transposed in the binary file so that the 20-char obs -names are carried

    - --- - - - - - - - -
    Parameters:filename (str) – the filename to write
    Returns:
    Return type:None
    -
    -

    Note

    -

    The ensemble is transposed in the binary file

    -
    -
    - -
    -
    -classmethod from_binary(pst, filename)[source]
    -

    instantiate an observation obsemble from a jco-type file

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a Pst instance
    • -
    • filename (str) – the binary file name
    • -
    -
    Returns:

    oe

    -
    Return type:

    ObservationEnsemble

    -
    -
    - -
    -
    -phi_vector
    -

    property decorated method to get a vector of L2 norm (phi) -for the realizations. The ObservationEnsemble.pst.weights can be -updated prior to calling this method to evaluate new weighting strategies

    - --- - - - - - -
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -add_base()[source]
    -

    add “base” control file values as a realization

    -
    - -
    - -
    -
    -class pyemu.en.ParameterEnsemble(pst, istransformed=False, **kwargs)[source]
    -

    Bases: pyemu.en.Ensemble

    -
    -
    Ensemble derived type for parameters
    -
    implements bounds enforcement, log10 transformation, -fixed parameters and null-space projection -Note: uses the parnme attribute of Pst.parameter_data from column names -and uses the parval1 attribute of Pst.parameter_data as mean values
    -
    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – The ‘columns’ and ‘mean_values’ args need for Ensemble -are derived from the pst.parameter_data.parnme and pst.parameter_data.parval1 -items, respectively
    • -
    • istransformed (bool) – flag indicating the transformation status (log10) of the arguments be passed
    • -
    • **kwargs (dict) – keyword arguments to pass to Ensemble constructor.
    • -
    • bound_tol (float) – fractional amount to reset bounds transgression within the bound. -This has been shown to be very useful for the subsequent recalibration -because it moves parameters off of their bounds, so they are not treated as frozen in -the upgrade calculations. defaults to 0.0
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    -
    -dropna(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.dropna()

    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame.dropna()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame.dropna()
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    - -
    -
    -copy()[source]
    -

    overload of Ensemble.copy()

    - --- - - - - - -
    Returns:ParameterEnsemble
    Return type:ParameterEnsemble
    -
    - -
    -
    -istransformed
    -

    property decorated method to get the current -transformation status of the ParameterEnsemble

    - --- - - - - - -
    Returns:istransformed
    Return type:bool
    -
    - -
    -
    -mean_values
    -

    the mean value vector while respecting log transform

    - --- - - - - - -
    Returns:mean_values
    Return type:pandas.Series
    -
    - -
    -
    -names
    -

    Get the names of the parameters in the ParameterEnsemble

    - --- - - - - - -
    Returns:list – parameter names
    Return type:list
    -
    - -
    -
    -adj_names
    -

    Get the names of adjustable parameters in the ParameterEnsemble

    - --- - - - - - -
    Returns:list – adjustable parameter names
    Return type:list
    -
    - -
    -
    -ubnd
    -

    the upper bound vector while respecting log transform

    - --- - - - - - -
    Returns:ubnd
    Return type:pandas.Series
    -
    - -
    -
    -lbnd
    -

    the lower bound vector while respecting log transform

    - --- - - - - - -
    Returns:lbnd
    Return type:pandas.Series
    -
    - -
    -
    -log_indexer
    -

    indexer for log transform

    - --- - - - - - -
    Returns:log_indexer
    Return type:pandas.Series
    -
    - -
    -
    -fixed_indexer
    -

    indexer for fixed status

    - --- - - - - - -
    Returns:fixed_indexer
    Return type:pandas.Series
    -
    - -
    -
    -draw(cov, num_reals=1, how='normal', enforce_bounds=None)[source]
    -

    draw realizations of parameter values

    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance matrix that describes the support around -the mean parameter values
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • how (str) – distribution to use to generate realizations. Options are -‘normal’ or ‘uniform’. Default is ‘normal’. If ‘uniform’, -cov argument is ignored
    • -
    • enforce_bounds (str) – how to enforce parameter bound violations. Options are -‘reset’ (reset individual violating values), ‘drop’ (drop realizations -that have one or more violating values. Default is None (no bounds enforcement)
    • -
    -
    -
    - -
    -
    -classmethod from_uniform_draw(pst, num_reals)[source]
    -

    this is an experiemental method to help speed up uniform draws -for a really large (>1E6) ensemble sizes. WARNING: this constructor -transforms the pe argument

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • num_reals (int) – number of realizations to generate
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_sparse_gaussian_draw(pst, cov, num_reals)[source]
    -

    instantiate a parameter ensemble from a sparse covariance matrix. -This is an advanced user method that assumes you know what you are doing -- few guard rails…

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • cov ((pyemu.SparseMatrix)) – sparse covariance matrix to use for drawing
    • -
    • num_reals (int) – number of realizations to generate
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_gaussian_draw(pst, cov, num_reals=1, use_homegrown=True, group_chunks=False, fill_fixed=True, enforce_bounds=False)[source]
    -

    instantiate a parameter ensemble from a covariance matrix

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • cov ((pyemu.Cov)) – covariance matrix to use for drawing
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • use_homegrown (bool) – flag to use home-grown full cov draws…much faster -than numpy…
    • -
    • group_chunks (bool) – flag to break up draws by par groups. Only applies -to homegrown, full cov case. Default is False
    • -
    • fill_fixed (bool) – flag to fill in fixed parameters from the pst into the -ensemble using the parval1 from the pst. Default is True
    • -
    • enforce_bounds (bool) – flag to enforce parameter bounds from the pst. realized -parameter values that violate bounds are simply changed to the -value of the violated bound. Default is False
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_binary(pst, filename)[source]
    -

    instantiate an parameter obsemble from a jco-type file

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a Pst instance
    • -
    • filename (str) – the binary file name
    • -
    -
    Returns:

    pe

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -project(projection_matrix, inplace=True, log=None, enforce_bounds='reset')[source]
    -

    project the ensemble using the null-space Monte Carlo method

    - --- - - - - - - - -
    Parameters:
      -
    • projection_matrix (pyemu.Matrix) – projection operator - must already respect log transform
    • -
    • inplace (bool) – project self or return a new ParameterEnsemble instance
    • -
    • log (pyemu.Logger) – for logging progress
    • -
    • enforce_bounds (str) – parameter bound enforcement flag. ‘drop’ removes -offending realizations, ‘reset’ resets offending values
    • -
    -
    Returns:

    ParameterEnsemble – if inplace is False

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -enforce(enforce_bounds='reset')[source]
    -

    entry point for bounds enforcement. This gets called for the -draw method(s), so users shouldn’t need to call this

    - --- - - - -
    Parameters:enforce_bounds (str) – can be ‘reset’ to reset offending values or ‘drop’ to drop -offending realizations
    -
    - -
    -
    -enfore_scale()[source]
    -

    enforce parameter bounds on the ensemble by finding the -scaling factor needed to bring the most violated parameter back in bounds

    -
    -

    Note

    -

    this method not fully implemented.

    -
    - --- - - - -
    Raises:NotImplementedError if called.
    -
    - -
    -
    -enforce_drop()[source]
    -

    enforce parameter bounds on the ensemble by dropping -violating realizations

    -
    - -
    -
    -enforce_reset()[source]
    -

    enforce parameter bounds on the ensemble by resetting -violating vals to bound

    -
    - -
    -
    -read_parfiles_prefix(prefix)[source]
    -

    thin wrapper around read_parfiles using the pnulpar prefix concept. Used to -fill ParameterEnsemble from PEST-type par files

    - --- - - - -
    Parameters:prefix (str) – the par file prefix
    -
    - -
    -
    -read_parfiles(parfile_names)[source]
    -

    read the ParameterEnsemble realizations from par files. Used to fill -the ParameterEnsemble with realizations from PEST-type par files

    - --- - - - -
    Parameters:parfile_names (list) – list of par files to load
    -
    -

    Note

    -

    log transforms after loading according and possibly resets -self.__istransformed

    -
    -
    - -
    -
    -classmethod from_parfiles(pst, parfile_names, real_names=None)[source]
    -

    create a parameter ensemble from parfiles. Accepts parfiles with less than the -parameters in the control (get NaNs in the ensemble) or extra parameters in the -parfiles (get dropped)

    - --- - - - - - -
    Parameters:
      -
    • pst – pyemu.Pst
    • -
    • parfile_names – list of str -par file names
    • -
    • real_names – str -optional list of realization names. If None, a single integer counter is used
    • -
    -
    Returns:

    pyemu.ParameterEnsemble

    -
    -
    - -
    -
    -to_csv(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.to_csv() to account -for parameter transformation so that the saved -ParameterEnsemble csv is not in Log10 space

    - --- - - - -
    Parameters:
      -
    • *args (list) – positional arguments to pass to pandas.DataFrame.to_csv()
    • -
    • **kwrags (dict) – keyword arguments to pass to pandas.DataFrame.to_csv()
    • -
    -
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -to_binary(filename)[source]
    -

    write the parameter ensemble to a jco-style binary file

    - --- - - - - - - - -
    Parameters:filename (str) – the filename to write
    Returns:
    Return type:None
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -to_parfiles(prefix)[source]
    -
    -
    write the parameter ensemble to PEST-style parameter files
    - --- - - - -
    Parameters:prefix (str) – file prefix for par files
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -add_base()[source]
    -

    add “base” control file values as a realization

    -
    - -
    - -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/glossary.html b/docs/_build/html/source/glossary.html deleted file mode 100644 index 40025b201..000000000 --- a/docs/_build/html/source/glossary.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - - - Glossary — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Glossary

    -
    -
    attributes
    -
    Internal data stored by an object.
    -
    base class
    -
    A base class, or superclass, is a class that is extended by another class -in a technique called inheritance. Inheritance can make programming -efficient by having one version of base attributes and methods that can be -tested and then extended by a derived class.
    -
    Bayes’ Theorem
    -
    -\[\begin{equation} -P\left(\boldsymbol{\theta}|\boldsymbol{d}\right) = -\frac{\mathcal{L}\left(\textbf{d}|\boldsymbol{\theta}\right) P\left(\boldsymbol{\theta}\right)} -{P\left(\textbf{d}\right)} \ldots -\underbrace{P\left(\boldsymbol{\theta}|\textbf{d}\right)}_{\text{posterior pdf}} \propto -\underbrace{\mathcal{L}\left(\boldsymbol{d}|\boldsymbol{\theta}\right)}_{\text{likelihood function}} -\quad -\underbrace{P\left(\boldsymbol{\theta}\right)}_{\text{prior pdf}} -\end{equation}\]
    -

    where \(\boldsymbol{\theta}\) is a vector of parameters, -and \(\mathbf{d}\) is a vector of observations It is computationally -expedient to assume that these quantities can be characterized by -multivariate Gaussian distributions and, thus, characterized only by their -first two moments — mean and covariance.

    -
    -
    behavior
    -
    Object behavior refers to the set of actions an object can perform often reporting -or modifying its internal state [RS16] .
    -
    class
    -
    Classes [PSF18] [RS16] are the building blocks for object-oriented programs. The class -defines both attributes (state) and functionality (behavior) of an object.
    -
    client code
    -
    In the case of pyEMU, client code is a python script that uses the class definitions to -make a desired instance (also know as an object) and to use the attributes -and instance methods to build the desired analysis. The client code -can be developed as a traditional python script or within a Jupyter notebook [KRP+16]. -Jupyter notebooks are used extensively in this documentation to illustrate features of -pyEMU.
    -
    derived class
    -
    A derived class, or subclass, inherits attributes and methods from its -base class. Derived classes then add attributes and methods as -needed. For example, in pyEMU, the linear analysis base class is inherited by the -Monte Carlo derived class.
    -
    FloPY
    -
    Object-oriented python module to build, run, and process MODFLOW models [BPL+16] . Because -of the similar modeling structure, the combination of flopy and pyEMU can be -very effective in constructing, documenting, and analyzing groundwater-flow models.
    -
    forecasts
    -
    Model outputs for which field observations are not available. Typically these -values are simulated under an uncertain future condition.
    -
    FOSM
    linear uncertainty analysis
    -
    First Order Second Moment (FOSM) is a technique to use an assumption of Gaussian -distributions to, analytically, calculate the covariance of model outputs -considering both the prior covariance and the likelihood function. In -other words, it’s an analytical calculation of the posterior covariance of -parameters using Bayes’ Theoerem. The equation for this calculation is the -Schur Complement. The key advantage of this is that we really only need a -few quantities — a Jacobian Matrix \(\mathbf{J}\), the prior covariance of -parameters \(\boldsymbol{\Sigma_\theta}\), and the observation covariance -\(\boldsymbol{\Sigma_\epsilon}\).
    -
    Gaussian (multivariate)
    -

    The equation for Gaussian (Normal) distribution for a single variable (\(x\)) is

    -
    -\[\begin{equation} -f(x|\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\frac{\left(x-\mu\right)^2}{\sigma^2}} -\end{equation}\]
    -

    where \(\mu\) is the mean and \(\sigma\) is the standard deviation -The equation for a multivariate Gaussian for a vector of \(k\) variables (\(\mathbf{x}\)) is

    -
    -\[\begin{equation} -f(\mathbf{x} | \mathbf{\mu},\mathbf{\Sigma})=\frac{1}{\sqrt{(2\pi)^k\left|\mathbf{\Sigma}\right|}}e^{-\frac{1}{2}\left( \left(\mathbf{x}-\mathbf{\mu} \right)^T \mathbf{\Sigma}^{-1}\left(\mathbf{x}-\mathbf{\mu} \right)\right)} -\end{equation}\]
    -

    here \(\mu\) is a \(k\)-length vector of mean values, \(\mathbf{\Sigma}\) is -he covariance matrix, and \(\left|\mathbf{\Sigma}\right|\) is the determinant of -he covariance matrix. These quantities are often abbreviated -s \(\mathcal{N}\left( \mu, \sigma \right)\) -nd \(\mathcal{N}\left(\boldsymbol{\mu}, \boldsymbol{\Sigma} \right)\) -or univariate and multivariate Gaussian distributions, respectively.

    -
    -
    GLUE
    -
    Generalized Likelihood Uncertainty Estimation [KB09] .
    -
    inheritance
    -
    A object-oriented programming technique where one class, referred to as the -derived class or subclass, extends a second class, known as the base class -or superclass. The subclass has all the defined attributes and behavior -of the superclass and typically adds additional attributes or methods. Many -derived classes override methods of the superclass to perform specific -functions [RS16].
    -
    instance methods
    -
    Functions defined in a class that become associated with a particular -instance or object after instantiation.
    -
    instantiation
    -
    Make a class instance, also known as an object, from a class.
    -
    Jacobian matrix
    -
    A matrix of the sensitivity of all observations in an inverse model to all -parameters. This is often shown as a matrix by various names \(\mathbf{X}\), -\(\mathbf{J}\), or \(\mathbf{H}\). Each element of the matrix is a single -sensitivity value \(\frac{\partial f(x_i)}{\partial x_j}\) for \(i\in NOBS\), \(j -\in NPAR\).
    -
    likelihood (multivariate distribution)
    -
    This is a function describing how much is learned from the model. It is -characterized by the misfit between modeled equivalents and observations.
    -
    measurement noise/error
    -
    Measurement noise is a contribution to Epistemic Uncertainty. This is the -expected error of repeated measurements due to things like instrument -error and also can be compounded by error of surveying a datum, location -of an observation on a map, and other factors.
    -
    modeled equivalent
    simulated equivalent
    -
    A modeled value collocated to correspond in time and space with an observation. -To make things confusing, they are often also called -“observations”! These are denoted by \(f(x)\) for a scalar value or \(f\left( \bf{x} \right)\) -for a vector of values in this documentation. Note that in addition to -\(f(x)\), authors use several different alternate -notations to distinguish an observation from a -corresponding modeled equivalent including subscripts, \(y\) and \(y_m\), -superscripts, \(y\) and \(y^\prime\), or diacritic marks, \(y\) and \(\hat{y}\).
    -
    Monte Carlo ensemble
    -
    A group of realizations of parameters, \(\mathbf{\Theta}\), observations, -\(\mathbf{D_{obs}}\), and the simulated equivalent values, -\(\mathbf{D_{sim}}\). Note that these three matrices are made up of column -vectors representing all of the \(\boldsymbol{\theta}\), \(\mathbf{d_{obs}}\), -and \(\mathbf{d_{sim}}\) vectors.
    -
    Monte Carlo observation realization
    -
    A set of observation values, often but not required to be a multi-Gaussian -distribution, sampled using the mean values of measured observations and -variance from the observation weight covariance matrix. Can be identified -as \(\boldsymbol{d_{obs}}\).
    -
    Monte Carlo parameter realization
    -
    A set of parameter values, often but not required to be a multi-Gaussian -distribution, sampled from the mean values of specified parameter values -(either starting values or, in some cases, optimal values following -parameter estimation) with covariance from a set of variance values, or a -covariance matrix. Can be identified as \(\mathbf{\theta}\).
    -
    NOBS
    -
    For PEST and PEST++, number of observations.
    -
    NPAR
    -
    For PEST and PEST++, number of parameters.
    -
    object
    instance
    -
    A programming entity that may store internal data (state), have -functionality (behavior) or both [RS16] .
    -
    objective function
    -
    Equation quantifying how well as simulation matches observations. Inverse -modeling is the process of calibrating a numerical model by mathematically -seeking the minimum of an objective function. (see Phi)
    -
    observation
    -
    Measured system state values. These values are used to compare with model -outputs collocated in space and time. The term is often used to mean -both field measurements and outputs from the model. These are denoted -by \(y\) for a scalar value or \(\bf{y}\) -for a vector of values in this documentation.
    -
    override
    -
    Implement a new version of a method within a derived class that would have -otherwise been inherited from the base class customizing the behavior -of the method for the derived class [RS16]. Overriding allows objects to -call a method by the same name but have behavior specific to the -object’s type.
    -
    parameter covariance matrix
    -
    The uncertainty of parameters can be expressed as a matrix as well. This -is formed also as a diagonal matrix from the bounds around parameter -values (assuming that the range between the bounds indicates \(4\sigma\) -(e.g. 95 percent of a normal distribution).) In pyEMU, some functions -accept a sigma_range argument which can override the \(4\sigma\) -assumption. In many cases of our applications, parameters are spatially -distributed (e.g. hydraulic conductivity fields) so a covariance matrix -with off-diagonal terms can be formed to characterize not only their -variance but also their correlation/covariance. We often use -geostatistical variograms to characterize the covariance of parameters. -The parameter covariance matrix is often identified as \(C(\mathbf{p})\), -\(\mathbf{\Sigma_\theta}\), or \(\mathbf{R}\).
    -
    parameters
    -
    Variable input values for models, typically representing system properties -and forcings. Values to be estimated in the history matching process.
    -
    Phi
    -

    For pyEMU and consistent with PEST and PEST++, Phi refers to the objective function, -defined as the weighted sum of squares of residuals. Phi, \(\Phi\), is typically calculated as

    -
    -\[\begin{array}{ccc} -\Phi=\sum_{i=1}^{n}\left(\frac{y_{i}-f\left(x_{i}\right)}{w_{i}}\right)^{2} & or & \Phi=\left(\mathbf{y}-\mathbf{Jx}\right)^{T}\mathbf{Q}^{-1}\left(\mathbf{y}-\mathbf{Jx}\right) -\end{array}\]
    -

    When regularization is included, an additional term is added, -quantifying a penalty assessed for parameter sets that violate the preferred -conditions regarding the parameter values. -In such a case, the value of \(\Phi\) as stated above is -renamed \(\Phi_m\) for “measurement Phi” and the additional regularization -term is named \(\Phi_r\). A scalar, \(\gamma\), parameter controls the -tradeoff between these two dual components of the total objective function \(\Phi_t\).

    -
    -\[\Phi_t = \Phi_m + \gamma \Phi_r\]
    -
    -
    PHIMLIM
    -
    A PEST input parameter the governs the strength with which regularization -is applied to the objective function. A high value of PHIMLIM indicates a -strong penalty for deviation from preferred parameter conditions while a -low value of PHIMLIM indicates a weak penalty. The reason this “dial”” is -listed as a function of PHIM (e.g. \(\mathbf{\Phi_M}\)) is because it can -then be interpreted as a limit on how well we want to fit the observation -data. PHIMLIM is actually controlling the value \(\gamma\) appearing in the -definition for Phi and formally trading off \(\Phi_m\) asnd \(\Phi_r\).
    -
    posterior (multivariate distribution)
    -
    The posterior distribution is the updated distribution (mean and -covariance) of parameter values \(\boldsymbol{\theta}\) updated from their -prior by an experiment (encapsulated in the likelihood function). In other -words, information gleaned from observations \(\mathbf{d}\) is used to -update the initial values of the parameters.
    -
    prior (multivariate distribution)
    -
    This distribution represents what we know about parameter values prior to -any modeling. It is also called “soft knowledge” or “expert -knowledge”. This information is more than just starting values, but also -encapsulates the understanding of uncertainty (characterized through the -covariance) based on direct estimation of parameters (e.g. pumping tests, -geologic maps, and grain size analysis, for example). In one -interpretation of the objective function, this is also where the -regularization information is contained.
    -
    regularization
    -
    A preferred condition pertaining to parameters, the deviation from which, -elicits a penalty added to the objective function. This serves as a -balance between the level of fit or “measurement Phi” -\((\mathbf{\Phi_M})\) and the coherence with soft knowledge/previous -conditions/prior knowledge/regularization \((\mathbf{\Phi_R})\). These terms -can also be interpreted as the likelihood function and prior distribution -in Bayes theorem.
    -
    residuals
    -
    The difference between observation values and modeled equivalents -\(r_i=y_i-f\left(x_i\right)\).
    -
    Schur complement
    -

    The formula used to propagate uncertainty from a prior through a -“notional” calibration (via the Jacobian) to the posterior update.

    -
    -\[\begin{split}\begin{equation} -\underbrace{\overline{\boldsymbol{\Sigma}}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{know now}}} = \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{knew}}} - \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T\left[\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T + \boldsymbol{\Sigma}_{\boldsymbol{\epsilon}}\right]^{-1}\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\text{what we learned}} -\end{equation}\end{split}\]
    -
    -
    sensitivity
    -
    The incremental change of an observation (actually the modeled equivalent) -due to an incremental change in a parameter. Typically expressed as a -finite-difference approximation of a partial derivative, \(\frac{\partial -f(x)}{\partial x}\).
    -
    state
    -
    The state of the object refers to the current value of all the internal data -stored in and object [RS16] .
    -
    static methods
    helper methods
    -
    Functions that are defined for the python module and available to the script. They -are not tied to specified objects. pyEMU has a number of helper methods including -functions to plot results, read or write specific data types, and interact with the -FloPY module in analysis of MODFLOW models.
    -
    structural (model) error
    -
    Epistemic uncertainty is actually dominated by structural error relative -to measurement noise. The structural error is the expected misfit between -measured and modeled values at observation locations due to model -inadequacy (including everything from model simplification due to the -necessity of discretizing the domain, processes that are missing from the -model, etc.).
    -
    weight
    epistemic uncertainty
    -
    A value by which a residual is divided by when constructing the sum of -squared residuals. In principal, \(w_i\approx\frac{1}{\sigma_i}\) where \(\sigma_i\) is -an approximation of the expected error between model output and collocated -observation values. While the symbol \(\sigma\) implies a standard deviation, -it is important to note that measurement error only makes up a portion of -this error. Other aspects such as structural error (e.g. inadequacy inherent -in all models to perfectly simulate the natural world) also contribute to -this expected level of error. The reciprocal of weights are also called -Epistemic Uncertainty terms.
    -
    weight covariance matrix (correlation matrix)
    -
    In practice, this is usually a \(NOBS\times NOBS\) diagonal matrix with -values of weights on the diagonal representing the inverse of the -observation covariance. This implies a lack of correlation among the -observations. A full covariance matrix would indicate correlation among -the observations which, in reality, is present but, in practice, is rarely -characterized. The weight matrix is often identified as \(\mathbf{Q}^{-1}\) -or \(\mathbf{\Sigma_\epsilon}^{-1}\).
    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/introduction.html b/docs/_build/html/source/introduction.html deleted file mode 100644 index 585f86e10..000000000 --- a/docs/_build/html/source/introduction.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - Introduction — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Introduction

    -

    pyEMU is a set of python modules for programmatically interacting with the PEST -and PESTPP suites. It adopts much of the terminology from the PEST ecosystem, -but implements the functionality in a pythonic, easy-to-use interface. pyEMU is -available via github.

    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/modules.html b/docs/_build/html/source/modules.html deleted file mode 100644 index b485e9eb7..000000000 --- a/docs/_build/html/source/modules.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - pyemu — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/oop.html b/docs/_build/html/source/oop.html deleted file mode 100644 index f9170f3dd..000000000 --- a/docs/_build/html/source/oop.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - Notes on Object-Oriented Programming — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    Notes on Object-Oriented Programming

    -

    Analysis using PEST and PEST++ were traditionally run by writing a series of -input files (template, instruction, and contol files) and running a desired -program from either suite. pyEMU provides a programmatic way to interact with -PEST and PEST++ that allows for easier creation of necessary input files and -interpretation of the output generated by the suite of programs. The -programmatic approach also assists in creation of reproducible research [FB16] -by documenting choices made by the analyst in the generation of input files, in -steps of the analysis, or in the interpretation of results. pyEMU also extends -the functionality of PEST and PEST++ especially for performing linear and non- -linear uncertainty analysis. Users familiar with PEST and PEST++ input files -will find the terminology used in pyEMU familiar, the main difference between -using pyEMU and traditional methods is in the writing of client code to -perform the desired analysis.

    -

    The general workflow for pyEMU is to first create an instance (object) -of a class. The simplest object will only have attributes, -or data, associated with it and may be thought of as a data structure. Many -pyEMU objects also have instance methods associated with them which -allow the user to either manipulate attributes of the object or use the current -attributes, also known as the state of the object, to perform tasks.

    -

    For example

    -

    The jupyter notebook linked below uses the pyEMU -Pst Class (in the pyemu.pst module, pst_handler submodule) to create an object called p.

    -

    Attributes of the object -can then be accessed using p.attribute, for example, the parameter_data -are stored in the object as a pandas [MCK10] dataframe and can be accessed -using:

    -
    parm_data_df =  p.parameter_data
    -
    -
    -

    Methods are invoked using the dot notation, p.methodname(parameters). -One of the methods of the object used in the example notebook is -the get() method which will generate a new Pst instance using -a subset of the parameters or observations from an existing -object. To get a new object with the first 10 parameters and observations:

    -
    pnew = p.get(p.par_names[:10], p.obs_names[:10])
    -
    -
    -

    The attributes and methods associated with all the classes of pyEMU and -the helper methods are summarized in the Technical Documentation section.

    - -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/pst_demo.html b/docs/_build/html/source/pst_demo.html deleted file mode 100644 index db3aa73fd..000000000 --- a/docs/_build/html/source/pst_demo.html +++ /dev/null @@ -1,1307 +0,0 @@ - - - - - - - - Example Object-Oriented Access to the PEST Control File — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - - - -
    -

    Example Object-Oriented Access to the PEST Control File

    -

    The pst_handler module with pyemu.pst contains the Pst class -for dealing with pest control files. It relies heavily on pandas to -deal with tabular sections, such as parameters, observations, and prior -information. This jupyter notebook shows how to create a control-file -object (instantiate the class or make an instance of the class), how to -access attributes of the class, and how to call an instance method.

    -
    -
    -In [13]:
    -
    -
    -
    -import os
    -import numpy as np
    -import pyemu
    -from pyemu import Pst
    -
    -
    -
    -

    A PEST control file is required to make the object, and we need to pass -the name of the PEST control file as a parameter to the init method -for the class. The class instance (or object) is assigned to the -variable p.

    -
    -
    -In [14]:
    -
    -
    -
    -pst_name = os.path.join("..", "..", "examples", "henry","pest.pst")
    -p = Pst(pst_name)
    -
    -
    -
    -

    Now all of the relevant parts of the pest control file are attributes of -the object. For example, the parameter_data, observation data, and -prior information are available as pandas dataframes.

    -
    -
    -In [15]:
    -
    -
    -
    -p.parameter_data.head()
    -
    -
    -
    -
    -
    -Out[15]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor200.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    -
    -
    -
    -
    -In [16]:
    -
    -
    -
    -p.observation_data.head()
    -
    -
    -
    -
    -
    -Out[16]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.051396152.1458headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    -
    -
    -
    -
    -In [17]:
    -
    -
    -
    -p.prior_information.head()
    -
    -
    -
    -
    -
    -Out[17]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    equationobgnmepilblweight
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0
    -
    -
    -

    The client-code can be used to change values in the dataframes that can -be written to a new or updated control file using the write() method -as shown at the end of the notebook.

    -
    -
    -In [18]:
    -
    -
    -
    -p.parameter_data.loc['global_k', 'parval1'] = 225
    -p.parameter_data.head()
    -
    -
    -
    -
    -
    -Out[18]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    -
    -
    -

    A residual file (.rei or res) can also be passed to the -resfile argument at instantiation to enable some simple residual -analysis and evaluate if weight adjustments are needed. If -resfile = False, or not supplied, and if the residual file is in the -same directory as the pest control file and has the same base name, it -will be accessed automatically:

    -
    -
    -In [19]:
    -
    -
    -
    -p.res.head()
    -
    -
    -
    -
    -
    -Out[19]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    namegroupmeasuredmodelledresidualweight
    name
    h_obs01_1h_obs01_1head0.0513960.080402-0.029006152.1458
    h_obs01_2h_obs01_2head0.0221560.036898-0.0147420.0000
    h_obs02_1h_obs02_1head0.0468790.069121-0.022241152.1458
    h_obs02_2h_obs02_2head0.0208530.034311-0.0134580.0000
    h_obs03_1h_obs03_1head0.0365840.057722-0.021138152.1458
    -
    -
    -

    The weights can be updated by changing values in the observation -dataframe.

    -
    -
    -In [20]:
    -
    -
    -
    -p.observation_data.loc['h_obs01_1', 'weight'] = 25.0
    -p.observation_data.head()
    -
    -
    -
    -
    -
    -Out[20]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    -
    -
    -

    The Pst class exposes a method, get(), to create a new Pst -instance with a subset of parameters and or observations. For example, -make a new PEST control-file object using the first 10 entries from the -parameter and observation dataframes. Note this method does not -propogate prior information to the new instance:

    -
    -
    -In [21]:
    -
    -
    -
    -pnew = p.get(p.par_names[:10],p.obs_names[:10])
    -pnew.prior_information.head()
    -
    -
    -
    -
    -
    -Out[21]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    equationobgnmepilblweightnames
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0[mult1]
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0[kr01c01]
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0[kr01c02]
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0[kr01c03]
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0[kr01c04]
    -
    -
    -

    Check the parameter_data and observation_data dataframes for the new -object, note that the updated values for global_k parval1 and -h_obs01_1 weight are in these dataframes.

    -
    -
    -In [22]:
    -
    -
    -
    -pnew.parameter_data.head()
    -
    -
    -
    -
    -
    -Out[22]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    -
    -
    -
    -
    -In [23]:
    -
    -
    -
    -pnew.observation_data.head()
    -
    -
    -
    -
    -
    -Out[23]:
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    -
    -
    -

    The write(filename) method allows you to write a PEST control file -with the current state of the object: that is, make a new PEST control -file with the current information contained in an object.

    -
    -
    -In [24]:
    -
    -
    -
    -pnew.write("test.pst")
    -
    -
    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/pyemu.html b/docs/_build/html/source/pyemu.html deleted file mode 100644 index 98418cf7d..000000000 --- a/docs/_build/html/source/pyemu.html +++ /dev/null @@ -1,3087 +0,0 @@ - - - - - - - - pyemu package — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    pyemu package

    - -
    -

    Submodules

    -
    -
    -

    pyemu.en module

    -
    -
    -class pyemu.en.Ensemble(*args, **kwargs)[source]
    -

    Bases: pandas.core.frame.DataFrame

    -
    -
    The base class type for handling parameter and observation ensembles.
    -
    It is directly derived from pandas.DataFrame. This class should not be -instantiated directly.
    -
    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame(). Must contain -‘columns’ and ‘mean_values’
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    -
    -as_pyemu_matrix(typ=<class 'pyemu.mat.mat_handler.Matrix'>)[source]
    -

    Create a pyemu.Matrix from the Ensemble.

    - --- - - - - - - - -
    Parameters:typ (pyemu.Matrix or derived type) – the type of matrix to return
    Returns:pyemu.Matrix
    Return type:pyemu.Matrix
    -
    - -
    -
    -drop(arg)[source]
    -

    overload of pandas.DataFrame.drop()

    - --- - - - - - - - -
    Parameters:arg (iterable) – argument to pass to pandas.DataFrame.drop()
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -dropna(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.dropna()

    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame.dropna()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame.dropna()
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    - -
    -
    -draw(cov, num_reals=1, names=None)[source]
    -
    -
    draw random realizations from a multivariate
    -
    Gaussian distribution
    -
    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance structure to draw from
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • names (list) – list of columns names to draw for. If None, values all names -are drawn
    • -
    -
    -
    - -
    -
    -plot(bins=10, facecolor='0.5', plot_cols=None, filename='ensemble.pdf', func_dict=None, **kwargs)[source]
    -

    plot ensemble histograms to multipage pdf

    - --- - - - - - - - -
    Parameters:
      -
    • bins (int) – number of bins
    • -
    • facecolor (str) – color
    • -
    • plot_cols (list of str) – subset of ensemble columns to plot. If None, all are plotted. -Default is None
    • -
    • filename (str) – pdf filename. Default is “ensemble.pdf”
    • -
    • func_dict (dict) – a dict of functions to apply to specific columns (e.g., np.log10)
    • -
    • **kwargs (dict) – keyword args to pass to plot_utils.ensemble_helper()
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -classmethod from_dataframe(**kwargs)[source]
    -

    class method constructor to create an Ensemble from -a pandas.DataFrame

    - --- - - - - - - - -
    Parameters:**kwargs (dict) – optional args to pass to the -Ensemble Constructor. Expects ‘df’ in kwargs.keys() -that must be a pandas.DataFrame instance
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -static reseed()[source]
    -

    method to reset the numpy.random seed using the pyemu.en -SEED global variable

    -
    - -
    -
    -copy()[source]
    -

    make a deep copy of self

    - --- - - - - - -
    Returns:Ensemble
    Return type:Ensemble
    -
    - -
    -
    -covariance_matrix(localizer=None)[source]
    -

    calculate the approximate covariance matrix implied by the ensemble using -mean-differencing operation at the core of EnKF

    - --- - - - - - - - -
    Parameters:localizer (pyemu.Matrix) – covariance localizer to apply
    Returns:cov – covariance matrix
    Return type:pyemu.Cov
    -
    - -
    - -
    -
    -class pyemu.en.ObservationEnsemble(pst, **kwargs)[source]
    -

    Bases: pyemu.en.Ensemble

    -

    Ensemble derived type for observations. This class is primarily used to -generate realizations of observation noise. These are typically generated from -the weights listed in the control file. However, a general covariance matrix can -be explicitly used.

    -
    -

    Note

    -

    Does not generate noise realizations for observations with zero weight

    -
    -
    -
    -copy()[source]
    -

    overload of Ensemble.copy()

    - --- - - - - - -
    Returns:ObservationEnsemble
    Return type:ObservationEnsemble
    -
    - -
    -
    -names
    -

    property decorated method to get current non-zero weighted -column names. Uses ObservationEnsemble.pst.nnz_obs_names

    - --- - - - - - -
    Returns:list – non-zero weight observation names
    Return type:list
    -
    - -
    -
    -mean_values
    -

    property decorated method to get mean values of observation noise. -This is a zero-valued pandas.Series

    - --- - - - - - -
    Returns:mean_values
    Return type:pandas Series
    -
    - -
    -
    -draw(cov, num_reals)[source]
    -

    draw realizations of observation noise and add to mean_values -Note: only draws noise realizations for non-zero weighted observations -zero-weighted observations are set to mean value for all realizations

    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance matrix that describes the support volume around the -mean values.
    • -
    • num_reals (int) – number of realizations to draw
    • -
    -
    -
    - -
    -
    -nonzero
    -

    property decorated method to get a new ObservationEnsemble -of only non-zero weighted observations

    - --- - - - - - -
    Returns:ObservationEnsemble
    Return type:ObservationEnsemble
    -
    - -
    -
    -classmethod from_id_gaussian_draw(pst, num_reals)[source]
    -

    this is an experiemental method to help speed up independent draws -for a really large (>1E6) ensemble sizes.

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • num_reals (int) – number of realizations to draw
    • -
    -
    Returns:

    ObservationEnsemble

    -
    Return type:

    ObservationEnsemble

    -
    -
    - -
    -
    -to_binary(filename)[source]
    -

    write the observation ensemble to a jco-style binary file. The -ensemble is transposed in the binary file so that the 20-char obs -names are carried

    - --- - - - - - - - -
    Parameters:filename (str) – the filename to write
    Returns:
    Return type:None
    -
    -

    Note

    -

    The ensemble is transposed in the binary file

    -
    -
    - -
    -
    -classmethod from_binary(pst, filename)[source]
    -

    instantiate an observation obsemble from a jco-type file

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a Pst instance
    • -
    • filename (str) – the binary file name
    • -
    -
    Returns:

    oe

    -
    Return type:

    ObservationEnsemble

    -
    -
    - -
    -
    -phi_vector
    -

    property decorated method to get a vector of L2 norm (phi) -for the realizations. The ObservationEnsemble.pst.weights can be -updated prior to calling this method to evaluate new weighting strategies

    - --- - - - - - -
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -add_base()[source]
    -

    add “base” control file values as a realization

    -
    - -
    - -
    -
    -class pyemu.en.ParameterEnsemble(pst, istransformed=False, **kwargs)[source]
    -

    Bases: pyemu.en.Ensemble

    -
    -
    Ensemble derived type for parameters
    -
    implements bounds enforcement, log10 transformation, -fixed parameters and null-space projection -Note: uses the parnme attribute of Pst.parameter_data from column names -and uses the parval1 attribute of Pst.parameter_data as mean values
    -
    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – The ‘columns’ and ‘mean_values’ args need for Ensemble -are derived from the pst.parameter_data.parnme and pst.parameter_data.parval1 -items, respectively
    • -
    • istransformed (bool) – flag indicating the transformation status (log10) of the arguments be passed
    • -
    • **kwargs (dict) – keyword arguments to pass to Ensemble constructor.
    • -
    • bound_tol (float) – fractional amount to reset bounds transgression within the bound. -This has been shown to be very useful for the subsequent recalibration -because it moves parameters off of their bounds, so they are not treated as frozen in -the upgrade calculations. defaults to 0.0
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    -
    -dropna(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.dropna()

    - --- - - - - - - - -
    Parameters:
      -
    • *args (list) – positional args to pass to pandas.DataFrame.dropna()
    • -
    • **kwargs (dict) – keyword args to pass to pandas.DataFrame.dropna()
    • -
    -
    Returns:

    Ensemble

    -
    Return type:

    Ensemble

    -
    -
    - -
    -
    -copy()[source]
    -

    overload of Ensemble.copy()

    - --- - - - - - -
    Returns:ParameterEnsemble
    Return type:ParameterEnsemble
    -
    - -
    -
    -istransformed
    -

    property decorated method to get the current -transformation status of the ParameterEnsemble

    - --- - - - - - -
    Returns:istransformed
    Return type:bool
    -
    - -
    -
    -mean_values
    -

    the mean value vector while respecting log transform

    - --- - - - - - -
    Returns:mean_values
    Return type:pandas.Series
    -
    - -
    -
    -names
    -

    Get the names of the parameters in the ParameterEnsemble

    - --- - - - - - -
    Returns:list – parameter names
    Return type:list
    -
    - -
    -
    -adj_names
    -

    Get the names of adjustable parameters in the ParameterEnsemble

    - --- - - - - - -
    Returns:list – adjustable parameter names
    Return type:list
    -
    - -
    -
    -ubnd
    -

    the upper bound vector while respecting log transform

    - --- - - - - - -
    Returns:ubnd
    Return type:pandas.Series
    -
    - -
    -
    -lbnd
    -

    the lower bound vector while respecting log transform

    - --- - - - - - -
    Returns:lbnd
    Return type:pandas.Series
    -
    - -
    -
    -log_indexer
    -

    indexer for log transform

    - --- - - - - - -
    Returns:log_indexer
    Return type:pandas.Series
    -
    - -
    -
    -fixed_indexer
    -

    indexer for fixed status

    - --- - - - - - -
    Returns:fixed_indexer
    Return type:pandas.Series
    -
    - -
    -
    -draw(cov, num_reals=1, how='normal', enforce_bounds=None)[source]
    -

    draw realizations of parameter values

    - --- - - - -
    Parameters:
      -
    • cov (pyemu.Cov) – covariance matrix that describes the support around -the mean parameter values
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • how (str) – distribution to use to generate realizations. Options are -‘normal’ or ‘uniform’. Default is ‘normal’. If ‘uniform’, -cov argument is ignored
    • -
    • enforce_bounds (str) – how to enforce parameter bound violations. Options are -‘reset’ (reset individual violating values), ‘drop’ (drop realizations -that have one or more violating values. Default is None (no bounds enforcement)
    • -
    -
    -
    - -
    -
    -classmethod from_uniform_draw(pst, num_reals)[source]
    -

    this is an experiemental method to help speed up uniform draws -for a really large (>1E6) ensemble sizes. WARNING: this constructor -transforms the pe argument

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • num_reals (int) – number of realizations to generate
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_sparse_gaussian_draw(pst, cov, num_reals)[source]
    -

    instantiate a parameter ensemble from a sparse covariance matrix. -This is an advanced user method that assumes you know what you are doing -- few guard rails…

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • cov ((pyemu.SparseMatrix)) – sparse covariance matrix to use for drawing
    • -
    • num_reals (int) – number of realizations to generate
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_gaussian_draw(pst, cov, num_reals=1, use_homegrown=True, group_chunks=False, fill_fixed=True, enforce_bounds=False)[source]
    -

    instantiate a parameter ensemble from a covariance matrix

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file instance
    • -
    • cov ((pyemu.Cov)) – covariance matrix to use for drawing
    • -
    • num_reals (int) – number of realizations to generate
    • -
    • use_homegrown (bool) – flag to use home-grown full cov draws…much faster -than numpy…
    • -
    • group_chunks (bool) – flag to break up draws by par groups. Only applies -to homegrown, full cov case. Default is False
    • -
    • fill_fixed (bool) – flag to fill in fixed parameters from the pst into the -ensemble using the parval1 from the pst. Default is True
    • -
    • enforce_bounds (bool) – flag to enforce parameter bounds from the pst. realized -parameter values that violate bounds are simply changed to the -value of the violated bound. Default is False
    • -
    -
    Returns:

    ParameterEnsemble

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -classmethod from_binary(pst, filename)[source]
    -

    instantiate an parameter obsemble from a jco-type file

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a Pst instance
    • -
    • filename (str) – the binary file name
    • -
    -
    Returns:

    pe

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -project(projection_matrix, inplace=True, log=None, enforce_bounds='reset')[source]
    -

    project the ensemble using the null-space Monte Carlo method

    - --- - - - - - - - -
    Parameters:
      -
    • projection_matrix (pyemu.Matrix) – projection operator - must already respect log transform
    • -
    • inplace (bool) – project self or return a new ParameterEnsemble instance
    • -
    • log (pyemu.Logger) – for logging progress
    • -
    • enforce_bounds (str) – parameter bound enforcement flag. ‘drop’ removes -offending realizations, ‘reset’ resets offending values
    • -
    -
    Returns:

    ParameterEnsemble – if inplace is False

    -
    Return type:

    ParameterEnsemble

    -
    -
    - -
    -
    -enforce(enforce_bounds='reset')[source]
    -

    entry point for bounds enforcement. This gets called for the -draw method(s), so users shouldn’t need to call this

    - --- - - - -
    Parameters:enforce_bounds (str) – can be ‘reset’ to reset offending values or ‘drop’ to drop -offending realizations
    -
    - -
    -
    -enfore_scale()[source]
    -

    enforce parameter bounds on the ensemble by finding the -scaling factor needed to bring the most violated parameter back in bounds

    -
    -

    Note

    -

    this method not fully implemented.

    -
    - --- - - - -
    Raises:NotImplementedError if called.
    -
    - -
    -
    -enforce_drop()[source]
    -

    enforce parameter bounds on the ensemble by dropping -violating realizations

    -
    - -
    -
    -enforce_reset()[source]
    -

    enforce parameter bounds on the ensemble by resetting -violating vals to bound

    -
    - -
    -
    -read_parfiles_prefix(prefix)[source]
    -

    thin wrapper around read_parfiles using the pnulpar prefix concept. Used to -fill ParameterEnsemble from PEST-type par files

    - --- - - - -
    Parameters:prefix (str) – the par file prefix
    -
    - -
    -
    -read_parfiles(parfile_names)[source]
    -

    read the ParameterEnsemble realizations from par files. Used to fill -the ParameterEnsemble with realizations from PEST-type par files

    - --- - - - -
    Parameters:parfile_names (list) – list of par files to load
    -
    -

    Note

    -

    log transforms after loading according and possibly resets -self.__istransformed

    -
    -
    - -
    -
    -classmethod from_parfiles(pst, parfile_names, real_names=None)[source]
    -

    create a parameter ensemble from parfiles. Accepts parfiles with less than the -parameters in the control (get NaNs in the ensemble) or extra parameters in the -parfiles (get dropped)

    - --- - - - - - -
    Parameters:
      -
    • pst – pyemu.Pst
    • -
    • parfile_names – list of str -par file names
    • -
    • real_names – str -optional list of realization names. If None, a single integer counter is used
    • -
    -
    Returns:

    pyemu.ParameterEnsemble

    -
    -
    - -
    -
    -to_csv(*args, **kwargs)[source]
    -

    overload of pandas.DataFrame.to_csv() to account -for parameter transformation so that the saved -ParameterEnsemble csv is not in Log10 space

    - --- - - - -
    Parameters:
      -
    • *args (list) – positional arguments to pass to pandas.DataFrame.to_csv()
    • -
    • **kwrags (dict) – keyword arguments to pass to pandas.DataFrame.to_csv()
    • -
    -
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -to_binary(filename)[source]
    -

    write the parameter ensemble to a jco-style binary file

    - --- - - - - - - - -
    Parameters:filename (str) – the filename to write
    Returns:
    Return type:None
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -to_parfiles(prefix)[source]
    -
    -
    write the parameter ensemble to PEST-style parameter files
    - --- - - - -
    Parameters:prefix (str) – file prefix for par files
    -
    -

    Note

    -

    this function back-transforms inplace with respect to -log10 before writing

    -
    -
    - -
    -
    -add_base()[source]
    -

    add “base” control file values as a realization

    -
    - -
    - -
    -
    -

    pyemu.ev module

    -

    module for error variance analysis, using FOSM -assumptions.

    -
    -
    -class pyemu.ev.ErrVar(jco, **kwargs)[source]
    -

    Bases: pyemu.la.LinearAnalysis

    -

    Derived class for error variance analysis. Supports 2-term and -3-term error variance analysis. Inherits from pyemu.LinearAnalysis.

    - --- - - - -
    Parameters:
      -
    • **kwargs (dict) – keyword args to pass to the LinearAnalysis base constructor.
    • -
    • omitted_parameters (list) – list of parameters to treat as “omitted”. Passing this argument -activates 3-term error variance analysis.
    • -
    • omitted_parcov (varies) – argument that can be cast to a parcov for the omitted parameters. -If None, omitted_parcov will be formed by extracting from the -LinearAnalsis.parcov attribute.
    • -
    • omitted_predictions (varies) – argument that can be cast to a “predictions” attribute for -prediction sensitivity vectors WRT omitted parameters. If None, -these vectors will be extracted from the LinearAnalysis.predictions -attribute
    • -
    • kl (bool) – flag to perform KL scaling on the jacobian before error variance -calculations
    • -
    -
    -
    -

    Note

    -

    There are some additional keyword args that can be passed to active -the 3-term error variance calculation

    -
    -
    -
    -omitted_predictions
    -

    get the omitted prediction sensitivity vectors (stored as -a pyemu.Matrix)

    - --- - - - - - -
    Returns:omitted_predictions – a matrix of prediction sensitivity vectors (column wise) to -omitted parameters
    Return type:pyemu.Matrix
    -
    -

    Note

    -

    returns a reference

    -

    if ErrorVariance.__omitted_predictions is not set, then dynamically load the -attribute before returning

    -
    -
    - -
    -
    -omitted_jco
    -

    get the omitted jco

    - --- - - - - - -
    Returns:omitted_jco
    Return type:pyemu.Jco
    -
    -

    Note

    -

    returns a reference

    -

    if ErrorVariance.__omitted_jco is None, -then dynamically load the attribute before returning

    -
    -
    - -
    -
    -omitted_parcov
    -

    get the omitted prior parameter covariance matrix

    - --- - - - - - -
    Returns:omitted_parcov
    Return type:pyemu.Cov
    -
    -

    Note

    -

    returns a reference

    -

    If ErrorVariance.__omitted_parcov is None, -attribute is dynamically loaded

    -
    -
    - -
    -
    -get_errvar_dataframe(singular_values=None)[source]
    -
    -
    get a pandas dataframe of error variance results indexed
    -
    on singular value and (prediction name,<errvar term>)
    -
    - --- - - - - - - - -
    Parameters:singular_values (list) – singular values to test. defaults to -range(0,min(nnz_obs,nadj_par) + 1)
    Returns:pandas.DataFrame – multi-indexed pandas dataframe
    Return type:pandas.DataFrame
    -
    - -
    -
    -get_identifiability_dataframe(singular_value=None, precondition=False)[source]
    -

    get the parameter identifiability as a pandas dataframe

    - --- - - - - - - - -
    Parameters:
      -
    • singular_value (int) – the singular spectrum truncation point. Defaults to minimum of -non-zero-weighted observations and adjustable parameters
    • -
    • precondition (bool) – flag to use the preconditioned hessian (xtqt + sigma_theta^-1). -Default is False
    • -
    -
    Returns:

    pandas.DataFrame – A pandas dataframe of the V_1**2 Matrix with the -identifiability in the column labeled “ident”

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -variance_at(singular_value)[source]
    -

    get the error variance of all three terms at a singluar value

    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to test
    Returns:dict – dictionary of (err var term,prediction_name), standard_deviation pairs
    Return type:dict
    -
    - -
    -
    -R(singular_value)[source]
    -

    get resolution Matrix (V_1 * V_1^T) at a singular value

    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc R at
    Returns:R – resolution matrix at singular_value
    Return type:pyemu.Matrix
    -
    - -
    -
    -I_minus_R(singular_value)[source]
    -

    get I - R at singular value

    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc R at
    Returns:I - R – identity matrix minus resolution matrix at singular_value
    Return type:pyemu.Matrix
    -
    - -
    -
    -G(singular_value)[source]
    -
    -
    get the parameter solution Matrix at a singular value
    -
    V_1 * S_1^(_1) * U_1^T
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc R at
    Returns:G – parameter solution matrix at singular value
    Return type:pyemu.Matrix
    -
    - -
    -
    -first_forecast(singular_value)[source]
    -

    wrapper around ErrVar.first_forecast

    -
    - -
    -
    -first_prediction(singular_value)[source]
    -
    -
    get the null space term (first term) contribution to prediction error variance
    -
    at a singular value. used to construct error variance dataframe
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc first term at
    Returns:dict – dictionary of (“first”,prediction_names),error variance pairs at singular_value
    Return type:dict
    -
    - -
    -
    -first_parameter(singular_value)[source]
    -
    -
    get the null space term (first term) contribution to parameter error variance
    -
    at a singular value
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc first term at
    Returns:first_term – first term contribution to parameter error variance
    Return type:pyemu.Cov
    -
    - -
    -
    -second_forecast(singular_value)[source]
    -

    wrapper around ErrVar.second_prediction

    -
    - -
    -
    -second_prediction(singular_value)[source]
    -
    -
    get the solution space contribution to predictive error variance
    -
    at a singular value (y^t * G * obscov * G^T * y). Used to construct -error variance dataframe
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc second term at
    Returns:dict – dictionary of (“second”,prediction_names), error variance
    Return type:dict
    -
    - -
    -
    -second_parameter(singular_value)[source]
    -
    -
    get the solution space contribution to parameter error variance
    -
    at a singular value (G * obscov * G^T)
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc second term at
    Returns:second_parameter – second term contribution to parameter error variance
    Return type:pyemu.Cov
    -
    - -
    -
    -third_forecast(singular_value)[source]
    -

    wrapper around ErrVar.third_prediction

    -
    - -
    -
    -third_prediction(singular_value)[source]
    -
    -
    get the omitted parameter contribution to prediction error variance
    -
    at a singular value. used to construct error variance dataframe
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc third term at
    Returns:dict – dictionary of (“third”,prediction_names),error variance
    Return type:dict
    -
    - -
    -
    -third_parameter(singular_value)[source]
    -
    -
    get the omitted parameter contribution to parameter error variance
    -
    at a singular value (G * omitted_jco * Sigma_(omitted_pars) * omitted_jco^T * G^T)
    -
    - --- - - - - - - - -
    Parameters:singular_value (int) – singular value to calc third term at
    Returns:third_parameter – 0.0 if need_omitted is False
    Return type:pyemu.Cov
    -
    - -
    - -
    -
    -

    pyemu.la module

    -

    This module contains the LinearAnalysis object, which is the base class for -all other pyemu analysis objects (Schur, ErrVar, MonteCarlo, and EnsembleSmoother).

    -
    -
    -class pyemu.la.LinearAnalysis(jco=None, pst=None, parcov=None, obscov=None, predictions=None, ref_var=1.0, verbose=False, resfile=False, forecasts=None, sigma_range=4.0, scale_offset=True, **kwargs)[source]
    -

    Bases: object

    -

    The super class for linear analysis. Can be used directly, but -for prior uncertainty analyses only. The derived types -(pyemu.Schur, pyemu.ErrVar, pyemu.MonteCarlo, pyemu.EnsembleSmoother) -are for different forms of posterior uncertainty analyses. This class -tries hard to not load items until they are needed; all arguments are -optional

    - --- - - - -
    Parameters:
      -
    • jco ((varies)) – something that can be cast or loaded into a pyemu.Jco. Can be a -str for a filename or pyemu.Matrix/pyemu.Jco object.
    • -
    • pst ((varies)) – something that can be cast into a pyemu.Pst. Can be an str for a -filename or an existing pyemu.Pst. If None, a pst filename is sought -with the same base name as the jco argument (if passed)
    • -
    • parcov ((varies)) – prior parameter covariance matrix. If str, a filename is assumed and -the prior parameter covariance matrix is loaded from a file using -the file extension. If None, the prior parameter covariance matrix is -constructed from the parameter bounds in the control file represented -by the pst argument. Can also be a pyemu.Cov instance
    • -
    • obscov ((varies)) – observation noise covariance matrix. If str, a filename is assumed and -the observation noise covariance matrix is loaded from a file using -the file extension. If None, the observation noise covariance matrix is -constructed from the weights in the control file represented -by the pst argument. Can also be a pyemu.Cov instance
    • -
    • predictions ((varies)) – prediction (aka forecast) sensitivity vectors. If str, a filename -is assumed and predictions are loaded from a file using the file extension. -Can also be a pyemu.Matrix instance, a numpy.ndarray or a collection -of pyemu.Matrix or numpy.ndarray.
    • -
    • ref_var ((float)) – reference variance
    • -
    • verbose ((either bool or string)) –
      -
      controls log file / screen output. If str, a filename is assumed and
      -
      and log file is written to verbose
      -
      -
    • -
    • sigma_range (float) – defines range of upper bound - lower bound in terms of standard -deviation (sigma). For example, if sigma_range = 4, the bounds -represent 4 * sigma. Default is 4.0, representing approximately -95% confidence of implied normal distribution. This arg is only -used if constructing parcov from parameter bounds.
    • -
    • scale_offset (True) – flag to apply parameter scale and offset to parameter bounds -when calculating prior parameter covariance matrix from -bounds. This arg is onlyused if constructing parcov -from parameter bounds.Default is True.
    • -
    -
    -
    -

    Note

    -

    the class makes heavy use of property decorator to encapsulate -private attributes

    -
    -
    -
    -forecast_names
    -

    get the forecast (aka prediction) names

    - --- - - - - - -
    Returns:forecast_names – list of forecast names
    Return type:list
    -
    - -
    -
    -parcov
    -

    get the prior parameter covariance matrix attribute

    - --- - - - - - -
    Returns:parcov – a reference to the parcov attribute
    Return type:pyemu.Cov
    -
    -

    Note

    -

    returns a reference

    -

    if LinearAnalysis.__parcov is not set, the dynamically load the -parcov attribute before returning

    -
    -
    - -
    -
    -obscov
    -

    get the observation noise covariance matrix attribute

    - --- - - - - - -
    Returns:obscov – a reference to the obscov attribute
    Return type:pyemu.Cov
    -
    -

    Note

    -

    returns a reference

    -

    if LinearAnalysis.__obscov is not set, the dynamically load the -obscov attribute before returning

    -
    -
    - -
    -
    -nnz_obs_names
    -

    wrapper around pyemu.Pst.nnz_obs_names for listing non-zero -observation names

    - --- - - - - - -
    Returns:nnz_obs_names – pyemu.Pst.nnz_obs_names
    Return type:list
    -
    - -
    -
    -adj_par_names()[source]
    -

    wrapper around pyemu.Pst.adj_par_names for list adjustable parameter -names

    - --- - - - - - -
    Returns:adj_par_names – pyemu.Pst.adj_par_names
    Return type:list
    -
    - -
    -
    -jco
    -

    get the jacobian matrix attribute

    - --- - - - - - -
    Returns:jco – the jacobian matrix attribute
    Return type:pyemu.Jco
    -
    -

    Note

    -

    returns a reference

    -

    if LinearAnalysis.__jco is not set, the dynamically load the -jco attribute before returning

    -
    -
    - -
    -
    -predictions
    -

    get the predictions (aka forecasts) attribute

    - --- - - - - - -
    Returns:predictions – a matrix of prediction sensitivity vectors (column wise)
    Return type:pyemu.Matrix
    -
    -

    Note

    -

    returns a reference

    -
    -
    if LinearAnalysis.__predictions is not set, the dynamically load the
    -
    predictions attribute before returning
    -
    -
    -
    - -
    -
    -predictions_iter
    -

    property decorated prediction iterator

    - --- - - - - - -
    Returns:iterator – iterator on prediction sensitivity vectors (matrix)
    Return type:iterator
    -
    - -
    -
    -forecasts_iter
    -

    synonym for LinearAnalysis.predictions_iter()

    -
    - -
    -
    -forecasts
    -

    synonym for LinearAnalysis.predictions

    -
    - -
    -
    -pst
    -

    get the pyemu.Pst attribute

    - --- - - - - - -
    Returns:pst
    Return type:pyemu.Pst
    -
    -

    Note

    -

    returns a references

    -

    If LinearAnalysis.__pst is None, then the pst attribute is -dynamically loaded before returning

    -
    -
    - -
    -
    -fehalf
    -

    get the KL parcov scaling matrix attribute. Create the attribute if -it has not yet been created

    - --- - - - - - -
    Returns:fehalf
    Return type:pyemu.Matrix
    -
    - -
    -
    -qhalf
    -

    get the square root of the cofactor matrix attribute. Create the attribute if -it has not yet been created

    - --- - - - - - -
    Returns:qhalf
    Return type:pyemu.Matrix
    -
    - -
    -
    -qhalfx
    -

    get the half normal matrix attribute. Create the attribute if -it has not yet been created

    - --- - - - - - -
    Returns:qhalfx
    Return type:pyemu.Matrix
    -
    - -
    -
    -xtqx
    -

    get the normal matrix attribute. Create the attribute if -it has not yet been created

    - --- - - - - - -
    Returns:xtqx
    Return type:pyemu.Matrix
    -
    - -
    -
    -mle_covariance
    -

    get the maximum likelihood parameter covariance matrix.

    - --- - - - - - -
    Returns:pyemu.Matrix
    Return type:pyemu.Matrix
    -
    - -
    -
    -prior_parameter
    -

    the prior parameter covariance matrix. Just a wrapper around -LinearAnalysis.parcov

    - --- - - - - - -
    Returns:prior_parameter
    Return type:pyemu.Cov
    -
    - -
    -
    -prior_forecast
    -

    thin wrapper for prior_prediction

    - --- - - - - - -
    Returns:prior_forecast – a dictionary of forecast name, prior variance pairs
    Return type:dict
    -
    - -
    -
    -mle_parameter_estimate
    -

    get the maximum likelihood parameter estimate.

    - --- - - - - - -
    Returns:post_expt – the maximum likelihood parameter estimates
    Return type:pandas.Series
    -
    - -
    -
    -prior_prediction
    -

    get a dict of prior prediction variances

    - --- - - - - - -
    Returns:prior_prediction – dictionary of prediction name, prior variance pairs
    Return type:dict
    -
    - -
    -
    -apply_karhunen_loeve_scaling()[source]
    -

    apply karhuene-loeve scaling to the jacobian matrix.

    -
    -

    Note

    -

    This scaling is not necessary for analyses using Schur’s -complement, but can be very important for error variance -analyses. This operation effectively transfers prior knowledge -specified in the parcov to the jacobian and reset parcov to the -identity matrix.

    -
    -
    - -
    -
    -clean()[source]
    -

    drop regularization and prior information observation from the jco

    -
    - -
    -
    -reset_pst(arg)[source]
    -

    reset the LinearAnalysis.pst attribute

    - --- - - - -
    Parameters:arg ((str or matrix)) – the value to assign to the pst attribute
    -
    - -
    -
    -reset_parcov(arg=None)[source]
    -

    reset the parcov attribute to None

    - --- - - - -
    Parameters:arg (str or pyemu.Matrix) – the value to assign to the parcov attribute. If None, -the private __parcov attribute is cleared but not reset
    -
    - -
    -
    -reset_obscov(arg=None)[source]
    -

    reset the obscov attribute to None

    - --- - - - -
    Parameters:arg (str or pyemu.Matrix) – the value to assign to the obscov attribute. If None, -the private __obscov attribute is cleared but not reset
    -
    - -
    -
    -drop_prior_information()[source]
    -

    drop the prior information from the jco and pst attributes

    -
    - -
    -
    -get(par_names=None, obs_names=None, astype=None)[source]
    -

    method to get a new LinearAnalysis class using a -subset of parameters and/or observations

    - --- - - - - - - - -
    Parameters:
      -
    • par_names (list) – par names for new object
    • -
    • obs_names (list) – obs names for new object
    • -
    • astype (pyemu.Schur or pyemu.ErrVar) – type to cast the new object. If None, return type is -same as self
    • -
    -
    Returns:

    new

    -
    Return type:

    LinearAnalysis

    -
    -
    - -
    -
    -adjust_obscov_resfile(resfile=None)[source]
    -

    reset the elements of obscov by scaling the implied weights -based on the phi components in res_file so that the total phi -is equal to the number of non-zero weights.

    - --- - - - -
    Parameters:resfile (str) – residual file to use. If None, residual file with case name is -sought. default is None
    -
    -

    Note

    -

    calls pyemu.Pst.adjust_weights_resfile()

    -
    -
    - -
    -
    -get_par_css_dataframe()[source]
    -

    get a dataframe of composite scaled sensitivities. Includes both -PEST-style and Hill-style.

    - --- - - - - - -
    Returns:css
    Return type:pandas.DataFrame
    -
    - -
    -
    -get_cso_dataframe()[source]
    -

    get a dataframe of composite observation sensitivity, as returned by PEST in the -seo file.

    -

    Note that this formulation deviates slightly from the PEST documentation in that the -values are divided by (npar-1) rather than by (npar).

    -

    The equation is cso_j = ((Q^1/2*J*J^T*Q^1/2)^1/2)_jj/(NPAR-1) -Returns: -cso : pandas.DataFrame

    -
    - -
    - -
    -
    -

    pyemu.logger module

    -

    module for logging pyemu progress

    -
    -
    -class pyemu.logger.Logger(filename, echo=False)[source]
    -

    Bases: object

    -
    -
    a basic class for logging events during the linear analysis calculations
    -
    if filename is passed, then a file handle is opened.
    -
    - --- - - - -
    Parameters:
      -
    • filename (str) – Filename to write logged events to. If False, no file will be created, -and logged events will be displayed on standard out.
    • -
    • echo (bool) – Flag to cause logged events to be echoed to the screen.
    • -
    -
    -
    -
    -items
    -

    dict – Dictionary holding events to be logged. If a log entry is -not in items, then it is treated as a new entry with the string -being the key and the datetime as the value. If a log entry is -in items, then the end time and delta time are written and -the item is popped from the keys.

    -
    - -
    -
    -statement(phrase)[source]
    -

    log a one time statement

    - --- - - - -
    Parameters:phrase (str) – statement to log
    -
    - -
    -
    -log(phrase)[source]
    -

    log something that happened. The first time phrase is passed the -start time is saved. The second time the phrase is logged, the -elapsed time is written

    - --- - - - -
    Parameters:phrase (str) – the thing that happened
    -
    - -
    -
    -warn(message)[source]
    -

    write a warning to the log file.

    - --- - - - -
    Parameters:message (str) – the warning text
    -
    - -
    -
    -lraise(message)[source]
    -

    log an exception, close the log file, then raise the exception

    - --- - - - - - -
    Parameters:message (str) – the exception message
    Raises:exception with message
    -
    - -
    - -
    -
    -

    pyemu.mc module

    -

    pyEMU Monte Carlo module. Supports easy Monte Carlo -and GLUE analyses. The MonteCarlo class inherits from -pyemu.LinearAnalysis

    -
    -
    -class pyemu.mc.MonteCarlo(**kwargs)[source]
    -

    Bases: pyemu.la.LinearAnalysis

    -

    LinearAnalysis derived type for monte carlo analysis

    - --- - - - -
    Parameters:**kwargs (dict) – dictionary of keyword arguments. See pyemu.LinearAnalysis for -complete definitions
    -
    -
    -parensemble
    -

    pyemu.ParameterEnsemble – pyemu object derived from a pandas dataframe, the ensemble -of parameters from the PEST control file with associated -starting value and bounds. Object also exposes methods -relevant to the dataframe and parameters– see documentation.

    -
    - -
    -
    -obsensemble
    -

    pyemu.ObservationEnsemble – pyemu object derived from a pandas dataframe, the ensemble -of observations from the PEST control file with associated -starting weights. Object also exposes methods -relevant to the dataframe and observations– see documentation.

    -
    - - --- - - - - - -
    Returns:pyEMU MonteCarlo object
    Return type:MonteCarlo
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -
    -
    -
    -num_reals
    -

    get the number of realizations in the parameter ensemble

    - --- - - - - - -
    Returns:num_real
    Return type:int
    -
    - -
    -
    -get_nsing(epsilon=0.0001)[source]
    -

    get the number of solution space dimensions given -a ratio between the largest and smallest singular values

    - --- - - - - - - - -
    Parameters:epsilon (float) – singular value ratio
    Returns:nsing – number of singular components above the epsilon ratio threshold
    Return type:float
    -
    -

    Note

    -

    If nsing == nadj_par, then None is returned

    -
    -
    - -
    -
    -get_null_proj(nsing=None)[source]
    -

    get a null-space projection matrix of XTQX

    - --- - - - - - - - -
    Parameters:nsing (int) – optional number of singular components to use -If Nonte, then nsing is determined from -call to MonteCarlo.get_nsing()
    Returns:v2_proj – the null-space projection matrix (V2V2^T)
    Return type:pyemu.Matrix
    -
    - -
    -
    -draw(num_reals=1, par_file=None, obs=False, enforce_bounds=None, cov=None, how='gaussian')[source]
    -
    -
    draw stochastic realizations of parameters and
    -
    optionally observations, filling MonteCarlo.parensemble and -optionally MonteCarlo.obsensemble.
    -
    - --- - - - -
    Parameters:
      -
    • num_reals (int) – number of realization to generate
    • -
    • par_file (str) – parameter file to use as mean values. If None, -use MonteCarlo.pst.parameter_data.parval1. -Default is None
    • -
    • obs (bool) – add a realization of measurement noise to observation values, -forming MonteCarlo.obsensemble.Default is False
    • -
    • enforce_bounds (str) – enforce parameter bounds based on control file information. -options are ‘reset’, ‘drop’ or None. Default is None
    • -
    • how (str) – type of distribution to draw from. Must be in [“gaussian”,”uniform”] -default is “gaussian”.
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(pst="pest.pst")

    -

    >>>mc.draw(1000)

    -
    -
    - -
    -
    -project_parensemble(par_file=None, nsing=None, inplace=True, enforce_bounds='reset')[source]
    -

    perform the null-space projection operations for null-space monte carlo

    - --- - - - - - - - -
    Parameters:
      -
    • par_file (str) – an optional file of parameter values to use
    • -
    • nsing (int) – number of singular values to in forming null subspace matrix
    • -
    • inplace (bool) – overwrite the existing parameter ensemble with the -projected values
    • -
    • enforce_bounds (str) – how to enforce parameter bounds. can be None, ‘reset’, or ‘drop’. -Default is None
    • -
    -
    Returns:

    par_en – if inplace is False, otherwise None

    -
    Return type:

    pyemu.ParameterEnsemble

    -
    -
    -

    Note

    -

    to use this method, the MonteCarlo instance must have been constructed -with the jco argument.

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000)

    -

    >>>mc.project_parensemble(par_file="final.par",nsing=100)

    -
    -
    - -
    -
    -write_psts(prefix, existing_jco=None, noptmax=None)[source]
    -
    -
    write parameter and optionally observation realizations
    -
    to a series of pest control files
    -
    - --- - - - -
    Parameters:
      -
    • prefix (str) – pest control file prefix
    • -
    • existing_jco (str) – filename of an existing jacobian matrix to add to the -pest++ options in the control file. This is useful for -NSMC since this jco can be used to get the first set of -parameter upgrades for free! Needs to be the path the jco -file as seen from the location where pest++ will be run
    • -
    • noptmax (int) – value of NOPTMAX to set in new pest control files
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>mc = pyemu.MonteCarlo(jco="pest.jcb")

    -

    >>>mc.draw(1000, obs=True)

    -

    >>>mc.write_psts("mc_", existing_jco="pest.jcb", noptmax=1)

    -
    -
    - -
    - -
    -
    -

    pyemu.sc module

    -

    module for FOSM-based uncertainty analysis using a -linearized form of Bayes equation known as the Schur compliment

    -
    -
    -class pyemu.sc.Schur(jco, **kwargs)[source]
    -

    Bases: pyemu.la.LinearAnalysis

    -

    derived type for prior and posterior uncertainty and data-worth -analysis using Schur compliment

    - --- - - - -
    Parameters:**kwargs (dict) – keyword arguments to pass to the LinearAnalysis constructor. See -LinearAnalysis definition for argument types
    -
    -

    Note

    -

    Same call signature as the base LinearAnalysis class

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -
    -
    -
    -pandas
    -

    get a pandas dataframe of prior and posterior for all predictions

    - --- - - - - - -
    Returns:
    -
    pandas.DataFrame
    -
    a dataframe with prior and posterior uncertainty estimates -for all forecasts (predictions)
    -
    -
    Return type:pandas.DataFrame
    -
    - -
    -
    -posterior_parameter
    -

    get the posterior parameter covariance matrix. If Schur.__posterior_parameter -is None, the posterior parameter covariance matrix is calculated via -Schur compliment before returning

    - --- - - - - - -
    Returns:posterior_parameter – the posterior parameter covariance matrix
    Return type:pyemu.Cov
    -
    -

    Note

    -

    returns a reference

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    >>>post_cov = sc.posterior_parameter

    -

    >>>post_cov.to_ascii("post.cov")

    -
    -
    - -
    -
    -map_parameter_estimate
    -

    get the posterior expectation for parameters using Bayes linear -estimation

    - --- - - - - - -
    Returns:post_expt – a dataframe with prior and posterior parameter expectations
    Return type:pandas.DataFrame
    -
    - -
    -
    -map_forecast_estimate
    -

    get the prior and posterior forecast (prediction) expectations.

    - --- - - - - - -
    Returns:pandas.DataFrame – dataframe with prior and posterior forecast expected values
    Return type:pandas.DataFrame
    -
    - -
    -
    -posterior_forecast
    -

    thin wrapper around posterior_prediction

    -
    - -
    -
    -posterior_prediction
    -

    get posterior forecast (prediction) variances

    - --- - - - - - -
    Returns:dict – a dictionary of forecast names, posterior variance pairs
    Return type:dict
    -
    -

    Note

    -

    This method is not as easy to use as Schur.get_forecast_summary(), please -use it instead

    -
    -
    - -
    -
    -get_parameter_summary(include_map=False)[source]
    -

    get a summary of the parameter uncertainty

    - --- - - - - - - - -
    Parameters:include_map (bool) – if True, add the prior and posterior expectations -and report standard deviation instead of variance
    Returns:pandas.DataFrame – dataframe of prior,posterior variances and percent -uncertainty reduction of each parameter
    Return type:pandas.DataFrame
    -
    -

    Note

    -

    this is the primary method for accessing parameter uncertainty -estimates - use this!

    -
    -
    -

    Example

    -

    >>>import matplotlib.pyplot as plt

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    >>>sc = pyemu.Schur(jco="pest.jcb",forecasts=["fore1","fore2"])

    -

    >>>par_sum = sc.get_parameter_summary()

    -

    >>>par_sum.plot(kind="bar")

    -

    >>>plt.show()

    -
    -
    - -
    -
    -get_forecast_summary(include_map=False)[source]
    -

    get a summary of the forecast uncertainty

    - --- - - - - - - - -
    Parameters:include_map (bool) – if True, add the prior and posterior expectations -and report standard deviation instead of variance
    Returns:pandas.DataFrame – dataframe of prior,posterior variances and percent -uncertainty reduction of each parameter
    Return type:pandas.DataFrame
    -
    -

    Note

    -

    this is the primary method for accessing forecast uncertainty -estimates - use this!

    -
    -
    -

    Example

    -

    >>>import matplotlib.pyplot as plt

    -

    >>>import pyemu

    -

    This usage assumes you have set the ++forecasts() argument in the -control file:

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    or, you can pass the forecasts directly, assuming the forecasts are -names of zero-weight observations:

    -

    >>>sc = pyemu.Schur(jco="pest.jcb",forecasts=["fore1","fore2"])

    -

    >>>fore_sum = sc.get_forecast_summary()

    -

    >>>fore_sum.plot(kind="bar")

    -

    >>>plt.show()

    -
    -
    - -
    -
    -get_conditional_instance(parameter_names)[source]
    -

    get a new Schur instance that includes conditional update from -some parameters becoming known perfectly

    - --- - - - - - - - -
    Parameters:parameter_names (list) – parameters that are to be treated as notionally perfectly -known
    Returns:la_cond – a new Schur instance conditional on perfect knowledge -of some parameters
    Return type:Schur
    -
    -

    Note

    -

    this method is used by the get_parameter_contribution() method - -don’t call this method directly

    -
    -
    - -
    -
    -get_par_contribution(parlist_dict=None)[source]
    -

    get a dataframe the prior and posterior uncertainty -reduction as a result of some parameter becoming perfectly known

    - --- - - - - - - - -
    Parameters:parlist_dict (dict) – a nested dictionary-list of groups of parameters -that are to be treated as perfectly known. key values become -row labels in returned dataframe. If None, each adjustable parameter -is sequentially treated as known and the returned dataframe -has row labels for each adjustable parameter
    Returns:pandas.DataFrame – a dataframe that summarizes the parameter contribution analysis. -The dataframe has index (row labels) of the keys in parlist_dict -and a column labels of forecast names. The values in the dataframe -are the posterior variance of the forecast conditional on perfect -knowledge of the parameters in the values of parlist_dict
    Return type:pandas.DataFrame
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    >>>df = sc.get_par_contribution()

    -
    -
    - -
    -
    -get_par_group_contribution()[source]
    -

    get the forecast uncertainty contribution from each parameter -group. Just some sugar for get_contribution_dataframe() - this method -automatically constructs the parlist_dict argument where the keys are the -group names and the values are the adjustable parameters in the groups

    - --- - - - - - -
    Returns:pandas.DataFrame – a dataframe that summarizes the parameter contribution analysis. -The dataframe has index (row labels) that are the parameter groups -and a column labels of forecast names. The values in the dataframe -are the posterior variance of the forecast conditional on perfect -knowledge of the adjustable parameters in each parameter groups
    Return type:pandas.DataFrame
    -
    - -
    -
    -get_added_obs_importance(obslist_dict=None, base_obslist=None, reset_zero_weight=False)[source]
    -

    get a dataframe fo the posterior uncertainty -as a results of added some observations

    - --- - - - - - - - -
    Parameters:
      -
    • obslist_dict (dict) – a nested dictionary-list of groups of observations -that are to be treated as gained. key values become -row labels in returned dataframe. If None, then every zero-weighted -observation is tested sequentially. Default is None
    • -
    • base_obslist (list) – observation names to treat as the “existing” observations. -The values of obslist_dict will be added to this list during -each test. If None, then the values in obslist_dict will -be treated as the entire calibration dataset. That is, there -are no existing data. Default is None. Standard practice would -be to pass this argument as Schur.pst.nnz_obs_names.
    • -
    • reset_zero_weight ((boolean or float)) – a flag to reset observations with zero weight in either -obslist_dict or base_obslist. The value of reset_zero_weights -can be cast to a float,then that value will be assigned to -zero weight obs. Otherwise, zero weight obs will be given a -weight of 1.0. Default is False.
    • -
    -
    Returns:

    pandas.DataFrame – dataframe with row labels (index) of obslist_dict.keys() and -columns of forecast_name. The values in the dataframe are the -posterior variance of the forecasts resulting from notional inversion -using the observations in obslist_dict[key value] plus the observations -in base_obslist (if any)

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    all observations listed in obslist_dict and base_obslist with zero -weights will be dropped unless reset_zero_weight is set

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    >>>df = sc.get_added_obs_importance(base_obslist=sc.pst.nnz_obs_names,reset_zero=True)

    -
    -
    - -
    -
    -get_removed_obs_importance(obslist_dict=None, reset_zero_weight=False)[source]
    -

    get a dataframe the posterior uncertainty -as a result of losing some observations

    - --- - - - - - - - -
    Parameters:
      -
    • obslist_dict (dict) – dictionary of groups of observations -that are to be treated as lost. key values become -row labels in returned dataframe. If None, then test every -(nonzero weight - see reset_zero_weight) observation
    • -
    • reset_zero_weight (bool or float) – a flag to reset observations with zero weight in obslist_dict. -If the value of reset_zero_weights can be cast to a float, -then that value will be assigned to zero weight obs. Otherwise, -zero weight obs will be given a weight of 1.0
    • -
    -
    Returns:

    pandas.DataFrame – a dataframe with index of obslist_dict.keys() and columns -of forecast names. The values in the dataframe are the posterior -variances of the forecasts resulting from losing the information -contained in obslist_dict[key value]

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    all observations listed in obslist_dict with zero -weights will be dropped unless reset_zero_weight is set

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    df = sc.get_removed_obs_importance()

    -
    -
    - -
    -
    -obs_group_importance()[source]
    -
    - -
    -
    -get_removed_obs_group_importance()[source]
    -
    - -
    -
    -get_added_obs_group_importance()[source]
    -
    - -
    -
    -next_most_important_added_obs(forecast=None, niter=3, obslist_dict=None, base_obslist=None, reset_zero_weight=False)[source]
    -

    find the most important observation(s) by sequentially evaluating -the importance of the observations in obslist_dict. The most important observations -from each iteration is added to base_obslist and removed obslist_dict for the -next iteration. In this way, the added observation importance values include -the conditional information from the last iteration.

    - --- - - - - - - - -
    Parameters:
      -
    • forecast (str) – name of the forecast to use in the ranking process. If -more than one forecast has been listed, this argument is required
    • -
    • niter (int) – number of sequential iterations
    • -
    • dict (obslist_dict) – nested dictionary-list of groups of observations -that are to be treated as gained. key values become -row labels in result dataframe. If None, then test every observation -individually
    • -
    • base_obslist (list) – observation names to treat as the “existing” observations. -The values of obslist_dict will be added to this list during testing. -If None, then each list in the values of obslist_dict will be -treated as an individual calibration dataset.
    • -
    • reset_zero_weight ((boolean or float)) – a flag to reset observations with zero weight in either -obslist_dict or base_obslist. If the value of reset_zero_weights -can be cast to a float,then that value will be assigned to -zero weight obs. Otherwise, zero weight obs will be given a weight of 1.0
    • -
    -
    Returns:

    pandas.DataFrame – DataFrame with columns of best obslist_dict key for each iteration. -Columns of forecast variance percent reduction for this iteration, -(percent reduction compared to initial base case)

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>sc = pyemu.Schur(jco="pest.jcb")

    -

    >>>df = sc.next_most_added_importance_obs(forecast="fore1",

    -

    >>>      base_obslist=sc.pst.nnz_obs_names,reset_zero=True

    -
    -
    - -
    -
    -next_most_par_contribution(niter=3, forecast=None, parlist_dict=None)[source]
    -

    find the largest parameter(s) contribution for prior and posterior -forecast by sequentially evaluating the contribution of parameters in -parlist_dict. The largest contributing parameters from each iteration are -treated as known perfectly for the remaining iterations. In this way, the -next iteration seeks the next most influential group of parameters.

    - --- - - - - - - - -
    Parameters:
      -
    • forecast (str) – name of the forecast to use in the ranking process. If -more than one forecast has been listed, this argument is required
    • -
    • parlist_dict (dict) – a nested dictionary-list of groups of parameters -that are to be treated as perfectly known. key values become -row labels in dataframe
    • -
    -
    Returns:

    pandas.DataFrame – a dataframe with index of iteration number and columns -of parlist_dict keys. The values are the results of the knowing -each parlist_dict entry expressed as posterior variance reduction

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    - -
    -
    -

    pyemu.smoother module

    -

    this is a prototype ensemble smoother based on the LM-EnRML -algorithm of Chen and Oliver 2013. It requires the pest++ “sweep” utility -to propagate the ensemble forward.

    -
    -
    -class pyemu.smoother.Phi(em)[source]
    -

    Bases: object

    -

    Container class for dealing with ensemble residuals and phi vectors

    - --- - - - -
    Parameters:em (EnsembleMethod) –
    -
    -
    -write(cur_lam=0.0)[source]
    -
    - -
    -
    -phi_vec_dict
    -
    - -
    -
    -get_meas_and_regul_phi(obsensemble, parensemble)[source]
    -
    - -
    -
    -update()[source]
    -
    - -
    -
    -report(cur_lam=0.0)[source]
    -
    - -
    -
    -get_residual_obs_matrix(obsensemble)[source]
    -
    - -
    -
    -get_residual_par_matrix(parensemble)[source]
    -
    - -
    - -
    -
    -class pyemu.smoother.EnsembleMethod(pst, parcov=None, obscov=None, num_slaves=0, use_approx_prior=True, submit_file=None, verbose=False, port=4004, slave_dir='template')[source]
    -

    Bases: object

    -

    Base class for ensemble-type methods. Should not be instantiated directly

    - --- - - - -
    Parameters:
      -
    • pst (pyemu.Pst or str) – a control file instance or filename
    • -
    • parcov (pyemu.Cov or str) – a prior parameter covariance matrix or filename. If None, -parcov is constructed from parameter bounds (diagonal)
    • -
    • obscov (pyemu.Cov or str) – a measurement noise covariance matrix or filename. If None, -obscov is constructed from observation weights.
    • -
    • num_slaves (int) – number of slaves to use in (local machine) parallel evaluation of the parmaeter -ensemble. If 0, serial evaluation is used. Ignored if submit_file is not None
    • -
    • submit_file (str) – the name of a HTCondor submit file. If not None, HTCondor is used to -evaluate the parameter ensemble in parallel by issuing condor_submit -as a system command
    • -
    • port (int) – the TCP port number to communicate on for parallel run management
    • -
    • slave_dir (str) – path to a directory with a complete set of model files and PEST -interface files
    • -
    -
    -
    -
    -initialize(*args, **kwargs)[source]
    -
    - -
    -
    -update(lambda_mults=[1.0], localizer=None, run_subset=None, use_approx=True)[source]
    -
    - -
    - -
    -
    -class pyemu.smoother.EnsembleSmoother(pst, parcov=None, obscov=None, num_slaves=0, submit_file=None, verbose=False, port=4004, slave_dir='template', drop_bad_reals=None, save_mats=False)[source]
    -

    Bases: pyemu.smoother.EnsembleMethod

    -

    an implementation of the GLM iterative ensemble smoother

    - --- - - - -
    Parameters:
      -
    • pst (pyemu.Pst or str) – a control file instance or filename
    • -
    • parcov (pyemu.Cov or str) – a prior parameter covariance matrix or filename. If None, -parcov is constructed from parameter bounds (diagonal)
    • -
    • obscov (pyemu.Cov or str) – a measurement noise covariance matrix or filename. If None, -obscov is constructed from observation weights.
    • -
    • num_slaves (int) – number of slaves to use in (local machine) parallel evaluation of the parmaeter -ensemble. If 0, serial evaluation is used. Ignored if submit_file is not None
    • -
    • use_approx_prior (bool) – a flag to use the MLE (approx) upgrade solution. If True, a MAP -solution upgrade is used
    • -
    • submit_file (str) – the name of a HTCondor submit file. If not None, HTCondor is used to -evaluate the parameter ensemble in parallel by issuing condor_submit -as a system command
    • -
    • port (int) – the TCP port number to communicate on for parallel run management
    • -
    • slave_dir (str) – path to a directory with a complete set of model files and PEST -interface files
    • -
    • drop_bad_reals (float) – drop realizations with phi greater than drop_bad_reals. If None, all -realizations are kept. Default is None
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>es = pyemu.EnsembleSmoother(pst="pest.pst")

    -
    -
    -
    -initialize(num_reals=1, init_lambda=None, enforce_bounds='reset', parensemble=None, obsensemble=None, restart_obsensemble=None, regul_factor=0.0, use_approx_prior=True, build_empirical_prior=False)[source]
    -

    Initialize the iES process. Depending on arguments, draws or loads -initial parameter observations ensembles and runs the initial parameter -ensemble

    - --- - - - -
    Parameters:
      -
    • num_reals (int) – the number of realizations to draw. Ignored if parensemble/obsensemble -are not None
    • -
    • init_lambda (float) – the initial lambda to use. During subsequent updates, the lambda is -updated according to upgrade success
    • -
    • enforce_bounds (str) – how to enfore parameter bound transgression. options are -reset, drop, or None
    • -
    • parensemble (pyemu.ParameterEnsemble or str) – a parameter ensemble or filename to use as the initial -parameter ensemble. If not None, then obsenemble must not be -None
    • -
    • obsensemble (pyemu.ObservationEnsemble or str) – an observation ensemble or filename to use as the initial -observation ensemble. If not None, then parensemble must -not be None
    • -
    • restart_obsensemble (pyemu.ObservationEnsemble or str) – an observation ensemble or filename to use as an -evaluated observation ensemble. If not None, this will skip the initial -parameter ensemble evaluation - user beware!
    • -
    • regul_factor (float) – the regularization penalty fraction of the composite objective. The -Prurist, MAP solution would be regul_factor = 1.0, yielding equal -parts measurement and regularization to the composite objective function. -Default is 0.0, which means only seek to minimize the measurement objective -function
    • -
    • use_approx_prior (bool) – a flag to use the inverse, square root of the prior ccovariance matrix -for scaling the upgrade calculation. If True, this matrix is not used. -Default is True
    • -
    • build_empirical_prior (bool) – flag to build the prior parameter covariance matrix from an existing parensemble. -If True and parensemble is None, an exception is raised
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>es = pyemu.EnsembleSmoother(pst="pest.pst")

    -

    >>>es.initialize(num_reals=100)

    -
    -
    - -
    -
    -get_localizer()[source]
    -

    get an empty/generic localizer matrix that can be filled

    - --- - - - - - -
    Returns:localizer – matrix with nnz obs names for rows and adj par names for columns
    Return type:pyemu.Matrix
    -
    - -
    -
    -update(lambda_mults=[1.0], localizer=None, run_subset=None, use_approx=True, calc_only=False)[source]
    -

    update the iES one GLM cycle

    - --- - - - -
    Parameters:
      -
    • lambda_mults (list) – a list of lambda multipliers to test. Each lambda mult value will require -evaluating (a subset of) the parameter ensemble.
    • -
    • localizer (pyemu.Matrix) – a jacobian localizing matrix
    • -
    • run_subset (int) – the number of realizations to test for each lambda_mult value. For example, -if run_subset = 30 and num_reals=100, the first 30 realizations will be run (in -parallel) for each lambda_mult value. Then the best lambda_mult is selected and the -remaining 70 realizations for that lambda_mult value are run (in parallel).
    • -
    • use_approx (bool) – a flag to use the MLE or MAP upgrade solution. True indicates use MLE solution
    • -
    • calc_only (bool) – a flag to calculate the upgrade matrix only (not run the ensemble). This is mostly for -debugging and testing on travis. Default is False
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>es = pyemu.EnsembleSmoother(pst="pest.pst")

    -

    >>>es.initialize(num_reals=100)

    -

    >>>es.update(lambda_mults=[0.1,1.0,10.0],run_subset=30)

    -
    -
    - -
    - -
    -
    -

    Module contents

    -

    pyEMU: python modules for Environmental Model Uncertainty analyses. These -modules are designed to work directly and seamlessly with PEST and PEST++ model -independent interface. pyEMU can also be used to setup this interface.

    -

    Several forms of uncertainty analyses are support including FOSM-based -analyses (pyemu.Schur and pyemu.ErrVar), Monte Carlo (including -GLUE and null-space Monte Carlo) and a prototype iterative Ensemble Smoother

    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/pyemu.mat.html b/docs/_build/html/source/pyemu.mat.html deleted file mode 100644 index e9c296ef9..000000000 --- a/docs/_build/html/source/pyemu.mat.html +++ /dev/null @@ -1,1572 +0,0 @@ - - - - - - - - pyemu.mat package — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    pyemu.mat package

    -
    -

    Submodules

    -
    -
    -

    pyemu.mat.mat_handler module

    -

    Matrix, Jco and Cov classes for easy linear algebra

    -
    -
    -pyemu.mat.mat_handler.save_coo(x, row_names, col_names, filename, chunk=None)[source]
    -

    write a PEST-compatible binary file. The data format is -[int,int,float] for i,j,value. It is autodetected during -the read with Matrix.from_binary().

    - --- - - - -
    Parameters:
      -
    • x (numpy.sparse) – coo sparse matrix
    • -
    • row_names (list) – list of row_names
    • -
    • col_names (list) – list of col_names
    • -
    • filename (str) – filename to save binary file
    • -
    • droptol (float) – absolute value tolerance to make values smaller than zero. Default is None
    • -
    • chunk (int) – number of elements to write in a single pass. Default is None
    • -
    -
    -
    - -
    -
    -pyemu.mat.mat_handler.concat(mats)[source]
    -

    Concatenate Matrix objects. Tries either axis.

    - --- - - - - - - - -
    Parameters:mats (list) – list of Matrix objects
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -pyemu.mat.mat_handler.get_common_elements(list1, list2)[source]
    -
    -
    find the common elements in two lists. used to support auto align
    -
    might be faster with sets
    -
    - --- - - - - - - - -
    Parameters:
      -
    • list1 (list) – a list of objects
    • -
    • list2 (list) – a list of objects
    • -
    -
    Returns:

    list – list of common objects shared by list1 and list2

    -
    Return type:

    list

    -
    -
    - -
    -
    -class pyemu.mat.mat_handler.Matrix(x=None, row_names=[], col_names=[], isdiagonal=False, autoalign=True)[source]
    -

    Bases: object

    -

    a class for easy linear algebra

    - --- - - - - - - - -
    Parameters:
      -
    • x (numpy.ndarray) – Matrix entries
    • -
    • row_names (list) – list of row names
    • -
    • col_names (list) – list of column names
    • -
    • isdigonal (bool) – to determine if the Matrix is diagonal
    • -
    • autoalign (bool) – used to control the autoalignment of Matrix objects -during linear algebra operations
    • -
    -
    Returns:

    Matrix

    -
    Return type:

    Matrix

    -
    -
    -
    -binary_header_dt
    -

    numpy.dtype – the header info in the PEST binary file type

    -
    - -
    -
    -binary_rec_dt
    -

    numpy.dtype – the record info in the PEST binary file type

    -
    - -
    -
    -to_ascii : write a PEST-style ASCII matrix format file
    -
    - -
    -
    -to_binary : write a PEST-stle compressed binary format file
    -
    - -
    -

    Note

    -

    this class makes heavy use of property decorators to encapsulate -private attributes

    -
    -
    -
    -integer
    -

    alias of numpy.int32

    -
    - -
    -
    -double
    -

    alias of numpy.float64

    -
    - -
    -
    -char
    -

    alias of numpy.uint8

    -
    - -
    -
    -binary_header_dt = dtype([('itemp1', '<i4'), ('itemp2', '<i4'), ('icount', '<i4')])
    -
    - -
    -
    -binary_rec_dt = dtype([('j', '<i4'), ('dtemp', '<f8')])
    -
    - -
    -
    -coo_rec_dt = dtype([('i', '<i4'), ('j', '<i4'), ('dtemp', '<f8')])
    -
    - -
    -
    -par_length = 12
    -
    - -
    -
    -obs_length = 20
    -
    - -
    -
    -new_par_length = 100
    -
    - -
    -
    -new_obs_length = 100
    -
    - -
    -
    -reset_x(x, copy=True)[source]
    -

    reset self.__x private attribute

    - --- - - - -
    Parameters:
      -
    • x (numpy.ndarray) –
    • -
    • copy (bool) – flag to make a copy of ‘x’. Defaule is True
    • -
    -
    -
    -

    Note

    -

    makes a copy of ‘x’ argument

    -
    -
    - -
    -
    -hadamard_product(other)[source]
    -

    Overload of numpy.ndarray.__mult__(): element-wise multiplication. -Tries to speedup by checking for scalars of diagonal matrices on -either side of operator

    - --- - - - - - - - -
    Parameters:other (scalar,numpy.ndarray,Matrix object) – the thing for element-wise multiplication
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -mult_isaligned(other)[source]
    -

    check if matrices are aligned for dot product multiplication

    - --- - - - - - - - -
    Parameters:other ((Matrix)) –
    Returns:bool – True if aligned, False if not aligned
    Return type:bool
    -
    - -
    -
    -element_isaligned(other)[source]
    -

    check if matrices are aligned for element-wise operations

    - --- - - - - - - - -
    Parameters:other (Matrix) –
    Returns:bool – True if aligned, False if not aligned
    Return type:bool
    -
    - -
    -
    -newx
    -

    return a copy of x

    - --- - - - - - -
    Returns:numpy.ndarray
    Return type:numpy.ndarray
    -
    - -
    -
    -x
    -

    return a reference to x

    - --- - - - - - -
    Returns:numpy.ndarray
    Return type:numpy.ndarray
    -
    - -
    -
    -as_2d
    -

    get a 2D representation of x. If not self.isdiagonal, simply -return reference to self.x, otherwise, constructs and returns -a 2D, diagonal ndarray

    - --- - - - - - -
    Returns:numpy.ndarray
    Return type:numpy.ndarray
    -
    - -
    -
    -shape
    -

    get the implied, 2D shape of self

    - --- - - - - - -
    Returns:tuple – length 2 tuple of ints
    Return type:tuple
    -
    - -
    -
    -ncol
    -

    length of second dimension

    - --- - - - - - -
    Returns:int – number of columns
    Return type:int
    -
    - -
    -
    -nrow
    -

    length of first dimensions

    - --- - - - - - -
    Returns:int – number of rows
    Return type:int
    -
    - -
    -
    -T
    -

    wrapper function for Matrix.transpose() method

    -
    - -
    -
    -transpose
    -

    transpose operation of self

    - --- - - - - - -
    Returns:Matrix – transpose of self
    Return type:Matrix
    -
    - -
    -
    -inv
    -

    inversion operation of self

    - --- - - - - - -
    Returns:Matrix – inverse of self
    Return type:Matrix
    -
    - -
    -
    -get_maxsing(eigthresh=1e-05)[source]
    -

    Get the number of singular components with a singular -value ratio greater than or equal to eigthresh

    - --- - - - - - - - -
    Parameters:eigthresh (float) – the ratio of the largest to smallest singular value
    Returns:int – number of singular components
    Return type:int
    -
    - -
    -
    -pseudo_inv_components(maxsing=None, eigthresh=1e-05)[source]
    -

    Get the truncated SVD components

    - --- - - - - - -
    Parameters:
      -
    • maxsing (int) – the number of singular components to use. If None, -maxsing is calculated using Matrix.get_maxsing() and eigthresh
    • -
    • eigthresh (float) – the ratio of largest to smallest singular components to use -for truncation. Ignored if maxsing is not None
    • -
    -
    Returns:

      -
    • u (Matrix) – truncated left singular vectors
    • -
    • s (Matrix) – truncated singular value matrix
    • -
    • v (Matrix) – truncated right singular vectors
    • -
    -

    -
    -
    - -
    -
    -pseudo_inv(maxsing=None, eigthresh=1e-05)[source]
    -

    The pseudo inverse of self. Formed using truncated singular -value decomposition and Matrix.pseudo_inv_components

    - --- - - - - - - - -
    Parameters:
      -
    • maxsing (int) – the number of singular components to use. If None, -maxsing is calculated using Matrix.get_maxsing() and eigthresh
    • -
    • eigthresh (float) – the ratio of largest to smallest singular components to use -for truncation. Ignored if maxsing is not None
    • -
    -
    Returns:

    Matrix

    -
    Return type:

    Matrix

    -
    -
    - -
    -
    -sqrt
    -

    square root operation

    - --- - - - - - -
    Returns:Matrix – square root of self
    Return type:Matrix
    -
    - -
    -
    -full_s
    -

    Get the full singular value matrix of self

    - --- - - - - - -
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -s
    -

    the singular value (diagonal) Matrix

    - --- - - - - - -
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -u
    -

    the left singular vector Matrix

    - --- - - - - - -
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -v
    -

    the right singular vector Matrix

    - --- - - - - - -
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -zero2d
    -

    get an 2D instance of self with all zeros

    - --- - - - - - -
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -static find_rowcol_indices(names, row_names, col_names, axis=None)[source]
    -
    - -
    -
    -indices(names, axis=None)[source]
    -
    -
    get the row and col indices of names. If axis is None, two ndarrays
    -
    are returned, corresponding the indices of names for each axis
    -
    - --- - - - - - - - -
    Parameters:
      -
    • names (iterable) – column and/or row names
    • -
    • axis ((int) (optional)) – the axis to search.
    • -
    -
    Returns:

    numpy.ndarray – indices of names.

    -
    Return type:

    numpy.ndarray

    -
    -
    - -
    -
    -old_indices(names, axis=None)[source]
    -
    -
    get the row and col indices of names. If axis is None, two ndarrays
    -
    are returned, corresponding the indices of names for each axis
    -
    - --- - - - - - - - -
    Parameters:
      -
    • names (iterable) – column and/or row names
    • -
    • axis ((int) (optional)) – the axis to search.
    • -
    -
    Returns:

    numpy.ndarray – indices of names.

    -
    Return type:

    numpy.ndarray

    -
    -
    - -
    -
    -align(names, axis=None)[source]
    -

    reorder self by names. If axis is None, reorder both indices

    - --- - - - -
    Parameters:
      -
    • names (iterable) – names in rowS andor columnS
    • -
    • axis ((int)) – the axis to reorder. if None, reorder both axes
    • -
    -
    -
    - -
    -
    -get(row_names=None, col_names=None, drop=False)[source]
    -

    get a new Matrix instance ordered on row_names or col_names

    - --- - - - - - - - -
    Parameters:
      -
    • row_names (iterable) – row_names for new Matrix
    • -
    • col_names (iterable) – col_names for new Matrix
    • -
    • drop (bool) – flag to remove row_names and/or col_names
    • -
    -
    Returns:

    Matrix

    -
    Return type:

    Matrix

    -
    -
    - -
    -
    -copy()[source]
    -
    - -
    -
    -drop(names, axis)[source]
    -

    drop elements from self in place

    - --- - - - -
    Parameters:
      -
    • names (iterable) – names to drop
    • -
    • axis ((int)) – the axis to drop from. must be in [0,1]
    • -
    -
    -
    - -
    -
    -extract(row_names=None, col_names=None)[source]
    -

    wrapper method that Matrix.gets() then Matrix.drops() elements. -one of row_names or col_names must be not None.

    - --- - - - - - - - -
    Parameters:
      -
    • row_names (iterable) – row names to extract
    • -
    • col_names ((enumerate)) – col_names to extract
    • -
    -
    Returns:

    Matrix

    -
    Return type:

    Matrix

    -
    -
    - -
    -
    -get_diagonal_vector(col_name='diag')[source]
    -

    Get a new Matrix instance that is the diagonal of self. The -shape of the new matrix is (self.shape[0],1). Self must be square

    - --- - - - - - - - -
    Parameters:col_name – str -the name of the column in the new Matrix
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -to_coo(filename, droptol=None, chunk=None)[source]
    -

    write a PEST-compatible binary file. The data format is -[int,int,float] for i,j,value. It is autodetected during -the read with Matrix.from_binary().

    - --- - - - -
    Parameters:
      -
    • filename (str) – filename to save binary file
    • -
    • droptol (float) – absolute value tolerance to make values smaller than zero. Default is None
    • -
    • chunk (int) – number of elements to write in a single pass. Default is None
    • -
    -
    -
    - -
    -
    -to_binary(filename, droptol=None, chunk=None)[source]
    -

    write a PEST-compatible binary file. The format is the same -as the format used to storage a PEST Jacobian matrix

    - --- - - - -
    Parameters:
      -
    • filename (str) – filename to save binary file
    • -
    • droptol (float) – absolute value tolerance to make values smaller than zero. Default is None
    • -
    • chunk (int) – number of elements to write in a single pass. Default is None
    • -
    -
    -
    - -
    -
    -classmethod from_binary(filename)[source]
    -

    class method load from PEST-compatible binary file into a -Matrix instance

    - --- - - - - - - - -
    Parameters:filename (str) – filename to read
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -static read_binary(filename, sparse=False)[source]
    -
    - -
    -
    -classmethod from_fortranfile(filename)[source]
    -
    -
    a binary load method to accommodate one of the many
    -
    bizarre fortran binary writing formats
    -
    - --- - - - - - - - -
    Parameters:filename (str) – name of the binary matrix file
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -to_ascii(out_filename, icode=2)[source]
    -

    write a PEST-compatible ASCII Matrix/vector file

    - --- - - - -
    Parameters:
      -
    • out_filename (str) – output filename
    • -
    • icode ((int)) – PEST-style info code for Matrix style
    • -
    -
    -
    - -
    -
    -classmethod from_ascii(filename)[source]
    -

    load a pest-compatible ASCII Matrix/vector file into a -Matrix instance

    - --- - - - -
    Parameters:filename (str) – name of the file to read
    -
    - -
    -
    -static read_ascii(filename)[source]
    -
    - -
    -
    -df()[source]
    -

    wrapper of Matrix.to_dataframe()

    -
    - -
    -
    -classmethod from_dataframe(df)[source]
    -
    -
    class method to create a new Matrix instance from a
    -
    pandas.DataFrame
    -
    - --- - - - - - - - -
    Parameters:df (pandas.DataFrame) –
    Returns:Matrix
    Return type:Matrix
    -
    - -
    -
    -classmethod from_names(row_names, col_names, isdiagonal=False, autoalign=True, random=False)[source]
    -

    class method to create a new Matrix instance from -row names and column names, filled with trash

    - --- - - - - - - - -
    Parameters:
      -
    • row_names (iterable) – row names for the new matrix
    • -
    • col_names (iterable) – col_names for the new matrix
    • -
    • isdiagonal (bool) – flag for diagonal matrix. Default is False
    • -
    • autoalign (bool) – flag for autoaligning new matrix -during linear algebra calcs. Default -is True
    • -
    • random (bool) – flag for contents of the trash matrix. -If True, fill with random numbers, if False, fill with zeros -Default is False
    • -
    -
    Returns:

    mat – the new Matrix instance

    -
    Return type:

    Matrix

    -
    -
    - -
    -
    -to_dataframe()[source]
    -

    return a pandas.DataFrame representation of the Matrix object

    - --- - - - - - -
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -to_sparse(trunc=0.0)[source]
    -

    get the COO sparse Matrix representation of the Matrix

    - --- - - - - - -
    Returns:scipy.sparse.Matrix
    Return type:scipy.sparse.Matrix
    -
    - -
    -
    -extend(other, inplace=False)[source]
    -

    extend self with the elements of other.

    - --- - - - - - - - -
    Parameters:
      -
    • other ((Matrix)) – the Matrix to extend self by
    • -
    • inplace (bool) – inplace = True not implemented
    • -
    -
    Returns:

    Matrix – if not inplace

    -
    Return type:

    Matrix

    -
    -
    - -
    - -
    -
    -class pyemu.mat.mat_handler.Jco(x=None, row_names=[], col_names=[], isdiagonal=False, autoalign=True)[source]
    -

    Bases: pyemu.mat.mat_handler.Matrix

    -

    a thin wrapper class to get more intuitive attribute names. Functions -exactly like Matrix

    -
    -
    -par_names
    -

    thin wrapper around Matrix.col_names

    - --- - - - - - -
    Returns:list – parameter names
    Return type:list
    -
    - -
    -
    -obs_names
    -

    thin wrapper around Matrix.row_names

    - --- - - - - - -
    Returns:list – observation names
    Return type:list
    -
    - -
    -
    -npar
    -

    number of parameters in the Jco

    - --- - - - - - -
    Returns:int – number of parameters (columns)
    Return type:int
    -
    - -
    -
    -nobs
    -

    number of observations in the Jco

    - --- - - - - - -
    Returns:int – number of observations (rows)
    Return type:int
    -
    - -
    -
    -replace_cols(other, parnames=None)[source]
    -

    Replaces columns in one Matrix with columns from another. -Intended for Jacobian matrices replacing parameters.

    - --- - - - -
    Parameters:
      -
    • other (Matrix) – Matrix to use for replacing columns in self
    • -
    • parnames (list) – parameter (column) names to use in other. If None, all -columns in other are used
    • -
    -
    -
    - -
    -
    -classmethod from_pst(pst, random=False)[source]
    -

    construct a new empty Jco from a control file filled -with trash

    - --- - - - - - - - -
    Parameters:
      -
    • pst (Pst) – a control file instance. If type is ‘str’, -Pst is loaded from filename
    • -
    • random (bool) – flag for contents of the trash matrix. -If True, fill with random numbers, if False, fill with zeros -Default is False
    • -
    -
    Returns:

    jco – the new Jco instance

    -
    Return type:

    Jco

    -
    -
    - -
    - -
    -
    -class pyemu.mat.mat_handler.Cov(x=None, names=[], row_names=[], col_names=[], isdiagonal=False, autoalign=True)[source]
    -

    Bases: pyemu.mat.mat_handler.Matrix

    -

    a subclass of Matrix for handling diagonal or dense Covariance matrices -todo:block diagonal

    -
    -
    -identity
    -

    get an identity Matrix like self

    - --- - - - - - -
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -zero
    -

    get an instance of self with all zeros

    - --- - - - - - -
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -condition_on(conditioning_elements)[source]
    -
    -
    get a new Covariance object that is conditional on knowing some
    -
    elements. uses Schur’s complement for conditional Covariance -propagation
    -
    - --- - - - - - - - -
    Parameters:conditioning_elements (iterable) – names of elements to condition on
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -draw(mean=1.0)[source]
    -

    Obtain a random draw from a covariance matrix either with mean==1 -or with specified mean vector

    - --- - - - - - - - -
    Parameters:mean (scalar of enumerable of length self.shape[0]) – mean values. either a scalar applied to to the entire -vector of length N or an N-length vector
    Returns:numpy.nparray – A vector of conditioned values, sampled -using the covariance matrix (self) and applied to the mean
    Return type:numpy.ndarray
    -
    - -
    -
    -names
    -

    wrapper for getting row_names. row_names == col_names for Cov

    - --- - - - - - -
    Returns:list – names
    Return type:list
    -
    - -
    -
    -replace(other)[source]
    -

    replace elements in the covariance matrix with elements from other. -if other is not diagonal, then self becomes non diagonal

    - --- - - - -
    Parameters:other (Cov) – the Cov to replace elements in self with
    -
    -

    Note

    -

    operates in place

    -
    -
    - -
    -
    -to_uncfile(unc_file, covmat_file='Cov.mat', var_mult=1.0)[source]
    -

    write a PEST-compatible uncertainty file

    - --- - - - -
    Parameters:
      -
    • unc_file (str) – filename of the uncertainty file
    • -
    • covmat_file (str Covariance Matrix filename. Default is) –
    • -
    • If None, and Cov.isdiaonal, then a standard deviation ("Cov.mat".) –
    • -
    • of the uncertainty file is written. Exception raised if None (form) –
    • -
    • not Cov.isdiagonal (and) –
    • -
    • var_mult (float) – variance multiplier for the covmat_file entry
    • -
    -
    -
    - -
    -
    -classmethod from_obsweights(pst_file)[source]
    -

    instantiates a Cov instance from observation weights in -a PEST control file. Calls Cov.from_observation_data()

    - --- - - - - - - - -
    Parameters:pst_file (str) – pest control file name
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -classmethod from_observation_data(pst)[source]
    -
    -
    instantiates a Cov from a pandas dataframe
    -
    of pyemu.Pst.observation_data
    -
    - --- - - - - - - - -
    Parameters:pst (pyemu.Pst) –
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -classmethod from_parbounds(pst_file, sigma_range=4.0, scale_offset=True)[source]
    -

    Instantiates a Cov from a pest control file parameter data section. -Calls Cov.from_parameter_data()

    - --- - - - - - - - -
    Parameters:
      -
    • pst_file (str) – pest control file name
    • -
    • sigma_range (float) – defines range of upper bound - lower bound in terms of standard -deviation (sigma). For example, if sigma_range = 4, the bounds -represent 4 * sigma. Default is 4.0, representing approximately -95% confidence of implied normal distribution
    • -
    • scale_offset (bool) – flag to apply scale and offset to parameter upper and lower -bounds before calculating varaince. Default is True
    • -
    -
    Returns:

    Cov

    -
    Return type:

    Cov

    -
    -
    - -
    -
    -classmethod from_parameter_data(pst, sigma_range=4.0, scale_offset=True)[source]
    -

    load Covariances from a pandas dataframe of -pyemu.Pst.parameter_data

    - --- - - - - - - - -
    Parameters:
      -
    • pst ((pyemu.Pst)) –
    • -
    • sigma_range (float) – defines range of upper bound - lower bound in terms of standard -deviation (sigma). For example, if sigma_range = 4, the bounds -represent 4 * sigma. Default is 4.0, representing approximately -95% confidence of implied normal distribution
    • -
    • scale_offset (bool) – flag to apply scale and offset to parameter upper and lower -bounds before calculating varaince. Default is True
    • -
    -
    Returns:

    Cov

    -
    Return type:

    Cov

    -
    -
    - -
    -
    -classmethod from_uncfile(filename)[source]
    -

    instaniates a Cov from a PEST-compatible uncertainty file

    - --- - - - - - - - -
    Parameters:filename (str) – uncertainty file name
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -static get_uncfile_dimensions(filename)[source]
    -

    quickly read an uncertainty file to find the dimensions

    - --- - - - - - - - -
    Parameters:filename (str) – uncertainty filename
    Returns:nentries – number of elements in file
    Return type:int
    -
    - -
    -
    -classmethod identity_like(other)[source]
    -

    Get an identity matrix Cov instance like other

    - --- - - - - - - - -
    Parameters:other (Matrix) – must be square
    Returns:Cov
    Return type:Cov
    -
    - -
    -
    -to_pearson()[source]
    -

    Convert Cov instance to Pearson correlation coefficient -matrix

    - --- - - - - - -
    Returns:Matrix – this is on purpose so that it is clear the returned -instance is not a Cov
    Return type:Matrix
    -
    - -
    - -
    -
    -class pyemu.mat.mat_handler.SparseMatrix(x, row_names, col_names)[source]
    -

    Bases: object

    -

    Note: less rigid about references since this class is for big matrices and -don’t want to be making copies

    -
    -
    -shape
    -
    - -
    -
    -classmethod from_binary(filename)[source]
    -
    - -
    -
    -to_coo(filename)[source]
    -
    - -
    -
    -classmethod from_coo(filename)[source]
    -
    - -
    -
    -to_matrix()[source]
    -
    - -
    -
    -classmethod from_matrix(matrix, droptol=None)[source]
    -
    - -
    -
    -block_extend_ip(other)[source]
    -

    designed for combining dense martrices into a sparse block diagonal matrix. -other must not have any rows or columns in common with self

    -
    - -
    -
    -get_matrix(row_names, col_names)[source]
    -
    - -
    -
    -get_sparse_matrix(row_names, col_names)[source]
    -
    - -
    - -
    -
    -

    Module contents

    -

    This module contains classes for handling matrices in a linear algebra setting. -The primary objects are the Matrix() and Cov(). These objects overload most numerical -operators to autoalign the elements based on row and column names.

    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/pyemu.pst.html b/docs/_build/html/source/pyemu.pst.html deleted file mode 100644 index 38a799e5d..000000000 --- a/docs/_build/html/source/pyemu.pst.html +++ /dev/null @@ -1,1717 +0,0 @@ - - - - - - - - pyemu.pst package — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    pyemu.pst package

    -
    -

    Submodules

    -
    -
    -

    pyemu.pst.pst_controldata module

    -

    This module contains several class definitions for parts of the -PEST control file: ControlData, RegData and SvdData. These -classes are automatically created and appended to a Pst object; -users shouldn’t need to deal with these classes explicitly

    -
    -
    -pyemu.pst.pst_controldata.SFMT(x)
    -
    - -
    -
    -pyemu.pst.pst_controldata.SFMT_LONG(x)
    -
    - -
    -
    -pyemu.pst.pst_controldata.IFMT(x)
    -
    - -
    -
    -pyemu.pst.pst_controldata.FFMT(x)
    -
    - -
    -
    -class pyemu.pst.pst_controldata.RegData[source]
    -

    Bases: object

    -

    an object that encapsulates the regularization section -of the PEST control file

    -
    -
    -write(f)[source]
    -

    write the regularization section to an open -file handle

    - --- - - - -
    Parameters:f (file handle) –
    -
    - -
    - -
    -
    -class pyemu.pst.pst_controldata.SvdData(**kwargs)[source]
    -

    Bases: object

    -

    an object that encapsulates the singular value decomposition -section of the PEST control file

    -
    -
    -write(f)[source]
    -

    write an SVD section to a file handle

    - --- - - - -
    Parameters:f (file handle) –
    -
    - -
    -
    -parse_values_from_lines(lines)[source]
    -

    parse values from lines of the SVD section

    - --- - - - -
    Parameters:lines (list) –
    -
    - -
    - -
    -
    -class pyemu.pst.pst_controldata.ControlData[source]
    -

    Bases: object

    -

    an object that encapsulates the control data section -of the PEST control file

    -
    -
    -static get_dataframe()[source]
    -

    get a generic (default) control section dataframe

    - --- - - - - - -
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -parse_values_from_lines(lines)[source]
    -

    cast the string lines for a pest control file into actual inputs

    - --- - - - -
    Parameters:lines (list) – strings from pest control file
    -
    - -
    -
    -copy()[source]
    -
    - -
    -
    -formatted_values
    -

    list the entries and current values in the control data section

    - --- - - - - - -
    Returns:formatted_values
    Return type:pandas.Series
    -
    - -
    -
    -write(f)[source]
    -

    write control data section to a file

    - --- - - - -
    Parameters:f (file handle or string filename) –
    -
    - -
    - -
    -
    -

    pyemu.pst.pst_handler module

    -

    This module contains most of the pyemu.Pst object definition. This object -is the primary mechanism for dealing with PEST control files

    -
    -
    -class pyemu.pst.pst_handler.Pst(filename, load=True, resfile=None, flex=False)[source]
    -

    Bases: object

    -

    basic class for handling pest control files to support linear analysis -as well as replicate some of the functionality of the pest utilities

    - --- - - - - - - - -
    Parameters:
      -
    • filename (str) – the name of the control file
    • -
    • load ((boolean)) – flag to load the control file. Default is True
    • -
    • resfile (str) – corresponding residual file. If None, a residual file -with the control file base name is sought. Default is None
    • -
    -
    Returns:

    Pst – a control file object

    -
    Return type:

    Pst

    -
    -
    -
    -parameter_data = None
    -

    pandas.DataFrame – parameter data loaded from pst control file

    -
    - -
    -
    -observation_data = None
    -

    pandas.DataFrame – observation data loaded from pst control file

    -
    - -
    -
    -prior_information = None
    -

    pandas.DataFrame – prior_information data loaded from pst control file

    -
    - -
    -
    -control_data = None
    -

    pyemu.pst.pst_controldata.ControlData – control data object loaded from pst control file

    -
    - -
    -
    -svd_data = None
    -

    pyemu.pst.pst_controldata.SvdData – singular value decomposition (SVD) object loaded from pst control file

    -
    - -
    -
    -reg_data = None
    -

    pyemu.pst.pst_controldata.RegData – regularization data object loaded from pst control file

    -
    - -
    -
    -classmethod from_par_obs_names(par_names=['par1'], obs_names=['obs1'])[source]
    -
    - -
    -
    -phi
    -

    get the weighted total objective function

    - --- - - - - - -
    Returns:phi – sum of squared residuals
    Return type:float
    -
    - -
    -
    -phi_components
    -

    get the individual components of the total objective function

    - --- - - - - - - - -
    Returns:

    dict – dictionary of observation group, contribution to total phi

    -
    Return type:

    dict

    -
    Raises:
      -
    • Assertion error if Pst.observation_data groups don’t match
    • -
    • Pst.res groups
    • -
    -
    -
    - -
    -
    -phi_components_normalized
    -
    -
    get the individual components of the total objective function
    -
    normalized to the total PHI being 1.0
    -
    - --- - - - - - - - -
    Returns:

    dict – dictionary of observation group, normalized contribution to total phi

    -
    Return type:

    dict

    -
    Raises:
      -
    • Assertion error if self.observation_data groups don’t match
    • -
    • self.res groups
    • -
    -
    -
    - -
    -
    -set_res(res)[source]
    -

    reset the private Pst.res attribute

    - --- - - - -
    Parameters:res ((varies)) – something to use as Pst.res attribute
    -
    - -
    -
    -res
    -

    get the residuals dataframe attribute

    - --- - - - - - -
    Returns:res
    Return type:pandas.DataFrame
    -
    -

    Note

    -

    if the Pst.__res attribute has not been loaded, -this call loads the res dataframe from a file

    -
    -
    - -
    -
    -nprior
    -

    number of prior information equations

    - --- - - - - - -
    Returns:nprior – the number of prior info equations
    Return type:int
    -
    - -
    -
    -nnz_obs
    -

    get the number of non-zero weighted observations

    - --- - - - - - -
    Returns:nnz_obs – the number of non-zeros weighted observations
    Return type:int
    -
    - -
    -
    -nobs
    -

    get the number of observations

    - --- - - - - - -
    Returns:nobs – the number of observations
    Return type:int
    -
    - -
    -
    -npar_adj
    -

    get the number of adjustable parameters (not fixed or tied)

    - --- - - - - - -
    Returns:npar_adj – the number of adjustable parameters
    Return type:int
    -
    - -
    -
    -npar
    -

    get number of parameters

    - --- - - - - - -
    Returns:npar – the number of parameters
    Return type:int
    -
    - -
    -
    -forecast_names
    -

    get the forecast names from the pestpp options (if any). -Returns None if no forecasts are named

    - --- - - - - - -
    Returns:forecast_names – a list of forecast names.
    Return type:list
    -
    - -
    -
    -obs_groups
    -

    get the observation groups

    - --- - - - - - -
    Returns:obs_groups – a list of unique observation groups
    Return type:list
    -
    - -
    -
    -nnz_obs_groups
    -
    -
    get the observation groups that contain at least one non-zero weighted
    -
    observation
    -
    - --- - - - - - -
    Returns:nnz_obs_groups – a list of observation groups that contain at -least one non-zero weighted observation
    Return type:list
    -
    - -
    -
    -par_groups
    -

    get the parameter groups

    - --- - - - - - -
    Returns:par_groups – a list of parameter groups
    Return type:list
    -
    - -
    -
    -prior_groups
    -

    get the prior info groups

    - --- - - - - - -
    Returns:prior_groups – a list of prior information groups
    Return type:list
    -
    - -
    -
    -prior_names
    -

    get the prior information names

    - --- - - - - - -
    Returns:prior_names – a list of prior information names
    Return type:list
    -
    - -
    -
    -par_names
    -

    get the parameter names

    - --- - - - - - -
    Returns:par_names – a list of parameter names
    Return type:list
    -
    - -
    -
    -adj_par_names
    -

    get the adjustable (not fixed or tied) parameter names

    - --- - - - - - -
    Returns:adj_par_names – list of adjustable (not fixed or tied) parameter names
    Return type:list
    -
    - -
    -
    -obs_names
    -

    get the observation names

    - --- - - - - - -
    Returns:obs_names – a list of observation names
    Return type:list
    -
    - -
    -
    -nnz_obs_names
    -

    get the non-zero weight observation names

    - --- - - - - - -
    Returns:nnz_obs_names – a list of non-zero weighted observation names
    Return type:list
    -
    - -
    -
    -zero_weight_obs_names
    -

    get the zero-weighted observation names

    - --- - - - - - -
    Returns:zero_weight_obs_names – a list of zero-weighted observation names
    Return type:list
    -
    - -
    -
    -estimation
    -

    check if the control_data.pestmode is set to estimation

    - --- - - - - - -
    Returns:estimation – True if pestmode is estmation, False otherwise
    Return type:bool
    -
    - -
    -
    -tied
    -
    - -
    -
    -flex_load(filename)[source]
    -
    - -
    -
    -load(filename)[source]
    -

    load the pest control file information

    - --- - - - - - -
    Parameters:filename (str) – pst filename
    Raises:lots of exceptions for incorrect format
    -
    - -
    -
    -rectify_pgroups()[source]
    -

    private method to synchronize parameter groups section with -the parameter data section

    -
    - -
    -
    -add_pi_equation(par_names, pilbl=None, rhs=0.0, weight=1.0, obs_group='pi_obgnme', coef_dict={})[source]
    -

    a helper to construct a new prior information equation.

    - --- - - - -
    Parameters:
      -
    • par_names (list) – parameter names in the equation
    • -
    • pilbl (str) – name to assign the prior information equation. If None, -a generic equation name is formed. Default is None
    • -
    • rhs ((float)) – the right-hand side of the equation
    • -
    • weight ((float)) – the weight of the equation
    • -
    • obs_group (str) – the observation group for the equation. Default is ‘pi_obgnme’
    • -
    • coef_dict (dict) – a dictionary of parameter name, coefficient pairs to assign -leading coefficients for one or more parameters in the equation. -If a parameter is not listed, 1.0 is used for its coefficients. -Default is {}
    • -
    -
    -
    - -
    -
    -rectify_pi()[source]
    -

    rectify the prior information equation with the current state of the -parameter_data dataframe. Equations that list fixed, tied or missing parameters -are removed. This method is called during Pst.write()

    -
    - -
    -
    -sanity_checks()[source]
    -
    - -
    -
    -write(new_filename, update_regul=False)[source]
    -

    write a pest control file

    - --- - - - -
    Parameters:
      -
    • new_filename (str) – name of the new pest control file
    • -
    • update_regul ((boolean)) – flag to update zero-order Tikhonov prior information -equations to prefer the current parameter values
    • -
    -
    -
    - -
    -
    -get(par_names=None, obs_names=None)[source]
    -

    get a new pst object with subset of parameters and/or observations

    - --- - - - - - - - -
    Parameters:
      -
    • par_names (list) – a list of parameter names to have in the new Pst instance. -If None, all parameters are in the new Pst instance. Default -is None
    • -
    • obs_names (list) – a list of observation names to have in the new Pst instance. -If None, all observations are in teh new Pst instance. Default -is None
    • -
    -
    Returns:

    Pst – a new Pst instance

    -
    Return type:

    Pst

    -
    -
    - -
    -
    -zero_order_tikhonov(parbounds=True)[source]
    -
    - -
    -
    -parrep(parfile=None, enforce_bounds=True)[source]
    -
    -
    replicates the pest parrep util. replaces the parval1 field in the
    -
    parameter data section dataframe
    -
    - --- - - - -
    Parameters:
      -
    • parfile (str) – parameter file to use. If None, try to use -a parameter file that corresponds to the case name. -Default is None
    • -
    • enforce_hounds (bool) – flag to enforce parameter bounds after parameter values are updated. -This is useful because PEST and PEST++ round the parameter values in the -par file, which may cause slight bound violations
    • -
    -
    -
    - -
    -
    -adjust_weights_recfile(recfile=None)[source]
    -

    adjusts the weights by group of the observations based on the phi components -in a pest record file so that total phi is equal to the number of -non-zero weighted observations

    - --- - - - -
    Parameters:recfile (str) – record file name. If None, try to use a record file -with the Pst case name. Default is None
    -
    - -
    -
    -adjust_weights_resfile(resfile=None)[source]
    -

    adjusts the weights by group of the observations based on the phi components -in a pest residual file so that total phi is equal to the number of -non-zero weighted observations

    - --- - - - -
    Parameters:resfile (str) – residual file name. If None, try to use a residual file -with the Pst case name. Default is None
    -
    - -
    -
    -adjust_weights_by_list(obslist, weight)[source]
    -

    reset the weight for a list of observation names. Supports the -data worth analyses in pyemu.Schur class

    - --- - - - -
    Parameters:
      -
    • obslist (list) – list of observation names
    • -
    • weight ((float)) – new weight to assign
    • -
    -
    -
    - -
    -
    -adjust_weights(obs_dict=None, obsgrp_dict=None)[source]
    -

    reset the weights of observation groups to contribute a specified -amount to the composite objective function

    - --- - - - -
    Parameters:
      -
    • obs_dict (dict) – dictionary of obs name,new contribution pairs
    • -
    • obsgrp_dict (dict) – dictionary of obs group name,contribution pairs
    • -
    -
    -
    -

    Note

    -

    if all observations in a named obs group have zero weight, they will be -assigned a non-zero weight so that the request phi contribution -can be met. Similarly, any observations listed in obs_dict with zero -weight will also be reset

    -
    -
    - -
    -
    -proportional_weights(fraction_stdev=1.0, wmax=100.0, leave_zero=True)[source]
    -

    setup weights inversely proportional to the observation value

    - --- - - - -
    Parameters:
      -
    • fraction_stdev (float) – the fraction portion of the observation -val to treat as the standard deviation. set to 1.0 for -inversely proportional
    • -
    • wmax (float) – maximum weight to allow
    • -
    • leave_zero (bool) – flag to leave existing zero weights
    • -
    -
    -
    - -
    -
    -calculate_pertubations()[source]
    -

    experimental method to calculate finite difference parameter -pertubations. The pertubation values are added to the -Pst.parameter_data attribute

    -
    -

    Note

    -

    user beware!

    -
    -
    - -
    -
    -build_increments()[source]
    -

    experimental method to calculate parameter increments for use -in the finite difference pertubation calculations

    -
    -

    Note

    -

    user beware!

    -
    -
    - -
    -
    -add_transform_columns()[source]
    -

    add transformed values to the Pst.parameter_data attribute

    -
    - -
    -
    -enforce_bounds()[source]
    -

    enforce bounds violation resulting from the -parameter pertubation calculations

    -
    - -
    -
    -classmethod from_io_files(tpl_files, in_files, ins_files, out_files, pst_filename=None)[source]
    -

    create a Pst instance from model interface files. Assigns generic values for -parameter info. Tries to use INSCHEK to set somewhat meaningful observation -values

    - --- - - - - - - - -
    Parameters:
      -
    • tpl_files (list) – list of template file names
    • -
    • in_files (list) – list of model input file names (pairs with template files)
    • -
    • ins_files (list) – list of instruction file names
    • -
    • out_files (list) – list of model output file names (pairs with instruction files)
    • -
    • pst_filename (str) – name of control file to write. If None, no file is written. -Default is None
    • -
    -
    Returns:

    Pst

    -
    Return type:

    Pst

    -
    -
    -

    Note

    -

    calls pyemu.helpers.pst_from_io_files()

    -
    -
    - -
    -
    -add_parameters(template_file, in_file=None, pst_path=None)[source]
    -

    add new parameters to a control file

    - --- - - - - - - - -
    Parameters:
      -
    • template_file (str) – template file
    • -
    • in_file (str(optional)) – model input file. If None, template_file.replace(‘.tpl’,’‘) is used
    • -
    • pst_path (str(optional)) – the path to append to the template_file and in_file in the control file. If -not None, then any existing path in front of the template or in file is split off -and pst_path is prepended. Default is None
    • -
    -
    Returns:

    new_par_data – the data for the new parameters that were added. If no new parameters are in the -new template file, returns None

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    populates the new parameter information with default values

    -
    -
    - -
    -
    -add_observations(ins_file, out_file, pst_path=None, inschek=True)[source]
    -

    add new parameters to a control file

    - --- - - - - - - - -
    Parameters:
      -
    • ins_file (str) – instruction file
    • -
    • out_file (str) – model output file
    • -
    • pst_path (str(optional)) – the path to append to the instruction file and out file in the control file. If -not None, then any existing path in front of the template or in file is split off -and pst_path is prepended. Default is None
    • -
    • inschek (bool) – flag to run inschek. If successful, inscheck outputs are used as obsvals
    • -
    -
    Returns:

    new_obs_data – the data for the new observations that were added

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    populates the new observation information with default values

    -
    -
    - -
    -
    -write_input_files()[source]
    -

    writes model input files using template files and current parvals. -just syntatic sugar for pst_utils.write_input_files()

    -
    -

    Note

    -

    adds “parval1_trans” column to Pst.parameter_data that includes the -effect of scale and offset

    -
    -
    - -
    -
    -get_res_stats(nonzero=True)[source]
    -

    get some common residual stats from the current obsvals, -weights and grouping in self.observation_data and the modelled values in -self.res. The key here is ‘current’ because if obsval, weights and/or -groupings have changed in self.observation_data since the res file was generated -then the current values for obsval, weight and group are used

    - --- - - - - - - - -
    Parameters:nonzero (bool) – calculate stats using only nonzero-weighted observations. This may seem -obsvious to most users, but you never know….
    Returns:df – a dataframe with columns for groups names and indices of statistic name.
    Return type:pd.DataFrame
    -
    -

    Note

    -

    the normalized RMSE is normalized against the obsval range (max - min)

    -
    -
    - -
    -
    -plot(kind=None, **kwargs)[source]
    -

    method to plot various parts of the control. Depending -on ‘kind’ argument, a multipage pdf is written

    - --- - - - - - - - -
    Parameters:
      -
    • kind (str) – options are ‘prior’ (prior parameter histograms, ‘1to1’ (line of equality -and sim vs res), ‘obs_v_sim’ (time series using datetime suffix), ‘phi_pie’ -(pie chart of phi components)
    • -
    • kwargs (dict) – optional args for plots
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -write_par_summary_table(filename=None, group_names=None, sigma_range=4.0)[source]
    -

    write a stand alone parameter summary latex table

    - --- - - - - - - - -
    Parameters:
      -
    • filename (str) – latex filename. If None, use <case>.par.tex. Default is None
    • -
    • group_names (dict) – par group names : table names for example {“w0”:”well stress period 1”}. -Default is None
    • -
    • sigma_range (float) – number of standard deviations represented by parameter bounds. Default -is 4.0, implying 95% confidence bounds
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -write_obs_summary_table(filename=None, group_names=None)[source]
    -

    write a stand alone observation summary latex table

    - --- - - - - - - - -
    Parameters:
      -
    • filename (str) – latex filename. If None, use <case>.par.tex. Default is None
    • -
    • group_names (dict) – par group names : table names for example {“w0”:”well stress period 1”}. -Default is None
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -run(exe_name='pestpp', cwd=None)[source]
    -

    run a command related to the pst instance. If -write() has been called, then the filename passed to write -is in the command, otherwise the original constructor -filename is used

    -
    -
    exe_name : str
    -
    the name of the executable to call. Default is “pestpp”
    -
    cwd : str
    -
    the directory to execute the command in. If None, -os.path.split(self.filename) is used to find -cwd. Default is None
    -
    -
    - -
    - -
    -
    -

    pyemu.pst.pst_utils module

    -

    This module contains helpers and default values that support -the pyemu.Pst object.

    -
    -
    -pyemu.pst.pst_utils.SFMT(item)[source]
    -
    - -
    -
    -pyemu.pst.pst_utils.SFMT_LONG(x)
    -
    - -
    -
    -pyemu.pst.pst_utils.IFMT(x)
    -
    - -
    -
    -pyemu.pst.pst_utils.FFMT(x)
    -
    - -
    -
    -pyemu.pst.pst_utils.str_con(item)[source]
    -
    - -
    -
    -pyemu.pst.pst_utils.read_resfile(resfile)[source]
    -

    load a residual file into a pandas.DataFrame

    - --- - - - - - - - -
    Parameters:resfile (str) – residual file name
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.pst.pst_utils.read_parfile(parfile)[source]
    -

    load a pest-compatible .par file into a pandas.DataFrame

    - --- - - - - - - - -
    Parameters:parfile (str) – pest parameter file name
    Returns:pandas.DataFrame
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.pst.pst_utils.write_parfile(df, parfile)[source]
    -

    write a pest parameter file from a dataframe

    - --- - - - -
    Parameters:
      -
    • df ((pandas.DataFrame)) – dataframe with column names that correspond to the entries -in the parameter data section of a pest control file
    • -
    • parfile (str) – name of the parameter file to write
    • -
    -
    -
    - -
    -
    -pyemu.pst.pst_utils.parse_tpl_file(tpl_file)[source]
    -

    parse a pest template file to get the parameter names

    - --- - - - - - - - -
    Parameters:tpl_file (str) – template file name
    Returns:par_names – list of parameter names
    Return type:list
    -
    - -
    -
    -pyemu.pst.pst_utils.write_input_files(pst)[source]
    -

    write parameter values to a model input files using a template files with -current parameter values (stored in Pst.parameter_data.parval1). -This is a simple implementation of what PEST does. It does not -handle all the special cases, just a basic function…user beware

    - --- - - - -
    Parameters:pst ((pyemu.Pst)) – a Pst instance
    -
    - -
    -
    -pyemu.pst.pst_utils.write_to_template(parvals, tpl_file, in_file)[source]
    -

    write parameter values to model input files using template files

    - --- - - - -
    Parameters:
      -
    • parvals (dict or pandas.Series) – a way to look up parameter values using parameter names
    • -
    • tpl_file (str) – template file
    • -
    • in_file (str) – input file
    • -
    -
    -
    - -
    -
    -pyemu.pst.pst_utils.get_marker_indices(marker, line)[source]
    -

    method to find the start and end parameter markers -on a template file line. Used by write_to_template()

    - --- - - - - - - - -
    Parameters:
      -
    • marker (str) – template file marker char
    • -
    • line (str) – template file line
    • -
    -
    Returns:

    indices – list of start and end indices (zero based)

    -
    Return type:

    list

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.parse_ins_file(ins_file)[source]
    -

    parse a pest instruction file to get observation names

    - --- - - - - - - - -
    Parameters:ins_file (str) – instruction file name
    Returns:
    Return type:list of observation names
    -
    - -
    -
    -pyemu.pst.pst_utils.parse_ins_string(string)[source]
    -

    split up an instruction file line to get the observation names

    - --- - - - - - - - -
    Parameters:string (str) – instruction file line
    Returns:obs_names – list of observation names
    Return type:list
    -
    - -
    -
    -pyemu.pst.pst_utils.populate_dataframe(index, columns, default_dict, dtype)[source]
    -

    helper function to populate a generic Pst dataframe attribute. This -function is called as part of constructing a generic Pst instance

    - --- - - - - - - - -
    Parameters:
      -
    • index ((varies)) – something to use as the dataframe index
    • -
    • columns ((varies)) – something to use as the dataframe columns
    • -
    • default_dict ((dict)) – dictionary of default values for columns
    • -
    • dtype (numpy.dtype) – dtype used to cast dataframe columns
    • -
    -
    Returns:

    new_df

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.generic_pst(par_names=['par1'], obs_names=['obs1'], addreg=False)[source]
    -

    generate a generic pst instance. This can used to later fill in -the Pst parts programatically.

    - --- - - - - - - - -
    Parameters:
      -
    • par_names ((list)) – parameter names to setup
    • -
    • obs_names ((list)) – observation names to setup
    • -
    -
    Returns:

    new_pst

    -
    Return type:

    pyemu.Pst

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.pst_from_io_files(tpl_files, in_files, ins_files, out_files, pst_filename=None)[source]
    -

    generate a new pyemu.Pst instance from model interface files. This -function is emulated in the Pst.from_io_files() class method.

    - --- - - - - - - - -
    Parameters:
      -
    • tpl_files ((list)) – template file names
    • -
    • in_files ((list)) – model input file names
    • -
    • ins_files ((list)) – instruction file names
    • -
    • out_files ((list)) – model output file names
    • -
    • pst_filename (str) – filename to save new pyemu.Pst. If None, Pst is not written. -default is None
    • -
    -
    Returns:

    new_pst

    -
    Return type:

    pyemu.Pst

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.try_run_inschek(pst)[source]
    -

    attempt to run INSCHEK for each instruction file, model output -file pair in a pyemu.Pst. If the run is successful, the INSCHEK written -.obf file is used to populate the pst.observation_data.obsval attribute

    - --- - - - -
    Parameters:pst ((pyemu.Pst)) –
    -
    - -
    -
    -pyemu.pst.pst_utils.try_process_ins_file(ins_file, out_file=None)[source]
    -
    - -
    -
    -pyemu.pst.pst_utils.get_phi_comps_from_recfile(recfile)[source]
    -

    read the phi components from a record file by iteration

    - --- - - - - - - - -
    Parameters:recfile (str) – pest record file name
    Returns:iters – nested dictionary of iteration number, {group,contribution}
    Return type:dict
    -
    - -
    -
    -pyemu.pst.pst_utils.smp_to_ins(smp_filename, ins_filename=None, use_generic_names=False, gwutils_compliant=False, datetime_format=None, prefix='')[source]
    -

    create an instruction file for an smp file

    - --- - - - - - - - -
    Parameters:
      -
    • smp_filename (str) – existing smp file
    • -
    • ins_filename (str) – instruction file to create. If None, create -an instruction file using the smp filename -with the “.ins” suffix
    • -
    • use_generic_names (bool) – flag to force observations names to use a generic -int counter instead of trying to use a datetime str
    • -
    • gwutils_compliant (bool) – flag to use instruction set that is compliant with the -pest gw utils (fixed format instructions). If false, -use free format (with whitespace) instruction set
    • -
    • datetime_format (str) – str to pass to datetime.strptime in the smp_to_dataframe() function
    • -
    • prefix (str) – a prefix to add to the front of the obsnmes. Default is ‘’
    • -
    -
    Returns:

    df – dataframe instance of the smp file with the observation names and -instruction lines as additional columns

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.dataframe_to_smp(dataframe, smp_filename, name_col='name', datetime_col='datetime', value_col='value', datetime_format='dd/mm/yyyy', value_format='{0:15.6E}', max_name_len=12)[source]
    -

    write a dataframe as an smp file

    - --- - - - -
    Parameters:
      -
    • dataframe (pandas.DataFrame) –
    • -
    • smp_filename (str) – smp file to write
    • -
    • name_col (str) – the column in the dataframe the marks the site namne
    • -
    • datetime_col (str) – the column in the dataframe that is a datetime instance
    • -
    • value_col (str) – the column in the dataframe that is the values
    • -
    • datetime_format (str) – either ‘dd/mm/yyyy’ or ‘mm/dd/yyy’
    • -
    • value_format (str) – a python float-compatible format
    • -
    -
    -
    - -
    -
    -pyemu.pst.pst_utils.date_parser(items)[source]
    -

    datetime parser to help load smp files

    - --- - - - - - - - -
    Parameters:items (iterable) – something or somethings to try to parse into datetimes
    Returns:dt – the cast datetime things
    Return type:iterable
    -
    - -
    -
    -pyemu.pst.pst_utils.smp_to_dataframe(smp_filename, datetime_format=None)[source]
    -

    load an smp file into a pandas dataframe (stacked in wide format)

    - --- - - - - - - - -
    Parameters:
      -
    • smp_filename (str) – smp filename to load
    • -
    • datetime_format (str) – should be either “%m/%d/%Y %H:%M:%S” or “%d/%m/%Y %H:%M:%S” -If None, then we will try to deduce the format for you, which -always dangerous
    • -
    -
    Returns:

    df

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.pst.pst_utils.del_rw(action, name, exc)[source]
    -
    - -
    -
    -pyemu.pst.pst_utils.start_workers(worker_dir, exe_rel_path, pst_rel_path, num_workers=None, worker_root='..', port=4004, rel_path=None)[source]
    -
    - -
    -
    -pyemu.pst.pst_utils.res_from_obseravtion_data(observation_data)[source]
    -

    create a generic residual dataframe filled with np.NaN for -missing information

    - --- - - - - - - - -
    Parameters:observation_data (pandas.DataFrame) – pyemu.Pst.observation_data
    Returns:res_df
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.pst.pst_utils.clean_missing_exponent(pst_filename, clean_filename='clean.pst')[source]
    -

    fixes the issue where some terrible fortran program may have -written a floating point format without the ‘e’ - like 1.0-3, really?!

    - --- - - - - - - - -
    Parameters:
      -
    • pst_filename (str) – the pest control file
    • -
    • clean_filename (str) – the new pest control file to write. Default is “clean.pst”
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -

    Module contents

    -

    This is the pyemu module for handling PEST control files. It provides functionality -for reading and writing control files, creating new control files, and manipulating -all sections of the control file. The primary object is the Pst…start there.

    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/source/pyemu.utils.html b/docs/_build/html/source/pyemu.utils.html deleted file mode 100644 index 79f86be79..000000000 --- a/docs/_build/html/source/pyemu.utils.html +++ /dev/null @@ -1,3440 +0,0 @@ - - - - - - - - pyemu.utils package — pyEMU 0.3 documentation - - - - - - - - - - - - - - - - - - -
    -
    -
    - - -
    - -
    -

    pyemu.utils package

    -
    -

    Submodules

    -
    -
    -

    pyemu.utils.geostats module

    -

    Geostatistical analyses within the pyemu framework. -Support for Ordinary Kriging as well as construction of -covariance matrices from (nested) geostistical structures. -Also support for reading GSLIB and SGEMS files

    -
    -
    -class pyemu.utils.geostats.GeoStruct(nugget=0.0, variograms=[], name='struct1', transform='none')[source]
    -

    Bases: object

    -

    a geostatistical structure object. The object contains -variograms and (optionally) nugget information.

    - --- - - - -
    Parameters:
      -
    • nugget ((float)) – nugget contribution
    • -
    • variograms ((list or Vario2d instance)) – variogram(s) associated with this GeoStruct instance
    • -
    • name ((str)) – name to assign the structure. Default is “struct1”.
    • -
    • transform ((str)) – the transformation to apply to the GeoStruct. Can be -“none” or “log”, depending on the transformation of the -property being represented by the GeoStruct. -Default is “none
    • -
    -
    -
    -
    -variograms
    -

    list – the Vario2d objects associated with the GeoStruct

    -
    - -
    -
    -name
    -

    str – the name of the GeoStruct

    -
    - -
    -
    -transform
    -

    str – the transform of the GeoStruct

    -
    - -
    -

    Example

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)

    -

    >>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)

    -
    -
    -
    -to_struct_file(f)[source]
    -

    write a PEST-style structure file

    - --- - - - -
    Parameters:f ((str or file handle)) – file to write the GeoStruct information to
    -
    - -
    -
    -sparse_covariance_matrix(x, y, names)[source]
    -

    build a pyemu.Cov instance from GeoStruct

    - --- - - - - - - - -
    Parameters:
      -
    • x ((iterable of floats)) – x-coordinate locations
    • -
    • y ((iterable of floats)) – y-coordinate locations
    • -
    • names ((iterable of str)) – (parameter) names of locations.
    • -
    -
    Returns:

    sparse – the sparse covariance matrix implied by this GeoStruct for the x,y pairs.

    -
    Return type:

    pyemu.SparseMatrix

    -
    -
    -

    Example

    -

    >>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")

    -

    >>>cov = gs.covariance_matrix(pp_df.x,pp_df.y,pp_df.name)

    -
    -
    - -
    -
    -covariance_matrix(x, y, names=None, cov=None)[source]
    -

    build a pyemu.Cov instance from GeoStruct

    - --- - - - - - - - -
    Parameters:
      -
    • x ((iterable of floats)) – x-coordinate locations
    • -
    • y ((iterable of floats)) – y-coordinate locations
    • -
    • names ((iterable of str)) – names of location. If None, cov must not be None. Default is None.
    • -
    • cov ((pyemu.Cov) instance) – an existing Cov instance. The contribution of this GeoStruct is added -to cov. If cov is None, names must not be None. Default is None
    • -
    -
    Returns:

    cov – the covariance matrix implied by this GeoStruct for the x,y pairs. -cov has row and column names supplied by the names argument unless -the “cov” argument was passed.

    -
    Return type:

    pyemu.Cov

    -
    -
    -

    Note

    -

    either “names” or “cov” must be passed. If “cov” is passed, cov.shape -must equal len(x) and len(y).

    -
    -
    -

    Example

    -

    >>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")

    -

    >>>cov = gs.covariance_matrix(pp_df.x,pp_df.y,pp_df.name)

    -
    -
    - -
    -
    -covariance(pt0, pt1)[source]
    -

    get the covariance between two points implied by the GeoStruct. -This is used during the ordinary kriging process to get the RHS

    - --- - - - - - - - -
    Parameters:
      -
    • pt0 ((iterable length 2 of floats)) –
    • -
    • pt1 ((iterable length 2 of floats)) –
    • -
    -
    Returns:

    covariance – the covariance between pt0 and pt1 implied by the GeoStruct

    -
    Return type:

    float

    -
    -
    - -
    -
    -covariance_points(x0, y0, xother, yother)[source]
    -

    Get the covariance between point x0,y0 and the points -contained in xother, yother.

    - --- - - - - - - - -
    Parameters:
      -
    • x0 ((float)) – x-coordinate
    • -
    • y0 ((float)) – y-coordinate
    • -
    • xother ((iterable of floats)) – x-coordinate of other points
    • -
    • yother ((iterable of floats)) – y-coordinate of other points
    • -
    -
    Returns:

    cov – a 1-D array of covariance between point x0,y0 and the -points contained in xother, yother. len(cov) = len(xother) = -len(yother)

    -
    Return type:

    numpy.ndarray

    -
    -
    - -
    -
    -sill
    -

    get the sill of the GeoStruct

    - --- - - - - - -
    Returns:sill – the sill of the (nested) GeoStruct, including nugget and contribution -from each variogram
    Return type:float
    -
    - -
    -
    -plot(**kwargs)[source]
    -

    make a cheap plot of the GeoStruct

    - --- - - - - - - - -
    Parameters:**kwargs ((dict)) – keyword arguments to use for plotting.
    Returns:ax – the axis with the GeoStruct plot
    Return type:matplotlib.pyplot.axis
    -
    -

    Note

    -

    optional arguments include “ax” (an existing axis), -“individuals” (plot each variogram on a separate axis), -“legend” (add a legend to the plot(s)). All other kwargs -are passed to matplotlib.pyplot.plot()

    -
    -
    - -
    - -
    -
    -class pyemu.utils.geostats.OrdinaryKrige(geostruct, point_data)[source]
    -

    Bases: object

    -

    Ordinary Kriging using Pandas and Numpy.

    - --- - - - -
    Parameters:
      -
    • geostruct ((GeoStruct)) – a pyemu.geostats.GeoStruct to use for the kriging
    • -
    • point_data ((pandas.DataFrame)) – the conditioning points to use for kriging. point_data must contain -columns “name”, “x”, “y”.
    • -
    -
    -
    -

    Note

    -

    if point_data is an str, then it is assumed to be a pilot points file -and is loaded as such using pyemu.pp_utils.pp_file_to_dataframe()

    -

    If zoned interpolation is used for grid-based interpolation, then -point_data must also contain a “zone” column

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)

    -

    >>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)

    -

    >>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")

    -

    >>>ok = pyemu.utils.geostats.OrdinaryKrige(gs,pp_df)

    -
    -
    -
    -check_point_data_dist(rectify=False)[source]
    -

    check for point_data entries that are closer than -EPSILON distance - this will cause a singular kriging matrix.

    - --- - - - -
    Parameters:rectify ((boolean)) – flag to fix the problems with point_data -by dropping additional points that are -closer than EPSILON distance. Default is False
    -
    -

    Note

    -

    this method will issue warnings for points that are closer -than EPSILON distance

    -
    -
    - -
    -
    -calc_factors_grid(spatial_reference, zone_array=None, minpts_interp=1, maxpts_interp=20, search_radius=10000000000.0, verbose=False, var_filename=None, forgive=False)[source]
    -

    calculate kriging factors (weights) for a structured grid.

    - --- - - - - - - - -
    Parameters:
      -
    • spatial_reference ((flopy.utils.reference.SpatialReference)) – a spatial reference that describes the orientation and -spatail projection of the the structured grid
    • -
    • zone_array ((numpy.ndarray)) – an integer array of zones to use for kriging. If not None, -then point_data must also contain a “zone” column. point_data -entries with a zone value not found in zone_array will be skipped. -If None, then all point_data will (potentially) be used for -interpolating each grid node. Default is None
    • -
    • minpts_interp ((int)) – minimum number of point_data entires to use for interpolation at -a given grid node. grid nodes with less than minpts_interp -point_data found will be skipped (assigned np.NaN). Defaut is 1
    • -
    • maxpts_interp ((int)) – maximum number of point_data entries to use for interpolation at -a given grid node. A larger maxpts_interp will yield “smoother” -interplation, but using a large maxpts_interp will slow the -(already) slow kriging solution process and may lead to -memory errors. Default is 20.
    • -
    • search_radius ((float)) – the size of the region around a given grid node to search for -point_data entries. Default is 1.0e+10
    • -
    • verbose ((boolean)) – a flag to echo process to stdout during the interpolatino process. -Default is False
    • -
    • var_filename ((str)) – a filename to save the kriging variance for each interpolated grid node. -Default is None.
    • -
    • forgive ((boolean)) – flag to continue if inversion of the kriging matrix failes at one or more -grid nodes. Inversion usually fails if the kriging matrix is singular, -resulting from point_data entries closer than EPSILON distance. If True, -warnings are issued for each failed inversion. If False, an exception -is raised for failed matrix inversion.
    • -
    -
    Returns:

    df – a dataframe with information summarizing the ordinary kriging -process for each grid node

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    this method calls OrdinaryKrige.calc_factors()

    -
    -
    -

    Example

    -

    >>>import flopy

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)

    -

    >>>gs = pyemu.utils.geostats.GeoStruct(variograms=v,nugget=0.5)

    -

    >>>pp_df = pyemu.pp_utils.pp_file_to_dataframe("hkpp.dat")

    -

    >>>ok = pyemu.utils.geostats.OrdinaryKrige(gs,pp_df)

    -

    >>>m = flopy.modflow.Modflow.load("mymodel.nam")

    -

    >>>df = ok.calc_factors_grid(m.sr,zone_array=m.bas6.ibound[0].array,

    -

    >>>                          var_filename="ok_var.dat")

    -

    >>>ok.to_grid_factor_file("factors.dat")

    -
    -
    - -
    -
    -calc_factors(x, y, minpts_interp=1, maxpts_interp=20, search_radius=10000000000.0, verbose=False, pt_zone=None, forgive=False)[source]
    -

    calculate ordinary kriging factors (weights) for the points -represented by arguments x and y

    - --- - - - - - - - -
    Parameters:
      -
    • x ((iterable of floats)) – x-coordinates to calculate kriging factors for
    • -
    • y ((iterable of floats)) – y-coordinates to calculate kriging factors for
    • -
    • minpts_interp ((int)) – minimum number of point_data entires to use for interpolation at -a given x,y interplation point. interpolation points with less -than minpts_interp point_data found will be skipped -(assigned np.NaN). Defaut is 1
    • -
    • maxpts_interp ((int)) – maximum number of point_data entries to use for interpolation at -a given x,y interpolation point. A larger maxpts_interp will -yield “smoother” interplation, but using a large maxpts_interp -will slow the (already) slow kriging solution process and may -lead to memory errors. Default is 20.
    • -
    • search_radius ((float)) – the size of the region around a given x,y interpolation point to search for -point_data entries. Default is 1.0e+10
    • -
    • verbose ((boolean)) – a flag to echo process to stdout during the interpolatino process. -Default is False
    • -
    • forgive ((boolean)) – flag to continue if inversion of the kriging matrix failes at one or more -interpolation points. Inversion usually fails if the kriging matrix is singular, -resulting from point_data entries closer than EPSILON distance. If True, -warnings are issued for each failed inversion. If False, an exception -is raised for failed matrix inversion.
    • -
    -
    Returns:

    df – a dataframe with information summarizing the ordinary kriging -process for each interpolation points

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -to_grid_factors_file(filename, points_file='points.junk', zone_file='zone.junk')[source]
    -

    write a grid-based PEST-style factors file. This file can be used with -the fac2real() method to write an interpolated structured array

    - --- - - - -
    Parameters:
      -
    • filename ((str)) – factor filename
    • -
    • points_file ((str)) – points filename to add to the header of the factors file. -Not used by fac2real() method. Default is “points.junk”
    • -
    • zone_file ((str)) – zone filename to add to the header of the factors file. -Not used by fac2real() method. Default is “zone.junk”
    • -
    -
    -
    -

    Note

    -

    this method should be called after OrdinaryKirge.calc_factors_grid()

    -
    -
    - -
    - -
    -
    -class pyemu.utils.geostats.Vario2d(contribution, a, anisotropy=1.0, bearing=0.0, name='var1')[source]
    -

    Bases: object

    -

    base class for 2-D variograms.

    - --- - - - - - - - -
    Parameters:
      -
    • contribution ((float)) – sill of the variogram
    • -
    • a ((float)) – (practical) range of correlation
    • -
    • anisotropy ((float)) – Anisotropy ratio. Default is 1.0
    • -
    • bearing ((float)) – angle in degrees East of North corresponding to anisotropy ellipse. -Default is 0.0
    • -
    • name ((str)) – name of the variogram. Default is “var1”
    • -
    -
    Returns:

    Vario2d

    -
    Return type:

    Vario2d

    -
    -
    -

    Note

    -

    This base class should not be instantiated directly as it does not implement -an h_function() method.

    -
    -
    -
    -to_struct_file(f)[source]
    -

    write the Vario2d to a PEST-style structure file

    - --- - - - -
    Parameters:f ((str or file handle)) – item to write to
    -
    - -
    -
    -bearing_rads
    -

    get the bearing of the Vario2d in radians

    - --- - - - - - -
    Returns:bearing_rads – the Vario2d bearing in radians
    Return type:float
    -
    - -
    -
    -rotation_coefs
    -

    get the rotation coefficents in radians

    - --- - - - - - -
    Returns:rotation_coefs – the rotation coefficients implied by Vario2d.bearing
    Return type:list
    -
    - -
    -
    -inv_h(h)[source]
    -

    the inverse of the h_function. Used for plotting

    - --- - - - - - - - -
    Parameters:h ((float)) – the value of h_function to invert
    Returns:inv_h – the inverse of h
    Return type:float
    -
    - -
    -
    -plot(**kwargs)[source]
    -

    get a cheap plot of the Vario2d

    - --- - - - - - - - -
    Parameters:**kwargs ((dict)) – keyword arguments to use for plotting
    Returns:ax
    Return type:matplotlib.pyplot.axis
    -
    -

    Note

    -

    optional arguments in kwargs include -“ax” (existing matplotlib.pyplot.axis). Other -kwargs are passed to matplotlib.pyplot.plot()

    -
    -
    - -
    -
    -add_sparse_covariance_matrix(x, y, names, iidx, jidx, data)[source]
    -

    build a pyemu.SparseMatrix instance implied by Vario2d

    - --- - - - - - - - -
    Parameters:
      -
    • x ((iterable of floats)) – x-coordinate locations
    • -
    • y ((iterable of floats)) – y-coordinate locations
    • -
    • names ((iterable of str)) – names of locations. If None, cov must not be None
    • -
    • iidx (1-D ndarray) – i row indices
    • -
    • jidx (1-D ndarray) – j col indices
    • -
    • data (1-D ndarray) – nonzero entries
    • -
    -
    Returns:

    -
    Return type:

    None

    -
    -
    - -
    -
    -covariance_matrix(x, y, names=None, cov=None)[source]
    -

    build a pyemu.Cov instance implied by Vario2d

    - --- - - - - - - - -
    Parameters:
      -
    • x ((iterable of floats)) – x-coordinate locations
    • -
    • y ((iterable of floats)) – y-coordinate locations
    • -
    • names ((iterable of str)) – names of locations. If None, cov must not be None
    • -
    • cov ((pyemu.Cov)) – an existing Cov instance. Vario2d contribution is added to cov
    • -
    -
    Returns:

    cov

    -
    Return type:

    pyemu.Cov

    -
    -
    -

    Note

    -

    either names or cov must not be None.

    -
    -
    - -
    -
    -covariance_points(x0, y0, xother, yother)[source]
    -

    get the covariance between base point x0,y0 and -other points xother,yother implied by Vario2d

    - --- - - - - - - - -
    Parameters:
      -
    • x0 ((float)) – x-coordinate of base point
    • -
    • y0 ((float)) – y-coordinate of base point
    • -
    • xother ((float or numpy.ndarray)) – x-coordinates of other points
    • -
    • yother ((float or numpy.ndarray)) – y-coordinates of other points
    • -
    -
    Returns:

    cov – covariance between base point and other points implied by -Vario2d.

    -
    Return type:

    numpy.ndarray

    -
    -
    -

    Note

    -

    len(cov) = len(xother) = len(yother)

    -
    -
    - -
    -
    -covariance(pt0, pt1)[source]
    -

    get the covarince between two points implied by Vario2d

    - --- - - - - - - - -
    Parameters:
      -
    • pt0 ((iterable of len 2)) – first point x and y
    • -
    • pt1 ((iterable of len 2)) – second point x and y
    • -
    -
    Returns:

    cov – covariance between pt0 and pt1

    -
    Return type:

    float

    -
    -
    - -
    - -
    -
    -class pyemu.utils.geostats.ExpVario(contribution, a, anisotropy=1.0, bearing=0.0, name='var1')[source]
    -

    Bases: pyemu.utils.geostats.Vario2d

    -

    Exponetial variogram derived type

    - --- - - - - - - - -
    Parameters:
      -
    • contribution ((float)) – sill of the variogram
    • -
    • a ((float)) – (practical) range of correlation
    • -
    • anisotropy ((float)) – Anisotropy ratio. Default is 1.0
    • -
    • bearing ((float)) – angle in degrees East of North corresponding to anisotropy ellipse. -Default is 0.0
    • -
    • name ((str)) – name of the variogram. Default is “var1”
    • -
    -
    Returns:

    ExpVario

    -
    Return type:

    ExpVario

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.ExpVario(a=1000,contribution=1.0)

    -
    -
    - -
    -
    -class pyemu.utils.geostats.GauVario(contribution, a, anisotropy=1.0, bearing=0.0, name='var1')[source]
    -

    Bases: pyemu.utils.geostats.Vario2d

    -

    Gaussian variogram derived type

    - --- - - - - - - - -
    Parameters:
      -
    • contribution ((float)) – sill of the variogram
    • -
    • a ((float)) – (practical) range of correlation
    • -
    • anisotropy ((float)) – Anisotropy ratio. Default is 1.0
    • -
    • bearing ((float)) – angle in degrees East of North corresponding to anisotropy ellipse. -Default is 0.0
    • -
    • name ((str)) – name of the variogram. Default is “var1”
    • -
    -
    Returns:

    GauVario

    -
    Return type:

    GauVario

    -
    -
    -

    Note

    -

    the Gaussian variogram can be unstable (not invertible) for long ranges.

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.GauVario(a=1000,contribution=1.0)

    -
    -
    - -
    -
    -class pyemu.utils.geostats.SphVario(contribution, a, anisotropy=1.0, bearing=0.0, name='var1')[source]
    -

    Bases: pyemu.utils.geostats.Vario2d

    -

    Spherical variogram derived type

    - --- - - - - - - - -
    Parameters:
      -
    • contribution ((float)) – sill of the variogram
    • -
    • a ((float)) – (practical) range of correlation
    • -
    • anisotropy ((float)) – Anisotropy ratio. Default is 1.0
    • -
    • bearing ((float)) – angle in degrees East of North corresponding to anisotropy ellipse. -Default is 0.0
    • -
    • name ((str)) – name of the variogram. Default is “var1”
    • -
    -
    Returns:

    SphVario

    -
    Return type:

    SphVario

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>v = pyemu.utils.geostats.SphVario(a=1000,contribution=1.0)

    -
    -
    - -
    -
    -pyemu.utils.geostats.read_struct_file(struct_file, return_type=<class 'pyemu.utils.geostats.GeoStruct'>)[source]
    -

    read an existing PEST-type structure file into a GeoStruct instance

    - --- - - - - - - - -
    Parameters:
      -
    • struct_file ((str)) – existing pest-type structure file
    • -
    • return_type ((object)) – the instance type to return. Default is GeoStruct
    • -
    -
    Returns:

    GeoStruct

    -
    Return type:

    list or GeoStruct

    -
    -
    -

    Note

    -

    if only on structure is listed in struct_file, then return type -is GeoStruct. Otherwise, return type is a list of GeoStruct

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>gs = pyemu.utils.geostats.reads_struct_file("struct.dat")

    -
    -
    - -
    -
    -pyemu.utils.geostats.read_sgems_variogram_xml(xml_file, return_type=<class 'pyemu.utils.geostats.GeoStruct'>)[source]
    -

    function to read an SGEMS-type variogram XML file into -a GeoStruct

    - --- - - - - - - - -
    Parameters:
      -
    • xml_file ((str)) – SGEMS variogram XML file
    • -
    • return_type ((object)) – the instance type to return. Default is GeoStruct
    • -
    -
    Returns:

    GeoStruct

    -
    Return type:

    GeoStruct

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>gs = pyemu.utils.geostats.read_sgems_variogram_xml("sgems.xml")

    -
    -
    - -
    -
    -pyemu.utils.geostats.gslib_2_dataframe(filename, attr_name=None, x_idx=0, y_idx=1)[source]
    -

    function to read a GSLIB point data file into a pandas.DataFrame

    - --- - - - - - - - - - -
    Parameters:
      -
    • filename ((str)) – GSLIB file
    • -
    • attr_name ((str)) – the column name in the dataframe for the attribute. If None, GSLIB file -can have only 3 columns. attr_name must be in the GSLIB file header
    • -
    • x_idx ((int)) – the index of the x-coordinate information in the GSLIB file. Default is -0 (first column)
    • -
    • y_idx ((int)) – the index of the y-coordinate information in the GSLIB file. -Default is 1 (second column)
    • -
    -
    Returns:

    df

    -
    Return type:

    pandas.DataFrame

    -
    Raises:

    exception if attr_name is None and GSLIB file has more than 3 columns

    -
    -
    -

    Note

    -

    assigns generic point names (“pt0, pt1, etc)

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>df = pyemu.utiils.geostats.gslib_2_dataframe("prop.gslib",attr_name="hk")

    -
    -
    - -
    -
    -pyemu.utils.geostats.load_sgems_exp_var(filename)[source]
    -

    read an SGEM experimental variogram into a sequence of -pandas.DataFrames

    - --- - - - - - - - -
    Parameters:filename ((str)) – an SGEMS experimental variogram XML file
    Returns:dfs – a list of pandas.DataFrames of x, y, pairs for each -division in the experimental variogram
    Return type:list
    -
    - -
    -
    -pyemu.utils.geostats.fac2real(pp_file=None, factors_file='factors.dat', out_file='test.ref', upper_lim=1e+30, lower_lim=-1e+30, fill_value=1e+30)[source]
    -

    A python replication of the PEST fac2real utility for creating a -structure grid array from previously calculated kriging factors (weights)

    - --- - - - - - -
    Parameters:
      -
    • pp_file ((str)) – PEST-type pilot points file
    • -
    • factors_file ((str)) – PEST-style factors file
    • -
    • out_file ((str)) – filename of array to write. If None, array is returned, else -value of out_file is returned. Default is “test.ref”.
    • -
    • upper_lim ((float)) – maximum interpolated value in the array. Values greater than -upper_lim are set to fill_value
    • -
    • lower_lim ((float)) – minimum interpolated value in the array. Values less than lower_lim -are set to fill_value
    • -
    • fill_value ((float)) – the value to assign array nodes that are not interpolated
    • -
    -
    Returns:

      -
    • arr (numpy.ndarray) – if out_file is None
    • -
    • out_file (str) – if out_file it not None
    • -
    -

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pyemu.utils.geostats.fac2real("hkpp.dat",out_file="hk_layer_1.ref")

    -
    -
    - -
    -
    -pyemu.utils.geostats.parse_factor_line(line)[source]
    -

    function to parse a factor file line. Used by fac2real()

    - --- - - - - - -
    Parameters:line ((str)) – a factor line from a factor file
    Returns:
      -
    • inode (int) – the inode of the grid node
    • -
    • itrans (int) – flag for transformation of the grid node
    • -
    • fac_data (dict) – a dictionary of point number, factor
    • -
    -
    -
    - -
    -
    -

    pyemu.utils.gw_utils module

    -

    module of utilities for groundwater modeling

    -
    -
    -pyemu.utils.gw_utils.modflow_pval_to_template_file(pval_file, tpl_file=None)[source]
    -

    write a template file for a modflow parameter value file. -Uses names in the first column in the pval file as par names.

    - --- - - - - - - - -
    Parameters:
      -
    • pval_file (str) – parameter value file
    • -
    • tpl_file (str, optional) – template file to write. If None, use <pval_file>.tpl. -Default is None
    • -
    -
    Returns:

    df – pandas DataFrame with control file parameter information

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.modflow_hob_to_instruction_file(hob_file)[source]
    -

    write an instruction file for a modflow head observation file

    - --- - - - - - - - -
    Parameters:hob_file (str) – modflow hob file
    Returns:df – pandas DataFrame with control file observation information
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.utils.gw_utils.modflow_hydmod_to_instruction_file(hydmod_file)[source]
    -

    write an instruction file for a modflow hydmod file

    - --- - - - - - - - -
    Parameters:hydmod_file (str) – modflow hydmod file
    Returns:df – pandas DataFrame with control file observation information
    Return type:pandas.DataFrame
    -
    -

    Note

    -

    calls modflow_read_hydmod_file()

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.modflow_read_hydmod_file(hydmod_file, hydmod_outfile=None)[source]
    -

    read in a binary hydmod file and return a dataframe of the results

    - --- - - - - - - - -
    Parameters:
      -
    • hydmod_file (str) – modflow hydmod binary file
    • -
    • hydmod_outfile (str) – output file to write. If None, use <hydmod_file>.dat. -Default is None
    • -
    -
    Returns:

    df – pandas DataFrame with hymod_file values

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    requires flopy

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_pilotpoints_grid(ml=None, sr=None, ibound=None, prefix_dict=None, every_n_cell=4, use_ibound_zones=False, pp_dir='.', tpl_dir='.', shapename='pp.shp')[source]
    -

    setup regularly-spaced (gridded) pilot point parameterization

    - --- - - - - - - - -
    Parameters:
      -
    • ml (flopy.mbase) – a flopy mbase dervied type. If None, sr must not be None.
    • -
    • sr (flopy.utils.reference.SpatialReference) – a spatial reference use to locate the model grid in space. If None, -ml must not be None. Default is None
    • -
    • ibound (numpy.ndarray) – the modflow ibound integer array. Used to set pilot points only in active areas. -If None and ml is None, then pilot points are set in all rows and columns according to -every_n_cell. Default is None.
    • -
    • prefix_dict (dict) – a dictionary of pilot point parameter prefix, layer pairs. example : {“hk”:[0,1,2,3]} would -setup pilot points with the prefix “hk” for model layers 1 - 4 (zero based). If None, a generic set -of pilot points with the “pp” prefix are setup for a generic nrowXncol grid. Default is None
    • -
    • use_ibound_zones (bool) – a flag to use the greater-than-zero values in the ibound as pilot point zones. If False,ibound -values greater than zero are treated as a single zone. Default is False.
    • -
    • pp_dir (str) – directory to write pilot point files to. Default is ‘.’
    • -
    • tpl_dir (str) – directory to write pilot point template file to. Default is ‘.’
    • -
    • shapename (str) – name of shapefile to write that containts pilot point information. Default is “pp.shp”
    • -
    -
    Returns:

    pp_df – a dataframe summarizing pilot point information (same information -written to shapename

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.pp_file_to_dataframe(pp_filename)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.pp_tpl_to_dataframe(tpl_filename)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.write_pp_shapfile(pp_df, shapename=None)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.write_pp_file(filename, pp_df)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.fac2real(pp_file=None, factors_file='factors.dat', out_file='test.ref', upper_lim=1e+30, lower_lim=-1e+30, fill_value=1e+30)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_mtlist_budget_obs(list_filename, gw_filename='mtlist_gw.dat', sw_filename='mtlist_sw.dat', start_datetime='1-1-1970', gw_prefix='gw', sw_prefix='sw', save_setup_file=False)[source]
    -

    setup observations of gw (and optionally sw) mass budgets from mt3dusgs list file. writes -an instruction file and also a _setup_.csv to use when constructing a pest -control file

    - --- - - - - - - - -
    Parameters:
      -
    • list_filename (str) – modflow list file
    • -
    • gw_filename (str) – output filename that will contain the gw budget observations. Default is -“mtlist_gw.dat”
    • -
    • sw_filename (str) – output filename that will contain the sw budget observations. Default is -“mtlist_sw.dat”
    • -
    • start_datetime (str) – an str that can be parsed into a pandas.TimeStamp. used to give budget -observations meaningful names
    • -
    • gw_prefix (str) – a prefix to add to the GW budget observations. Useful if processing -more than one list file as part of the forward run process. Default is ‘gw’.
    • -
    • sw_prefix (str) – a prefix to add to the SW budget observations. Useful if processing -more than one list file as part of the forward run process. Default is ‘sw’.
    • -
    • save_setup_file ((boolean)) – a flag to save _setup_<list_filename>.csv file that contains useful -control file information
    • -
    -
    Returns:

    frun_line, ins_filenames, df – the command to add to the forward run script, the names of the instruction -files and a dataframe with information for constructing a control file. If INSCHEK fails -to run, df = None

    -
    Return type:

    str, list(str), pandas.DataFrame

    -
    -
    -

    Note

    -

    This function uses INSCHEK to get observation values; the observation values are -the values of the list file list_filename. If INSCHEK fails to run, the obseravtion -values are set to 1.0E+10

    -

    the instruction files are named <out_filename>.ins

    -

    It is recommended to use the default value for gw_filename or sw_filename.

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_mtlist_budget_obs(list_filename, gw_filename='mtlist_gw.dat', sw_filename='mtlist_sw.dat', start_datetime='1-1-1970')[source]
    -

    process an MT3D list file to extract mass budget entries.

    - --- - - - - - -
    Parameters:
      -
    • list_filename (str) – the mt3d list file
    • -
    • gw_filename (str) – the name of the output file with gw mass budget information. -Default is “mtlist_gw.dat”
    • -
    • sw_filename (str) – the name of the output file with sw mass budget information. -Default is “mtlist_sw.dat”
    • -
    • start_datatime (str) – an str that can be cast to a pandas.TimeStamp. Used to give -observations a meaningful name
    • -
    -
    Returns:

      -
    • gw (pandas.DataFrame) – the gw mass dataframe
    • -
    • sw (pandas.DataFrame (optional)) – the sw mass dataframe
    • -
    -

    -
    -
    -

    Note

    -

    requires flopy

    -

    if SFT is not active, no SW mass budget will be returned

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_mflist_budget_obs(list_filename, flx_filename='flux.dat', vol_filename='vol.dat', start_datetime="1-1'1970", prefix='', save_setup_file=False)[source]
    -

    setup observations of budget volume and flux from modflow list file. writes -an instruction file and also a _setup_.csv to use when constructing a pest -control file

    - --- - - - - - - - -
    Parameters:
      -
    • list_filename (str) – modflow list file
    • -
    • flx_filename (str) – output filename that will contain the budget flux observations. Default is -“flux.dat”
    • -
    • vol_filename (str)) – output filename that will contain the budget volume observations. Default -is “vol.dat”
    • -
    • start_datetime (str) – an str that can be parsed into a pandas.TimeStamp. used to give budget -observations meaningful names
    • -
    • prefix (str) – a prefix to add to the water budget observations. Useful if processing -more than one list file as part of the forward run process. Default is ‘’.
    • -
    • save_setup_file ((boolean)) – a flag to save _setup_<list_filename>.csv file that contains useful -control file information
    • -
    -
    Returns:

    df – a dataframe with information for constructing a control file. If INSCHEK fails -to run, reutrns None

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    This function uses INSCHEK to get observation values; the observation values are -the values of the list file list_filename. If INSCHEK fails to run, the obseravtion -values are set to 1.0E+10

    -

    the instruction files are named <flux_file>.ins and <vol_file>.ins, respectively

    -

    It is recommended to use the default values for flux_file and vol_file.

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_mflist_budget_obs(list_filename, flx_filename='flux.dat', vol_filename='vol.dat', start_datetime='1-1-1970')[source]
    -

    process a MODFLOW list file to extract flux and volume water budget entries.

    - --- - - - - - -
    Parameters:
      -
    • list_filename (str) – the modflow list file
    • -
    • flx_filename (str) – the name of the output file with water budget flux information. -Default is “flux.dat”
    • -
    • vol_filename (str) – the name of the output file with water budget volume information. -Default is “vol.dat”
    • -
    • start_datatime (str) – an str that can be cast to a pandas.TimeStamp. Used to give -observations a meaningful name
    • -
    -
    Returns:

      -
    • flx (pandas.DataFrame) – the flux dataframe
    • -
    • vol (pandas.DataFrame) – the volume dataframe
    • -
    -

    -
    -
    -

    Note

    -

    requires flopy

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_hds_timeseries(hds_file, kij_dict, prefix=None, include_path=False, model=None)[source]
    -

    a function to setup extracting time-series from a binary modflow -head save (or equivalent format - ucn, sub, etc). Writes -an instruction file and a _set_ csv

    - --- - - - -
    Parameters:
      -
    • hds_file (str) – binary filename
    • -
    • kij_dict (dict) – dictionary of site_name: [k,i,j] pairs
    • -
    • prefix (str) – string to prepend to site_name when forming obsnme’s. Default is None
    • -
    • include_path (bool) – flag to prepend hds_file path. Useful for setting up -process in separate directory for where python is running.
    • -
    • model (flopy.mbase) – a flopy model. If passed, the observation names will have the datetime of the -observation appended to them. If None, the observation names will have the -stress period appended to them. Default is None.
    • -
    -
    -
    -

    Note

    -

    This function writes hds_timeseries.config that must be in the same -dir where apply_hds_timeseries() is called during the forward run

    -

    assumes model time units are days!!!

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_hds_timeseries(config_file=None)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_hds_obs(hds_file, kperk_pairs=None, skip=None, prefix='hds')[source]
    -

    a function to setup using all values from a -layer-stress period pair for observations. Writes -an instruction file and a _setup_ csv used -construct a control file.

    - --- - - - - - - - -
    Parameters:
      -
    • hds_file (str) – a MODFLOW head-save file. If the hds_file endswith ‘ucn’, -then the file is treated as a UcnFile type.
    • -
    • kperk_pairs (iterable) – an iterable of pairs of kper (zero-based stress -period index) and k (zero-based layer index) to -setup observations for. If None, then a shit-ton -of observations may be produced!
    • -
    • skip (variable) – a value or function used to determine which values -to skip when setting up observations. If np.scalar(skip) -is True, then values equal to skip will not be used. If not -np.scalar(skip), then skip will be treated as a lambda function that -returns np.NaN if the value should be skipped.
    • -
    • prefix (str) – the prefix to use for the observation names. default is “hds”.
    • -
    -
    Returns:

    (forward_run_line, df) – a python code str to add to the forward run script and the setup info for the observations

    -
    Return type:

    str, pd.DataFrame

    -
    -
    -

    Note

    -

    requires flopy

    -

    writes <hds_file>.dat.ins instruction file

    -

    writes _setup_<hds_file>.csv which contains much -useful information for construction a control file

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.last_kstp_from_kper(hds, kper)[source]
    -

    function to find the last time step (kstp) for a -give stress period (kper) in a modflow head save file.

    - --- - - - - - - - -
    Parameters:
      -
    • hds (flopy.utils.HeadFile) –
    • -
    • kper (int) – the zero-index stress period number
    • -
    -
    Returns:

    kstp – the zero-based last time step during stress period -kper in the head save file

    -
    Return type:

    int

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_hds_obs(hds_file)[source]
    -

    process a modflow head save file. A companion function to -setup_hds_obs that is called during the forward run process

    - --- - - - -
    Parameters:hds_file (str) – a modflow head save filename. if hds_file ends with ‘ucn’, -then the file is treated as a UcnFile type.
    -
    -

    Note

    -

    requires flopy

    -

    writes <hds_file>.dat

    -

    expects <hds_file>.dat.ins to exist

    -

    uses pyemu.pst_utils.parse_ins_file to get observation names

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_sft_obs(sft_file, ins_file=None, start_datetime=None, times=None, ncomp=1)[source]
    -

    writes an instruction file for a mt3d-usgs sft output file

    - --- - - - - - - - -
    Parameters:
      -
    • sft_file (str) – the sft output file (ASCII)
    • -
    • ins_file (str) – the name of the instruction file to create. If None, the name -is <sft_file>.ins. Default is None
    • -
    • start_datetime (str) – a pandas.to_datetime() compatible str. If not None, -then the resulting observation names have the datetime -suffix. If None, the suffix is the output totim. Default -is None
    • -
    • times (iterable) – a container of times to make observations for. If None, all times are used. -Default is None.
    • -
    • ncomp (int) – number of components in transport model. Default is 1.
    • -
    -
    Returns:

    df – a dataframe with obsnme and obsval for the sft simulated concentrations and flows. -If inschek was not successfully run, then returns None

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    setups up observations for SW conc, GW conc and flowgw for all times and reaches.

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_sft_obs()[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_sfr_seg_parameters(nam_file, model_ws='.', par_cols=['flow', 'runoff', 'hcond1', 'hcond2', 'pptsw'], tie_hcond=True)[source]
    -

    Setup multiplier parameters for SFR segment data. Just handles the -standard input case, not all the cryptic SFR options. Loads the dis, bas, and sfr files -with flopy using model_ws. However, expects that apply_sfr_seg_parameters() will be called -from within model_ws at runtime.

    - --- - - - - - - - -
    Parameters:
      -
    • nam_file (flopy.modflow.mf.Modflow) – MODFLOw name file. DIS, BAS, and SFR must be available as pathed in the nam_file
    • -
    • model_ws (str) – model workspace for flopy to load the MODFLOW model from
    • -
    • OR
    • -
    • nam_file – flopy modflow model object
    • -
    • par_cols (list(str)) – segment data entires to parameterize
    • -
    • tie_hcond (flag to use same mult par for hcond1 and hcond2 for a given segment. Default is True) –
    • -
    -
    Returns:

    df – a dataframe with useful parameter setup information

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    the number (and numbering) of segment data entries must consistent across -all stress periods. -writes <nam_file>+”_backup_.sfr” as the backup of the original sfr file -skips values = 0.0 since multipliers don’t work for these

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_sfr_reach_parameters(nam_file, model_ws='.', par_cols=['strhc1'])[source]
    -

    Setup multiplier paramters for reach data, when reachinput option is specififed in sfr. -Similare to setup_sfr_seg_parameters() method will apply params to sfr reachdata -Can load the dis, bas, and sfr files with flopy using model_ws. Or can pass a model object (SFR loading can be slow) -However, expects that apply_sfr_reach_parameters() will be called -from within model_ws at runtime.

    - --- - - - - - - - -
    Parameters:
      -
    • nam_file (flopy.modflow.mf.Modflow) – MODFLOw name file. DIS, BAS, and SFR must be available as pathed in the nam_file
    • -
    • model_ws (str) – model workspace for flopy to load the MODFLOW model from
    • -
    • OR
    • -
    • nam_file – flopy modflow model object
    • -
    • par_cols (list(str)) – segment data entires to parameterize
    • -
    -
    Returns:

    df – a dataframe with useful parameter setup information

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Note

    -

    skips values = 0.0 since multipliers don’t work for these

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_sfr_seg_parameters(reach_pars=False)[source]
    -

    apply the SFR segement multiplier parameters. Expected to be run in the same dir -as the model exists

    - --- - - - - - - - -
    Parameters:reach_pars (bool) – if reach paramters need to be applied
    Returns:sfr
    Return type:flopy.modflow.ModflowSfr instance
    -
    -

    Note

    -

    expects “sfr_seg_pars.config” to exist -expects <nam_file>+”_backup_.sfr” to exist

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_sfr_parameters(reach_pars=False)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.setup_sfr_obs(sfr_out_file, seg_group_dict=None, ins_file=None, model=None, include_path=False)[source]
    -

    setup observations using the sfr ASCII output file. Setups -the ability to aggregate flows for groups of segments. Applies -only flow to aquier and flow out.

    - --- - - - - - - - -
    Parameters:
      -
    • sft_out_file (str) – the existing SFR output file
    • -
    • seg_group_dict (dict) – a dictionary of SFR segements to aggregate together for a single obs. -the key value in the dict is the base observation name. If None, all segments -are used as individual observations. Default is None
    • -
    • model (flopy.mbase) – a flopy model. If passed, the observation names will have the datetime of the -observation appended to them. If None, the observation names will have the -stress period appended to them. Default is None.
    • -
    • include_path (bool) – flag to prepend sfr_out_file path to sfr_obs.config. Useful for setting up -process in separate directory for where python is running.
    • -
    -
    Returns:

    df – dataframe of obsnme, obsval and obgnme if inschek run was successful. Else None

    -
    Return type:

    pd.DataFrame

    -
    -
    -

    Note

    -

    This function writes “sfr_obs.config” which must be kept in the dir where -“apply_sfr_obs()” is being called during the forward run

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_sfr_obs()[source]
    -

    apply the sfr observation process - pairs with setup_sfr_obs(). -requires sfr_obs.config. Writes <sfr_out_file>.processed, where -<sfr_out_file> is defined in “sfr_obs.config”

    - --- - - - - - - - -
    Parameters:None
    Returns:df – a dataframe of aggregrated sfr segment aquifer and outflow
    Return type:pd.DataFrame
    -
    - -
    -
    -pyemu.utils.gw_utils.load_sfr_out(sfr_out_file)[source]
    -

    load an ASCII SFR output file into a dictionary of kper: dataframes. aggregates -segments and only returns flow to aquifer and flow out.

    - --- - - - - - - - -
    Parameters:sfr_out_file (str) – SFR ASCII output file
    Returns:sfr_dict – dictionary of {kper:dataframe}
    Return type:dict
    -
    - -
    -
    -pyemu.utils.gw_utils.modflow_sfr_gag_to_instruction_file(gage_output_file, ins_file=None)[source]
    -

    writes an instruction file for an SFR gage output file to read Flow only at all times

    - --- - - - - - -
    Parameters:
      -
    • gage_output_file (str) – the gage output filename (ASCII)
    • -
    • ins_file (str) – the name of the instruction file to create. If None, the name -is <gage_output_file>.ins. Default is None
    • -
    -
    Returns:

      -
    • df (pandas.DataFrame) – a dataframe with obsnme and obsval for the sfr simulated flows. -If inschek was not successfully run, then returns None
    • -
    • ins_file (str) – file name of instructions file relating to gage output.
    • -
    • obs_file (str) – file name of processed gage output for all times
    • -
    -

    -
    -
    -

    Note

    -

    setups up observations for gage outputs only for the Flow column.

    -
    -

    TODO : allow other observation types and align explicitly with times - now returns all values

    -
    - -
    -
    -pyemu.utils.gw_utils.setup_gage_obs(gage_file, ins_file=None, start_datetime=None, times=None)[source]
    -

    writes an instruction file for a mt3d-usgs sft output file

    - --- - - - - - -
    Parameters:
      -
    • gage_file (str) – the gage output file (ASCII)
    • -
    • ins_file (str) – the name of the instruction file to create. If None, the name -is <gage_file>.processed.ins. Default is None
    • -
    • start_datetime (str) – a pandas.to_datetime() compatible str. If not None, -then the resulting observation names have the datetime -suffix. If None, the suffix is the output totim. Default -is None
    • -
    • times (iterable) – a container of times to make observations for. If None, all times are used. -Default is None.
    • -
    -
    Returns:

      -
    • df (pandas.DataFrame) – a dataframe with obsnme and obsval for the sft simulated concentrations and flows. -If inschek was not successfully run, then returns None
    • -
    • ins_file (str) – file name of instructions file relating to gage output.
    • -
    • obs_file (str) – file name of processed gage output (processed according to times passed above.)
    • -
    -

    -
    -
    -

    Note

    -

    setups up observations for gage outputs (all columns).

    -
    -
    - -
    -
    -pyemu.utils.gw_utils.apply_gage_obs(return_obs_file=False)[source]
    -
    - -
    -
    -pyemu.utils.gw_utils.write_hfb_zone_multipliers_template(m)[source]
    -

    write a template file for an hfb using multipliers per zone (double yuck!)

    - --- - - - - - - - -
    Parameters:m (flopy.modflow.Modflow instance with an HFB file) –
    Returns:(hfb_mults, tpl_filename) – a dictionary with original unique HFB conductivity values and their corresponding parameter names -and the name of the template file
    Return type:(dict, str)
    -
    - -
    -
    -pyemu.utils.gw_utils.write_hfb_template(m)[source]
    -

    write a template file for an hfb (yuck!)

    - --- - - - - - - - -
    Parameters:m (flopy.modflow.Modflow instance with an HFB file) –
    Returns:(tpl_filename, df) – the name of the template file and a dataframe with useful info.
    Return type:(str, pandas.DataFrame)
    -
    - -
    -
    -

    pyemu.utils.helpers module

    -

    module with high-level functions to help -perform complex tasks

    -
    -
    -pyemu.utils.helpers.remove_readonly(func, path, excinfo)[source]
    -

    remove readonly dirs, apparently only a windows issue -add to all rmtree calls: shutil.rmtree(*,onerror=remove_readonly), wk

    - --- - - - -
    Parameters:
      -
    • func (object) – func
    • -
    • path (str) – path
    • -
    • excinfo (object) – info
    • -
    -
    -
    - -
    -
    -pyemu.utils.helpers.run(cmd_str, cwd='.', verbose=False)[source]
    -

    an OS agnostic function to execute command

    - --- - - - -
    Parameters:
      -
    • cmd_str (str) – the str to execute with os.system()
    • -
    • cwd (str) – the directory to execute the command in
    • -
    • verbose (bool) – flag to echo to stdout complete cmd str
    • -
    -
    -
    -

    Note

    -

    uses platform to detect OS and adds .exe or ./ as appropriate

    -

    for Windows, if os.system returns non-zero, raises exception

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pyemu.helpers.run("pestpp pest.pst")

    -
    -
    - -
    -
    -pyemu.utils.helpers.geostatistical_draws(pst, struct_dict, num_reals=100, sigma_range=4, verbose=True)[source]
    -

    a helper function to construct a parameter ensenble from a full prior covariance matrix -implied by the geostatistical structure(s) in struct_dict. This function is much more efficient -for problems with lots of pars (>200K).

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file (or the name of control file)
    • -
    • struct_dict (dict) – a python dict of GeoStruct (or structure file), and list of pp tpl files pairs -If the values in the dict are pd.DataFrames, then they must have an -‘x’,’y’, and ‘parnme’ column. If the filename ends in ‘.csv’, -then a pd.DataFrame is loaded, otherwise a pilot points file is loaded.
    • -
    • num_reals (int) – number of realizations to draw. Default is 100
    • -
    • sigma_range (float) – a float representing the number of standard deviations implied by parameter bounds. -Default is 4.0, which implies 95% confidence parameter bounds.
    • -
    • verbose (bool) – flag for stdout.
    • -
    -
    Returns:

    par_ens

    -
    Return type:

    pyemu.ParameterEnsemble

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pst = pyemu.Pst("pest.pst")

    -

    >>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}

    -

    >>>pe = pyemu.helpers.geostatistical_draws(pst,struct_dict=sd,num_reals=100)

    -

    >>>pe.to_csv("par_ensemble.csv")

    -
    -
    - -
    -
    -pyemu.utils.helpers.pilotpoint_prior_builder(pst, struct_dict, sigma_range=4)[source]
    -
    - -
    -
    -pyemu.utils.helpers.sparse_geostatistical_prior_builder(pst, struct_dict, sigma_range=4, verbose=False)[source]
    -

    a helper function to construct a full prior covariance matrix using -a mixture of geostastical structures and parameter bounds information. -The covariance of parameters associated with geostatistical structures is defined -as a mixture of GeoStruct and bounds. That is, the GeoStruct is used to construct a -pyemu.Cov, then the entire pyemu.Cov is scaled by the uncertainty implied by the bounds and -sigma_range. Sounds complicated…

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file (or the name of control file)
    • -
    • struct_dict (dict) – a python dict of GeoStruct (or structure file), and list of pp tpl files pairs -If the values in the dict are pd.DataFrames, then they must have an -‘x’,’y’, and ‘parnme’ column. If the filename ends in ‘.csv’, -then a pd.DataFrame is loaded, otherwise a pilot points file is loaded.
    • -
    • sigma_range (float) – a float representing the number of standard deviations implied by parameter bounds. -Default is 4.0, which implies 95% confidence parameter bounds.
    • -
    • verbose (bool) – flag for stdout.
    • -
    -
    Returns:

    Cov – a sparse covariance matrix that includes all adjustable parameters in the control -file.

    -
    Return type:

    pyemu.SparseMatrix

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pst = pyemu.Pst("pest.pst")

    -

    >>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}

    -

    >>>cov = pyemu.helpers.sparse_geostatistical_prior_builder(pst,struct_dict=sd)

    -

    >>>cov.to_coo("prior.jcb")

    -
    -
    - -
    -
    -pyemu.utils.helpers.geostatistical_prior_builder(pst, struct_dict, sigma_range=4, par_knowledge_dict=None, verbose=False)[source]
    -

    a helper function to construct a full prior covariance matrix using -a mixture of geostastical structures and parameter bounds information. -The covariance of parameters associated with geostatistical structures is defined -as a mixture of GeoStruct and bounds. That is, the GeoStruct is used to construct a -pyemu.Cov, then the entire pyemu.Cov is scaled by the uncertainty implied by the bounds and -sigma_range. Sounds complicated…

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – a control file (or the name of control file)
    • -
    • struct_dict (dict) – a python dict of GeoStruct (or structure file), and list of pp tpl files pairs -If the values in the dict are pd.DataFrames, then they must have an -‘x’,’y’, and ‘parnme’ column. If the filename ends in ‘.csv’, -then a pd.DataFrame is loaded, otherwise a pilot points file is loaded.
    • -
    • sigma_range (float) – a float representing the number of standard deviations implied by parameter bounds. -Default is 4.0, which implies 95% confidence parameter bounds.
    • -
    • par_knowledge_dict (dict) – used to condition on existing knowledge about parameters. This functionality is -currently in dev - don’t use it.
    • -
    • verbose (bool) – stdout flag
    • -
    -
    Returns:

    Cov – a covariance matrix that includes all adjustable parameters in the control -file.

    -
    Return type:

    pyemu.Cov

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pst = pyemu.Pst("pest.pst")

    -

    >>>sd = {"struct.dat":["hkpp.dat.tpl","vka.dat.tpl"]}

    -

    >>>cov = pyemu.helpers.geostatistical_prior_builder(pst,struct_dict=sd)

    -

    >>>cov.to_ascii("prior.cov")

    -
    -
    - -
    -
    -pyemu.utils.helpers.condition_on_par_knowledge(cov, par_knowledge_dict)[source]
    -

    experimental function to include conditional prior information -for one or more parameters in a full covariance matrix

    -
    - -
    -
    -pyemu.utils.helpers.kl_setup(num_eig, sr, struct, prefixes, factors_file='kl_factors.dat', islog=True, basis_file=None, tpl_dir='.')[source]
    -

    setup a karhuenen-Loeve based parameterization for a given -geostatistical structure.

    - --- - - - - - - - -
    Parameters:
      -
    • num_eig (int) – number of basis vectors to retain in the reduced basis
    • -
    • sr (flopy.reference.SpatialReference) –
    • -
    • struct (str or pyemu.geostats.Geostruct) – geostatistical structure (or file containing one)
    • -
    • array_dict (dict) – a dict of arrays to setup as KL-based parameters. The key becomes the -parameter name prefix. The total number of parameters is -len(array_dict) * num_eig
    • -
    • basis_file (str) – the name of the PEST-format binary file where the reduced basis will be saved
    • -
    • tpl_file (str) – the name of the template file to make. The template -file is a csv file with the parameter names, the -original factor values,and the template entries. -The original values can be used to set the parval1 -entries in the control file
    • -
    -
    Returns:

    back_array_dict – a dictionary of back transformed arrays. This is useful to see -how much “smoothing” is taking place compared to the original -arrays.

    -
    Return type:

    dict

    -
    -
    -

    Note

    -

    requires flopy

    -
    -
    -

    Example

    -

    >>>import flopy

    -

    >>>import pyemu

    -

    >>>m = flopy.modflow.Modflow.load("mymodel.nam")

    -

    >>>a_dict = {"hk":m.lpf.hk[0].array}

    -

    >>>ba_dict = pyemu.helpers.kl_setup(10,m.sr,"struct.dat",a_dict)

    -
    -
    - -
    -
    -pyemu.utils.helpers.eigen_basis_to_factor_file(nrow, ncol, basis, factors_file, islog=True)[source]
    -
    - -
    -
    -pyemu.utils.helpers.kl_apply(par_file, basis_file, par_to_file_dict, arr_shape)[source]
    -

    Applies a KL parameterization transform from basis factors to model -input arrays. Companion function to kl_setup()

    - --- - - - -
    Parameters:
      -
    • par_file (str) – the csv file to get factor values from. Must contain -the following columns: name, new_val, org_val
    • -
    • basis_file (str) – the binary file that contains the reduced basis
    • -
    • par_to_file_dict (dict) – a mapping from KL parameter prefixes to array file names.
    • -
    -
    -
    -

    Note

    -

    This is the companion function to kl_setup.

    -

    This function should be called during the forward run

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pyemu.helpers.kl_apply("kl.dat","basis.dat",{"hk":"hk_layer_1.dat",(100,100))

    -
    -
    - -
    -
    -pyemu.utils.helpers.zero_order_tikhonov(pst, parbounds=True, par_groups=None, reset=True)[source]
    -

    setup preferred-value regularization

    - --- - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – the control file instance
    • -
    • parbounds (bool) – flag to weight the prior information equations according -to parameter bound width - approx the KL transform. Default -is True
    • -
    • par_groups (list) – parameter groups to build PI equations for. If None, all -adjustable parameters are used. Default is None
    • -
    • reset (bool) – flag to reset the prior_information attribute of the pst -instance. Default is True
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pst = pyemu.Pst("pest.pst")

    -

    >>>pyemu.helpers.zero_order_tikhonov(pst)

    -
    -
    - -
    -
    -pyemu.utils.helpers.regweight_from_parbound(pst)[source]
    -

    sets regularization weights from parameter bounds -which approximates the KL expansion. Called by -zero_order_tikhonov().

    - --- - - - -
    Parameters:pst (pyemu.Pst) – a control file instance
    -
    - -
    -
    -pyemu.utils.helpers.first_order_pearson_tikhonov(pst, cov, reset=True, abs_drop_tol=0.001)[source]
    -

    setup preferred-difference regularization from a covariance matrix. -The weights on the prior information equations are the Pearson -correlation coefficients implied by covariance matrix.

    - --- - - - -
    Parameters:
      -
    • pst (pyemu.Pst) – pst instance
    • -
    • cov (pyemu.Cov) – covariance matrix instance
    • -
    • reset (bool) – drop all other pi equations. If False, append to -existing pi equations
    • -
    • abs_drop_tol (float) – tolerance to control how many pi equations are written. -If the Pearson C is less than abs_drop_tol, the prior information -equation will not be included in the control file
    • -
    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    >>>pst = pyemu.Pst("pest.pst")

    -

    >>>cov = pyemu.Cov.from_ascii("prior.cov")

    -

    >>>pyemu.helpers.first_order_pearson_tikhonov(pst,cov,abs_drop_tol=0.25)

    -
    -
    - -
    -
    -pyemu.utils.helpers.simple_tpl_from_pars(parnames, tplfilename='model.input.tpl')[source]
    -

    Make a template file just assuming a list of parameter names the values of which should be -listed in order in a model input file -:param parnames: list of names from which to make a template file -:param tplfilename: filename for TPL file (default: model.input.tpl)

    - --- - - - -
    Returns:writes a file <tplfilename> with each parameter name on a line
    -
    - -
    -
    -pyemu.utils.helpers.simple_ins_from_obs(obsnames, insfilename='model.output.ins')[source]
    -

    writes an instruction file that assumes wanting to read the values names in obsnames in order -one per line from a model output file -:param obsnames: list of obsnames to read in -:param insfilename: filename for INS file (default: model.output.ins)

    - --- - - - -
    Returns:writes a file <insfilename> with each observation read off a line
    -
    - -
    -
    -pyemu.utils.helpers.pst_from_parnames_obsnames(parnames, obsnames, tplfilename='model.input.tpl', insfilename='model.output.ins')[source]
    -

    Creates a Pst object from a list of parameter names and a list of observation names. -Default values are provided for the TPL and INS -:param parnames: list of names from which to make a template file -:param obsnames: list of obsnames to read in -:param tplfilename: filename for TPL file (default: model.input.tpl) -:param insfilename: filename for INS file (default: model.output.ins)

    - --- - - - -
    Returns:Pst object
    -
    - -
    -
    -pyemu.utils.helpers.start_workers(worker_dir, exe_rel_path, pst_rel_path, num_workers=None, worker_root='..', port=4004, rel_path=None, local=True, cleanup=True, master_dir=None, verbose=False, silent_master=False)[source]
    -

    start a group of pest(++) workers on the local machine

    - --- - - - -
    Parameters:
      -
    • worker_dir (str) – the path to a complete set of input files
    • -
    • exe_rel_path (str) – the relative path to the pest(++) executable from within the worker_dir
    • -
    • pst_rel_path (str) – the relative path to the pst file from within the worker_dir
    • -
    • num_workers (int) – number of workers to start. defaults to number of cores
    • -
    • worker_root (str) – the root to make the new worker directories in
    • -
    • rel_path (str) – the relative path to where pest(++) should be run from within the -worker_dir, defaults to the uppermost level of the worker dir
    • -
    • local (bool) – flag for using “localhost” instead of hostname on worker command line
    • -
    • cleanup (bool) – flag to remove worker directories once processes exit
    • -
    • master_dir (str) – name of directory for master instance. If master_dir -exists, then it will be removed. If master_dir is None, -no master instance will be started
    • -
    • verbose (bool) – flag to echo useful information to stdout
    • -
    -
    -
    -

    Note

    -

    if all workers (and optionally master) exit gracefully, then the worker -dirs will be removed unless cleanup is false

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    start 10 workers using the directory “template” as the base case and -also start a master instance in a directory “master”.

    -

    >>>pyemu.helpers.start_workers("template","pestpp","pest.pst",10,master_dir="master")

    -
    -
    - -
    -
    -pyemu.utils.helpers.read_pestpp_runstorage(filename, irun=0)[source]
    -

    read pars and obs from a specific run in a pest++ serialized run storage file into -pandas.DataFrame(s)

    - --- - - - - - -
    Parameters:
      -
    • filename (str) – the name of the run storage file
    • -
    • irun (int) – the run id to process. Default is 0
    • -
    -
    Returns:

      -
    • par_df (pandas.DataFrame) – parameter information
    • -
    • obs_df (pandas.DataFrame) – observation information
    • -
    -

    -
    -
    - -
    -
    -pyemu.utils.helpers.jco_from_pestpp_runstorage(rnj_filename, pst_filename)[source]
    -

    read pars and obs from a pest++ serialized run storage file (e.g., .rnj) and return -pyemu.Jco. This can then be passed to Jco.to_binary or Jco.to_coo, etc., to write jco file -in a subsequent step to avoid memory resource issues associated with very large problems.

    - --- - - - - - - - -
    Parameters:
      -
    • rnj_filename (str) – the name of the run storage file
    • -
    • pst_filename (str) – the name of the pst file
    • -
    -
    Returns:

    jco_cols

    -
    Return type:

    pyemu.Jco

    -
    -

    Notes

    -

    TODO: -0. Check rnj file contains transformed par vals (i.e., in model input space) -1. Currently only returns pyemu.Jco; doesn’t write jco file due to memory issues

    -
    -
    associated with very large problems
    -
      -
    1. Compare rnj and jco from Freyberg problem in autotests
    2. -
    -
    - -
    -
    -pyemu.utils.helpers.parse_dir_for_io_files(d)[source]
    -

    a helper function to find template/input file pairs and -instruction file/output file pairs. the return values from this -function can be passed straight to pyemu.Pst.from_io_files() classmethod -constructor. Assumes the template file names are <input_file>.tpl and -instruction file names are <output_file>.ins.

    - --- - - - - - -
    Parameters:d (str) – directory to search for interface files
    Returns:
      -
    • tpl_files (list) – list of template files in d
    • -
    • in_files (list) – list of input files in d
    • -
    • ins_files (list) – list of instruction files in d
    • -
    • out_files (list) – list of output files in d
    • -
    -
    -
    - -
    -
    -pyemu.utils.helpers.pst_from_io_files(tpl_files, in_files, ins_files, out_files, pst_filename=None)[source]
    -

    generate a Pst instance from the model io files. If ‘inschek’ -is available (either in the current directory or registered -with the system variables) and the model output files are available -, then the observation values in the control file will be set to the -values of the model-simulated equivalents to observations. This can be -useful for testing

    - --- - - - - - - - -
    Parameters:
      -
    • tpl_files (list) – list of pest template files
    • -
    • in_files (list) – list of corresponding model input files
    • -
    • ins_files (list) – list of pest instruction files
    • -
    • out_files (list) – list of corresponding model output files
    • -
    • pst_filename (str) – name of file to write the control file to. If None, -control file is not written. Default is None
    • -
    -
    Returns:

    pst

    -
    Return type:

    pyemu.Pst

    -
    -
    -

    Example

    -

    >>>import pyemu

    -

    this will construct a new Pst instance from template and instruction files -found in the current directory, assuming that the naming convention follows -that listed in parse_dir_for_io_files()

    -

    >>>pst = pyemu.helpers.pst_from_io_files(*pyemu.helpers.parse_dir_for_io_files('.'))

    -
    -
    - -
    -
    -class pyemu.utils.helpers.PstFromFlopyModel(model, new_model_ws, org_model_ws=None, pp_props=[], const_props=[], temporal_bc_props=[], grid_props=[], grid_geostruct=None, pp_space=None, zone_props=[], pp_geostruct=None, par_bounds_dict=None, sfr_pars=False, temporal_bc_geostruct=None, remove_existing=False, k_zone_dict=None, mflist_waterbudget=True, mfhyd=True, hds_kperk=[], use_pp_zones=False, obssim_smp_pairs=None, external_tpl_in_pairs=None, external_ins_out_pairs=None, extra_pre_cmds=None, extra_model_cmds=None, extra_post_cmds=None, redirect_forward_output=True, tmp_files=None, model_exe_name=None, build_prior=True, sfr_obs=False, all_wells=False, bc_props=[], spatial_bc_props=[], spatial_bc_geostruct=None, hfb_pars=False, kl_props=None, kl_num_eig=100, kl_geostruct=None)[source]
    -

    Bases: object

    -
    -
    a monster helper class to setup multiplier parameters for an
    -
    existing MODFLOW model. does all kinds of coolness like building a -meaningful prior, assigning somewhat meaningful parameter groups and -bounds, writes a forward_run.py script with all the calls need to -implement multiplier parameters, run MODFLOW and post-process.
    -
    - --- - - - - - - - -
    Parameters:
      -
    • model (flopy.mbase) – a loaded flopy model instance. If model is an str, it is treated as a -MODFLOW nam file (requires org_model_ws)
    • -
    • new_model_ws (str) – a directory where the new version of MODFLOW input files and PEST(++) -files will be written
    • -
    • org_model_ws (str) – directory to existing MODFLOW model files. Required if model argument -is an str. Default is None
    • -
    • pp_props (list) – pilot point multiplier parameters for grid-based properties. -A nested list of grid-scale model properties to parameterize using -name, iterable pairs. For 3D properties, the iterable is zero-based -layer indices. For example, [“lpf.hk”,[0,1,2,]] would setup pilot point multiplier -parameters for layer property file horizontal hydraulic conductivity for model -layers 1,2, and 3. For time-varying properties (e.g. recharge), the -iterable is for zero-based stress period indices. For example, [“rch.rech”,[0,4,10,15]] -would setup pilot point multiplier parameters for recharge for stress -period 1,5,11,and 16.
    • -
    • const_props (list) – constant (uniform) multiplier parameters for grid-based properties. -A nested list of grid-scale model properties to parameterize using -name, iterable pairs. For 3D properties, the iterable is zero-based -layer indices. For example, [“lpf.hk”,[0,1,2,]] would setup constant (uniform) multiplier -parameters for layer property file horizontal hydraulic conductivity for model -layers 1,2, and 3. For time-varying properties (e.g. recharge), the -iterable is for zero-based stress period indices. For example, [“rch.rech”,[0,4,10,15]] -would setup constant (uniform) multiplier parameters for recharge for stress -period 1,5,11,and 16.
    • -
    • temporal_bc_props (list) – boundary condition stress-period level multiplier parameters. -A nested list of boundary condition elements to parameterize using -name, iterable pairs. The iterable is zero-based stress-period indices. -For example, to setup multipliers for WEL flux and for RIV conductance, -bc_props = [[“wel.flux”,[0,1,2]],[“riv.cond”,None]] would setup -multiplier parameters for well flux for stress periods 1,2 and 3 and -would setup one single river conductance multiplier parameter that is applied -to all stress periods
    • -
    • spatial_bc_props (list) – boundary condition spatial multiplier parameters. -A nested list of boundary condition elements to parameterize using -names (e.g. [[“riv.cond”,0],[“wel.flux”,1] to setup up cell-based parameters for -each boundary condition element listed. These multipler parameters are applied across -all stress periods. For this to work, there must be the same number of entries -for all stress periods. If more than one BC of the same type is in a single -cell, only one parameter is used to multiply all BCs in the same cell.
    • -
    • grid_props (list) – grid-based (every active model cell) multiplier parameters. -A nested list of grid-scale model properties to parameterize using -name, iterable pairs. For 3D properties, the iterable is zero-based -layer indices (e.g., [“lpf.hk”,[0,1,2,]] would setup a multiplier -parameter for layer property file horizontal hydraulic conductivity for model -layers 1,2, and 3 in every active model cell). For time-varying properties (e.g. recharge), the -iterable is for zero-based stress period indices. For example, [“rch.rech”,[0,4,10,15]] -would setup grid-based multiplier parameters in every active model cell -for recharge for stress period 1,5,11,and 16.
    • -
    • grid_geostruct (pyemu.geostats.GeoStruct) – the geostatistical structure to build the prior parameter covariance matrix -elements for grid-based parameters. If None, a generic GeoStruct is created -using an “a” parameter that is 10 times the max cell size. Default is None
    • -
    • pp_space (int) – number of grid cells between pilot points. If None, use the default -in pyemu.pp_utils.setup_pilot_points_grid. Default is None
    • -
    • zone_props (list) – zone-based multiplier parameters. -A nested list of grid-scale model properties to parameterize using -name, iterable pairs. For 3D properties, the iterable is zero-based -layer indices (e.g., [“lpf.hk”,[0,1,2,]] would setup a multiplier -parameter for layer property file horizontal hydraulic conductivity for model -layers 1,2, and 3 for unique zone values in the ibound array. -For time-varying properties (e.g. recharge), the iterable is for -zero-based stress period indices. For example, [“rch.rech”,[0,4,10,15]] -would setup zone-based multiplier parameters for recharge for stress -period 1,5,11,and 16.
    • -
    • pp_geostruct (pyemu.geostats.GeoStruct) – the geostatistical structure to use for building the prior parameter -covariance matrix for pilot point parameters. If None, a generic -GeoStruct is created using pp_space and grid-spacing information. -Default is None
    • -
    • par_bounds_dict (dict) – a dictionary of model property/boundary condition name, upper-lower bound pairs. -For example, par_bounds_dict = {“hk”:[0.01,100.0],”flux”:[0.5,2.0]} would -set the bounds for horizontal hydraulic conductivity to -0.001 and 100.0 and set the bounds for flux parameters to 0.5 and -2.0. For parameters not found in par_bounds_dict, -pyemu.helpers.wildass_guess_par_bounds_dict is -used to set somewhat meaningful bounds. Default is None
    • -
    • temporal_bc_geostruct (pyemu.geostats.GeoStruct) – the geostastical struture to build the prior parameter covariance matrix -for time-varying boundary condition multiplier parameters. This GeoStruct -express the time correlation so that the ‘a’ parameter is the length of -time that boundary condition multiplier parameters are correlated across. -If None, then a generic GeoStruct is created that uses an ‘a’ parameter -of 3 stress periods. Default is None
    • -
    • spatial_bc_geostruct (pyemu.geostats.GeoStruct) – the geostastical struture to build the prior parameter covariance matrix -for spatially-varying boundary condition multiplier parameters. -If None, a generic GeoStruct is created using an “a” parameter that -is 10 times the max cell size. Default is None.
    • -
    • remove_existing (bool) – a flag to remove an existing new_model_ws directory. If False and -new_model_ws exists, an exception is raised. If True and new_model_ws -exists, the directory is destroyed - user beware! Default is False.
    • -
    • k_zone_dict (dict) – a dictionary of zero-based layer index, zone array pairs. Used to -override using ibound zones for zone-based parameterization. If None, -use ibound values greater than zero as zones.
    • -
    • use_pp_zones (bool) – a flag to use ibound zones (or k_zone_dict, see above) as pilot -point zones. If False, ibound values greater than zero are treated as -a single zone for pilot points. Default is False
    • -
    • obssim_smp_pairs (list) – a list of observed-simulated PEST-type SMP file pairs to get observations -from and include in the control file. Default is []
    • -
    • external_tpl_in_pairs (list) – a list of existing template file, model input file pairs to parse parameters -from and include in the control file. Default is []
    • -
    • external_ins_out_pairs (list) – a list of existing instruction file, model output file pairs to parse -observations from and include in the control file. Default is []
    • -
    • extra_pre_cmds (list) – a list of preprocessing commands to add to the forward_run.py script -commands are executed with os.system() within forward_run.py. Default -is None.
    • -
    • redirect_forward_output (bool) – flag for whether to redirect forward model output to text files (True) or -allow model output to be directed to the screen (False) -Default is True
    • -
    • extra_post_cmds (list) – a list of post-processing commands to add to the forward_run.py script. -Commands are executed with os.system() within forward_run.py. -Default is None.
    • -
    • tmp_files (list) – a list of temporary files that should be removed at the start of the forward -run script. Default is [].
    • -
    • model_exe_name (str) – binary name to run modflow. If None, a default from flopy is used, -which is dangerous because of the non-standard binary names -(e.g. MODFLOW-NWT_x64, MODFLOWNWT, mfnwt, etc). Default is None.
    • -
    • build_prior (bool) – flag to build prior covariance matrix. Default is True
    • -
    • sfr_obs (bool) – flag to include observations of flow and aquifer exchange from -the sfr ASCII output file
    • -
    • all_wells (bool [DEPRECATED - now use spatial_bc_pars]) –
    • -
    • hfb_pars (bool) – add HFB parameters. uses pyemu.gw_utils.write_hfb_template(). the resulting -HFB pars have parval1 equal to the values in the original file and use the -spatial_bc_geostruct to build geostatistical covariates between parameters
    • -
    -
    Returns:

    PstFromFlopyModel

    -
    Return type:

    PstFromFlopyModel

    -
    -
    -
    -pst
    -

    pyemu.Pst

    -
    - -
    -

    Note

    -

    works a lot better if TEMPCHEK, INSCHEK and PESTCHEK are available in the -system path variable

    -
    -
    -
    -setup_sfr_obs()[source]
    -

    setup sfr ASCII observations

    -
    - -
    -
    -setup_sfr_pars()[source]
    -

    setup multiplier parameters for sfr segment data -Adding support for reachinput (and isfropt = 1)

    -
    - -
    -
    -setup_hfb_pars()[source]
    -

    setup non-mult parameters for hfb (yuck!)

    -
    - -
    -
    -setup_mult_dirs()[source]
    -

    setup the directories to use for multiplier parameterization. Directories -are make within the PstFromFlopyModel.m.model_ws directory

    -
    - -
    -
    -setup_model(model, org_model_ws, new_model_ws)[source]
    -

    setup the flopy.mbase instance for use with multipler parameters. -Changes model_ws, sets external_path and writes new MODFLOW input -files

    - --- - - - -
    Parameters:
      -
    • model (flopy.mbase) – flopy model instance
    • -
    • org_model_ws (str) – the orginal model working space
    • -
    • new_model_ws (str) – the new model working space
    • -
    -
    -
    - -
    -
    -get_count(name)[source]
    -

    get the latest counter for a certain parameter type.

    - --- - - - - - - - -
    Parameters:name (str) – the parameter type
    Returns:count – the latest count for a parameter type
    Return type:int
    -
    -

    Note

    -

    calling this function increments the counter for the passed -parameter type

    -
    -
    - -
    -
    -prep_mlt_arrays()[source]
    -

    prepare multipler arrays. Copies existing model input arrays and -writes generic (ones) multiplier arrays

    -
    - -
    -
    -write_u2d(u2d)[source]
    -

    write a flopy.utils.Util2D instance to an ASCII text file using the -Util2D filename

    - --- - - - - - - - -
    Parameters:u2d (flopy.utils.Util2D) –
    Returns:filename – the name of the file written (without path)
    Return type:str
    -
    - -
    -
    -write_const_tpl(name, tpl_file, zn_array)[source]
    -

    write a template file a for a constant (uniform) multiplier parameter

    - --- - - - - - - - -
    Parameters:
      -
    • name (str) – the base parameter name
    • -
    • tpl_file (str) – the template file to write
    • -
    • zn_array (numpy.ndarray) – an array used to skip inactive cells
    • -
    -
    Returns:

    df – a dataframe with parameter information

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -write_grid_tpl(name, tpl_file, zn_array)[source]
    -

    write a template file a for grid-based multiplier parameters

    - --- - - - - - - - -
    Parameters:
      -
    • name (str) – the base parameter name
    • -
    • tpl_file (str) – the template file to write
    • -
    • zn_array (numpy.ndarray) – an array used to skip inactive cells
    • -
    -
    Returns:

    df – a dataframe with parameter information

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -static write_zone_tpl(model, name, tpl_file, zn_array, zn_suffix, logger=None)[source]
    -

    write a template file a for zone-based multiplier parameters

    - --- - - - - - - - -
    Parameters:
      -
    • model (flopy model object) – model from which to obtain workspace information, nrow, and ncol
    • -
    • name (str) – the base parameter name
    • -
    • tpl_file (str) – the template file to write
    • -
    • zn_array (numpy.ndarray) – an array used to skip inactive cells
    • -
    • logger (a logger object) – optional - a logger object to document errors, etc.
    • -
    -
    Returns:

    df – a dataframe with parameter information

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -grid_prep()[source]
    -

    prepare grid-based parameterizations

    -
    - -
    -
    -pp_prep(mlt_df)[source]
    -

    prepare pilot point based parameterizations

    - --- - - - -
    Parameters:mlt_df (pandas.DataFrame) – a dataframe with multiplier array information
    -
    -

    Note

    -

    calls pyemu.pp_utils.setup_pilot_points_grid()

    -
    -
    - -
    -
    -kl_prep(mlt_df)[source]
    -

    prepare KL based parameterizations

    - --- - - - -
    Parameters:mlt_df (pandas.DataFrame) – a dataframe with multiplier array information
    -
    -

    Note

    -

    calls pyemu.helpers.setup_kl()

    -
    -
    - -
    -
    -setup_array_pars()[source]
    -

    main entry point for setting up array multipler parameters

    -
    - -
    -
    -setup_observations()[source]
    -

    main entry point for setting up observations

    -
    - -
    -
    -draw(num_reals=100, sigma_range=6)[source]
    -

    draw like a boss!

    - --- - - - - - -
    Parameters:
      -
    • num_reals (int) – number of realizations to generate. Default is 100
    • -
    • sigma_range (float) – number of standard deviations represented by the parameter bounds. Default -is 6.
    • -
    -
    Returns:

      -
    • cov (pyemu.Cov)
    • -
    • a full covariance matrix
    • -
    -

    -
    -
    - -
    -
    -build_prior(fmt='ascii', filename=None, droptol=None, chunk=None, sparse=False, sigma_range=6)[source]
    -

    build a prior parameter covariance matrix.

    - --- - - - - - -
    Parameters:
      -
    • fmt (str) – the format to save the cov matrix. Options are “ascii”,”binary”,”uncfile”, “coo”. -default is “ascii”
    • -
    • filename (str) – the filename to save the prior cov matrix to. If None, the name is formed using -model nam_file name. Default is None.
    • -
    • droptol (float) – tolerance for dropping near-zero values when writing compressed binary. -Default is None
    • -
    • chunk (int) – chunk size to write in a single pass - for binary only
    • -
    • sparse (bool) – flag to build a pyemu.SparseMatrix format cov matrix. Default is False
    • -
    • sigma_range (float) – number of standard deviations represented by the parameter bounds. Default -is 6.
    • -
    -
    Returns:

      -
    • cov (pyemu.Cov)
    • -
    • a full covariance matrix
    • -
    -

    -
    -
    - -
    -
    -build_pst(filename=None)[source]
    -

    build the pest control file using the parameterizations and -observations.

    - --- - - - -
    Parameters:filename (str) – the filename to save the pst to. If None, the name if formed from -the model namfile name. Default is None.
    -
    -

    Note

    -

    calls pyemu.Pst.from_io_files

    -

    calls PESTCHEK

    -
    -
    - -
    -
    -add_external()[source]
    -

    add external (existing) template files and instrution files to the -Pst instance

    -
    - -
    -
    -write_forward_run()[source]
    -

    write the forward run script forward_run.py

    -
    - -
    -
    -parse_k(k, vals)[source]
    -

    parse the iterable from a property or boundary condition argument

    - --- - - - - - - - -
    Parameters:
      -
    • k (int or iterable int) – the iterable
    • -
    • vals (iterable of ints) – the acceptable values that k may contain
    • -
    -
    Returns:

    k_vals – parsed k values

    -
    Return type:

    iterable of int

    -
    -
    - -
    -
    -parse_pakattr(pakattr)[source]
    -

    parse package-iterable pairs from a property or boundary condition -argument

    - --- - - - - - -
    Parameters:pakattr (iterable len 2) –
    Returns:
      -
    • pak (flopy.PakBase) – the flopy package from the model instance
    • -
    • attr ((varies)) – the flopy attribute from pak. Could be Util2D, Util3D, -Transient2D, or MfList
    • -
    • attrname ((str)) – the name of the attribute for MfList type. Only returned if -attr is MfList. For example, if attr is MfList and pak is -flopy.modflow.ModflowWel, then attrname can only be “flux”
    • -
    -
    -
    - -
    -
    -setup_bc_pars()[source]
    -
    - -
    -
    -setup_temporal_bc_pars()[source]
    -

    main entry point for setting up boundary condition multiplier -parameters

    -
    - -
    -
    -setup_spatial_bc_pars()[source]
    -

    helper to setup crazy numbers of well flux multipliers

    -
    - -
    -
    -bc_helper(k, pak, attr, col)[source]
    -

    helper to setup boundary condition multiplier parameters for a given -k, pak, attr set.

    - --- - - - -
    Parameters:
      -
    • k (int or iterable of int) – the zero-based stress period indices
    • -
    • pak (flopy.PakBase=) – the MODFLOW package
    • -
    • attr (MfList) – the MfList instance
    • -
    • col (str) – the column name in the MfList recarray to parameterize
    • -
    -
    -
    - -
    -
    -setup_hds()[source]
    -

    setup modflow head save file observations for given kper (zero-based -stress period index) and k (zero-based layer index) pairs using the -kperk argument.

    -
    -

    Note

    -

    this can setup a shit-ton of observations

    -

    this is useful for dataworth analyses or for monitoring -water levels as forecasts

    -
    -
    - -
    -
    -setup_smp()[source]
    -

    setup observations from PEST-style SMP file pairs

    -
    - -
    -
    -setup_hob()[source]
    -

    setup observations from the MODFLOW HOB package

    -
    - -
    -
    -setup_hyd()[source]
    -

    setup observations from the MODFLOW HYDMOD package

    -
    - -
    -
    -setup_water_budget_obs()[source]
    -

    setup observations from the MODFLOW list file for -volume and flux water buget information

    -
    - -
    - -
    -
    -pyemu.utils.helpers.apply_array_pars(arr_par_file='arr_pars.csv')[source]
    -

    a function to apply array-based multipler parameters. Used to implement -the parameterization constructed by PstFromFlopyModel during a forward run

    - --- - - - -
    Parameters:
      -
    • arr_par_file (str) –
    • -
    • to csv file detailing parameter array multipliers (path) –
    • -
    -
    -
    -

    Note

    -

    “arr_pars.csv” - is written by PstFromFlopy

    -

    the function should be added to the forward_run.py script but can be called on any correctly formatted csv

    -
    -
    - -
    -
    -pyemu.utils.helpers.apply_bc_pars()[source]
    -

    a function to apply boundary condition multiplier parameters. Used to implement -the parameterization constructed by PstFromFlopyModel during a forward run

    -
    -

    Note

    -

    requires “bc_pars.csv”

    -

    should be added to the forward_run.py script

    -
    -
    - -
    -
    -pyemu.utils.helpers.apply_hfb_pars()[source]
    -

    a function to apply HFB multiplier parameters. Used to implement -the parameterization constructed by write_hfb_zone_multipliers_template()

    -

    This is to account for the horrible HFB6 format that differs from other BCs making this a special case

    -
    -

    Note

    -

    requires “hfb_pars.csv”

    -

    should be added to the forward_run.py script

    -
    -
    - -
    -
    -pyemu.utils.helpers.plot_flopy_par_ensemble(pst, pe, num_reals=None, model=None, fig_axes_generator=None, pcolormesh_transform=None)[source]
    -

    function to plot ensemble of parameter values for a flopy/modflow model. Assumes -the FlopytoPst helper was used to setup the model and the forward run.

    - --- - - - -
    Parameters:
      -
    • pst (Pst instance) –
    • -
    • pe (ParameterEnsemble instance) –
    • -
    • num_reals (int) – number of realizations to process. If None, all realizations are processed. -default is None
    • -
    • model (flopy.mbase) – model instance used for masking inactive areas and also geo-locating plots. If None, -generic plots are made. Default is None.
    • -
    • fig_axes_generator (function) – a function that returns a pyplot.figure and list of pyplot.axes instances for plots. -If None, a generic 8.5inX11in figure with 3 rows and 2 cols is used.
    • -
    • pcolormesh_transform (cartopy.csr.CRS) – transform to map pcolormesh plot into correct location. Requires model arg
    • -
    -
    -
    -

    Note

    -

    Needs “arr_pars.csv” to run

    -
    -
    -

    Todo

    -

    add better support for log vs nonlog- currently just logging everything -add support for cartopy basemap and stamen tiles

    -
    -
    - -
    -
    -pyemu.utils.helpers.plot_summary_distributions(df, ax=None, label_post=False, label_prior=False, subplots=False, figsize=(11, 8.5), pt_color='b')[source]
    -

    helper function to plot gaussian distrbutions from prior and posterior -means and standard deviations

    - --- - - - - - -
    Parameters:
      -
    • df (pandas.DataFrame) – a dataframe and csv file. Must have columns named: -‘prior_mean’,’prior_stdev’,’post_mean’,’post_stdev’. If loaded -from a csv file, column 0 is assumed to tbe the index
    • -
    • ax (matplotlib.pyplot.axis) – If None, and not subplots, then one is created -and all distributions are plotted on a single plot
    • -
    • label_post (bool) – flag to add text labels to the peak of the posterior
    • -
    • label_prior (bool) – flag to add text labels to the peak of the prior
    • -
    • subplots ((boolean)) – flag to use subplots. If True, then 6 axes per page -are used and a single prior and posterior is plotted on each
    • -
    • figsize (tuple) – matplotlib figure size
    • -
    -
    Returns:

      -
    • figs (list) – list of figures
    • -
    • axes (list) – list of axes
    • -
    -

    -
    -
    -

    Note

    -

    This is useful for demystifying FOSM results

    -

    if subplots is False, a single axis is returned

    -
    -
    -

    Example

    -

    >>>import matplotlib.pyplot as plt

    -

    >>>import pyemu

    -

    >>>pyemu.helpers.plot_summary_distributions("pest.par.usum.csv")

    -

    >>>plt.show()

    -
    -
    - -
    -
    -pyemu.utils.helpers.gaussian_distribution(mean, stdev, num_pts=50)[source]
    -

    get an x and y numpy.ndarray that spans the +/- 4 -standard deviation range of a gaussian distribution with -a given mean and standard deviation. useful for plotting

    - --- - - - - - -
    Parameters:
      -
    • mean (float) – the mean of the distribution
    • -
    • stdev (float) – the standard deviation of the distribution
    • -
    • num_pts (int) – the number of points in the returned ndarrays. -Default is 50
    • -
    -
    Returns:

      -
    • x (numpy.ndarray) – the x-values of the distribution
    • -
    • y (numpy.ndarray) – the y-values of the distribution
    • -
    -

    -
    -
    - -
    -
    -pyemu.utils.helpers.build_jac_test_csv(pst, num_steps, par_names=None, forward=True)[source]
    -

    build a dataframe of jactest inputs for use with sweep

    - --- - - - - - - - -
    Parameters:
      -
    • pst (pyemu.Pst) –
    • -
    • num_steps (int) – number of pertubation steps for each parameter
    • -
    • par_names (list) – names of pars to test. If None, all adjustable pars are used -Default is None
    • -
    • forward (bool) – flag to start with forward pertubations. Default is True
    • -
    -
    Returns:

    df – the index of the dataframe is par name and the parval used.

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -pyemu.utils.helpers.write_df_tpl(filename, df, sep=', ', tpl_marker='~', **kwargs)[source]
    -

    function write a pandas dataframe to a template file. -:param filename: template filename -:type filename: str -:param df: dataframe to write -:type df: pandas.DataFrame -:param sep: separate to pass to df.to_csv(). default is ‘,’ -:type sep: char -:param tpl_marker: template file marker. default is ‘~’ -:type tpl_marker: char -:param kwargs: additional keyword args to pass to df.to_csv() -:type kwargs: dict

    - --- - - - - - -
    Returns:
    Return type:None
    -
    -

    Note

    -

    If you don’t use this function, make sure that you flush the -file handle before df.to_csv() and you pass mode=’a’ to to_csv()

    -
    -
    - -
    -
    -

    pyemu.utils.optimization module

    -
    -
    -pyemu.utils.optimization.add_pi_obj_func(pst, obj_func_dict=None, out_pst_name=None)[source]
    -
    - -
    -
    -

    pyemu.utils.pp_utils module

    -
    -
    -pyemu.utils.pp_utils.setup_pilotpoints_grid(ml=None, sr=None, ibound=None, prefix_dict=None, every_n_cell=4, use_ibound_zones=False, pp_dir='.', tpl_dir='.', shapename='pp.shp')[source]
    -

    setup grid-based pilot points. Uses the ibound to determine -where to set pilot points. pilot points are given generic pp_ -names unless prefix_dict is passed. Write template files and a -shapefile as well…hopefully this is useful to someone…

    - --- - - - - - - - -
    Parameters:
      -
    • ml (flopy.mbase) – a flopy mbase dervied type. If None, sr must not be None.
    • -
    • sr (flopy.utils.reference.SpatialReference) – a spatial reference use to locate the model grid in space. If None, -ml must not be None. Default is None
    • -
    • ibound (numpy.ndarray) – the modflow ibound integer array. Used to set pilot points only in active areas (!=0). -If None and ml is None, then pilot points are set in all rows and columns according to -every_n_cell. Default is None.
    • -
    • prefix_dict (dict) – a dictionary of layer index, pilot point parameter prefixes. -Example : {0:["hk_"],1:["vka_"]} would setup pilot points with -the prefix "hk" for model layers 1 and "vka" for model layer 2 (zero based). -If None, a generic set of pilot points with the “pp” prefix are setup -for a generic nrowXncol grid. Default is None.
    • -
    • use_ibound_zones (bool) – a flag to use the greater-than-zero values in the ibound as pilot point zones. If False,ibound -values greater than zero are treated as a single zone. Default is False.
    • -
    • pp_dir (str) – directory to write pilot point files to. Default is ‘.’
    • -
    • tpl_dir (str) – directory to write pilot point template file to. Default is ‘.’
    • -
    • shapename (str) – name of shapefile to write that contains pilot point information. Default is “pp.shp”
    • -
    -
    Returns:

    pp_df – a dataframe summarizing pilot point information (same information -written to shapename

    -
    Return type:

    pandas.DataFrame

    -
    -
    -

    Example

    -

    >>>import flopy

    -

    >>>from pyemu.utils import setup_pilotpoints_grid

    -

    >>>m = flopy.modflow.Modfow.load("mymodel.nam")

    -

    >>>setup_pilotpoints_grid(m,prefix_dict={0:['hk_'],1:['vka_']},

    -

    >>>                       every_n_cell=3,shapename='layer1_pp.shp')

    -
    -
    - -
    -
    -pyemu.utils.pp_utils.pp_file_to_dataframe(pp_filename)[source]
    -

    read a pilot point file to a pandas Dataframe

    - --- - - - - - - - -
    Parameters:pp_filename (str) – pilot point file
    Returns:df – a dataframe with pp_utils.PP_NAMES for columns
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.utils.pp_utils.pp_tpl_to_dataframe(tpl_filename)[source]
    -

    read a pilot points template file to a pandas dataframe

    - --- - - - - - - - -
    Parameters:tpl_filename (str) – pilot points template file
    Returns:df – a dataframe with “parnme” included
    Return type:pandas.DataFrame
    -
    - -
    -
    -pyemu.utils.pp_utils.write_pp_shapfile(pp_df, shapename=None)[source]
    -

    write pilot points dataframe to a shapefile

    - --- - - - -
    Parameters:
      -
    • pp_df (pandas.DataFrame or str) – pilot point dataframe or a pilot point filename. Dataframe -must include “x” and “y”
    • -
    • shapename (str) – shapefile name. If None, pp_df must be str and shapefile -is saved as <pp_df>.shp
    • -
    -
    -
    -

    Note

    -

    requires pyshp

    -
    -
    - -
    -
    -pyemu.utils.pp_utils.write_pp_file(filename, pp_df)[source]
    -

    write a pilot points dataframe to a pilot points file

    - --- - - - -
    Parameters:
      -
    • filename (str) – pilot points file to write
    • -
    • pp_df (pandas.DataFrame) – a dataframe that has columns “x”,”y”,”zone”, and “value”
    • -
    -
    -
    - -
    -
    -pyemu.utils.pp_utils.pilot_points_to_tpl(pp_file, tpl_file=None, name_prefix=None)[source]
    -

    write a template file for a pilot points file

    - --- - - - - - - - -
    Parameters:
      -
    • pp_file (str) – pilot points file
    • -
    • tpl_file (str) – template file name to write. If None, append “.tpl” to -the pp_file arg. Default is None
    • -
    • name_prefix (str) – name to prepend to parameter names for each pilot point. For example, -if name_prefix = "hk_", then each pilot point parameter will be named -“hk_0001”,”hk_0002”, etc. If None, parameter names from pp_df.name -are used. Default is None.
    • -
    -
    Returns:

    pp_df

    -
    -
    a dataframe with pilot point information (name,x,y,zone,parval1)
    -

    with the parameter information (parnme,tpl_str)

    -
    -
    -

    -
    Return type:

    pandas.DataFrame

    -
    -
    - -
    -
    -

    Module contents

    -

    pyemu utils module contains lots of useful functions and -classes, including support for geostatistical interpolation and -covariance matrices, pilot point setup and processing and -functionality dedicated to wrapping MODFLOW models into -the PEST(++) model independent framework

    -
    -
    - - -
    - -
    -
    - -
    -
    - - - - - - - \ No newline at end of file diff --git a/docs/_build/latex/Makefile b/docs/_build/latex/Makefile deleted file mode 100644 index b1a5d2d2f..000000000 --- a/docs/_build/latex/Makefile +++ /dev/null @@ -1,81 +0,0 @@ -# Makefile for Sphinx LaTeX output - -ALLDOCS = $(basename $(wildcard *.tex)) -ALLPDF = $(addsuffix .pdf,$(ALLDOCS)) -ALLDVI = $(addsuffix .dvi,$(ALLDOCS)) -ALLPS = $(addsuffix .ps,$(ALLDOCS)) - -# Prefix for archive names -ARCHIVEPREFIX = -# Additional LaTeX options -LATEXOPTS = -# format: pdf or dvi -FMT = pdf - -LATEX = latex -PDFLATEX = pdflatex -MAKEINDEX = makeindex - - -all: $(ALLPDF) -all-pdf: $(ALLPDF) -all-dvi: $(ALLDVI) -all-ps: $(ALLPS) - -all-pdf-ja: - for f in *.pdf *.png *.gif *.jpg *.jpeg; do extractbb $$f; done - for f in *.tex; do platex -kanji=utf8 $(LATEXOPTS) $$f; done - for f in *.tex; do platex -kanji=utf8 $(LATEXOPTS) $$f; done - for f in *.tex; do platex -kanji=utf8 $(LATEXOPTS) $$f; done - -for f in *.idx; do mendex -U -f -d "`basename $$f .idx`.dic" -s python.ist $$f; done - for f in *.tex; do platex -kanji=utf8 $(LATEXOPTS) $$f; done - for f in *.tex; do platex -kanji=utf8 $(LATEXOPTS) $$f; done - for f in *.dvi; do dvipdfmx $$f; done - -zip: all-$(FMT) - mkdir $(ARCHIVEPREFIX)docs-$(FMT) - cp $(ALLPDF) $(ARCHIVEPREFIX)docs-$(FMT) - zip -q -r -9 $(ARCHIVEPREFIX)docs-$(FMT).zip $(ARCHIVEPREFIX)docs-$(FMT) - rm -r $(ARCHIVEPREFIX)docs-$(FMT) - -tar: all-$(FMT) - mkdir $(ARCHIVEPREFIX)docs-$(FMT) - cp $(ALLPDF) $(ARCHIVEPREFIX)docs-$(FMT) - tar cf $(ARCHIVEPREFIX)docs-$(FMT).tar $(ARCHIVEPREFIX)docs-$(FMT) - rm -r $(ARCHIVEPREFIX)docs-$(FMT) - -gz: tar - gzip -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz - -bz2: tar - bzip2 -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar - -xz: tar - xz -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar - -# The number of LaTeX runs is quite conservative, but I don't expect it -# to get run often, so the little extra time won't hurt. -%.dvi: %.tex - $(LATEX) $(LATEXOPTS) '$<' - $(LATEX) $(LATEXOPTS) '$<' - $(LATEX) $(LATEXOPTS) '$<' - -$(MAKEINDEX) -s python.ist '$(basename $<).idx' - $(LATEX) $(LATEXOPTS) '$<' - $(LATEX) $(LATEXOPTS) '$<' - -%.pdf: %.tex - $(PDFLATEX) $(LATEXOPTS) '$<' - $(PDFLATEX) $(LATEXOPTS) '$<' - $(PDFLATEX) $(LATEXOPTS) '$<' - -$(MAKEINDEX) -s python.ist '$(basename $<).idx' - $(PDFLATEX) $(LATEXOPTS) '$<' - $(PDFLATEX) $(LATEXOPTS) '$<' - -%.ps: %.dvi - dvips '$<' - -clean: - rm -f *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla *.ps *.tar *.tar.gz *.tar.bz2 *.tar.xz $(ALLPDF) $(ALLDVI) - -.PHONY: all all-pdf all-dvi all-ps clean zip tar gz bz2 xz -.PHONY: all-pdf-ja diff --git a/docs/_build/latex/footnotehyper-sphinx.sty b/docs/_build/latex/footnotehyper-sphinx.sty deleted file mode 100644 index 5995f012d..000000000 --- a/docs/_build/latex/footnotehyper-sphinx.sty +++ /dev/null @@ -1,269 +0,0 @@ -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{footnotehyper-sphinx}% - [2017/10/27 v1.7 hyperref aware footnote.sty for sphinx (JFB)] -%% -%% Package: footnotehyper-sphinx -%% Version: based on footnotehyper.sty 2017/03/07 v1.0 -%% as available at http://www.ctan.org/pkg/footnotehyper -%% License: the one applying to Sphinx -%% -%% Refer to the PDF documentation at http://www.ctan.org/pkg/footnotehyper for -%% the code comments. -%% -%% Differences: -%% 1. a partial tabulary compatibility layer added (enough for Sphinx mark-up), -%% 2. use of \spx@opt@BeforeFootnote from sphinx.sty, -%% 3. use of \sphinxunactivateextrasandspace from sphinx.sty, -%% 4. macro definition \sphinxfootnotemark, -%% 5. macro definition \sphinxlongtablepatch -%% 6. replaced an \undefined by \@undefined -\DeclareOption*{\PackageWarning{footnotehyper-sphinx}{Option `\CurrentOption' is unknown}}% -\ProcessOptions\relax -\newbox\FNH@notes -\newdimen\FNH@width -\let\FNH@colwidth\columnwidth -\newif\ifFNH@savingnotes -\AtBeginDocument {% - \let\FNH@latex@footnote \footnote - \let\FNH@latex@footnotetext\footnotetext - \let\FNH@H@@footnotetext \@footnotetext - \newenvironment{savenotes} - {\FNH@savenotes\ignorespaces}{\FNH@spewnotes\ignorespacesafterend}% - \let\spewnotes \FNH@spewnotes - \let\footnote \FNH@footnote - \let\footnotetext \FNH@footnotetext - \let\endfootnote \FNH@endfntext - \let\endfootnotetext\FNH@endfntext - \@ifpackageloaded{hyperref} - {\ifHy@hyperfootnotes - \let\FNH@H@@footnotetext\H@@footnotetext - \else - \let\FNH@hyper@fntext\FNH@nohyp@fntext - \fi}% - {\let\FNH@hyper@fntext\FNH@nohyp@fntext}% -}% -\def\FNH@hyper@fntext{\FNH@fntext\FNH@hyper@fntext@i}% -\def\FNH@nohyp@fntext{\FNH@fntext\FNH@nohyp@fntext@i}% -\def\FNH@fntext #1{% - \ifx\ifmeasuring@\@undefined - \expandafter\@secondoftwo\else\expandafter\@firstofone\fi -% these two lines modified for Sphinx (tabulary compatibility): - {\ifmeasuring@\expandafter\@gobbletwo\else\expandafter\@firstofone\fi}% - {\ifx\equation$\expandafter\@gobbletwo\fi #1}%$ -}% -\long\def\FNH@hyper@fntext@i#1{% - \global\setbox\FNH@notes\vbox - {\unvbox\FNH@notes - \FNH@startnote - \@makefntext - {\rule\z@\footnotesep\ignorespaces - \ifHy@nesting\expandafter\ltx@firstoftwo - \else\expandafter\ltx@secondoftwo - \fi - {\expandafter\hyper@@anchor\expandafter{\Hy@footnote@currentHref}{#1}}% - {\Hy@raisedlink - {\expandafter\hyper@@anchor\expandafter{\Hy@footnote@currentHref}% - {\relax}}% - \let\@currentHref\Hy@footnote@currentHref - \let\@currentlabelname\@empty - #1}% - \@finalstrut\strutbox - }% - \FNH@endnote - }% -}% -\long\def\FNH@nohyp@fntext@i#1{% - \global\setbox\FNH@notes\vbox - {\unvbox\FNH@notes - \FNH@startnote - \@makefntext{\rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox}% - \FNH@endnote - }% -}% -\def\FNH@startnote{% - \hsize\FNH@colwidth - \interlinepenalty\interfootnotelinepenalty - \reset@font\footnotesize - \floatingpenalty\@MM - \@parboxrestore - \protected@edef\@currentlabel{\csname p@\@mpfn\endcsname\@thefnmark}% - \color@begingroup -}% -\def\FNH@endnote{\color@endgroup}% -\def\FNH@savenotes{% - \begingroup - \ifFNH@savingnotes\else - \FNH@savingnotestrue - \let\@footnotetext \FNH@hyper@fntext - \let\@mpfootnotetext \FNH@hyper@fntext - \let\H@@mpfootnotetext\FNH@nohyp@fntext - \FNH@width\columnwidth - \let\FNH@colwidth\FNH@width - \global\setbox\FNH@notes\box\voidb@x - \let\FNH@thempfn\thempfn - \let\FNH@mpfn\@mpfn - \ifx\@minipagerestore\relax\let\@minipagerestore\@empty\fi - \expandafter\def\expandafter\@minipagerestore\expandafter{% - \@minipagerestore - \let\thempfn\FNH@thempfn - \let\@mpfn\FNH@mpfn - }% - \fi -}% -\def\FNH@spewnotes {% - \endgroup - \ifFNH@savingnotes\else - \ifvoid\FNH@notes\else - \begingroup - \let\@makefntext\@empty - \let\@finalstrut\@gobble - \let\rule\@gobbletwo - \FNH@H@@footnotetext{\unvbox\FNH@notes}% - \endgroup - \fi - \fi -}% -\def\FNH@footnote@envname {footnote}% -\def\FNH@footnotetext@envname{footnotetext}% -\def\FNH@footnote{% -% this line added for Sphinx: - \spx@opt@BeforeFootnote - \ifx\@currenvir\FNH@footnote@envname - \expandafter\FNH@footnoteenv - \else - \expandafter\FNH@latex@footnote - \fi -}% -\def\FNH@footnoteenv{% -% this line added for Sphinx (footnotes in parsed literal blocks): - \catcode13=5 \sphinxunactivateextrasandspace - \@ifnextchar[% - \FNH@footnoteenv@i %] - {\stepcounter\@mpfn - \protected@xdef\@thefnmark{\thempfn}% - \@footnotemark - \def\FNH@endfntext@fntext{\@footnotetext}% - \FNH@startfntext}% -}% -\def\FNH@footnoteenv@i[#1]{% - \begingroup - \csname c@\@mpfn\endcsname #1\relax - \unrestored@protected@xdef\@thefnmark{\thempfn}% - \endgroup - \@footnotemark - \def\FNH@endfntext@fntext{\@footnotetext}% - \FNH@startfntext -}% -\def\FNH@footnotetext{% - \ifx\@currenvir\FNH@footnotetext@envname - \expandafter\FNH@footnotetextenv - \else - \expandafter\FNH@latex@footnotetext - \fi -}% -\def\FNH@footnotetextenv{% - \@ifnextchar[% - \FNH@footnotetextenv@i %] - {\protected@xdef\@thefnmark{\thempfn}% - \def\FNH@endfntext@fntext{\@footnotetext}% - \FNH@startfntext}% -}% -\def\FNH@footnotetextenv@i[#1]{% - \begingroup - \csname c@\@mpfn\endcsname #1\relax - \unrestored@protected@xdef\@thefnmark{\thempfn}% - \endgroup - \ifFNH@savingnotes - \def\FNH@endfntext@fntext{\FNH@nohyp@fntext}% - \else - \def\FNH@endfntext@fntext{\FNH@H@@footnotetext}% - \fi - \FNH@startfntext -}% -\def\FNH@startfntext{% - \setbox\z@\vbox\bgroup - \FNH@startnote - \FNH@prefntext - \rule\z@\footnotesep\ignorespaces -}% -\def\FNH@endfntext {% - \@finalstrut\strutbox - \FNH@postfntext - \FNH@endnote - \egroup - \begingroup - \let\@makefntext\@empty\let\@finalstrut\@gobble\let\rule\@gobbletwo - \FNH@endfntext@fntext {\unvbox\z@}% - \endgroup -}% -\AtBeginDocument{% - \let\FNH@@makefntext\@makefntext - \ifx\@makefntextFB\@undefined - \expandafter\@gobble\else\expandafter\@firstofone\fi - {\ifFBFrenchFootnotes \let\FNH@@makefntext\@makefntextFB \else - \let\FNH@@makefntext\@makefntextORI\fi}% - \expandafter\FNH@check@a\FNH@@makefntext{1.2!3?4,}% - \FNH@@@1.2!3?4,\FNH@@@\relax -}% -\long\def\FNH@check@a #11.2!3?4,#2\FNH@@@#3{% - \ifx\relax#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi - \FNH@bad@makefntext@alert - {\def\FNH@prefntext{#1}\def\FNH@postfntext{#2}\FNH@check@b}% -}% -\def\FNH@check@b #1\relax{% - \expandafter\expandafter\expandafter\FNH@check@c - \expandafter\meaning\expandafter\FNH@prefntext - \meaning\FNH@postfntext1.2!3?4,\FNH@check@c\relax -}% -\def\FNH@check@c #11.2!3?4,#2#3\relax{% - \ifx\FNH@check@c#2\expandafter\@gobble\fi\FNH@bad@makefntext@alert -}% -% slight reformulation for Sphinx -\def\FNH@bad@makefntext@alert{% - \PackageWarningNoLine{footnotehyper-sphinx}% - {Footnotes will be sub-optimal, sorry. This is due to the document class or^^J - some package modifying macro \string\@makefntext.^^J - You can try to report this incompatibility at^^J - https://github.com/sphinx-doc/sphinx with this info:}% - \typeout{\meaning\@makefntext}% - \let\FNH@prefntext\@empty\let\FNH@postfntext\@empty -}% -% this macro from original footnote.sty is not used anymore by Sphinx -% but for simplicity sake let's just keep it as is -\def\makesavenoteenv{\@ifnextchar[\FNH@msne@ii\FNH@msne@i}%] -\def\FNH@msne@i #1{% - \expandafter\let\csname FNH$#1\expandafter\endcsname %$ - \csname #1\endcsname - \expandafter\let\csname endFNH$#1\expandafter\endcsname %$ - \csname end#1\endcsname - \FNH@msne@ii[#1]{FNH$#1}%$ -}% -\def\FNH@msne@ii[#1]#2{% - \expandafter\edef\csname#1\endcsname{% - \noexpand\savenotes - \expandafter\noexpand\csname#2\endcsname - }% - \expandafter\edef\csname end#1\endcsname{% - \expandafter\noexpand\csname end#2\endcsname - \noexpand\expandafter - \noexpand\spewnotes - \noexpand\if@endpe\noexpand\@endpetrue\noexpand\fi - }% -}% -% end of footnotehyper 2017/02/16 v0.99 -% some extras for Sphinx : -% \sphinxfootnotemark: usable in section titles and silently removed from TOCs. -\def\sphinxfootnotemark [#1]% - {\ifx\thepage\relax\else\protect\spx@opt@BeforeFootnote - \protect\footnotemark[#1]\fi}% -\AtBeginDocument{% - % let hyperref less complain - \pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}% - % to obtain hyperlinked footnotes in longtable environment we must replace - % hyperref's patch of longtable's patch of \@footnotetext by our own - \let\LT@p@ftntext\FNH@hyper@fntext - % this *requires* longtable to be used always wrapped in savenotes environment -}% -\endinput -%% -%% End of file `footnotehyper-sphinx.sty'. diff --git a/docs/_build/latex/latexmkjarc b/docs/_build/latex/latexmkjarc deleted file mode 100644 index 39ea47f3b..000000000 --- a/docs/_build/latex/latexmkjarc +++ /dev/null @@ -1,7 +0,0 @@ -$latex = 'platex ' . $ENV{'LATEXOPTS'} . ' -kanji=utf8 %O %S'; -$dvipdf = 'dvipdfmx %O -o %D %S'; -$makeindex = 'rm -f %D; mendex -U -f -d %B.dic -s python.ist %S || echo "mendex exited with error code $? (ignoring)" && : >> %D'; -add_cus_dep( "glo", "gls", 0, "makeglo" ); -sub makeglo { - return system( "mendex -J -f -s gglo.ist -o '$_[0].gls' '$_[0].glo'" ); -} diff --git a/docs/_build/latex/latexmkrc b/docs/_build/latex/latexmkrc deleted file mode 100644 index bba17fa6b..000000000 --- a/docs/_build/latex/latexmkrc +++ /dev/null @@ -1,9 +0,0 @@ -$latex = 'latex ' . $ENV{'LATEXOPTS'} . ' %O %S'; -$pdflatex = 'pdflatex ' . $ENV{'LATEXOPTS'} . ' %O %S'; -$lualatex = 'lualatex ' . $ENV{'LATEXOPTS'} . ' %O %S'; -$xelatex = 'xelatex --no-pdf ' . $ENV{'LATEXOPTS'} . ' %O %S'; -$makeindex = 'makeindex -s python.ist %O -o %D %S'; -add_cus_dep( "glo", "gls", 0, "makeglo" ); -sub makeglo { - return system( "makeindex -s gglo.ist -o '$_[0].gls' '$_[0].glo'" ); -} \ No newline at end of file diff --git a/docs/_build/latex/pyEMU.aux b/docs/_build/latex/pyEMU.aux deleted file mode 100644 index a012f23dc..000000000 --- a/docs/_build/latex/pyEMU.aux +++ /dev/null @@ -1,55 +0,0 @@ -\relax -\providecommand\hyper@newdestlabel[2]{} -\providecommand\HyperFirstAtBeginDocument{\AtBeginDocument} -\HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined -\global\let\oldcontentsline\contentsline -\gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}} -\global\let\oldnewlabel\newlabel -\gdef\newlabel#1#2{\newlabelxx{#1}#2} -\gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}} -\AtEndDocument{\ifx\hyper@anchor\@undefined -\let\contentsline\oldcontentsline -\let\newlabel\oldnewlabel -\fi} -\fi} -\global\let\hyper@last\relax -\gdef\HyperFirstAtBeginDocument#1{#1} -\providecommand\HyField@AuxAddToFields[1]{} -\providecommand\HyField@AuxAddToCoFields[2]{} -\select@language{english} -\@writefile{toc}{\select@language{english}} -\@writefile{lof}{\select@language{english}} -\@writefile{lot}{\select@language{english}} -\newlabel{index::doc}{{}{1}{}{section*.2}{}} -\newlabel{index:id1}{{}{1}{}{section*.3}{}} -\newlabel{index:id2}{{}{1}{}{section*.4}{}} -\newlabel{index:id3}{{}{1}{}{section*.5}{}} -\@writefile{toc}{\contentsline {chapter}{\numberline {1}Contents}{3}{chapter.1}} -\@writefile{lof}{\addvspace {10\p@ }} -\@writefile{lot}{\addvspace {10\p@ }} -\newlabel{index:contents}{{1}{3}{Contents}{chapter.1}{}} -\newlabel{index:notes}{{1}{3}{Contents}{chapter.1}{}} -\@writefile{toc}{\contentsline {section}{\numberline {1.1}Notes on Object-Oriented Programming}{3}{section.1.1}} -\newlabel{source/oop:notes-on-object-oriented-programming}{{1.1}{3}{Notes on Object-Oriented Programming}{section.1.1}{}} -\newlabel{source/oop::doc}{{1.1}{3}{Notes on Object-Oriented Programming}{section.1.1}{}} -\@writefile{toc}{\contentsline {section}{\numberline {1.2}Glossary}{3}{section.1.2}} -\newlabel{source/glossary:glossary}{{1.2}{3}{Glossary}{section.1.2}{}} -\newlabel{source/glossary::doc}{{1.2}{3}{Glossary}{section.1.2}{}} -\newlabel{source/glossary:term-class}{{1.2}{3}{class\index {class|textbf}\phantomsection }{section*.6}{}} -\newlabel{source/glossary:term-object}{{1.2}{3}{object\index {object|textbf}\phantomsection }{section*.7}{}} -\newlabel{source/glossary:term-instance}{{1.2}{3}{instance\index {instance|textbf}\phantomsection }{section*.8}{}} -\@writefile{toc}{\contentsline {chapter}{\numberline {2}Technical Documentation}{5}{chapter.2}} -\@writefile{lof}{\addvspace {10\p@ }} -\@writefile{lot}{\addvspace {10\p@ }} -\newlabel{index:technical-documentation}{{2}{5}{Technical Documentation}{chapter.2}{}} -\@writefile{toc}{\contentsline {chapter}{\numberline {3}References}{7}{chapter.3}} -\@writefile{lof}{\addvspace {10\p@ }} -\@writefile{lot}{\addvspace {10\p@ }} -\newlabel{index:references}{{3}{7}{References}{chapter.3}{}} -\bibcite{DOH15}{DOH15} -\bibcite{WFD16}{WFD16} -\bibcite{WWHD15}{WWHD15} -\@writefile{toc}{\contentsline {chapter}{Bibliography}{9}{chapter*.9}} -\newlabel{index:doh15}{{3}{9}{\bibname }{section*.10}{}} -\newlabel{index:wfd16}{{3}{9}{\bibname }{section*.11}{}} -\newlabel{index:wwhd15}{{3}{9}{\bibname }{section*.12}{}} diff --git a/docs/_build/latex/pyEMU.dvi b/docs/_build/latex/pyEMU.dvi deleted file mode 100644 index 2059376d18e89be6f85b488b401b29b871c2fa8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22120 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 patch level 2 -Babel <3.9m> and hyphenation patterns for 69 languages loaded. -(sphinxmanual.cls -Document Class: sphinxmanual 2017/03/26 v1.6 Document class (Sphinx manual) -("C:\Program Files\MiKTeX 2.9\tex\latex\base\report.cls" -Document Class: report 2014/09/29 v1.4h Standard LaTeX document class -("C:\Program Files\MiKTeX 2.9\tex\latex\base\size10.clo" -File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) -) -\c@part=\count79 -\c@chapter=\count80 -\c@section=\count81 -\c@subsection=\count82 -\c@subsubsection=\count83 -\c@paragraph=\count84 -\c@subparagraph=\count85 -\c@figure=\count86 -\c@table=\count87 -\abovecaptionskip=\skip41 -\belowcaptionskip=\skip42 -\bibindent=\dimen102 -)) -("C:\Program Files\MiKTeX 2.9\tex\latex\base\inputenc.sty" -Package: inputenc 2015/03/17 v1.2c Input encoding file -\inpenc@prehook=\toks14 -\inpenc@posthook=\toks15 - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\utf8.def" -File: utf8.def 2015/06/27 v1.1n UTF-8 support for inputenc -Now handling font encoding OML ... -... no UTF-8 mapping file for font encoding OML -Now handling font encoding T1 ... -... processing UTF-8 mapping file for font encoding T1 - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\t1enc.dfu" -File: t1enc.dfu 2015/06/27 v1.1n UTF-8 support for inputenc - defining Unicode char U+00A1 (decimal 161) - defining Unicode char U+00A3 (decimal 163) - defining Unicode char U+00AB (decimal 171) - defining Unicode char U+00BB (decimal 187) - defining Unicode char U+00BF (decimal 191) - defining Unicode char U+00C0 (decimal 192) - defining Unicode char U+00C1 (decimal 193) - defining Unicode char U+00C2 (decimal 194) - defining Unicode char U+00C3 (decimal 195) - defining Unicode char U+00C4 (decimal 196) - defining Unicode char U+00C5 (decimal 197) - defining Unicode char U+00C6 (decimal 198) - defining Unicode char U+00C7 (decimal 199) - defining Unicode char U+00C8 (decimal 200) - defining Unicode char U+00C9 (decimal 201) - defining Unicode char U+00CA (decimal 202) - defining Unicode char U+00CB (decimal 203) - defining Unicode char U+00CC (decimal 204) - defining Unicode char U+00CD (decimal 205) - defining Unicode char U+00CE (decimal 206) - defining Unicode char U+00CF (decimal 207) - defining Unicode char U+00D0 (decimal 208) - defining Unicode char U+00D1 (decimal 209) - defining Unicode char U+00D2 (decimal 210) - defining Unicode char U+00D3 (decimal 211) - defining Unicode char U+00D4 (decimal 212) - defining Unicode char U+00D5 (decimal 213) - defining Unicode char U+00D6 (decimal 214) - defining Unicode char U+00D8 (decimal 216) - defining Unicode char U+00D9 (decimal 217) - defining Unicode char U+00DA (decimal 218) - defining Unicode char U+00DB (decimal 219) - defining Unicode char U+00DC (decimal 220) - defining Unicode char U+00DD (decimal 221) - defining Unicode char U+00DE (decimal 222) - defining Unicode char U+00DF (decimal 223) - defining Unicode char U+00E0 (decimal 224) - defining Unicode char U+00E1 (decimal 225) - defining Unicode char U+00E2 (decimal 226) - defining Unicode char U+00E3 (decimal 227) - defining Unicode char U+00E4 (decimal 228) - defining Unicode char U+00E5 (decimal 229) - defining Unicode char U+00E6 (decimal 230) - defining Unicode char U+00E7 (decimal 231) - defining Unicode char U+00E8 (decimal 232) - defining Unicode char U+00E9 (decimal 233) - defining Unicode char U+00EA (decimal 234) - defining Unicode char U+00EB (decimal 235) - defining Unicode char U+00EC (decimal 236) - defining Unicode char U+00ED (decimal 237) - defining Unicode char U+00EE (decimal 238) - defining Unicode char U+00EF (decimal 239) - defining Unicode char U+00F0 (decimal 240) - defining Unicode char U+00F1 (decimal 241) - defining Unicode char U+00F2 (decimal 242) - defining Unicode char U+00F3 (decimal 243) - defining Unicode char U+00F4 (decimal 244) - defining Unicode char U+00F5 (decimal 245) - defining Unicode char U+00F6 (decimal 246) - defining Unicode char U+00F8 (decimal 248) - defining Unicode char U+00F9 (decimal 249) - defining Unicode char U+00FA (decimal 250) - defining Unicode char U+00FB (decimal 251) - defining Unicode char U+00FC (decimal 252) - defining Unicode char U+00FD (decimal 253) - defining Unicode char U+00FE (decimal 254) - defining Unicode char U+00FF (decimal 255) - defining Unicode char U+0102 (decimal 258) - defining Unicode char U+0103 (decimal 259) - defining Unicode char U+0104 (decimal 260) - defining Unicode char U+0105 (decimal 261) - defining Unicode char U+0106 (decimal 262) - defining Unicode char U+0107 (decimal 263) - defining Unicode char U+010C (decimal 268) - defining Unicode char U+010D (decimal 269) - defining Unicode char U+010E (decimal 270) - defining Unicode char U+010F (decimal 271) - defining Unicode char U+0110 (decimal 272) - defining Unicode char U+0111 (decimal 273) - defining Unicode char U+0118 (decimal 280) - defining Unicode char U+0119 (decimal 281) - defining Unicode char U+011A (decimal 282) - defining Unicode char U+011B (decimal 283) - defining Unicode char U+011E (decimal 286) - defining Unicode char U+011F (decimal 287) - defining Unicode char U+0130 (decimal 304) - defining Unicode char U+0131 (decimal 305) - defining Unicode char U+0132 (decimal 306) - defining Unicode char U+0133 (decimal 307) - defining Unicode char U+0139 (decimal 313) - defining Unicode char U+013A (decimal 314) - defining Unicode char U+013D (decimal 317) - defining Unicode char U+013E (decimal 318) - defining Unicode char U+0141 (decimal 321) - defining Unicode char U+0142 (decimal 322) - defining Unicode char U+0143 (decimal 323) - defining Unicode char U+0144 (decimal 324) - defining Unicode char U+0147 (decimal 327) - defining Unicode char U+0148 (decimal 328) - defining Unicode char U+014A (decimal 330) - defining Unicode char U+014B (decimal 331) - defining Unicode char U+0150 (decimal 336) - defining Unicode char U+0151 (decimal 337) - defining Unicode char U+0152 (decimal 338) - defining Unicode char U+0153 (decimal 339) - defining Unicode char U+0154 (decimal 340) - defining Unicode char U+0155 (decimal 341) - defining Unicode char U+0158 (decimal 344) - defining Unicode char U+0159 (decimal 345) - defining Unicode char U+015A (decimal 346) - defining Unicode char U+015B (decimal 347) - defining Unicode char U+015E (decimal 350) - defining Unicode char U+015F (decimal 351) - defining Unicode char U+0160 (decimal 352) - defining Unicode char U+0161 (decimal 353) - defining Unicode char U+0162 (decimal 354) - defining Unicode char U+0163 (decimal 355) - defining Unicode char U+0164 (decimal 356) - defining Unicode char U+0165 (decimal 357) - defining Unicode char U+016E (decimal 366) - defining Unicode char U+016F (decimal 367) - defining Unicode char U+0170 (decimal 368) - defining Unicode char U+0171 (decimal 369) - defining Unicode char U+0178 (decimal 376) - defining Unicode char U+0179 (decimal 377) - defining Unicode char U+017A (decimal 378) - defining Unicode char U+017B (decimal 379) - defining Unicode char U+017C (decimal 380) - defining Unicode char U+017D (decimal 381) - defining Unicode char U+017E (decimal 382) - defining Unicode char U+200C (decimal 8204) - defining Unicode char U+2013 (decimal 8211) - defining Unicode char U+2014 (decimal 8212) - defining Unicode char U+2018 (decimal 8216) - defining Unicode char U+2019 (decimal 8217) - defining Unicode char U+201A (decimal 8218) - defining Unicode char U+201C (decimal 8220) - defining Unicode char U+201D (decimal 8221) - defining Unicode char U+201E (decimal 8222) - defining Unicode char U+2030 (decimal 8240) - defining Unicode char U+2031 (decimal 8241) - defining Unicode char U+2039 (decimal 8249) - defining Unicode char U+203A (decimal 8250) - defining Unicode char U+2423 (decimal 9251) -) -Now handling font encoding OT1 ... -... processing UTF-8 mapping file for font encoding OT1 - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\ot1enc.dfu" -File: ot1enc.dfu 2015/06/27 v1.1n UTF-8 support for inputenc - defining Unicode char U+00A1 (decimal 161) - defining Unicode char U+00A3 (decimal 163) - defining Unicode char U+00B8 (decimal 184) - defining Unicode char U+00BF (decimal 191) - defining Unicode char U+00C5 (decimal 197) - defining Unicode char U+00C6 (decimal 198) - defining Unicode char U+00D8 (decimal 216) - defining Unicode char U+00DF (decimal 223) - defining Unicode char U+00E6 (decimal 230) - defining Unicode char U+00EC (decimal 236) - defining Unicode char U+00ED (decimal 237) - defining Unicode char U+00EE (decimal 238) - defining Unicode char U+00EF (decimal 239) - defining Unicode char U+00F8 (decimal 248) - defining Unicode char U+0131 (decimal 305) - defining Unicode char U+0141 (decimal 321) - defining Unicode char U+0142 (decimal 322) - defining Unicode char U+0152 (decimal 338) - defining Unicode char U+0153 (decimal 339) - defining Unicode char U+2013 (decimal 8211) - defining Unicode char U+2014 (decimal 8212) - defining Unicode char U+2018 (decimal 8216) - defining Unicode char U+2019 (decimal 8217) - defining Unicode char U+201C (decimal 8220) - defining Unicode char U+201D (decimal 8221) -) -Now handling font encoding OMS ... -... processing UTF-8 mapping file for font encoding OMS - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\omsenc.dfu" -File: omsenc.dfu 2015/06/27 v1.1n UTF-8 support for inputenc - defining Unicode char U+00A7 (decimal 167) - defining Unicode char U+00B6 (decimal 182) - defining Unicode char U+00B7 (decimal 183) - defining Unicode char U+2020 (decimal 8224) - defining Unicode char U+2021 (decimal 8225) - defining Unicode char U+2022 (decimal 8226) -) -Now handling font encoding OMX ... -... no UTF-8 mapping file for font encoding OMX -Now handling font encoding U ... -... no UTF-8 mapping file for font encoding U - defining Unicode char U+00A9 (decimal 169) - defining Unicode char U+00AA (decimal 170) - defining Unicode char U+00AE (decimal 174) - defining Unicode char U+00BA (decimal 186) - defining Unicode char U+02C6 (decimal 710) - defining Unicode char U+02DC (decimal 732) - defining Unicode char U+200C (decimal 8204) - defining Unicode char U+2026 (decimal 8230) - defining Unicode char U+2122 (decimal 8482) - defining Unicode char U+2423 (decimal 9251) -)) - defining Unicode char U+00A0 (decimal 160) - defining Unicode char U+2500 (decimal 9472) - defining Unicode char U+2502 (decimal 9474) - defining Unicode char U+2514 (decimal 9492) - defining Unicode char U+251C (decimal 9500) - defining Unicode char U+2572 (decimal 9586) - -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\cmap\cmap.sty -Package: cmap 2008/03/06 v1.0h CMap support: searchable PDF -) -("C:\Program Files\MiKTeX 2.9\tex\latex\base\fontenc.sty" -Package: fontenc 2005/09/27 v1.99g Standard LaTeX package - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\t1enc.def" -File: t1enc.def 2005/09/27 v1.99g Standard LaTeX file -LaTeX Font Info: Redeclaring font encoding T1 on input line 48. -)<>) -("C:\Program Files\MiKTeX 2.9\tex\latex\amsmath\amsmath.sty" -Package: amsmath 2013/01/14 v2.14 AMS math features -\@mathmargin=\skip43 - -For additional information on amsmath, use the `?' option. -("C:\Program Files\MiKTeX 2.9\tex\latex\amsmath\amstext.sty" -Package: amstext 2000/06/29 v2.01 - -("C:\Program Files\MiKTeX 2.9\tex\latex\amsmath\amsgen.sty" -File: amsgen.sty 1999/11/30 v2.0 -\@emptytoks=\toks16 -\ex@=\dimen103 -)) -("C:\Program Files\MiKTeX 2.9\tex\latex\amsmath\amsbsy.sty" -Package: amsbsy 1999/11/29 v1.2d -\pmbraise@=\dimen104 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\amsmath\amsopn.sty" -Package: amsopn 1999/12/14 v2.01 operator names -) -\inf@bad=\count88 -LaTeX Info: Redefining \frac on input line 210. -\uproot@=\count89 -\leftroot@=\count90 -LaTeX Info: Redefining \overline on input line 306. -\classnum@=\count91 -\DOTSCASE@=\count92 -LaTeX Info: Redefining \ldots on input line 378. -LaTeX Info: Redefining \dots on input line 381. -LaTeX Info: Redefining \cdots on input line 466. -\Mathstrutbox@=\box26 -\strutbox@=\box27 -\big@size=\dimen105 -LaTeX Font Info: Redeclaring font encoding OML on input line 566. -LaTeX Font Info: Redeclaring font encoding OMS on input line 567. -\macc@depth=\count93 -\c@MaxMatrixCols=\count94 -\dotsspace@=\muskip10 -\c@parentequation=\count95 -\dspbrk@lvl=\count96 -\tag@help=\toks17 -\row@=\count97 -\column@=\count98 -\maxfields@=\count99 -\andhelp@=\toks18 -\eqnshift@=\dimen106 -\alignsep@=\dimen107 -\tagshift@=\dimen108 -\tagwidth@=\dimen109 -\totwidth@=\dimen110 -\lineht@=\dimen111 -\@envbody=\toks19 -\multlinegap=\skip44 -\multlinetaggap=\skip45 -\mathdisplay@stack=\toks20 -LaTeX Info: Redefining \[ on input line 2665. -LaTeX Info: Redefining \] on input line 2666. -) -("C:\Program Files\MiKTeX 2.9\tex\latex\amsfonts\amssymb.sty" -Package: amssymb 2013/01/14 v3.01 AMS font symbols - -("C:\Program Files\MiKTeX 2.9\tex\latex\amsfonts\amsfonts.sty" -Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support -\symAMSa=\mathgroup4 -\symAMSb=\mathgroup5 -LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' -(Font) U/euf/m/n --> U/euf/b/n on input line 106. -)) -("C:\Program Files\MiKTeX 2.9\tex\generic\babel\babel.sty" -Package: babel 2015/08/03 3.9m The Babel package - -************************************* -* Local config file bblopts.cfg used -* -("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\bblopts.cfg" -File: bblopts.cfg 2006/07/31 v1.0 MiKTeX 'babel' configuration -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\babel-english\english.l -df -Language: english 2012/08/20 v3.3p English support from the babel system - ("C:\Program Files\MiKTeX 2.9\tex\generic\babel\babel.def" -File: babel.def 2015/08/03 3.9m Babel common definitions -\babel@savecnt=\count100 -\U@D=\dimen112 -) -\l@canadian = a dialect from \language\l@american -\l@australian = a dialect from \language\l@british -\l@newzealand = a dialect from \language\l@british -)) -("C:\Program Files\MiKTeX 2.9\tex\latex\psnfss\times.sty" -Package: times 2005/04/12 PSNFSS-v9.2a (SPQR) -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\fncychap\fncychap.sty -Package: fncychap 2007/07/30 v1.34 LaTeX package (Revised chapters) -\RW=\skip46 -\mylen=\skip47 -\myhi=\skip48 -\px=\skip49 -\py=\skip50 -\pyy=\skip51 -\pxx=\skip52 -\c@AlphaCnt=\count101 -\c@AlphaDecCnt=\count102 -) -(sphinx.sty -Package: sphinx 2018/03/11 v1.7.2 LaTeX package (Sphinx markup) - ("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\ltxcmds.sty" -Package: ltxcmds 2011/11/09 v1.22 LaTeX kernel commands for general use (HO) -) -("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphicx.sty" -Package: graphicx 2014/10/28 v1.0g Enhanced LaTeX Graphics (DPC,SPQR) - -("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\keyval.sty" -Package: keyval 2014/10/28 v1.15 key=value parser (DPC) -\KV@toks@=\toks21 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphics.sty" -Package: graphics 2014/10/28 v1.0p Standard LaTeX Graphics (DPC,SPQR) - -("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\trig.sty" -Package: trig 1999/03/16 v1.09 sin cos tan (DPC) -) -("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\graphics.cfg" -File: graphics.cfg 2007/01/18 v1.5 graphics configuration of teTeX/TeXLive -) -Package graphics Info: Driver file: pdftex.def on input line 94. - -("C:\Program Files\MiKTeX 2.9\tex\latex\pdftex-def\pdftex.def" -File: pdftex.def 2011/05/27 v0.06d Graphics/color for pdfTeX - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\infwarerr.sty" -Package: infwarerr 2010/04/08 v1.3 Providing info/warning/error messages (HO) -) -\Gread@gobject=\count103 -)) -\Gin@req@height=\dimen113 -\Gin@req@width=\dimen114 -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\fancyhdr\fancyhdr.sty -Package: fancyhdr 2017/06/30 v3.9a Extensive control of page headers and footer -s -\f@nch@headwidth=\skip53 -\f@nch@O@elh=\skip54 -\f@nch@O@erh=\skip55 -\f@nch@O@olh=\skip56 -\f@nch@O@orh=\skip57 -\f@nch@O@elf=\skip58 -\f@nch@O@erf=\skip59 -\f@nch@O@olf=\skip60 -\f@nch@O@orf=\skip61 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\base\textcomp.sty" -Package: textcomp 2005/09/27 v1.99g Standard LaTeX package -Package textcomp Info: Sub-encoding information: -(textcomp) 5 = only ISO-Adobe without \textcurrency -(textcomp) 4 = 5 + \texteuro -(textcomp) 3 = 4 + \textohm -(textcomp) 2 = 3 + \textestimated + \textcurrency -(textcomp) 1 = TS1 - \textcircled - \t -(textcomp) 0 = TS1 (full) -(textcomp) Font families with sub-encoding setting implement -(textcomp) only a restricted character set as indicated. -(textcomp) Family '?' is the default used for unknown fonts. -(textcomp) See the documentation for details. -Package textcomp Info: Setting ? sub-encoding to TS1/1 on input line 79. - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\ts1enc.def" -File: ts1enc.def 2001/06/05 v3.0e (jk/car/fm) Standard LaTeX file -Now handling font encoding TS1 ... -... processing UTF-8 mapping file for font encoding TS1 - -("C:\Program Files\MiKTeX 2.9\tex\latex\base\ts1enc.dfu" -File: ts1enc.dfu 2015/06/27 v1.1n UTF-8 support for inputenc - defining Unicode char U+00A2 (decimal 162) - defining Unicode char U+00A3 (decimal 163) - defining Unicode char U+00A4 (decimal 164) - defining Unicode char U+00A5 (decimal 165) - defining Unicode char U+00A6 (decimal 166) - defining Unicode char U+00A7 (decimal 167) - defining Unicode char U+00A8 (decimal 168) - defining Unicode char U+00A9 (decimal 169) - defining Unicode char U+00AA (decimal 170) - defining Unicode char U+00AC (decimal 172) - defining Unicode char U+00AE (decimal 174) - defining Unicode char U+00AF (decimal 175) - defining Unicode char U+00B0 (decimal 176) - defining Unicode char U+00B1 (decimal 177) - defining Unicode char U+00B2 (decimal 178) - defining Unicode char U+00B3 (decimal 179) - defining Unicode char U+00B4 (decimal 180) - defining Unicode char U+00B5 (decimal 181) - defining Unicode char U+00B6 (decimal 182) - defining Unicode char U+00B7 (decimal 183) - defining Unicode char U+00B9 (decimal 185) - defining Unicode char U+00BA (decimal 186) - defining Unicode char U+00BC (decimal 188) - defining Unicode char U+00BD (decimal 189) - defining Unicode char U+00BE (decimal 190) - defining Unicode char U+00D7 (decimal 215) - defining Unicode char U+00F7 (decimal 247) - defining Unicode char U+0192 (decimal 402) - defining Unicode char U+02C7 (decimal 711) - defining Unicode char U+02D8 (decimal 728) - defining Unicode char U+02DD (decimal 733) - defining Unicode char U+0E3F (decimal 3647) - defining Unicode char U+2016 (decimal 8214) - defining Unicode char U+2020 (decimal 8224) - defining Unicode char U+2021 (decimal 8225) - defining Unicode char U+2022 (decimal 8226) - defining Unicode char U+2030 (decimal 8240) - defining Unicode char U+2031 (decimal 8241) - defining Unicode char U+203B (decimal 8251) - defining Unicode char U+203D (decimal 8253) - defining Unicode char U+2044 (decimal 8260) - defining Unicode char U+204E (decimal 8270) - defining Unicode char U+2052 (decimal 8274) - defining Unicode char U+20A1 (decimal 8353) - defining Unicode char U+20A4 (decimal 8356) - defining Unicode char U+20A6 (decimal 8358) - defining Unicode char U+20A9 (decimal 8361) - defining Unicode char U+20AB (decimal 8363) - defining Unicode char U+20AC (decimal 8364) - defining Unicode char U+20B1 (decimal 8369) - defining Unicode char U+2103 (decimal 8451) - defining Unicode char U+2116 (decimal 8470) - defining Unicode char U+2117 (decimal 8471) - defining Unicode char U+211E (decimal 8478) - defining Unicode char U+2120 (decimal 8480) - defining Unicode char U+2122 (decimal 8482) - defining Unicode char U+2126 (decimal 8486) - defining Unicode char U+2127 (decimal 8487) - defining Unicode char U+212E (decimal 8494) - defining Unicode char U+2190 (decimal 8592) - defining Unicode char U+2191 (decimal 8593) - defining Unicode char U+2192 (decimal 8594) - defining Unicode char U+2193 (decimal 8595) - defining Unicode char U+2329 (decimal 9001) - defining Unicode char U+232A (decimal 9002) - defining Unicode char U+2422 (decimal 9250) - defining Unicode char U+25E6 (decimal 9702) - defining Unicode char U+25EF (decimal 9711) - defining Unicode char U+266A (decimal 9834) -)) -LaTeX Info: Redefining \oldstylenums on input line 334. -Package textcomp Info: Setting cmr sub-encoding to TS1/0 on input line 349. -Package textcomp Info: Setting cmss sub-encoding to TS1/0 on input line 350. -Package textcomp Info: Setting cmtt sub-encoding to TS1/0 on input line 351. -Package textcomp Info: Setting cmvtt sub-encoding to TS1/0 on input line 352. -Package textcomp Info: Setting cmbr sub-encoding to TS1/0 on input line 353. -Package textcomp Info: Setting cmtl sub-encoding to TS1/0 on input line 354. -Package textcomp Info: Setting ccr sub-encoding to TS1/0 on input line 355. -Package textcomp Info: Setting ptm sub-encoding to TS1/4 on input line 356. -Package textcomp Info: Setting pcr sub-encoding to TS1/4 on input line 357. -Package textcomp Info: Setting phv sub-encoding to TS1/4 on input line 358. -Package textcomp Info: Setting ppl sub-encoding to TS1/3 on input line 359. -Package textcomp Info: Setting pag sub-encoding to TS1/4 on input line 360. -Package textcomp Info: Setting pbk sub-encoding to TS1/4 on input line 361. -Package textcomp Info: Setting pnc sub-encoding to TS1/4 on input line 362. -Package textcomp Info: Setting pzc sub-encoding to TS1/4 on input line 363. -Package textcomp Info: Setting bch sub-encoding to TS1/4 on input line 364. -Package textcomp Info: Setting put sub-encoding to TS1/5 on input line 365. -Package textcomp Info: Setting uag sub-encoding to TS1/5 on input line 366. -Package textcomp Info: Setting ugq sub-encoding to TS1/5 on input line 367. -Package textcomp Info: Setting ul8 sub-encoding to TS1/4 on input line 368. -Package textcomp Info: Setting ul9 sub-encoding to TS1/4 on input line 369. -Package textcomp Info: Setting augie sub-encoding to TS1/5 on input line 370. -Package textcomp Info: Setting dayrom sub-encoding to TS1/3 on input line 371. -Package textcomp Info: Setting dayroms sub-encoding to TS1/3 on input line 372. - -Package textcomp Info: Setting pxr sub-encoding to TS1/0 on input line 373. -Package textcomp Info: Setting pxss sub-encoding to TS1/0 on input line 374. -Package textcomp Info: Setting pxtt sub-encoding to TS1/0 on input line 375. -Package textcomp Info: Setting txr sub-encoding to TS1/0 on input line 376. -Package textcomp Info: Setting txss sub-encoding to TS1/0 on input line 377. -Package textcomp Info: Setting txtt sub-encoding to TS1/0 on input line 378. -Package textcomp Info: Setting lmr sub-encoding to TS1/0 on input line 379. -Package textcomp Info: Setting lmdh sub-encoding to TS1/0 on input line 380. -Package textcomp Info: Setting lmss sub-encoding to TS1/0 on input line 381. -Package textcomp Info: Setting lmssq sub-encoding to TS1/0 on input line 382. -Package textcomp Info: Setting lmvtt sub-encoding to TS1/0 on input line 383. -Package textcomp Info: Setting lmtt sub-encoding to TS1/0 on input line 384. -Package textcomp Info: Setting qhv sub-encoding to TS1/0 on input line 385. -Package textcomp Info: Setting qag sub-encoding to TS1/0 on input line 386. -Package textcomp Info: Setting qbk sub-encoding to TS1/0 on input line 387. -Package textcomp Info: Setting qcr sub-encoding to TS1/0 on input line 388. -Package textcomp Info: Setting qcs sub-encoding to TS1/0 on input line 389. -Package textcomp Info: Setting qpl sub-encoding to TS1/0 on input line 390. -Package textcomp Info: Setting qtm sub-encoding to TS1/0 on input line 391. -Package textcomp Info: Setting qzc sub-encoding to TS1/0 on input line 392. -Package textcomp Info: Setting qhvc sub-encoding to TS1/0 on input line 393. -Package textcomp Info: Setting futs sub-encoding to TS1/4 on input line 394. -Package textcomp Info: Setting futx sub-encoding to TS1/4 on input line 395. -Package textcomp Info: Setting futj sub-encoding to TS1/4 on input line 396. -Package textcomp Info: Setting hlh sub-encoding to TS1/3 on input line 397. -Package textcomp Info: Setting hls sub-encoding to TS1/3 on input line 398. -Package textcomp Info: Setting hlst sub-encoding to TS1/3 on input line 399. -Package textcomp Info: Setting hlct sub-encoding to TS1/5 on input line 400. -Package textcomp Info: Setting hlx sub-encoding to TS1/5 on input line 401. -Package textcomp Info: Setting hlce sub-encoding to TS1/5 on input line 402. -Package textcomp Info: Setting hlcn sub-encoding to TS1/5 on input line 403. -Package textcomp Info: Setting hlcw sub-encoding to TS1/5 on input line 404. -Package textcomp Info: Setting hlcf sub-encoding to TS1/5 on input line 405. -Package textcomp Info: Setting pplx sub-encoding to TS1/3 on input line 406. -Package textcomp Info: Setting pplj sub-encoding to TS1/3 on input line 407. -Package textcomp Info: Setting ptmx sub-encoding to TS1/4 on input line 408. -Package textcomp Info: Setting ptmj sub-encoding to TS1/4 on input line 409. -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\titlesec\titlesec.sty -Package: titlesec 2016/03/21 v2.10.2 Sectioning titles -\ttl@box=\box28 -\beforetitleunit=\skip62 -\aftertitleunit=\skip63 -\ttl@plus=\dimen115 -\ttl@minus=\dimen116 -\ttl@toksa=\toks22 -\titlewidth=\dimen117 -\titlewidthlast=\dimen118 -\titlewidthfirst=\dimen119 -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\tabulary\tabulary.sty -Package: tabulary 2014/06/11 v0.10 tabulary package (DPC) - -("C:\Program Files\MiKTeX 2.9\tex\latex\tools\array.sty" -Package: array 2014/10/28 v2.4c Tabular extension package (FMi) -\col@sep=\dimen120 -\extrarowheight=\dimen121 -\NC@list=\toks23 -\extratabsurround=\skip64 -\backup@length=\skip65 -) -\TY@count=\count104 -\TY@linewidth=\dimen122 -\tymin=\dimen123 -\tymax=\dimen124 -\TY@tablewidth=\dimen125 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\tools\longtable.sty" -Package: longtable 2014/10/28 v4.11 Multi-page Table package (DPC) -\LTleft=\skip66 -\LTright=\skip67 -\LTpre=\skip68 -\LTpost=\skip69 -\LTchunksize=\count105 -\LTcapwidth=\dimen126 -\LT@head=\box29 -\LT@firsthead=\box30 -\LT@foot=\box31 -\LT@lastfoot=\box32 -\LT@cols=\count106 -\LT@rows=\count107 -\c@LT@tables=\count108 -\c@LT@chunks=\count109 -\LT@p@ftn=\toks24 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\ltxmisc\varwidth.sty" -Package: varwidth 2009/03/30 ver 0.92; Variable-width minipages -\@vwid@box=\box33 -\sift@deathcycles=\count110 -\@vwid@loff=\dimen127 -\@vwid@roff=\dimen128 -) -(sphinxmulticell.sty -Package: sphinxmulticell 2017/02/23 v1.6 better span rows and columns of a tabl -e (Sphinx team) -\sphinx@TY@tablewidth=\dimen129 -) ("C:\Program Files\MiKTeX 2.9\tex\latex\base\makeidx.sty" -Package: makeidx 2014/09/29 v1.0m Standard LaTeX package -) (C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\framed\framed.sty -Package: framed 2011/10/22 v 0.96: framed or shaded text with page breaks -\OuterFrameSep=\skip70 -\fb@frw=\dimen130 -\fb@frh=\dimen131 -\FrameRule=\dimen132 -\FrameSep=\dimen133 -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\xcolor\xcolor.sty -Package: xcolor 2016/05/11 v2.12 LaTeX color extensions (UK) - -("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\color.cfg" -File: color.cfg 2007/01/18 v1.5 color configuration of teTeX/TeXLive -) -Package xcolor Info: Driver file: pdftex.def on input line 225. -Package xcolor Info: Model `cmy' substituted by `cmy0' on input line 1348. -Package xcolor Info: Model `hsb' substituted by `rgb' on input line 1352. -Package xcolor Info: Model `RGB' extended on input line 1364. -Package xcolor Info: Model `HTML' substituted by `rgb' on input line 1366. -Package xcolor Info: Model `Hsb' substituted by `hsb' on input line 1367. -Package xcolor Info: Model `tHsb' substituted by `hsb' on input line 1368. -Package xcolor Info: Model `HSB' substituted by `hsb' on input line 1369. -Package xcolor Info: Model `Gray' substituted by `gray' on input line 1370. -Package xcolor Info: Model `wave' substituted by `hsb' on input line 1371. -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\fancyvrb\fancyvrb.sty -Package: fancyvrb 2008/02/07 - -Style option: `fancyvrb' v2.7a, with DG/SPQR fixes, and firstline=lastline fix -<2008/02/07> (tvz) -\FV@CodeLineNo=\count111 -\FV@InFile=\read1 -\FV@TabBox=\box34 -\c@FancyVerbLine=\count112 -\FV@StepNumber=\count113 -\FV@OutFile=\write3 -) (footnotehyper-sphinx.sty -Package: footnotehyper-sphinx 2017/10/27 v1.7 hyperref aware footnote.sty for s -phinx (JFB) -\FNH@notes=\box35 -\FNH@width=\dimen134 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\float\float.sty" -Package: float 2001/11/08 v1.3d Float enhancements (AL) -\c@float@type=\count114 -\float@exts=\toks25 -\float@box=\box36 -\@float@everytoks=\toks26 -\@floatcapt=\box37 -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\wrapfig\wrapfig.sty -\wrapoverhang=\dimen135 -\WF@size=\dimen136 -\c@WF@wrappedlines=\count115 -\WF@box=\box38 -\WF@everypar=\toks27 -Package: wrapfig 2003/01/31 v 3.6 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\ltxmisc\parskip.sty" -Package: parskip 2001/04/09 non-zero parskip adjustments -) -("C:\Program Files\MiKTeX 2.9\tex\latex\base\alltt.sty" -Package: alltt 1997/06/16 v2.0g defines alltt environment -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\upquote\upquote.sty -Package: upquote 2012/04/19 v1.3 upright-quote and grave-accent glyphs in verba -tim -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\capt-of\capt-of.sty -Package: capt-of 2009/12/29 v0.2 standard captions outside of floats -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\needspace\needspace.sty -Package: needspace 2010/09/12 v1.3d reserve vertical space -) ("C:\Program Files\MiKTeX 2.9\tex\latex\carlisle\remreset.sty") -(sphinxhighlight.sty -Package: sphinxhighlight 2016/05/29 stylesheet for highlighting with pygments -) -("C:\Program Files\MiKTeX 2.9\tex\latex\oberdiek\kvoptions.sty" -Package: kvoptions 2011/06/30 v3.11 Key value format for package options (HO) - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\kvsetkeys.sty" -Package: kvsetkeys 2012/04/25 v1.16 Key value parser (HO) - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\etexcmds.sty" -Package: etexcmds 2011/02/16 v1.5 Avoid name clashes with e-TeX commands (HO) - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\ifluatex.sty" -Package: ifluatex 2010/03/01 v1.3 Provides the ifluatex switch (HO) -Package ifluatex Info: LuaTeX not detected. -) -Package etexcmds Info: Could not find \expanded. -(etexcmds) That can mean that you are not using pdfTeX 1.50 or -(etexcmds) that some package has redefined \expanded. -(etexcmds) In the latter case, load this package earlier. -))) -\sphinxverbatimsep=\dimen137 -\sphinxverbatimborder=\dimen138 -\sphinxshadowsep=\dimen139 -\sphinxshadowsize=\dimen140 -\sphinxshadowrule=\dimen141 -\spx@notice@border=\dimen142 -\spx@image@box=\box39 -\c@literalblock=\count116 -\sphinxcontinuationbox=\box40 -\sphinxvisiblespacebox=\box41 -\sphinxVerbatim@TitleBox=\box42 -\py@argswidth=\skip71 -\lineblockindentation=\skip72 -\DUlineblockindent=\skip73 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\geometry\geometry.sty" -Package: geometry 2010/09/12 v5.6 Page Geometry - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\ifpdf.sty" -Package: ifpdf 2011/01/30 v2.3 Provides the ifpdf switch (HO) -Package ifpdf Info: pdfTeX in PDF mode is detected. -) -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\ifvtex.sty" -Package: ifvtex 2010/03/01 v1.5 Detect VTeX and its facilities (HO) -Package ifvtex Info: VTeX not detected. -) -("C:\Program Files\MiKTeX 2.9\tex\generic\ifxetex\ifxetex.sty" -Package: ifxetex 2010/09/12 v0.6 Provides ifxetex conditional -) -\Gm@cnth=\count117 -\Gm@cntv=\count118 -\c@Gm@tempcnt=\count119 -\Gm@bindingoffset=\dimen143 -\Gm@wd@mp=\dimen144 -\Gm@odd@mp=\dimen145 -\Gm@even@mp=\dimen146 -\Gm@layoutwidth=\dimen147 -\Gm@layoutheight=\dimen148 -\Gm@layouthoffset=\dimen149 -\Gm@layoutvoffset=\dimen150 -\Gm@dimlist=\toks28 - -("C:\Program Files\MiKTeX 2.9\tex\latex\geometry\geometry.cfg")) -("C:\Program Files\MiKTeX 2.9\tex\latex\hyperref\hyperref.sty" -Package: hyperref 2012/11/06 v6.83m Hypertext links for LaTeX - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\hobsub-hyperref.sty" -Package: hobsub-hyperref 2012/04/25 v1.12 Bundle oberdiek, subset hyperref (HO) - - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\hobsub-generic.sty" -Package: hobsub-generic 2012/04/25 v1.12 Bundle oberdiek, subset generic (HO) -Package: hobsub 2012/04/25 v1.12 Construct package bundles (HO) -Package hobsub Info: Skipping package `infwarerr' (already loaded). -Package hobsub Info: Skipping package `ltxcmds' (already loaded). -Package hobsub Info: Skipping package `ifluatex' (already loaded). -Package hobsub Info: Skipping package `ifvtex' (already loaded). -Package: intcalc 2007/09/27 v1.1 Expandable calculations with integers (HO) -Package hobsub Info: Skipping package `ifpdf' (already loaded). -Package hobsub Info: Skipping package `etexcmds' (already loaded). -Package hobsub Info: Skipping package `kvsetkeys' (already loaded). -Package: kvdefinekeys 2011/04/07 v1.3 Define keys (HO) -Package: pdftexcmds 2011/11/29 v0.20 Utility functions of pdfTeX for LuaTeX (HO -) -Package pdftexcmds Info: LuaTeX not detected. -Package pdftexcmds Info: \pdf@primitive is available. -Package pdftexcmds Info: \pdf@ifprimitive is available. -Package pdftexcmds Info: \pdfdraftmode found. -Package: pdfescape 2011/11/25 v1.13 Implements pdfTeX's escape features (HO) -Package: bigintcalc 2012/04/08 v1.3 Expandable calculations on big integers (HO -) -Package: bitset 2011/01/30 v1.1 Handle bit-vector datatype (HO) -Package: uniquecounter 2011/01/30 v1.2 Provide unlimited unique counter (HO) -) -Package hobsub Info: Skipping package `hobsub' (already loaded). -Package: letltxmacro 2010/09/02 v1.4 Let assignment for LaTeX macros (HO) -Package: hopatch 2011/06/24 v1.1 Wrapper for package hooks (HO) -Package: xcolor-patch 2011/01/30 xcolor patch -Package: atveryend 2011/06/30 v1.8 Hooks at the very end of document (HO) -Package atveryend Info: \enddocument detected (standard20110627). -Package: atbegshi 2011/10/05 v1.16 At begin shipout hook (HO) -Package: refcount 2011/10/16 v3.4 Data extraction from label references (HO) -Package: hycolor 2011/01/30 v1.7 Color options for hyperref/bookmark (HO) -) -("C:\Program Files\MiKTeX 2.9\tex\latex\oberdiek\auxhook.sty" -Package: auxhook 2011/03/04 v1.3 Hooks for auxiliary files (HO) -) -\@linkdim=\dimen151 -\Hy@linkcounter=\count120 -\Hy@pagecounter=\count121 - -("C:\Program Files\MiKTeX 2.9\tex\latex\hyperref\pd1enc.def" -File: pd1enc.def 2012/11/06 v6.83m Hyperref: PDFDocEncoding definition (HO) -Now handling font encoding PD1 ... -... no UTF-8 mapping file for font encoding PD1 -) -\Hy@SavedSpaceFactor=\count122 - -("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\hyperref.cfg" -File: hyperref.cfg 2002/06/06 v1.2 hyperref configuration of TeXLive -) -Package hyperref Info: Option `unicode' set `true' on input line 4319. - -("C:\Program Files\MiKTeX 2.9\tex\latex\hyperref\puenc.def" -File: puenc.def 2012/11/06 v6.83m Hyperref: PDF Unicode definition (HO) -Now handling font encoding PU ... -... no UTF-8 mapping file for font encoding PU -) -Package hyperref Info: Option `colorlinks' set `true' on input line 4319. -Package hyperref Info: Option `breaklinks' set `true' on input line 4319. -Package hyperref Info: Hyper figures OFF on input line 4443. -Package hyperref Info: Link nesting OFF on input line 4448. -Package hyperref Info: Hyper index ON on input line 4451. -Package hyperref Info: Plain pages OFF on input line 4458. -Package hyperref Info: Backreferencing OFF on input line 4463. -Package hyperref Info: Implicit mode ON; LaTeX internals redefined. -Package hyperref Info: Bookmarks ON on input line 4688. -\c@Hy@tempcnt=\count123 - -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\latex\url\url.sty -\Urlmuskip=\muskip11 -Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc. -) -LaTeX Info: Redefining \url on input line 5041. -\XeTeXLinkMargin=\dimen152 -\Fld@menulength=\count124 -\Field@Width=\dimen153 -\Fld@charsize=\dimen154 -Package hyperref Info: Hyper figures OFF on input line 6295. -Package hyperref Info: Link nesting OFF on input line 6300. -Package hyperref Info: Hyper index ON on input line 6303. -Package hyperref Info: backreferencing OFF on input line 6310. -Package hyperref Info: Link coloring ON on input line 6313. -Package hyperref Info: Link coloring with OCG OFF on input line 6320. -Package hyperref Info: PDF/A mode OFF on input line 6325. -LaTeX Info: Redefining \ref on input line 6365. -LaTeX Info: Redefining \pageref on input line 6369. -\Hy@abspage=\count125 -\c@Item=\count126 -\c@Hfootnote=\count127 -) - -Package hyperref Message: Driver (autodetected): hpdftex. - -("C:\Program Files\MiKTeX 2.9\tex\latex\hyperref\hpdftex.def" -File: hpdftex.def 2012/11/06 v6.83m Hyperref driver for pdfTeX -\Fld@listcount=\count128 -\c@bookmark@seq@number=\count129 - -("C:\Program Files\MiKTeX 2.9\tex\latex\oberdiek\rerunfilecheck.sty" -Package: rerunfilecheck 2011/04/15 v1.7 Rerun checks for auxiliary files (HO) -Package uniquecounter Info: New unique counter `rerunfilecheck' on input line 2 -82. -) -\Hy@SectionHShift=\skip74 -) -("C:\Program Files\MiKTeX 2.9\tex\latex\oberdiek\hypcap.sty" -Package: hypcap 2011/02/16 v1.11 Adjusting the anchors of captions (HO) -) -\@indexfile=\write4 - -Writing index file pyEMU.idx -(pyEMU.aux) -LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 65. -LaTeX Font Info: Try loading font information for TS1+cmr on input line 65. - ("C:\Program Files\MiKTeX 2.9\tex\latex\base\ts1cmr.fd" -File: ts1cmr.fd 2014/09/29 v2.5h Standard LaTeX font definitions -) -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for PD1/pdf/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Checking defaults for PU/pdf/m/n on input line 65. -LaTeX Font Info: ... okay on input line 65. -LaTeX Font Info: Try loading font information for T1+ptm on input line 65. - -("C:\Program Files\MiKTeX 2.9\tex\latex\psnfss\t1ptm.fd" -File: t1ptm.fd 2001/06/04 font definitions for T1/ptm. -) -(C:\Users\hwreeves\AppData\Roaming\MiKTeX\2.9\tex\context\base\supp-pdf.mkii -[Loading MPS to PDF converter (version 2006.09.02).] -\scratchcounter=\count130 -\scratchdimen=\dimen155 -\scratchbox=\box43 -\nofMPsegments=\count131 -\nofMParguments=\count132 -\everyMPshowfont=\toks29 -\MPscratchCnt=\count133 -\MPscratchDim=\dimen156 -\MPnumerator=\count134 -\makeMPintoPDFobject=\count135 -\everyMPtoPDFconversion=\toks30 -) -*geometry* driver: auto-detecting -*geometry* detected driver: pdftex -*geometry* verbose mode - [ preamble ] result: -* driver: pdftex -* paper: letterpaper -* layout: -* layoutoffset:(h,v)=(0.0pt,0.0pt) -* modes: twoside -* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt) -* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt) -* \paperwidth=614.295pt -* \paperheight=794.96999pt -* \textwidth=469.75502pt -* \textheight=650.43001pt -* \oddsidemargin=0.0pt -* \evensidemargin=0.0pt -* \topmargin=-37.0pt -* \headheight=12.0pt -* \headsep=25.0pt -* \topskip=10.0pt -* \footskip=30.0pt -* \marginparwidth=36.135pt -* \marginparsep=11.0pt -* \columnsep=10.0pt -* \skip\footins=9.0pt plus 4.0pt minus 2.0pt -* \hoffset=0.0pt -* \voffset=0.0pt -* \mag=1000 -* \@twocolumnfalse -* \@twosidetrue -* \@mparswitchtrue -* \@reversemarginfalse -* (1in=72.27pt=25.4mm, 1cm=28.453pt) - -\AtBeginShipoutBox=\box44 -Package hyperref Info: Link coloring ON on input line 65. -("C:\Program Files\MiKTeX 2.9\tex\latex\hyperref\nameref.sty" -Package: nameref 2012/10/27 v2.43 Cross-referencing by name of section - -("C:\Program Files\MiKTeX 2.9\tex\generic\oberdiek\gettitlestring.sty" -Package: gettitlestring 2010/12/03 v1.4 Cleanup title references (HO) -) -\c@section@level=\count136 -) -LaTeX Info: Redefining \ref on input line 65. -LaTeX Info: Redefining \pageref on input line 65. -LaTeX Info: Redefining \nameref on input line 65. - -(pyEMU.out) (pyEMU.out) -\@outlinefile=\write5 -Package hyperref Info: Option `pageanchor' set `false' on input line 68. -LaTeX Font Info: Try loading font information for T1+phv on input line 68. - ("C:\Program Files\MiKTeX 2.9\tex\latex\psnfss\t1phv.fd" -File: t1phv.fd 2001/06/04 scalable font definitions for T1/phv. -) -LaTeX Font Info: Font shape `T1/phv/bx/n' in size <10> not available -(Font) Font shape `T1/phv/b/n' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/bx/n' in size <24.88> not available -(Font) Font shape `T1/phv/b/n' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/bx/it' in size <10> not available -(Font) Font shape `T1/phv/b/it' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/b/it' in size <10> not available -(Font) Font shape `T1/phv/b/sl' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/bx/it' in size <17.28> not available -(Font) Font shape `T1/phv/b/it' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/b/it' in size <17.28> not available -(Font) Font shape `T1/phv/b/sl' tried instead on input line 68. -LaTeX Font Info: Font shape `T1/phv/bx/n' in size <17.28> not available -(Font) Font shape `T1/phv/b/n' tried instead on input line 68. -<> -LaTeX Font Info: Try loading font information for U+msa on input line 68. - ("C:\Program Files\MiKTeX 2.9\tex\latex\amsfonts\umsa.fd" -File: umsa.fd 2013/01/14 v3.01 AMS symbols A -) -LaTeX Font Info: Try loading font information for U+msb on input line 68. - -("C:\Program Files\MiKTeX 2.9\tex\latex\amsfonts\umsb.fd" -File: umsb.fd 2013/01/14 v3.01 AMS symbols B -) -LaTeX Font Info: Font shape `T1/phv/bx/n' in size <12> not available -(Font) Font shape `T1/phv/b/n' tried instead on input line 68. - [1 - -{C:/Users/hwreeves/AppData/Local/MiKTeX/2.9/pdftex/config/pdftex.map}] [2 - -] -LaTeX Font Info: Font shape `T1/phv/bx/n' in size <14.4> not available -(Font) Font shape `T1/phv/b/n' tried instead on input line 68. - (pyEMU.toc -LaTeX Font Info: Font shape `T1/ptm/bx/n' in size <10> not available -(Font) Font shape `T1/ptm/b/n' tried instead on input line 2. -) -\tf@toc=\write6 - [1 - -] -[2 - -] [1] [2 - -] -Chapter 1. -[3] [4 - -] -Chapter 2. -LaTeX Font Info: Try loading font information for TS1+ptm on input line 104. - -("C:\Program Files\MiKTeX 2.9\tex\latex\psnfss\ts1ptm.fd" -File: ts1ptm.fd 2001/06/04 font definitions for TS1/ptm. -) [5] [6 - -] -Chapter 3. -[7] [8 - -] -No file pyEMU.ind. -Package atveryend Info: Empty hook `BeforeClearDocument' on input line 142. -[9 - -] -Package atveryend Info: Empty hook `AfterLastShipout' on input line 142. - (pyEMU.aux) -Package atveryend Info: Executing hook `AtVeryEndDocument' on input line 142. -Package atveryend Info: Executing hook `AtEndAfterFileList' on input line 142. -Package rerunfilecheck Info: File `pyEMU.out' has not changed. -(rerunfilecheck) Checksum: E57AC9067C569283B3FC65A491C8C23C;784. -Package atveryend Info: Empty hook `AtVeryVeryEnd' on input line 142. - ) -Here is how much of TeX's memory you used: - 13259 strings out of 493673 - 183471 string characters out of 3143952 - 270049 words of memory out of 3000000 - 16317 multiletter control sequences out of 15000+200000 - 41198 words of font info for 58 fonts, out of 3000000 for 9000 - 1026 hyphenation exceptions out of 8191 - 37i,11n,45p,320b,397s stack positions out of 5000i,500n,10000p,200000b,50000s -{C:/Program Files/MiKTeX 2.9/fonts/enc/dvips/fontname/8r.enc} -Output written on pyEMU.pdf (13 pages, 80677 bytes). -PDF statistics: - 144 PDF objects out of 1000 (max. 8388607) - 32 named destinations out of 1000 (max. 500000) - 57 words of extra memory for PDF output out of 10000 (max. 10000000) - diff --git a/docs/_build/latex/pyemu.pdf b/docs/_build/latex/pyemu.pdf deleted file mode 100644 index 623cb1fe414ebd259b584420420f56fcb0137f2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80677 zcmdqJbzGIt@&F7-O9_aSf`~}yfx`g~EhSyj-AH#yr_v1~Ee+C03W$Kx(xB3zbayDc z&pC?nRqy?N@4dhG{o}>Y^K3jjJF~O1GqbaMyeTUrdY#N6J{mXcA}T-V-E$WY(Pzz~y{7t_w()=<|1^Wlhv zqLkGP6JGti@9PwR~BNbt;W)12P@zcPW5^=ct8k7iuMp(t~JCv zZzr)s@20#b7~Lzj#2+`?oss@(XgrqrrG^I;3RRrcz$xOo%lZDL;}{Q5?tGfn@LGXo zr((s@-aMC#mczT_EQcG*rJmkEUi8crZm(2cdAM|ljL6tM72DVy4OmU z9Nn2x^B03Bxs&~I`)#jCram6VK3;oQ9I9Q!*g9J)XqM&sR-nRiKL5b^S`}JOw({5a z2XnIv3ZiDsUlU(BIIGhW^DQ zTT9F&o1_Dd`Qg`3W=o{5jK#6vY`r)Y6e=i1U;~l`Y z=0h?3c|IYm@JK^G`SX>Nw!DVc*B7TX*)J~YsQM#9`$E7n9nM783_n$D4!f8WH!t5d zz_L(%Qyn9pAus-568BCb*d!2{6IqbP5JR7(Tdw6z`v>BH&NtNfLNxItSe=+H#54>f z&2ftoLPkta#JId6=%H#JY_pNX|jLQ#Nz= zngPQ}M)Bp-w0A+LZ!VpJGcKQkTOau0o5OVQJv!}|X0woUzhVkgsq43fM4!f>m5!}{1R#C|2bEuF%> zp(Xsfl8nQ~m5$BMDojI5gYT;f$iY@5^N*ED$tX|BC}O2xMad{cNvm(HYYnVP5SWe< zSc%^ovwm+(s{w(s0S{DziHS)N_OgP#EMYHu*vs&*MLW8)9@zc`Iy*ysdlM^55csbM zIsSxD8rC@+;ZIz4usRJU7T`Hg%vp^LtXdEDGKIb1fSLZ54b12H4u<@R40yqoc zFE|Bo(wu*yWce2;&nEh>2%&#Mr~t$DlXu3jB79`P=`i6_j(GAnkguP5;w3JCj+r?4>M5r20#AI>DQCR`H8Q) zOmT&VK9|+zuRhqA)#p;18)^*e$|p>%EG&|GAkx?4zKV@%6RXKsxiV4s&SIjt(rP9n zudHzO4U<~ya@m4b(nHM$^JbQ=L#Ou`TED5Ydti?UN2 zAI4dXq|;COxx~tGQqgVazI@g(Og0G#v`IZyiE*}*xrHOTMt5h6YI*IF`-|Wg!S6=Z zM?ZGH}`gKO7 zkW`{;7i-lm9%ZZ8ps2=pnP zOHe(5h#1e7?D30YKT6>+SCZt@mGjS$WM6qSKrAHS*Q~X0gP+e|+`pd5i!;EwOyM!+ zszgHDt2Zk>uWV3EKsfQ4x^+k?OwwLC8pX(aB+>C+)&<^1UB|CA3f5=+*bm)*^J4mp@QmVYtcT8YCKBg6e*M#dpi}ZB@`w#oTD)*F`U|8yT-1M(8vI zo8_@u3<2@26O_7G?yq;JR{QdwDSP>cAi81+J6e!JAuNwr zE@>AcuLAesgh^S#IE)hG=9fx$LwVWL@RR=^N;z`Mt=SNQ)J3FAS z;#ceLB-(xMr)VaaEjm?~Y%;mi`a<<$v!gM0Imfz`z!b}k%;Y@$cASvlbq&Hk#xN|~ z-ru=LYLrj_ zn3;*v(!tzZ^Bf2q)fsuh07;bG+<@+auv0={Y8Ix)ARLqsn7xyOX){JyTPuBeLwibf zU>riil#B|75A7+>_Xs&NRp<5=V2EwQjd~>OmHntrIDK_St@wj#3uSqP^TZH@v1dVVS2tTDyQ6W znKRFIi#v@FE5DCo^Zkd=RdNnyU)~^n;hvLB^$y*UcS-o^ zTC0cryt>SI@Jg=w4ss_~x_@|3^K7&-aCv3#dW<{oGQH2m{A%Z+ie*dH_lhqjT9fDT z-8;%O1_HzE?w?Q~gyDoLeQ>gxI4!-q7tkT%7|b0u13ZfcV7c0hr zS=(BrZ|^fs6W2p$hUa2oB{~^@Y>wwVIA?UdgGKK>l3}S}Oqz_MjAD#}>afn40Bs`&Yy~LQ zp0=J?lJmUC+AY!?ArQvVxO?5rRT z7IsQjW>yd@8w({flm*1Z3I?cT1F^8N|B%KbV6J1OaqAD-$ya%EbCdcKvlb za*nE>UikkyiDy&+DLWWU35KwMm;r9Furq<6?9hKp)wf;7IjYV!X8*frol(UC1#tkZ zVrBv|1|Q;IG&Z140JZv`J7WNEe=-Z0Jvi>apL967e>a2wXr=&Z`icB3zzb&o z$1|0s*k?ONL+CzPJawOI5uXn|^Zl?ib9`QM%hIRDS+v{Jnw(J|sD{^0+!>JyD-xyq zNg710YskBK$i1H3p<0+-dhfD(BB+&)sqBNHa5qguuIo9Hn3kT_=UmG^_e$*nDlV9XtDv?_wv(9}VF^~+h7&Y;en@=N|r^IozwJ<*+ zEYwcD4@Xo21v8cy;14J9tv9$=r_Wp$prOCQ{V)t8ar<5M1cFQUjMX47OHdc@TR!SV zC&z%V6P5nbIbt%yS+`f8jVJ1o896=Zr4P+Z3k_|=N&Ng;*SLJU1M7s{==mCZcSu*S zy+0eDz#Zco61QuF$i!_F3B~w7D-_qG^3jND1Y(drToexVz(G^tc`S9sd6>SKjWO-^ z@)yf^y}1~I2uB=o1L{%kV%11GBf<#YR9|mUj%xitbhOT}+=~e3{@eDXtZ#1Ts&fq; z|ap3W8{-^@)}&Jf;$+ z)hZd~yVvEJH0nkixry)A;2k6dTy4p`jDAt8JbC<@{44F6vswKdnLY6gu&bF zp`!>N6J0+12T`<&Q zm*e=6*=cj+gn$x1^T0aSxCrq=r3;HCeW^-H0Myx+8K>&dB;RVKd_dQGk+1QT4b$zI zo(Z|3n$_QxL7~)vzm9qjm;2tmFgkaK>VZp`d-Mq0iH@jH6T=2bphT^kjCGkf9r)H| z+FLfU^KtLpgv7TMtCeO7^*DDwWM!p>CJbdziN+}N7nMmaX(IFU*x!{J^U1;uHyX;m zqpQ63ze@7S-JbrkNu=Do&e`6RkKo}TC$alh+0Z)+~*lPbX!hUrP zff4>x^Zy%RU=CIgGY1z$fK$Q&OeGr|2Z-Z8cBSD`_s6OH(@ukl67c!} zm+IS21Iz{mL6`uRumFb-Ku813!2*K9=8}~e!~}tPsoys*jIg~6BR@Acd=tUQ4?nSB zlxI|w6N5dp#`gBscASiiMke;g4tgMcD+@+bdnaQPdqXG(qqVc4g##Th$3Lf;?Yw&f zSZw$b{(FlJFze?e{0jDGH;wK5&hZxzFcYwLfSnbT84Ng{kl(yQzzO}A=)l0fhZR`f zY#hKkWdGfxWIHb}zu;vCV8Out@?XNjl+)Ri{tMUghdT+lmQX-+0EmAWH3HV?&)&~m zOAgk5?68D}zr$?$7KG%ed>C#b#%B9JP4?2~x4Yt!pdSuxfDc0z+%ebMA=&GCZ1BD3_Au{ID90VO|oOvh~o zGuk}arODjX5INcId%?65kFGoOp~$ZlTx#un6iUQqqVtjnX9;CK>|W#V76j0%KUwl^ zasOgL;7t3A1p%Ub|CO@}{Y4<*xc^@C;pqOIOaP1P@3$4u^HO;hIs<~6|8!eXr~rh{ z_^^Q62nSEaLpI%L?EDlLg9JMTEHC@_KJHhH4xsK2@(kw^Q?}p>DB8nU58M6n#T>U0 z75%{_>^;^fa|~)~Onfop3SUK%I}{khsP;EsNN)wD;sYV++ldxo!&{7>?5)s z>#f;ji|JbENOj=`;i{+c#iPE zN=(uV3>vjmiM8AW8~pZLQ<=gLYxS-di`=g0mvIyD#4Y`0Io@f%Lnb@fy#DZRFyF!; zn&dPKjioVvIyVY8#Y4K=_qmH})05F-Gi@|?9XEGsRUGvC%!8jZ47+a>#nT2YOGU?h zt{h1sY^W=pGZuPdnzNwV%eQM1*Py`^kX3pd9u;ygr(Sqrh@WKFGbVm@sq$T7U<6@< zJ7&B4^8HEf=k~;Xrg2rP%A9n*Db+`+%%P@1h(a4Fu3F;=ivEZm;?6S6nK-5v`IVjd zikp{ZRu5-zSfKY5S)3b)y4Gv*IwzL3f+jHBZ*;btXxm}k<>fL{ImMOn*;|CBBIjdo zO?yvY;m2`bw0Y|k)qx!S2T?z-)BY^#aBt>(Ko<(vE`RGRvBOr;`5-y;eDn#>fzWU6 z(%)^y&o9Pbq5iDo&yVSM2v#Tr1cb_ejmHD=kDrVF4^T`%Yy{ZH{R+l@UOE2)#sSz7 zW>!iTHWm;Ya1i?&=)b`TGb4Z{W?^9iv9bRF>-k;gFIZuoArL45f|Eet1n}&BMGK$4 zf2TUmM<;;%kEq2zQYpZ}>Q6R)S1HT@Z~v)E2@RL9f}4{QAR13?ZPB_pi-}B4jbkxR z&~Y6TMESY#hT_|XVlt?|IL}V`NFU6kh%lMAHAUt)JkZHg6lkm$gqf6bk}<^NPf=Uk z&V;p9_MmbxCHJp=N0zyd?Iz!)@6C{DkGpFSzw7U}5Swm`-PrK+ZiQKtT=g-68h(z#%sL_4y_G>2V|VG&XD3|)dg5wqql{{9#kj^v zD#Dya4UEX!$Cl_D`;peqi9{QKScv51-`5=bd6oaiH3w(nU(5^O;{Ox#^4pqY|79(} zasRz}fus9(GV+gPf#dwzIFp6{z_o~#>x7+e)*UnKJtZ$2-yLnmmm9%UyCzI7j+={t z5=e-McHkC;pP4O+0=m}8l@hOcr;!w08rk81s+u1O^;RP|?LLuGh01M=jyZQszw0Uw zC(BHGhoJ|^$}7L^C`KuNSa=vGL=v-ry67@m@=5onh8&S$xt>8e3WR z8Twx5j)9!6&PjS}qJkW=)nae3^nC8-oi>M|N-nS_vqgiM&c6r;gHq zc990@h|=a~pLD&CYQEg#yGryhe@#mnx4z>fDOA|RbHy9;B-~XeM%aSH{srpTuKwQD zyNG!{p;~{Cuk(8LkMaedn7_)`cP;-n2cN&m7soF;jQx9n^KSyI!1m^+mH_nEU%>vX z)B!E{6Xfr_;D08bfAD;n|AU}vXoN&C5*RpsKfLTNsS}!<^0alR6|?Eu;6r@eW?~8^ z$jjy1ohPooeSq^TKXC89ruK~$MBR?Z-n(}%qG`DZqBh~&_8yWp)#m%ql#u#}bmO%B zz4%TWCoivJgOqt~8CS|kc)=aLtDO>E7dr&Zn?j_Jjvvrp<(K79kO(Gv{ z8DH<)keH8Xp^?CP`(x5{{gghhA$B>n{zRxtd#)J$J(9l6x$d<23_<6$85z{;bS{qr z&~zhO#c4nJ@2SM@28)aZP$&`{UPgL;AAX+w*IRMGKIkXg|13=3RnyN?bdG=K3;!-m zzeoif_unfkIJ*B`S)G@!Gi3#wyZuwc6&nGTLE^^~KK^=HI}c^HyL)9tD$pQOmYApV zh60|&vu88wu45+YAB=ZzM#t5)2R{^fBOj3JioAfKQgy zA%hsE<0T^IO=b_xr*-MBCXGd<)$=Fkbz*G`kwZohM{D1jp7fh~Hkd76GP-$RLfL~z z?BWBlw)k5w3y;)_&2v}hot|909C_-oFO}2r>7(xL%;=)x&XM=?_r^oIi*Hx06~wG4 z6K}4C>z2{xf$bNXOyuI!XVF$ml-7UOC>-al$e)GaH!l&0X#7_e3pn!mDR6*lfaCsq zApj2i|25d3H42=<|5QA^+;T?(Wu2>@X-Z<|o7F4GMjn=gw!6F)dAR*hHr7Pz-)7D$5`ljvSzL6|W<`3LOy34W4&ce^T=tW3qW zAc|Lz+11}}-Sk|jP4PJ^e@pqZv(@>X=+?xaf4}_fVU@5v}ukIFHX-!<} zeXIXiE(9kuCeU6Ag>eR4>A=04wC@wRkJ~dP<@LJ+ftk)5ia(0bH%9)&DuJ0`9t2SJ zpPD5w(|NT8NYOX^|3Hl382=B1;=DM17YYu52me%?BrB@hjd0?5%quVC-H_2w3%)t( ze&Z&FVFDZWjzPD<)|3~LrF*`G_+s&Tks}~VtkD|V=-_(zqglendo|~hB2zdorPm+ ziS^}7sm>-uu~Pfs;Jd}#!Cj`mg3P$7bv-9 zm3je5zWG2==a^D^2lK;%fCW=_L@&kVC7VWY%2nT!X~pZou}g<9IVdh}-`0jMmsg15 ziDb`d5%-2M7A-oPUR2+4jawPGwq6}o>e0n&F7=UQ`GS|lK*o)EEoYyBt93Z&r2fd9>I%33s85b3TMZK1ZY$_Y+CJkN~qC-8f zA12$6kHxi!dmyJ@v9XnURojxQpGT$8MJT8@j@c!Yc8Zwr-i7BXPZs%+eISUeS3UII zbxGR-b>q1(5b{KD=X{@KbHl9&*sJ|;JyD)SSv)pg3+`;Ff%BB^@BupvsEzQovb9`mSB>q6a??IqN?(By+cvaP&3{#^u42tggnv(RO3Fx7W>E zK7LUPUSj%fR1{+qHf6{UgKE4gE0g7=kq8>0H(oTxHEVBqdOlY}xy|H*9EEXR^YP{^ z*(pNc@L}?wl-PM6;a?~*xC4AXTmpoZ|B(^{gTGtMA6JjSfcpWcKT0qY%rOE|`0aOC z8Qu~of2%)##Pkj3Oo4r)@%zoAzdrzAh8;No5lL2N2#6I3F|q)WX&@ZP3fv1}fwKK) z@qZxv@8skJvNp7{H@31cv<9wTf~;(f7)5~s$lBQYuO|TChWKBS4S`+y0isK=+cj*! z)|Hio2?*c=w|;<_4J-UG>_0rWFt9R#(+Xw+0fvK((G+B8>1b#5z#ha56oCLgaMlL> zWdQKG`7Z~+4h1eNfq{EB!0jMl?+-`GbJl4a8D1ofc9%} z5d3Qx6wLHjWURoHvH@3me+2{rj{hDZ>;@ii3ic};7`R*gH-LZ)vHTvdg`RgP|1d&e zJ^|VL{UiG`q2^04Qu9@hUE%vlQS z^LziAFl!9sJ^05)4>&&n&;qE=9yl26E-JuASlxfh&NGr=_gT*#7>fURfIa6~ z1RFlA9`?X#0LX>61K7j0<0<6m|%71*$AtH zm0|T~tolJa@C}xS@#gH?S$akz?E6_AoIU^sR)+NnT>YeEhUI~j6@JSZrbx~(!CC-& zvY*lk`|zy-CJI0SHf|Wi**|!nFj`rG3(aR!2#Du*sXrs*-=%P}VM7IU+K&gIfdlyY z28{f(DLKo-+54RZFzJEy4Pyta-4Axd%CKpGwfjCr=lg+=3HITvQy392zQIyfc0l}r zPT_eNGk~$f#(9>Wum1r9w1MSe%LmpDK44h-9TqO?=f1&-g7wXGo^NL=5BPn_x77ph z>U%f;E)OH=8|eUnZ&wjJU z4BMbEF`X5G=4V;hjs^JnCG79-?Msde?KB^cB6t^GXfzTP#ax}T^G*%v9^O?u8*>o`;-Z^n8jHi0JJucn?(cKF)L zn~wID>*%tGALBU%S*2cg6h!S+&Uq={V5GlONQi7agItCtk;smSg7}ip&WoiN>-Afd zDMCCvzAO~ck6hJ)d{3055l}6FpRhf6@WfN?1QYS;%hR0sT^d3I4AkCwk7@*q)vGIB zIyuS#r@Qy0DDkEYP>@x;%)Jrl6j^ytE^{FGCIzwa;h>`mNSY&>c4t>oLcEyUu+SeP zksuQ7;d!mz#X*3$mT)W-_dhc)R>7C`!a!I;V7XAQb>U?^;?)z|QUr1@JT5%Kqa$nt ze#9qu1SJDzxJfIBlqKlEwI7gZ5TNrq3 zwx-O4hQ44$+AWLsVJj6&1p)6_7RH+~BJ@YEn(FjUhmt*qF%GPT64}{5ygYRuJz38^ zok6hmL9)2>L6)ON%PxVVey$SP`~JsMQbLrHM?7B9vlNbYM*gQeH3-)|p#jsy@w#p| zb@eQ9D;?0DUbrLsQE??%OCE{%#EJgJ*JGg*sOT4~O9zuw>!(sYSw-V@SakDmY zRMeg&f{c>1hb0076zz0be|8ELeFNR?kYejA_M5|=Z8U;g=xx|K8)__Q^OISpt?Q|` zGCaK_5feWqp0eIbkfzbue&2V**sN^{F+%^O*w>(aY}S72eB#>X%?Eu(veZ;iA20jN zp`%J6=XsT?`;C57&8tZp?)*59D%2+J+!M-tB1{HfuBMn?KVnzQna z1o($44A4*S7Vl&*R!9^v^1chbkX@x}E5*W3=6Ds$QgLGXlIE9_Rc!Yj9Z&7`9=kW) z7IJf4#)N9uqpKzY{S`2z*IFX-)m|7W4`Vil96Y;E|Dp(yy7E|@v>b=j?<$6zjcqrOMOvXt0Nf9X1R_`U2Ei<2)3 zCRG@E4o_$t8y(6s>qKpNnj)*BbmxZiXE&Z`Ax7d2-j(MrkH?r8=F!L-8$6aVHUQB+ z8KGeK)Ekhh#*J+!y}qKwYml-zLbfO!kXEWEX7m&xQFV+{M=Jl_yBoH-`KN>q6@}9t zEEFV^$6K4+6M2zt4rNO;1xzS=~`%$C3vd}>q}$H^}!3`v0(P{=ZiM!Pj&ZEROdm0 z#Yhq}AB4Cs`^$&S`LSTQDj?_C>A5|>2m-M$_8cj1h244)=;%Y0NX@No-5@$I4SLm# zp@#e-^)Yrl$`uROInOd&^?pH$*B*JB<>@$R#_a2hm@3|W+O*A~%M5Q(y*mR64N+Ge z&8s?|AD|Da4y6ZszdU5k-V%H~nX?}CJm?YMTXfXi&TaVp7-#B=9@R#-5(oQ!iClcgh3r?yjlUvGj2Y3F>TocWG9#MP>2m*hgM&n zb*xWF-|?YR;hLB6YQIJeF_z?v?NJrNqS*+f@!9p6$GUkK$US(G8uDEs7s&2#h7 zauyZ`205B+h>KJ~=^Ok7_z2MDW_O_%=8DXyyvAUWleD~`=C9H>dNZ86w6BdfMGQlBuf-7C8QU`r z(}H-}Ow(4d`%|KW!O9N%J9-1g!9mUTTRTK8DsrJOs<}}dLfs*O7J)Z2R<67k7p1q! zGe^oVve%GnS#&=c>EqRWKqW+Lf~MQ}Y~LsbN5ZeX=cZJ(({-5b-8+X@=wT&YdvrX~H4TJcn5B zWQ!fCgoj8xVb?IRZb)xvMBP`;BiF%}KzoA3vaifn<3I1^qqOa4v047|vCjpiFqxTl zabhPXTIGvin>)0J%U~os`I#sN4q~?jA6IZS4w)|%o-7a zITF=?Btp73nvxMmzknlEwK6qnnwaD9K!9kfKV|uew|_%-g$;q5sHC36!&7QMsbtoT zX6(=Oe09|C3>-}LhsqZp%Db7^9xgx8R@>)qXL>3dt`;esRE#?;U&##aR9B`leyZ%4B*SdK1sq6_ekl=cL!f=|#3iHjG!N5YX(qSn!d$g}#=-c? zl4OtS^#cVgNrcAkW6Q!e?+nbIq<8GN8@ADo=M7`4e&nd8;(N-*SGLmLFHM|$A zDN!9A%O)jZOdYL=VrC?@8&ou`7{vP^Ts@S2vgNKKlGh;o=6;Wt8*i=mP`2O^ip3Js zJ(`z~7t9_yWE~z~h*;=3urKbqiQ0@OYoLJLf6#i!@`mRg(%|k32u?`%Nac3tN8vPW zoLY2Es-z@^Ns(61aOB*S9VP!fV^o?PHI19~pKwzd4>#IlaONoLD3cbSigc%ts%EOJYw*tye&mU%z)|BFgP~6zNF{Mdk1MTBGghSIeK-w;}wyEu;NQ(_}OnW@}i&gJeGJbFs{nckwt23UYc_)gw)r~_av3Q z(qlWFBBs_eqgePlzy6gGWWQIbG*=7n#1p=evIsBcVSH?#J99u;Y4jY-wv%(M&+@9@ zh5;_!k^6@7$7}sq3tT}Zu^8@G*t#<>6^|^WcjVhW9BksNzc5Q+9SzmA_WL|QL)iJK zG-owa`<8E&3vyEOt!DI%42$Odud%%>E*cr}otsYsY`#kFxqVa^GE0^^xLlb@DJ-+1 z`Tmym=HPQdd9yEV3$NZNKpZr$zkJ_jXN23;y+i&n^dYFg3%BDj`q$>tOGqZdA?1cL zjOFW*=n+<;_<*YWO{D&a(}KE7wUUe z>3GF;J0Rtt_hzwi-;>B&GsaF*e5N*WahdblX;BzYC7o*|Pl(>%lyF*QVcfvR z<@depdw=!a*KRM;*ST*y(;r;w-XD$>4e_S!6HKekxI<|aTpC7@G+e}nmai6Oa6ed8^u?re zE_H3XgFCi^N3k;M9j4zwP=L)+idcsdH-7bVZhBBuRr}7sSgP-xr#XQOB_r&YeeBlV z^#(*76m$ZnWl;Mz#XYkRro(MB!d;9C#VzqKcQPz4Wb|CMKXSRPw?;HUus3%vfzk3H z#(cH2VRV(?WoA?{-r<1vRsVo=KKeuNlfVyY_BmZVvBjCKN?=2ckLgbWuwSA^ruW{? z`V{@PyyB!qZT9Pl(|zU(R>2&X6S^=3(ncj;7P&-gAXtvOsvKo5r&9z}-uH>;OZ0zw zdp$KYZiVA=US~+A3X`Ntnygk(=IbpA#+esw50G8H_Hm{?vO>R~<9?4ci#V5c6oK|q zcD31OeBKX^Hl*F6f(s^TbIBq!dD>b})Ov2Q5PiYfee=}(vi|MD@YxE?9i&4V<$8?D z=-I6Bs6zc@@>Von6LM{F=PLS-BJTPICvptsT5jwY2D%t%TIL;Gs%AQdrqYu0wr>@u zM3<$23hW?xzJB5D5w*K&biE3(q$Aw|u3pp@Z&z(;qSjxsA3lA8Iq4deCAP(p5s+7h zdQ;qwvP+40>+QiCgL~OD>fIh zT?xWvK9IsdY`76!i#d+J^$u(>;sgBbj4j3FxtsHzY}TWmTA2y0m0hcd`GM+4XZHCq zgO!Ci23v#vyN@Uo!Xw@Ls+F(G4%gZvTe`RSL>L4RV}{0}y(6HkTXb75>>&^fd~0=M zjP>TtPA?bfI-;@HpxPQ+!&48j&5c^=kJZGpWR45`>m*;s;s&GZNRV8;2Kjk*9-(D= zNnyY0BJn+y_ocP$(J$@peeLR8fHNL#WCLZh%;zj*`W!Hmu~@1Ck`n? zQvIU#&dZ!Eos1N5E!`SsdY5v0+L;N0tw5T-sy>_Yy2lk21NTEqp2mbTHhZ>|?o_(V zeRKzjjjq`X2h<*M`B-09zXA?ow`8xhi1Ol2v@f*_m%5~BBpkYQU~3CD!%r;i-hcTf zU8<-)ckb0iIkwaGu64f7ffa=o^|xC$E~#QXwvO#k8t%}sE%JRbWcMmY`S-DrNRBhiugH`>Hn&&L;(5<* zHHMrzE4t|z-jZ-_uH(q&6Tv-9FO+la)xObW+9b9O`^e*@pnh1}=k z^MyI=&Py+j@2g^%4w^U95_zhF9;P@BL=Z;2n)lhUr(Mk?6LPv_SAR_NX59vdy#iW#pK=A=)0m;w;U!F(G61_4$KLJX;BWLnwDZ@&`G zE78h|pDs8FeK^EkkAs?0xOj1VM8_z2#vWGXJ}-jKs9B>twnWc3JnW(FW3UVQDt4;> z$y!4{-}XQY_+_Px7RF{TTjdsS`C}*HMQp*ewe{zd*K}X5qp=7OJ7FC^Ax~DlrPN6u z&{$!k)u3UR(chw9lZt5<=t!?eK_0O?`*f?PHDj_BgX4zG2mE7$TczXZg~Z0UIg}3y ziCfrgCk&ffPBM##6T5=MDX9yLYY7D(klZ6$VToDi;Ue!8FS^{gyy=^zdF28$dQN{C zojxRhJ~rT!DeWwsbb`I3K`6+&IezF zvSRB7oRnS)S%aRGqjySw*+F3~=9H=}?N<$|38oUSE2*`apNU0L5h*uJv3y#3cW1Ff zwx|Fs-z?y~bAVxUpPG7%P{GJdN&f^w^>FR+=VD14;!D1*M4L}X#ypYJu#BE0RM*~q z0$RS%|B+0Cs%x)Xns}r#O96y0D)=e&^NU_Rmbdt?UwLa~p4>uQKpIJ2+ap9mALj0q zAh2hsInkCY7N8isei7x1N-3S;Mm<&kGGds<>)rr-y8I$>!570vYnBcJGJIk87Bd++ zEyHAO_9XmLl^k5Jh^a8#EvtIk z(H&`ux{XQ4c;!B(YoYUP+-i*5{!^`$*z`k9<2QIq%w2c`UfVx;(F{5w)G%CXR2}r0 zmQJUu9ezb{+dZ;88`P9nv38WA1m){m&H!OwcyZ74F^k}pkBOoh32f42T=o+}7s?Pb zG+fKc=*o89C3|6y@=tnAa#V;}RNCz1GvDCeyzh^OK%W-=VhsyvG3VjuP?SC7JmDAY z^n^T|CN?DNo^H;Og6N=*&ju~3v9X9sGO;4h$@VG67H$U&Pg!wOCQZv-B+Tfos~vb8 zH&!Zzh@WnbTDi8#TZOXLG{zF;AE7$IOL^n~308yn`^$v%50)thPg_sw#>?EQaP9O# zBS)~om?9uh-q{l5oOKb)A7Odf&Cilirx3bGx!mS zo(F-vuDFGsGK%Jjp1tL1GLKyN@peRgrEwy~NSPdiRiE%cJUZ(-Z}Pn0B$+!*yFBC~ zrbLiN5l%i6RFXu;9>hfTYRRcwmWRd1bu5c456Wz2ot6 z0Rg37phqM;MMx7%vTwht6*buL#@$4t?7NpVi|qsklPTiNd7Rrgoek~vBMLG_+}J2X zNv2B7I@O1J5?4u9`56IrBioH#ZGoin(a|r^HtG&;nLA*H=d9K& z78$H#j9m444RV0|leiY0H&ktZdy=RFc|9Dgc~(UM1+rM z&OOXTT+QJZY4^LeXxJm%OYUm2Mh#F0VBeUbUW^;OtgHeWY)v4jKmD@XL>wrK^Qik# zFC%jEX9bl9qm+SPFlB0)8y=Q(Zsi2;`4m=m9B^4s*PwlBdAt%hdFcXbw<_d$_k8QS zQ6^ihNL;L&F5%7$Oxy^?9PC+QyKuLxX~}E$iZf@LDmS&C0rUVozkkub5GO}(D`Dfre5S}v9u?)J@W@_gofuS_Z; z5_J2zY9ztpboyO?zI%-P;YeOVlH2k=gr_CGc;PuT*D;MTp&Xc0GY2{mA4LVw6W-so z(SMuFQ;>%C8WCq_i_l)@(a0(pxjUTIK68ZiW ztJ;Gy4`EIYQK8lJyWA4wr{IuH|783=0U>6bEX7gF>_wH5o-q-GkY0wVNI6;t@3bHv zF}Z-|U{5tc-&KwWcB0QiKPDl)ksw}-ONwZHe3RQPhbSRVttOQ125*(H{?o>up&q)9 z%JeH9_I|N0qILOROjVCA3a46pMiI}~7f9?0?tmD5L9k0nw!x6&iriNO)vAUi<)<(3 zOjfELOrzWGuq0CBeT`mnDb84aFvUn748@L9%E0(|B6-_a^6O^4W6Cu;+N{0FR+1w(vI+jLR6159;Fw+6Z`URoU;JK2cNZuoIsW+La@d$clE z8OYgA*F76x`haAQA5m*DY(gQ8$-aw*v&tHCT7;WYZ@6 z&|umPnQC^O9SWz<+4m3Pyf6=KZ*b*3`tT`slW!G~v2&0;#iW{R49%h4R^3*1d9SmI|vflm^(xF8do_6z05|juXL7;UtN2R|3;J)?H2^ zFeC61SL@iS3-)QIvu97Pl{I=a7RKR#bJBruZMf;JCp3ZYnEO z^xkZ8o+ig>oc9WQlw4``Do;rrmvIjCT<>w{uvhLlvJZx-ft{NDqN8rcE*7;pj&4 zYFefz-m*IZ(-g;&SUy+&N{eJj?xBybWcW7pneyO^r$y2A`)-=1O znW)a_<4d;h-zPjZxu%7%u+!=3t-N?F6c8*4@uw)q=%Bh_?m5L5VMBsl9KRxs@TKcG z`Ku2Jb%x^cvtscqC(nC2NSy@__r0!H%xxVofPzGL=r*l=GLC$BA3SbMvzo#YFa(1! z+vIs&pKV*_1n<8?tlA@ZzO&0R>2fMgT{6JI)^X!>_re_NfJT30`5XH8P`Xmm;=x0? z>$Svd^ww*GhKhx}asf&@Nm<&ryG{p;93Kjc)=`g1MKd*#`z^;}lESY+V+02j98qayz_IgHeh21Xe%*H|X6+iK; zTsE|5{Y-++HLsY_@+kTNVOCOLbR%XgWf)(~yldI;uDbw{l|fj~5)%&>Rv>X?RfAlc z_3eSxiMp!^jV6Wn8m)tGDOgO)JQX7T^g-)@XgZVVVCJeE)Ur@CzfLNQSBg&Tt>q!# zY`9bTyM5ohh6CAEcZZ2D-1X08sdR_}p*I@%sOS<9obOs6bi_|G$lg!vnl{>YPYk%j z8pP>?X}Fu?3z0qH-j-=E3r&Lv?CXcSD1|<^HUYiQE|Ir-`nnbWUQ>4~igC;b`j1^0 zk==xb9ipLQ2OcC`$3wWC*D%w!+y*^dH8aCOojXyAxkFLeHI7f>8|E0I2ICqk)m83> z2W2@wu9T@@a3Qlbaz{}sKmJJb?hY^KQ}ODrDY9pT+F+46vOtJ_Y@Y=HzkkS|~AOl;5^ zB=Fos7a|X?i53^jSm@gqxTZH{|8>jgv-r%LpjM_kq?{dZI2s}qT+2iJFN5T@4Oc&JNSCnQGvg^>%@~`y3jc7CZY!Wr-XgZtsEtiO6!=AoWAy*#xRj$pHz)5y#)5;YeMcFVbh zFGx;r!=rd2AATr`?c&%oUh{)IIF~ROzhg$ zh*s?qO+xpkN1GS?BtNa$yv`g2y>(DH>8>%hd7Kzbo`iF<$IgCYY!YzF|A4tj!g7<% z+=cG7{M^?%Ep}QY_pL(AHfX8=)~(kQEK1 zepe(FoPhQg|JwDm=gkno+G4i+H(|s}UUqRS_@OW4ySsc`qi!P--yLS1zZgqSM59N1 zCG}CkX=Gf%%MQsq9-s}xd!jPTF=VPt(+`MSvi0{&Yjxh5+&6v6f|oma`c{|9D%LbW z6(=8eVqe|Jm=!bXh#7CJxuu`yt@5FLUJ-$roruIZ4W3a@vsoTz^3$}s_U05*noaK0 zPb!dx-4fX;X^|_M+~|W29($X|Df7hDjN0ACQ!j>Vd6&ZbL@ToP$Sh_?`G=G%-x83T z_w^q0ec5u8xz;TWk{gJN5S^S|n044B4Q_s-&Hn&*Njx<<+Yk#!qx5lL`3Iuziv(6d zB92DpO&JmKsF21y4%mhhQY zkf_Vgu38QBl)ym%sK7s!zkP!&<3>*g@5?_ z%ra?)t=nPqYcY|-S_jBA);3UQV(X;(Ag^|xvJf;kZ0O1g6FNzO)!mYi0x7UY^7VM##%!hz}UC(BbLpDMD z#^riFheIbLjCmHs^rC~f{`c+WjpgH*i9qt~*ji?wi#aW|%srINDb=}kS7jb({~AHy zYBTA*XFq17d|V9!+^i)GTZdLxBY~+LXqZS^>X9!jQY_EAop{5W6fdDur_N`_>gGqF zT}}82*S*_#O=_DR;A?f@xve>#H_9>d*$~o#$|f+hg_^|BTGY>Jcq&HQp|=7R;5Id+ zxfYS@>0o#U!CPIX*@MA>?H>w`h}2V$6kzoR!LJfjH8PkMo(xY)$Vm!|hx0ftv`1@_ z&rFKuGP=DdN^H~jKtR}8MqN(JxE&ozPlqV})XqT8KVF1kjsf{+j0?|9`0RyX`(dlG zZ#08Ht5u*5&eU*jaao}SX{(N!l9Dr*QOXEn)F|J-b46-LRb|;I?mf1$o8$Eq9+Chx z9kdrUQFSIPmV3M~Q+hIdU z8?0?E`e>x6J|Yg3TySK1r~mPV`92%tR!k(W1EyS-`Ahy{z$82`<#Z!PD4 zG=qV_76IY|A@WM1tKPz~ui@S=DuNaDiOmJ|t?9K{ax8fJlgl5xw)uO;tm!xvUQx9v z@#T)Ac5yDub@~wp&P<^6d7PK2uD*(U_=;;Tgx-Iw1UJrD*eOCYX#NLWR6=hOd|;of z?GsG+r6HJiJh&RDg%8P3>jYGWVO}P=pum9M%J4X4Xse-CNO2tLgagTscT_cT50TrQ zIVz}?c6zsL%%<>G5*jZzmScnhmQ|wCxBy&wVW-ihAd0eOyvK`p2T~}HUROzyYI@<< zuoUE0&>jc9P4zAGe;R#>{3k>23sJQV34ti!A8FJyC{V zO3ME0t(qsyzRNHqNqjz($x!8|saj(PtDz2Wb`+b+zg+oWa3}49JRjP^6Rl*`m{-hj zcEfEQg)MRkn-#HIZW|B?UmuTfupn&Ls|b5}ggu8MX*4ydViU9bg1RKus~mz!Hv3?I zY|?)2M!h_;w^Q@$rCC(oPoA#5A7UWN4EL-&0>aeU65NMyrSpgPjaWSUUIq*c4fiId@(AxF18a6Qc+fnJV=^H+NbXh;Gsp#_5jZy z55I4g2y`iRjk~JZa87#blOc8MJIv1o(NLNYt*%cHF5k=UmZXlYCO=I@npno#V~=t~ zKbSqJ=PADQvq`&~h50xde?J=F7RbQgZ}kp;#4mqg1%ZY^8%Y_jNxm(^(rII(oEaeZ z<8x|SR53ES6RwJdWtuq%J0v8xSjW0i z+FcROOrmqjnIM+njFB~!(IjveQ?J1}tP2 zUL#yLeD?SL-IWMK>RU%G#4EH_6~*LKlz-$3qJRtM$W=&EWbpQNU0-HnZ@^2jT;86@ z7~)$2jGkGu12G(yWhMHrcO=8#&2;idyyr|Y5A)IzGZFOQ`mj|YsgDC3UsCf>8k%%Y z-&3G+UDJt2Jq4S=xb3mVSUacQB=7XYk)C+DcklSp^cWHWU2lp;IV!#k$<7yk4NIzO z3ff}kdOU&&hMYp|@LomuadAT&qzm8* zei=*U~yt4=514#w#B zzTFmaO`fBVPdEeYpTG6)qqU&sB@J~|biCKa?a`)_8xchgCQ@M5Ni$7~J@M0MHkIWa zkXvjMO9fj{WQIs`hC>qQNi52tsTR%b!~gc0FQ6LdzHEPRLp1eXZSM2rd#KH-uabd$ z6O<96YTyax`whD#WtCCMg*sqYRK$)5d!t6knu0(3`33aYnhEt!^85d7u3%vN&q1I6 znh*PDYDdV}(a^!%&dJu{JCppqmeIHVu7;45Qjn8Uq?R$aHgI-S)VGnM6|l83`rcus zZ|eAy`M5{M$7a)4)i@H!1O&+@GmK~qv1cz5Z^)g?}7<^J27K(Q?q|B z|2g14&(Sh6u)zSto%F5D4f$++YX@Cqs79a;u_|`5e0h9r%0Cj*qzyM$f__i%t>jR7c z#(;mF1Hc4eV(wxLF!`>y0GI;I0A}uXX2v!EbAScF5?}?e2G{^>%x#PTwgB7jM>~L> zv4gp-5x@c92yirZ`QGVh?gnrIIGH&Z8v~qNZ2`^zcYuengDvHMeJJ0Rb;jS8?tfC^ z|7rVT_%>ht*ObTqQ%CVnZk&6a zxM(blMKzDXY;6-fQQ_k|$pVJZhe#zPCC$%oljP$hEx<7cqddXp1S6R`c75DDdY$}q zp8RBR?3ij@YF+BsbyvfzcXr?RlcSDdL%<909lnd(_0fkEs7E%RjpBlzM0Ey_V5y|*!A0^%eQAgaN- zGx!$uj4+2cu=nqAb|KjPGw^!oF>i5zf`P3vX$kF&5Vz%kFbyuYLEdSB%$xi_f9&tW z345>;F_5IPqC$kIa|LE@0~x7U3megaqg4P<_vLC=$oP5%@K1=kHJ0_@&SdZwIlW!3m1TM~@i?G4j#I z^;L>;&0E^!Ui7dLT+r^t2M~_k4*`R;{WXT* zWd90z2;=ZM_w#X!tIr=*A38_?mm7#L2A(dP3^hs$TJNh%I`xT{ZU3t~^otnuGdkkw zi*3DY>eyFn_v_00ldGXNJTS1ZfY<@})58d8SQP@o4dVG@63B;_S?}x%&EN#e<@%Y8 z-`8~>@`LoAZE35ftN7q&uQ?l@uUs?8DpM*(2`F=uOU#-SEzUF02Z6_ubZo*djKGlET%!^b4 zX-)M(gFV}tzfR?J`{MiD4UY+K0Nc8Q_4fmD3%nx(0R#Im_yvW=Javt{gothH9vvmw znO1Xkr(`E*>jL?`f2YQE$>+LeDEP~By0;|rV4H$~Vm%*a`)rq22Uf5+HhEPE)E{{# zeOd9r93a*ACS?RG3P3L6=M=URQPn$i(IUClz^6R}Ghi(5Zyi5IbF4c@2*;2~(b>@W z+Z%}ETqE(fkYt+9PBU;mF7^?utpEHC#KpEHi;7wo4KJTC(89TT9*2QeaCso!#FzP{ z*-MXn;M-7h`|vwTy$2rsLaZ+4F0c<4ftPGai&x>$kz9b_EdP|E7Cl!3b$67zpU!gq z`EXqKQHY=yHmPSd_rs?-C=Ep3w3WpTPm1L6kv^ z!IXGOFFU^TPZqPe+=^1vPno=DM%||1I5uLxk~fLVgMn2ObZ}>aVl$L`CL$S3UQ({q z-`DG}2{eqhj{5gy_cee{~p+kIqhA|D?PRfcCfBmXVD zht6sseLL;60=ocN5P#4d&@|@6l zMKfjKYN-v}6Zw^AxMO#uE-f+_)o`^^i4Ho!6}uvluF?Vc6lvMm`*T(ct-aS;BtJfq z&__LTae9Z6R|XaX6FkB<;b~H#Q>e-_1~Y6#uza8$v=JA0aYtOdkkR6Ih~3~kn>t0kAr%2eF|wO|l;ejD4?TM@hv?YGap0Km83yd-0dDahCzYmYvx`(k4i}&D zQ9>RcHrU&;cy1Q;=cv!4oS=Sels!Byge};pIo+PUoJ)v@u5i-o7P}XgQvF`Ez7F^h z&#)e+5MLVHa3$uNt+$Ix9_Px3M+sJOPE#ry=ZaSw)3;Fp1x>*51YIsxr6uT6Y9q1z zsTZ(_TYRt>nh@BNiH_DBIOHb8M>bA3?+9aw*`xe!Nvot@mK%IR9&FwNDN#F(3 zmx1!;qYzzMC<9sJQDjvd+T|W;kRO95g-2U|rD3bhY^(ff*-n%n=_FPYUo0J)Fqj%x znd;UZPTf{DKvA*$A^%}u`Cy(aGWrUv*rN3g;HwSnJ1Jtf0&t_xH^9%%a1%w1IV%$L z_2yb}?1+M}O0|U^mJ@K#y>v*_Jg5VXJ;K&+mbEJA7DF19jiW9C?0w?uZ!sTPVkIUF z&J2zIc#Z9x8uW`yiNT_;iOPqmH4%`=7yNQLoY^dbyb@mk2GFT&rSv0<2Juo^WT;Pb zHFSl|Mfeyh$IIDc!*z?1Y38rvJZ!^{SA?T;dY?=CO_JR-nr%+Q=BRsaOdrKX(gHIYQK(pA-dmzd$WZ7@ELsNlV z0#T~2Z^g&NlNFmT`oA5u-q(0Bu7Q;>wL zulvVYM7%o_h+%36G8K566|Cs)6wtuvjZaOFB@`S+6>x5q#MFD$lI)On05Fl6WLr3` zW}~DvWK{9^Wc}!M@VvSoi2cg;BK%%X=LC$a{>dv4tzDRkOga_S|Bmo&NkDzzSN8d#qiOA!O{NdUqB?7=#!Xmn zmtdt?M=)?Y^;B=sM%mJg3ACf(uuqhI7q^e`Z29$GpOy2@NvShh9vJJ=QMNJNLZCzc zQ5wuoC6={4gZjv-)5k~77ha)rMEag&*Z_t3`gC_#`K)5?7Q@0n%|{Vj9(WmjhGE3f zR4{woNe;?XVHXo^nd4q}q)>Zv^MDInCshvP0eFzERI5v?Skt3OueZ+1v}b!hr6Ios zI|y2>Xi8PZqQ9AY2Bc7GMP>-$LviI$sv8fz>oqfX|BpmJ* zraj>G_6j(J^01!8CFZ2FSy5{%KLm=L2|Z^ zA2S@&cg@ImN5AdFGr~Fozw3CYJgk{Cke>`~}@iv`{ z3(@jU4Nyo?avg%FTf0PLBO``vF3WFjqEhHxy9_{PXjW^WAN%j>WZRj^@PhIq>5oo8 zx5aKyMdB?D=%Cp%JtEo;m6w5Jjpxt9BVu%% zV(t!QQ=YzV7w*`3p5kSUO%L(eRt9ce%5cB-`tsFB6r2p7Ms+sV1e6J(4Jb#9M_U~P z4wWWc4{eX~|7lGJ5$Od-HuUa9V7^J$BwH{aT-Ikg#31nxNH<=`Rk5#tYy^3vzKxpL z0%(W}PT$@o2!bw+@Zt0PGAMJQL{3w*GBSiGbdsyFZB##H)PT#HlLk{Unhz`3HdDyC zw_peyRCmm#926;+CF=>|`U|b-9=_q@TKh?|hvIKReg1QD4Q9!-FyE@&g&BB(+Q&Yy zJDp2K3}Beej3GqAyfM{k@5%0k1z#ya&yQjcc$dM6mfEd}o!P&|+q{lGDjPfO;ttC0 zR3Y&yNas#oVH6bhNTppEp*6I-MB^Z=A;|Q7a|&84aoVszN_hKgP~i^wz~{7SBzY3q zZ8n=%oE?-RH9x!I?ZQDck1)ad+YZgnhxny!phL1?&t`LcR;&)=MqKD^Qm2roGnc(c z_D5+!AH){#(3-W!V)KI!A%QP$k=b9aK~SCm7i708ckZZC#jgyI%(p_N3`Xn~eEUOE zoR3>7o1CM9Orxh4oWoXmW&SO)zv5ogDP+74IPKI*CD?>)(}7HFeEeq_wl@4H7MK}9 z-_rx_frYKAuKUxtY9JkY=1Bc;SZ%z#Qmf56{6}1ON6cA2*0*c_a(;maI}hV(c^DQJ zbepkg!yfEo+sz&bQ|Y#N-k$i^(vN5PvkU2qSLg<6bajDrDmwOAnPA+?bNoo#Ned8o zATc9393U)u%~Zh$weMxx+ZLQ^V89ku#+wr-Z%F;9!-koUA)^@#)ur*6%2SM>(BFQX zO{Ad=4~6{9U_@Sa_{iOuIJjf#lR^^Rq;S?cyiDrPp#LSZI9K*Z#a_3aRj+hqV$E>o zvm+7BVV;}}%rKgRSpu1Ncd67{<&HL`b;PFk+vrx^T}^xDZR5|yVszJ`%8@d%_GsmZ zp846~@wBEr(fwpLvlbcWTt1@aU+GLHbc;|`aUCUSzoXV?(#C+Rqwj5!ul&>-rfyM> ze_!aIV>l{+4DRJoUxyR-iie;t`%b%Jx8$2un z-teiAk!4*Xo|;J}cLQ8vg+2KumIJBzW$o4Wj<~>q1)7LZggC=do7$XeglEShXqjCa8J+NarY>_X)u8=Jshw zqIx7oaY#$sb6Fa<@Jf6o5jXJQk!sDd3C;XYZd|0(raZ4TZseHTwXs*Rg^%)8aJeDC zcs>LN@{HuPlg;22f=erY`crBD3P2@l-^SO5tn{d6I-Q{7kr` z%4^-&&hJB=;XFcrQ1OB!Z9{A(t_t5WyfNN1_Gb>9_> zjiK!Kvd_jhVFTf(_Ar7v9yM+Fp=8tga>(-35W;9V`4JR(&uRF!DAl9XxNXe*MD)2tThE3$ zX?zqc*`~f;C$XMv&Vf`Hg+|eh@JOxKN@XJ6rKP~In4}oiweoh@_s4MNgNrM1vlDF~ zrP#478d3=_*G)w5ld{h{b(c&3E492&imFTMhRcP@V>@s4=U{xg zp7tljMOM%Ps*nrCA`xwCK6tns$y7Xni(k0n+l}<*XhFJJHorF7ZlZggI|5k+7si*m zB6zKI^Lo0F4HbQ;Ebn2OloQY`yv8=KjBjjQUMRyAdx`I1_ugpRAY2>hxKbzfFQ)`* zSQX`hJsTt+$E0@E&d&v(eiI>KZJ(3_iMT7(2JwFmrQG$Ro z&jBM3mYqA^s;yu+YAklt%A+i%7P{tF4^5(frELmV6GYE>+zGatB*lZyD42GboW_}l z9oz*_)gU3LVbK^Q+MF(#MAgL-zOxG!RSW#?;v7|i&&tKAf&(hAx-Q&DXQy^-6O|MC zp+8=cY5h)-QMx6feta@_d02G~@J(y1lA=`!v$|fdW zJiH{YHz}4dbGe}S&b8+@@ZEguiB^P-fy!|L=mcT^crl_ATQ}98o&eL_0WbLcpgodtF{MMVe^f_& zN8spRh?Gzwjbz$Vb(d7)*&*E8=ed`jnpP57;{_W;PZUE6k8NjRj3(jR5I#0nu6O0o zM4s}twFbW1z8@-+F6Q16&MTFl9vnxti&(#0I%0TeN!fe#o}o*${Jg+HM=yo^vt`~WY5XnpG;-0vED)-&y#Q0&ntV* zdd(wVyT)A*K80nMU`ctKh(V@Z7-XRjciQdE z9%z>&W|#+b#yiX>oZB!d2c%Q0~)ajpxI!pvx7}h+s{UxVULT1(y5X zAOnw-!K`KlWY~I0_$e+rSO)stcGHH$WP=R&0_QRP@yYg|+P0cmBR_U-l0w!xNs(Qs zhi5OPbbkm=WT#>KJ^oF9RGKdM6*|f6OO4H_HOBXguC0f{zRig7W5?8nAuGX`WHjlm z)Is{!-AvFRCJV2$$np7NPKixoooox%^z27sN9koQtFW_p&(3OpBDmKwS)|%8zGD%D z#w`M=Xd+qTJw4mL5JATY?Ky$nP|b7qPe!^L`pq9E!|je$gtAHI$CvN#z!^Pi6-sGz z9U|_OWloiFw-Uo!CpzvE+fahHYs;nPcID)BfASDKixau-c=%;-0s@O%WVBLn2FiP$ zPew)!7+~f7OykK#p&KX}HfD(Le=816AE_@qDRT3>#n11Y`U_umE;jYh9g19Q;V{=^ zDNL>1AI5Gv^{=oSs4%#XXS>&U^bb8exRbZU}z@d$=TrgrH|$uwnU0td!s3vn*3zlsnt#cG(867f3?W%hR4L^SwgP!g(S+4?et z*XSxvv3mP*==!|ltZ7Ja6T~`!RR~Q~c_i)0ozLxl_$N;Jde2nO0Wu_7=-zg13P!=Fz^C$6vVF8sryZj?*7DIyiD)}uhTw3uz?Bdy=M*5bf;& zf<>y|Dmt(%sQCpY^zy7eUn&oVzqxS-o|4t2%<<~)Iy*4GHiA$OnxlmLjYbx6;ITtL z`sfi03$X4M?BzuS3ZJw!uDr)jwDcwJRul5d#UmvM98BKuQm+W&Js5t&@w)^L! zhIEb6I6uIMoXLm8)Q4!JtUsc@pdIOeodfRtgS0Cx7Q{k>5~ti0So)PlE~Y!!k-@>9 z_(5bn{SJYOq(yqcdE$OcLJLU zi87?=?y$%6&L?xegZjfvI6aq*3Ud!l;_3G09)W%>f3+Ru^cS_sxAJ%!HcvE-yre7 z0pmAz`(FS?g>P_Z{7nM?4gMVejr#uoi0@yv+5f0*`=^Hbe~S26{~PN4qlThq=lJjC ze=dX9fLP5mD*KId;W)LIg4C4T^yu;2D?LA$1HBoMG<5E$COegXk)lK<$UE)o2J z{I`*{0wt%1IGWmQqu*XXPF#KGF}HLWm$WP^JHoR>q~oP~*LzS&&h!wC_rM&WK$N;& z3h5@X5nQMcKqRb2e>&1{4@`#h%K{F-82tnA&I_df{^a*#9mf3$&j@N~56lh@21b7j ztbl;zN?u)nhT2_RkTcQW+xsdzrb`H<`1Ez(Zp0Z#djV{#%cI5Lh=9Zu^Xndb*Eh2m z8U*;S%1=M=WpuC@1?4$qxmZy8(GoM@eZI$^5D~Q6N8ml5T zt&E<>q%Wgx7&{R4H1^cBfQ(FCJ)P>Wv`OAKu=l3RZy}cEQ&%0{XVfbIL9du;co8JD zXXHU)4(?T94cRKX=X0r&Vp)?Nh@In8pUu_(y=V$;|NhAj^J^8W5CfU)5aF@$638(I z9HK1@^uB<^@5Z-?@p_B=wu^t)H@y1`-TmvU|3fwMbB*-#t7Ga5hW59r5Sa_CY2v#W(&l(>njGLPg=Ku6X;RKGDr<;Q!L2KGC!Mtl|ML9vKrOIj7Y% z+5vKS?}Ktl0`KEHw9qR~MeFYEe>B$dU28a>;FoFC^PCQ^^o>UC9vtbp0dyj&=rQ5E zb%AYvguoivepcP<=D((Mw*{96M^rM8eO)(y;uSyEf=we{#$ecd&4kqkzWCyO2E-;f z`2lmUu)k~qFE_ih>t1fc5(4u?O??TDbosM?xvQ}HbNcH8btHFkuzy{C^7?#nW4*&h zM0BJEzUW%!L9^7b`L5J|F}@$LI@&vU{CK_^`{EL_(IZ1O%Z$r{ zsUVxgeG6aSK| z*7#TgkIO8i#kRk+)5sLFF{ZCI4t7VitRroF^)Jt*f)d5)=K`Upbk2 zd#;?!b^4dt*25R%X>Gykj)^!+L#Q?Tvz_bGJ4A#nvhxisru%3(cm$_R!Y8ZPI-`NV zbFisMp5*w`UjD5(T-uANzX-QlZUwjNMYMiqZvw=C_r`HF<`QVBR8qWWSKid9ocy{> z%BZRU=LsmhuQ);ytJ)aiWo=3q(o4e}L@W14g+(ON6C|wxO3n?CVrQD%uh2IHNPCC!W z9dOb>@Tgd)IqpN*k6?Q#d30FkPMEw*dC`&iriqrA2&U(td?RS811K5~`m9QXbcBMR zhub-!x-bI+Op(Vzs^#f0$cPv^T_e6~6X-@|8S9$$BCbO}vw#4fBw`yn(z7k~G^U#F z=!Cvmus^}0KKzSPC^HlcI0}8@24X=H?O$-T?bpXb<$RpG6+$8yVAbeuK<+nlc1STf zsaSKQV3_Zbo+^-xd%i%rCI}>CMmS_-R*KSqACX2E7zM8q-IaQHZ;%~bl6jun-daNQ zUFG14g9S&z@yLr*&y+K#DVTbrU99{r1a>EMym)r+lOGOLI_aMAe9W}5^a-N;Q8^V+ z7=`RfeMvf>!-Jc3SPl0mO&Ip!j4`+=8~;Ihb6+a+$osgKtBjt{Mt$LD_k8Bt%7oujubQ(eUIie<>4Fs0A4H+SK9c$6ob1GoS z*npRiOK8L_%~ys6hF1-hmypB3n!@3pBQp=q+jy_B>+t++h9DUnNG2x*PL$S z-hx6?8=Q%vKbiWrD_RYspcH@kUm@aMt3R>0+w`_X(P|*tth(3+-w$tl)K>2>4Nla3 zf|-UN^P#kH-5?$zx8`1Hk<9L{5T|A1g!Ug%`O>Fvmq)j6qM*a@PDyufA+8v?cX|=U z#i-8;4?6_?DXc%0+-ItrEa%!S(4!qRy8zpEEj&Q8)9P55 z@H8N223}vveJ(f@3AE|3bRnziWG@sw?}PYcFZq&m^UZpcBH+r`#{dS^`kjhR;pHP_ zRPFEk&p#hHLPl)$y|yaZI9$vuybCOlhPgf2@XOR3%&^VYK2(89kE zN@sl-N4}pHM{s463lMvqiMx>K)cs2WG~vG;H%|}}pBXPK;or63DL^rj^HcYV&F_js zP*>ra_EQyv%Mrgay*_LRK4VKUUwM4uwm%}goo5*r*4SQ=OX&%40!_~sWzLLi-%Z9sL`SiXDKdZln;MrDDdydd@)>4?lW zZ6vtBxK%ATEZT$dAf?2R?IyF1z2&X>nHJ&Q`7om&_^|EG#mp316s~=0Hhp{@xYRYz zeXV%g;`x@w0ub8D^znvImGCcN;?J5W(~DYnVdkceBUv7*JO7|i>E6Glx})ul^#wNC zC5KbATiN%aGvVfkB|fa!Tvr}&7sryO9OPiNW3OaNz*&g4co4qur&>v;qeg58V$B=m zU_5Zd_NR9~3guB>IkUif6E|@}wS8hiu>Fb;;frc?5;FpBN{G4R` zg+PoiF5G_Xw(vV1rQ{O~cm}7#d)#%Yb#F7#r;uF`zFV({k2vQ8)b~fa8SokVyc5O_ zW`B+0n$rB4Gv)(rg`Q2qNxU%25KC5+2nGLhDbq)uPl0&q@>Sdz7&5NJjk&es>&OS*O#VJWbpg$4hKh_B;_0C}|Mx@kQ7}reSd0 zXClC-3URmA{`bFqFHyx9jbBf^7=}UGDuUyBi_W`R{9g&|e`db5p_G@c(l%>R9@b|X zai(@-K^V=_CXR1iy^tPcY{GW)`c%g+cvk&1jDc>&zM&ESzTB>a0(^+jKlfDE z{PmF5l%0;vE-2^?D4rM0ViLYoKX!R!OnkqfnXuk#G?4X~@SR`eB_TkZW-m>~dmBcS zR+&Ovg@|oLP_n-@FJN`zhK@Dvug>GY`#J>^jxDupzPb^T@9SkLzkF;L7G|3yRR#t3iStM7k@-!pS4DZy?XF z`NMtOvh5JPU8C~gJY|dWNqWOJqsrBuWxY{rnIay~S18hk$Iyj0 z;99^4J0trD1WgY$0R0Q=l8BOKndV~{p+r)#V3kxbPLCz9D(f^3ou|6Ihk6i*UOY1% z-H_B-gCPi(q|4dnw6^>Ghf?qJsZ7EfMvRQ&TNEvyjR#nm40R-f<8cwUa2yToNzY4^(S^4dT@Ba0Ng(+Fn^ za!R?Q?3I>!Uc1>)Ck5cv@-Ys57H=q9G_+s|UTK6Ws}zI^Di27y%-o1GeL~`>_l}sY z1;yME727!;hNDT2H~6R6MD(Mv7{d;F8R#IKbLSyddhhzM_l@0IS>dCz-Az^@Qg^bo z%=xReAC}0{UwY_P7)+cciEh@t@LxK0E;W=x6nC&Z zn+E15g#TpI?-Y)VQs;|lzw3RT&gq1OP;O%>1bfK65qO8Nh+h)J0c&M8^}nbNgH8I z3~$M4`lSFD{b;0kQqD&IwFepfzUB+=Y*%=l_;u4DcvhGPw6^b3dZpbjLf#3wX~P-y zhNI&Sw=}wisbvtFX6`DG2UcdiBhsc85fPkT)=dUbcoH>ELpUvxCj+%uI??*YXF3F!j^~U&%arF z8Cnxomof5GZ#fRvG1+qHx8vGUA7C2ziEUMV(Y^{SueWNvOUmTLqaaY{K%G+0q%p2gM=^cQ zsIl+p4cPEM>>f9db1&QJOc`Fy0vl|1B|6)ti4%~1zwIJ^b=C+Mbet-9(`#Dil3E^F zv%wi!7=&RgzcjNb(OD9pPdaOYdbaYGsz))jTeVDVQ>u9d8zJq{5p{(X5myiIT0lKF zuf{F%hN>gdnHp;s#!V*)FOu$wrYLQW)x4PliLgxs*L?Y_{Cd`xqGRUHBThjDQe~O_ z@e<%kH%o*e+P<{JfJCJ5*}o`@j11!o8fP{Ny)fO1ZPg(=ec4>`yD^Ek>gg)~Z`XsA zE+SDHsKh%>`AzVz9EuG!j}M>lXV&;|@L<5G$D-#^8a2a2;C;XDIC{k+_Jh9XSk^rZ zWp41fG`_UeQA($Y)@I7!!_wAuxPlo4E4L;Jm||NwzebLW_k3g_*BK#sd`PWT3(f9) zK(c$lV;V)V6n~`jlp?mWdc$vEm;)3|^`J4tG<+K>hS0|sym@C280ADL_{x%QEM6=y z9PD?H-TCPBZQGHqToSEOYoCP?KawnqO9Vac%R)}PIYW7t1c<(SU~N%bQV#p&CFhD? zhJU!8d_<$<50lK0YAIQ3A~jUBfc?$(N5l286#J|rGfPAbEVUE@lCX(Jbq_rctIqF9 z<-o?%)BxvsF~hD>F0edS_4GaY6Ch`kpa{d{kkuSOOh0zW(H`dndKM2pR@9Fe7H>5Q zk9?`J91jDiY(awFZs+f*c8>P8kyN+jHTKkK<~MzQ!S-~!a^$68_l;Up=R=7o)5X|_ z|7MYLOlkIYAWu*}wSaF@qPr5?eS6m=VO45Au&Ut~qwm02Vk%i4gn3bCF|}>NoO-8O z{7btY%7e!sZyMWlxwQ21#wu{np(G$i_THurLTGWxZLGrM=~`f?CUKU=&uU-j^bJQ% zu={Jf%xQw$bf-vLdL_RuYt%b_7cSEjzIfjUoB^-ym^-eC$ex1Sl zTF82r8&zdndKX+T09WCJp<#<37%p`motU-;xw)l!TTx*=u6?g*Kw~^>;&a}Tg%Oir zM>cGpeoikd!X zIxb59q14*5nH%Mb_%`f_RSnaSHeRfQYkRXtOP0iWvfq9lKO1z4$5-hUfFP&&7h}4* zp7t9DHKt#pVGv}*!%9W3y+A9Z-*V_bjqB!H620L6Fk?MUyX2g1Yqz025nw1%{QrGWdz4~jWHEC02vU*nVK*s+LcwkobO-N`GuM1WKk z;%En`eHzT}YntEh*xC<6tl1toHTcdfi=sf^3B-KIO1uz)lg0R4C8*zi(0pxwE7T9j zfXHYlR7-=%`o%>%iTZ>LEE$gqD@^~+WV6REX6Pi;TbuyLI+ttohfL_k`x0MzruSuU z7D0_VdAjU2Sw-#|8bMywozZ~ZBV5w$sCB~BBZ4f^jfL5Uov#^#!1ls&En#o6C#yn$ zi5nbX^^yW~+GM>kLI>1CJ?bFy0Evg{dQa})CWTSWOC3-p8*@a-q1RATVz^MUI(RQU zmyxQV)pTzPQ#0Z38cRE6LCdEmiw4;pfD`Hi^<;R>5!=DWLJ&t zNVoMhu1h#fdN#UF0we6hX)tPiP;k}9g1BFATsY_{7z+*2nvvB3SZ%KF6X0n# zTBo8$Jc_}ez>zKe!i{LP&bRS8EEHKxgk7$UM6Bl4ReCf>RxKnLOHnH&7$Us6luNfu1vAVUOEctQ=2UKI=Soni9!=tSgYCY^4huRW| z392GrPvg_E(HxY{X85f|HnH9&UE4@gRjuueGJ!L%g@MQqGkk3Sw1!YX0^gHZ%np@b z__5LARrZKAEGYT8Q@1P~`nFQlocVpXI&X0`G*@zJs!8sx z>+4OeTT*_yBQglN_1+zd`i{M?ir_6b?zWm(9YgvT^OD&_6T{r83#MB|>(hs#uo=|B zAYwf%_!N$Z84f!23uZ2@do?QSUULq(w=I+wr;)7-1^Dq{PY3?Nx{qSYWw6_B$ay2D%{LG(ejo_WJ&|<)nEkgKFc53 zlFk$w@`Z;_V>c>z9ljJE!dsMvtKQjFJ$@50K1&i6lOM{hd?tq)lKo+7w4f6jKW4|U zWiKB}@ziaM616cTn}xF$j}^-)oQS?~+41z`1<3)SFz?9Ow-;6~(ul#(N8nGc zo>%ypmHL8?d2naL^4zU^!s3v;CBz4tvc!$^wrI9auVs2NL~P>KYKS?L>tJ$9`-FSv zah&MjIuz0GuP=D%9drI_BlSNByQgMRq5w_P+qP}nwr$(CZQHhO`)r?W+qTU<)ipge zHPIcNkvHoiBk~7)>v?@`OKT41RU3Yw%KaBvDu*^326bfg=q5Mhc=^}llr6B@v0WL0 zdx6%Yl*E_E;$_=M& z;t*Hw4ea=^{+1U3KlawP0tU%!D&I1<*ja6-;fYUwYkb2K$3lXcD5fz%ND2rq7~|3k ztS7)ktA$<^ceY9;)(2?7x@kLc2_5(b_Z2`nKIpz%aCHu30OxjREAn}XlYCRCf+?xF zsDg}bUuXP{B0O1s1(m950?Qmic^dBel3@})d5sT(#!-dZsUBCEYPt27Z;6mXp=iJT zV7^6I(s$r*^^N9+c6%pnlgU{p~3j#C!(Yb{Fcvgx%>I-{w+@B@Gy zQCE@GZRp4@F9C{!J^<4V;*J@6azj+w5akg~wQiY+QJbBGlG^Q*=Y+KN0rv1e0Qs@s2w^dPmMx@j^0Iiee{!9=5xIjIF=gjPT zt#qKb=6J!RJVm~;>{V2SclWoYNsPo$K8q~uC;JiBvMb_o8=VH${9?g9Ef5^=c=Km6 z6^b)9yu*iT{ixFQzFX@OwZ3j9#T^lecEC&GWar^+>jruZvDLLd2D9+M9Mo#-Ut@wT z=*iH&`1bT%_=H}J8*4dpD)t@H)kr+j@SXE?eK?+5YWPxPL10u;4-qAu_A%zSInamXz3gS z@>GS0?{3lLlq;#2sC~L)omty}%wxRur0~xqMe38LCZkmg|6O{#Q-tvI<^Bqv84EyL9|*xX>wF+ zb)}Hnk1%~rqD1n6s_<&gLdU{;^}WN9EH7Z!YfF!i^cpmJt(Kz5gXgg*^R#L7MA}k` z)GcLrQzmQYbCRvd&Nv<%nS~noI7Ax6N&t@*`kVOML0!j}vz#pNv!-0RYJK)XSfSF+ z;plVMqF5mW#2wbA`~`jpEGcD4vrwWJ4RiQVQSKJ^+bljaBCpbUtuz{}zLQOV(l-2en=Vi8&W%uSN4<$QhRz;4$P#orDej(DHGEoFtTMjNH0o?CwqErbPeJLKn&cKR zn$+>7o#n>4p5wUeYyB*EzvauQ*z>kEjkcAH%vwCkyBC}}2I2^g_T8dYRH;hn*_5Z* zoUr+NI@jOEr?gYVn^%&;z?B#B){&yqIXL^`MolBml;3R2ucdg%gHlkZW|V z6T(o)Kb4$vQTHI4cCw#^_=~W>=LR!1$JqFKx5$n9v?g4TZU)d+mOIEpxf~zL6zLL? z(*^W(e|eIk=rE-BiUy*AqEXUd;f^cn|LH)fQ#(=svGiSeXSZm4>W0N@!4e^(|AO*y zs!4x^AW^35UbO{vm6QOKo-99Ato%Y%p3R9giPf#Ol_gHTJB^ZVGt{GtSm(T!B zSJg+pRgI=vo42@KUBy z&8m3H8(WH1xN<@gk-gEeBru0gOSX5o7;-8j7VR0g0-<7diAn0phV>h}zbkIvj!|IS z7*poI6G4Ea;c=;D=22L|%1q*MRGI%i$nC`ubGY$g$S3dk8s4;SGcMl5tGbZCw}uoL zY;#n!*?=`GW;~ucN{unKH>l7r#2qOB`9;|0l1ble-QLg_z25CeB6nXzja76$6-Fhl z*0xB2O`?u`9>|5u&W*9}*6K(l!kDIsmKRrRi%TSY;LW6FQI{CBQyF8WHCp7l0q$hp zM1F)Y5h@svwAGqyZd1uH2zS-Z$?}+SH6c3DR%U9ZH&W?i4ns#jO%zX|JYci0h~S+~ zk-6fSQ-w<&*K$pUbMh@%8k#|`(|b+q;`zIz(_wScceOcR|D=s`N}89()UYBHXo+5P z+%pSkuRe*0u(%mp^)S-v>w<(LQ>v#5mg#$0iMH0UZ6~?}f6-OYV8c!Rz*=FZ(){`H zJEQz%+YK4JBq#!uzQ{|M6JU#6W`cweHU_nAAf7zIju*d#ZM4oS%mbJ~-YC0(H&XgW zL}SQN2k83_VQ|Dfr5mjUUmw?Lbi_{C!@AizD3Hy@HVJ%sueK_S6d@ZpPF8o3s4S4W zcDh`1XOce*`EobafImVwH`9hSnTUtur?@Q^CA8Cry7cGC6m=>!-)zkBTi+*2=h+o@ z4u&~^WiwwSb2@Kw@rlFpe$FDyC|UYS9@Gxv$v4=9i81%>b9q|Dv42lXSluWE1Z}<`%P{|?{_XZQ|XQ!_cjYVP< z&OBQzRERPLzIaDtv%5WaGLX!h_jhFtG<}is9l%|m z-(L+#C05yMPo>0t$RqJi9x5d%>aF|dN%E!Qt)6X%z3WkMn!8*W{yqbeipH{&1-kr0 zDx8Rl9XA$oWtJsjO6i31~(1pQWT`q><^o<)l^|(zOy78sb?p`86YXEthMF+K6(oIj6=+iXVwhHcM3|< zuT}ABqyrH894AuiZNaH7SqMcAh1OU)!{iS}m1KATLr~?Yg zWc%Io+F@i=u`(W1V+b#ubu2Ey{dDLpcH-}KTSD;>aURo|r2R;(BCnkmns?Kyvg5AA zB3F<@PN_w%$jR!=eLhEF@UsPUgC;pyfkODjJm=FxB`A^J)ab`qtD$%yvSzkx@>la> zHl}Yxl@zLq=DSWAG@84-MBw%icni3IR`Cd`B8v&>HwSVcrzP!`u6W@Q#n^knEa_fl zSC2M8O%;`4SeKvXPX5A6y8O@$3w(!C!>6$7R8@?Rt*YpNT)qC;4T;3_U}E{yyX~5= znpN7-|L~^Gx)D4dZmYDR8YBYZ;?!YNm>Maw{OZlRVY%^AysnJ$>BGpsoiO4`lC0CGgIpZUG zJas_p3Ww62;M|qi1;*iN_N7V}l6>XvMWbhb{Wk##xg80KWxAtW1Ouc2&oBD~#&FS< zL_sLi+C-U6MtrEgtN{~NEoH>(#6wzAuWV#=*h*e}qGu-|Vir^tA6pt~aFT{D3d8+N zDyE{&AVU)}j1`d<#s1o5d~}dH4Ngz}by|@lf<-d;X4thVnY!gA)}9a7+o_$O!21c# zJ~|Iuva83J({u4eed5zjQ|?BK7YD57&*P*rp(zkiNWP#DDCI@hWHh>jSF^XK=K>ZNnztBtLv8=3 z1;>QhMaX-y+|P;XMhp5LK9;j{18+Xa&r%KaXe5T6Idq?tiLY$0rFcAuuYBo44(EwK z(I^OBkrxFH+U>+R0mo2xd^vA@XZmAUNP!4~uYHNs%O_LE|7RL+KEDVit|e@R9)?)q z>Vui)JU?m1{m)~WK@bDt!Zxq{x5oR6X4X(%-#im~K(9vXXo&A6yF6GI@5_PuChnL| z#eGfX&ZcPsK5%8qZmcUf_W&R^CH1#?64A}wBZv2fxV8BXJ99~VTxta1a zO2wE*R;}Ww=(l!WPHWOIOFyHWxqFo5N}!ud6I{)kXm1L-2%|+KLBDU>&;7}2+k_5 zXxxx~--pU$Nf1A00>YggA%H836(}G08x_z@22En%qiW-V) zPT(qgj~hRK@*1Dr7g~iqVRUK&^LM$Jy)PY{M#UbM=r-|e4>VgG%B3;=roSuA{UpuQ zz;J}JPgUxyWMm6wsQ8IGPJU$CYJw)(eS^vIxX|RV)Oq@7jv@x%Lda6{^w|zjq{%~^j{{?|2+Pe zNyPAPo%??|K4t+&9+)8P}Fjx&7TX;T|Qs0ocd4s*Z0P;j@4mS$7$BF z4HJSeNJo`%*=@9%*5){ z;NFVFYD{*4p(zvsD>EPiGcX26N5?-2fFd|Jy*afsG`fH(Rufn%EG#X*l<#9823ODc zS^ar*YG^M25B>NzW^HhBZe}la{)9jExT;fA0Cj0*0M69f%mEQWMQJHLAOT83dawkB zX7(pGCeR8l%x$eqfa6-38SC8{ihxtNIe=}yd;l^ySF*J~l+&R<^l1su0}y)$7G_4z ztN(=L;i-FkRN8_0sr8wOk>gwTfeBE9eH)Y8k9P1nH~?yIWNr3Ef222RcK0`Rp|}}1 zf7+{`i=QA$u8xkb3=VD1E}$PVRTR`;_q@6^vAW-3x5kfZFpiAhRu)$V$ItQ-|MQ>0 zujMW*&Gl{oooOE3-$+v%Fosqq#}?Ly5A09+jN<0y{sjl;#-{eSSr`kz88bAofAw55 zIy!&DAEV#J+}|4HyM7h>qce-kXL|c^f6eb%oEjb7DVynY!;FmIQ5MHC#`nUirV&EgjjlwLOX3yMS-6ae!aCOkytq{oY^% z&~N7RYgnAWzdkWnH8#CJ@_N6^T0>X6=Rf!ZB|5P`ZKC5l2lT8lPcgq}TAdNvo>`ex zTAf)MKvS_czb{w%PORR_%;wtE-p&16Kh?DWTx4RSzeUQeEliD#zu_Ql{?$`gvcJ}# z{^uX+KQKy8P)b5YEWg>HpY*~b{lR-x7sqex?Z1k#Ilp18_z9Tr;nf>=#e6Y+rg5Lui`MT`_m2Wu9}Y$ULup0)+gDWx zv+*HT4G^BuJ=wu03>kv`IDD5n#4D#>l5XHK*}00}4(?)-n*CS6K>l^vBbXN-nN$hG zy%Y$sPk#~r?2;5y0``Jef1dlhFfa#f(8L@?XbL zEH6*ay%(+@L4Qr>4wag5mg^S7af(h4VZZv0#3np3{=icyuaH6qjPS-cG zI!FXgt-R?SdBJB^`!c1mUqP7srR_!&y3wfgY4YCvWchya%iZadVQe#a69pW4KP*Jc zY|^EuKVhW(+b3NBg=z8Q@ebs_!gBp6A3}QgF`tH}m79UQ&A;5isXKVS{yl%?UCHyd z9#C$r7Q^f04%j;QMET}0Yk?gy@-`2KaxS?i4h#k@wyAv1C3{8&Am8o=lNRT>C~er1 zfh!Soe_G7#9XZ)dh`zy;3!cU8#9rcX`$HOR`Pe zLQ8NaO{N%4deF7-a^A?uz|JC6uq(UT_$8g;P>}yz7FgD=P~|X05Ft$21AM39sxc{h^W`uSE1Fx*Dy+-gBpiP5w)Kk?Ei+a!IJdl_ z?CCoBKkXsx@R5&KYG0ZLR9k@PXf9>3)6D&3>-Yr($%-o;E&Pp+YQ`FVYz^}b>t!Y% z^_Y$TCzdbRvSU(F>xTGu4~ZPx?6h}zrwQ@g1TP2NUnlzDbhR#(%iS&7QS38Te>()1 zer1qwy1yI2dCF7ufOE1JzL=n^F{|j&^Hb8tLQJm{hg_Yf=+Frq?iY$QCmR5$?QVvQ%Dw(*~ZY?4Q`hxMo5q|oD} zesk8r=~C+Y#06;?5sMA!S)1|Xy35h8Awi5IA@uued24zy%mvXC;ALaj%Lb@@QT}$8 z&zd7FBnkAlR3Y+ppjQ*EliTCm?_4f^?N@syw=x8@i;tgn!m?`bHK{+HF4yO5(GBk$ z5^%Z<#KD^fl=!I|?DL#%V{Vf{0IlW8TUmJ4x)`Cu6C&s{PU?Op3|urWs2L>oK@!Ta zEgn7rfbKr%sz$bWO`0BLQbrdey$BaKx_KfiMNh@m(xKJcYPG&I{u2GNs)Ztp3fF(pIM8%wqv8RmC|l# zWYwNq1fwL$thrJr-Ek*7=?ZjP0w| z4_-(#&pugb^sJ50Q_*mJbx_qMLg!CfCkpEv1(E)Ykg8aT{-Z-g$|Wj(weKSRhl;}$ z1M+!G0M#Vg_DwH6%5rg^H8`o8suTDn11yo^aEn z|H)C4UK6bG9-E%?09GeaMrwdN{tjJd*J^rrX(*7jUG}!WQ&F0|zcnhrl&orUotN&w zA$gD>SCBO1j&3DFcAbc$`QW&04H*vn!s3LgqA}yS0_>QQf`~e*Y;no`T6TFw&^h~5?!g4^ z6fl#jFSSC+kmL93nxbs1Vn^+Z@l1Sh=t+E-`sSAdxobOg zR4vu|i3DsJqnAjAC~FR- zMN_C-*opWLdAm|pgiP1Sx@JC?tVsg5@uP?A?40$EbXe|tg{ z_+n?Oa2yPx7-GY4feY6pLxjyNGmVhZ0z^KqVCu(nMe<};t*h2BDYv0Z#1e>HCPXJ_ ze@}Sx+0lO?-^lgl1CfUsK}u`cufW1oM0uO=XD;;CKi`K^p-`(Bgv&+FJ?_(04n1L6 zlp-bQcYt3()3d8O(otIPg5$VlOehzXPbVxwM(w2>_ST>`tmRzFS7Sq_E^uUM_cc(DQpe*DeaN0ccq~_w6#$FTH-Y z+UIg1goMTU{VK{P$8@*H-9>=7tE+_|v_$#J!-g^R;(n%%s|rN#VZe9mDEJNr$JUgH z4Nr1&m3wakLFu&GP#Vf(B?8z?fFv>f>u#QEfOIKXZqyr9$jgi*3NUD|SH5d&g4UvV ziKw*0^7QoXA>d_hJYNLJd;}C0kf5BdVDmvtLty=3ovEa>Va6S-)rX}zyUf1dX@{r? z^$vPXW$Fiy-KT}gcj_D2wQx?n|IWtv`TvK{w z0DGt)?>$9CQBgx|&)L0lrh0gc(~=7Q#@>tZuu)s3xWfngAnbpmZGb5jU)?MI{7&YD zk1_q!6{~@*f_34z*w5M%Op$op+Mg+nB=!^+5MOCC8>N_PJd7zYad&Myu$&}&HA`yk zb$6H(wJ}?^UfOybi@_u9v+%&+ybWBKqp+22m!AP6CD`)l6&OW2-4j{SzVj$Sf=(S# z!;oHq{<#``aVZe|9TYXV{exA)YZso9&4@J&L3o7zNaqYsFmtE03Bz0yUXnAR;n`WA zO9gXxl^2YnAgjt!PoQfh=Z=<&Apca@$fbxwD34rKOc&`l;R?e9W!rD(Fwf|7vH4;Fn=%H$1aeis z(L4#=&S@igsg-ZrVoFY9$b=bH4Pje07zdN`Qv5z?^u@(*8GJ)05_n={I zKVYC^bR*s439P?S8qAGkFET|E^c60n_@3<4#*Lj0yyLn-uuX%g8_2^U;etaOQDC-1 z_zkm;8hkeL&3sqFu@($9c&94ZA;`g0Et1?oP%|CZ7L}SKQ0th`9r|K}TVPxzvwM+3 zJ)PXxR@4O0_R`=NAmt#|Q>X5KEy(fXl}sTOf{uCm*#?-twWC*vsyJ5?8=AKQ;@Yy3 zX_tkxy-Rs~Li6KTzbTHSRu_#W?@4Owt^H0z9mUg@)rvSnt}L}DU9M2`ZhpO(H#wPST1}# zjKB~hSNM~)N8osd8{|m#h8jb)FTapV^Btzf1nfDsKMLoG(zEutx{>h#m$b_^)z3B3 zEUv&)9L7dcS2KJ5t!DDJaJXIkSTvaKuY#qWW-8WoRMv^dj(EkX7|?836g+rQ!1uA0 zYa*h*rBJiU2y0;7#T(l|=I=3R84>U#c5uw)`w< z_43_F7JdrY9yKS+>R#A4*0SywaT3DLN@ruJG*{~Zf4fEk4RdT9N^0DYhCq5&&l*OGkQc-plW@a#OTF$Qb zFSR4Fr>O^?Xm*qh-{bj+2s7B+^7SoDH^4DW&|M)sU-Vo3{)V)geIeU}(CA8}r zg(458T*@HBlGp%eth^MmdtZ>a&9%9Yvd<(QElBCm7<#=V`im%q`-0wpqhjbnwIA=f zqDQ4T61Qo*jM=u;g3q#PSgpS#rvi2^G0VQHHE>1rTtl*t*5>M>aV_cgH5%lKr7*#< z3@XKo+Y`P87a(6z7zq9i)K;RxJNO8g(qPoxs6PVF`?PpaV?-j?R1Um^VBLeNg=9jC zGS6o4gFKC(6*JbCPEE&dd4q1=4ty>X;~r~YWY#p9>jCdVYHV39(QEw15jfYv|Dhr# z+Ag^A$qpEqiPXnZ$h~r28{^-E@s>QZlPX~OCq8`~f*;$#v@`4*&Ngc5JW}IWcQlj) z9R4gx#;<%eOZU4oqi2o9D_jQUO6(-dyh8LB%6;x1nzqjw#QQ=3D+e}48K8MjfNs!u zqbct=@HZ1;<0r{aSt%jIJ}HC0($K)8!TLfa?F^Q!OI}7pO%$5Z4ENvlnRxNE;Q?NM zo<-_KYjcloQ+?F}i&7&S&cAbeIl_8on{&QFr*-e48djSKRI0PM$2FgoGwCeENxKXj|&q14kn?`i_%PYVgbo!{@Da;E+3; zaIm&(?dd`2v5oU)i|tI*CmikGTV*^L)Ps4$^On_Z++8Tbhw>_E?X$b&C+ulkJ9Z(_ zCk6ZwAhakM(@tDRk0R}0qql|eUJ{tp9arOy@_3+?Tx~s;G794>aLR4*bNiBg!Y{rk z>?~I5-!>M1Hip4^sWXlNBX64qYi2F4qYj1(@fn?sI-c%+#DNRgP|6=GCANm#oDE zXt`b7e0zhhx4678g#_@6{ju}ze)CAzYyW8wR0DkN?XK#VmXNw2V>y16Ti9Hu18PWj zDrs=V1s?aX=_Ki5gThGq_E=<)Z^A(wBfv%cSEHT-xp@-MZzS<6C}HO`Ri5(4<* z4#ljv{i*XP=un5F8zQcl+4lJgb13_ncxaO!kQLBeQE zv25Od>ui~H!!7yp0ePbAbV8j70KCNbp{zQrgjWxP>xIte@uy$B1~ksh(ZRjm5ZGpG zi7)sBDFX>xuuRL2AMiB5#1uX5_64O;QT01%O!H$4E53H?{*;+o;`B;W2M4B zik%)8Cvr_q2;X%#&pqa#3a4=NL&MoUVCBg-E`NkWVQx@xE?*d;eX(r)l%t5#8TB+% zz^i0PU(;3o+Q(4u+rVS{$23W1cz?M|zsnLu*Lh=PCn!XN+CRAPmaGSKbM!126h!vm zddhx(WoE-nrbG%=AeBpkp`HW%X_f-(*rPa;{y0d!w+>tx6+eA$U57k)c*@ySwE}-N zrp=H9ia!btvl+o4OBWPplaB*g|I?#dOvNzE1kxW?V~fNPz4bKklDPL!<8ow|5)gSf z$`}QH_O@$LNZvF6L(Pme;@5xP@&{pNZ_HTb=C_4YFudr-xE8hLvPy-HcPb~YO;R9f z6=^iMvao^yGYFiLm_JFlJ8%3?^88f}jY(r& z`7HIH#(kX{W(i8>RX%3GgV|n<3^S@NQt~-XS#>jx?cBKu2YiaJ$PUmIE4z$SRv%SL zX#NdY5szD8Q6%RQ^&IXW=Cw|%r;kpsA5B#IG@fTNYWMBXh!sKf-%k$0eo>(Olw7>Ns1gnFVKC^@8Bh)2>0#|eWmfZ!~y^>dI;b8~v zLrYjWUKG;l_KltGWv zDi|D!QOJzy0wU^>ntFu?#l0>*-s$MoCbeub@{p5|u#TBbpQh@piGXLOgUpaS(ZIz6 z%<>U!5v^n2?}V;SYo3kSn>V|GO91nEs7}k_?QP7E^Ot-Dc3BeHHpd0fqa`>dR}hJf zE^Xl&Y3Iv4*`1Eo#$*v3JDUVmjeZ{vP~rJ_y|#=g3<;yli)GLz3seNhEBTheoJA=j z*BHVdAC2P5_oLEQa_LZb0pbeuSI*Qw6k|yZZL_ewTojZ6e~14g!`SVG_*kb6Md09T zAQDdaipE8S z3twk&7jZ$8T8V$kBk-Hj@O_CO$x0r<^@%6=#oMprU;GP@su5!}F@0!_mr|`XbI0K< zk4lEm)Ru31E9zFVGW@$WWl8D^TdA3ECJLfD1&R_zU-;v<`_fmHUxw##aI6;Mo7INy78?(J^2R9Bk>P&anQ3<}koH2@Ci=vvr{qw1!HP zQsfK~7pfej9Xm!lT3Pln5f5W*;4!N0K!KJ;hw6SCncBnuX-MG-6zdj|T5!^HI;Rh$ zu8@?2=+n>5VX$Es%9}O!G)Q+ zpw-nvuzI4FHFIQ^jSPc%8;jn^LQl1G4>3tRrS(MEPNi-72t5z9*i4I-gE()5oeKtD z7jt(p+$}kK4ucD7J1e+-Lf!S4p_5z-{u>eIG*O?wpJk_m@Td;z$~`ZB)GH!Q#xapu||cppy(@*g^U?$XtGl)JTQhKZbL7Q+5o$ifDe6VVtkzf|3() zdK7l7Kld(R4_XumIYMLX5AN2-^c5)2K?Nu_&Dua!-UI6_!P2w+N{zpt%nG4$hf?}^ zJF1S~UO?zj(il|BNZe~yBwE7#-230|8Wa z6E-TUL17dpG`M+4vr;3rB?gB^%eia;(_U}~Spl_$ZABG)Utqq^_+meA!b8@(vGYXcszAq*IccwAgK{ND(YFw1d zEWzwr-CPye3*HSt{=D#Tq6@tSQc+@5F;GT4XnPV73r$!K_o!x!8x*BD)P1;2qORk) zoyC5&KWs<>uZ`BG*+7=R9C1dpQ&%QBS+|#8A)CiuaX>?+?~BP}1j$7{{bdXjpWm^( zSLU5zORHw1bnS1s<+1!}MnE7Hlj4OjkESxE#=F`5>P1WtesL`FdOq|-gL^rLbOR$f zxk9AkKqv%tdXGgtLtXMHMvPNSMDuN*y^kJh;5t?EV!>1g&USi`QE!G)k@aSXVzxf}~GbY)nM(RdZ;>o?uM5%TPg8VGK!xW}8R~9Bz|5 z$n`)#YC((IRoEqTbPW*oE*$CZ@S8Wf`>O|)=uK$OOP_;8i@4RT3Ua>H8~>K%giUc} zJ~U?lj3LxK0xs7|N$j+3F(ax`HAi(1Udt;u@t*oV!mQ_Ip{oZ9`ejb3 z=--;_MzM*o25JB{qkMT)Bn`RD{BAp)^zE&RaAmcz>=+t*Eh&CRWjF|u*m3HYn~zJd zU?P&y!!OPe3cyG4#Fgr?uw!;C?oN#0#hf^D=9$i zC_<)YqK{D_*dA50ZQ@&xrS?jx2nv!DZdn4FnEN^`_o9+H9ld*JtLdOOt%7%v=-z0ms58X^2BF1&;DM3VHVXx?!3rQ=e zd%=7Qmq^BzVCK;EW5-3K8*PE6nhPb=Yr&m8sIxAa z8bhllWxslCtUC7SnJx*;^h0oi@; zc(E8o&JKMR1T_QteGz_i1n(IVDf!%tR(Ayz;(v(c4@1u9O1}4zhz`+mljt;M67D|h&TeMqW8Lqj z9Lm;vxP?AYX_|!H+uUhLR%0YAjmtfE=_wtJqJTZ`6~~Zkz#pZ|veoTpvOA^_?`S^M>w;zssP4M?=Ly&`<_A})oRrU&?M$O(9L9MN2Dbnr;-X!tX}Gc!)tFEVwBbd@ z>KRVn2w7LI#!C&_taL!;%ihQ_&u8obllc>3V^*52sCCmL}6jM^tU_ZAOi;rc)(L~9L4}8ZZy>Kxz z#QsdonZ;Ar%J^hrsPvkKk+9q1o~O+soyjP>2}?;Ws7Pdr=UvJ>cKB|)LBtmO(My@? zk)K{+n7ZbV&Ba778XXPD-Lq_^Q|xlEiq?wgKaFiB)82OsdaM>8T-5k}u8$W0BDcKT z$S)B#v3Bh%Ga-3xu|ec&;g(ncrHUoGlSi)U;d56}d~$qJW_ev;%u)7#!t*g%vdSQzQd-Refyn;3F!rsL!s^qO0YqNVjs;dr;lx9FVw+$Rw5 zfP7AoC2d#bPpvJft#coFrdIbLEJ1Ezq3~R-F=BBL;5e*QB?6|Y_j|Y|Yv}KFwsrdf z;P*GIo$G|-soK{y`ra~~VN0T#^fQP<L06}Y>YEPci45) z(chAC_#v2ZJgifG^2cZ4KThdo2hKGzR?&{)Jio}VMwg>M;Be956hFV=g8j_};vyYC z^zG%99?)>RvNDwx8%N>oaI>9zyK4W)^T;2>Q+<^FjoFBx+4@a1u`td1VkF{N{PTy7 zNdT61lCBe?bHsFDj;$>c)xKWqu+&uUwMsV$#6)Yhll|R7X`=A+a0Yef3w(DJ{Ef|t z(MB^2_`>Jus=BzEaa*>UZo<3?sKI(BJPq^Tq$#?V!zA<6+YVT3m2J>9faC+kiKS2| z`u=uotqX5rceAT@#|R$8+tQd)1C`KYnD(lb0qUe{!_b8G)= z?jX>bMN!qjK$3yka`?tHaT49`VVLedt?3L1oyaI1jZDMzl-??BWgwsLGz2IUl_ zc?qZ_C*k!*5JhfcC@LRk2g8pTD&PckRL`3ZkmvQ?#!oOrk``E#^nu*KJ(4Lqphrtr z%MEXo2fKoy+8VD>+DpCx)5bkSbKoTRw4+g3!oo_VHf}6=x@;(mJT>zwxxLp32%ghgxpyHp0QE>;0V7PX-ipos9 zV-6l2H_dy~`d_D5hKA~A* z9F6r(Ek`53E{wd&aoaqg6Dkjat(;o)aWpc)T5a3ONC$bMPqaM56p8@(RFdF>p2owF zEei9EU^VSJE!Ak&0Frz+vm|{X8Gh-70|nn+Ic&ETM`DrU60t9j9+@*zlT+9#;DlCI zgl6vk0J4SxG00`G;@QG^cS#o;B2<{o%fn@F*T|qx)7Ha6P-d>csm8zVtVy$tOTFAM zMTN$k0| zLX~b+#n?>x zvL7FyiT0eZLh?banf_5DY?j=ggU6 z(Y5Wq`kuUy_(o2k;S*ciZ`*9GU5n zh9xFeR3sxT&h`X06BZvt!8-u!dh2TSIxJ8^8MX}&1$iv>5KsX@dm`pxh~QC~6Jcb3 z2!`N3=-9eU(bUJ!$kY=6*ZxN{9RBkazsWk<&M<7;(VxF;3&&kpj=)>z&~rw$LzNHL zo}8xik3#HY39`I_JoD?~=OzZl1QZ75x96&%ICrRl>;nvNP{yy*dAr7d)tkf#dx+jl zqdh6)GnlL;oe+LQ%Rp|h4^6E<8V}!bO^hS@q z%3%Mft$GQZLou^IWH&VJWWrSv!yWYpBPUFkx?mDi2YQz-R!)Ure@;y#Et|{kQj7Ml zRfT-F_=Gp()xu+`Dpx`);Q46+J!0YpMbnEZ10XTVCsdEyQfcl9dsoj>Y>23XcbmrSi?{P2ET$@DD;dcOZ*8c9J#1OZo2LSlER-KC_v zQ@Xobx{(eE2|Bi?SCh+-xe-AIY-h1xcnfIKUGnaE`7KC3L21UOagE{dNX%vkqhlJqgfU2pV$T3&`9{(J=7lJqmI&uVTvg3*a6L3N!qF~ zd~k@2SH5f#gURB#ay{OfQJdD@?FgzknwH`hbyUSZox;rgT@-&Nv#x*{kSg6M1Epic(HD2pg~ocMwcl|o z>G_j3Go{^pI2dz`Z?LO+#i#zZRv@jw6L5ogn$;c)R}if_buOF8fZZfT&W=Zt&IlxvNr#9iHjTt@B1?|eQ4#j{mfj(7XLY>X`pw#m(NF8P!d zb=POj%c{19?%UR-XC&xS2xbzr+|t6pq~~>UbIGUdBx9w9Wer_4C%*Nemd%wnIIy=a zhclc$N?TdRaFH2DR56mhhtH)rc#n%Iwy#czc5fh)qd{`;d5Z>J_Vgf5a#A9N`!F1W z??om96Y+&(EKTFFSy4CL=JjDrfp}lM(!PxG2^Xb|wfM$btI%ld(kH6OI2>E6T)Cs< z2Ny5#%q&|&e|$xq)mhyxH^NJHOD^GbWy(!JMbjmF%hDISPk8e%ug)#mel8Ek?@h&_ zAKBe0Q?$$)Yy_+43Rzi3<8bNz0;8utKjJvz3I^Y*nNt`)dG6HSrr$>{UhFDpl;M|X zJjo~pb)ekN{%KOyW^!;+vo+zP|-?4CL&6r7-6$ZXks0P;aN|YzqG#wA^4hoM9 z6Lo0Ely8?OS(6HdggJTKn=FqNK9lho6kO8CdyI5KSEsg3QgZpVoiL0zUm8qPklbOexk^ z)G{iDDM2@yEFh{qo1{A1t&@{o$wcl%x*ZLL6F%5=cA$XVz5lr{9txsEZ}hMaaG&#m zhz1%7t`B+C*^?!{R`{%56=8CXb7KG6APb)09<$r41A*s^w9OQLb)O_^yJ(zY9gG1{ z-P1iOZV9EPW(R#XK2Jv?;#9PP8;e4?j5!{mxPBl}6&@#~WYc=s*a2T7C7m;o%1ZN^ zEuE*jJA;qe{GF?d_IhjwVd8=;;k->kC9z~3S$DF2;P-u3_Jyhk)V;#$depSrgcc8y z9VY0jKgC1IY2$5c@G_TDEja}S(eZR%8Re_?Dm@#=S-M+778UrBZ2x|2KF^`zJN*`l zGKW_wefPX)8Tsmk4#^4(CimO-L*MB=xt8^k%bIoP*mdo@zftkK2VEWM91rKpsxQ-v znW~OFxZ)H_^La5^FnFMefdZ0KkL`K$for;(_VdkV$33`>n}DNuy;6rQE7#j2cEfx0tTh{`o-(NpIFC(xvOz_YtM!wO{W0^rTgUd0*T{kx6|lD=C`tT;v6$@xh(Z zkmZpd88Y`e-iN<4A0!(5klERE*a+W8cqlYR*Qm8LA{wvTW;f5~XHhS(++#?M8h&{t zD;tF*b3K{N=xQy2g&H`1TucngT+f!ZlYcLc%=AL;LDI^PrAoR_^})+bN!q+9x~=3= zJ6Li_Z{67K8MhF8c9498T%z(Mn&v$2tDFsKCZhg?foi?OR@y_4u2b4DKIW?~NhghdG&`6r2lJ?oZh%66YT7O@>oZyv#$pLgi4{>RQu*h*GQ-}nH~PB?u4i}jhOTN4OYpT1 ze_|p>i(ezSDpE8-u{9{TXrnRsDQC~(f=`40tLzB}mbigS(HO=A4lu&I=8#FDm+%W@Nf1h|ZS7JI7XjHN7Y*x|H=+p^r^ku+(*#AwpbBZl@aUQ-_^qa10I)ygg#t5-r{8iELn$t* zi7I5$bbZb=SPWETPNO_fAtQas`(4-INjcQ5Wya(UY5O(VAKB(E1#$)6=#0FMJ1cPm zwb8G_VuubyN2`QjQOh@E^y)h&gg(aKQ0)+KvvjY|sMfNj)E82GTX@XxK4t3h;qKxC z+k5Y{Ug~~ctV~anC4E}Gda*Bmolu6`qUZAK&1r&R7!K>j>s=)+7D_&WS0!#Rr84!bfCu~6n!lCgm#@6?fk*3+D{)9& zEn<~3CAni?_#u<0sXC7EO5R5<9;~m|y#xdq4AWc#XvYXc)4y^Fv9Sest(5SjDtX!p>S%0zBJekwoJoPu5DdQ;sZmFe4!Bi+_zL2wNIYeDxF0_jIyx=6@?E2PwnKRG2X+Gd4iZr^D}p)$z3K16Sw#1qndWa#ihPh{6^v6<8&cIe3X0EHD zMuCmqD_O&760jmWtv?OoQPUGMax@PVyf(hs68-)qwj!2gfgouy?(O>*_1_0>rA=NB zrC|B`n4UDjKUDmyn{ag+omgom4^7+_nISLNqZ%F6FE5I5B325Rq*zN26NRoAHY61h zBwmZ{IlML1P03G$(1pKtQD>Gn_}~!r_VFs;k{ZOQYU{YfkcKLUtN-(Ry@Jc#!by{f zUxl9A2FSWzjM z?2`;5XHI@w+z8$4H3zLEV+`E8^G ze^mcY$@?0zCjy?SU#ppg9z{tHGnbgzBt5-_52Cr1TidS4FkT?QQcT}$b-B*ZibJ8w zbe7SXd*PPj4w2U-@`=n1wI@{aWHds@^_7uK=wczYpQ|9?7kJ|x#`hsC2xtc@{KAyN zOBm{RmRw+U{c)!%XM;%rUZ;j`q9|Lj>Q~y$6s|psVD>$6VGH{{;?YgvlGH2$OiBD3 zBQ_8WLHa|fPY$)UY|O9fhO_O8U*tfvWbqCfR5$D2UC(3f?!okisRoXy_tA%_?p|_@ z6~uoXvcg?39T%(MdRL96d6FTfq`(ISEv8q+NCl6Y_9Mwe0n3LYje>8>SkHz0TN;{x`|u%5mVC^sEX($;n>CemL?wxRnC&chh=T$krB z3H&>^hI+r$R7SDBs$Eps<;cL+%p#*!eE236!jMoZIbkrFuqCuHB^Ex~6Kl;jVwPt1 zIwTznbK?5GRzg+wK)TqLL($k5Emqr$a5n?O3w!zz&s(kc8G%)F?G1--tgnA}acY5C z-~N)^xpsJeG#^q(Jm|Hg9ZTeP=UF+o8y>MOU1e2=ef#*=>FIl424?%Gm>HkG4^8uo z94eZVA7kU4U=bkH5b%9KqpZK!E`fm-vb@h~pat#xdPA`6lcXY7W0g0T)cVs$4?id1 za$dhu&obJReeuJTUr!p(lv=xgdpCJUx64PDiQ`U|A$H0Bfs2OxUIP{Uw>REie70`g zGxn0o&hpvtK@Z6dqd3?#COqRJ*)a@NP!P)?^_*3-bgc2`)b%4z_bI-$g)l7w`r|qt z>lAAHT9@S{tQWOixc#e-wx$SFvw>YHhcYf*`*kNz-@=}x)@U>Bt(L|Csa>?ppq!1ecO}# z+4BV4g{PH_W|^@gQ4JfU;SY~2_*Mf4r+$)ZSr}d(3-Z#=h*;<<=YlBJt%|{`u zjeD`l)aG|_zJ~WMx~smL*CUBPIR02U5{Bnw*~e(f z^s@Qh=eN*@?VKv4u=|l}ZxkP16>t}fjP<*E?G8EO#-rg@3m&@A0OIY!`RdBI`SM+l zzd62z8Mu?&HBQ~z=*u^x4#YJrR*l2r3z(;TmE)qZ*E zWx57)tJevh<(rp0*uG6c8;CBvxtecF-unaWh2sCCC;j5|WPSZZig4YLhoK8ku&HM} zvCIwFhmOhzww>@|=!}&{+-p1%<@FTbj|J_#K*;^5t-29i!a(H0nAF=D!_g@{a`m2U zv(NRXj9Ku^irB;eg>mUz7dw%VM6LYU@71zs%%&;n>mOFhN|QN6Zo1M2YmWv6k6j5# zpNmx{V`ch}6*Jf%|79smsz^>r?U^lQk`26mG$YnZCE!@Ed{YQ9z z7w%w({av^N4*9!`$G_`#z~L~?lVv;pf2`Y4tfrz&@$n(SN{N8rbjfSx8cf%V@7UkH zyFa}`rG@4@VzV?g4#U-v6s{M)u7G+_n zW2t^dOUoHWkF6;DQH;xqlfx_O8jH&teW?q%IzEU{S>U4b81g);tb8a7QlcN3knOsH zdkJISw+B@b1o92Xd&L0!Cy5tR>H6IcO;bYv6<=eKE25`|=|}mY3u=VHJpbo~`uay7^jwq3F)|FiE1R^0?PYJtE}Av z(gX1m<712>0`y!7MB%%steCH;d4-njga+(K!7 zvU=F`lZe8C_#Rzn!D8vmmyX;4PxO5oTPyNvdEd>tO8JzTrkGx_z^!t`;ulzX_qn!* zAH}h(0=;Em8nMHc9B_G8j*R849(T8o$}2SF-hf=mLi}&v_ObVkzqI|T>xFhKwUbLo zNEChd{kBiTu{!#-MSTBzWtk2Ryk9W{pEzpzFUybm^o2L11@ZdEO@r%e!<+9y)MEOd z;2G@lvPJZXB&Cdv3GkrIf$)@Fj6LQZB~hAx$QW1oJn8s>75al2?T6;f(T~sr*T~~; zn&Tf$UtcP9*YJb7a;y5#zYnLQ@_@;exL<9QpwgpxgRh6=U!L4~gNpt`^I1OgwW+n2 zm_I&gVsEMq{^;Cu-7UQ`<&xNk``lr@Z0*n(i@V$RlfYbo9{oF9J$+oariQyN%-oJK zmococ-LLNT9r0n?+Z_`0SE8X?FL@Wf-XO&&XZzuCv9IoE!B<;OQbSeu!Jf}(mCs|9 zCwXWE3tv#s4ozt(Z+mR`Pz5xH{3(GVWJdic~bO6uk9gl?d%T@FPi@#vOoS- zWQ6OCR*#MkAA!T+sC}P*>>hqI+Juptb7wT}ts*}7rhH7k8$EHC;H7>>cwB$Qluy-b z$FDztO$9MPG29nI_6^&p=&x9%C9a`r$ZY1jig#svW2Dov4G~f(Ht{a;$?i|W%hKw+X^c$AfOrJs)gaZ5_zsv`G%L=c{*;pro~Lk)^Q@zQWApbn z2j3?JDfPq>Ou*{X2>a&QzITdFSJ@5dV;8j!nhTZ6S6OGfFhV?_|p2g`EyhD zc_Evd*F3VM=F!3fnB0=p1sEH9Rfc~&bz#A5)(lSj;=J$IaFB0a6_w9Ytr8H^!xONN z=7yEhmJ=BM@e$?SONW7vd(`LUZQtG#!xtI+TAlU+)8)hF8n-J>NbM10pbfV}(1fH{ zN^?i4J?M31P5Rms8`pf6i;r&&Fmf=Bi0oKgbwJ>D7x@T3ai8$1nu^^^N!7tGygnwXUTiLrsU;$eLT0>8?!K{-oRVP^lz|B^LE%Pe)RnuUZnM9FOi0zaFKIpB3^4n`d;NX7QPwkcb!K9P<$0@jfhP3j1`NrFIK{<6I(Okw2Dm*e(_)b~h6zmDrC$^`d(o;J!u85Zg!5c0)v3H#!m zp*%YlGGD4N?jBj|!TaeMhK9VlOaO@dy!mrJ_K1~ zu}VA+X1A_=j*W#--wLw+W?&$Qojv!?JA2-0a8jOa?p3c6hDD!it28f3(|FqpoPI8ja8V)ege79ds1g90I{(kG_zFU6o#v!u=8mOX+$)+7=8g52oj-Jm89OR4Kr6f~n8Sw#w=f zRH<CZ#kFIKEH1^X3sad*^pvH zRLjJhy`J{avvQ`B|JfH(eN>2P4}w=FLJr(=o!S3e+?`L<(T&op^h)kb^v%msTS{I7 zF5lF?GMZx)H@%vf=iQ{XEq%w-j5F3u+Eud}mF?>$7hR=X&yx7w96Q#2vTN1SQ?{s= zoR^yRt|`y$d^yjlPju#JsgvHc=?_P{xh~1k6>4pZ6(0ySrL%z;7vYz&N?mmg>Mw=g zr+-~$zC|nk_?GTMLEO~^`avcP&JShG2Myv5o&qvSfiBe@`a`63a`JW!5l%#OPLt8}UbMO^iyM|O6uLp%kH z?$!G(rHxbMdy|yDl8-Fxc^>zhm%{6qt~o zK&hA2bGMS9@sh{8AE@9XFIiVLyGq7K=;jOp9JdM~9V?T5MOUKhJvZw{(l`BV9+d(N zCXMzmcFK9Lv8~>~Yas0|*|~aWctqT;OZuv`nHKCNcPdK0X2V<}My(W;hJ*k_5HI>} z>;z?BD6LC&JI%LF;;u{&T}HEK?`>0q2k@pp(UQx?arv-g5qHkPFJ+OdW8RESFGv*OougD% zi_{OPk+GDL%DQZQvqqG6^x%^iZyXpn+S^wW-P&7)?L}zP4AiJ2kb^Q*2pHeadNvm%e0@Z8xen-9KV%! zQG9`S|&w~6yRKJNGzbyDbl^!VU@G2<(s!w8x^0w zU@*N&Y!hIMzQzM>RF4;2w_9U}d6_o9=YBX{Xj9sbwY1ztpEI~2X|ppyPokTB)O!7! zaCQ_dfix;(W(l-xh>c3>n6BVAvGU|~hWstv<3W-S8>`v_2{(ve_AKSnT1Lw@+PSOP zjGBwOK_ZOE1Pgj;8tU(FTcFU!49SFiRbuqEC%!NwAD(?Iw3F`dH&fdAJ#?=vf>vZY zFyu*7}CKFJh~~d&;33G`^DyPN}a3GK<|C4=GmIIi3wYfaGJH9k$Xbet@y=)tQqL?u#!BEgJr#!4+q%RUocu_?UKDl;I8 z-T0mn9cm{^zLhN2rM$c{yX!tPRKKJ$j430JBksA|Ae&^mBIoarqQw7Ek57`1`l08t z80*KNaH?Fw_4u+$(p)FP+W5-csjh%1##RicAGO%uRiv8Ei4J@;m8LBYro z;(WB;BkhC{*@wM4KZAzvps9To*sxBq*x)8R4y~=QrJW#Z!4uZV55c%LtA;%_G-X_v z3@9YAYz?0;4M9FlsP_!8P52xOf3Q}?d*0%|6Q)E{`3!$WuOq8vj6^#v<>Hp`&5Vg& zypjNJkG1=6P+q2L@(F&}DSoP_ASUQLc;U%0$|j1=$O>8u$TA^+>7`FKm%Q|PIOr`; zoa}((+Q6$3R%NUqsENn~BtylseiTlpi75F|F{mj;B~-70f1D4g6{o~4FMNnq6jFp0 zUE4!NG(ZKjEcMux-t++Fr-$BfLhrm6*U7Xmx zRVCb8fk@>*|1(OF_r*Dc=eAW#-)J6t zTOqz{%Q$G8^m?mt$PMpK*Wkj{gr`B0)lY8Jz2N zu2+$gW3kRDSaDht?Onm`=e^}zV?xKM0GTjD*cu3T6AnMRO;uNX>;1h-od>yjx=A-6gS*WdbdBk(NCZC`)Ip|U9%%eAl zDh^;qD{SxOA#xio>X7Z02%P%9peGFHhv!wns%Qcq3Q-W*D&^QS!f1kuu zV|o`Oc2N8)Up^7dQ;xwP7+&3refm@y8Rg-!`s4J2)-OEO z0V6Vjv@RcbJ}@ezhCY;&i0AjzZJ3g`B$;psO!D8Z>uZizd`8-QNpvN=8QZhB$n{ps z>XDQot$oJFJJ7;5x-V*(RM#}0-Qo^@V4bHBsf+5_rOdGE(Mz#toV|BT+EProE5Qk33j(QYoNRB*v`CzNQOovNGHf}BE1(T zZAWQ9OQbo(`l=UUCw*h@ayM~L3{3>;+?YY5?bX<(+4s>K%r$r3Q;uNDx_)bg=dtNf z;S`|L!F?f7mF^9C&|}3ZRE;)|hopz@rha7H!u>*aokY_!dy0sZ(x*MNk95Et%=Mjl zyi)>}UZ|P{)XS?u*D7K;^Da$Mky8e6U(-h~yI7PlpAznv^QO1_$OUZC@Z{Zw!bh~+q7^z<6hLlP&~(YS?WB~4v4t#_}EwUkbt>=6goSW zsi+-ykaWFxmTkgTEj^i(sWPm{uCQvKA5T#Ua_XwbPm$CS6tQrS*v~k;6Y<>q zi&?G=)|^Hr4;`Cbf~?)endJG1^%O3GS%3Q@DNzhYa5+A4lDS?r%x~&35$xOjlCT(x z%Z3EkBE3Hj@mmaJ_n5 z^F-uR*}haCHSc?FS6<^s>cPu7GsCY(?uC%twAPuuY)Kyp3qIy1EG#^j@p2A{-Hb}B zF3~ZYYcB{HyzF#U+})eV$YzRDH2>kqjYWMfOOvTYzQW3ThBt){;4{+6v-1WdccPxj zVMbA}eD&N&g<$Ye7H?m$sGOa@EwR59xU55@K(^gWl689x0mUh0FjsC>Z%`@f5~O)C zW{G0vIr(1u`TM<|>-mZ+Uy>9TQlq27%M&|dOo#@hlh$>V)1cjciv?zUlZG#a*MSXu zH9l0DI%N79_*Kol*vb>U&MAm*ji!Zvl)NK0Bcyd7q#ZXIw5{}TOLhu|?zQmsZmeHt zsa$G;iDC>cvB%fZoito=Xpm^t3fp{4;^x(8FVByS_>$uu3$zyobzVyHaEyKAaCvOY zHFRtsPDYZFcNqfHP+}|Rlj@igTSxSFl<}#G} zL4aKMK}$$$dV30N-#Sj6ZD;Go?d$vBqDPJ}}7b?i8#EYtFLm4>g?KgIC! zxcqjHP{2KnecxF;>Ygd}iueJ%#148B&4q@cR>uhQ+YsxqL&~Q|vdYTku8p(ADHFYk zfs9`-F?jpE*CRYGSjE3NcyxJ|eZjjW*nPZZLfg5^0G~_LD3sZZb$OFzZ$LRo!Z`d& zPckaLbQ_tb$hhjFpM7X|RX};K&?k}Ay9;A(!mFAhE-yQMm@4;G+v?vJ6p3&e^LT!| z8Xh$_?O5{ox=rua3za9)t*)iapNyxh2lgIm;Q57lryqLF+D+arK-!{LN5mIB3Nrk40ke9H%5qyBn zQWs8RzQ1{MC@edf%fYfTocr-(a)pTSTLMkmgLzMC86g;NDB&x1@$U+iIGDsPyb9c! z?BnIJT)O$ln~bj9&^y<4Q&!Rfw8%E3qe%#}F^ujinhami{N|uOcJ0Tb&ujJ*lA*On zfxHZS>d`4TUk=H4+~1&DeQu=45&u^Bj`y>XU%EVsQfgcX`rG!pO;H}E22uj z7{hewz+b>^?t`n9O3mv1+q;!e?6$1x&B24<=w=4dj?O^mn5WszSCcN6#CB=tt{s@b zLv~AW)*j%6xw&Cu99ez2E!g;QB{BCNy^~&it+D}!-)>q9#PELOi?~`Nj^t4rzq>ky z7{%Dcgtt*9`-J_izL0)j(#N%u!BN#xxQK|&$C@p8dc;n@B@W) zp{SLOc#hF=DzXjkR~b&4^Q$@J1Z1y?ANp@3<@LW#)-y5>@u1Z7_m#-k@afvTJTj`f zY%I6XNkV;7lM5qT>26$Gv-!T;c!DW4Js#~8x1pMcXR@nyw)mq)E0Q{3nQJbg$)>lBIdmn@IESq7nRoXgOlHKRN8Wv z#_i`%-WDc`M0)PekKAz&e=(a_NWVLe6-zYrMEW*yRTX-35sRG`*HrTj((e|;B;Th+ z=G#MA=!UQ6-Ty`=Xdo{B&<%R4yWoPu%&e-Pm7!1@skYMyOoQ|OhFrrM-CjWrFY0?} zF#}(UPSEQq9ya!l2v+Zi?negEm=`o#w8MfgS7*hWAV?@pQX-7m@~ZO6<{PTbA8k0e zrS-Ply_X`mWme8tBcnx!8q>4?~;Rws181cyv5HyPwoNl|C>KQK(j)cy+0=jpbYAvX@IO zzjmKU|FyMV#f5q5x`~t$_wgHhAr`pk)ozM|8*Q5Rx02>9nX79-&+{j%!LlKxa*4FC z>kl72Z&Y&}mvOCLy&=5{yHGkk5<_1Zx&4ojW4@E zKV>K@wGQ`@uu=`w8>owgOD}g&YHMT5=R9gAv8^T|oeK0Jxf3n1G7s)#t^XL4tE=;% zaXV%%uD8XYZFBy{vjvMhHByJ<;Es|6OmE0y%yaFjzBPk)HmNw3!bNk3m|qoB{Y?Dw zUWO(rrQgJ()*XBVseFHP!SwP6g>fU38`yC<`c2mf}mphe$cKFjPV z(r-EhQrKeCpvP+NcCPR%ne!dWiPx>vO59(T-~0H6mk5XOH5Bp4o`YH?ESqRBHH;vg zg3{4cu&RdNdNt;NNj%kO?Gma!NuK$(gljej)e}rT5)P#P2 z$fXzYtS;i2TV)@mo9?u}Pug^?5Pu8&<(4f?$1vHkUC$~$zt4!JEqVKcU`JL~vvknj zn#Xfoi|WT+4&5ZZPhRTXNB?vsmWJ>7#n2tXVtg5O9*y0(8=&q^F-Jqo6-|oXi(c%5 zvxS+hSA`IP%7s;{_1)2(dh2D)XcswAf&v!5lFUvMIO*!G9DL6$XRCL#<(=h=++tr& zpM8d6&=vkg_mwUKm)6=dVP$;qn&&iFKGRB;BjahWrk8U+h2mr)+zopmc77(W zQd7lA18*;s9bG)C;@A_Gn|)cNC!^sXKH{XADoG&)^;UttjjOX7^1Q&Yz#nmwC__bz zbO8Nb+sje=s;b-TsJ+4#GC76@(LVhwlif^#%im%go|VY-#Jv25PyO)lqo|dR(^HWM zjYb2;&Yhu+B3qWE;O}^%ImASm&#N>A?mt6MefG9cmV*woY~=c`*_TvT!1L3msm$2P zZmyoCB|#G+UXe;3s?w5@mpAMOWg-hA3n%ex6L|H=JQR1+Nz;|JUingR#K9#C%UnN^ z-f(ux5;VL?Q{_@n_8}!We5Msh!yXDL(ebanB!Z!u(QX4~!nqhpD3L zrB;O12B7lZ|NH==|2nEQGxb&ruU!eMeCAA7uSaQYGCi~U3qO0dauWyM>)1-2SIR#4 zNT|hKLdY%DZ--@E>|1g+8yiI*EhF_gLN94(g@Gzu`?tM>N{eMlm5SeC`HU^okzwLc zz3A)uytAI$BhDb(TmaViZlz^PfM0Ok_!3pI!rDGXS4Uns{kF!%k15f)kI3fC+-%t} zBB_ck3!Wyo+#VLLTDiVYmi?Y;rhK2ArBEy=OoK9Aak`w-Dk_`t<!v3|Wwt|(Oi490sp}ERMp5_E1L3s9 znGLkI@c^$W(i9z+{5;wtBZl4JN1YJF_JaGQkO%jr9LYazcQZSz!=hTOcR| zggBMt#0l*D^UtHtECHzAUNy4nO`6{0;~=~K!4|XA#=V!2)5ssCuv_G1nh*PzZiFt+J)R|>2FS#-LD)j zC$Ka4Po@?K%8vMzF{X{n$A{EGB(K>1Tk!xHI~zE;*%^aQHmCxGE2!^iENW}x1OiJ* zE6PhN{ZcXl z0?G>1s%CEFWabEzMZ!?T1^*Tp2znyTf91;tB!-<4FS5n|h4h}PoUF3KS){+LLWGf9>>j7A$fHF#y0ntOcz0FW6J8MdnWa zZ?GKyonHCB!2Yi!%QIk4k~dF*Wdl6NU$E=|^xvF?{>&cyUwXia6x3-m|G)I$|8kgT z0sm6Qf4B<>|1V%<;^!a0za;U$6c=d+rvc0Bn;JU;Uf0$cFesold40e&If0-jIu5W( z%G?NWQ>-U@pn=#A>Y|NnzkP+&Cr$s4v4=K?zEts>wp zPIz>>uJde{b9S-<+%v%W6VZZxIVhxLLBGA#Pnod)<04P)`~hVSB;tZtPnZPktOxq- zxc>C#x5Y%pQiWM+nx~4RQ1c32!No!ROvsf`q@ETu&nX@KUn|Pds+x55P$hk z03!4^q~A*R`x*)H*ZI$C&%%YC8XSIBd!i7?o&#~jiC_5z_uoo?cH}QS8y^uo4=%t}uB4o(c9pWz3H-q1|n&dJ!}4hxh4#C}rwKQsgbi1nud{Vsr0PI_c#Q*0(7B+x%!XY3PUE18*-04i?e`;f6IIZ&k+Tw4}hUQMjEW&c)P!6D0 z=ufvne?n9f6^3#mi9cP+FQS^b@Xw+r!T{0R0lo?eoAVd$lWPDp>=)>hYXC9q9O8dl z{#nggy#GdQzww^b$IbwP|MWRO9|W)?enCIE55PwJ+dd4q{^_xPmWKn~g8T~@5C!z7 z#{SFG-);ST2yup+|EldNbmoAp|3zmG5IBCZ%hA}-$=uckPzVUXlM@>O(f=hP8Tkw7 zUm}v5zk2tF2+%lBsyv4mXzXu=tYprWtf9eFJ#QBSBe`o-wIe+!#4-Mcpu#M=6 zO8%h%9EYFn4ZwBayNT1~0M5ft^yy#Re`)~`{KXDGjh!7?{r>|y3lyn>r*Q%_r0tvl z_>abZKJvS@lftKr1=u6?Z*xBfm7i7*3@5*aTqxVm7NKlExdCPSDLUv0RAAWsZ|nTk z+y7|tUw;aO%79%%PhkA(=Ai6Iz5UIHpP{3WJ}|Sh1*S3b$iU%mobY=<11KlD0=T>r zR|RE15y8*$GPXv>U=>H>--S;qk=8dbwsPd*0n0jDJ8D9J6D6=PA0O+fM(tJ$wJ zYvh?5@}LD_2Yw+CHRQnzc`!#FoREi;hnSFOze~ydDPZ}h!0&saf9{<;XY;4P$=RuD zP9WJL3!JogQs8$r!btMpdw+qSKyqUMDk`^jUDLij7)%H%up6~2n*op!BWOli z8H4DBxmY1kID`}U4hqT+{4zt>=^zj~pd6q*|5p)Z2Yov`V|L17!GT1ilY+2^V-@!OnpT z@Z{GI;J!G3k=X?FhlU(Fkv|}tKQsi=YERP;5M;!5iU#B0fCH+0iUxrokiP3Q4GKYy z>!)c52y*m0MFZ+XMjEGRz`GXYD0PO$h8%}Z(-2VBvu(gQ*pY^Hx*Xu3&X$7!wtlt^ zD1-wU{+)Uj1~Bd{4R{tg?wl$IfpVVf2M}H(vN-f$S6wc$V`#|6oY3d8!=X zG9YKm!8o9Z^ZkXLmk|_>ob;Tk55oE%GKV5q&-INBf;cB5Rt{vOak@S>2y(V{iUwnY zBO{#CGFl%T^ZSg{D;gD>>THKfPkOpDTL#kP6AVybLD_`&*>U4 zM4jUcFx7*c#S9Eb$l&r+-+)rc*MFyJ$N}UWjT3fG51{Au6lfRtX7_A;|Is&A*7NvS zA;?Jk3>MfqzX7~DKIeCV2>6^10df5KHrNozndIp<*kQ;QU8iYmtQ=?M3%sU2=d*!u z{hV(C?C9JW53uS#@WGI?*3<2>!_N73V3KgoreW-ebG8a&J8%0yICowifS)|qUk*;> z9O5(<4#c^!3\linewidth\hsize\linewidth\else\hsize\@tempdima\fi -% longtable ignores \abovecaptionskip/\belowcaptionskip, so add hooks here -% to uniformize control of caption distance to tables - \abovecaptionskip\sphinxabovecaptionskip - \belowcaptionskip\sphinxbelowcaptionskip - \caption[{#2}]% - {\strut\ignorespaces#2\ifhmode\unskip\@finalstrut\strutbox\fi}% - }\hss}% - \par\prevdepth\dp\strutbox -}% -\def\spx@abovecaptionskip{\abovecaptionskip} -\newcommand*\sphinxabovecaptionskip{\z@skip} -\newcommand*\sphinxbelowcaptionskip{\z@skip} - -\newcommand\sphinxaftercaption -{% this default definition serves with a caption *above* a table, to make sure - % its last baseline is \sphinxbelowcaptionspace above table top - \nobreak - \vskip\dimexpr\sphinxbelowcaptionspace\relax - \vskip-\baselineskip\vskip-\parskip -}% -% varwidth is crucial for our handling of general contents in merged cells -\RequirePackage{varwidth} -% but addition of a compatibility patch with hyperref is needed -% (tested with varwidth v 0.92 Mar 2009) -\AtBeginDocument {% - \let\@@vwid@Hy@raisedlink\Hy@raisedlink - \long\def\@vwid@Hy@raisedlink#1{\@vwid@wrap{\@@vwid@Hy@raisedlink{#1}}}% - \edef\@vwid@setup{% - \let\noexpand\Hy@raisedlink\noexpand\@vwid@Hy@raisedlink % HYPERREF ! - \unexpanded\expandafter{\@vwid@setup}}% -}% -% Homemade package to handle merged cells -\RequirePackage{sphinxmulticell} -\RequirePackage{makeidx} -% For framing code-blocks and warning type notices, and shadowing topics -\RequirePackage{framed} -% The xcolor package draws better fcolorboxes around verbatim code -\IfFileExists{xcolor.sty}{ - \RequirePackage{xcolor} -}{ - \RequirePackage{color} -} -% For highlighted code. -\RequirePackage{fancyvrb} -\fvset{fontsize=\small} -\define@key{FV}{hllines}{\def\sphinx@verbatim@checkifhl##1{\in@{, ##1,}{#1}}} -% For hyperlinked footnotes in tables; also for gathering footnotes from -% topic and warning blocks. Also to allow code-blocks in footnotes. -\RequirePackage{footnotehyper-sphinx} -% For the H specifier. Do not \restylefloat{figure}, it breaks Sphinx code -% for allowing figures in tables. -\RequirePackage{float} -% For floating figures in the text. Better to load after float. -\RequirePackage{wrapfig} -% Separate paragraphs by space by default. -\RequirePackage{parskip} -% For parsed-literal blocks. -\RequirePackage{alltt} -% Display "real" single quotes in literal blocks. -\RequirePackage{upquote} -% control caption around literal-block -\RequirePackage{capt-of} -\RequirePackage{needspace} -\RequirePackage{remreset}% provides \@removefromreset -% to make pdf with correct encoded bookmarks in Japanese -% this should precede the hyperref package -\ifx\kanjiskip\@undefined -% for non-Japanese: make sure bookmarks are ok also with lualatex - \PassOptionsToPackage{pdfencoding=unicode}{hyperref} -\else - \RequirePackage{atbegshi} - \ifx\ucs\@undefined - \ifnum 42146=\euc"A4A2 - \AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}} - \else - \AtBeginShipoutFirst{\special{pdf:tounicode 90ms-RKSJ-UCS2}} - \fi - \else - \AtBeginShipoutFirst{\special{pdf:tounicode UTF8-UCS2}} - \fi -\fi - -\ifx\@jsc@uplatextrue\@undefined\else - \PassOptionsToPackage{setpagesize=false}{hyperref} -\fi - -% These options can be overriden inside 'hyperref' key -% or by later use of \hypersetup. -\PassOptionsToPackage{colorlinks,breaklinks,% - linkcolor=InnerLinkColor,filecolor=OuterLinkColor,% - menucolor=OuterLinkColor,urlcolor=OuterLinkColor,% - citecolor=InnerLinkColor}{hyperref} - -% stylesheet for highlighting with pygments -\RequirePackage{sphinxhighlight} -% fix baseline increase from Pygments latex formatter in case of error tokens -% and keep \fboxsep's scope local via added braces -\def\PYG@tok@err{% - \def\PYG@bc##1{{\setlength{\fboxsep}{-\fboxrule}% - \fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}}% -} -\def\PYG@tok@cs{% - \def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}% - \def\PYG@bc##1{{\setlength{\fboxsep}{0pt}% - \colorbox[rgb]{1.00,0.94,0.94}{\strut ##1}}}% -}% - - -%% OPTIONS -% -% Handle options via "kvoptions" (later loaded by hyperref anyhow) -\RequirePackage{kvoptions} -\SetupKeyvalOptions{prefix=spx@opt@} % use \spx@opt@ prefix - -% Sphinx legacy text layout: 1in margins on all four sides -\ifx\@jsc@uplatextrue\@undefined -\DeclareStringOption[1in]{hmargin} -\DeclareStringOption[1in]{vmargin} -\DeclareStringOption[.5in]{marginpar} -\else -% Japanese standard document classes handle \mag in a special way -\DeclareStringOption[\inv@mag in]{hmargin} -\DeclareStringOption[\inv@mag in]{vmargin} -\DeclareStringOption[.5\dimexpr\inv@mag in\relax]{marginpar} -\fi - -\DeclareStringOption[0]{maxlistdepth}% \newcommand*\spx@opt@maxlistdepth{0} -\DeclareStringOption[-1]{numfigreset} -\DeclareBoolOption[false]{nonumfigreset} -\DeclareBoolOption[false]{mathnumfig} -% \DeclareBoolOption[false]{usespart}% not used -% dimensions, we declare the \dimen registers here. -\newdimen\sphinxverbatimsep -\newdimen\sphinxverbatimborder -\newdimen\sphinxshadowsep -\newdimen\sphinxshadowsize -\newdimen\sphinxshadowrule -% \DeclareStringOption is not convenient for the handling of these dimensions -% because we want to assign the values to the corresponding registers. Even if -% we added the code to the key handler it would be too late for the initial -% set-up and we would need to do initial assignments explicitely. We end up -% using \define@key directly. -% verbatim -\sphinxverbatimsep=\fboxsep - \define@key{sphinx}{verbatimsep}{\sphinxverbatimsep\dimexpr #1\relax} -\sphinxverbatimborder=\fboxrule - \define@key{sphinx}{verbatimborder}{\sphinxverbatimborder\dimexpr #1\relax} -% topic boxes -\sphinxshadowsep =5pt - \define@key{sphinx}{shadowsep}{\sphinxshadowsep\dimexpr #1\relax} -\sphinxshadowsize=4pt - \define@key{sphinx}{shadowsize}{\sphinxshadowsize\dimexpr #1\relax} -\sphinxshadowrule=\fboxrule - \define@key{sphinx}{shadowrule}{\sphinxshadowrule\dimexpr #1\relax} -% verbatim -\DeclareBoolOption[true]{verbatimwithframe} -\DeclareBoolOption[true]{verbatimwrapslines} -\DeclareBoolOption[true]{verbatimhintsturnover} -\DeclareBoolOption[true]{inlineliteralwraps} -\DeclareStringOption[t]{literalblockcappos} -\DeclareStringOption[r]{verbatimcontinuedalign} -\DeclareStringOption[r]{verbatimcontinuesalign} -% parsed literal -\DeclareBoolOption[true]{parsedliteralwraps} -% \textvisiblespace for compatibility with fontspec+XeTeX/LuaTeX -\DeclareStringOption[\textcolor{red}{\textvisiblespace}]{verbatimvisiblespace} -\DeclareStringOption % must use braces to hide the brackets - [{\makebox[2\fontcharwd\font`\x][r]{\textcolor{red}{\tiny$\m@th\hookrightarrow$}}}]% - {verbatimcontinued} -% notices/admonitions -% the dimensions for notices/admonitions are kept as macros and assigned to -% \spx@notice@border at time of use, hence \DeclareStringOption is ok for this -\newdimen\spx@notice@border -\DeclareStringOption[0.5pt]{noteborder} -\DeclareStringOption[0.5pt]{hintborder} -\DeclareStringOption[0.5pt]{importantborder} -\DeclareStringOption[0.5pt]{tipborder} -\DeclareStringOption[1pt]{warningborder} -\DeclareStringOption[1pt]{cautionborder} -\DeclareStringOption[1pt]{attentionborder} -\DeclareStringOption[1pt]{dangerborder} -\DeclareStringOption[1pt]{errorborder} -% footnotes -\DeclareStringOption[\mbox{ }]{AtStartFootnote} -% we need a public macro name for direct use in latex file -\newcommand*{\sphinxAtStartFootnote}{\spx@opt@AtStartFootnote} -% no such need for this one, as it is used inside other macros -\DeclareStringOption[\leavevmode\unskip]{BeforeFootnote} -% some font styling. -\DeclareStringOption[\sffamily\bfseries]{HeaderFamily} -% colours -% same problems as for dimensions: we want the key handler to use \definecolor. -% first, some colours with no prefix, for backwards compatibility -\newcommand*{\sphinxDeclareColorOption}[2]{% - \definecolor{#1}#2% - \define@key{sphinx}{#1}{\definecolor{#1}##1}% -}% -\sphinxDeclareColorOption{TitleColor}{{rgb}{0.126,0.263,0.361}} -\sphinxDeclareColorOption{InnerLinkColor}{{rgb}{0.208,0.374,0.486}} -\sphinxDeclareColorOption{OuterLinkColor}{{rgb}{0.216,0.439,0.388}} -\sphinxDeclareColorOption{VerbatimColor}{{rgb}{1,1,1}} -\sphinxDeclareColorOption{VerbatimBorderColor}{{rgb}{0,0,0}} -% now the colours defined with "sphinx" prefix in their names -\newcommand*{\sphinxDeclareSphinxColorOption}[2]{% - % set the initial default - \definecolor{sphinx#1}#2% - % set the key handler. The "value" ##1 must be acceptable by \definecolor. - \define@key{sphinx}{#1}{\definecolor{sphinx#1}##1}% -}% -% Default color chosen to be as in minted.sty LaTeX package! -\sphinxDeclareSphinxColorOption{VerbatimHighlightColor}{{rgb}{0.878,1,1}} -% admonition boxes, "light" style -\sphinxDeclareSphinxColorOption{noteBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{hintBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{importantBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{tipBorderColor}{{rgb}{0,0,0}} -% admonition boxes, "heavy" style -\sphinxDeclareSphinxColorOption{warningBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{cautionBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{attentionBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{dangerBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{errorBorderColor}{{rgb}{0,0,0}} -\sphinxDeclareSphinxColorOption{warningBgColor}{{rgb}{1,1,1}} -\sphinxDeclareSphinxColorOption{cautionBgColor}{{rgb}{1,1,1}} -\sphinxDeclareSphinxColorOption{attentionBgColor}{{rgb}{1,1,1}} -\sphinxDeclareSphinxColorOption{dangerBgColor}{{rgb}{1,1,1}} -\sphinxDeclareSphinxColorOption{errorBgColor}{{rgb}{1,1,1}} - -\DeclareDefaultOption{\@unknownoptionerror} -\ProcessKeyvalOptions* -% don't allow use of maxlistdepth via \sphinxsetup. -\DisableKeyvalOption{sphinx}{maxlistdepth} -\DisableKeyvalOption{sphinx}{numfigreset} -\DisableKeyvalOption{sphinx}{nonumfigreset} -\DisableKeyvalOption{sphinx}{mathnumfig} -% user interface: options can be changed midway in a document! -\newcommand\sphinxsetup[1]{\setkeys{sphinx}{#1}} - - -%% MAXLISTDEPTH -% -% remove LaTeX's cap on nesting depth if 'maxlistdepth' key used. -% This is a hack, which works with the standard classes: it assumes \@toodeep -% is always used in "true" branches: "\if ... \@toodeep \else .. \fi." - -% will force use the "false" branch (if there is one) -\def\spx@toodeep@hack{\fi\iffalse} - -% do nothing if 'maxlistdepth' key not used or if package enumitem loaded. -\ifnum\spx@opt@maxlistdepth=\z@\expandafter\@gobbletwo\fi -\AtBeginDocument{% -\@ifpackageloaded{enumitem}{\remove@to@nnil}{}% - \let\spx@toodeepORI\@toodeep - \def\@toodeep{% - \ifnum\@listdepth<\spx@opt@maxlistdepth\relax - \expandafter\spx@toodeep@hack - \else - \expandafter\spx@toodeepORI - \fi}% -% define all missing \@list... macros - \count@\@ne - \loop - \ltx@ifundefined{@list\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \expandafter\let - \csname @list\romannumeral\the\count@\expandafter\endcsname - \csname @list\romannumeral\the\numexpr\count@-\@ne\endcsname - % workaround 2.6--3.2d babel-french issue (fixed in 3.2e; no change needed) - \ltx@ifundefined{leftmargin\romannumeral\the\count@} - {\expandafter\let - \csname leftmargin\romannumeral\the\count@\expandafter\endcsname - \csname leftmargin\romannumeral\the\numexpr\count@-\@ne\endcsname}{}% - \advance\count@\@ne - \repeat -% define all missing enum... counters and \labelenum... macros and \p@enum.. - \count@\@ne - \loop - \ltx@ifundefined{c@enum\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \newcounter{enum\romannumeral\the\count@}% - \expandafter\def - \csname labelenum\romannumeral\the\count@\expandafter\endcsname - \expandafter - {\csname theenum\romannumeral\the\numexpr\count@\endcsname.}% - \expandafter\def - \csname p@enum\romannumeral\the\count@\expandafter\endcsname - \expandafter - {\csname p@enum\romannumeral\the\numexpr\count@-\@ne\expandafter - \endcsname\csname theenum\romannumeral\the\numexpr\count@-\@ne\endcsname.}% - \advance\count@\@ne - \repeat -% define all missing labelitem... macros - \count@\@ne - \loop - \ltx@ifundefined{labelitem\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \expandafter\let - \csname labelitem\romannumeral\the\count@\expandafter\endcsname - \csname labelitem\romannumeral\the\numexpr\count@-\@ne\endcsname - \advance\count@\@ne - \repeat - \PackageInfo{sphinx}{maximal list depth extended to \spx@opt@maxlistdepth}% -\@gobble\@nnil -} - - -%% INDEX, BIBLIOGRAPHY, APPENDIX, TABLE OF CONTENTS -% -% fix the double index and bibliography on the table of contents -% in jsclasses (Japanese standard document classes) -\ifx\@jsc@uplatextrue\@undefined\else - \renewenvironment{sphinxtheindex} - {\cleardoublepage\phantomsection - \begin{theindex}} - {\end{theindex}} - - \renewenvironment{sphinxthebibliography}[1] - {\cleardoublepage% \phantomsection % not needed here since TeXLive 2010's hyperref - \begin{thebibliography}{1}} - {\end{thebibliography}} -\fi - -% disable \@chappos in Appendix in pTeX -\ifx\kanjiskip\@undefined\else - \let\py@OldAppendix=\appendix - \renewcommand{\appendix}{ - \py@OldAppendix - \gdef\@chappos{} - } -\fi - -% make commands known to non-Sphinx document classes -\providecommand*{\sphinxtableofcontents}{\tableofcontents} -\ltx@ifundefined{sphinxthebibliography} - {\newenvironment - {sphinxthebibliography}{\begin{thebibliography}}{\end{thebibliography}}% - } - {}% else clause of \ltx@ifundefined -\ltx@ifundefined{sphinxtheindex} - {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}}% - {}% else clause of \ltx@ifundefined - - -%% COLOR (general) -% -% FIXME: \normalcolor should probably be used in place of \py@NormalColor -% elsewhere, and \py@NormalColor should never be defined. \normalcolor -% switches to the colour from last \color call in preamble. -\def\py@NormalColor{\color{black}} -% FIXME: it is probably better to use \color{TitleColor}, as TitleColor -% can be customized from 'sphinxsetup', and drop usage of \py@TitleColor -\def\py@TitleColor{\color{TitleColor}} -% FIXME: this line should be dropped, as "9" is default anyhow. -\ifdefined\pdfcompresslevel\pdfcompresslevel = 9 \fi - - -%% PAGE STYLING -% -% Style parameters and macros used by most documents here -\raggedbottom -\sloppy -\hbadness = 5000 % don't print trivial gripes - -\pagestyle{empty} % start this way - -% Redefine the 'normal' header/footer style when using "fancyhdr" package: -% Note: this presupposes "twoside". If "oneside" class option, there will be warnings. -\ltx@ifundefined{fancyhf}{}{ - % Use \pagestyle{normal} as the primary pagestyle for text. - \fancypagestyle{normal}{ - \fancyhf{} -% (for \py@HeaderFamily cf "TITLES") - \fancyfoot[LE,RO]{{\py@HeaderFamily\thepage}} - \fancyfoot[LO]{{\py@HeaderFamily\nouppercase{\rightmark}}} - \fancyfoot[RE]{{\py@HeaderFamily\nouppercase{\leftmark}}} - \fancyhead[LE,RO]{{\py@HeaderFamily \@title\sphinxheadercomma\py@release}} - \renewcommand{\headrulewidth}{0.4pt} - \renewcommand{\footrulewidth}{0.4pt} - % define chaptermark with \@chappos when \@chappos is available for Japanese - \ltx@ifundefined{@chappos}{} - {\def\chaptermark##1{\markboth{\@chapapp\space\thechapter\space\@chappos\space ##1}{}}} - } - % Update the plain style so we get the page number & footer line, - % but not a chapter or section title. This is to keep the first - % page of a chapter and the blank page between chapters `clean.' - \fancypagestyle{plain}{ - \fancyhf{} - \fancyfoot[LE,RO]{{\py@HeaderFamily\thepage}} - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0.4pt} - } -} - -% geometry -\ifx\kanjiskip\@undefined - \PassOptionsToPackage{% - hmargin={\unexpanded{\spx@opt@hmargin}},% - vmargin={\unexpanded{\spx@opt@vmargin}},% - marginpar=\unexpanded{\spx@opt@marginpar}} - {geometry} -\else - % set text width for Japanese documents to be integer multiple of 1zw - % and text height to be integer multiple of \baselineskip - % the execution is delayed to \sphinxsetup then geometry.sty - \normalsize\normalfont - \newcommand*\sphinxtextwidthja[1]{% - \if@twocolumn\tw@\fi - \dimexpr - \numexpr\dimexpr\paperwidth-\tw@\dimexpr#1\relax\relax/ - \dimexpr\if@twocolumn\tw@\else\@ne\fi zw\relax - zw\relax}% - \newcommand*\sphinxmarginparwidthja[1]{% - \dimexpr\numexpr\dimexpr#1\relax/\dimexpr1zw\relax zw\relax}% - \newcommand*\sphinxtextlinesja[1]{% - \numexpr\@ne+\dimexpr\paperheight-\topskip-\tw@\dimexpr#1\relax\relax/ - \baselineskip\relax}% - \ifx\@jsc@uplatextrue\@undefined\else - % the way we found in order for the papersize special written by - % geometry in the dvi file to be correct in case of jsbook class - \ifnum\mag=\@m\else % do nothing special if nomag class option or 10pt - \PassOptionsToPackage{truedimen}{geometry}% - \fi - \fi - \PassOptionsToPackage{% - hmarginratio={1:1},% - textwidth=\unexpanded{\sphinxtextwidthja{\spx@opt@hmargin}},% - vmarginratio={1:1},% - lines=\unexpanded{\sphinxtextlinesja{\spx@opt@vmargin}},% - marginpar=\unexpanded{\sphinxmarginparwidthja{\spx@opt@marginpar}},% - footskip=2\baselineskip,% - }{geometry}% - \AtBeginDocument - {% update a dimension used by the jsclasses - \ifx\@jsc@uplatextrue\@undefined\else\fullwidth\textwidth\fi - % for some reason, jreport normalizes all dimensions with \@settopoint - \@ifclassloaded{jreport} - {\@settopoint\textwidth\@settopoint\textheight\@settopoint\marginparwidth} - {}% <-- "false" clause of \@ifclassloaded - }% -\fi - -% fix fncychap's bug which uses prematurely the \textwidth value -\@ifpackagewith{fncychap}{Bjornstrup} - {\AtBeginDocument{\mylen\textwidth\advance\mylen-2\myhi}}% - {}% <-- "false" clause of \@ifpackagewith - - -%% TITLES -% -% Since Sphinx 1.5, users should use HeaderFamily key to 'sphinxsetup' rather -% than defining their own \py@HeaderFamily command (which is still possible). -% Memo: \py@HeaderFamily is also used by \maketitle as defined in -% sphinxmanual.cls/sphinxhowto.cls -\newcommand{\py@HeaderFamily}{\spx@opt@HeaderFamily} - -% This sets up the fancy chapter headings that make the documents look -% at least a little better than the usual LaTeX output. -\@ifpackagewith{fncychap}{Bjarne}{ - \ChNameVar {\raggedleft\normalsize \py@HeaderFamily} - \ChNumVar {\raggedleft\Large \py@HeaderFamily} - \ChTitleVar{\raggedleft\Large \py@HeaderFamily} - % This creates (numbered) chapter heads without the leading \vspace*{}: - \def\@makechapterhead#1{% - {\parindent \z@ \raggedright \normalfont - \ifnum \c@secnumdepth >\m@ne - \if@mainmatter - \DOCH - \fi - \fi - \interlinepenalty\@M - \if@mainmatter - \DOTI{#1}% - \else% - \DOTIS{#1}% - \fi - }} -}{}% <-- "false" clause of \@ifpackagewith - -% Augment the sectioning commands used to get our own font family in place, -% and reset some internal data items (\titleformat from titlesec package) -\titleformat{\section}{\Large\py@HeaderFamily}% - {\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\subsection}{\large\py@HeaderFamily}% - {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\subsubsection}{\py@HeaderFamily}% - {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} -% By default paragraphs (and subsubsections) will not be numbered because -% sphinxmanual.cls and sphinxhowto.cls set secnumdepth to 2 -\titleformat{\paragraph}{\py@HeaderFamily}% - {\py@TitleColor\theparagraph}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\subparagraph}{\py@HeaderFamily}% - {\py@TitleColor\thesubparagraph}{0.5em}{\py@TitleColor}{\py@NormalColor} - - -%% GRAPHICS -% -% \sphinxincludegraphics defined to resize images larger than the line width, -% except if height or width option present. -% -% If scale is present, rescale before fitting to line width. (since 1.5) -\newbox\spx@image@box -\newcommand*{\sphinxincludegraphics}[2][]{% - \in@{height}{#1}\ifin@\else\in@{width}{#1}\fi - \ifin@ % height or width present - \includegraphics[#1]{#2}% - \else % no height nor width (but #1 may be "scale=...") - \setbox\spx@image@box\hbox{\includegraphics[#1,draft]{#2}}% - \ifdim \wd\spx@image@box>\linewidth - \setbox\spx@image@box\box\voidb@x % clear memory - \includegraphics[#1,width=\linewidth]{#2}% - \else - \includegraphics[#1]{#2}% - \fi - \fi -} - - -%% FIGURE IN TABLE -% -\newenvironment{sphinxfigure-in-table}[1][\linewidth]{% - \def\@captype{figure}% - \sphinxsetvskipsforfigintablecaption - \begin{minipage}{#1}% -}{\end{minipage}} -% store original \caption macro for use with figures in longtable and tabulary -\AtBeginDocument{\let\spx@originalcaption\caption} -\newcommand*\sphinxfigcaption - {\ifx\equation$%$% this is trick to identify tabulary first pass - \firstchoice@false\else\firstchoice@true\fi - \spx@originalcaption } -\newcommand*\sphinxsetvskipsforfigintablecaption - {\abovecaptionskip\smallskipamount - \belowcaptionskip\smallskipamount} - - -%% FOOTNOTES -% -% Support large numbered footnotes in minipage -% But now obsolete due to systematic use of \savenotes/\spewnotes -% when minipages are in use in the various macro definitions next. -\def\thempfootnote{\arabic{mpfootnote}} - - -%% NUMBERING OF FIGURES, TABLES, AND LITERAL BLOCKS -\ltx@ifundefined{c@chapter} - {\newcounter{literalblock}}% - {\newcounter{literalblock}[chapter]% - \def\theliteralblock{\ifnum\c@chapter>\z@\arabic{chapter}.\fi - \arabic{literalblock}}% - }% -\ifspx@opt@nonumfigreset - \ltx@ifundefined{c@chapter}{}{% - \@removefromreset{figure}{chapter}% - \@removefromreset{table}{chapter}% - \@removefromreset{literalblock}{chapter}% - \ifspx@opt@mathnumfig - \@removefromreset{equation}{chapter}% - \fi - }% - \def\thefigure{\arabic{figure}}% - \def\thetable {\arabic{table}}% - \def\theliteralblock{\arabic{literalblock}}% - \ifspx@opt@mathnumfig - \def\theequation{\arabic{equation}}% - \fi -\else -\let\spx@preAthefigure\@empty -\let\spx@preBthefigure\@empty -% \ifspx@opt@usespart % <-- LaTeX writer could pass such a 'usespart' boolean -% % as sphinx.sty package option -% If document uses \part, (triggered in Sphinx by latex_toplevel_sectioning) -% LaTeX core per default does not reset chapter or section -% counters at each part. -% But if we modify this, we need to redefine \thechapter, \thesection to -% include the part number and this will cause problems in table of contents -% because of too wide numbering. Simplest is to do nothing. -% \fi -\ifnum\spx@opt@numfigreset>0 - \ltx@ifundefined{c@chapter} - {} - {\g@addto@macro\spx@preAthefigure{\ifnum\c@chapter>\z@\arabic{chapter}.}% - \g@addto@macro\spx@preBthefigure{\fi}}% -\fi -\ifnum\spx@opt@numfigreset>1 - \@addtoreset{figure}{section}% - \@addtoreset{table}{section}% - \@addtoreset{literalblock}{section}% - \ifspx@opt@mathnumfig - \@addtoreset{equation}{section}% - \fi - \g@addto@macro\spx@preAthefigure{\ifnum\c@section>\z@\arabic{section}.}% - \g@addto@macro\spx@preBthefigure{\fi}% -\fi -\ifnum\spx@opt@numfigreset>2 - \@addtoreset{figure}{subsection}% - \@addtoreset{table}{subsection}% - \@addtoreset{literalblock}{subsection}% - \ifspx@opt@mathnumfig - \@addtoreset{equation}{subsection}% - \fi - \g@addto@macro\spx@preAthefigure{\ifnum\c@subsection>\z@\arabic{subsection}.}% - \g@addto@macro\spx@preBthefigure{\fi}% -\fi -\ifnum\spx@opt@numfigreset>3 - \@addtoreset{figure}{subsubsection}% - \@addtoreset{table}{subsubsection}% - \@addtoreset{literalblock}{subsubsection}% - \ifspx@opt@mathnumfig - \@addtoreset{equation}{subsubsection}% - \fi - \g@addto@macro\spx@preAthefigure{\ifnum\c@subsubsection>\z@\arabic{subsubsection}.}% - \g@addto@macro\spx@preBthefigure{\fi}% -\fi -\ifnum\spx@opt@numfigreset>4 - \@addtoreset{figure}{paragraph}% - \@addtoreset{table}{paragraph}% - \@addtoreset{literalblock}{paragraph}% - \ifspx@opt@mathnumfig - \@addtoreset{equation}{paragraph}% - \fi - \g@addto@macro\spx@preAthefigure{\ifnum\c@subparagraph>\z@\arabic{subparagraph}.}% - \g@addto@macro\spx@preBthefigure{\fi}% -\fi -\ifnum\spx@opt@numfigreset>5 - \@addtoreset{figure}{subparagraph}% - \@addtoreset{table}{subparagraph}% - \@addtoreset{literalblock}{subparagraph}% - \ifspx@opt@mathnumfig - \@addtoreset{equation}{subparagraph}% - \fi - \g@addto@macro\spx@preAthefigure{\ifnum\c@subsubparagraph>\z@\arabic{subsubparagraph}.}% - \g@addto@macro\spx@preBthefigure{\fi}% -\fi -\expandafter\g@addto@macro -\expandafter\spx@preAthefigure\expandafter{\spx@preBthefigure}% -\let\thefigure\spx@preAthefigure -\let\thetable\spx@preAthefigure -\let\theliteralblock\spx@preAthefigure -\g@addto@macro\thefigure{\arabic{figure}}% -\g@addto@macro\thetable{\arabic{table}}% -\g@addto@macro\theliteralblock{\arabic{literalblock}}% - \ifspx@opt@mathnumfig - \let\theequation\spx@preAthefigure - \g@addto@macro\theequation{\arabic{equation}}% - \fi -\fi - - -%% LITERAL BLOCKS -% -% Based on use of "fancyvrb.sty"'s Verbatim. -% - with framing allowing page breaks ("framed.sty") -% - with breaking of long lines (exploits Pygments mark-up), -% - with possibly of a top caption, non-separable by pagebreak. -% - and usable inside tables or footnotes ("footnotehyper-sphinx"). - -% For extensions which use \OriginalVerbatim and compatibility with Sphinx < -% 1.5, we define and use these when (unmodified) Verbatim will be needed. But -% Sphinx >= 1.5 does not modify the \Verbatim macro anymore. -\let\OriginalVerbatim \Verbatim -\let\endOriginalVerbatim\endVerbatim - -% for captions of literal blocks -% at start of caption title -\newcommand*{\fnum@literalblock}{\literalblockname\nobreakspace\theliteralblock} -% this will be overwritten in document preamble by Babel translation -\newcommand*{\literalblockname}{Listing } -% file extension needed for \caption's good functioning, the file is created -% only if a \listof{literalblock}{foo} command is encountered, which is -% analogous to \listoffigures, but for the code listings (foo = chosen title.) -\newcommand*{\ext@literalblock}{lol} - -\newif\ifspx@inframed % flag set if we are already in a framed environment -% if forced use of minipage encapsulation is needed (e.g. table cells) -\newif\ifsphinxverbatimwithminipage \sphinxverbatimwithminipagefalse - -% Framing macro for use with framed.sty's \FrameCommand -% - it obeys current indentation, -% - frame is \fboxsep separated from the contents, -% - the contents use the full available text width, -% - #1 = color of frame, #2 = color of background, -% - #3 = above frame, #4 = below frame, #5 = within frame, -% - #3 and #4 must be already typeset boxes; they must issue \normalcolor -% or similar, else, they are under scope of color #1 -\long\def\spx@fcolorbox #1#2#3#4#5{% - \hskip\@totalleftmargin - \hskip-\fboxsep\hskip-\fboxrule - % use of \color@b@x here is compatible with both xcolor.sty and color.sty - \color@b@x {\color{#1}\spx@CustomFBox{#3}{#4}}{\color{#2}}{#5}% - \hskip-\fboxsep\hskip-\fboxrule - \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth -}% -% #1 = for material above frame, such as a caption or a "continued" hint -% #2 = for material below frame, such as a caption or "continues on next page" -% #3 = actual contents, which will be typeset with a background color -\long\def\spx@CustomFBox#1#2#3{% - \begingroup - \setbox\@tempboxa\hbox{{#3}}% inner braces to avoid color leaks - \vbox{#1% above frame - % draw frame border _latest_ to avoid pdf viewer issue - \kern\fboxrule - \hbox{\kern\fboxrule - \copy\@tempboxa - \kern-\wd\@tempboxa\kern-\fboxrule - \vrule\@width\fboxrule - \kern\wd\@tempboxa - \vrule\@width\fboxrule}% - \kern-\dimexpr\ht\@tempboxa+\dp\@tempboxa+\fboxrule\relax - \hrule\@height\fboxrule - \kern\dimexpr\ht\@tempboxa+\dp\@tempboxa\relax - \hrule\@height\fboxrule - #2% below frame - }% - \endgroup -}% -\def\spx@fcolorbox@put@c#1{% hide width from framed.sty measuring - \moveright\dimexpr\fboxrule+.5\wd\@tempboxa\hb@xt@\z@{\hss#1\hss}% -}% -\def\spx@fcolorbox@put@r#1{% right align with contents, width hidden - \moveright\dimexpr\fboxrule+\wd\@tempboxa-\fboxsep\hb@xt@\z@{\hss#1}% -}% -\def\spx@fcolorbox@put@l#1{% left align with contents, width hidden - \moveright\dimexpr\fboxrule+\fboxsep\hb@xt@\z@{#1\hss}% -}% -% -\def\sphinxVerbatim@Continued - {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuedalign\endcsname - {\normalcolor\sphinxstylecodecontinued\literalblockcontinuedname}}% -\def\sphinxVerbatim@Continues - {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuesalign\endcsname - {\normalcolor\sphinxstylecodecontinues\literalblockcontinuesname}}% -\def\sphinxVerbatim@Title - {\spx@fcolorbox@put@c{\unhcopy\sphinxVerbatim@TitleBox}}% -\let\sphinxVerbatim@Before\@empty -\let\sphinxVerbatim@After\@empty -% Defaults are redefined in document preamble according to language -\newcommand*\literalblockcontinuedname{continued from previous page}% -\newcommand*\literalblockcontinuesname{continues on next page}% -% -\def\spx@verbatimfcolorbox{\spx@fcolorbox{VerbatimBorderColor}{VerbatimColor}}% -\def\sphinxVerbatim@FrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@After}% -\def\sphinxVerbatim@FirstFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@Continues}% -\def\sphinxVerbatim@MidFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@Continues}% -\def\sphinxVerbatim@LastFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@After}% - -% For linebreaks inside Verbatim environment from package fancyvrb. -\newbox\sphinxcontinuationbox -\newbox\sphinxvisiblespacebox -\newcommand*\sphinxafterbreak {\copy\sphinxcontinuationbox} - -% Take advantage of the already applied Pygments mark-up to insert -% potential linebreaks for TeX processing. -% {, <, #, %, $, ' and ": go to next line. -% _, }, ^, &, >, - and ~: stay at end of broken line. -% Use of \textquotesingle for straight quote. -% FIXME: convert this to package options ? -\newcommand*\sphinxbreaksbeforelist {% - \do\PYGZob\{\do\PYGZlt\<\do\PYGZsh\#\do\PYGZpc\%% {, <, #, %, - \do\PYGZdl\$\do\PYGZdq\"% $, " - \def\PYGZsq - {\discretionary{}{\sphinxafterbreak\textquotesingle}{\textquotesingle}}% ' -} -\newcommand*\sphinxbreaksafterlist {% - \do\PYGZus\_\do\PYGZcb\}\do\PYGZca\^\do\PYGZam\&% _, }, ^, &, - \do\PYGZgt\>\do\PYGZhy\-\do\PYGZti\~% >, -, ~ -} -\newcommand*\sphinxbreaksatspecials {% - \def\do##1##2% - {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% - \sphinxbreaksbeforelist - \def\do##1##2% - {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% - \sphinxbreaksafterlist -} - -\def\sphinx@verbatim@nolig@list {\do \`}% -% Some characters . , ; ? ! / are not pygmentized. -% This macro makes them "active" and they will insert potential linebreaks. -% Not compatible with math mode (cf \sphinxunactivateextras). -\newcommand*\sphinxbreaksbeforeactivelist {}% none -\newcommand*\sphinxbreaksafteractivelist {\do\.\do\,\do\;\do\?\do\!\do\/} -\newcommand*\sphinxbreaksviaactive {% - \def\do##1{\lccode`\~`##1% - \lowercase{\def~}{\discretionary{}{\sphinxafterbreak\char`##1}{\char`##1}}% - \catcode`##1\active}% - \sphinxbreaksbeforeactivelist - \def\do##1{\lccode`\~`##1% - \lowercase{\def~}{\discretionary{\char`##1}{\sphinxafterbreak}{\char`##1}}% - \catcode`##1\active}% - \sphinxbreaksafteractivelist - \lccode`\~`\~ -} - -% If the linebreak is at a space, the latter will be displayed as visible -% space at end of first line, and a continuation symbol starts next line. -\def\spx@verbatim@space {% - \nobreak\hskip\z@skip - \discretionary{\copy\sphinxvisiblespacebox}{\sphinxafterbreak} - {\kern\fontdimen2\font}% -}% - -% if the available space on page is less than \literalblockneedspace, insert pagebreak -\newcommand{\sphinxliteralblockneedspace}{5\baselineskip} -\newcommand{\sphinxliteralblockwithoutcaptionneedspace}{1.5\baselineskip} -% The title (caption) is specified from outside as macro \sphinxVerbatimTitle. -% \sphinxVerbatimTitle is reset to empty after each use of Verbatim. -\newcommand*\sphinxVerbatimTitle {} -% This box to typeset the caption before framed.sty multiple passes for framing. -\newbox\sphinxVerbatim@TitleBox -% This is a workaround to a "feature" of French lists, when literal block -% follows immediately; usable generally (does only \par then), a priori... -\newcommand*\sphinxvspacefixafterfrenchlists{% - \ifvmode\ifdim\lastskip<\z@ \vskip\parskip\fi\else\par\fi -} -% Holder macro for labels of literal blocks. Set-up by LaTeX writer. -\newcommand*\sphinxLiteralBlockLabel {} -\newcommand*\sphinxSetupCaptionForVerbatim [1] -{% - \sphinxvspacefixafterfrenchlists - \needspace{\sphinxliteralblockneedspace}% -% insert a \label via \sphinxLiteralBlockLabel -% reset to normal the color for the literal block caption - \def\sphinxVerbatimTitle - {\py@NormalColor\sphinxcaption{\sphinxLiteralBlockLabel #1}}% -} -\newcommand*\sphinxSetupCodeBlockInFootnote {% - \fvset{fontsize=\footnotesize}\let\caption\sphinxfigcaption - \sphinxverbatimwithminipagetrue % reduces vertical spaces - % we counteract (this is in a group) the \@normalsize from \caption - \let\normalsize\footnotesize\let\@parboxrestore\relax - \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% -} -% needed to create wrapper environments of fancyvrb's Verbatim -\newcommand*{\sphinxVerbatimEnvironment}{\gdef\FV@EnvironName{sphinxVerbatim}} -\newcommand*{\sphinxverbatimsmallskipamount}{\smallskipamount} -% serves to implement line highlighting and line wrapping -\newcommand\sphinxFancyVerbFormatLine[1]{% - \expandafter\sphinx@verbatim@checkifhl\expandafter{\the\FV@CodeLineNo}% - \ifin@ - \sphinxVerbatimHighlightLine{#1}% - \else - \sphinxVerbatimFormatLine{#1}% - \fi -}% -\newcommand\sphinxVerbatimHighlightLine[1]{% - \edef\sphinxrestorefboxsep{\fboxsep\the\fboxsep\relax}% - \fboxsep0pt\relax % cf LaTeX bug graphics/4524 - \colorbox{sphinxVerbatimHighlightColor}% - {\sphinxrestorefboxsep\sphinxVerbatimFormatLine{#1}}% - % no need to restore \fboxsep here, as this ends up in a \hbox from fancyvrb -}% -% \sphinxVerbatimFormatLine will be set locally to one of those two: -\newcommand\sphinxVerbatimFormatLineWrap[1]{% - \hsize\linewidth - \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@ - \doublehyphendemerits\z@\finalhyphendemerits\z@ - \strut #1\strut}% -}% -\newcommand\sphinxVerbatimFormatLineNoWrap[1]{\hb@xt@\linewidth{\strut #1\hss}}% -\g@addto@macro\FV@SetupFont{% - \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% - \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% -}% -\newenvironment{sphinxVerbatim}{% - % first, let's check if there is a caption - \ifx\sphinxVerbatimTitle\empty - \sphinxvspacefixafterfrenchlists - \parskip\z@skip - \vskip\sphinxverbatimsmallskipamount - % there was no caption. Check if nevertheless a label was set. - \ifx\sphinxLiteralBlockLabel\empty\else - % we require some space to be sure hyperlink target from \phantomsection - % will not be separated from upcoming verbatim by a page break - \needspace{\sphinxliteralblockwithoutcaptionneedspace}% - \phantomsection\sphinxLiteralBlockLabel - \fi - \else - \parskip\z@skip - \if t\spx@opt@literalblockcappos - \vskip\spx@abovecaptionskip - \def\sphinxVerbatim@Before - {\sphinxVerbatim@Title\nointerlineskip - \kern\dimexpr-\dp\strutbox+\sphinxbelowcaptionspace\relax}% - \else - \vskip\sphinxverbatimsmallskipamount - \def\sphinxVerbatim@After - {\nointerlineskip\kern\dp\strutbox\sphinxVerbatim@Title}% - \fi - \def\@captype{literalblock}% - \capstart - % \sphinxVerbatimTitle must reset color - \setbox\sphinxVerbatim@TitleBox - \hbox{\begin{minipage}{\linewidth}% - \sphinxVerbatimTitle - \end{minipage}}% - \fi - \global\let\sphinxLiteralBlockLabel\empty - \global\let\sphinxVerbatimTitle\empty - \fboxsep\sphinxverbatimsep \fboxrule\sphinxverbatimborder - \ifspx@opt@verbatimwithframe\else\fboxrule\z@\fi - \let\FrameCommand \sphinxVerbatim@FrameCommand - \let\FirstFrameCommand\sphinxVerbatim@FirstFrameCommand - \let\MidFrameCommand \sphinxVerbatim@MidFrameCommand - \let\LastFrameCommand \sphinxVerbatim@LastFrameCommand - \ifspx@opt@verbatimhintsturnover\else - \let\sphinxVerbatim@Continued\@empty - \let\sphinxVerbatim@Continues\@empty - \fi - \ifspx@opt@verbatimwrapslines - % fancyvrb's Verbatim puts each input line in (unbreakable) horizontal boxes. - % This customization wraps each line from the input in a \vtop, thus - % allowing it to wrap and display on two or more lines in the latex output. - % - The codeline counter will be increased only once. - % - The wrapped material will not break across pages, it is impossible - % to achieve this without extensive rewrite of fancyvrb. - % - The (not used in sphinx) obeytabs option to Verbatim is - % broken by this change (showtabs and tabspace work). - \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineWrap - \let\FV@Space\spx@verbatim@space - % Allow breaks at special characters using \PYG... macros. - \sphinxbreaksatspecials - % Breaks at punctuation characters . , ; ? ! and / (needs catcode activation) - \fvset{codes*=\sphinxbreaksviaactive}% - \else % end of conditional code for wrapping long code lines - \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineNoWrap - \fi - \let\FancyVerbFormatLine\sphinxFancyVerbFormatLine - % workaround to fancyvrb's check of \@currenvir - \let\VerbatimEnvironment\sphinxVerbatimEnvironment - % workaround to fancyvrb's check of current list depth - \def\@toodeep {\advance\@listdepth\@ne}% - % The list environment is needed to control perfectly the vertical space. - % Note: \OuterFrameSep used by framed.sty is later set to \topsep hence 0pt. - % - if caption: distance from last text baseline to caption baseline is - % A+(B-F)+\ht\strutbox, A = \abovecaptionskip (default 10pt), B = - % \baselineskip, F is the framed.sty \FrameHeightAdjust macro, default 6pt. - % Formula valid for F < 10pt. - % - distance of baseline of caption to top of frame is like for tables: - % \sphinxbelowcaptionspace (=0.5\baselineskip) - % - if no caption: distance of last text baseline to code frame is S+(B-F), - % with S = \sphinxverbatimtopskip (=\smallskip) - % - and distance from bottom of frame to next text baseline is - % \baselineskip+\parskip. - % The \trivlist is used to avoid possible "too deeply nested" error. - \itemsep \z@skip - \topsep \z@skip - \partopsep \z@skip - % trivlist will set \parsep to \parskip = zero - % \leftmargin will be set to zero by trivlist - \rightmargin\z@ - \parindent \z@% becomes \itemindent. Default zero, but perhaps overwritten. - \trivlist\item\relax - \ifsphinxverbatimwithminipage\spx@inframedtrue\fi - % use a minipage if we are already inside a framed environment - \ifspx@inframed\noindent\begin{minipage}{\linewidth}\fi - \MakeFramed {% adapted over from framed.sty's snugshade environment - \advance\hsize-\width\@totalleftmargin\z@\linewidth\hsize\@setminipage - }% - % For grid placement from \strut's in \FancyVerbFormatLine - \lineskip\z@skip - % active comma should not be overwritten by \@noligs - \ifspx@opt@verbatimwrapslines - \let\verbatim@nolig@list \sphinx@verbatim@nolig@list - \fi - % will fetch its optional arguments if any - \OriginalVerbatim -} -{% - \endOriginalVerbatim - \par\unskip\@minipagefalse\endMakeFramed % from framed.sty snugshade - \ifspx@inframed\end{minipage}\fi - \endtrivlist -} -\newenvironment {sphinxVerbatimNoFrame} - {\spx@opt@verbatimwithframefalse - % needed for fancyvrb as literal code will end in \end{sphinxVerbatimNoFrame} - \def\sphinxVerbatimEnvironment{\gdef\FV@EnvironName{sphinxVerbatimNoFrame}}% - \begin{sphinxVerbatim}} - {\end{sphinxVerbatim}} -\newenvironment {sphinxVerbatimintable} - {% don't use a frame if in a table cell - \spx@opt@verbatimwithframefalse - \sphinxverbatimwithminipagetrue - % the literal block caption uses \sphinxcaption which is wrapper of \caption, - % but \caption must be modified because longtable redefines it to work only - % for the own table caption, and tabulary has multiple passes - \let\caption\sphinxfigcaption - % reduce above caption skip - \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% - \def\sphinxVerbatimEnvironment{\gdef\FV@EnvironName{sphinxVerbatimintable}}% - \begin{sphinxVerbatim}} - {\end{sphinxVerbatim}} - - -%% PARSED LITERALS -% allow long lines to wrap like they do in code-blocks - -% this should be kept in sync with definitions in sphinx.util.texescape -\newcommand*\sphinxbreaksattexescapedchars{% - \def\do##1##2% put potential break point before character - {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% - \do\{\{\do\textless\<\do\#\#\do\%\%\do\$\$% {, <, #, %, $ - \def\do##1##2% put potential break point after character - {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% - \do\_\_\do\}\}\do\textasciicircum\^\do\&\&% _, }, ^, &, - \do\textgreater\>\do\textasciitilde\~% >, ~ -} -\newcommand*\sphinxbreaksviaactiveinparsedliteral{% - \sphinxbreaksviaactive % by default handles . , ; ? ! / - \do\-% we need also the hyphen character (ends up "as is" in parsed-literal) - \lccode`\~`\~ % - % update \dospecials as it is used by \url - % but deactivation will already have been done hence this is unneeded: - % \expandafter\def\expandafter\dospecials\expandafter{\dospecials - % \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist\do\-}% -} -\newcommand*\sphinxbreaksatspaceinparsedliteral{% - \lccode`~32 \lowercase{\let~}\spx@verbatim@space\lccode`\~`\~ -} -\newcommand*{\sphinxunactivateextras}{\let\do\@makeother - \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist\do\-}% -% the \catcode13=5\relax (deactivate end of input lines) is left to callers -\newcommand*{\sphinxunactivateextrasandspace}{\catcode32=10\relax - \sphinxunactivateextras}% -% now for the modified alltt environment -\newenvironment{sphinxalltt} -{% at start of next line to workaround Emacs/AUCTeX issue with this file -\begin{alltt}% - \ifspx@opt@parsedliteralwraps - \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% - \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% - \sphinxbreaksattexescapedchars - \sphinxbreaksviaactiveinparsedliteral - \sphinxbreaksatspaceinparsedliteral -% alltt takes care of the ' as derivative ("prime") in math mode - \everymath\expandafter{\the\everymath\sphinxunactivateextrasandspace - \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% -% not sure if displayed math (align,...) can end up in parsed-literal, anyway - \everydisplay\expandafter{\the\everydisplay - \catcode13=5 \sphinxunactivateextrasandspace - \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% - \fi } -{\end{alltt}} - -% Protect \href's first argument in contexts such as sphinxalltt (or -% \sphinxcode). Sphinx uses \#, \%, \& ... always inside \sphinxhref. -\protected\def\sphinxhref#1#2{{% - \sphinxunactivateextrasandspace % never do \scantokens with active space! - \endlinechar\m@ne\everyeof{{#2}}% keep catcode regime for #2 - \scantokens{\href{#1}}% normalise it for #1 during \href expansion -}} -% Same for \url. And also \nolinkurl for coherence. -\protected\def\sphinxurl#1{{% - \sphinxunactivateextrasandspace\everyeof{}% (<- precaution for \scantokens) - \endlinechar\m@ne\scantokens{\url{#1}}% -}} -\protected\def\sphinxnolinkurl#1{{% - \sphinxunactivateextrasandspace\everyeof{}% - \endlinechar\m@ne\scantokens{\nolinkurl{#1}}% -}} - - -%% TOPIC AND CONTENTS BOXES -% -% Again based on use of "framed.sty", this allows breakable framed boxes. -\long\def\spx@ShadowFBox#1{% - \leavevmode\begingroup - % first we frame the box #1 - \setbox\@tempboxa - \hbox{\vrule\@width\sphinxshadowrule - \vbox{\hrule\@height\sphinxshadowrule - \kern\sphinxshadowsep - \hbox{\kern\sphinxshadowsep #1\kern\sphinxshadowsep}% - \kern\sphinxshadowsep - \hrule\@height\sphinxshadowrule}% - \vrule\@width\sphinxshadowrule}% - % Now we add the shadow, like \shadowbox from fancybox.sty would do - \dimen@\dimexpr.5\sphinxshadowrule+\sphinxshadowsize\relax - \hbox{\vbox{\offinterlineskip - \hbox{\copy\@tempboxa\kern-.5\sphinxshadowrule - % add shadow on right side - \lower\sphinxshadowsize - \hbox{\vrule\@height\ht\@tempboxa \@width\dimen@}% - }% - \kern-\dimen@ % shift back vertically to bottom of frame - % and add shadow at bottom - \moveright\sphinxshadowsize - \vbox{\hrule\@width\wd\@tempboxa \@height\dimen@}% - }% - % move left by the size of right shadow so shadow adds no width - \kern-\sphinxshadowsize - }% - \endgroup -} - -% use framed.sty to allow page breaks in frame+shadow -% works well inside Lists and Quote-like environments -% produced by ``topic'' directive (or local contents) -% could nest if LaTeX writer authorized it -\newenvironment{sphinxShadowBox} - {\def\FrameCommand {\spx@ShadowFBox }% - % configure framed.sty not to add extra vertical spacing - \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% - % the \trivlist will add the vertical spacing on top and bottom which is - % typical of center environment as used in Sphinx <= 1.4.1 - % the \noindent has the effet of an extra blank line on top, to - % imitate closely the layout from Sphinx <= 1.4.1; the \FrameHeightAdjust - % will put top part of frame on this baseline. - \def\FrameHeightAdjust {\baselineskip}% - % use package footnote to handle footnotes - \savenotes - \trivlist\item\noindent - % use a minipage if we are already inside a framed environment - \ifspx@inframed\begin{minipage}{\linewidth}\fi - \MakeFramed {\spx@inframedtrue - % framed.sty puts into "\width" the added width (=2shadowsep+2shadowrule) - % adjust \hsize to what the contents must use - \advance\hsize-\width - % adjust LaTeX parameters to behave properly in indented/quoted contexts - \FrameRestore - % typeset the contents as in a minipage (Sphinx <= 1.4.1 used a minipage and - % itemize/enumerate are therein typeset more tightly, we want to keep - % that). We copy-paste from LaTeX source code but don't do a real minipage. - \@pboxswfalse - \let\@listdepth\@mplistdepth \@mplistdepth\z@ - \@minipagerestore - \@setminipage - }% - }% - {% insert the "endminipage" code - \par\unskip - \@minipagefalse - \endMakeFramed - \ifspx@inframed\end{minipage}\fi - \endtrivlist - % output the stored footnotes - \spewnotes - } - - -%% NOTICES AND ADMONITIONS -% -% Some are quite plain -% the spx@notice@bordercolor etc are set in the sphinxadmonition environment -\newenvironment{sphinxlightbox}{% - \par\allowbreak - \noindent{\color{spx@notice@bordercolor}% - \rule{\linewidth}{\spx@notice@border}}\par\nobreak - {\parskip\z@skip\noindent}% - } - {% - % counteract previous possible negative skip (French lists!): - % (we can't cancel that any earlier \vskip introduced a potential pagebreak) - \sphinxvspacefixafterfrenchlists - \nobreak\vbox{\noindent\kern\@totalleftmargin - {\color{spx@notice@bordercolor}% - \rule[\dimexpr.4\baselineskip-\spx@notice@border\relax] - {\linewidth}{\spx@notice@border}}\hss}\allowbreak - }% end of sphinxlightbox environment definition -% may be renewenvironment'd by user for complete customization -\newenvironment{sphinxnote}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinxhint}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinximportant}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinxtip}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -% or just use the package options -% these are needed for common handling by notice environment of lightbox -% and heavybox but they are currently not used by lightbox environment -% and there is consequently no corresponding package option -\definecolor{sphinxnoteBgColor}{rgb}{1,1,1} -\definecolor{sphinxhintBgColor}{rgb}{1,1,1} -\definecolor{sphinximportantBgColor}{rgb}{1,1,1} -\definecolor{sphinxtipBgColor}{rgb}{1,1,1} - -% Others get more distinction -% Code adapted from framed.sty's "snugshade" environment. -% Nesting works (inner frames do not allow page breaks). -\newenvironment{sphinxheavybox}{\par - \setlength{\FrameRule}{\spx@notice@border}% - \setlength{\FrameSep}{\dimexpr.6\baselineskip-\FrameRule\relax} - % configure framed.sty's parameters to obtain same vertical spacing - % as for "light" boxes. We need for this to manually insert parskip glue and - % revert a skip done by framed before the frame. - \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% - \vspace{\FrameHeightAdjust} - % copied/adapted from framed.sty's snugshade - \def\FrameCommand##1{\hskip\@totalleftmargin - \fboxsep\FrameSep \fboxrule\FrameRule - \fcolorbox{spx@notice@bordercolor}{spx@notice@bgcolor}{##1}% - \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% - \savenotes - % use a minipage if we are already inside a framed environment - \ifspx@inframed - \noindent\begin{minipage}{\linewidth} - \else - % handle case where notice is first thing in a list item (or is quoted) - \if@inlabel - \noindent\par\vspace{-\baselineskip} - \else - \vspace{\parskip} - \fi - \fi - \MakeFramed {\spx@inframedtrue - \advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize - % minipage initialization copied from LaTeX source code. - \@pboxswfalse - \let\@listdepth\@mplistdepth \@mplistdepth\z@ - \@minipagerestore - \@setminipage }% - } - {% - \par\unskip - \@minipagefalse - \endMakeFramed - \ifspx@inframed\end{minipage}\fi - % set footnotes at bottom of page - \spewnotes - % arrange for similar spacing below frame as for "light" boxes. - \vskip .4\baselineskip - }% end of sphinxheavybox environment definition -% may be renewenvironment'd by user for complete customization -\newenvironment{sphinxwarning}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxcaution}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxattention}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxdanger}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxerror}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -% or just use package options - -% the \colorlet of xcolor (if at all loaded) is overkill for our use case -\newcommand{\sphinxcolorlet}[2] - {\expandafter\let\csname\@backslashchar color@#1\expandafter\endcsname - \csname\@backslashchar color@#2\endcsname } - -% the main dispatch for all types of notices -\newenvironment{sphinxadmonition}[2]{% #1=type, #2=heading - % can't use #1 directly in definition of end part - \def\spx@noticetype {#1}% - % set parameters of heavybox/lightbox - \sphinxcolorlet{spx@notice@bordercolor}{sphinx#1BorderColor}% - \sphinxcolorlet{spx@notice@bgcolor}{sphinx#1BgColor}% - \spx@notice@border \dimexpr\csname spx@opt@#1border\endcsname\relax - % start specific environment, passing the heading as argument - \begin{sphinx#1}{#2}} - % workaround some LaTeX "feature" of \end command - {\edef\spx@temp{\noexpand\end{sphinx\spx@noticetype}}\spx@temp} - - -%% PYTHON DOCS MACROS AND ENVIRONMENTS -% (some macros here used by \maketitle in sphinxmanual.cls and sphinxhowto.cls) - -% \moduleauthor{name}{email} -\newcommand{\moduleauthor}[2]{} - -% \sectionauthor{name}{email} -\newcommand{\sectionauthor}[2]{} - -% Allow the release number to be specified independently of the -% \date{}. This allows the date to reflect the document's date and -% release to specify the release that is documented. -% -\newcommand{\py@release}{\releasename\space\version} -\newcommand{\version}{}% part of \py@release, used by title page and headers -% \releaseinfo is used on titlepage (sphinxmanual.cls, sphinxhowto.cls) -\newcommand{\releaseinfo}{} -\newcommand{\setreleaseinfo}[1]{\renewcommand{\releaseinfo}{#1}} -% this is inserted via template and #1=release config variable -\newcommand{\release}[1]{\renewcommand{\version}{#1}} -% this is defined by template to 'releasename' latex_elements key -\newcommand{\releasename}{} -% Fix issue in case release and releasename deliberately left blank -\newcommand{\sphinxheadercomma}{, }% used in fancyhdr header definition -\newcommand{\sphinxifemptyorblank}[1]{% -% test after one expansion of macro #1 if contents is empty or spaces - \if&\expandafter\@firstofone\detokenize\expandafter{#1}&% - \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}% -\AtBeginDocument {% - \sphinxifemptyorblank{\releasename} - {\sphinxifemptyorblank{\version}{\let\sphinxheadercomma\empty}{}} - {}% -}% - -% Allow specification of the author's address separately from the -% author's name. This can be used to format them differently, which -% is a good thing. -% -\newcommand{\py@authoraddress}{} -\newcommand{\authoraddress}[1]{\renewcommand{\py@authoraddress}{#1}} - -% {fulllineitems} is the main environment for object descriptions. -% -\newcommand{\py@itemnewline}[1]{% - \kern\labelsep - \@tempdima\linewidth - \advance\@tempdima \labelwidth\makebox[\@tempdima][l]{#1}% - \kern-\labelsep -} - -\newenvironment{fulllineitems}{% - \begin{list}{}{\labelwidth \leftmargin - \rightmargin \z@ \topsep -\parskip \partopsep \parskip - \itemsep -\parsep - \let\makelabel=\py@itemnewline}% -}{\end{list}} - -% Signatures, possibly multi-line -% -\newlength{\py@argswidth} -\newcommand{\py@sigparams}[2]{% - \parbox[t]{\py@argswidth}{#1\sphinxcode{)}#2}} -\newcommand{\pysigline}[1]{\item[{#1}]} -\newcommand{\pysiglinewithargsret}[3]{% - \settowidth{\py@argswidth}{#1\sphinxcode{(}}% - \addtolength{\py@argswidth}{-2\py@argswidth}% - \addtolength{\py@argswidth}{\linewidth}% - \item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}}]} -\newcommand{\pysigstartmultiline}{% - \def\pysigstartmultiline{\vskip\smallskipamount\parskip\z@skip\itemsep\z@skip}% - \edef\pysigstopmultiline - {\noexpand\leavevmode\parskip\the\parskip\relax\itemsep\the\itemsep\relax}% - \parskip\z@skip\itemsep\z@skip -} - -% Production lists -% -\newenvironment{productionlist}{% -% \def\sphinxoptional##1{{\Large[}##1{\Large]}} - \def\production##1##2{\\\sphinxcode{\sphinxupquote{##1}}&::=&\sphinxcode{\sphinxupquote{##2}}}% - \def\productioncont##1{\\& &\sphinxcode{\sphinxupquote{##1}}}% - \parindent=2em - \indent - \setlength{\LTpre}{0pt}% - \setlength{\LTpost}{0pt}% - \begin{longtable}[l]{lcl} -}{% - \end{longtable} -} - -% Definition lists; requested by AMK for HOWTO documents. Probably useful -% elsewhere as well, so keep in in the general style support. -% -\newenvironment{definitions}{% - \begin{description}% - \def\term##1{\item[{##1}]\mbox{}\\*[0mm]}% -}{% - \end{description}% -} - -%% FROM DOCTUTILS LATEX WRITER -% -% The following is stuff copied from docutils' latex writer. -% -\newcommand{\optionlistlabel}[1]{\normalfont\bfseries #1 \hfill}% \bf deprecated -\newenvironment{optionlist}[1] -{\begin{list}{} - {\setlength{\labelwidth}{#1} - \setlength{\rightmargin}{1cm} - \setlength{\leftmargin}{\rightmargin} - \addtolength{\leftmargin}{\labelwidth} - \addtolength{\leftmargin}{\labelsep} - \renewcommand{\makelabel}{\optionlistlabel}} -}{\end{list}} - -\newlength{\lineblockindentation} -\setlength{\lineblockindentation}{2.5em} -\newenvironment{lineblock}[1] -{\begin{list}{} - {\setlength{\partopsep}{\parskip} - \addtolength{\partopsep}{\baselineskip} - \topsep0pt\itemsep0.15\baselineskip\parsep0pt - \leftmargin#1\relax} - \raggedright} -{\end{list}} - -% From docutils.writers.latex2e -% inline markup (custom roles) -% \DUrole{#1}{#2} tries \DUrole#1{#2} -\providecommand*{\DUrole}[2]{% - \ifcsname DUrole\detokenize{#1}\endcsname - \csname DUrole\detokenize{#1}\endcsname{#2}% - \else% backwards compatibility: try \docutilsrole#1{#2} - \ifcsname docutilsrole\detokenize{#1}\endcsname - \csname docutilsrole\detokenize{#1}\endcsname{#2}% - \else - #2% - \fi - \fi -} - -\providecommand*{\DUprovidelength}[2]{% - \ifdefined#1\else\newlength{#1}\setlength{#1}{#2}\fi -} - -\DUprovidelength{\DUlineblockindent}{2.5em} -\ifdefined\DUlineblock\else - \newenvironment{DUlineblock}[1]{% - \list{}{\setlength{\partopsep}{\parskip} - \addtolength{\partopsep}{\baselineskip} - \setlength{\topsep}{0pt} - \setlength{\itemsep}{0.15\baselineskip} - \setlength{\parsep}{0pt} - \setlength{\leftmargin}{#1}} - \raggedright - } - {\endlist} -\fi - -%% TEXT STYLING -% -% to obtain straight quotes we execute \@noligs as patched by upquote, and -% \scantokens is needed in cases where it would be too late for the macro to -% first set catcodes and then fetch its argument. We also make the contents -% breakable at non-escaped . , ; ? ! / using \sphinxbreaksviaactive. -% the macro must be protected if it ends up used in moving arguments, -% in 'alltt' \@noligs is done already, and the \scantokens must be avoided. -\protected\def\sphinxupquote#1{{\def\@tempa{alltt}% - \ifx\@tempa\@currenvir\else - \ifspx@opt@inlineliteralwraps - \sphinxbreaksviaactive\let\sphinxafterbreak\empty - % do not overwrite the comma set-up - \let\verbatim@nolig@list\sphinx@literal@nolig@list - \fi - % fix a space-gobbling issue due to LaTeX's original \do@noligs - \let\do@noligs\sphinx@do@noligs - \@noligs\endlinechar\m@ne\everyeof{}% (<- in case inside \sphinxhref) - \expandafter\scantokens - \fi {{#1}}}}% extra brace pair to fix end-space gobbling issue... -\def\sphinx@do@noligs #1{\catcode`#1\active\begingroup\lccode`\~`#1\relax - \lowercase{\endgroup\def~{\leavevmode\kern\z@\char`#1 }}} -\def\sphinx@literal@nolig@list {\do\`\do\<\do\>\do\'\do\-}% - -% Some custom font markup commands. -\protected\def\sphinxstrong#1{\textbf{#1}} -\protected\def\sphinxcode#1{\texttt{#1}} -\protected\def\sphinxbfcode#1{\textbf{\sphinxcode{#1}}} -\protected\def\sphinxemail#1{\textsf{#1}} -\protected\def\sphinxtablecontinued#1{\textsf{#1}} -\protected\def\sphinxtitleref#1{\emph{#1}} -\protected\def\sphinxmenuselection#1{\emph{#1}} -\protected\def\sphinxaccelerator#1{\underline{#1}} -\protected\def\sphinxcrossref#1{\emph{#1}} -\protected\def\sphinxtermref#1{\emph{#1}} -% \optional is used for ``[, arg]``, i.e. desc_optional nodes. -\long\protected\def\sphinxoptional#1{% - {\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}} - -% additional customizable styling -% FIXME: convert this to package options ? -\protected\def\sphinxstyleindexentry #1{\texttt{#1}} -\protected\def\sphinxstyleindexextra #1{ \emph{(#1)}} -\protected\def\sphinxstyleindexpageref #1{, \pageref{#1}} -\protected\def\sphinxstyletopictitle #1{\textbf{#1}\par\medskip} -\let\sphinxstylesidebartitle\sphinxstyletopictitle -\protected\def\sphinxstyleothertitle #1{\textbf{#1}} -\protected\def\sphinxstylesidebarsubtitle #1{~\\\textbf{#1} \smallskip} -% \text.. commands do not allow multiple paragraphs -\protected\def\sphinxstyletheadfamily {\sffamily} -\protected\def\sphinxstyleemphasis #1{\emph{#1}} -\protected\def\sphinxstyleliteralemphasis#1{\emph{\sphinxcode{#1}}} -\protected\def\sphinxstylestrong #1{\textbf{#1}} -\protected\def\sphinxstyleliteralstrong#1{\sphinxbfcode{#1}} -\protected\def\sphinxstyleabbreviation #1{\textsc{#1}} -\protected\def\sphinxstyleliteralintitle#1{\sphinxcode{#1}} -\newcommand*\sphinxstylecodecontinued[1]{\footnotesize(#1)}% -\newcommand*\sphinxstylecodecontinues[1]{\footnotesize(#1)}% -% figure legend comes after caption and may contain arbitrary body elements -\newenvironment{sphinxlegend}{\par\small}{\par} - -% For curly braces inside \index macro -\def\sphinxleftcurlybrace{\{} -\def\sphinxrightcurlybrace{\}} - -% Declare Unicode characters used by linux tree command to pdflatex utf8/utf8x -\def\spx@bd#1#2{% - \leavevmode - \begingroup - \ifx\spx@bd@height \@undefined\def\spx@bd@height{\baselineskip}\fi - \ifx\spx@bd@width \@undefined\setbox0\hbox{0}\def\spx@bd@width{\wd0 }\fi - \ifx\spx@bd@thickness\@undefined\def\spx@bd@thickness{.6\p@}\fi - \ifx\spx@bd@lower \@undefined\def\spx@bd@lower{\dp\strutbox}\fi - \lower\spx@bd@lower#1{#2}% - \endgroup -}% -\@namedef{sphinx@u2500}% BOX DRAWINGS LIGHT HORIZONTAL - {\spx@bd{\vbox to\spx@bd@height} - {\vss\hrule\@height\spx@bd@thickness - \@width\spx@bd@width\vss}}% -\@namedef{sphinx@u2502}% BOX DRAWINGS LIGHT VERTICAL - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss\vrule\@height\spx@bd@height - \@width \spx@bd@thickness\hss}}% -\@namedef{sphinx@u2514}% BOX DRAWINGS LIGHT UP AND RIGHT - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss\raise.5\spx@bd@height - \hb@xt@\z@{\hss\vrule\@height.5\spx@bd@height - \@width \spx@bd@thickness\hss}% - \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness - \@width.5\spx@bd@width\vss}}}% -\@namedef{sphinx@u251C}% BOX DRAWINGS LIGHT VERTICAL AND RIGHT - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss - \hb@xt@\z@{\hss\vrule\@height\spx@bd@height - \@width \spx@bd@thickness\hss}% - \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness - \@width.5\spx@bd@width\vss}}}% -\protected\def\sphinxunichar#1{\@nameuse{sphinx@u#1}}% - -% Tell TeX about pathological hyphenation cases: -\hyphenation{Base-HTTP-Re-quest-Hand-ler} -\endinput diff --git a/docs/_build/latex/sphinxhighlight.sty b/docs/_build/latex/sphinxhighlight.sty deleted file mode 100644 index 1557ce6e3..000000000 --- a/docs/_build/latex/sphinxhighlight.sty +++ /dev/null @@ -1,105 +0,0 @@ -\NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesPackage{sphinxhighlight}[2016/05/29 stylesheet for highlighting with pygments] - - -\makeatletter -\def\PYG@reset{\let\PYG@it=\relax \let\PYG@bf=\relax% - \let\PYG@ul=\relax \let\PYG@tc=\relax% - \let\PYG@bc=\relax \let\PYG@ff=\relax} -\def\PYG@tok#1{\csname PYG@tok@#1\endcsname} -\def\PYG@toks#1+{\ifx\relax#1\empty\else% - \PYG@tok{#1}\expandafter\PYG@toks\fi} -\def\PYG@do#1{\PYG@bc{\PYG@tc{\PYG@ul{% - \PYG@it{\PYG@bf{\PYG@ff{#1}}}}}}} -\def\PYG#1#2{\PYG@reset\PYG@toks#1+\relax+\PYG@do{#2}} - -\expandafter\def\csname PYG@tok@w\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}} -\expandafter\def\csname PYG@tok@c\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} -\expandafter\def\csname PYG@tok@cp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@cs\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}\def\PYG@bc##1{\setlength{\fboxsep}{0pt}\colorbox[rgb]{1.00,0.94,0.94}{\strut ##1}}} -\expandafter\def\csname PYG@tok@k\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@kp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@kt\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.56,0.13,0.00}{##1}}} -\expandafter\def\csname PYG@tok@o\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} -\expandafter\def\csname PYG@tok@ow\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@nb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@nf\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.49}{##1}}} -\expandafter\def\csname PYG@tok@nc\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.05,0.52,0.71}{##1}}} -\expandafter\def\csname PYG@tok@nn\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.05,0.52,0.71}{##1}}} -\expandafter\def\csname PYG@tok@ne\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@nv\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} -\expandafter\def\csname PYG@tok@no\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.38,0.68,0.84}{##1}}} -\expandafter\def\csname PYG@tok@nl\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.13,0.44}{##1}}} -\expandafter\def\csname PYG@tok@ni\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.84,0.33,0.22}{##1}}} -\expandafter\def\csname PYG@tok@na\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@nt\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.45}{##1}}} -\expandafter\def\csname PYG@tok@nd\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.33,0.33,0.33}{##1}}} -\expandafter\def\csname PYG@tok@s\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@sd\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@si\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.44,0.63,0.82}{##1}}} -\expandafter\def\csname PYG@tok@se\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@sr\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.14,0.33,0.53}{##1}}} -\expandafter\def\csname PYG@tok@ss\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.32,0.47,0.09}{##1}}} -\expandafter\def\csname PYG@tok@sx\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.78,0.36,0.04}{##1}}} -\expandafter\def\csname PYG@tok@m\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@gh\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} -\expandafter\def\csname PYG@tok@gu\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}} -\expandafter\def\csname PYG@tok@gd\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}} -\expandafter\def\csname PYG@tok@gi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}} -\expandafter\def\csname PYG@tok@gr\endcsname{\def\PYG@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}} -\expandafter\def\csname PYG@tok@ge\endcsname{\let\PYG@it=\textit} -\expandafter\def\csname PYG@tok@gs\endcsname{\let\PYG@bf=\textbf} -\expandafter\def\csname PYG@tok@gp\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.78,0.36,0.04}{##1}}} -\expandafter\def\csname PYG@tok@go\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.20,0.20,0.20}{##1}}} -\expandafter\def\csname PYG@tok@gt\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}} -\expandafter\def\csname PYG@tok@err\endcsname{\def\PYG@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}} -\expandafter\def\csname PYG@tok@kc\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@kd\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@kn\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@kr\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@bp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} -\expandafter\def\csname PYG@tok@fm\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.49}{##1}}} -\expandafter\def\csname PYG@tok@vc\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} -\expandafter\def\csname PYG@tok@vg\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} -\expandafter\def\csname PYG@tok@vi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} -\expandafter\def\csname PYG@tok@vm\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} -\expandafter\def\csname PYG@tok@sa\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@sb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@sc\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@dl\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@s2\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@sh\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@s1\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} -\expandafter\def\csname PYG@tok@mb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@mf\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@mh\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@mi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@il\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@mo\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} -\expandafter\def\csname PYG@tok@ch\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} -\expandafter\def\csname PYG@tok@cm\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} -\expandafter\def\csname PYG@tok@cpf\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} -\expandafter\def\csname PYG@tok@c1\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} - -\def\PYGZbs{\char`\\} -\def\PYGZus{\char`\_} -\def\PYGZob{\char`\{} -\def\PYGZcb{\char`\}} -\def\PYGZca{\char`\^} -\def\PYGZam{\char`\&} -\def\PYGZlt{\char`\<} -\def\PYGZgt{\char`\>} -\def\PYGZsh{\char`\#} -\def\PYGZpc{\char`\%} -\def\PYGZdl{\char`\$} -\def\PYGZhy{\char`\-} -\def\PYGZsq{\char`\'} -\def\PYGZdq{\char`\"} -\def\PYGZti{\char`\~} -% for compatibility with earlier versions -\def\PYGZat{@} -\def\PYGZlb{[} -\def\PYGZrb{]} -\makeatother - -\renewcommand\PYGZsq{\textquotesingle} diff --git a/docs/_build/latex/sphinxhowto.cls b/docs/_build/latex/sphinxhowto.cls deleted file mode 100644 index 11a49a205..000000000 --- a/docs/_build/latex/sphinxhowto.cls +++ /dev/null @@ -1,95 +0,0 @@ -% -% sphinxhowto.cls for Sphinx (http://sphinx-doc.org/) -% - -\NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesClass{sphinxhowto}[2017/03/26 v1.6 Document class (Sphinx HOWTO)] - -% 'oneside' option overriding the 'twoside' default -\newif\if@oneside -\DeclareOption{oneside}{\@onesidetrue} -% Pass remaining document options to the parent class. -\DeclareOption*{\PassOptionsToClass{\CurrentOption}{\sphinxdocclass}} -\ProcessOptions\relax - -% Default to two-side document -\if@oneside -% nothing to do (oneside is the default) -\else -\PassOptionsToClass{twoside}{\sphinxdocclass} -\fi - -\LoadClass{\sphinxdocclass} - -% Set some sane defaults for section numbering depth and TOC depth. You can -% reset these counters in your preamble. -% -\setcounter{secnumdepth}{2} -\setcounter{tocdepth}{2}% i.e. section and subsection - -% Change the title page to look a bit better, and fit in with the fncychap -% ``Bjarne'' style a bit better. -% -\renewcommand{\maketitle}{% - \noindent\rule{\textwidth}{1pt}\par - \begingroup % for PDF information dictionary - \def\endgraf{ }\def\and{\& }% - \pdfstringdefDisableCommands{\def\\{, }}% overwrite hyperref setup - \hypersetup{pdfauthor={\@author}, pdftitle={\@title}}% - \endgroup - \begin{flushright} - \sphinxlogo - \py@HeaderFamily - {\Huge \@title }\par - {\itshape\large \py@release \releaseinfo}\par - \vspace{25pt} - {\Large - \begin{tabular}[t]{c} - \@author - \end{tabular}}\par - \vspace{25pt} - \@date \par - \py@authoraddress \par - \end{flushright} - \@thanks - \setcounter{footnote}{0} - \let\thanks\relax\let\maketitle\relax - %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} -} - -\newcommand{\sphinxtableofcontents}{ - \begingroup - \parskip = 0mm - \tableofcontents - \endgroup - \rule{\textwidth}{1pt} - \vspace{12pt} -} - -\@ifundefined{fancyhf}{ - \pagestyle{plain}}{ - \pagestyle{normal}} % start this way; change for -\pagenumbering{arabic} % ToC & chapters - -\thispagestyle{empty} - -% Fix the bibliography environment to add an entry to the Table of -% Contents. -% For an article document class this environment is a section, -% so no page break before it. -% -\newenvironment{sphinxthebibliography}[1]{% - % \phantomsection % not needed here since TeXLive 2010's hyperref - \begin{thebibliography}{1}% - \addcontentsline{toc}{section}{\ifdefined\refname\refname\else\ifdefined\bibname\bibname\fi\fi}}{\end{thebibliography}} - - -% Same for the indices. -% The memoir class already does this, so we don't duplicate it in that case. -% -\@ifclassloaded{memoir} - {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}} - {\newenvironment{sphinxtheindex}{% - \phantomsection % needed because no chapter, section, ... is created by theindex - \begin{theindex}% - \addcontentsline{toc}{section}{\indexname}}{\end{theindex}}} diff --git a/docs/_build/latex/sphinxmanual.cls b/docs/_build/latex/sphinxmanual.cls deleted file mode 100644 index 5b3d183cb..000000000 --- a/docs/_build/latex/sphinxmanual.cls +++ /dev/null @@ -1,114 +0,0 @@ -% -% sphinxmanual.cls for Sphinx (http://sphinx-doc.org/) -% - -\NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesClass{sphinxmanual}[2017/03/26 v1.6 Document class (Sphinx manual)] - -% chapters starting at odd pages (overridden by 'openany' document option) -\PassOptionsToClass{openright}{\sphinxdocclass} - -% 'oneside' option overriding the 'twoside' default -\newif\if@oneside -\DeclareOption{oneside}{\@onesidetrue} -% Pass remaining document options to the parent class. -\DeclareOption*{\PassOptionsToClass{\CurrentOption}{\sphinxdocclass}} -\ProcessOptions\relax - -% Defaults two-side document -\if@oneside -% nothing to do (oneside is the default) -\else -\PassOptionsToClass{twoside}{\sphinxdocclass} -\fi - -\LoadClass{\sphinxdocclass} - -% Set some sane defaults for section numbering depth and TOC depth. You can -% reset these counters in your preamble. -% -\setcounter{secnumdepth}{2} -\setcounter{tocdepth}{1} - -% Change the title page to look a bit better, and fit in with the fncychap -% ``Bjarne'' style a bit better. -% -\renewcommand{\maketitle}{% - \let\spx@tempa\relax - \ifHy@pageanchor\def\spx@tempa{\Hy@pageanchortrue}\fi - \hypersetup{pageanchor=false}% avoid duplicate destination warnings - \begin{titlepage}% - \let\footnotesize\small - \let\footnoterule\relax - \noindent\rule{\textwidth}{1pt}\par - \begingroup % for PDF information dictionary - \def\endgraf{ }\def\and{\& }% - \pdfstringdefDisableCommands{\def\\{, }}% overwrite hyperref setup - \hypersetup{pdfauthor={\@author}, pdftitle={\@title}}% - \endgroup - \begin{flushright}% - \sphinxlogo - \py@HeaderFamily - {\Huge \@title \par} - {\itshape\LARGE \py@release\releaseinfo \par} - \vfill - {\LARGE - \begin{tabular}[t]{c} - \@author - \end{tabular} - \par} - \vfill\vfill - {\large - \@date \par - \vfill - \py@authoraddress \par - }% - \end{flushright}%\par - \@thanks - \end{titlepage}% - \setcounter{footnote}{0}% - \let\thanks\relax\let\maketitle\relax - %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} - \if@openright\cleardoublepage\else\clearpage\fi - \spx@tempa -} - -\newcommand{\sphinxtableofcontents}{% - \pagenumbering{roman}% - \pagestyle{plain}% - \begingroup - \parskip \z@skip - \tableofcontents - \endgroup - % before resetting page counter, let's do the right thing. - \if@openright\cleardoublepage\else\clearpage\fi - \pagenumbering{arabic}% - \ifdefined\fancyhf\pagestyle{normal}\fi -} - -% This is needed to get the width of the section # area wide enough in the -% library reference. Doing it here keeps it the same for all the manuals. -% -\renewcommand*\l@section{\@dottedtocline{1}{1.5em}{2.6em}} -\renewcommand*\l@subsection{\@dottedtocline{2}{4.1em}{3.5em}} - -% Fix the bibliography environment to add an entry to the Table of -% Contents. -% For a report document class this environment is a chapter. -% -\newenvironment{sphinxthebibliography}[1]{% - \if@openright\cleardoublepage\else\clearpage\fi - % \phantomsection % not needed here since TeXLive 2010's hyperref - \begin{thebibliography}{1}% - \addcontentsline{toc}{chapter}{\bibname}}{\end{thebibliography}} - -% Same for the indices. -% The memoir class already does this, so we don't duplicate it in that case. -% -\@ifclassloaded{memoir} - {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}} - {\newenvironment{sphinxtheindex}{% - \if@openright\cleardoublepage\else\clearpage\fi - \phantomsection % needed as no chapter, section, ... created - \begin{theindex}% - \addcontentsline{toc}{chapter}{\indexname}}{\end{theindex}}} diff --git a/docs/_build/latex/sphinxmulticell.sty b/docs/_build/latex/sphinxmulticell.sty deleted file mode 100644 index f0d11b1f9..000000000 --- a/docs/_build/latex/sphinxmulticell.sty +++ /dev/null @@ -1,317 +0,0 @@ -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{sphinxmulticell}% - [2017/02/23 v1.6 better span rows and columns of a table (Sphinx team)]% -\DeclareOption*{\PackageWarning{sphinxmulticell}{Option `\CurrentOption' is unknown}}% -\ProcessOptions\relax -% -% --- MULTICOLUMN --- -% standard LaTeX's \multicolumn -% 1. does not allow verbatim contents, -% 2. interacts very poorly with tabulary. -% -% It is needed to write own macros for Sphinx: to allow code-blocks in merged -% cells rendered by tabular/longtable, and to allow multi-column cells with -% paragraphs to be taken into account sanely by tabulary algorithm for column -% widths. -% -% This requires quite a bit of hacking. First, in Sphinx, the multi-column -% contents will *always* be wrapped in a varwidth environment. The issue -% becomes to pass it the correct target width. We must trick tabulary into -% believing the multicolumn is simply separate columns, else tabulary does not -% incorporate the contents in its algorithm. But then we must clear the -% vertical rules... -% -% configuration of tabulary -\setlength{\tymin}{3\fontcharwd\font`0 }% minimal width of "squeezed" columns -\setlength{\tymax}{10000pt}% allow enough room for paragraphs to "compete" -% we need access to tabulary's final computed width. \@tempdima is too volatile -% to hope it has kept tabulary's value when \sphinxcolwidth needs it. -\newdimen\sphinx@TY@tablewidth -\def\tabulary{% - \def\TY@final{\sphinx@TY@tablewidth\@tempdima\tabular}% - \let\endTY@final\endtabular - \TY@tabular}% -% next hack is needed only if user has set latex_use_latex_multicolumn to True: -% it fixes tabulary's bug with \multicolumn defined "short" in first pass. (if -% upstream tabulary adds a \long, our extra one causes no harm) -\def\sphinx@tempa #1\def\multicolumn#2#3#4#5#6#7#8#9\sphinx@tempa - {\def\TY@tab{#1\long\def\multicolumn####1####2####3{\multispan####1\relax}#9}}% -\expandafter\sphinx@tempa\TY@tab\sphinx@tempa -% -% TN. 1: as \omit is never executed, Sphinx multicolumn does not need to worry -% like standard multicolumn about |l| vs l|. On the other hand it assumes -% columns are separated by a | ... (if not it will add extraneous -% \arrayrulewidth space for each column separation in its estimate of available -% width). -% -% TN. 1b: as Sphinx multicolumn uses neither \omit nor \span, it can not -% (easily) get rid of extra macros from >{...} or <{...} between columns. At -% least, it has been made compatible with colortbl's \columncolor. -% -% TN. 2: tabulary's second pass is handled like tabular/longtable's single -% pass, with the difference that we hacked \TY@final to set in -% \sphinx@TY@tablewidth the final target width as computed by tabulary. This is -% needed only to handle columns with a "horizontal" specifier: "p" type columns -% (inclusive of tabulary's LJRC) holds the target column width in the -% \linewidth dimension. -% -% TN. 3: use of \begin{sphinxmulticolumn}...\end{sphinxmulticolumn} mark-up -% would need some hacking around the fact that groups can not span across table -% cells (the code does inserts & tokens, see TN1b). It was decided to keep it -% simple with \sphinxstartmulticolumn...\sphinxstopmulticolumn. -% -% MEMO about nesting: if sphinxmulticolumn is encountered in a nested tabular -% inside a tabulary it will think to be at top level in the tabulary. But -% Sphinx generates no nested tables, and if some LaTeX macro uses internally a -% tabular this will not have a \sphinxstartmulticolumn within it! -% -\def\sphinxstartmulticolumn{% - \ifx\equation$% $ tabulary's first pass - \expandafter\sphinx@TYI@start@multicolumn - \else % either not tabulary or tabulary's second pass - \expandafter\sphinx@start@multicolumn - \fi -}% -\def\sphinxstopmulticolumn{% - \ifx\equation$% $ tabulary's first pass - \expandafter\sphinx@TYI@stop@multicolumn - \else % either not tabulary or tabulary's second pass - \ignorespaces - \fi -}% -\def\sphinx@TYI@start@multicolumn#1{% - % use \gdef always to avoid stack space build up - \gdef\sphinx@tempa{#1}\begingroup\setbox\z@\hbox\bgroup -}% -\def\sphinx@TYI@stop@multicolumn{\egroup % varwidth was used with \tymax - \xdef\sphinx@tempb{\the\dimexpr\wd\z@/\sphinx@tempa}% per column width - \endgroup - \expandafter\sphinx@TYI@multispan\expandafter{\sphinx@tempa}% -}% -\def\sphinx@TYI@multispan #1{% - \kern\sphinx@tempb\ignorespaces % the per column occupied width - \ifnum#1>\@ne % repeat, taking into account subtleties of TeX's & ... - \expandafter\sphinx@TYI@multispan@next\expandafter{\the\numexpr#1-\@ne\expandafter}% - \fi -}% -\def\sphinx@TYI@multispan@next{&\relax\sphinx@TYI@multispan}% -% -% Now the branch handling either the second pass of tabulary or the single pass -% of tabular/longtable. This is the delicate part where we gather the -% dimensions from the p columns either set-up by tabulary or by user p column -% or Sphinx \X, \Y columns. The difficulty is that to get the said width, the -% template must be inserted (other hacks would be horribly complicated except -% if we rewrote crucial parts of LaTeX's \@array !) and we can not do -% \omit\span like standard \multicolumn's easy approach. Thus we must cancel -% the \vrule separators. Also, perhaps the column specifier is of the l, c, r -% type, then we attempt an ad hoc rescue to give varwidth a reasonable target -% width. -\def\sphinx@start@multicolumn#1{% - \gdef\sphinx@multiwidth{0pt}\gdef\sphinx@tempa{#1}\sphinx@multispan{#1}% -}% -\def\sphinx@multispan #1{% - \ifnum#1=\@ne\expandafter\sphinx@multispan@end - \else\expandafter\sphinx@multispan@next - \fi {#1}% -}% -\def\sphinx@multispan@next #1{% - % trick to recognize L, C, R, J or p, m, b type columns - \ifdim\baselineskip>\z@ - \gdef\sphinx@tempb{\linewidth}% - \else - % if in an l, r, c type column, try and hope for the best - \xdef\sphinx@tempb{\the\dimexpr(\ifx\TY@final\@undefined\linewidth\else - \sphinx@TY@tablewidth\fi-\arrayrulewidth)/\sphinx@tempa - -\tw@\tabcolsep-\arrayrulewidth\relax}% - \fi - \noindent\kern\sphinx@tempb\relax - \xdef\sphinx@multiwidth - {\the\dimexpr\sphinx@multiwidth+\sphinx@tempb+\tw@\tabcolsep+\arrayrulewidth}% - % hack the \vline and the colortbl macros - \sphinx@hack@vline\sphinx@hack@CT&\relax - % repeat - \expandafter\sphinx@multispan\expandafter{\the\numexpr#1-\@ne}% -}% -% packages like colortbl add group levels, we need to "climb back up" to be -% able to hack the \vline and also the colortbl inserted tokens. This creates -% empty space whether or not the columns were | separated: -\def\sphinx@hack@vline{\ifnum\currentgrouptype=6\relax - \kern\arrayrulewidth\arrayrulewidth\z@\else\aftergroup\sphinx@hack@vline\fi}% -\def\sphinx@hack@CT{\ifnum\currentgrouptype=6\relax - \let\CT@setup\sphinx@CT@setup\else\aftergroup\sphinx@hack@CT\fi}% -% It turns out \CT@row@color is not expanded contrarily to \CT@column@color -% during LaTeX+colortbl preamble preparation, hence it would be possible for -% \sphinx@CT@setup to discard only the column color and choose to obey or not -% row color and cell color. It would even be possible to propagate cell color -% to row color for the duration of the Sphinx multicolumn... the (provisional?) -% choice has been made to cancel the colortbl colours for the multicolumn -% duration. -\def\sphinx@CT@setup #1\endgroup{\endgroup}% hack to remove colour commands -\def\sphinx@multispan@end#1{% - % first, trace back our steps horizontally - \noindent\kern-\dimexpr\sphinx@multiwidth\relax - % and now we set the final computed width for the varwidth environment - \ifdim\baselineskip>\z@ - \xdef\sphinx@multiwidth{\the\dimexpr\sphinx@multiwidth+\linewidth}% - \else - \xdef\sphinx@multiwidth{\the\dimexpr\sphinx@multiwidth+ - (\ifx\TY@final\@undefined\linewidth\else - \sphinx@TY@tablewidth\fi-\arrayrulewidth)/\sphinx@tempa - -\tw@\tabcolsep-\arrayrulewidth\relax}% - \fi - % we need to remove colour set-up also for last cell of the multi-column - \aftergroup\sphinx@hack@CT -}% -\newcommand*\sphinxcolwidth[2]{% - % this dimension will always be used for varwidth, and serves as maximum - % width when cells are merged either via multirow or multicolumn or both, - % as always their contents is wrapped in varwidth environment. - \ifnum#1>\@ne % multi-column (and possibly also multi-row) - % we wrote our own multicolumn code especially to handle that (and allow - % verbatim contents) - \ifx\equation$%$ - \tymax % first pass of tabulary (cf MEMO above regarding nesting) - \else % the \@gobble thing is for compatibility with standard \multicolumn - \sphinx@multiwidth\@gobble{#1/#2}% - \fi - \else % single column multirow - \ifx\TY@final\@undefined % not a tabulary. - \ifdim\baselineskip>\z@ - % in a p{..} type column, \linewidth is the target box width - \linewidth - \else - % l, c, r columns. Do our best. - \dimexpr(\linewidth-\arrayrulewidth)/#2- - \tw@\tabcolsep-\arrayrulewidth\relax - \fi - \else % in tabulary - \ifx\equation$%$% first pass - \tymax % it is set to a big value so that paragraphs can express themselves - \else - % second pass. - \ifdim\baselineskip>\z@ - \linewidth % in a L, R, C, J column or a p, \X, \Y ... - \else - % we have hacked \TY@final to put in \sphinx@TY@tablewidth the table width - \dimexpr(\sphinx@TY@tablewidth-\arrayrulewidth)/#2- - \tw@\tabcolsep-\arrayrulewidth\relax - \fi - \fi - \fi - \fi -}% -% fallback default in case user has set latex_use_latex_multicolumn to True: -% \sphinxcolwidth will use this only inside LaTeX's standard \multicolumn -\def\sphinx@multiwidth #1#2{\dimexpr % #1 to gobble the \@gobble (!) - (\ifx\TY@final\@undefined\linewidth\else\sphinx@TY@tablewidth\fi - -\arrayrulewidth)*#2-\tw@\tabcolsep-\arrayrulewidth\relax}% -% -% --- MULTIROW --- -% standard \multirow -% 1. does not allow verbatim contents, -% 2. does not allow blank lines in its argument, -% 3. its * specifier means to typeset "horizontally" which is very -% bad for paragraph content. 2016 version has = specifier but it -% must be used with p type columns only, else results are bad, -% 4. it requires manual intervention if the contents is too long to fit -% in the asked-for number of rows. -% 5. colour panels (either from \rowcolor or \columncolor) will hide -% the bottom part of multirow text, hence manual tuning is needed -% to put the multirow insertion at the _bottom_. -% -% The Sphinx solution consists in always having contents wrapped -% in a varwidth environment so that it makes sense to estimate how many -% lines it will occupy, and then ensure by insertion of suitable struts -% that the table rows have the needed height. The needed mark-up is done -% by LaTeX writer, which has its own id for the merged cells. -% -% The colour issue is solved by clearing colour panels in all cells, -% whether or not the multirow is single-column or multi-column. -% -% In passing we obtain baseline alignements across rows (only if -% \arraylinestretch is 1, as LaTeX's does not obey \arraylinestretch in "p" -% multi-line contents, only first and last line...) -% -% TODO: examine the situation with \arraylinestretch > 1. The \extrarowheight -% is hopeless for multirow anyhow, it makes baseline alignment strictly -% impossible. -\newcommand\sphinxmultirow[2]{\begingroup - % #1 = nb of spanned rows, #2 = Sphinx id of "cell", #3 = contents - % but let's fetch #3 in a way allowing verbatim contents ! - \def\sphinx@nbofrows{#1}\def\sphinx@cellid{#2}% - \afterassignment\sphinx@multirow\let\next= -}% -\def\sphinx@multirow {% - \setbox\z@\hbox\bgroup\aftergroup\sphinx@@multirow\strut -}% -\def\sphinx@@multirow {% - % The contents, which is a varwidth environment, has been captured in - % \box0 (a \hbox). - % We have with \sphinx@cellid an assigned unique id. The goal is to give - % about the same height to all the involved rows. - % For this Sphinx will insert a \sphinxtablestrut{cell_id} mark-up - % in LaTeX file and the expansion of the latter will do the suitable thing. - \dimen@\dp\z@ - \dimen\tw@\ht\@arstrutbox - \advance\dimen@\dimen\tw@ - \advance\dimen\tw@\dp\@arstrutbox - \count@=\dimen@ % type conversion dim -> int - \count\tw@=\dimen\tw@ - \divide\count@\count\tw@ % TeX division truncates - \advance\dimen@-\count@\dimen\tw@ - % 1300sp is about 0.02pt. For comparison a rule default width is 0.4pt. - % (note that if \count@ holds 0, surely \dimen@>1300sp) - \ifdim\dimen@>1300sp \advance\count@\@ne \fi - % now \count@ holds the count L of needed "lines" - % and \sphinx@nbofrows holds the number N of rows - % we have L >= 1 and N >= 1 - % if L is a multiple of N, ... clear what to do ! - % else write L = qN + r, 1 <= r < N and we will - % arrange for each row to have enough space for: - % q+1 "lines" in each of the first r rows - % q "lines" in each of the (N-r) bottom rows - % for a total of (q+1) * r + q * (N-r) = q * N + r = L - % It is possible that q == 0. - \count\tw@\count@ - % the TeX division truncates - \divide\count\tw@\sphinx@nbofrows\relax - \count4\count\tw@ % q - \multiply\count\tw@\sphinx@nbofrows\relax - \advance\count@-\count\tw@ % r - \expandafter\xdef\csname sphinx@tablestrut_\sphinx@cellid\endcsname - {\noexpand\sphinx@tablestrut{\the\count4}{\the\count@}{\sphinx@cellid}}% - \dp\z@\z@ - % this will use the real height if it is >\ht\@arstrutbox - \sphinxtablestrut{\sphinx@cellid}\box\z@ - \endgroup % group was opened in \sphinxmultirow -}% -\newcommand*\sphinxtablestrut[1]{% - % #1 is a "cell_id", i.e. the id of a merged group of table cells - \csname sphinx@tablestrut_#1\endcsname -}% -% LaTeX typesets the table row by row, hence each execution can do -% an update for the next row. -\newcommand*\sphinx@tablestrut[3]{\begingroup - % #1 = q, #2 = (initially) r, #3 = cell_id, q+1 lines in first r rows - % if #2 = 0, create space for max(q,1) table lines - % if #2 > 0, create space for q+1 lines and decrement #2 - \leavevmode - \count@#1\relax - \ifnum#2=\z@ - \ifnum\count@=\z@\count@\@ne\fi - \else - % next row will be with a #2 decremented by one - \expandafter\xdef\csname sphinx@tablestrut_#3\endcsname - {\noexpand\sphinx@tablestrut{#1}{\the\numexpr#2-\@ne}{#3}}% - \advance\count@\@ne - \fi - \vrule\@height\ht\@arstrutbox - \@depth\dimexpr\count@\ht\@arstrutbox+\count@\dp\@arstrutbox-\ht\@arstrutbox\relax - \@width\z@ - \endgroup - % we need this to avoid colour panels hiding bottom parts of multirow text - \sphinx@hack@CT -}% -\endinput -%% -%% End of file `sphinxmulticell.sty'. diff --git a/docs/conf.py b/docs/conf.py index 07ca9461b..d475ed893 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,185 +1,60 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# pyEMU documentation build configuration file, created by -# sphinx-quickstart on Thu Sep 14 07:46:08 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # - - import os import sys -sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../')) -sys.path.insert(0, os.path.abspath('../pyemu/')) +sys.path.insert(0, os.path.abspath('../pyemu')) -# -- General configuration ------------------------------------------------ +# -- Project information ----------------------------------------------------- -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +project = 'pyEMU' +copyright = '2020, Jeremy White, Mike Fienen, Brioch Hemmings, and others' +author = 'Jeremy White, Mike Fienen, Brioch Hemmings, and others' +release = "1.0.0" -autodoc_member_order = 'bysource' +# -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', - 'sphinx.ext.inheritance_diagram'] - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = False -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = False -napoleon_use_admonition_for_examples = True -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True -napoleon_use_keyword = True - -# Inheritance diagram options can be changed here -inheritance_node_attrs = dict(shape='rectangle', fontsize=12) -inheritance_graph_attrs = dict(rankdir="TB", size='""') -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' -# The master toctree document. -master_doc = 'index' +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon','sphinx.ext.githubpages','sphinx.ext.autosummary'] -# General information about the project. -project = 'pyEMU' -copyright = '2017, Jeremy White, Mike Fienen, John Doherty' -author = 'Jeremy White, Mike Fienen, John Doherty' +#autosummary_generate = True +extensions.append('autoapi.extension') -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.3' -# The full version, including alpha/beta/rc tags. -release = '0.3' +autoapi_type = 'python' +autoapi_dirs = [os.path.join('..','pyemu')] -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', '**.ipynb_checkpoints'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# -- Options for HTML output ---------------------------------------------- +# -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'classic' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = {'show_related': True} +html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyEMUdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'pyEMU.tex', 'pyEMU Documentation', - 'Jeremy White, Mike Fienen, John Doherty', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyemu', 'pyEMU Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'pyEMU', 'pyEMU Documentation', - author, 'pyEMU', 'One line description of project.', - 'Miscellaneous'), -] +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/conf.py.bak b/docs/conf.py.bak deleted file mode 100644 index e62d57090..000000000 --- a/docs/conf.py.bak +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# pyEMU documentation build configuration file, created by -# sphinx-quickstart on Thu Sep 14 07:46:08 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../')) -sys.path.insert(0, os.path.abspath('../pyemu/')) - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -autodoc_member_order = 'bysource' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', - 'nbsphinx', - 'IPython.sphinxext.ipython_console_highlighting', - 'sphinx.ext.inheritance_diagram'] - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = False -napoleon_include_private_with_doc = False -napoleon_include_special_with_doc = False -napoleon_use_admonition_for_examples = True -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True -napoleon_use_keyword = True - -# Inheritance diagram options can be changed here -inheritance_node_attrs = dict(shape='rectangle', fontsize=14) -inheritance_graph_attrs = dict(rankdir="TB", size='""') - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'pyEMU' -copyright = '2017, Jeremy White, Mike Fienen, John Doherty' -author = 'Jeremy White, Mike Fienen, John Doherty' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.3' -# The full version, including alpha/beta/rc tags. -release = '0.3' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', '**.ipynb_checkpoints'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = {'show_related': True} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyEMUdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'pyEMU.tex', 'pyEMU Documentation', - 'Jeremy White, Mike Fienen, John Doherty', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyemu', 'pyEMU Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'pyEMU', 'pyEMU Documentation', - author, 'pyEMU', 'One line description of project.', - 'Miscellaneous'), -] diff --git a/docs/index.rst b/docs/index.rst index f06bd1788..61b7c052a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,86 +1,16 @@ -.. pyemu documentation master file, created by - sphinx-quickstart on Thu Sep 14 11:12:03 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. -################################# -Welcome to pyEMU's documentation! -################################# - -pyEMU [WFD16]_ is a set of python modules for performing linear and non-linear -uncertainty analysis including parameter and forecast analyses, data-worth -analysis, and error-variance analysis. These python modules can interact with -the PEST [DOH15]_ and PEST++ [WWHD15]_ suites and use terminology consistent -with them. pyEMU is written in an object-oriented programming style, and -thus expects that users will write, or adapt, client code in python to -implement desired analysis. :doc:`source/oop` are provided in this documentation. - -pyEMU is available via github_ . - -.. _github: https://github.com/jtwhite79/pyemu -.. _Notes: +Welcome to pyEMU's documentation! +================================= -Contents -******** +This documentation is more-or-less complete. However, the example notebooks in the repo +provide a more complete picture of how the various components of pyEMU function. -.. toctree:: - :maxdepth: 2 - source/Monte_carlo_page - source/oop - source/glossary - -.. _Technical: -Technical Documentation -*********************** +Indices and tables +================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` - - -References -********** - -.. [BPL+16] Bakker, M., Post, V., Langevin, C. D., Hughes, J. D., White, J. T., - Starn, J. J. and Fienen, M. N., 2016, Scripting MODFLOW model development using - Python and FloPy: Groundwater, v. 54, p. 733-739, https://doi.org/10.1111/gwat.12413. - -.. [DOH15] Doherty, John, 2015. Calibration and - Uncertainty Analysis for Complex Environmental Models: Brisbane, Australia, Watermark Numerical - Computing, http://www.pesthomepage.org/Home.php . - -.. [FB16] Fienen, M.N., and Bakker, Mark, 2016, HESS Opinions: Repeatable research--- what hydrologists can learn - from the Duke cancer research scandal: Hydrology and Earth System Sciences, v. 20, no. 9, pg. 3739-3743, - https://doi.org/10.5194/hess-20-3739-2016 . - -.. [KB09] Beven, Keith, 2009, Environmental modelling--- an uncertain future?: London, Routledge, 310 p. - -.. [KRP+16] Kluyver, Thomas; Ragan-Kelley, Benjamin; Perez, Fernado; Granger, Brian; Bussonnier, Matthias; - Federic, Jonathan; Kelley, Kyle; Hamrick, Jessica; Grout, Jason; Corlay, Sylvain; Ivanov, Paul; Avila, Damian; - Aballa, Safia; Willing, Carol; and Jupyter Development Team, 2016, Jupyter Notebooks-- a publishing - format for reproducible computational workflows: in Positioning and Power in Academic - Publishing-- Players, Agents, and Agendas. F. Loizides and B. Schmidt (eds). - IOS Press, https://doi.org/10.3233/978-1-61499-649-1-87 and https://jupyter.org . - -.. [MCK10] McKinney, Wes, 2010, Data structures for statistical computing in python: - in Proceedings of the 9th Python in Science Conference, Stefan van - der Walt and Jarrod Millman (eds.), p. 51-56, https://pandas.pydata.org/index.html . - -.. [PSF18] The Python Software Foundation, 2018, Documentation, The python tutorial, - 9. Classes: https://docs.python.org/3.6/tutorial/classes.html . - -.. [RS16] Reges, Stuart, and Stepp, Marty, 2016, Building Java Programs--- A Back to Basics - Approach, Fourth Edition: Boston, Pearson, 1194 p. - -.. [WFD16] White, J.T., Fienen, M.N., and Doherty, J.E., 2016, A python framework - for environmental model uncertainty analysis: Environmental Modeling & - Software, v. 85, pg. 217-228, https://doi.org/10.1016/j.envsoft.2016.08.017 . - -.. [WWHD15] Welter, D.E., White, J.T., Hunt, R.J., and Doherty, J.E., 2015, - Approaches in highly parameterized inversion: PEST++ Version 3, a Parameter - ESTimation and uncertainty analysis software suite optimized for large - environmental models: U.S. Geological Survey Techniques and Methods, book 7, - section C12, 54 p., https://doi.org/10.3133/tm7C12 . \ No newline at end of file diff --git a/docs/index.rst.bak b/docs/index.rst.bak deleted file mode 100644 index fcbf9fa61..000000000 --- a/docs/index.rst.bak +++ /dev/null @@ -1,87 +0,0 @@ -.. pyemu documentation master file, created by - sphinx-quickstart on Thu Sep 14 11:12:03 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -################################# -Welcome to pyEMU's documentation! -################################# - -pyEMU [WFD16]_ is a set of python modules for performing linear and non-linear -uncertainty analysis including parameter and forecast analyses, data-worth -analysis, and error-variance analysis. These python modules can interact with -the PEST [DOH15]_ and PEST++ [WWHD15]_ suites and use terminology consistent -with them. pyEMU is written in an object-oriented programming style, and -thus expects that users will write, or adapt, client code in python to -implement desired analysis. :doc:`source/oop` are provided in this documentation. - -pyEMU is available via github_ . - -.. _github: https://github.com/jtwhite79/pyemu -.. _Notes: - - -Contents -******** - -.. toctree:: - :maxdepth: 1 - - source/Monte_carlo_page - source/ensembles - source/oop - source/glossary - -.. _Technical: - -Technical Documentation -*********************** - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - - -References -********** - -.. [BPL+16] Bakker, M., Post, V., Langevin, C. D., Hughes, J. D., White, J. T., - Starn, J. J. and Fienen, M. N., 2016, Scripting MODFLOW model development using - Python and FloPy: Groundwater, v. 54, p. 733-739, https://doi.org/10.1111/gwat.12413. - -.. [DOH15] Doherty, John, 2015. Calibration and - Uncertainty Analysis for Complex Environmental Models: Brisbane, Australia, Watermark Numerical - Computing, http://www.pesthomepage.org/Home.php . - -.. [FB16] Fienen, M.N., and Bakker, Mark, 2016, HESS Opinions: Repeatable research--- what hydrologists can learn - from the Duke cancer research scandal: Hydrology and Earth System Sciences, v. 20, no. 9, pg. 3739-3743, - https://doi.org/10.5194/hess-20-3739-2016 . - -.. [KB09] Beven, Keith, 2009, Environmental modelling--- an uncertain future?: London, Routledge, 310 p. - -.. [KRP+16] Kluyver, Thomas; Ragan-Kelley, Benjamin; Perez, Fernado; Granger, Brian; Bussonnier, Matthias; - Federic, Jonathan; Kelley, Kyle; Hamrick, Jessica; Grout, Jason; Corlay, Sylvain; Ivanov, Paul; Avila, Damian; - Aballa, Safia; Willing, Carol; and Jupyter Development Team, 2016, Jupyter Notebooks-- a publishing - format for reproducible computational workflows: in Positioning and Power in Academic - Publishing-- Players, Agents, and Agendas. F. Loizides and B. Schmidt (eds). - IOS Press, https://doi.org/10.3233/978-1-61499-649-1-87 and https://jupyter.org . - -.. [MCK10] McKinney, Wes, 2010, Data structures for statistical computing in python: - in Proceedings of the 9th Python in Science Conference, Stefan van - der Walt and Jarrod Millman (eds.), p. 51-56, https://pandas.pydata.org/index.html . - -.. [PSF18] The Python Software Foundation, 2018, Documentation, The python tutorial, - 9. Classes: https://docs.python.org/3.6/tutorial/classes.html . - -.. [RS16] Reges, Stuart, and Stepp, Marty, 2016, Building Java Programs--- A Back to Basics - Approach, Fourth Edition: Boston, Pearson, 1194 p. - -.. [WFD16] White, J.T., Fienen, M.N., and Doherty, J.E., 2016, A python framework - for environmental model uncertainty analysis: Environmental Modeling & - Software, v. 85, pg. 217-228, https://doi.org/10.1016/j.envsoft.2016.08.017 . - -.. [WWHD15] Welter, D.E., White, J.T., Hunt, R.J., and Doherty, J.E., 2015, - Approaches in highly parameterized inversion: PEST++ Version 3, a Parameter - ESTimation and uncertainty analysis software suite optimized for large - environmental models: U.S. Geological Survey Techniques and Methods, book 7, - section C12, 54 p., https://doi.org/10.3133/tm7C12 . \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat index 1572a56c4..2119f5109 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -9,7 +9,6 @@ if "%SPHINXBUILD%" == "" ( ) set SOURCEDIR=. set BUILDDIR=_build -set SPHINXPROJ=pyemu if "%1" == "" goto help @@ -26,11 +25,11 @@ if errorlevel 9009 ( exit /b 1 ) -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd diff --git a/docs/source/modules.rst b/docs/modules.rst similarity index 100% rename from docs/source/modules.rst rename to docs/modules.rst diff --git a/docs/pyemu.docx b/docs/pyemu.docx deleted file mode 100644 index d2e23845762ef40a499d5789f712c77e11bc34b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124765 zcmeFYbyS=|mnYn~Yj6!7+#xswhhV`WxI=&>SOX1>y96f$cXxM(gy8PbxOZdepv(Kt z&d%S}&voDFM@7VspaGlPzYOk%;kP( zqN%3LpfG$hl^yE(wwaIUzk0Ox<%lC_h{y?V;X*=&wU{F50L9NV;aetCe1)5b&P2!` zBjrGyeUFrjIg}|_z-QZN&USaT)P#DGS1i9qemr8B(Ueyq+Z-=sAUbT=XUY(N!SjkbZA2Rf0of{dA7Du$5eBqgy@-d{M}+4RC@k-SXo7ucN*qL8lI`(zCUYUjsKhH zEo}$OPGfNsEwawkgzE(dJ(u9%m4@Y>uQ(09@@I^IrYo%wk(fWo{wf)BCY+tf;C+W5TmGmT`lq4ob_i2hHkS0;|DAo1b{ zTm)U2X%8FJuGVx^Nl*>jrCxKu1`3&>RUL})Kr+3ov*M#s+kaKD{2%N(c zys>^NU8Q6<7-roLv~G5=#CxU#(=Lo|sLJl~6AKzXZM@jB|0iCk5hB{eUs_y|0RSoh zI)az$S8MKn0gbh*m51|-!TX1$`&R%VyqLQe>i@e_WpbVR%XS~;#kfItslG;Hk05x2 zs6=h>TFJ!ZATfQ|)7s4b>Xu60`GXdHSj?Z*r}G9*-|nR&J~;A0iXZ`HI0H#Qf{-qA z{F3h_$YTUCzP~WP5tV)+z%CkoY59a%ByF^drTaJF&9GvnbS2H(KlK1kkUcw(K%!v@ z%7>$`rWkDmBG8}q0(;eC<}eI?gJJ@=vFi*JW$ixjlF(Rkh$6ca4(ep%77f0GU3Pl^ zUI2j@BV8StRLy7p!hVtk*B%a0i=5H3Z}cb$AIp9h&%=j(Ap4lnKGZ4|f6-j9LU}q4 zbdlO6fBV(3I*6a)XA*$#)WfgGEBoXsRqg%awKns{!;+OQH-JBR z%M$1H=V_eV?6=zu&a0tR;ku8T3>TY~O4s|7i_f>1K(H>z=Qr5=+R7pj_}Vasf_#r+;Gz z0mZJJKD(v*Szm;>C7GaRF1bwKOd0jZkE5LgNGY3UbYSe>_l(vF>LE+j`4`}#5ApIV zT@JR0V+)Gxe*MsZ2i?O%_BtaukUiuM8eh!xG`l}sujvWRFYIfXk)P1ubVU1o_8T(* z>EL#LL~IhTstyakFOfIE1NGx$IfWgRM8vApR ztR^a>?>Lh;phEw(I!n%GkAKrCq!{a7oZ)12x)JrBOZn$if^8dLBF4v+koy+ zY6gGatnZL*F#3s<_9j+-4w94u?ba3+K4TipxU1{NvT+o78CjH3h<{H)&6vn2lGiDs z%lWt-!14z2ZS`Y0k}2)jpTqw3%2Cj?^>&6It>Fb5CI@9VVF)ArHj=m7_*()iUV=z+ z@Gs;F;J)wgwH%I*guJtaJm^Cd6;ZE8Rr+nK08m-mO&$@+ifF~&WfS{xnFX=ty>|9t zm?fc+=jm3oy0{gD+`=VhmHQr5y{uRwNapXcPZ;|BIL<51%yhYt^g)ycFU(@D&z*Dr z>G8Z)Y7ejD(*O2n)5$|r`XJ-flU|P<#QZpqe|`RvS>8{xMg4q;A?LOn{*J~a6Z|IJI1sy)ZlsD)3uPfIMLsJ&)y(OHscD%K0-oYrngF0|`gi=4DHgGqeS{>J7 zviM$GLqLsbGkCAHRGbG-UG>-=cj1P5G=B(QTH4yphV5n8*3!@Qt1gIce(BEZqDo{5 zcgeT{A&Gb)S*|-yy&jE6EunEbFMzYgww5jL1o-DYaIY7-J%V9*?6@yiM_b*CsX=0m zCm>8`8giCI`R4CDmg^4jlqKeXS$$w)3fSF2D<@7mPkl7Xak^({jQo_~*B0N>2QDr* z?d8zVkp~J$+8kxSSfnvJf3nt#@8;mwCYzn`(Uc=Jqx|?r`S&*&(`TY!&v(o^*K458 zvVrfNA{4mRCgN>3_y&fyHpDVOniH)qmWRy_xKzV4n))}+4_8@v7&f08_8B^QaZOX+ zDt}WbVQKo?_qh<^3lYK&z%=fl%`brI>(>_VwpnyA;r*F45oMZqK$jaD$y$s79Ju#=nE-F}BWQFs2M?vUI;YcUd94U+BHD zS`?}N&^oq%VtAZ|-txP$i`iY@AGaNLL0Q^>FX z0Qd^B%#NB|4YVEly@rRu|K*+K2)1Ll&igp!PR2oBI^<`f#53zrB;C(b-3f{l<^_$1 zE~NL;v~GwYNIJo!pV({d8K)7Bfs_P@7p+g1CKjk^DJyYresz|=@z8PCwhnLCoAFd3 zbz4#Q3CI2Zo-gYp9a0e*HVi9%{mYS>|AHrdR!rDLjX{wb1wF$LXOXXv&S1=@*dP58 zf{-dAO==mNvX2@jFx4a~WG{^F*w9~I?KaR?i(VoA&D(@WQ3J(^4c+L=bsG7~OmEqO zC;_7Gx*p=WlIi$@n_a42G6CmzO~NoJLLyxAcQx^q9 zkyiQY-Uay64x(`+>{ZjVSneZNW-Gh*@qlwgxLi;+%K0ef{JlxOG>3ny!8I^=wa?ks z+5u22Tpem<{ylCd`@C1f(7q49-)x+803EKAH#FT=m7u;4|G{F#3HVM|B>TSii}L&4 zwKd@m+59)J2iN9l1G-^Ryx^yYy@p$T+sNp}$Hbv+ws~}Eg`i6%=lvj2KtGfG=aFb& z-&zoYI-!IhYW5k((~et?W1ABBLQi#uN}7v-;e$faAYt{)PJB; zP`l=_M9!r4hbUUBGd(Zx2Dde8~>gONJ{+Pz4x?eFHOSDhfg9wMv3!rOFd`<{kNTsdXuv6Zvv5Z zHfb_3`Z6=W_0lGVp2e8(=rV}8BmRmznUb(U%RrM6eq>~^Zm^%kjm%28GR-DvOVH#Y z!rS%0-kIKH*!@$r8)#~K`?xB_@bRSX`R2X_?0j@jW5}24dk|i`{97xiTHBm3i~i~l zxG56r^@t5cTcrRsv9gv&nI1M-HopVFam<}#<7oUhadMyL*4_uQ#64n%QF0FpHRT>L zgA?cmFF9wHkFUZHx;?9YlI0x8!aQnpJCMEXwo|*6j6J}D)3@ekX&Dmqfv4qMtGaa} z+59$L_8prW2~^2lH^Is)Vsp=s(EnK8vlH!^#$Iare5_)|hit@cKUH-aM;0ZT>}(Ss z`_o4vF#E^pF&&o3k0HxJT6?)=QVev%Y11?jn!MKuc5- zv_drO{hWO0Fr+sX2gvSZ!?(1RQ*~4OS;k)uLGlf&Y$w+-XZ5@%*kL5~eLBr9do=-LsDZ6P?jB7F+gsJ{r zoatH&=pYl@omPk5!%cNhQ;XhC(j+VKju{!8^*#{*5$#uMXfNG4aq7=61ujmPGY*lB zqO#9TmE%UBH)-X)-M}xh5b_G^vLenzF zGpO)p@TgAO6IOt9`&pTRYS?Spj!8UuBVQk9x|a(oiL=*bN^-zzORqA@$$ymBVM0yy zERF5L{I-nPzYWL5%Al6JTYR<6SZa7y7@!hXZg3pRB)yf}!n8}BKqS+~INo_j!s{w5 z{Ed2wJQ*Bt_q*z?1*N8;eb!tDzwIfI3`d5iadmnm{yU$tl+W;*eNKa!6lKeiW)COe z!_>TlCg=2+Tqs1cUYV#E> z+aB4mPzbXTQf59=q`q(TLiNCSyiwDMV-osGKrM5Y@9B=iMQj+HhKb|4m_&IJhizfjT0$$5_ZZdg<7VOR<$?QyK-8br zP^)JvL!u=U%bXbe{pMTMbow(Tws$?O()L5O2oa~ep%)t=6Nd_rx0G=Q#ZgM&n@uS4 zDAqN#T@X&pg5YE5I6eIwe=P3zATS~AgZ2csP@UzcfNz68Ynhjd$#D&cI;lscP|vv+ zY9FI&vwA;dGA-+|9`kYJ&_F(ns-rj`VhWJbIl>MF-z(K{AYUPQ?e)G#>begT1q{rs z$5cc+f{&Fpga@S)h>NsJA2TvUST|{qv~&`uH2p0J~D z>@UA@&Uw7zb|UsJfI&Wm%fD=L^=mq~gSFU{hY7 zlRc8#EdC^a)o)O`^r0taXpJB~B<^uX3$e;;B!Zsl_G0r%#PIh4`)|ROL)Amp%Mm!L zn&x{iq~1-dsr-X$P3x(qKH^c5;t6vT%dm5RGIsL*rM567LTq9pb}q73IGwSavg74k z!uqAs6v|SphPr1%=PzZ^=!Gk7goUAg3-2ksk>W@jxtr9%Bh>{P&e4cSF>(>_uj=>R zx6y$)mE61F__$zn##f@%RyxWB9O#YO@2g(XVv67ilIdt(=asX2%+SuR&796yJ#Btu zb6k`>nI7GhYbe9ks`$Kj%*15(ALN*5z46oJ_4i+RxvWbz2mp8pac3J~@?D-ZQ4fNu# z&ri!=L&i9+Nxk@&+`t1H6#fy;Vdl3oE3w2x`-2FniJ<%exOr$>H;NC|EjNq9q+Zy* zB>=$p^?tS?{vDxcM`;FrzK!QZRH_G()b%(lstUHK zv9Y7L(3+!hESOF>iBFrb%kZ;);=m6gmjLL$mWi*5*n0ToBt3p;#dFMXDb2$q$W=Sl*Y*e5j2*Xb?t0NV z=fY&&25PtyLLZAgGI1pRe^FuUfj6l#-I{3<1E#GjTORqo=4AZxphis%$t(W3N5P>* z9wBR?8S|q@0QHID`RS&pj^`5_>E73bf}?L@nwKU1UCAS9v*5c|zH~PdRbj_6P3~K| z6Pg}09YXtsNEO%~HjE^%a<#1_JCZ|)J?{tInx7q_V_dwHNKY9S!SC3s^fP25y|r#R zsC^Z!3>^xwuZ?Jau#~alH{~%dvU-JYSaN^v5ftniYR9(hPqyT25Q`Z%SC>1IbN4jaMc3x#9>4mcks%JSdKedF{WLd8 zYu@KSty{1wTe(|VGD?dN%qB+d*Lw;WcFWNG6lQ{W!HZI!?_sbi9vtVqs_jtst{YXd z73rqq7`Rn}U*93S)114r%Lbaxy#B26anH23~C=4Fhce&35gb&z3T$GQhka5*aVVe4>uAJ^4N)&qzJR&>K6 z=K?%b4@zENznVqAq?BnSn=K(D43jQ!%4@0n%3ybH{!1&yAtZ9wpaS)c2~^=*2J@g2!0 zahh%ExbyMpAT&YoHmoQbS{aS_w{+KyZ^oTh=iaO);BeDl!4?9=xEI9!Tq%f%$j{Ar z%}0iDRoGSq&#CS?E0$UB=qPKV6f`FM6rCjJ*re!eUzJ(c_vBK#&@Dw9t6I=5_=G;U zzZ$3e1y{Od_7i)2`*-IW?N8i>D;k$W^G<4hQCLFQrK9Y0@h6);1zbA@UgoAQ_ zGL!|oxg7n~cj8Bn{xoxGN3oJcPiisPf|XP+GMZIJY+}jL#5G$md|Ne*;L^}#dN^TQ z3#IyWsbVunbt{R61Bp8#H{1M$88-Jgge)E2);Jq>`SGY6m?Nb+XW!;>^5=6}oieD8y^oMu!(_vB<>5#MpY| zY)O8-{EbE}!uW74r`PIKxD$t_ztx&?Cc-#5OP7QFo$KR|;d}ipZZ>}dC&p&fD7enA z6VF+4!C*>=Krno;aYZ#-F?1gRb5ZOXUf@%&@5u)FcJj{i>nZtIy$B%&Y-w{Buo$9A zFP`u=`!7LM!ydm5w}9?InIg4LesZWRy5RzuTi9PLUbr&pR4ax1ok zSwD?rl7Y&^CjydUIEME|116a>3rJF*asJe^6j%54CjN%8u`C{agd;(fTjWv%{^BY_ z3w7peDsvfE-+syciSHpsLAD=pP-HYj&HX-Ck-L%V4j|v6x^d#6cJC|$rAe;_w|yaW zCK~M1Oj|NC2~+vXAhF?UCw+Ou3F{28*uQo7yq72Q+ioD^(USzYDST?zOYWI-#^9U% z&f2V!TBxTw_J*d!jjz4kG6t1lzBFmCu7vPP{`lDKKEFTST`qF44qB6)EjG0j_>s-& z6>;n3t zPUbH3EuH>U*S#{f`h~`+XR!NT2LJj}`9@Hv$%?V?Lb|s-NchW zUgzgU=d4PO!EnS?ezK9KT1@9~nK*azZ0#L;wWfgVwm=(^bAo&5yh4GcVx2bbYb#8= zGnUNCvABXw+Rf9eD?cISD4zb{1`|kHQ(8{J?ugZ?G|}t0e2^a3_~EFH&F=AIr*a+< zk1B=uDwTjKd!`*3Dq4mu&YhIYafmfU8T2me%2-{Fec?;weq4CT`(@?px3_2C<(~&} zF%F)tshlj2E@z9X8FF<}=i7`9(sR&hBrO&uMZ(hj@W!%|l29_y>TLncpV5)hF`O)< zcZqG@Kt1HHSuFrb)(d~ta<7M!Y4p}T?3gA5LcgZRQLCWhBiB1+8Ma7pb^NTXDB9F$ zqxV|nEg4OCB5B5z+C{#P1ur9Wi=xt zEDTj>l|QkkmgiQ2h9!6;MogQx+iTXrCGi_31k%XxBsFhzG;L)+|z0(LE_1XD;G9<>jZ z*H@69hz1SN-H+%h&&Evc?NzV*r}*hP47~#Dx@d-(l>w}GZ<&)^w%2%d5)#{UF!2pu zZV5g^?}|c-W%@891@pj+B_dWzC07ob2PzpIIkngUwUS*A;dYtG(QtEg-+WBVM_Gk`jSBp#1z|=2@?tkEB|D$4< z92@9(tHU{MByTH{M66q&H$M34d}X}&y|VjXcKdi2EW$H$?(^=}Pqy+V`qaemT(vhC#t4?_l!Jc4|L1Y0Mqt~h^qejZgj9jBQoyuyjlI3&!7shk^pTzi^_TyAT z2^8)b_RmDQ20(Z8hdg)T#-D8dI9N<32Bd4Js(iKxEQ#B{lb)kLltsB$Bl?jiC2S&P z@EF=ElcVf;ABB0Mq$j^4nDWYTNZyCgWIiBL)_L&q6SWsP`)EcGL3O90X8V!_qAG2W zXY@MTzE`h4UgCxc_WEe+iqux1?05j|&1N_OKb^U{mcAmJtV}m=U~s~9uUBWQt)KYh zaiRr!6X;Nv{iPyZu&`KYm-0^k?1~fD zc7LHmB_7meGSD4N5nhn@`VbBMhDvbX{3659eO@3e@guSZ369>LAV;nkaB>;L&19qO z{@29I6;RP2*wsn_;Reu?&TLnD9gorH2@}#qlex;or~IwEB7g$39DhWyHF}=}FjMk3 zdux%OSl#6qqx*|nZC0K9l6aldbfp-@GdbQgBiVY|jmmyAja8iL0?s2@``1xvwR?)$ zUB*68DEf}W#a5NQt_nAic4)B)R zmLy;Ku6=y=RRNGM{%t7}M{Hv%j3qCA+NHC(nGi$~*6Z14-K48FLy;dGpKAZ^w8RqI z++{5tHHb|@q6=akl_Yi7n3K=DI2$2^&tUPlcYA^V3$wXra5r{&{g9l0K%4%Y?%Sgg z$_^$ieW}-4`z8}ic#1BoxsoxX*$E;dRve4#*2Ih!Ywo)Fh2wPlG+VJ^SXpezT_d%7 z)~krI2Syh5RrGhfU}Y~nM>67DTAxPDzrTKG)kJdB4EEE$_TFylta6tYJ7*sI>KfXB z0^7#irrY)p%%Q}kQPfd=Ial&#cgdd#Sw1~@}f~n zJ~2%tjt+nv$fb8Aq54c22cXroCiJj3BNQ-#Na>J_9eO1B@E-7=KFf#}Pcc)qXi!xY z7oxvoSQ+K~+08A?9Qg9u4s3{$8=~dYe-3yC?0Ryq`w~+~zkWSIsaNL7Q?^z<>&_iB z;o2??;T4tU%c|A~skH>SlsV}q3rAKaVepv@GpuV5XB${fm>js?nJ6@pnzz`m7s<|O zd!WxJbmaU^-*=<3qkmobPluFK6T=xpMrcO|>2(h1TPNB_cXG7KTSt8I?#wA+#y*HT8KKc$BNNVpMwyIDe>H!!KgnAo+X@Y$c zQJ|BNn4e!66Y3@w9)n8!i@lR-28+E1?An<7CqU}V5eQym#||B4=4w<3<;VWT9Tmp` zwX-hIzT-1Uw!yKF2^8$nw_KRPCVF>3Z)Ku31c?JUFX=TxO1ba0VnBTkaRBRSz|I?V zkzND!Y47ZzxkMcEus5}idlziKMX>Zx) zj)z$jGGQHZ=Xb?a;u*xb>M8FtZEgUC$}?Xb_IImZ%2bMNx;^!Fd_NoBYmE;vbfRymf_11Z4TaU9_Fj9 zl3pT=Mr-_fjAG@wS@iR4uQU2os>+}UIyCmHOV3(&Uen1&f?6Nr+xvth@0);PEK;r6!8P#U~`8;>HC;@z6z-loID|bpml20+C3{xs(3SASmN3 zzQ}Z?ejpG89VB4K4eKxCs;-O)$>S?sG=gA|=qC|-7B27tvqJ>29bUgt=<2^_!ROeI zu+xQEi=jZ2vg$88EM0XU3Nwq@cFFQnB=sJf;->l&k!D!Sov&9Y?6q~6kPO&m*|h|{Wc`= zK&#~hQ#~phz;|L{KXk{yw2ii9tS|O=rc`|ede&kS_rhkGB9ZAmiA-0^ImR^pAqHBN z@^QM}ZpGeOH%^|7ej@SN<8}y&v|(cQbU#cJ)v|sJvkt`Tq&FUv$0!qxHY#0|h-Ojy zr@VMp0wZnWvZDW*x8i0s3V~{Q{OWO0=eM-K$W3PCnGZ#w1oi}v% zYrf{=wtxK95uwK&(kK zf}x-?8kXr8M`Tme^jID(fkbY=NZi`Z#V;$GXZZc-@rFD{1nB~8c|(Ya&x6dexBORb zrW?~qCohph|6qk2h`I5Zl{GvgP(ZE>m@=>E*ZJ@_Ci3IRgxp@pQ@d_%H}fC6lE~*4 zn|rI*aAq0`8DTVN58-lytZbr#^j&DeV|*UdMx%)6{rB%Sl#(@qdk4+QRq8TeTh%t% zcZ>K|m0TzLrI|}_GT7`J+Fogz4lPxXiFxyeN$lG+CEsRw@)+8bJF*>gx^9@6O&b#@E!y?vHAuz-);w&H%AH0xliS^6pfa90! zxIoI+*Ac{OvD9CW4D-8e&-*Jjv^@Q;$08301W;=M`fJeyj?vXw0Nj)*tfac6!C(wQ# z*sfLQg|UATmx3r6xmEq3UA(G2l9eYt5wP+pim-dbB|VuB%V!qoV_UQDbu4PA5G{;! zMpUxXuDO<=_XIVVCS{nf^{WEsc|?{42=UKdo&!2mmuT7$RL_#g_3kR@o1~$E8YFSA z?h!tcvZj3uoml7Cco0EFzP}pZ zgmkm&X!4Q1rF{2GLTNkU3SPQk>?@`ja7Ku}2dt%#_s zoVvvczci%ZKZ+mM~zz5Rp3qhr|R)%DHoANb$9 zfAIAp&wpX-CI1)B{x`l}gn)pEjEsbg_7A=g5WW7vH~}&W9WN@Oj24>3msj+Bf#^i< z67uT$Fc|nhK#48CPGgcV3T!jM{=wQmIs5;{SkV6wXa9q-f8%QvAp4?>|FQJ^GRS{y zgqPmW>o01_^}GVWMnZT|CL{uY6yRye9~(plmk{o|2qheY7Hn76k3koE>y;U^k)aJg z8RNJ8IJ4GQ)i};iRrz_>ek+SGPN5Zn`;+0q&j7i!?X)L+Q{rd9o-=?JS|o|{447tu z+dKn8NJGJYjiu2ZST%yd*UJ9n;BAy`vnRbNgl7Pz_SWVjp;?$2Tn4EB3`l7N{MT^* zCAZGC2oHi*MIcVM-k@c7sQu&}!>EK1f&{5wB!wq!saxNJ17Hpbm zE9m1ZY)?s4N4T0C+=RdBIR!&=3>VzF2|xxWo+krfj0Y=N&)}jN_5;(7Ms$W^ST`k* zw7+J@&)0bIrYB628fKePWYQD*G*)TfP215CKj=^2IPcCXz{#R%;-x5@i?uZge=~CG z0mFkxg-Q|u86emG%&?l+#-3oGk+9S2Ni^WC8n$CF!B(CCn*RMh1>|PMZ!Cu|;DBj;TUEp#>`DolQa8 zG=hMO(wC!G04Ctg|6%cDbBOdj|L?6C$mCwZf3O?zvBY2S0SR6Q!y2MtYnhfrfqRYk zC_b?F;H|}SE5k#<5O7RSea{Fj?#~@gBN3WQFb-UMxTn4*wG? zh8v{bX0=00aL5C9dJp>nSM1aGVTtDS@T2W}qJb`k)d)&xE6fzsea2Io25%)j1M2ld zxNc1Bw$IWApGN3209O(hBr>xp3-^QO z^kx&nowuARj8D$gP1yTs(55)V`M1XX+v-ZIY#Qda!9Khip{abamo zH8eo#{tqqP8>GK!xp>bD|K8%s{lQn{;Q1+_HU9OZ@!%!Bsz{{xPh{RY9M(9gtbWv` z9DZ{*X~HQ4X@bH!rJ2I73Kz&htn<`{dC5aW5D^_ht8W7Wd?6s-D1*X2MOkUWmtB^; zg?$Ax8Xley+a_Xhy*P~`Wl@|T!qv>^{uKSfCw&kMn zj*nyu_G17CO%z|_mn+qZ6MKbk>j%jB^j=f8Sj5bQMn^{f(|loMmLBBS-OB#2b40n+ zMb9ha_hkYf=mQL1>8rIZP-3pHRoOwWCQYMKL}{8Y>`2(DgI%IjYOF~f!tJt;5#)LUn05y zg36@9j3DK>vBvC;iO>rgW!i&AKGrpKH@A0A*SJ)2dKeo^7_E?-I*T6fsP@#&nYVN* z6=(EcE?f!-E#h|sIQ*;aL1IW1N$&i>SW2TbHhg@{7^WCzKSsv@`Y;Sr>hWe&R6s}x z3iwbre_f?^4w?0kkkU-;_jYzIsOxI(@MTZua`|-7(xJqZP*oCkK5@QaV2i=%CDg(r z8fl(_kGT7Fh%YTnX-|f+jwE(|P8GQZ8J7A%I+45}kyZ0y`d2(P83BWV(F16LeQqK_)a%DPL}ocElbkNDxwI?3H)VlY>B%YKj%I@nBy>4O*e8{ zmz+PH*}t24y?6fUY*TA?#y34>JM!XmK~dWS@EL>Re41;!*0;jFyRD_Yqov+q$G9U} zm7J5Vt37O~HpAFgxd{usx zI%5uCY#9h(rI&fr0jS7XbXc`0v396bX9< z+^Rwk&In*$TXylCR#nGBrqpo$Lih?$AQpYeVdM22$fE2)`7!&65y-Av2YcTxf7`R| zYli2ziJakdKZ$ZeLj;rf+ubxX(fUN)0mDry1Ar2?rIxO4Cl)I~>@^Q6kCl>atMDW( zxbb5kF#p>603Vc`)1-SXpu!DlpH$;!lpqe~9m~uOr|`%#;8iy-y!z^W(ACHy z#p7a>fS=u<&qb7=Z;RulLC=6X7ixbMX}YvUJze1Dj@2` zP-?coytuM&i31JILenN*-U21JFhmyQ3J33lY9-5!byhtS4T#MSMT_Bgm= zf{Z9C!LOc}Bm;{dBx<2vVFKJFAjw1%S+#apeAEM0B`|wnD4TEEpYYh9apugu2*w<{ zeWniqN9gyr7)?e=zj**WCUgrw@Wb#1xQQR+;IN&V$R~Da{J=$QHwp|d3S+Zi&}&Pgko3-_3x;OG z_kQIW5Zg@vtbxcc&wmPe!kg=6G=_`G&D~I=^<}u!xg%_I-V^&Tb+bbKN&-QKu!CU| zBeb|~Jdm6#_2E~L3%mg`E;S%UcHQaIxFz|Zk2lS=E;Wo$cQBO(YOe{fsz5DbPz{#<<_jba`b((U)_m7wwz9S>4E)?DiNq z%*P|`%y=j39UiV@H^F?Tc7@X>(G#Cjdtt`#kAkP)U}&1tR9}tL-JqoCEJ(Q=L<^rT zHhSQO6|K?YUH{NnLHhtxYsx>Cq(~arpa<-MNbd=r=DJB{#g&U{MxwbJ9@JoHVK?!S zVfPT)VGVLK4VEx*N+?#}pS#%RwjIifN2ejumt)?UBoVxMb1p(e6W4agdoY1JLoDpdK$b9@ngJNJqdy}){u$5& z!YBsO&-l|iBs!#)ZS{~HnWDEqA99F2XmNoRgEUA`ydkMq90c~F2LpKI0m?hzrG}lx zY&v*s8R!lC8Bhbqq>Yr6fNc&UfL&pfkoA~s)J1W%%0r`&MIjpE+JzG6V%%$(ewe!} zTwnXJn?ckz5}Gk#wX+#U;shD#Z0-OYN{T#<=QO%T-#i1t{mJ2CFz~P$UMI`8YY3?% zZRgAB4psq~pJ;N}i2!NjnPt7ZA8>-O@4&P_z!bwO1$)ppoXiNhT&l&XEndQ3%(k81 z0~-eJ^s+n=cBC53P7Cg>Ws89O8iAV>{;ZC7wpBJ;FJ{|512ySoKZSV2$6HPj_t>5I zd5Te64L?au3uxduiQ+8wUqofxfn@4nZot)HO_0Ze0H>uuJ@k{1KQG|;LHGXk(?a_* zAn<|OW}9|JW7rJ$!AxM279_hv{o&6}_^>O9$&pXa;NKfD?yzp+7N?H3Mm%C5c-tTD82%(?>R`$}-N}2e2Qr)H6ns=2$*4{e zTpZC@!P1;>6#G#jwQ@YWZLQ~Xwitgd*zw{$8iPgOg3?T;E1S?8b4b@7Fv2cg6k~qO z;j$G~DjsZUwxT%XMNu4$clHXRniD<+w}Wt{BWg8F20E;j-5jyF#Z`nvpI05-jcuWK*Kakvt|-t3{yM}zSsK**VxGkd(rK? zEA@GQCV2eN%1A(`jU6n02OQia6-q+|(}=ABDcWp-v4LI#u5R08uARbPzHmqG!b^M$ zjyJeqMvfUd{T1V4^3bB@Xo#Nx!Uox6oTTJ~C_H|9k`QhNx%us*A17r$NeJxNk$PZA zTM3JHdhm2ee!nft(lJyPS|n zt(ek4CnE*UC@$}n<|HMbCiYD}Eh$&6ET=Z zpCb)Ld$7{1^jq5KoVv}kAGsjG1uM8A>;z&K1yZdzdLn~n{R!`+dja4RW@{t#Jj?2y zV@b6XNZvF_3peiX##}h#_9q6p&3oXuzU!E38n;~C5uEe7JgJxcQml?}9Y1KcJaU@} zJBXLWhxhcGHR?zD6T%skp|k_BXpo%#x1IWs9KtonyW%Xbs>2sMQBnsxD{L1W-|r{` zNx()TJ~d9hY;U$lEov}^z8stfzb}>zM>W)~XbBJE&wwC*kUrEkR-AQ*JwCR*!^!kj zJsiy8bNd~}0C^x4T_`Gq+uagEC*vE(H3mjLhReF51B+*Gwc$~&;=EtD3buO)S%xGC z1TS!hq=Ktl){thz74J#le{>!TJN02>vhboRpd;1=b>EH|u3`yD30{MW)+t;b9Jm2RG%*jSn0xu2%4%vGAaudraFf87ql(oB+PVt16EXf-OFpm4>Jt7E+<^7(5`>@%rbtQY?Q7|WpMTo@my^14le6 zNCisLmhaFmmrD~0iC_8yVF`Z**x~qYjhr6sXyAZ|V8Rjpq7Uxyk}r^F0HSKmgFu?v ztRyS&59A;g#MmrMgWiOAuMO4N;DP(vRobtdCzRXMa6uP?nxxQ-$F{$FaN_|-`U7ad zJ5o#F+h7>g)X}B?v?&9OCD=nkQDaeDep`d&W#gH@eG9U}8(e@TXY1~h!&BAgcU8ZTmTgC}Xb(Vq?;?Xks80hI3q z*Qi2z$V`tn*cYDIL0WLPLU{bZZM$jl6A|Ii#C#jnnq0_}n$n%NwE4Gr^M!4F3TbJV##x3;sh5I+0Wb%qM@qxPOUM z!u>ATg*Bk}B4X0u!iBK#tr|jLxMm~ZZuS-!eOoZyG)dr)Lw!T7F=9l$5DZOudF#4* z_G()yka6}w-VVBb*3Cu>mzAW2M-N>+2J6Fyf7n$O?OjCQC^j#EOe%mH($kzE$}Khi z;SCAE-X}@gr&$V?GZ~oK2ozp2xH8gD`|3yv->f$q5ljwup+z~`c&!oAQ$HNLRgtAP zra$G;LjeC|24C#!q3E6z4%=D~=%U}zYG3q_!v4L%(c`CswC9uq0QYHuWUZjbR|c?e zG|bS@nju=k!>f-PP{o0E@ow%8Xxf=oE|5ejhQcn)o!Yy}w@RYZ`JP{9J41>(1Z`OTd1RRQ!UF9=P5wyUlH@V+byhd&CQpZtLz0*YAejzC_F6mFf>{ zHruv9FCfg}wDBGn81Uwc+mJaI5Xz3zI{%iAKi6zGU=2s|Y4LruOg(flzMFc;t6-ez zPgtb~r_bgf%^MhJN)o(AyAhhQQRlkt2S6G*7chgfp~mJrQQ#&)CNbbGrgINcCA3(; z&A~EgdH>jeDN`eqrljm-XV}OyaO({J*vPgF#yu!%7B0?B8`de}Ay^YEj^XB4v#wZa z3buz2BF#Oafy5un#RXxq5Ym_z+_;jO0`W#Rp-IPj{@Cya52&y1yg)quGKl3d1H=n` z1`Hbggv)L_vn{I4AHeWJo_LnwB?C{GFghr~?B1-Pu^RP*Z0RI~atcov?giDqoq$Pp z2**nDJSZ<#daR3W*$w|U(%w6&skCnw#&HCd5pkE1o%KBH zJ?CFol$GrJcVEA&g|9?G&Dqga%7&0TXTpTyGpWPL&9WvSN2E6_^c)*zNOQC78Fx5! z8$X*phC{DU7NK>f_~36dILrRs>vJ-t1i7}SFdw&Xn5qd8L@hoh_N|~vMO`>;ZwtIt z#@DCNy})JR#OtDtQALB)&7m8Of)7L+%|bAK>0zMHnIY~qUBPOPYox0Gz?B7KPe1pd zL`OrJ`E7o5QbV?<)K1LW;+svHl)OMdEqQq~G#R&<>|N-SBe#izG6)$8bC4mJNF$o{ zFSz47ORSKU1-vuk5P)pWtqQehCCYVRTi!0VVox8=fLFVY?gfXhCd~Nc=SHtO&HAgi zOLB4BNi*pW;pnA!w|E=n2);pd%r~X)!$uwXX$lnF_{EZ>PE~0gz;v&xi71>%+X;Or ztqFk3L^$)u8Im(>C7CV#`~P@oBiYZ3-FZL2=Y^uJl!M}O$~hb1bH0ijv{i)DTdP|% zG>(d&o!AgDZrQ>(0WC^Cl|@$gy(V`{U)yfP{?t~$Sj;Z?aCP&caYVQ3FDO zvG>goI+Z=AmhHVf$sXVNWRQ)@LF>ixQ|F+#(;V>a_&S2hG%|*`fsdx~MyMC@feu}x z%b8NJr&Tzy<*jen=y?0pbxErx@T_9S#*TtrLbo;Z7d&I$@Ir1nT}xp)6I~q$&$>T`a7;4zvC}~1&gaE;Q(?dS zIJ8BDP%hV9dxkTd=2OP-8Wae=+UC(~od4W3IyPlBXlX*450IU!@jy)S4jpHsZqVcxL2&iMRn z*FN%yf5oG>;lqGxOER&Yi9RGrrebtOJxQYdFTapVGtgMPHdp*lDf=Y@T!->X^pR_71-sF9+ z7~pGJ(?ez_4CQ1fKgmY;nzj>W0KE3dP%`*cq%a?H=_Di*t>6%8H}Z&44Pnkr@b!!q z)d6hH$qw?r)YH>T62b$s1G zGHnCw1B8I%Pam3qI0M?qI3kXGik{@Bj5{Wno&-bh{P=k7(FaJU@>eL=4+_osp-37F zxB#eaR5xfi7nqXk*L0|K2Do1r)6|E!CuNZbeTK36*UKqBzI_eWkyjG2lVNo5yhY4} zS{Evt8(cB&0?v%$wPy@UvR5rAj~2N>x~aT9{*-*6>-a#7WPSK8W(Kld#eunAaL8u3 zOn{v9&$un&JTo?&HcAjZY$E@lYa522d{nZ12oq@O4XKSAHG%VDQYucTB+$|w4%`l* z?oiz)8=MgCZ5!4D*`SzdSk+STS~>_U0}~9KF2%(cxs}%9eW4L#wYq%&(IA_oj2EM} zQJ8`sj6U9>nb14Vl)M}{KEgW$X9`EJm~4h!9*qWGq!5;3M!0UF2~`wbse7v^cgqZV z%e<$UK8C|Shf~Vs#MQG!)l{=V>h>DgK}6FSwqt(4RU}yt6FX&M`WD{SKg+|%O$-W>Y>idV4q)gi)CWJ1`pPOQ1<8Q3{n`Q%c$+hc5xQ#h}@#W=1xpEOPw+mK)KVS{_dMn#!>tQ z_B?(S*ENT~vPyV8$Lm`wT|%|~qc}s09z&!jz-LKvFm9CIOz*|ceNzhlrsVu(>bv*< zFOqkI(!W=CsoATi;JT$Er*B8dKZ(OE7!JHbanAJvso$r zI(*Rw*b1K(L(bhm-3y6Rdd4ecBqhqDrEbqYYG5t(JTg*oMl;`()TwS!ZM%vif#){c z5U#BkA|zw1Y+PDFu42c)96J&9xQw$HmLCC_>fwds zY55~c%1o$CvDvg5ZuJ8>5 z7^tSIRKmwcUKlV%be)r6ydTUDhVbqL)D&-*L2DY-B4+-^x@It1hMsGpL#U3zXt9K` z;@HZdk^#K>pMayTD|}2gcVJoinNdgk3$08gRa@uDh9P3vqhj~KIAL}K$fQwu^T3E6R?f{JO9izIUF-L_3hbBo#Qh4?3x3}7yS&DuIfDRP2drH~QEBzIdlZu?+k zqN8~yZvWboc`9lZFL!y|7o++vPFN+wXW^_V=oiSOdtMoCV$<4pWf7r1Qj0@M;V}<5 z9y)lsjobT~6wn5`q;u_9>g3M-W8tHb6_ZW9n7Tv_S~S7{ww5=|@=TK^>8T6+jeLZ# zv3nH0Jh7SgL&P|y7mM&Fo0v)4@R=*anHBQ$3@@lK40$jX3A!^Q=uK2R>D*Q^&jQ+Y zJGwH=ezExdU;f6u%3dR#_%Y$pT=x>f#$&*Vf=(v;MloQcg?cmF#R{FItBk@$f?JYL z^H|bmHX&o%_)eIa07R=t+!)ty8pZJd+uN`80W`l$5#>jcoo6hlEEVVm6R)pj$}WHNw@6Up*U!R`>LOOi-I4V!)-M z5I{fL#Ql)6(h`ER@LKdJ32M46a{dlYk7DBq{lr)1>A?)qN}z{AWC;3;&WB-Iz3C0_ZHXNenZ0 zLQ`%Q4UP^dwCTyX@8Ge{6;jTkjT+^*@%eLbX{n7cQ=&FCOfzd4X_T7-DC8SmBAR1! zf$N=-dUagzM>*6p}YVHfZ8i(~}z5|Db&NkP|-P%CB zsKFB;B=T&+{hb-)hf^gxTZbAyA)93;N*n%qnmvrAm-|w@N5opk0G0*?=kO=T_}XHm zwSH`v5-zT7t>&9jl)BwXL5%VFf6Tbp3bmBE1YN#o`O)BRxcb4MRZ+!1eh#nXiG;#r-KDIxIndde{*fz=z^ z(6*e$b%5M_d-H_Dg{?xCHf5xv#r~)=Bc0|zdp(YZk5p1#NhWbA0Bc*styXa)`qbmz z7!G?}*-tY)B@hj=X1Jl`}%Or(vzYW1Mz85l#mp0|ZMXEliK8xO?*X z+~$Pye|*Om^0Oyea?=oqK@yl)!fumR>!l79W~aBwcBrT8w&KvV57DxJudY~Y+VsWG zgwVPG823@=4N|NyJ=bMtizXx21D83~6qXz88taHY(JS`@enH_NJ23&?9a2k~Sk-+o zZrDe~ms5X&lru{s@(?NlpGzh3)ojsOH0`0kp@HcYl4xuLRC2F@YRv4Up$D+KQkLp8 z&Z>RzVgDiuMb54}rkq`)O5w?;ORqy2+>lI#Znkj&+&%1p;ckc@Wiox>cP5%W`J0^ zVgU_~>-1t>qE}CkQ}c z%3ym{7lFatjN2|h(PxWC$+57o95%nu-WNmAnlaJqjiOpY`dCw0K+n$-&tB3Q$eK|> zNgRt9mQ<37#t_z@l)mt1!%4r6X+&)F$-ytzz0Zm<-2v_4Ma6BRbpAV@uHTodw1r|L^1UCx436XO=> za|*rw2j$9A00r-U*Oh^pSA)q4tx7OqqH}&d^*G29Wd{pr>)36XlM763t_@fu`lhtb zCdP0=ZMu!qz~!m7=Pfbv01Z}Y0L<&}w9$o$r$6z!%U{1yig!ukZKn+zusSBRC~eWI z6b>~wl%;ry$V1WzBZ}Rss+aEmK>6m*~nAdAzc}qp5VUL490z>1x zdEmMss@0*}^twd1MrzdBlL|2O)9APs>>`!P=F@dewht(*+yHbcU$YeSNp{c8lpmBD z+lg*mbCFr%bznQ^X9Ju$7a%{kT<-dK05iXpiUVJ<%4a>~w(#90qUuwn$-VS2`dF9P z#1vG&C;9Z6s*Y$J+rme8Jp&@GXN84xUFlR6^@+~Xa{sZn@KTp}ly!UYewUzO$E+7b zXYA_Z;sIl{C$u4XY2S>!RsFz4C5pJ93k6%4Ri$7zh1&_Zrz0 zRc7orB|{j|xt2oU2{N?)D15t=LI;#Ht0=P5g9=OsnEKHjLJOIi@Xu&v5*eI;zq}lr zqHPBih3gjWK7CVyRF?V&an`dm6}V~AMiseFp3I<)Fxhq@gQa3bjfU?1ZAUl2?fet` z9B3(SQ@)25q=2mqxd~4Td{=OXd$JY*ux%rZd|{`5l^7ziX>b2%~Vk{y)Gl zPqq{_P%n3{_LrLvuu%*qm_=cVMq~3?6~fiye)3D~p;(dz{AXjmj~HhTU!Sbu8NuVK zfiA*PmlWC_`5uzt^p>3^dm!gu)~$5>|2XA;X_2H+Xk&EFNDe<#q9^|Wjs)ig9+b?B zFh|PF@P9r-`f)JUvSG(pfkCbiua0nz13Mlkd1Q2pCbm#dL%Y;fUbNbm+y;u&Jv&=& z*`&M&-~Vl}He6P&bdySF6XpiWFGkRI(36_s&t1m`ar>w2cuFm>52Vr&H(E4GO(=T7 zRF00x{(1H6km(T19F9yVr5;0$PQ6WD$E|{tfeQs^`(mE|!QuoeefRm-Rg$8=t*bNJ zXB9t55Lwcb?O7=?uc-&a0ZAgFhHXo7I~cuEfyRl{ge z3kyJN`jPGRU;?sz?;O!3KtHI=-24BmH~(!5?El<{{-rzg+_V0L?y+|KIqpEc_58wp zYqNe<)Rno9&j-w&CyoW4u6o;T*b`#b(`*>eXr+vXZOSUIPnOae+@-`TbN61gIP`@P5>$QBc!e z)eWde1KycI6~u|PT*7bv_z>*yS9niSNe^E>y^enH{!K~nZA$pw+Lmp_RL3hs-A&#x zO?pNj_t&MBEw(L~`X9zxpH8nM-m|{-GB5V7@gA>G6zYHkJaQIq{X({Fhs}YC^y1A6 zk6%AU`n;7|)KOKsG zI8ZgfnoptCw;Ki%WAH+ZXQ?6B&`SS5UFY7Ek1zyR!rH}Q} zygEl7$edJOz9*K4yWaC#_Msb54e`kw8*SJNQGOeEjaaLSVm*A^Y*sWs>&mzeBJ#)`>GzYt0!JZh!0J>G{L0^ z>6h0boa-5{yHbQemH(quzvGR&wAZ@M6JIef_MG6Y12rPbrXR_{SBWDArD-eoHrC@T zQLen8y$RUbe9cQ_smxX5W_ASS=HT*OQ%Runh3T{B|E^f8QRw-R3DS1&&x$VMbEMja zg_{0lYJB^Up>O{jHnM^0;7&_$bMO7w%Z`uq6ce?f7<={NCDu}V=$XDtgZ0Sf6DEu* z$(Y1`sniL+x-c~)@DzBmGFf}iYTOZy)ZCL^XC$W>z)u0_U}@rC2mZM<`Z=Z+D}%NDLO1o}n5NfzW3CR)Tym`3 zkz?##MUUL*%)k9jiHuJkw__60%^(+sE-~{(9wkUln3QY_w`fGA-JoqMINBU=KgOx@ z4H;EUSv@}RcV8n0-HNHt1x^JQ2+CHYShfh4k#^wea9+-O1Ms(=pd4Q#iH!(r`RB~# zOkIzG9Lupm;hrOB_dF2^^2%#Z{#ap_OZPUll4a%Yzqhl*Zex?%-h6f0&!1v9q$`H2 z3b0m~Bj=VbKpKtVspa6;+Ldv6XG}P|y#J53vucv0 zul02sQaUf~?tegjd4b$pmLc#KPd1S;Jx_iQnhmnJWS((%&&Z#UNw?wIWbohvDI;fx zZ^|EWA#c5qQV;G~trY)XV=hGWHTa;?t5U?$l7RfnKeJqP&L{7C88h|au4AQEMHnE} zd&#Y?J#_y(X*7P4)1Kv@J(3>#+|ojhi3MI)^5L7%F5!)I@&{{9C$Ga>2b`H1wfOg5Fnqg+thiaJBxD5cE}HaY^S?bus`3sSZ7y3wcYph zugE#ib^7rUzhn9O8Ko)LR))>hkBtcXYFy7fXrAXspi;9RO+?TPi~L5S7vA0Ia&GU% zpw%g@9scb74a=E%qSo1P8Cqj%`JhhE z_pfj=G&-bpk7{+%eQ%&q6zRV7?{`M+lQs7DqYi3rbVH}TdSb6_G-LkhYnF+Xvu}7! z_nrr#d0a==*QYP-$Akgl=W0?KlDa~MWN31Z@5|bfpybjcE*qS7zls?4NM?Cjii0ke z5v{kRqDDt1FHKE1ka>IB!Ib*Kk&GC_ScsHLbuTlVm8eAU_Vr`#vui^84aU3&hGj2S zz8*`cx_D9adf>`uTz6f>%8lZy@1@JyTsL9RRqf`@nW~B6?p3_;ZBgxouX}NU=|bnW zR5J<6PiQN!=x@+-gNL^FFoeT(n{ti7(T7cX4V1{N+&RxsHT((%hqU zPi6DM-7jz5vR%)5(A%nTu0vD(`JRfAiOq8lA4~+mo$Ah#F5MDkZtp`x%>TUDah0#d zk)({CZLSPYFB4w_tWbU=r-F?3Ghqme-bIm{Gd-JSU^@WRUvS-a+lJVcaWUZB7 zgBl12YVrq#F4kQaL|^GwF8%DUmtpH|$ka&{UAoGtF!=psnn(7Y-YBo?SNESFmNFG# z`6q(wL%*Z zN0N}XV^I2ASj?OC*R40a+I_wLP46Fnf4S%#pW=%QqQe0A`x!_7MT^yo>e#@&+jd>g zRx`umg0WUES)G&Br=7iI-F=E@AzuIBn{}>?TOJbr^t#~A1?gUEbDgv*XC24<1C<7q z2T>c_UKid2Rkvbq

    FHViFRUjJ7E0%O@ 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 KIs9OgLH`Tjrw0-M diff --git a/docs/pyemu.mat.rst b/docs/pyemu.mat.rst new file mode 100644 index 000000000..9f4af623a --- /dev/null +++ b/docs/pyemu.mat.rst @@ -0,0 +1,21 @@ +pyemu.mat package +================= + +Submodules +---------- + +pyemu.mat.mat\_handler module +----------------------------- + +.. automodule:: pyemu.mat.mat_handler + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu.mat + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/pyemu.plot.rst b/docs/pyemu.plot.rst new file mode 100644 index 000000000..7eddf4517 --- /dev/null +++ b/docs/pyemu.plot.rst @@ -0,0 +1,21 @@ +pyemu.plot package +================== + +Submodules +---------- + +pyemu.plot.plot\_utils module +----------------------------- + +.. automodule:: pyemu.plot.plot_utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu.plot + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/pyemu.prototypes.rst b/docs/pyemu.prototypes.rst new file mode 100644 index 000000000..26a6a08a0 --- /dev/null +++ b/docs/pyemu.prototypes.rst @@ -0,0 +1,37 @@ +pyemu.prototypes package +======================== + +Submodules +---------- + +pyemu.prototypes.da module +-------------------------- + +.. automodule:: pyemu.prototypes.da + :members: + :undoc-members: + :show-inheritance: + +pyemu.prototypes.ensemble\_method module +---------------------------------------- + +.. automodule:: pyemu.prototypes.ensemble_method + :members: + :undoc-members: + :show-inheritance: + +pyemu.prototypes.moouu module +----------------------------- + +.. automodule:: pyemu.prototypes.moouu + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu.prototypes + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/pyemu.pst.rst b/docs/pyemu.pst.rst new file mode 100644 index 000000000..3e385524e --- /dev/null +++ b/docs/pyemu.pst.rst @@ -0,0 +1,37 @@ +pyemu.pst package +================= + +Submodules +---------- + +pyemu.pst.pst\_controldata module +--------------------------------- + +.. automodule:: pyemu.pst.pst_controldata + :members: + :undoc-members: + :show-inheritance: + +pyemu.pst.pst\_handler module +----------------------------- + +.. automodule:: pyemu.pst.pst_handler + :members: + :undoc-members: + :show-inheritance: + +pyemu.pst.pst\_utils module +--------------------------- + +.. automodule:: pyemu.pst.pst_utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu.pst + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/pyemu.rst b/docs/pyemu.rst new file mode 100644 index 000000000..7fe4952c0 --- /dev/null +++ b/docs/pyemu.rst @@ -0,0 +1,81 @@ +pyemu package +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + pyemu.mat + pyemu.plot + pyemu.prototypes + pyemu.pst + pyemu.utils + +Submodules +---------- + +pyemu.en module +--------------- + +.. automodule:: pyemu.en + :members: + :undoc-members: + :show-inheritance: + +pyemu.ev module +--------------- + +.. automodule:: pyemu.ev + :members: + :undoc-members: + :show-inheritance: + +pyemu.la module +--------------- + +.. automodule:: pyemu.la + :members: + :undoc-members: + :show-inheritance: + +pyemu.logger module +------------------- + +.. automodule:: pyemu.logger + :members: + :undoc-members: + :show-inheritance: + +pyemu.mc module +--------------- + +.. automodule:: pyemu.mc + :members: + :undoc-members: + :show-inheritance: + +pyemu.pyemu\_warnings module +---------------------------- + +.. automodule:: pyemu.pyemu_warnings + :members: + :undoc-members: + :show-inheritance: + +pyemu.sc module +--------------- + +.. automodule:: pyemu.sc + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/pyemu.utils.rst b/docs/pyemu.utils.rst new file mode 100644 index 000000000..adfefda6f --- /dev/null +++ b/docs/pyemu.utils.rst @@ -0,0 +1,77 @@ +pyemu.utils package +=================== + +Submodules +---------- + +pyemu.utils.geostats module +--------------------------- + +.. automodule:: pyemu.utils.geostats + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.gw\_utils module +---------------------------- + +.. automodule:: pyemu.utils.gw_utils + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.helpers module +-------------------------- + +.. automodule:: pyemu.utils.helpers + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.optimization module +------------------------------- + +.. automodule:: pyemu.utils.optimization + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.os\_utils module +---------------------------- + +.. automodule:: pyemu.utils.os_utils + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.pp\_utils module +---------------------------- + +.. automodule:: pyemu.utils.pp_utils + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.pst\_from module +---------------------------- + +.. automodule:: pyemu.utils.pst_from + :members: + :undoc-members: + :show-inheritance: + +pyemu.utils.smp\_utils module +----------------------------- + +.. automodule:: pyemu.utils.smp_utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyemu.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/.ipynb_checkpoints/pst_demo-checkpoint.ipynb b/docs/source/.ipynb_checkpoints/pst_demo-checkpoint.ipynb deleted file mode 100644 index f933e47cd..000000000 --- a/docs/source/.ipynb_checkpoints/pst_demo-checkpoint.ipynb +++ /dev/null @@ -1,1072 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# The Pst class\n", - "\n", - "The `pst_handler` module contains the `Pst` class for dealing with pest control files. It relies heavily on `pandas` to deal with tabular sections, such as parameters, observations, and prior information. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from __future__ import print_function\n", - "import os\n", - "import numpy as np\n", - "import pyemu\n", - "from pyemu import Pst" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We need to pass the name of a pest control file to instantiate the class. The class instance (or object)\n", - "is assigned to the variable *p*." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "pst_name = os.path.join(\"..\", \"..\", \"examples\", \"henry\",\"pest.pst\")\n", - "p = Pst(pst_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now all of the relevant parts of the pest control file are attributes of the object.\n", - "For example, the parameter_data, observation data, and prior information are available as pandas dataframes.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "

    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor200.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 200.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.051396152.1458headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 152.1458 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A residual file (`.rei` or `res`) can also be passed to the `resfile` argument at instantiation to enable some simple residual analysis and weight adjustments. If the residual file is in the same directory as the pest control file and has the same base name, it will be accessed automatically:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    namegroupmeasuredmodelledresidualweight
    name
    h_obs01_1h_obs01_1head0.0513960.080402-0.029006152.1458
    h_obs01_2h_obs01_2head0.0221560.036898-0.0147420.0000
    h_obs02_1h_obs02_1head0.0468790.069121-0.022241152.1458
    h_obs02_2h_obs02_2head0.0208530.034311-0.0134580.0000
    h_obs03_1h_obs03_1head0.0365840.057722-0.021138152.1458
    \n", - "
    " - ], - "text/plain": [ - " name group measured modelled residual weight\n", - "name \n", - "h_obs01_1 h_obs01_1 head 0.051396 0.080402 -0.029006 152.1458\n", - "h_obs01_2 h_obs01_2 head 0.022156 0.036898 -0.014742 0.0000\n", - "h_obs02_1 h_obs02_1 head 0.046879 0.069121 -0.022241 152.1458\n", - "h_obs02_2 h_obs02_2 head 0.020853 0.034311 -0.013458 0.0000\n", - "h_obs03_1 h_obs03_1 head 0.036584 0.057722 -0.021138 152.1458" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.res.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `pst` class has some `@decorated` convience methods related to the residuals allowing the user\n", - "to access the values and print in a straightforward way." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1855.6874378297073 {'conc': 197.05822096106502, 'head': 1658.6292168686423, 'regul_m': 0.0, 'regul_p': 0.0}\n" - ] - } - ], - "source": [ - "print(p.phi,p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some additional `@decorated` convience methods:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "603 75 601\n" - ] - } - ], - "source": [ - "print(p.npar,p.nobs,p.nprior)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['m', 'p'] ['conc', 'head']\n" - ] - } - ], - "source": [ - "print(p.par_groups,p.obs_groups)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Printing the attribue type shows that some are returned as lists and others single values." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "print(type(p.par_names)) # all parameter names\n", - "print(type(p.adj_par_names)) # adjustable parameter names\n", - "print(type(p.obs_names)) # all observation names\n", - "print(type(p.nnz_obs_names)) # non-zero weight observations\n", - "print(type(p.phi)) # float value that is the weighted total objective function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The \"control_data\" section of the pest control file is accessible in the `Pst.control_data` attribute:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "jacupdate = 999\n", - "numlam = 10\n", - "numlam has been changed to --> 100\n" - ] - } - ], - "source": [ - "print('jacupdate = {0}'.format(p.control_data.jacupdate))\n", - "print('numlam = {0}'.format(p.control_data.numlam))\n", - "p.control_data.numlam = 100\n", - "print('numlam has been changed to --> {0}'.format(p.control_data.numlam))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Pst` class also exposes a method to get a new `Pst` instance with a subset of parameters and or obseravtions. Note this method does not propogate prior information to the new instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " equation obgnme pilbl weight names\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0 [mult1]\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0 [kr01c01]\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0 [kr01c02]\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0 [kr01c03]\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0 [kr01c04]\n", - "kr01c05 1.0 * log(kr01c05) = 0.0 regul_p kr01c05 1.0 [kr01c05]\n", - "kr01c06 1.0 * log(kr01c06) = 0.0 regul_p kr01c06 1.0 [kr01c06]\n", - "kr01c07 1.0 * log(kr01c07) = 0.0 regul_p kr01c07 1.0 [kr01c07]\n" - ] - } - ], - "source": [ - "pnew = p.get(p.par_names[:10],p.obs_names[-10:])\n", - "print(pnew.prior_information)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also write a pest control file with altered parameters, observations, and/or prior information:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Python27\\Anaconda\\envs\\py36\\lib\\site-packages\\pandas\\core\\indexing.py:337: SettingWithCopyWarning: \n", - "A value is trying to be set on a copy of a slice from a DataFrame.\n", - "Try using .loc[row_indexer,col_indexer] = value instead\n", - "\n", - "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", - " self.obj[key] = _infer_fill_value(value)\n", - "C:\\Python27\\Anaconda\\envs\\py36\\lib\\site-packages\\pandas\\core\\indexing.py:517: SettingWithCopyWarning: \n", - "A value is trying to be set on a copy of a slice from a DataFrame.\n", - "Try using .loc[row_indexer,col_indexer] = value instead\n", - "\n", - "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", - " self.obj[item] = s\n" - ] - } - ], - "source": [ - "pnew.write(\"test.pst\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some other methods in `Pst` include:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    global_k1.0 * log(global_k) = 2.301030E+00regulmglobal_k4.507576
    mult11.0 * log(mult1) = 0.000000E+00regulmmult14.507576
    mult21.0 * log(mult2) = 0.000000E+00regulmmult21.660964
    kr01c011.0 * log(kr01c01) = 0.000000E+00regulpkr01c010.500000
    kr01c021.0 * log(kr01c02) = 0.000000E+00regulpkr01c020.500000
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "global_k 1.0 * log(global_k) = 2.301030E+00 regulm global_k 4.507576\n", - "mult1 1.0 * log(mult1) = 0.000000E+00 regulm mult1 4.507576\n", - "mult2 1.0 * log(mult2) = 0.000000E+00 regulm mult2 1.660964\n", - "kr01c01 1.0 * log(kr01c01) = 0.000000E+00 regulp kr01c01 0.500000\n", - "kr01c02 1.0 * log(kr01c02) = 0.000000E+00 regulp kr01c02 0.500000" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add preferred value regularization with weights proportional to parameter bounds\n", - "pyemu.utils.helpers.zero_order_tikhonov(pnew)\n", - "pnew.prior_information.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    01.0 * log(global_k) = 2.301030E+00regulmglobal_k1.0
    11.0 * log(mult1) = 0.000000E+00regulmmult11.0
    21.0 * log(mult2) = 0.000000E+00regulmmult21.0
    31.0 * log(kr01c01) = 0.000000E+00regulpkr01c011.0
    41.0 * log(kr01c02) = 0.000000E+00regulpkr01c021.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "0 1.0 * log(global_k) = 2.301030E+00 regulm global_k 1.0\n", - "1 1.0 * log(mult1) = 0.000000E+00 regulm mult1 1.0\n", - "2 1.0 * log(mult2) = 0.000000E+00 regulm mult2 1.0\n", - "3 1.0 * log(kr01c01) = 0.000000E+00 regulp kr01c01 1.0\n", - "4 1.0 * log(kr01c02) = 0.000000E+00 regulp kr01c02 1.0" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add preferred value regularization with unity weights\n", - "pyemu.utils.helpers.zero_order_tikhonov(pnew,parbounds=False)\n", - "pnew.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some more `res` functionality" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1855.6874378297073 36 {'conc': 197.05822096106502, 'head': 1658.6292168686423}\n", - "36.0 36 {'conc': 15.000000000000004, 'head': 21.0}\n" - ] - } - ], - "source": [ - "# adjust observation weights to account for residual phi components\n", - "#pnew = p.get()\n", - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "p.adjust_weights_resfile()\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "adjust observation weights by an arbitrary amount by groups:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "36.0 36 {'conc': 15.000000000000004, 'head': 21.0}\n", - "114.99999999999997 36 {'conc': 15.000000000000004, 'head': 99.99999999999997}\n" - ] - } - ], - "source": [ - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "grp_dict = {\"head\":100}\n", - "p.adjust_weights(obsgrp_dict=grp_dict)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "adjust observation weights by an arbitrary amount by individual observations:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "114.99999999999997 36 {'conc': 15.000000000000004, 'head': 99.99999999999997}\n", - "138.82580701146588 36 {'conc': 15.000000000000004, 'head': 123.82580701146588}\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Python27\\Anaconda\\envs\\py36\\lib\\site-packages\\pyemu-0.4-py3.6.egg\\pyemu\\pst\\pst_handler.py:1586: FutureWarning: 'name' is both a column name and an index level.\n", - "Defaulting to column but this will raise an ambiguity error in a future version\n", - "C:\\Python27\\Anaconda\\envs\\py36\\lib\\site-packages\\pyemu-0.4-py3.6.egg\\pyemu\\pst\\pst_handler.py:1587: FutureWarning: 'obsnme' is both a column name and an index level.\n", - "Defaulting to column but this will raise an ambiguity error in a future version\n" - ] - } - ], - "source": [ - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "obs_dict = {\"h_obs01_1\":25}\n", - "p.adjust_weights(obs_dict=obs_dict)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "setup weights inversely proportional to the observation values" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "36.0 36 {'conc': 14.999999999999998, 'head': 21.0}\n", - "222.96996562151 36 {'conc': 194.30909581453392, 'head': 28.66086980697609}\n" - ] - } - ], - "source": [ - "p.adjust_weights_resfile()\n", - "print(p.phi, p.nnz_obs, p.phi_components)\n", - "p.proportional_weights(fraction_stdev=0.1,wmax=20.0)\n", - "print(p.phi, p.nnz_obs, p.phi_components)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.1" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/docs/source/Monte_carlo.rst.bak b/docs/source/Monte_carlo.rst.bak deleted file mode 100644 index ed2cdeb68..000000000 --- a/docs/source/Monte_carlo.rst.bak +++ /dev/null @@ -1,20 +0,0 @@ -Run Monte Carlo Simulations from PEST Object --------------------------------------------- - -pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -*SWEEP.EXE* to generate results. - -.. rubric:: Workflow Overview - - blah blah blah - - -.. rubric:: Module Documentation - -.. automodule:: mc - :members: - - \ No newline at end of file diff --git a/docs/source/Monte_carlo_page.rst b/docs/source/Monte_carlo_page.rst deleted file mode 100644 index d68021cc0..000000000 --- a/docs/source/Monte_carlo_page.rst +++ /dev/null @@ -1,24 +0,0 @@ -Run Monte Carlo Simulations from PEST Object --------------------------------------------- - -pyEMU can assist with running Monte Carlo analysis for -models with PEST control files by generating -sets of input parameters using information from -the parameter definitions, and then using the PEST++ program -*SWEEP.EXE* to generate results. - -.. rubric:: Workflow Overview - -blah blah blah Uses :ref:`Ensembles`. - -.. rubric:: Inheritance - -.. inheritance-diagram:: pyemu.mc - -.. rubric:: Module Documentation - -.. automodule:: pyemu.mc - :members: - :show-inheritance: - :noindex: - \ No newline at end of file diff --git a/docs/source/ensembles.rst b/docs/source/ensembles.rst deleted file mode 100644 index 2d1084b95..000000000 --- a/docs/source/ensembles.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _Ensembles: - -Ensembles ---------- - -pyEMU ensembles is a base class with derived..bbb - -.. rubric:: Inheritance - -.. inheritance-diagram:: pyemu.en - -.. rubric:: Module Documentation - -.. automodule:: pyemu.en - :members: - :show-inheritance: - :noindex: \ No newline at end of file diff --git a/docs/source/ensembles.rst.bak b/docs/source/ensembles.rst.bak deleted file mode 100644 index 132dd2b9d..000000000 --- a/docs/source/ensembles.rst.bak +++ /dev/null @@ -1,16 +0,0 @@ -.. _Ensembles: - -Ensembles ---------- - -pyEMU ensembles is a base class with derived..bbb - -.. rubric:: Inheritance - -.. inheritance-diagram:: pyemu.en - -.. rubric:: Module Documentation - -.. automodule:: pyemu.en - :members: - :noindex: \ No newline at end of file diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst deleted file mode 100644 index 60b28e2b7..000000000 --- a/docs/source/glossary.rst +++ /dev/null @@ -1,330 +0,0 @@ -Glossary --------- - -.. glossary:: - :sorted: - - object - instance - A programming entity that may store internal data (:term:`state`), have - functionality (:term:`behavior`) or both [RS16]_ . - - class - Classes [PSF18]_ [RS16]_ are the building blocks for object-oriented programs. The class - defines both attributes (:term:`state`) and functionality (:term:`behavior`) of an :term:`object`. - - client code - In the case of pyEMU, client code is a python script that uses the class definitions to - make a desired :term:`instance` (also know as an :term:`object`) and to use the :term:`attributes` - and :term:`instance methods` to build the desired analysis. The client code - can be developed as a traditional python script or within a Jupyter notebook [KRP+16]_. - Jupyter notebooks are used extensively in this documentation to illustrate features of - pyEMU. - - state - The state of the object refers to the current value of all the internal data - stored in and object [RS16]_ . - - instance methods - Functions defined in a class that become associated with a particular - instance or object after :term:`instantiation`. - - instantiation - Make a class instance, also known as an object, from a class. - - behavior - Object behavior refers to the set of actions an object can perform often reporting - or modifying its internal :term:`state` [RS16]_ . - - attributes - Internal data stored by an object. - - static methods - helper methods - Functions that are defined for the python module and available to the script. They - are not tied to specified objects. pyEMU has a number of helper methods including - functions to plot results, read or write specific data types, and interact with the - :term:`FloPY` module in analysis of MODFLOW models. - - FloPY - Object-oriented python module to build, run, and process MODFLOW models [BPL+16]_ . Because - of the similar modeling structure, the combination of flopy and pyEMU can be - very effective in constructing, documenting, and analyzing groundwater-flow models. - - inheritance - A object-oriented programming technique where one class, referred to as the - :term:`derived class` or subclass, extends a second class, known as the :term:`base class` - or superclass. The subclass has all the defined attributes and behavior - of the superclass and typically adds additional attributes or methods. Many - derived classes :term:`override` methods of the superclass to perform specific - functions [RS16]_. - - override - Implement a new version of a method within a derived class that would have - otherwise been inherited from the base class customizing the behavior - of the method for the derived class [RS16]_. Overriding allows objects to - call a method by the same name but have behavior specific to the - object's type. - - base class - A base class, or superclass, is a class that is extended by another class - in a technique called :term:`inheritance`. Inheritance can make programming - efficient by having one version of base attributes and methods that can be - tested and then extended by a :term:`derived class`. - - derived class - A derived class, or subclass, inherits attributes and methods from its - :term:`base class`. Derived classes then add attributes and methods as - needed. For example, in pyEMU, the linear analysis base class is inherited by the - Monte Carlo derived class. - - GLUE - Generalized Likelihood Uncertainty Estimation [KB09]_ . - - parameters - Variable input values for models, typically representing system properties - and forcings. Values to be estimated in the history matching process. - - observation - Measured system state values. These values are used to compare with model - outputs collocated in space and time. The term is often used to mean - *both* field measurements and outputs from the model. These are denoted - by :math:`y` for a scalar value or :math:`\bf{y}` - for a vector of values in this documentation. - - modeled equivalent - simulated equivalent - A modeled value collocated to correspond in time and space with an observation. - To make things confusing, they are often *also* called - "observations"! These are denoted by :math:`f(x)` for a scalar value or :math:`f\left( \bf{x} \right)` - for a vector of values in this documentation. Note that in addition to - :math:`f(x)`, authors use several different alternate - notations to distinguish an observation from a - corresponding modeled equivalent including subscripts, :math:`y` and :math:`y_m`, - superscripts, :math:`y` and :math:`y^\prime`, or diacritic marks, :math:`y` and :math:`\hat{y}`. - - forecasts - Model outputs for which field observations are not available. Typically these - values are simulated under an uncertain future condition. - - Phi - For pyEMU and consistent with PEST and PEST++, Phi refers to the :term:`objective function`, - defined as the weighted sum of squares of residuals. Phi, :math:`\Phi`, is typically calculated as - - .. math:: - \begin{array}{ccc} - \Phi=\sum_{i=1}^{n}\left(\frac{y_{i}-f\left(x_{i}\right)}{w_{i}}\right)^{2} & or & \Phi=\left(\mathbf{y}-\mathbf{Jx}\right)^{T}\mathbf{Q}^{-1}\left(\mathbf{y}-\mathbf{Jx}\right) - \end{array} - - When regularization is included, an additional term is added, - quantifying a penalty assessed for parameter sets that violate the preferred - conditions regarding the parameter values. - In such a case, the value of :math:`\Phi` as stated above is - renamed :math:`\Phi_m` for "measurement Phi" and the additional regularization - term is named :math:`\Phi_r`. A scalar, :math:`\gamma`, parameter controls the - tradeoff between these two dual components of the total objective function :math:`\Phi_t`. - - .. math:: - \Phi_t = \Phi_m + \gamma \Phi_r - - weight - epistemic uncertainty - A value by which a residual is divided by when constructing the sum of - squared residuals. In principal, :math:`w_i\approx\frac{1}{\sigma_i}` where :math:`\sigma_i` is - an approximation of the expected error between model output and collocated - observation values. While the symbol :math:`\sigma` implies a standard deviation, - it is important to note that measurement error only makes up a portion of - this error. Other aspects such as structural error (e.g. inadequacy inherent - in all models to perfectly simulate the natural world) also contribute to - this expected level of error. The reciprocal of weights are also called - Epistemic Uncertainty terms. - - residuals - - The difference between observation values and modeled equivalents - :math:`r_i=y_i-f\left(x_i\right)`. - - sensitivity - The incremental change of an observation (actually the modeled equivalent) - due to an incremental change in a parameter. Typically expressed as a - finite-difference approximation of a partial derivative, :math:`\frac{\partial - f(x)}{\partial x}`. - - Jacobian matrix - A matrix of the sensitivity of all observations in an inverse model to all - parameters. This is often shown as a matrix by various names :math:`\mathbf{X}`, - :math:`\mathbf{J}`, or :math:`\mathbf{H}`. Each element of the matrix is a single - sensitivity value :math:`\frac{\partial f(x_i)}{\partial x_j}` for :math:`i\in NOBS`, :math:`j - \in NPAR`. - - *NOBS* - For PEST and PEST++, number of observations. - - *NPAR* - For PEST and PEST++, number of parameters. - - regularization - A preferred condition pertaining to parameters, the deviation from which, - elicits a penalty added to the objective function. This serves as a - balance between the level of fit or "measurement Phi" - :math:`(\mathbf{\Phi_M})` and the coherence with soft knowledge/previous - conditions/prior knowledge/regularization :math:`(\mathbf{\Phi_R})`. These terms - can also be interpreted as the likelihood function and prior distribution - in Bayes theorem. - - PHIMLIM - A PEST input parameter the governs the strength with which regularization - is applied to the objective function. A high value of PHIMLIM indicates a - strong penalty for deviation from preferred parameter conditions while a - low value of PHIMLIM indicates a weak penalty. The reason this "dial"" is - listed as a function of PHIM (e.g. :math:`\mathbf{\Phi_M}`) is because it can - then be interpreted as a limit on how well we want to fit the observation - data. PHIMLIM is actually controlling the value :math:`\gamma` appearing in the - definition for :term:`Phi` and formally trading off :math:`\Phi_m` asnd :math:`\Phi_r`. - - objective function - Equation quantifying how well as simulation matches observations. Inverse - modeling is the process of calibrating a numerical model by mathematically - seeking the minimum of an objective function. (see :term:`Phi`) - - Gaussian (multivariate) - The equation for Gaussian (Normal) distribution for a single variable (:math:`x`) is - - .. math:: - \begin{equation} - f(x|\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\frac{\left(x-\mu\right)^2}{\sigma^2}} - \end{equation} - - where :math:`\mu` is the mean and :math:`\sigma` is the standard deviation - The equation for a multivariate Gaussian for a vector of :math:`k` variables (:math:`\mathbf{x}`) is - - .. math:: - \begin{equation} - f(\mathbf{x} | \mathbf{\mu},\mathbf{\Sigma})=\frac{1}{\sqrt{(2\pi)^k\left|\mathbf{\Sigma}\right|}}e^{-\frac{1}{2}\left( \left(\mathbf{x}-\mathbf{\mu} \right)^T \mathbf{\Sigma}^{-1}\left(\mathbf{x}-\mathbf{\mu} \right)\right)} - \end{equation} - - where :math:`\mu` is a :math:`k`-length vector of mean values, :math:`\mathbf{\Sigma}` is - the covariance matrix, and :math:`\left|\mathbf{\Sigma}\right|` is the determinant of - the covariance matrix. These quantities are often abbreviated - as :math:`\mathcal{N}\left( \mu, \sigma \right)` - and :math:`\mathcal{N}\left(\boldsymbol{\mu}, \boldsymbol{\Sigma} \right)` - for univariate and multivariate Gaussian distributions, respectively. - - weight covariance matrix (correlation matrix) - In practice, this is usually a :math:`NOBS\times NOBS` diagonal matrix with - values of weights on the diagonal representing the inverse of the - observation covariance. This implies a lack of correlation among the - observations. A full covariance matrix would indicate correlation among - the observations which, in reality, is present but, in practice, is rarely - characterized. The weight matrix is often identified as :math:`\mathbf{Q}^{-1}` - or :math:`\mathbf{\Sigma_\epsilon}^{-1}`. - - parameter covariance matrix - The uncertainty of parameters can be expressed as a matrix as well. This - is formed also as a diagonal matrix from the bounds around parameter - values (assuming that the range between the bounds indicates :math:`4\sigma` - (e.g. 95 percent of a normal distribution).) In pyEMU, some functions - accept a *sigma\_range* argument which can override the :math:`4\sigma` - assumption. In many cases of our applications, parameters are spatially - distributed (e.g. hydraulic conductivity fields) so a covariance matrix - with off-diagonal terms can be formed to characterize not only their - variance but also their correlation/covariance. We often use - geostatistical variograms to characterize the covariance of parameters. - The parameter covariance matrix is often identified as :math:`C(\mathbf{p})`, - :math:`\mathbf{\Sigma_\theta}`, or :math:`\mathbf{R}`. - - measurement noise/error - Measurement noise is a contribution to Epistemic Uncertainty. This is the - expected error of repeated measurements due to things like instrument - error and also can be compounded by error of surveying a datum, location - of an observation on a map, and other factors. - - structural (model) error - Epistemic uncertainty is actually dominated by structural error relative - to measurement noise. The structural error is the expected misfit between - measured and modeled values at observation locations due to model - inadequacy (including everything from model simplification due to the - necessity of discretizing the domain, processes that are missing from the - model, etc.). - - Monte Carlo parameter realization - A set of parameter values, often but not required to be a multi-Gaussian - distribution, sampled from the mean values of specified parameter values - (either starting values or, in some cases, optimal values following - parameter estimation) with covariance from a set of variance values, or a - covariance matrix. Can be identified as :math:`\mathbf{\theta}`. - - Monte Carlo observation realization - A set of observation values, often but not required to be a multi-Gaussian - distribution, sampled using the mean values of measured observations and - variance from the observation weight covariance matrix. Can be identified - as :math:`\boldsymbol{d_{obs}}`. - - - Monte Carlo ensemble - A group of realizations of parameters, :math:`\mathbf{\Theta}`, observations, - :math:`\mathbf{D_{obs}}`, and the simulated equivalent values, - :math:`\mathbf{D_{sim}}`. Note that these three matrices are made up of column - vectors representing all of the :math:`\boldsymbol{\theta}`, :math:`\mathbf{d_{obs}}`, - and :math:`\mathbf{d_{sim}}` vectors. - - Bayes' Theorem - .. math:: - \begin{equation} - P\left(\boldsymbol{\theta}|\boldsymbol{d}\right) = - \frac{\mathcal{L}\left(\textbf{d}|\boldsymbol{\theta}\right) P\left(\boldsymbol{\theta}\right)} - {P\left(\textbf{d}\right)} \ldots - \underbrace{P\left(\boldsymbol{\theta}|\textbf{d}\right)}_{\text{posterior pdf}} \propto - \underbrace{\mathcal{L}\left(\boldsymbol{d}|\boldsymbol{\theta}\right)}_{\text{likelihood function}} - \quad - \underbrace{P\left(\boldsymbol{\theta}\right)}_{\text{prior pdf}} - \end{equation} - - where :math:`\boldsymbol{\theta}` is a vector of parameters, - and :math:`\mathbf{d}` is a vector of observations It is computationally - expedient to assume that these quantities can be characterized by - multivariate Gaussian distributions and, thus, characterized only by their - first two moments --- mean and covariance. - - posterior (multivariate distribution) - The posterior distribution is the updated distribution (mean and - covariance) of parameter values :math:`\boldsymbol{\theta}` updated from their - prior by an experiment (encapsulated in the likelihood function). In other - words, information gleaned from observations :math:`\mathbf{d}` is used to - update the initial values of the parameters. - - prior (multivariate distribution) - This distribution represents what we know about parameter values prior to - any modeling. It is also called "soft knowledge" or "expert - knowledge". This information is more than just starting values, but also - encapsulates the understanding of uncertainty (characterized through the - covariance) based on direct estimation of parameters (e.g. pumping tests, - geologic maps, and grain size analysis, for example). In one - interpretation of the objective function, this is also where the - regularization information is contained. - - likelihood (multivariate distribution) - This is a function describing how much is learned from the model. It is - characterized by the misfit between modeled equivalents and observations. - - FOSM - linear uncertainty analysis - First Order Second Moment (FOSM) is a technique to use an assumption of Gaussian - distributions to, analytically, calculate the covariance of model outputs - considering both the prior covariance and the likelihood function. In - other words, it's an analytical calculation of the posterior covariance of - parameters using Bayes' Theoerem. The equation for this calculation is the - Schur Complement. The key advantage of this is that we really only need a - few quantities --- a Jacobian Matrix :math:`\mathbf{J}`, the prior covariance of - parameters :math:`\boldsymbol{\Sigma_\theta}`, and the observation covariance - :math:`\boldsymbol{\Sigma_\epsilon}`. - - Schur complement - The formula used to propagate uncertainty from a prior through a - "notional" calibration (via the Jacobian) to the posterior update. - - .. math:: - \begin{equation} - \underbrace{\overline{\boldsymbol{\Sigma}}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{know now}}} = \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{knew}}} - \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T\left[\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T + \boldsymbol{\Sigma}_{\boldsymbol{\epsilon}}\right]^{-1}\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\text{what we learned}} - \end{equation} - \ No newline at end of file diff --git a/docs/source/glossary.rst.bak b/docs/source/glossary.rst.bak deleted file mode 100644 index ec33b0990..000000000 --- a/docs/source/glossary.rst.bak +++ /dev/null @@ -1,330 +0,0 @@ -Glossary --------- - -.. glossary:: - :sorted: - - object - instance - A programming entity that may store internal data (:term:`state`), have - functionality (:term:`behavior`) or both [RS16]_ . - - class - Classes [PSF18]_ [RS16]_ are the building blocks for object-oriented programs. The class - defines both attributes (:term:`state`) and functionality (:term:`behavior`) of an :term:`object`. - - client code - In the case of pyEMU, client code is a python script that uses the class definitions to - make a desired :term:`instance` (also know as an :term:`object`) and to use the :term:`attributes` - and :term:`instance methods` to build the desired analysis. The client code - can be developed as a traditional python script or within a Jupyter notebook [KRP+16]_. - Jupyter notebooks are used extensively in this documentation to illustrate features of - pyEMU. - - state - The state of the object refers to the current value of all the internal data - stored in and object [RS16]_ . - - instance methods - Functions defined in a class that become associated with a particular - instance or object after :term:`instantiation`. - - instantiation - Make a class instance, also known as an object, from a class. - - behavior - Object behavior refers to the set of actions an object can perform often reporting - or modifying its internal :term:`state` [RS16]_ . - - attributes - Internal data stored by an object. - - static methods - helper methods - Functions that are defined for the python module and available to the script. They - are not tied to specified objects. pyEMU has a number of helper methods including - functions to plot results, read or write specific data types, and interact with the - :term:`FloPY` module in analysis of MODFLOW models. - - FloPY - Object-oriented python module to build, run, and process MODFLOW models [BPL+16]_ . Because - of the similar modeling structure, the combination of flopy and pyEMU can be - very effective in constructing, documenting, and analyzing groundwater-flow models. - - inheritance - A object-oriented programming technique where one class, referred to as the - :term:`derived class` or subclass, extends a second class, known as the :term:`base class` - or superclass. The subclass has all the defined attributes and behavior - of the superclass and typically adds additional attributes or methods. Many - derived classes :term:`override` methods of the superclass to perform specific - functions [RS16]_. - - override - Implement a new version of a method within a derived class that would have - otherwise been inherited from the base class customizing the behavior - of the method for the derived class [RS16]_. Overriding allows objects to - call a method by the same name but have behavior specific to the - object's type. - - base class - A base class, or superclass, is a class that is extended by another class - in a technique called :term:`inheritance`. Inheritance can make programming - efficient by having one version of base attributes and methods that can be - tested and then extended by a :term:`derived class`. - - derived class - A derived class, or subclass, inherits attributes and methods from its - :term:`base class`. Derived classes then add attributes and methods as - needed. For example, in pyEMU, the linear analysis base class is inherited by the - Monte Carlo derived class. - - GLUE - Generalized Likelihood Uncertainty Estimation [KB09]_ . - - parameters - Variable input values for models, typically representing system properties - and forcings. Values to be estimated in the history matching process. - - observation - Measured system state values. These values are used to compare with model - outputs collocated in space and time. The term is often used to mean - *both* field measurements and outputs from the model. These are denoted - by :math:`y` for a scalar value or :math:`\bf{y}` - for a vector of values in this documentation. - - modeled equivalent - simulated equivalent - A modeled value collocated to correspond in time and space with an observation. - To make things confusing, they are often *also* called - "observations"! These are denoted by :math:`f(x)` for a scalar value or :math:`f\left( \bf{x} \right)` - for a vector of values in this documentation. Note that in addition to - :math:`f(x)`, authors use several different alternate - notations to distinguish an observation from a - corresponding modeled equivalent including subscripts, :math:`y` and :math:`y_m`, - superscripts, :math:`y` and :math:`y^\prime`, or diacritic marks, :math:`y` and :math:`\hat{y}`. - - forecasts - Model outputs for which field observations are not available. Typically these - values are simulated under an uncertain future condition. - - Phi - For pyEMU and consistent with PEST and PEST++, Phi refers to the :term:`objective function`, - defined as the weighted sum of squares of residuals. Phi, :math:`\Phi`, is typically calculated as - - .. math:: - \begin{array}{ccc} - \Phi=\sum_{i=1}^{n}\left(\frac{y_{i}-f\left(x_{i}\right)}{w_{i}}\right)^{2} & or & \Phi=\left(\mathbf{y}-\mathbf{Jx}\right)^{T}\mathbf{Q}^{-1}\left(\mathbf{y}-\mathbf{Jx}\right) - \end{array} - - When regularization is included, an additional term is added, - quantifying a penalty assessed for parameter sets that violate the preferred - conditions regarding the parameter values. - In such a case, the value of :math:`\Phi` as stated above is - renamed :math:`\Phi_m` for "measurement Phi" and the additional regularization - term is named :math:`\Phi_r`. A scalar, :math:`\gamma`, parameter controls the - tradeoff between these two dual components of the total objective function :math:`\Phi_t`. - - .. math:: - \Phi_t = \Phi_m + \gamma \Phi_r - - weight - epistemic uncertainty - A value by which a residual is divided by when constructing the sum of - squared residuals. In principal, :math:`w_i\approx\frac{1}{\sigma_i}` where :math:`\sigma_i` is - an approximation of the expected error between model output and collocated - observation values. While the symbol :math:`\sigma` implies a standard deviation, - it is important to note that measurement error only makes up a portion of - this error. Other aspects such as structural error (e.g. inadequacy inherent - in all models to perfectly simulate the natural world) also contribute to - this expected level of error. The reciprocal of weights are also called - Epistemic Uncertainty terms. - - residuals - - The difference between observation values and modeled equivalents - :math:`r_i=y_i-f\left(x_i\right)`. - - sensitivity - The incremental change of an observation (actually the modeled equivalent) - due to an incremental change in a parameter. Typically expressed as a - finite-difference approximation of a partial derivative, :math:`\frac{\partial - f(x)}{\partial x}`. - - Jacobian matrix - A matrix of the sensitivity of all observations in an inverse model to all - parameters. This is often shown as a matrix by various names :math:`\mathbf{X}`, - :math:`\mathbf{J}`, or :math:`\mathbf{H}`. Each element of the matrix is a single - sensitivity value :math:`\frac{\partial f(x_i)}{\partial x_j}` for :math:`i\in NOBS`, :math:`j - \in NPAR`. - - *NOBS* - For PEST and PEST++, number of observations. - - *NPAR* - For PEST and PEST++, number of parameters. - - regularization - A preferred condition pertaining to parameters, the deviation from which, - elicits a penalty added to the objective function. This serves as a - balance between the level of fit or "measurement Phi" - :math:`(\mathbf{\Phi_M})` and the coherence with soft knowledge/previous - conditions/prior knowledge/regularization :math:`(\mathbf{\Phi_R})`. These terms - can also be interpreted as the likelihood function and prior distribution - in Bayes theorem. - - PHIMLIM - A PEST input parameter the governs the strength with which regularization - is applied to the objective function. A high value of PHIMLIM indicates a - strong penalty for deviation from preferred parameter conditions while a - low value of PHIMLIM indicates a weak penalty. The reason this "dial"" is - listed as a function of PHIM (e.g. :math:`\mathbf{\Phi_M}`) is because it can - then be interpreted as a limit on how well we want to fit the observation - data. PHIMLIM is actually controlling the value :math:`\gamma` appearing in the - definition for :term:`Phi` and formally trading off :math:`\Phi_m` asnd :math:`\Phi_r`. - - objective function - Equation quantifying how well as simulation matches observations. Inverse - modeling is the process of calibrating a numerical model by mathematically - seeking the minimum of an objective function. (see :term:`Phi`) - - Gaussian (multivariate) - The equation for Gaussian (Normal) distribution for a single variable (:math:`x`) is - - .. math:: - \begin{equation} - f(x|\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\frac{\left(x-\mu\right)^2}{\sigma^2}} - \end{equation} - - where :math:`\mu` is the mean and :math:`\sigma` is the standard deviation - The equation for a multivariate Gaussian for a vector of :math:`k` variables (:math:`\mathbf{x}`) is - - .. math:: - \begin{equation} - f(\mathbf{x} | \mathbf{\mu},\mathbf{\Sigma})=\frac{1}{\sqrt{(2\pi)^k\left|\mathbf{\Sigma}\right|}}e^{-\frac{1}{2}\left( \left(\mathbf{x}-\mathbf{\mu} \right)^T \mathbf{\Sigma}^{-1}\left(\mathbf{x}-\mathbf{\mu} \right)\right)} - \end{equation} - - where :math:`\mu` is a :math:`k`-length vector of mean values, :math:`\mathbf{\Sigma}` is - the covariance matrix, and :math:`\left|\mathbf{\Sigma}\right|` is the determinant of - the covariance matrix. These quantities are often abbreviated - as :math:`\mathcal{N}\left( \mu, \sigma \right)` - and :math:`\mathcal{N}\left(\boldsymbol{\mu}, \boldsymbol{\Sigma} \right)` - for univariate and multivariate Gaussian distributions, respectively. - - weight covariance matrix (correlation matrix) - In practice, this is usually a :math:`NOBS\times NOBS` diagonal matrix with - values of weights on the diagonal representing the inverse of the - observation covariance. This implies a lack of correlation among the - observations. A full covariance matrix would indicate correlation among - the observations which, in reality, is present but, in practice, is rarely - characterized. The weight matrix is often identified as :math:`\mathbf{Q}^{-1}` - or :math:`\mathbf{\Sigma_\epsilon}^{-1}`. - - parameter covariance matrix - The uncertainty of parameters can be expressed as a matrix as well. This - is formed also as a diagonal matrix from the bounds around parameter - values (assuming that the range between the bounds indicates :math:`4\sigma` - (e.g. 95 percent of a normal distribution).) In pyEMU, some functions - accept a *sigma\_range* argument which can override the :math:`4\sigma` - assumption. In many cases of our applications, parameters are spatially - distributed (e.g. hydraulic conductivity fields) so a covariance matrix - with off-diagonal terms can be formed to characterize not only their - variance but also their correlation/covariance. We often use - geostatistical variograms to characterize the covariance of parameters. - The parameter covariance matrix is often identified as :math:`C(\mathbf{p})`, - :math:`\mathbf{\Sigma_\theta}`, or :math:`\mathbf{R}`. - - measurement noise/error - Measurement noise is a contribution to Epistemic Uncertainty. This is the - expected error of repeated measurements due to things like instrument - error and also can be compounded by error of surveying a datum, location - of an observation on a map, and other factors. - - structural (model) error - Epistemic uncertainty is actually dominated by structural error relative - to measurement noise. The structural error is the expected misfit between - measured and modeled values at observation locations due to model - inadequacy (including everything from model simplification due to the - necessity of discretizing the domain, processes that are missing from the - model, etc.). - - Monte Carlo parameter realization - A set of parameter values, often but not required to be a multi-Gaussian - distribution, sampled from the mean values of specified parameter values - (either starting values or, in some cases, optimal values following - parameter estimation) with covariance from a set of variance values, or a - covariance matrix. Can be identified as :math:`\mathbf{\theta}`. - - Monte Carlo observation realization - A set of observation values, often but not required to be a multi-Gaussian - distribution, sampled using the mean values of measured observations and - variance from the observation weight covariance matrix. Can be identified - as :math:`\boldsymbol{d_{obs}}`. - - - Monte Carlo ensemble - A group of realizations of parameters, :math:`\mathbf{\Theta}`, observations, - :math:`\mathbf{D_{obs}}`, and the simulated equivalent values, - :math:`\mathbf{D_{sim}}`. Note that these three matrices are made up of column - vectors representing all of the :math:`\boldsymbol{\theta}`, :math:`\mathbf{d_{obs}}`, - and :math:`\mathbf{d_{sim}}` vectors where :math:`\mathbf{d_{sim}}`. - - Bayes' Theorem - .. math:: - \begin{equation} - P\left(\boldsymbol{\theta}|\boldsymbol{d}\right) = - \frac{\mathcal{L}\left(\textbf{d}|\boldsymbol{\theta}\right) P\left(\boldsymbol{\theta}\right)} - {P\left(\textbf{d}\right)} \ldots - \underbrace{P\left(\boldsymbol{\theta}|\textbf{d}\right)}_{\text{posterior pdf}} \propto - \underbrace{\mathcal{L}\left(\boldsymbol{d}|\boldsymbol{\theta}\right)}_{\text{likelihood function}} - \quad - \underbrace{P\left(\boldsymbol{\theta}\right)}_{\text{prior pdf}} - \end{equation} - - where :math:`\boldsymbol{\theta}` is a vector of parameters, - and :math:`\mathbf{d}` is a vector of observations It is computationally - expedient to assume that these quantities can be characterized by - multivariate Gaussian distributions and, thus, characterized only by their - first two moments --- mean and covariance. - - posterior (multivariate distribution) - The posterior distribution is the updated distribution (mean and - covariance) of parameter values :math:`\boldsymbol{\theta}` updated from their - prior by an experiment (encapsulated in the likelihood function). In other - words, information gleaned from observations :math:`\mathbf{d}` is used to - update the initial values of the parameters. - - prior (multivariate distribution) - This distribution represents what we know about parameter values prior to - any modeling. It is also called "soft knowledge" or "expert - knowledge". This information is more than just starting values, but also - encapsulates the understanding of uncertainty (characterized through the - covariance) based on direct estimation of parameters (e.g. pumping tests, - geologic maps, and grain size analysis, for example). In one - interpretation of the objective function, this is also where the - regularization information is contained. - - likelihood (multivariate distribution) - This is a function describing how much is learned from the model. It is - characterized by the misfit between modeled equivalents and observations. - - FOSM - linear uncertainty analysis - First Order Second Moment (FOSM) is a technique to use an assumption of Gaussian - distributions to, analytically, calculate the covariance of model outputs - considering both the prior covariance and the likelihood function. In - other words, it's an analytical calculation of the posterior covariance of - parameters using Bayes' Theoerem. The equation for this calculation is the - Schur Complement. The key advantage of this is that we really only need a - few quantities --- a Jacobian Matrix :math:`\mathbf{J}`, the prior covariance of - parameters :math:`\boldsymbol{\Sigma_\theta}`, and the observation covariance - :math:`\boldsymbol{\Sigma_\epsilon}`. - - Schur complement - The formula used to propagate uncertainty from a prior through a - "notional" calibration (via the Jacobian) to the posterior update. - - .. math:: - \begin{equation} - \underbrace{\overline{\boldsymbol{\Sigma}}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{know now}}} = \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\substack{\text{what we} \\ \text{knew}}} - \underbrace{\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T\left[\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}\bf{J}^T + \boldsymbol{\Sigma}_{\boldsymbol{\epsilon}}\right]^{-1}\bf{J}\boldsymbol{\Sigma}_{\boldsymbol{\theta}}}_{\text{what we learned}} - \end{equation} - \ No newline at end of file diff --git a/docs/source/oop.rst b/docs/source/oop.rst deleted file mode 100644 index e19764ef0..000000000 --- a/docs/source/oop.rst +++ /dev/null @@ -1,50 +0,0 @@ -Notes on Object-Oriented Programming ------------------------------------- - -Analysis using PEST and PEST++ were traditionally run by writing a series of -input files (template, instruction, and contol files) and running a desired -program from either suite. pyEMU provides a programmatic way to interact with -PEST and PEST++ that allows for easier creation of necessary input files and -interpretation of the output generated by the suite of programs. The -programmatic approach also assists in creation of reproducible research [FB16]_ -by documenting choices made by the analyst in the generation of input files, in -steps of the analysis, or in the interpretation of results. pyEMU also extends -the functionality of PEST and PEST++ especially for performing linear and non- -linear uncertainty analysis. Users familiar with PEST and PEST++ input files -will find the terminology used in pyEMU familiar, the main difference between -using pyEMU and traditional methods is in the writing of :term:`client code` to -perform the desired analysis. - -The general workflow for pyEMU is to first create an :term:`instance` (:term:`object`) -of a class. The simplest object will only have attributes, -or data, associated with it and may be thought of as a data structure. Many -pyEMU objects also have :term:`instance methods` associated with them which -allow the user to either manipulate attributes of the object or use the current -attributes, also known as the :term:`state` of the object, to perform tasks. - -.. rubric:: For example - -The jupyter notebook linked below uses the pyEMU -Pst Class (in the pyemu.pst module, pst_handler submodule) to create an object called *p*. - -Attributes of the object -can then be accessed using *p.attribute*, for example, the parameter_data -are stored in the object as a pandas [MCK10]_ dataframe and can be accessed -using:: - - parm_data_df = p.parameter_data - -Methods are invoked using the dot notation, *p.methodname(parameters)*. -One of the methods of the object used in the example notebook is -the get() method which will generate a new Pst instance using -a subset of the parameters or observations from an existing -object. To get a new object with the first 10 parameters and observations:: - - pnew = p.get(p.par_names[:10], p.obs_names[:10]) - -The attributes and methods associated with all the classes of pyEMU and -the helper methods are summarized in the :ref:`Technical` section. - -.. toctree:: - - pst_demo \ No newline at end of file diff --git a/docs/source/oop.rst.bak b/docs/source/oop.rst.bak deleted file mode 100644 index 74d4e735b..000000000 --- a/docs/source/oop.rst.bak +++ /dev/null @@ -1,50 +0,0 @@ -Notes on Object-Oriented Programming ------------------------------------- - -Analysis using PEST and PEST++ were traditionally run by writing a series of -input files (template, instruction, and contol files) and running a desired -program from either suite. pyEMU provides a programmatic way to interact with -PEST and PEST++ that allows for easier creation of necessary input files and -interpretation of the output generated by the suite of programs. The -programmatic approach also assists in creation of reproducible research [FB16]_ -by documenting choices made by the analyst in the generation of input files, in -steps of the analysis, or in the interpretation of results. pyEMU also extends -the functionality of PEST and PEST++ especially for performing linear and non- -linear uncertainty analysis. Users familiar with PEST and PEST++ input files -will find the terminology used in pyEMU familiar, the main difference between -using pyEMU and traditional methods is in the writing of :term:`client code` to -perform the desired analysis. - -The general workflow for pyEMU is to first create an :term:`instance` (:term:`object`) -of a class. The simplest object will only have attributes, -or data, associated with it and may be thought of as a data structure. Many -pyEMU objects also have :term:`instance methods` associated with them which -allow the user to either manipulate attributes of the object or use the current -attributes, also known as the :term:`state` of the object, to perform tasks. - -.. rubric:: For example - -The Pst class jupyter notebook linked below uses the pyEMU -Pst Class (in the pyemu.pst module, pst_handler submodule) to create an object called *p*. - -Attributes of the object -can then be accessed using *p.attribute*, for example, the parameter_data -are stored in the object as a pandas [MCK10]_ dataframe and can be accessed -using:: - - parm_data_df = p.parameter_data - -Methods are invoked using the dot notation, *p.methodname(parameters)*. -One of the methods of the object used in the example notebook is -the get() method which will generate a new Pst instance using -a subset of the parameters or observations from an existing -object. To get a new object with the first 10 parameters and observations:: - - pnew = p.get(p.par_names[:10], p.obs_names[:10]) - -The attributes and methods associated with all the classes of pyEMU and -the helper methods are summarized in the :ref:`Technical` section. - -.. toctree:: - - pst_demo \ No newline at end of file diff --git a/docs/source/pst_demo.ipynb b/docs/source/pst_demo.ipynb deleted file mode 100644 index 20a832102..000000000 --- a/docs/source/pst_demo.ipynb +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example Object-Oriented Access to the PEST Control File\n", - "\n", - "The `pst_handler` module with `pyemu.pst` contains the `Pst` class for dealing with pest control files. It relies heavily on `pandas` to deal with tabular sections, such as parameters, observations, and prior information. This jupyter notebook shows how to create a control-file object (instantiate the class or make an instance of the class), how to access attributes of the class, and how to call an instance method." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import os\n", - "import numpy as np\n", - "import pyemu\n", - "from pyemu import Pst" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A PEST control file is required to make the object, and we need to pass the name of the PEST control file as a parameter to the __init__ method for the class. The class instance (or object)\n", - "is assigned to the variable *p*." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "pst_name = os.path.join(\"..\", \"..\", \"examples\", \"henry\",\"pest.pst\")\n", - "p = Pst(pst_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now all of the relevant parts of the pest control file are attributes of the object.\n", - "For example, the parameter_data, observation data, and prior information are available as pandas dataframes.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor200.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 200.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.051396152.1458headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 152.1458 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweight
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The client-code can be used to change values in the dataframes that can be written to \n", - "a new or updated control file using the `write()` method as shown at the end of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 225.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.parameter_data.loc['global_k', 'parval1'] = 225\n", - "p.parameter_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A residual file (`.rei` or `res`) can also be passed to the `resfile` argument at instantiation to enable some simple residual analysis and evaluate if weight adjustments are needed. If `resfile = False`, or not supplied, and if the residual file is in the same directory as the pest control file and has the same base name, it will be accessed automatically:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    namegroupmeasuredmodelledresidualweight
    name
    h_obs01_1h_obs01_1head0.0513960.080402-0.029006152.1458
    h_obs01_2h_obs01_2head0.0221560.036898-0.0147420.0000
    h_obs02_1h_obs02_1head0.0468790.069121-0.022241152.1458
    h_obs02_2h_obs02_2head0.0208530.034311-0.0134580.0000
    h_obs03_1h_obs03_1head0.0365840.057722-0.021138152.1458
    \n", - "
    " - ], - "text/plain": [ - " name group measured modelled residual weight\n", - "name \n", - "h_obs01_1 h_obs01_1 head 0.051396 0.080402 -0.029006 152.1458\n", - "h_obs01_2 h_obs01_2 head 0.022156 0.036898 -0.014742 0.0000\n", - "h_obs02_1 h_obs02_1 head 0.046879 0.069121 -0.022241 152.1458\n", - "h_obs02_2 h_obs02_2 head 0.020853 0.034311 -0.013458 0.0000\n", - "h_obs03_1 h_obs03_1 head 0.036584 0.057722 -0.021138 152.1458" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.res.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The weights can be updated by changing values in the observation dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p.observation_data.loc['h_obs01_1', 'weight'] = 25.0\n", - "p.observation_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Pst` class exposes a method, `get()`, to create a new `Pst` instance with a subset of parameters and or observations. For example, make a new PEST control-file object using the first 10 entries from the \n", - "parameter and observation dataframes. Note this method does not propogate prior information to the new instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    equationobgnmepilblweightnames
    pilbl
    mult11.0 * log(mult1) = 0.000000regul_mmult11.0[mult1]
    kr01c011.0 * log(kr01c01) = 0.0regul_pkr01c011.0[kr01c01]
    kr01c021.0 * log(kr01c02) = 0.0regul_pkr01c021.0[kr01c02]
    kr01c031.0 * log(kr01c03) = 0.0regul_pkr01c031.0[kr01c03]
    kr01c041.0 * log(kr01c04) = 0.0regul_pkr01c041.0[kr01c04]
    \n", - "
    " - ], - "text/plain": [ - " equation obgnme pilbl weight names\n", - "pilbl \n", - "mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0 [mult1]\n", - "kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0 [kr01c01]\n", - "kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0 [kr01c02]\n", - "kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0 [kr01c03]\n", - "kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0 [kr01c04]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew = p.get(p.par_names[:10],p.obs_names[:10])\n", - "pnew.prior_information.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the parameter_data and observation_data dataframes for the new object, note that\n", - "the updated values for `global_k` `parval1` and `h_obs01_1` `weight` are in these dataframes." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    parnmepartransparchglimparval1parlbndparubndpargpscaleoffsetdercomextra
    parnme
    global_kglobal_klogfactor225.0150.00250.00m1.00.01NaN
    mult1mult1logfactor1.00.751.25m1.00.01NaN
    mult2mult2logfactor1.00.502.00m1.00.01NaN
    kr01c01kr01c01logfactor1.00.1010.00p1.00.01NaN
    kr01c02kr01c02logfactor1.00.1010.00p1.00.01NaN
    \n", - "
    " - ], - "text/plain": [ - " parnme partrans parchglim parval1 parlbnd parubnd pargp scale \\\n", - "parnme \n", - "global_k global_k log factor 225.0 150.00 250.00 m 1.0 \n", - "mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 \n", - "mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 \n", - "kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 \n", - "kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 \n", - "\n", - " offset dercom extra \n", - "parnme \n", - "global_k 0.0 1 NaN \n", - "mult1 0.0 1 NaN \n", - "mult2 0.0 1 NaN \n", - "kr01c01 0.0 1 NaN \n", - "kr01c02 0.0 1 NaN " - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew.parameter_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
    \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
    obsnmeobsvalweightobgnmeextra
    obsnme
    h_obs01_1h_obs01_10.05139625.0000headNaN
    h_obs01_2h_obs01_20.0221560.0000headNaN
    h_obs02_1h_obs02_10.046879152.1458headNaN
    h_obs02_2h_obs02_20.0208530.0000headNaN
    h_obs03_1h_obs03_10.036584152.1458headNaN
    \n", - "
    " - ], - "text/plain": [ - " obsnme obsval weight obgnme extra\n", - "obsnme \n", - "h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN\n", - "h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN\n", - "h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN\n", - "h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN\n", - "h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pnew.observation_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `write(filename)` method allows you to write a PEST control file with the current state of the object: that is, make a new PEST control file with the current information contained in an object." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "pnew.write(\"test.pst\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/docs/source/pyemu.mat.rst b/docs/source/pyemu.mat.rst deleted file mode 100644 index 3711f0da4..000000000 --- a/docs/source/pyemu.mat.rst +++ /dev/null @@ -1,22 +0,0 @@ -pyemu.mat package -================= - -Submodules ----------- - -pyemu.mat.mat_handler module ----------------------------- - -.. automodule:: pyemu.mat.mat_handler - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.mat - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/pyemu.pst.rst b/docs/source/pyemu.pst.rst deleted file mode 100644 index 7483d9722..000000000 --- a/docs/source/pyemu.pst.rst +++ /dev/null @@ -1,38 +0,0 @@ -pyemu.pst package -================= - -Submodules ----------- - -pyemu.pst.pst_controldata module --------------------------------- - -.. automodule:: pyemu.pst.pst_controldata - :members: - :undoc-members: - :show-inheritance: - -pyemu.pst.pst_handler module ----------------------------- - -.. automodule:: pyemu.pst.pst_handler - :members: - :undoc-members: - :show-inheritance: - -pyemu.pst.pst_utils module --------------------------- - -.. automodule:: pyemu.pst.pst_utils - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.pst - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/pyemu.rst b/docs/source/pyemu.rst deleted file mode 100644 index 02c7d08f2..000000000 --- a/docs/source/pyemu.rst +++ /dev/null @@ -1,79 +0,0 @@ -pyemu package -============= - -Subpackages ------------ - -.. toctree:: - - pyemu.mat - pyemu.pst - pyemu.utils - -Submodules ----------- - -pyemu.en module ---------------- - -.. automodule:: pyemu.en - :members: - :undoc-members: - :show-inheritance: - -pyemu.ev module ---------------- - -.. automodule:: pyemu.ev - :members: - :undoc-members: - :show-inheritance: - -pyemu.la module ---------------- - -.. automodule:: pyemu.la - :members: - :undoc-members: - :show-inheritance: - -pyemu.logger module -------------------- - -.. automodule:: pyemu.logger - :members: - :undoc-members: - :show-inheritance: - -pyemu.mc module ---------------- - -.. automodule:: pyemu.mc - :members: - :undoc-members: - :show-inheritance: - -pyemu.sc module ---------------- - -.. automodule:: pyemu.sc - :members: - :undoc-members: - :show-inheritance: - -pyemu.smoother module ---------------------- - -.. automodule:: pyemu.smoother - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/pyemu.utils.rst b/docs/source/pyemu.utils.rst deleted file mode 100644 index 2e93a0887..000000000 --- a/docs/source/pyemu.utils.rst +++ /dev/null @@ -1,54 +0,0 @@ -pyemu.utils package -=================== - -Submodules ----------- - -pyemu.utils.geostats module ---------------------------- - -.. automodule:: pyemu.utils.geostats - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.gw_utils module ---------------------------- - -.. automodule:: pyemu.utils.gw_utils - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.helpers module --------------------------- - -.. automodule:: pyemu.utils.helpers - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.optimization module -------------------------------- - -.. automodule:: pyemu.utils.optimization - :members: - :undoc-members: - :show-inheritance: - -pyemu.utils.pp_utils module ---------------------------- - -.. automodule:: pyemu.utils.pp_utils - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pyemu.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/test.pst b/docs/source/test.pst deleted file mode 100644 index b73fa0a73..000000000 --- a/docs/source/test.pst +++ /dev/null @@ -1,71 +0,0 @@ -pcf -* control data - restart estimation - 10 10 2 8 3 0 - 3 5 single point 1 0 0 noobsreref - 1.000000E+01 2.000000E+00 1.000000E-01 3.000000E-02 10 999 lamforgive noderforgive - 3.000000E+00 3.000000E+00 1.000000E-03 0 0 - 3.000000E-01 1 1.100000E+00 noaui nosenreuse noboundscale - -1 5.000000E-03 4 4 1.000000E-03 4 0.000000E+00 1 -1.000000E+00 - 1 1 1 0 jcosave verboserec jcosaveitn reisaveitn parsaveitn noparsaverun -* singular value decomposition - 1 - 10000000 1.000000E-06 -1 -* parameter groups -p relative 1.0000000000E-02 0.0 switch 2.0000000000E+00 parabolic 1.0000000000E-05 5.0000000000E-01 smaller -m relative 1.0000000000E-02 0.0 switch 2.0000000000E+00 parabolic 1.0000000000E-05 5.0000000000E-01 smaller -* parameter data -global_k log factor 2.2500000000E+02 1.5000000000E+02 2.5000000000E+02 m 1.0000000000E+00 0.0000000000E+00 1 -mult1 log factor 1.0000000000E+00 7.5000000000E-01 1.2500000000E+00 m 1.0000000000E+00 0.0000000000E+00 1 -mult2 log factor 1.0000000000E+00 5.0000000000E-01 2.0000000000E+00 m 1.0000000000E+00 0.0000000000E+00 1 -kr01c01 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c02 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c03 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c04 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c05 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c06 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -kr01c07 log factor 1.0000000000E+00 1.0000000000E-01 1.0000000000E+01 p 1.0000000000E+00 0.0000000000E+00 1 -* observation groups -head -regul_p -regul_m -* observation data -h_obs01_1 5.1396200000E-02 2.5000000000E+01 head -h_obs01_2 2.2156200000E-02 0.0000000000E+00 head -h_obs02_1 4.6879400000E-02 1.5214580000E+02 head -h_obs02_2 2.0852800000E-02 0.0000000000E+00 head -h_obs03_1 3.6584100000E-02 1.5214580000E+02 head -h_obs03_2 1.9502200000E-02 0.0000000000E+00 head -h_obs04_1 2.7541600000E-02 1.5214580000E+02 head -h_obs04_2 1.6946400000E-02 0.0000000000E+00 head -h_obs05_1 2.6381600000E-02 1.5214580000E+02 head -h_obs05_2 7.5455700000E-03 0.0000000000E+00 head -* model command line -model.bat -* model input/output -misc\pp_locs.tpl misc\pp_locs.dat -misc\par2par_coarse.tpl misc\par2par_coarse.in -misc\global_k.tpl misc\global_k.dat -misc\conc.ins misc\conc.dat -misc\head.ins misc\head.dat -misc\pred_dist_one.ins misc\pred_dist_one.dat -misc\pred_dist_ten.ins misc\pred_dist_ten.dat -misc\pred_dist_half.ins misc\pred_dist_half.dat -* prior information -mult1 1.0 * log(mult1) = 0.000000 1.0000000000E+00 regul_m -kr01c01 1.0 * log(kr01c01) = 0.0 1.0000000000E+00 regul_p -kr01c02 1.0 * log(kr01c02) = 0.0 1.0000000000E+00 regul_p -kr01c03 1.0 * log(kr01c03) = 0.0 1.0000000000E+00 regul_p -kr01c04 1.0 * log(kr01c04) = 0.0 1.0000000000E+00 regul_p -kr01c05 1.0 * log(kr01c05) = 0.0 1.0000000000E+00 regul_p -kr01c06 1.0 * log(kr01c06) = 0.0 1.0000000000E+00 regul_p -kr01c07 1.0 * log(kr01c07) = 0.0 1.0000000000E+00 regul_p -++N_ITER_BASE(1) -++AUTO_NORM(4) -++OVERDUE_GIVEUP_FAC(1.5) -++forecasts(pd_ten,c_obs10_2) -++SUPER_EIGTHRES(1.0e-8) -++OVERDUE_RESCHED_FAC(1.15) -++N_ITER_SUPER(4) -++MAX_N_SUPER(77) diff --git a/docs2/Makefile b/docs2/Makefile deleted file mode 100644 index 4cc1a89f1..000000000 --- a/docs2/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = "build" - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs2/conf.py b/docs2/conf.py deleted file mode 100644 index b821d75b3..000000000 --- a/docs2/conf.py +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# - -import os -import sys -import sphinx_rtd_theme -sys.path.insert(0, os.path.abspath(os.path.join("..","..","pyemu"))) -#sys.path.insert(0, os.path.abspath(os.path.join("..",".."))) -print(sys.path) -print(os.listdir(os.path.abspath(os.path.join("..","..")))) - - -# -- Project information ----------------------------------------------------- - -project = 'pyEMU' -copyright = '2019, pyEMU development team' -author = 'pyEMU development team' - -# The full version, including alpha/beta/rc tags -release = '0.9' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx_rtd_theme', - 'sphinx.ext.todo', - 'sphinx.ext.autosummary' -] - -autoclass_content = "both" # include both class docstring and __init__ - -autosummary_generate = True # Make _autosummary files and include them -autosummary_imported_members = True -napoleon_numpy_docstring = False # Force consistency, leave only Google -#napoleon_use_rtype = False # More legible - -# autodoc_member_order = 'bysource' -add_module_names = True -# Add any paths that contain templates here, relative to this directory. -templates_path = [os.path.join('source','_templates')] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -#master_doc = "index" - -# -- Options for HTML output ------------------------------------------------- - -pygments_style = 'sphinx' - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file diff --git a/docs2/index.rst b/docs2/index.rst deleted file mode 100644 index 88f6c4892..000000000 --- a/docs2/index.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. pyEMU documentation master file, created by - sphinx-quickstart on Wed Aug 28 12:20:13 2019. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to pyEMU's documentation! -================================= - -Contents: - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - pyemu.Pst - pyemu.Matrix - pyemu.Cov - pyemu.Schur - pyemu.ErrVar - pyemu.ObservationEnsemble - pyemu.ParameterEnsemble - pyemu.helpers - pyemu.geostats - pyemu.gw_utils - pyemu.pp_utils - pyemu.smp_utils - pyemu.plot_utils - pyemu.pst_utils - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. automodule:: pyemu.pst - :members: Pst,pst_utils -.. automodule:: pyemu.mat - :members: Matrix,Jco,Cov -.. automodule:: pyemu.la - :members: LinearAnalysis -.. automodule:: pyemu.sc - :members: Schur -.. automodule:: pyemu.ev - :members: ErrVar -.. automodule:: pyemu.en - :members: Ensemble, ParameterEnsemble, ObservationEnsemble -.. autoclass:: pyemu.prototypes - :members: -.. automodule:: pyemu.utils.gw_utils - :members: -.. automodule:: pyemu.utils.helpers - :members: -.. automodule:: pyemu.utils.pp_utils - :members: -.. automodule:: pyemu.utils.os_utils - :members: -.. automodule:: pyemu.utils.geostats - :members: -.. automodule:: pyemu.plot.plot_utils - :members: - - diff --git a/docs2/make.bat b/docs2/make.bat deleted file mode 100644 index 6247f7e23..000000000 --- a/docs2/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/pyemu/plot/plot_utils.py b/pyemu/plot/plot_utils.py index fe4ad483f..579667ba9 100644 --- a/pyemu/plot/plot_utils.py +++ b/pyemu/plot/plot_utils.py @@ -651,9 +651,6 @@ def pst_prior(pst, logger=None, filename=None, **kwargs): Returns: [`matplotlib.Figure`]: a list of figures created. - TODO: - external parcov, unique mean-std pairs - """ if logger is None: logger = Logger("Default_Loggger.log", echo=False) diff --git a/pyemu/pst/pst_handler.py b/pyemu/pst/pst_handler.py index 1eabb2b2c..7493377aa 100644 --- a/pyemu/pst/pst_handler.py +++ b/pyemu/pst/pst_handler.py @@ -2370,9 +2370,6 @@ def from_io_files( all file paths are relatively to where python is running. - TODO: - add pst_path option - make in_files and out_files optional Example:: diff --git a/pyemu/utils/geostats.py b/pyemu/utils/geostats.py index 709036d8b..b67e80c03 100644 --- a/pyemu/utils/geostats.py +++ b/pyemu/utils/geostats.py @@ -579,8 +579,7 @@ def draw_conditional( This second conditioning surface provides an estimate of uncertainty (kriging error) away from the observation points. At the observation points, the kriged surface is equal to (less nugget effects) the observation. The conditioned correlated field - is then generated using: - T(x) = Z(x) + [S(x) − S∗(x)] + is then generated using: T(x) = Z(x) + [S(x) − S∗(x)] where T(x) is the conditioned simulation, Z(x) is a kriging estimator of the unknown field, S(x) is an unconditioned random field with the same covariance structure as the desired field, and S∗(x) is a kriging estimate of the unconditioned @@ -596,20 +595,21 @@ def draw_conditional( Args: seed (`int`): integer used for random seed. If seed is used as a PEST parameter, - then passing the same value for seed will yield the same - conditioned random fields. This allows runs to be recreated - given an ensemble of seeds. - obs_points (`str` or `dataframe`): locations for observation points. Either filename in pyemu - pilot point file format: ["name","x","y","zone","parval1"] or - a dataframe with these columns. Note that parval1 is not used. - base_values_file (`str`): filename containing 2d array with the base parameter values - from which the random field will depart (Z(x)) above. - Values of Z(x) are used for conditioning, not parval1 in the - observation point file. - factors_file (`str`): name of the factors file generated using the locations of the - observation points and the target grid. If None this file will - be generated and called conditional_factors.dat; but this is a slow - step and should not generally be called for every simulation. + then passing the same value for seed will yield the same + conditioned random fields. This allows runs to be recreated + given an ensemble of seeds. + obs_points (`str` or `dataframe`): locations for observation points. + Either filename in pyemupilot point file format: + ["name","x","y","zone","parval1"] ora dataframe with these columns. + Note that parval1 is not used. + base_values_file (`str`): filename containing 2d array with the base + parameter values from which the random field will depart (Z(x)) above. + Values of Z(x) are used for conditioning, not parval1 in the + observation point file. + factors_file (`str`): name of the factors file generated using the + locations of the observation points and the target grid. + If None this file will be generated and called conditional_factors.dat; + but this is a slow step and should not generally be called for every simulation. sg: flopy StructuredGrid object local (`boolean`): whether coordinates in obs_points are in local (model) or map coordinates num_reals (`int`): number of realizations to generate @@ -618,11 +618,11 @@ def draw_conditional( geostruct parameters is larger or smaller than desired. Returns: - `numpy.ndarray`: a 3-D array of realizations. Shape - is (num_reals, self.dely.shape[0], self.delx.shape[0]) + `numpy.ndarray`: a 3-D array of realizations. Shape is + (num_reals, self.dely.shape[0], self.delx.shape[0]) Note: - log transformation is respected and the returned `reals` array is - in arithmetic space + log transformation is respected and the returned `reals` + array is in arithmetic space """ # get a dataframe for the observation points, from file unless passed diff --git a/pyemu/utils/gw_utils.py b/pyemu/utils/gw_utils.py index 4c6ee8b7a..267415b78 100644 --- a/pyemu/utils/gw_utils.py +++ b/pyemu/utils/gw_utils.py @@ -2327,8 +2327,7 @@ def modflow_sfr_gag_to_instruction_file( If `parse_namefile` is true, only text up to first '.' is used as the gage_num - TODO: - allow other observation types and align explicitly with times - now returns all values + """ if ins_file is None: diff --git a/pyemu/utils/helpers.py b/pyemu/utils/helpers.py index b8b623878..cbcd9566b 100644 --- a/pyemu/utils/helpers.py +++ b/pyemu/utils/helpers.py @@ -214,8 +214,8 @@ def geostatistical_prior_builder( struct_dict (`dict`): a dict of GeoStruct (or structure file), and list of pilot point template files pairs. If the values in the dict are `pd.DataFrames`, then they must have an 'x','y', and 'parnme' column. - If the filename ends in '.csv', then a pd.DataFrame is loaded, - otherwise a pilot points file is loaded. + If the filename ends in '.csv', then a pd.DataFrame is loaded, + otherwise a pilot points file is loaded. sigma_range (`float`): a float representing the number of standard deviations implied by parameter bounds. Default is 4.0, which implies 95% confidence parameter bounds. verbose (`bool`, optional): flag to control output to stdout. Default is True. @@ -1091,15 +1091,6 @@ def jco_from_pestpp_runstorage(rnj_filename, pst_filename): `pyemu.Jco`: a jacobian matrix constructed from the run results and pest control file information. - - TODO: - Check rnj file contains transformed par vals (i.e., in model input space) - - Currently only returns pyemu.Jco; doesn't write jco file due to memory - issues associated with very large problems - - Compare rnj and jco from Freyberg problem in autotests - """ header_dtype = np.dtype( @@ -1170,7 +1161,7 @@ def parse_dir_for_io_files(d, prepend_path=False): Args: d (`str`): directory to search for interface files - prepend_path (`bool, optional): flag to prepend `d` to each file name. + prepend_path (`bool`, optional): flag to prepend `d` to each file name. Default is False Note: @@ -1216,9 +1207,9 @@ def pst_from_io_files( pst_filename (`str`): name of control file to write. If None, no file is written. Default is None pst_path (`str`): the path to append to the template_file and in_file in the control file. If - not None, then any existing path in front of the template or in file is split off - and pst_path is prepended. If python is being run in a directory other than where the control - file will reside, it is useful to pass `pst_path` as `.`. Default is None + not None, then any existing path in front of the template or in file is split off + and pst_path is prepended. If python is being run in a directory other than where the control + file will reside, it is useful to pass `pst_path` as `.`. Default is None Returns: @@ -1233,10 +1224,6 @@ def pst_from_io_files( all file paths are relatively to where python is running. - TODO: - add pst_path option - make in_files and out_files optional - Example:: tpl_files = ["my.tpl"] @@ -3927,25 +3914,21 @@ def apply_genericlist_pars(df): """ a function to apply list style mult parameters Args: - df (pandas.DataFrame): DataFrame that relates files containing + df (pandas.DataFrame): DataFrame that relates files containing multipliers to model input file names. Required columns include: {"model_file": file name of resulatant model input file, - "org_file": file name of original file that multipliers act on, - "fmt": format specifier for model input file - (currently on 'free' supported), - "sep": separator for model input file if 'free' formatted, - "head_rows": Number of header rows to transfer from orig file - to model file, - "index_cols": list of columns (either indexes or strings) to be - used to align mults, orig and model files, - "use_cols": columns to mults act on, - "upper_bound": ultimate upper bound for model input file - parameter, - "lower_bound": ultimate lower bound for model input file - parameter} + "org_file": file name of original file that multipliers act on, + "fmt": format specifier for model input file (currently on 'free' supported), + "sep": separator for model input file if 'free' formatted, + "head_rows": Number of header rows to transfer from orig file to model file, + "index_cols": list of columns (either indexes or strings) to be used to align mults, orig and model files, + "use_cols": columns to mults act on, + "upper_bound": ultimate upper bound for model input file parameter, + "lower_bound": ultimate lower bound for model input file parameter} """ + uniq = df.model_file.unique() for model_file in uniq: print("processing model file:", model_file) @@ -6607,9 +6590,9 @@ def get_maha_obs_summary(sim_en, l1_crit_val=6.34, l2_crit_val=9.2): Args: sim_en (`pyemu.ObservationEnsemble`): a simulated outputs ensemble - l1_crit_val (`float1): the chi squared critical value for the 1-D + l1_crit_val (`float`): the chi squared critical value for the 1-D mahalanobis distance. Default is 6.4 (p=0.01,df=1) - l2_crit_val (`float1): the chi squared critical value for the 2-D + l2_crit_val (`float`): the chi squared critical value for the 2-D mahalanobis distance. Default is 9.2 (p=0.01,df=2) Returns: diff --git a/pyemu/utils/pst_from.py b/pyemu/utils/pst_from.py index 70381b244..a9dc8bcd4 100644 --- a/pyemu/utils/pst_from.py +++ b/pyemu/utils/pst_from.py @@ -51,13 +51,14 @@ class PstFrom(object): """ Args: - original_d: - new_d: - longnames: - remove_existing: - spatial_reference: - zero_based: - start_datetime: + original_d (`str`): the path to a complete set of model input and output files + new_d (`str`): the path to where the model files and PEST interface files will be copied/built + longnames (`bool`): flag to use longer-than-PEST-likes parameter and observation names. Default is True + remove_existing (`bool`): flag to destroy any existing files and folders in `new_d`. Default is False + spatial_reference (varies): an object that faciliates geo-locating model cells based on index. Default is None + zero_based (`bool`): flag if the model uses zero-based indices, Default is True + start_datetime (`str`): a string that can be case to a datatime instance the represents the starting datetime + of the model """ @@ -72,7 +73,6 @@ def __init__( zero_based=True, start_datetime=None, ): - # TODO geostruct? self.original_d = original_d self.new_d = new_d @@ -222,6 +222,8 @@ def _flopy_mg_get_xy(self, args, **kwargs): return (self._spatial_ref_xarray[i, j], self._spatial_ref_yarray[i, j]) def parse_kij_args(self, args, kwargs): + """parse args into kij indices + """ if len(args) >= 2: ij_id = None if "ij_id" in kwargs: @@ -244,6 +246,8 @@ def parse_kij_args(self, args, kwargs): return i, j def initialize_spatial_reference(self): + """process the spatial reference argument + """ if self._spatial_reference is None: self.get_xy = self._generic_get_xy elif hasattr(self._spatial_reference, "xcentergrid") and hasattr( @@ -264,6 +268,9 @@ def initialize_spatial_reference(self): self.spatial_reference = self._spatial_reference def write_forward_run(self): + """write the forward run script + + """ # update python commands with system style commands for alist, ilist in zip( [self.pre_py_cmds, self.mod_py_cmds, self.post_py_cmds], @@ -337,16 +344,22 @@ def _pivot_par_struct_dict(self): def build_prior( self, fmt="ascii", filename=None, droptol=None, chunk=None, sigma_range=6 ): - """ + """Build the prior parameter covariance matrix Args: - fmt: - filename: - droptol: - chunk: - sigma_range: + fmt (`str`): the file format to save to. Default is "ASCII", can be "binary", "coo", or "none" + filename (`str`): the filename to save the cov to + droptol (`float`): absolute value of prior cov entries that are smaller than `droptol` are treated as + zero. + chunk (`int`): number of entries to write to binary/coo at once. Default is None (write all elements at once + sigma_range (`int`): number of standard deviations represented by parameter bounds. Default is 6 (99% + confidence). 4 would be approximately 95% confidence bounds Returns: + `pyemu.Cov`: the prior parameter covariance matrix + + Note: + This method processes parameters by group names """ struct_dict = self._pivot_par_struct_dict() @@ -376,15 +389,24 @@ def build_prior( return cov def draw(self, num_reals=100, sigma_range=6, use_specsim=False, scale_offset=True): - """ + """Draw a parameter ensemble from the distribution implied by the initial parameter values in the + control file and the prior parameter covariance matrix. Args: - num_reals: - sigma_range: - use_specsim: - scale_offset: + num_reals (`int`): the number of realizations to draw + sigma_range (`int`): number of standard deviations represented by parameter bounds. Default is 6 (99% + confidence). 4 would be approximately 95% confidence bounds + use_specsim (`bool`): flag to use spectral simulation for grid-scale pars (highly recommended). + Default is False + scale_offset (`bool`): flag to apply scale and offset to parameter bounds before calculating prior variance. + Dfault is True Returns: + `pyemu.ParameterEnsemble`: a prior parameter ensemble + + Note: + This method draws by parameter group + """ self.logger.log("drawing realizations") @@ -462,14 +484,13 @@ def draw(self, num_reals=100, sigma_range=6, use_specsim=False, scale_offset=Tru def build_pst(self, filename=None, update=False, version=1): """Build control file from i/o files in PstFrom object. - Warning: This builds a pest control file from scratch - - overwriting anything already in self.pst object and - anything already writen to `filename` + Warning: This builds a pest control file from scratch, overwriting + anything already in self.pst object and anything already writen to `filename` Args: filename (`str`): the filename to save the control file to. If None, the name is formed from the `PstFrom.original_d` - --- the orginal directory name from which the forward model + ,the orginal directory name from which the forward model was extracted. Default is None. The control file is saved in the `PstFrom.new_d` directory. update (bool) or (str): flag to add to existing Pst object and @@ -477,10 +498,11 @@ def build_pst(self, filename=None, update=False, version=1): components of Pst. Default is False - build from PstFrom components. Note: - This builds a pest control file from scratch - - overwriting anything already in self.pst object and - anything already writen to `filename` + This builds a pest control file from scratch, overwriting anything already + in self.pst object and anything already writen to `filename` + """ + par_data_cols = pyemu.pst_utils.pst_config["par_fieldnames"] obs_data_cols = pyemu.pst_utils.pst_config["obs_fieldnames"] if update: @@ -910,7 +932,8 @@ def add_observations( rebuild_pst (`bool`): (Re)Construct PstFrom.pst object after adding new obs - Returns: DataFrame of new observations + Returns: + `Pandas.DataFrame`: dataframe with info for new observations """ # TODO - array style outputs? or expecting post processing to tabular @@ -1157,11 +1180,10 @@ def add_parameters( Args: filenames (`str`): Model input filenames to parameterize - par_type (`str`): One of `grid` - for every element, - `constant` - for single parameter applied to every element, - `zone` - for zone-based parameterization (only for array-style) or - `pilotpoint` - for pilot-point base parameterization of array - style input files. + par_type (`str`): One of `grid` - for every element, `constant` - for single + parameter applied to every element, `zone` - for zone-based + parameterization (only for array-style) or `pilotpoint` - for + pilot-point base parameterization of array style input files. Note `kl` not yet implemented # TODO zone_array (`np.ndarray`): array defining spatial limits or zones for parameterization. @@ -1220,6 +1242,10 @@ def add_parameters( up a multiplier parameter process against the exsting model input array and the former setups a template file to write the model input file directly. Default is "multiplier". + + Returns: + `pandas.DataFrame`: dataframe with info for new parameters + """ # TODO need more support for temporal pars? # - As another partype using index_cols or an additional time_cols @@ -2072,6 +2098,7 @@ def write_list_tpl( par_style (`str`): either 'direct' or 'multiplier' Returns: + `pandas.DataFrame`: dataframe with info for the new parameters """ # get dataframe with autogenerated parnames based on `name`, `index_cols`, @@ -2618,15 +2645,14 @@ def write_array_tpl( ): """ write a template file for a 2D array. + Args: name (`str`): the base parameter name tpl_filename (`str`): the template file to write - include path suffix (`str`): suffix to append to par names par_type (`str`): type of parameter - zone_array (`numpy.ndarray`): an array used to skip inactive cells. - Values less than 1 are - not parameterized and are assigned a value of fill_value. - Default is None. + zone_array (`numpy.ndarray`): an array used to skip inactive cells. Values less than 1 are + not parameterized and are assigned a value of fill_value. Default is None. gpname (`str`): pargp filed in dataframe shape (`tuple`): dimensions of array to write longnames (`bool`): Use parnames > 12 char @@ -2637,6 +2663,7 @@ def write_array_tpl( Returns: df (`pandas.DataFrame`): a dataframe with parameter information + """ if shape is None and zone_array is None: From a761e02e8db1e9b7d4e999fd2c2bb4159ac29ea5 Mon Sep 17 00:00:00 2001 From: White Date: Wed, 16 Sep 2020 12:11:30 -0600 Subject: [PATCH 3/5] nojekyll --- .nojekyll | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .nojekyll diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb From dcd21b372aeb7a9725bd312dd60dfa532c2bb6cb Mon Sep 17 00:00:00 2001 From: White Date: Wed, 16 Sep 2020 13:50:41 -0600 Subject: [PATCH 4/5] more work on docs format --- docs/_build/doctrees/environment.pickle | Bin 1678213 -> 1677197 bytes docs/_build/doctrees/index.doctree | Bin 4383 -> 5404 bytes docs/_build/html/_sources/index.rst.txt | 5 +++++ docs/_build/html/index.html | 5 +++++ docs/_build/html/objects.inv | Bin 12626 -> 12619 bytes docs/_build/html/searchindex.js | 2 +- docs/index.rst | 5 +++++ pyemu/utils/pst_from.py | 2 +- 8 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index f7d7c1edddfb9ccf3963327bee5016c610c58157..789c1ba9f7b6408d6aa9b4811cb9f127f5066772 100644 GIT binary patch literal 1677197 zcmd3v2b^U^mH%g$o}QWR>7Je(n1Ln;Lzo_diXZ|*6kz}vOe^@f{od_<_svVZFagCZ zK_7@qn_YCx0W)TIb#;?rRS*GlTy@noB4AkkpQ>}~hC27Yu2a+1fA{lQhpwu3?yt_b zs&3``+<})5ELgZe{9kWXyIBtE`zHMQo}ksJ*Zg{CrZ;wWH>j4*O9b6EbIkK*uAAA~ zTjVvHGne#+N{wQt<@=?eHPbtO`;*#!tG&Ilw;Xi*?dSS?wm0|tweEIE)ZSj06vo7V ziyyRSdV}p&QT}dozu!T+VxvA4OwaU2LXGP0!E-Po61A+c$8WWQ5?H@(rnk85ce+xf zzpvxh+d-q=p1BINM|s`Xnee)u21uIetyT_zz`ECLRQ*PMrnfE;IvsS%-N~jmEyO1y zgpGE$R`Xi>XL>8eUtTkqh#orQ_m(tUjf!6cmo9GZKX2!gW_rttjpqJVFkObQ(@#3# zq!W%Ge}dogYx~EaBAj>p_|9OrKYo7T*ZunO<7c;mMzK8pc)wN)>eKDx$Gv)KywNFx zx4}Q~#Bu}t|7cqaOIm)__u4+lJ8|NKi4$ge%fnn0I{TXtJlNb1j{tu};Q&rJv$u3& z0wK?Mz0rn5IEkJf_pk)k^6k4596eQ!@9j_AWWTkTh01IXC z@|oViM5`_8P0Me0THRu&3(*}Y2s7>8NWEKwQe2p7v}#^Q)}bYBv;}V3h5t4F2*DGQ z!4GG8OU@6bCuGp#jyG9_kfm3Me`U~6u~G83O;#Jl-SGR1gLVf>hKT9*2KRW?E@Ttx zX?aum;NBvr)H9ddFjGFHd}w*6_>axyExiGeT@l<{0<}iej+x$q8H^tD-|ZO>f3N{_S5Uu`Na~&- zyY88-Mx%4x#4}agb*AnZ^7n;mPb;5+wpWC_{5m)X+yTX? z+nL(-m~CxnJ}}8>n55~oL|X+ulHuiJ%g14YsC937Bm4WQyaWesW8>8OY~5} zTF^X}{WkdcM*P*7NKk8(x{z~`nJ8jQrxnxydZSZsbo@fgpMoM&7iFsH75$l;!iToY zjlBh|$88lj+T1@a+Tk`7s+}SC!GD%UjFR z&I(mC3zN`fN`NwaWVi1(3wym*T{JaH%*uAR*@R-9D+Jg@S8!kF|GV5gP=`J1g(;e5^o2nJB6vL2hR?jJK6*vLgNbPYhD1w^q?jb zNl}n{%j@v&0SC#K*UTlCh_EpXi`riSSw<|cd3D)*7rHGd`DkHjJ?J!p;_m%3H_1e! z;opT|x-MFutMIpjSbdc{R)@72YB2m)q2xEAy}M|kG+EwJJ{xVXfSO(|K!Qmqw_dfX ze!WtL``3Vd6hF!}WvjKS9g2&e{ac(u-`UxcrZ z=!IS0g6UUC^U&R>cY>(^e$ZRet?#Zk_9}z;{B}{kxXSn9ua?SRb%W9jgvdgS(u<<= zGILBaXc|)aDkxOf@}rknSjeQkW<%7P@a-le%8x8R3ZJ-2rcm$RRH7bwqthscMR*Y! zMdczGdQf3CjFPsY?*dMKX7c$^;R{enMc*iF)7HqR*1Ryhus0})INmLNhW6Jusjb#;laT+1m=l z3sSVv@TM7p{!(x)B=)NK!1DI;31PD+djYcCsjdV@tV%>A3LiFc7$*x~Z=>wujmw_Y zI5cQo(TGFW1YWRcX-uYuytg`qj^S^x3zT{hER$xS32i`qmok>ikST9Z0FDR)%a1Of zB4f*sDW8h}abo!-Ous^=hlaA{rSrNLO~VVf&~28WM+a{|`Oe2Tgp6%N*_?#8sCb3S zVEhrh2*J~hx>p60qByAN)$&)^gbS)cQ8r5Qrx+*Rp-msUM^F%ULyGan93V{a{4 z9OTbJM>eZLCoCs7p%jb|UF-tB@1YFKU#vmt;%$o&;!!Gc9Y)GNp9qioeFGC6=)<7} zlq#e^C8)Q3uT?CI{!v1ZsA1~2AsG~uG#W;rIav?l5kV+6DkmToUvOarau$VM^evTx zF>j4-r`d(~ZPW!?EW1FVdMw27o`&8B^dF$tC|(RwJL1C^ns{HSPVYez$?kl;{D=4zT!z zUvH?6QuzYR5k6JEQK8Qz`syw9W|U?hSrDxP1cs#s`UjnI1sW_ldg(pA-03vir)}R} zEY&ORiDI?UEkVDeu%Ye9T_-<5JLu zVb^~73O?w{r;9{asYH<>RHIso71KV9!1i@SmrWFfnHzgc!XWwf9aHh5_b!{R8!Hu< zDv*6H71tFnv74gsRVrMJ9m6qQ`0CgcMa8W{$7?~T=s4gTVZBOI70nJbN%c1PQjBXi zp8uqaFCM>S$IkP{Pd{(xjteh7Ysb0gUVh%LU1!Wh4__Pq8pk6r@bqdky)_r_xa5K- z?YIDbdhx|)RfCB}YdZ0>lT$ABmdRO-36U%s z&s&eZ2cgf@h1XDL#*_772%m|~x(pe^5SVa*LM&U&uupn0Mu@fu|EmV$7HImb-C8~D z&4xJ;N%ldTt2((GF@2}etonQWY9SnmiXjvBq1Rvny|tElLYs+q_d#`d1&N}u8h<9E zMC%V7T<8zTs8Ni9i3T~QQGN1t7@T-(sQ!uk)i6e5H?Z;oQ34as!MAi=cVt}XuGAYm zsQ_pep@>DJf-%wDKo_mJTkGk?U9nM?uw5qQsC zv`|V&#B_q14{dem??lAjMr*e)cF|(}Ctke>eFkW-WyFT~DPcsczjx3x4hzfEGALi+ zn;04qD7`QP1#@yRnUUzGh)Q^QfCdLYI9WRbRpGc@6b!@{y50@#}i$-@y676G)*?LGiVq6D)iF9N$sd| ztWuAlm0?UpkrJjNG7EDgI?ioxsT!_hpZvPxRuAMrz`@(sVe0OudkC;II#- z#bC7E=|m&L6~#hOD!}xb>;}W*RP(w@%#6WnFe`4fC`PByCjwiAJtwOk{Dz_&>=$52 zFQefF9VQ5y_4UPO6FXjX=r0AXlnFBRtPxSUipE4NyxqIgg=(X*8wMdaL4QHCFmZ$= zCgze=+(wvm3z`L(?5TTV>a8&86?el+q+M1yS_|a{O0ZvT!uzRS3MXq|yx3b7#mM*H z>gZ3E5MFhcR0a;hSKyjBy5#SHS)FM)eYV)IZ-derCEE}u3m>OOyuF1d#>qzGWY}?! z3?!bjdlH-v6HH)z;h;nb;U7 zfk|k1y|!wFD!e$MWsDt@@LL<+F3^^h+B4-ZM@AAFab7SPxgvf-I4l~%HtO*JEOyty zaWc^Z6u6oES?qeczMIMUIT%O&!C3{41(XT-A8VthYi3%N%0CLvdanlGo(phQ8B zy|>(}Hp||m5B(i6hL^3)dX-d7;l@1!OmJ|N02ZX6t$_J}sIgG99P#sDW*r73O&F!e zt*DeM)QEKvSTKXA-m=&RK4zVIOgM=ez{Fe&I#)PAK&rEE2(sDV!#X1!N=^bLLUXqxzYRCf1|mD^tlK zPhy-W%gL%#47`J2pb)Z2N44bYR~WS>^{d_fe$lw8-^IY-4GHZu%x$z~8HwbqjeahS z!V5Kk+7(US#yE>zXgF)CM-e4hEecJz$cQxsNB%NH8m+^+tEXYxF&h3E_iV$VuL@T+T#S#GsE71M;N9LRZj1h25tivVWK47(wuKcC z-gPkcTP=fd4QE@loVy|{38jo0L4;$;~=c-}A%!*-3 znBpk)b6oZ{s75(CUSTED!S0;ZiYqzL;!gRMg*&D&>7r7)2J;aL^LPu6k4}~G3GtlxN zeQyv3)RILXt z*H7#!mb>sCg{~y@8YV;+2VP~el>sK0X1nYX&GZ!CFjTBXiREGy9ZrWd#me_cSaMXH zA|C!!K1Ri@%TRThFe2NTSVgni=&0Wuq3lc9+u@nw{|jZ<4^j17D%IiXRQS~Cy@k}{ z56em<`aO`lzJ_3Ak}@P;_pw|*a?FwGCjh-pLp1&{W!PwRyXqmb+Y+?=FI7wM z-2l}@GX(pb!kVJzDJEWuq>o{Zi($fYR2T$ao#|y3UQx)WS}^S#K1eGw!g*ng_H$`D zTC-P)j-;R`E))GAABH67g=I@lZpx{mjTy<$Yqg&2K?AZW4Ibt#VcJhUM-zi=r~QW5 znbW_L!DN1_B*-pGW>vs^-wKUvw2$RFX!F8tLK8Ua1Jn027ai2w5H_SyAT%7XM&)-z zi@aWs48m1Ah*Mj}q94NUw~H3_)@VOLEs%br7E$r`c(`aOWT?Gn${G|lSQ$O2w^sXA zxa$gH^yiChMj65FPD$is5bexd^q6jMP1stC!4A|$T%4P^skbT>4vPS=*Wo4@Wj4eX z3-Ma)Ed{@bHB<2i_)`qOV46y7MS<0EaM?}0wXi1Q?JoH6lPdJ{OJaWr^dfHTEpNgW z5;1&~(+s_FwUV-T@7@W_l$eL#4nx}Qkg~lU_G5`(Bs%ez^p>}#iU}LAQ#Oe{q=!szEpW2`s;%~u2#Mo{q?!+J1akp z{`&4&#maZ0zkc|N*HrF_|N83>D&LF#`q`geTKReO*Z+S11(k0_fBmZSzRE-Dui<01 zzNqqz=&$WVXHj* z*DCi%f1UchXIFk1{dNA;pQwC0`s>0IK2Z5V{MS2<>h@s2o>&)y^=>hMDL=M19M1m0 zQkmG6iCdJQ9fSAwBGJ;{HuIvH%V)M$#uqJEAUelu)DG6|1(?f*0Zsv$sF_^jq28fJyc9~Br(t~V$4xt46HiCB&(PWE_o-5WoGEyh#j~m z!wPd7)~vU|?Ava!y<)qZ{z*yiB@|0tiGZpno|6cu$p`n2>hnCY{{uQxzV=I$+*=i8 z6<_^YD)d>euR{{CTGvY&+Y~(^-ILoQ0mG;HM*GZxY}#!xS^d6ZaOfr{7O9{xuG*3RW)6RgF^Egmb#*v#^> zF>us<|(# z>>QOPM;i?aGq+Zb4Ly^{03DRcCnhowrjT8485dUpQO;BO?w^gm*y7&6OJ^#_g+dRB zvW7kXB@A1gqiDS+CqE&0hgCaH6{qZdQ2FjQm~YW)Z2j zObp8P>ds8%B@o)3^m;>yg?7<9Uj(hM=>IQ~Q=m8&Sd8;<5V{mL)5d>@W>fBuxeAVK zEa`Z#Xe|ywgu=U%vIA0iF+2&gAf=RdRc?V0Q2jC?Y&lLGgk$lP=5$o$m5{vhR`?%~ zU3nG!3vw&3fq%i&>+ql3@t-%~KW~)(bSrO$e=Bc+{{cm!<1>q*^{9wWCZxpWrb3-} z%E#I36y2~{bcPd1m%$hzr7RJST0V1tT0~!O7A@f%EY8TL)TCli&sPpm&m(5n)29lc zhVLAp2GPr%#U~@O9F2$F+%zLC4mJJa05ypY<1Ct1V%dh*a<;CFc+~dC1JowEgtKT{ zh1x{-DO+4t0#vtj;J{i1(FL1D-G)$I)IZABn4b)lt~)@bqDwT3(vTs%RI?>!#G|$& z4^W%v1Ty0>SdOSf9t1o zj=b@ie$O!QhM4s>_r~f(shGf-L(L=M00!eSz4D!Yis#5H-|Y7c1FwkLe{-*lCA|Xu zOwA>URqy05LDiXu`e~gbe>~9d83z6k>mTO+*sS?u0(_#D}mRd2>8^`ohI!E3(s^2pVydhS`%)PNPS!%?H zNb^T>yG2$#I=i3RIr7Mv{hndq5wSXG?vYeI5+0cl|ARX$H0Ol%NK2Gia-QBV4|C+F zr}le>fuF>>skxsvrAkgTTAe8Auj_E26>s8!B$+o?+lAu~Wm`Q>mUJ%vNd6 z344kVk?D{7`)Qpce|)XqGYtG8_NtisV?0%I#L^oqvn6()YW{&cI>feTNTess^w)3t z~k{rQ@TeG zxNtZ2ir=A<=YwM(VR^wR15 zaxh0;I!mPFrk8H$mxDR-(hK`N!@x^o7pb|I zQf)MrlW=2-=A`8E*hH8l(?@UUr+Vc;XNx7OT8tCDSW*83}U5G-r7@v(k- z=g21??)MA>pNJjC<~});_+%ov=S(X+;o>xwuVlK+5_EUJoXnB8zTEE_2Hp}ou+6=d z(PFDZI+~}#w|wZSaFbr9mww(a2Xo}5pZ0r(ftSS2^aJveIxpDIODavKmj;%&?^Q4C z_Y4Csi5>d~=B033WM40ZX)?XErC$!_s4Q*j_Y4CsiIWWGUP_J8ur@{8Pc=7%V>B&M zrk@_&FAsC%r<40V!@y7Cj7Pqog2 z(|>U^NsoIcE|z9$c!3 zRULU~sUlW0M{zHFbVlsYPq3d%{P|H^6Su$*G5;U3HSzMu1XA_+#}Hx_uQ(7D z>LdfZSXtN2-182}fp+B+Q8j&toxSWrCJt=OR!tvZKZ_Kr-$)blS4~&Wa=auX9t-a} z_|CvwG=Idey$xaIrOHS>UdND zNqyYSek^fg+15l{AAMUBaeYLX2UH)=KM)mAALR&k{_11ud|awV-WS!#x3RO7U8+RZ zW~-5JWg8wI4<^q1w5^G_UjCD< ziMU=O(gUiOw;zZKsF!j+Y5wYE)`Y`X=V?961BD3PhzYUH2U&mx8B57I<_jVwb~ z1P*ZHJ)C*-Fy=?D;p~^VryWk2%ls`77ki4UZQ!F1>LB{{@a3)L;dy$z85E9@9kp!1 z0{9>od~hlJl+yqlL-+72NpLu9y4sk83$F&tK?yF)f&Ur_4MYJ;@t%3O*yQ(q=L~jUA&&N8@Lvfbnd8^*meqZr~8xO^|@!InHi1K9CMFZHptOeP;E_nzju3re~ zfa=$Sw#6juhss&n`Kw>r3Fy?D?{`r>yNvDH?0P0LH(Nd1$$l28XHSqOa_iaF%A8M` z4et`)bH$64(rX2&7%Zp_b`A}_3GYF;fiBV!`X!$Csj?qTTpRRvZDk|GXU2_xkn%KiC{!DF9xE{U=tG9q#E3bfTR9+?i=QZMg zUMK$NcK9EE9-GC=$>OQv1S(vr^(66)(VXug<7A(>$PwGaC0NXEfS(^=dx*3Ts6CvL zDspR_?fmKxR5*1HV6xGI58%cT=!bu1`@yuj4U6igRFK;C3HEbIZTqOLiMZG4$p0&_ z(E0Uo%zwJkBe6*02kh*ak{CMzWo%Qj=-~Xj?1vK^-?B9^Z#ee!#Sz6Kj=>>r17V9} z55=*F{cwU~fvt)8!cq15;fP}q$Fc0}SmJ=A;iR!Tn*DHs<49W*&T)jhHSqv=CX&n) zh~*M?woI|CNjF;I`E-KjBDMg6=JB>BoTCZf6t!X|77~j{TI}qYBGKL>aL}3HXs{nn za8zwgILEOW-=Fe-7er?-GT9`PUP+Q7s8_HvY6@y|y6#B9kq8RyHntD~?N(b8&Y^|1 zq}{g;#j%LyIC=+@EG_uHXv)DP7X3Uv>}n`+IQ8>{u8{bB3tJBH`zBix zar?4fnwayck5O^oD}L5metROtM~_52=dv?nS`7}%z@zCSbat@S5ISerns82MpxA(G z_fkwS2uYOL!c9r6&mbYS5dZsZ4aEPGwkDkWANKXa2O0zMCBDoCEF&Jt^w?Q4C39pZ z8C@m8bQ4<;!F0W?3FnvwORWZ6VV$lhDiDFZk1gI5$i@sHp&mlv-E0+v!aHqEIHxcy zz64f)Zxeg)#m7{em5M9!X^c%3W+J1j?6eO*n_LRA#-{PG9Q@-;x)s zL@?rbn4JMr90%v&5DzCTe$UoHSp3%3gmV_7(&^$00#o9fcIi5zM+4%w90SutfZp3$PE)e$rvTMR+-3|kY<(F}-78Ph~C2tnM) z7H$e6Ymg$f5dXi3t%3Oe`L-sU`+t#sof>>#T5R8trzr1W%Qp4?x{PWdo=cqn4z>j1 z{I}YgaPE9KX4>}k21x}Xg0HZ}ndW3oih5ur!AQV1tYr;8&Wt~Rmz#|ckEFNHI z!IZ`3jOq}pA~=4-7D90R($<7?9D|T?GUQN!2x7yCtB!3}r3m#93TxRa2!&O)CY)1P z7Im1?FXu!!vN)Zcg#)S-svAe&6sNL<5FC%THKF6M*r%A-`YqR4EKI~I9>k@Zyv@b7 z3o-*2!uO!lGyBQJ=P$N3VeE73rSsL7PQpg#J#suBEvu$Rj+rSC%AM?dnL^nT*MuyA z1k(H2N(iL)ND~%I>bA|yQn)*&FzL0mNv=2;o%}U+)=ZszR9`1gMDmEY?`BIP-u|+! ziMUF95rNHh>%U6Q@J)N&b{iJ83MDu{m3|jwB|ubvU}wq{)d~GkWk@E_9%PFn(0*rY z!a1}h^+x?#ztu>Sib4_3mQh#z=|}X(qr?#=o7jp7ll8VHoHH4M8?xY&d&N$pmBx~c zKr%bnnJ}$5Tl@1jKNxa3fuqb(?q5L3Q5#jPaTNBQ?===LpaP&wd z^bK}qObHzuzs)G2*>(Zm$CgDf-D7LQIVL!?49lKHZ7daqBACCh<(q;zq8}I~jZ~aJ zvGoul58Ik>PGn>%*yooDLA~Vf^R*tJ7Kd~WU*W2IeoQ|)X_{sMTNr^=vNhox)`*1F^jdh;PpWEZafs;_c2-O=^=|`3t%_t4MlWHj zB8+adHQ}7m8p#L_-gx!tZq;jL%=l#|L1G_ZXUvpXV$P3t_A*5iaPMV{BjE0^HQ^lG zO6h}cJrMidGN@(5BeHwhSu#boZPvChC71B}Dq9)h^%Ywa&Up<~C+pg-L<~YIe`E_c zrIOf;NGp;QML;~n7DGThU~9rTh+(++AiH0lia{)gjJfKSAKkCABqb6$2eUO1IvZ?F zIH$7&KD1ncS@ZOqc@&CN&SuLutt@>_^_hm$gJr!+B-Yefq$ zNWX{RZ}j9h*_ko*WOUJk^5nz?bXV5>?8g&de$CcIT$1lb9CN-FbYY`qI0@BpjaUx)MKk5gl@7m;hfMKaZ1^5!LDnVY|cLEo}C0qeUP0oQ&Q0= zGa{9#l+bz~TM?o4Zd()1X>Acc2;jpDonR{PCWC6w*`J4PmbA$4K6X}3`9&+IDSr9d z3CMfc(g?`AY)v=^IU+#T;R~<@F^!dO6}317^(S^#OhHBWucSc5+6b73*-{9Y-`kpS z4raJf3pyRxD^e+H8@N+32<6aKu6o_k>C^~HQWrt8nJt7M*=TFRIg(M8b?D{6{a)$H zqDLZ_bJ&?Ntu3_wt*Iky&SGmJY#wK8!a1AOQSPG-l(q>ZGX+wbW@pQkQnaa;RG(%- zCqY$W3nHjITNBPvt!VpjCv7Qb97|#m(M#CbF-1g&O1eIR=SH>|g6BoHCY<9L3$vdy zl%>%K=)LR=nF69iC8CfJx`VBT5PFBL3Fm}Xs{BVyNcvTj5s!?%%FdE0qv(FzsGXx4 z38b&EdL-g`h@BZzJhZc*sUvJ2U~3_4eq(FGIhz%j`IW=kKozB7DMo?wKd@!&sdoKoaT*2BcLU?7;54a=Ai0_?gdn-f)`X74Vy{5F-2fN7Elk8J zTx~ZPy*Q}*UTa6)tL|?H+7L{S#PV?)+oz`GV?($`RrMw`S)^>-$`(P&#?8{iJZ?i6 z5vL>JC6;wUQj0@R-^tFTsi)Pg8N}0Yay6-u`2GECNyP8(u{9CbhIb*lx!$s{7Bb(3 z72C#S8|I4&aKoNB@{n#YbJ8HN``KAD1*T@jGl6AjCg8rtmPWwcZEM0gxXoU(S>0dQ z?X}9?x?e9;8~z@@&@Ot_pgx`E*FPy@{4+bFrWhZU*TPNo%>kkL2ev{&^Fdn^&S@?# zR((%f@eRX}&-fZwU3;~rL8}>RA|SS~r4SICY)v=^u}*y602Z2pMhmvuixVpHimh~o z%1eZ(&ShuLv?^`Stx7qn3A-I^ZG_z!wkDjj8=UgXUUe$XN(CaDGF!YU8#RAHt5K+m zu<+Sh2#ZNu6V6$Tsx4qyJwH7X+4R_%F=eAptYokWH>!my38S0Xnh2xoZB005v`jJ5 z#z2X1B=bIY7EH;gN!ffdZMuW<-E27o$~$dMIENC|jdmd*oAmTZ#B&cjGp2ZEuQ;Jf zQgQBLYa)!kWNX4XqeZYICHAxPt4wro=xYA%UZ7NLhA_&r+)LGW8!6V4H=6q|yI z?MB{?u8erZvw5wnj8)jh%5jrxu5# z9$;t1lvMuOQ(7Yd^c%J$0_c~vCY%Er#6_oc!>0lf$%b{Vdh7Y~b)hQ4Vl7(>VX?~A zgnJgy$dL`>gJB@DIGrutw0h*T2n7%pr?RyW7LT?y;he>Aa^rP+>q;^Psa(y@hA9=b zF`SNPlPU=cM|6E_cm(SAFUHS)I&}=B{K5AqYloO*lue_!_zgAq>N6@ffyr(`u2wlLs}C zy6`Br6awNTTNBPf49ZOJOTSZ8AfmXEE#4GG{)i@2MOZwIt%b07vaJc{ES8HeH>*vi zX(H(;nnIHZM=t-#&VnhI{9QV+HUj0}*m4Mz&)Ax94rO3p=Nj$R zf=Ph*Q9UOmOuL|m>PiD2gXXwYD_bj zLPdnbGuS!^ho{?`aL!>_y8}zkHMqzuT^SPLNaIE9ESS=WMwTfWu`UAS`D`f!$a8E> zI0rJY*K28Kbub7qyn`*=6vM`>wthmYAq3vaRzV27$<~BUz~ZePzxeUBV__mz;p)Xd zw6js~_~&@7s&@ZbG6D<5*V(=_EflH)P%BO(!57?TB%l&T<*j#rEhGB<94whsLVmV}^ ztDbK*mZSoLFo?4nktcw6CvZW9pSKFF!4n(^iHZ^~y#UYp%v$J9f zM$MsRfQhcQC2($F%OP-HXlueboMrWH4KAGUs`~N6I2@t8hn)peDA~0oem(*6F18c` zg@ zU|~S3K*uXOpz^NDE%KC5rI8o$GKlXjzihu>>plf$DM(i&9fhUis7WXmss9l^u z5ybTuu%As_f37q!kCOz4h|aB7D#1PUF!n105d=E- z^~N*n6cI{bZDtE1ur}J7a1Lves8hlR*l)~7)-NG~JBOV~(>kVZmPysI9MJ^dS!`JZ z-{WjeILD_h8Nn}FX1~C4(jc^HcGgUxsjbF3G)*euRbne5ygXYI&UuZC>eljWjXg%g zwb_#+#h0)%Y)bJ2ZOkkOY<)98fZoU!M}WS_)`WAQvRO>}BQXX_Ew}Vbh!Ee)&ZH^C z%=R-!G{JWVTNc6h4qFq>@olO5`#ObM18x9U;1ZK5>E7imX%XUA*;zG(xJ|E#{iPF* zUtudF9RJJKgmaFYVrP&JUAh|fNr(g=VrSBnAgP9Zv=Dp`uw@Z^zp*vp9N(%Y)IeXJ zvQ=Z;bluBJfVd9c>}t3`Ms^t@38oEfIRw*MTNBPPt&TkawJhr^>X|7J)S2vTnbxZ# z^p-J8CLwh?TMr?1s;vpsrAa-qI2{Q=E@K(5x23uXIdhL!rCjIPPpI7ek|dBvotY}3v$b& zdG$g=+a{lmLeJjG&Z4Pj*T6C^H2D8sFloi>~%BM)gNiNFZ%t zOCgXp*_v<;X;eX)Y;@}-ZBv{ciD=GcXU4Sds7>)nG@&j+Wd~aYp>l?;3FlOnODf%Z zk$zY%9fd^7?2MQaNp=TPng|b{t%2~Ev^C+J$I_q#M@fT@w#**~BZnS41Ew5SC*CTN z7Q)~r_VWpY>upUqXE37jU8n}TwJmB|9MX6nJ1eF%)MyF!h`qLV+w&Zw!A)#)|m-k*xt=K=o%D*k3!Ro%Z^JTlEj>l7M7wVRW#q3FnLk#V63~Ub@Xvfk@*#ws_M@vQDQFiXjNjW=kLl z&a^e*9Ko=hRhoWF@JfH;4>tqJGO@B59P9c`agpHDphSX&d$JwGVEcA&RI zDiDjl&lYd$e=@`f#gM{3$(BG66l_g6N3aZ*L9zxIiEspQ6FUp0Ak@a5s3OFY2#f34 zDhP`g*qU(8Vh}P;h88LiDZHC4-jo8V4xt!=;GJv<1i{;EO*lueEb1PmJ4cCd1aTKT z3#K4wb%>^q35zeWRS*_mur;Bxuvm497p`#K#KJ_Z!qvjn;CZdqlRfQ%F%^h~Vc}t} z`e!3*9t<^*0`M1GdN;s*cPRc((!@M2SuMOERktcX7*kRXcjia628=8xE}*j|hq9kd zs?ug#6LBHlC{0x6dL3%ng@syB@3sqsZ5Vmh(ww=f%sD96uLs0$J)`W9ZTZ9krAiz}MO$JpsDU!!* zmb3_O^Wm;W8^@(a8}eqReBp%RMz$_OahwoDO4o9@yTrSuduUY%10X#>EZ~R z|6(g4Y(8&m!a19{_+JioLx;T+FOmH(*mNWXqE z;t|k>BV6^Tqtg)ByitLK&|0<@LTHt(3Fm}HJLRC2*S6`A$mVo*W=t!O);rLo5iqB+ zg%B{0wl(1#%nHnXPQylG5zW=??3kj_-a5KC!saTr62j*1Y)v?4GZtn)r&XiTNauy@ z44Kl=-a14c!Sg(}6oTj3wkDk88IBH#c-jWOR189SJ3AYuP_z+fQWW9w7Pbn)uX<*S4^a%+Wg2mu|w)aenfx7xoJe_d=8vC(?``yySJnj|1 zgB4=7a3#6W9LE)rf;E!?!sr~ED+Nc?^V z``N_rkF_=7-0#c8$CffKFG_?X0iT@(QvzyRz9{*z8iHYxEr4Js*qU&U0ZLvbhBzEC z+{Dg;4Tkus1jF@g0R+PfY)$AGEP6w6hYDZs7A9g9t~yks7YB8?P-#cqtL|?H+UrP< z#B%X5wogsVg}Tp2(?kl!huI29!T6vwF^@g25wBE{-|xz30kt@E^EcR;Gmt-1wl(3L+GelWtnM%D_FCm`-LDs_4S$bcXcxUI`J8zFq{#5_N4V;7 zpA>EY(HZuWPf#ApmPb%-wl(1#<>F%1_q0p+!!U$%K3lqJ1ylPzG&m@TP&kLJgHSlj z)`W8k>%?^+g{J(zPf!A_N7BIHzIXU>$<@!6FqM=}97%@#($m26Eo2RAt7 zm%Zv#8k7n|Ft@P9n}ShGK3W9|B@qxWVGAK3ZnQPw9K@*F4xcr|(jyVf2iTc01*5*< zql0O*{GtcFot!&--^&(60Nr70!a1O23XnF|NrWSod)Zkq#iEw4bFj4OywF$KS_qM^ z*qU%oB&rqdLIB^EOSfTqB(nJrsH4$Qn{|{!XApYNAYr?tzSBkC3#dafacY8)WvN@ZbCDUqhbar2i3MHh@ zWNRX%PPaAToRmCgASRZJjat)(Z&w73dLFe|(jvhrc2-RZo}5oGKLdnjk*$x=yxP`; zbDHXV>`?dGc@%TfAiWo}vt~*!XHrPiKQW=67EGw!z}7{mz0lT#b83q!WQ%|ZK_c&A z%QhvF^R^O?CjNgHTLtm|+igua_y1_E>O=ddw#uYC4tgX~xtpCCQz|*#IZYw~^kudn z0_cmjCY%FWCvp$11b!B{;I}&gO!H`0J?14sU=Ol0X9_H5k|#$p0rxw$Faqw^wkDi| z8>!c?6)O~3Qwv%gV%l`HtG;>85>i?qp|hT?htOGLYr;95L0mgZw|6QKd7Qx(Z(40~ zrr<(J1jJ+6LI{Y**qU$;0@^lmNNz9;L=cl~@dpSZd^!P9U<)B2uCz7b9K>+)yjg0W zVJZfZT+hyiDH3%UPMb$aiX&WJz*a-JJlEEQb1q|vtY=KQ(P$*}PIiV&2_2O^79}zX zrnj*r5lnBkHQ^l7D6Ye)`ReqGMUO;8Ut(v*6j4q`T9ZfseSs~A0Qyf`6V3sxz}!b& z=`;9&WN)4O zb(&04l~%JQ5lmyYCY)pHdsp;nY}uw&CTG$n^DM$CY!$@+kF+)6-2aQOq1y+-Fsu;I zU`sb;khA>;1(90tbhZvc;iLrDb0bJ#)%h-cZF za1LU*xU*dC22H=#(@_ZJt?Z1LLQ&U#X=9g^Jc8y;Y&itY8*EL?7n-aweuySD(*Ha= zBXfc#Z@m8>Y&itYzuKB`j%FDaOLD3(5svlcm+UNzcIgvd{AO*khq zu&;BC_Bz2J6<7usEa>pYy>(B^D-P6|T&fa=*EdF z=urM@_Opp2ud+1}m*Kxd7IS?@XE7Y>nAVR7U>JJ-2DWrl&#Tkwq37|Tq!_=D{e0s6 z=h>Q=2k*BFCuyHHTMS>JhA{O0yV%lgy)Pe1y#IFg^NIK0Vr#;=_s8&CLWQ#Lm3+9G zSlf?Dqmjv%*%>lrqHe1RYXp%;@O+Uih2Z&|tqJFNM#B8VzIwt&i$gHKV`s$_jG9hK zg9(KZD!*pyAXNU>)`W8^1I04A=s^Y{gZ0O{>iw)DyVPzDh#nC0^>1k5d_AgY)v?aF;HwY_osWS7=!={Y~iK= z^m-s<5Z7PHemrsg(`-#Rcl|*n?M$8sK)1huE!x!WW9jAxo=BYjT=v6>)338N;oRwi zrABwMs%>agf$03VvBjG@UtKX1Haejc0^rST0R+H5+L~|8-$bJa7|TBlG70WiiEKmd%`ns5$a3FNxv6}2IB z6p9E=VaqqI4(gI4RUJYxgux@(3J8M}ZB005uzD(h+xT0Jy~Reg0RI^jeeFDUW(s8U zbau8(*{H3eA)73bgws>mY6zz%*_v?9X-MA9>9vbNkba*hBaq5-*qJb;qK)Gck_e1v zu|*IV`)y4)hcPU3p7mAnR1D&H6FVEGIMk*oRY_Wnn)=i^;qeBx3c}+bY)v@lF)DeK zypEUgnK(TXsr&~!Gp1D3_MIe^SQ-KIuWTU%%)i*0a1Lfnf`N_G;;ZBIA~zb1aDL9t zkSQE(ww%Z#cz(*3Lh$^ztqJFN@ZFJd4`MO`k*ql0RZlqUrIK?W&T_U00%Mu23Fj~d zr^Gkzw9c0bL~8Q+XZ!b36X?2K?uZ@*mR*qK!*J zO{7|!kWgYKfRNZ`Yr;8+zSpcgg)Q6E^()h}g_+l@T)}=i@%s~PO*r@aYH@2qDe$Hn za0zZDvD*@8^EIU)CbjC?*rizS`NFaTh zErvk)CtDNFA&r&2l2`NK^I*+Zqtw-By=XK7`UyKjrhv4rFp)>_{D>`u;Q79-3Fmm0 zWX^p>p$KDmo2yQ+HW#VH5C%)x3J8P6wkGC-LC2rgFCGL27>W$Gv*nvs2*Lm#OBfu_ zRzMgWV{5`WgGE7I`>L@FLErCU%Qp4B*7p@6h~F<`Kb`n}r>zO+ejlxMtGR21dL+`= z%g&4`4Q&cblSaUF*g^=HYivz82QyHIm67z6I|do-g z6V6>fTKD(LYnG~heY#UlL(n4;$EVntF~y;q;a{yoxPbXl$#}-1seB0K9b1(x?EcIh%G6*3onQ+yA)f+1zgSdWx{dnT~g|;T< z!S%3+o^GNB>LLhTe;ix5Y5CV(FP=$Uzm@%X;`&F}nsDy=A#oL7R#TLWKn$0%GhvEB zTgFI8A}}syiy$yAv^C)#MxyaeT{Sk8h(H)^b|x%gB%VxQG}$5ujGC>9d4SROyQKzf z?9m2wL(Mn>VZ4%^30oNPlL?F&wg>{_Wws`q!&n>pTwY7wfG=frw3*tRG>GUE?5vq0 zQn!ePdls^V5?UW+>mjs0WNX4Xt)Uj2@GE3(xJpDIm+!JOVai2&sU)6EV0?=$g24E? ztqJEahGot(H(VuS5XYkJu6nuJic|8@gvSE53c}<6{$_bB!FxQkp+*#n9FAstz_eOu zGqp+#VQ?f{0by{MtqJFKVxjixfWOi27qKOq`dz#CAmjT;k7qxdc>FwD6V5%pxLx*| z+G(aR3_agqOE>krcJBcSA?3Zwem?PjU~9s;_Xpb7w6voF7=*sRjV;{N_u2qk${?=4 zmHl|)`kQS{ICuTxPJ>=Kfnn(UkFceiI$taJ_)y~gJK4`C-haQX3FqEl3YpI6hsMFk z;G66Wm@-(G-pUclAsFswOCT7&W^2MZhGmfNtR84096|h*odr`6>j{WZ5Fznrwgy7t z54I+plNbaNaIHalvQ-5lgCkCGH3rZ&$c9n~fN{0}0$_`+3FiQoL$2c^4XIhabQHq4 zfSnQ33ZZVd3+F9TvIvrM*)j-{9kwQ%BUvU+ShSmsw$?vQgd>dI>@1kV&|9ZS5Ft@! zYak?iTNBPnEbOKmCiojY|5CPOQ_pMTvMyc!>9L zXY5vbAN$$F}L~?-(_pUxyJ{u^;-@7+CLSD z?aQCo;!S<8^?5=mq4%Dc1}P_xkS6Bwmf59VsZ^Ns^zKz0 zj2^z2oi)=ssFvt5JUkI;Bd)%Xt%tb!d|MN7WjF_E&GkLBtK?aiX|LOE2OfOhx#eja zaIz90rzSg7rku8gWArRe8F~q^8e175Ra1LasSq?H!S|%e9%SYLnFvX(2mz9Mjp^uRH5L*!;^8s5EIvI=3 zLfqfLx0!{BScR+p#>&go_ldK68yWFfTz<;-tZ8vknk*Q#xUlO1t~v$J4|B!Aj1o_HcqzQLA5pxkF`!a0PU`7aUqctYVXwl2IO+RySQTN643i3<74x)`W8~YXz97 za|Fk9s@+;$t2H@k5Yj)hvt|k@>R*SA8dXV9eS$59p!%q-3FoMw@v4ZHtuWncbekF@ zJrd#kfSnmrIO&!xrHfGcE?Whm@-15v&Z&$jDjA>U(BhEE;Gj%?i{l*?W5}_Q+&WvS6N$MgMhOk zN3@GAzv+ z44HySjzEbv!sa%%4#MVETNBRNtSfreBCLsa{6f=jb-I&s#G?_)ON5+0&d!`Er)aY% z^bvA&5?UW&t0A=Rv^C+J*79Pb-f6+dRK)@LblaAWLNecBXT+3@I*u(~Jt<9u$2Zv; z2#@=1O*rQ<8t(NgVDp|PqDLZ=gC6awN1YtyXu1fMzuLMqp1}UItqJE;hWvIXsAWvu zBqNZY7%k4hVlL?aDY!L)W+17+}B)Ioh z%$LDxNLFRZN`P?wo}DREIMJ#oG+}CsV?rMx^isAGLa1kJ!a1S7-|hS`Tec~K(Rev> z0=~pYmn1*Pel&6V`)o}(cltW<@xgG;vR&{xnfldm)Ezk%1Icbp9U)WhQWtCi>$yP~F{fRAxpnBNWgmY9Q z!U=G-aiQJubVOPlqB;B&SHqTM7a*;QAUTvRf*{#!Yr;8_&C_t@H|zo^bfEUg*DMUI z+pvxZpD5JkWBMmWVCS2V0k(RU*D8S zufoozDZS`~h)z#aOyEtkB@uWfTNBRVZG?k6ZG)QECmn*jg`GuHaM7k}0GF?pV0#H$ z5W#k%tqJGYHpGt5if`&Yqy_OprKzhY~`x!;%6>(`2IMcVaID0=^oZ26|%R~Ow2 z?<*w)z(efE695m`ns5$aG|YDPmMAR}aUAj(SH14!WT^INLgiq#3PNRrtqJE;MpW)I zW~j9|WO6n;E2dQ>ITA{%B1q0;iy%l&w>9A$$v~YnQy7E*rr5$w0VLlLQUmdOk^OAq z_p5D9%!A)y@+SRy7^sUN^!tn1!ma!+9!UIt1N+&;?=Q49;oR>_g_FfJXWI91F#7*J z>9CM!C<}F3L0AJSAppNyV>GR{l79X$sg(<-v2WD z>BReAv^C+}`|?}5Sv$p(5lG=db|y?IBvZ|s@Fli*Qwqr*ZK#8I{|oG=6Yu}0tqJGeUj%oHr(G{Y(C@!x z%Qp3Ua=VaFK%D+7_M?f@e_?CFxzk6*h3>^ht=Xv4V zZD{A`FbH8hgDu zNogWHp2OBacs$G2gmWG%G4Fx6Z9MBxNk%-9c`G|hreroJ<_oDhg6B?DZim+Xw0;yE-`V=^=nPCsXBA)J0{YhqqFiR1C`ojpd z(Af`%t^DF{&HY*&g7|lKR!l)8H?E~s5hR~wiy%loZEM0gk|k|CRh6y_Q7B^g8C$+7 zhU6AorGxjQAI0vw_y{DA*GKhi^!RYC(`mf2CLG)0Ik0KvV{;lud+4a9M5`mfY|)k z?$&A^G;AGjvZ~dU+*F9_Gwke{qDnSpc}fYePqF0?V4t)#F<-#o?PCCprb1vpW@pbH z7}Y?4{g5q(0Q;V;3FpApUhB6S;lyv^N}tqfV@?``HS{=FUFhUh9oZ@gswHeO1l548 z2_2Qiw^ZWKvEau+EKI~ITz!sZ@vdUItDTPs!>~l0#`d6T^<5tJR!{+X6Zo0|d@)(P<>c&sA%Klbtq$BVt-a`GON{=1I@=AV{#PIAmHrpEQ1w$z z@H~~Ri{N>Zt%2{fn&$=cHB%Qt^AO6zHG5%u0Zie$LL6DJ8YF zAf}`#B#eH_)k76U0_%ykCY-|>7ZnXYQPS9BG%TAvIU;-}JHw_3 zwKJNf6X7cE(IWr3c!X5(ufw*lGx=owg=)QWnc(@k$%6 z_*j^TRp3gSg^5_j{X?Ryv@j8?m|A9OB3AJPTNAO03#19J@~TA(7F;*8welMH_o|u7 z>+ql3@t-%~KW~)(gyll0=uTFShOpe_w@Tis&DKf%DYJA z`bYM&Naea+n#lF!)?8QGdU9x2ce2(fb>VXB^t&e+fv)^4J9~z%>`7Mx8G`;io}DAp!mf@=LZuWHQE;p~S?DJ7u(o-K=jda11m=b%=ruVRMFX7spCW(oxMF?P00flbU-zoO|PJS#__ zeV8qcK>MJr3Fpv86|~H6c59J{>f7whn4&ty09B}yQ2GX26`^#WtqJFpmP-)bdQp6% zINg6sMRZO*rSXG$_HS1Tbz9|m1 z{VEHGQbvH>!j?mTyu{Xob09;VMxp7$0d{>pAsK;KKE%$1DHe5JA`44GA0hJrwjx62 zy|yOig-m8wOps9#NapM8Ow17(C4-Q;m#v79`Kql6=VS)N3CVO941*2 zjFdOoEuKxv!Le)^q#PVAO<1fV*iLpY#c6@Ib{}>ej2^y(oi)?ymN(h~4^Kqeh^sGR z>mjawyse42u%Cyt=6ZsAmajy%*uqU&#;WqT3bs z+_2wZVIo%Hs^>Pi(}Po(`_k>G3dDLldXB69+m@W!x=<4-8N+NPq{1$hCg!mVxA2OT zZ!48!)E2+(?A(}Eyu6+Q`gP(8`i455t%Z2@7+Vu@AwLS?%yo}#F?{uBT03JLhG7Z6 zoGsmyL|*?nB!R+6Ilq*xhtRm#*2Fx}Xctb>KEbrulVM0>4_o?N(U2ktjW$~kq0zK8 z;he_UWKb9P+?9Q=6y$CY(WIb{kA5Y z16cyOZh88hh*2maIp{oBz2LkpPT>nii6cz@YU|K=8Q{;hCY&=_Jr&eTg;ry4u~CKZ zLclc*`hn)m6v*l*XRJ=_-?U|kCftr-t0Q%3+}4D1ZbNeSgV!zw`iB@35lHC?>`a(8 zaE2r^2JzI{*)YYEcgQAe@RA}4pWSR#giqPl zgmXT46iS?0n-cd2r4i|oNa^p{nK7kw{47mft5K83)lez{^-{Jl0;*?g!a1lhSw`Vj zY3Mi9>4_*Bjj%q<&X6grSyl&#T!QO^Y-t47`)o}($AvGYjH4dO2t@P^b|y>_nY@&8 zHssvL7DeFPV{5`WoWZGXwOY__t5JanMU405Wlvti0)fY)v@lBR_T#@+rukk@oV*N`SO(VQ0#eR^A;f;g~2vOGzfsUcwefpxtO| z!a20DGJHH2zC2kfG+S^{mp)lXqY>B#*cmbfHcOA2$R)Vm%a%rP-C=9OIj$v{Q+81( z!nv0%-xSU)Q>scFVe(bBBEsY=wkGC-Nyne|wLSze!BAxKN4ESqV}ddWlZV)f2$KhF zO*m(=D5&eV;fWAramWS#pZeND0^x8lTMgl`!PbOx4x_bhHFpVBk3>3Wvom8_Q)ZcY z)1(qmXR?J6P^a6Pa1Lsq4hur*UNr_Glqt4wQz*0SJdm;oiXvMML2;7K( zRk^BPpYD{?Q1nQ|^I~>pO!3T8XEdn<)D3K51k?*{P0SA{aj+~GR3Z{Vy@#EdIRuqZ zLqNTYEsTJAyR8Z5pa!6rYDdm62%+4~7H$eEsDTd zV{5`aoJ3=tI_5r z;Y_kKF;{S6B?L}^EsDUo($<7?IBR1D@Xg;^vr(s?9mq+8$gXE+%@oe+@hQf;Ucg(8>VvgMm{nPqiAi6cyY z#a2X^{KD3Rb0!PZi=dUux2yX&)?MhTQ$5Rl9Bn$#S@1jq$@?}(xQQtiNXYyY6r z!Bq!1?^9lJg^6VnM5An31ktdq3FnBGiIX*L_z1SvA5MfLoJX;B+0gcOnF@XKS7#1Q|>8uVZT=2wrV#!a0IL@m&jjSf~Q= zQ6Z=0*i#vG^_9g{H+K@8;%E6DbeBVk;r#;TN_hoRg5p(_m?{)~FZ4OMlXn%)QJM z$Y|X~^WEtdXKZw}1lVe}ECOuI)`W9lOWWQapAJ>yU_^5oI|HWGC$FCu=^{K%VQV2g z9%*Yr=V8$r#trIX`3WKm6S0bqpDC)0g^5_j?Y1Uj6|a>hJpR`Sgo^HDW$Pk%Sr*{G zk9pqAbu(KlH^blMU6otlpQ~o({u52Bn*04)ccNKsbjF)rakn>}9&uJy45*j)bJztX zyWEQ$_J+KAvD|2h6aO>iU8Gz;i~TH8uJ=n5xvt!r>r2}=@6fL9WDO4IRrRZyk`dU1 z+|JIPp(}gCN3>%V#I3JoKbN@mRkkMLD)Mq9GuQX|Z;6YtEYAxH-A+(#k0X=RsJ*AB zy)vB^DSd{WRZ~h^jzCe{G|8xf4(C6`RzkRa($<7AH|x*Dt=K7R%{cTD;by>&)=46< z2(@dcWTGYjBfn0dD-RPy2;-w+3g5wX2qMQ=#9N zTs$`&Q}Pz*sUyYWT_h_o)`p2Z)ueSBU`r!R7TTJKYsp^_>s+^PD5jR7oIhWlJKE9${<3Ii!^lB-lk&II+_>G5y-fh(}PDva@6gN^O$N z1r;eJs4ixUBB(C3HQ^l9+G(%bZU zCfJ&6VFX*v)`W9x%bVpu+<`F-#a8Pmq@xhhE7=(_g`{@I=#WzS2%8zU9>V5jwkDjj z85X`M1f{~H{+W|h43hZ-I~%5C)CMv_CZUdi`6ycs0rMeS6VAbmNH7(zSm^lrM}xIE zg!5f?R!rfjg@a5ukwSv!TWmoD&)02DILEU|;t4zBsn?O#!u3mt;1=z4)jL=Bo@Ii| z)=nrcV5=h(|L<>>A{;8~!1vN<-#i(Cl#XWm!L)j*TLcNESR0{oBwG!ka+s|N=XGnD zpwesw`rUzvaAa~3I}4^v)aPunn4mI(E}x(JUdTMOY4*qU(81CEQe3$oi9r04#7sTibk8#^1ORPwrQNp%Fwt!z02%+0nY zbT9|B@`RIt7A9g9IIV7BB3}Q(H4_UHv5Gym6Qkk!*D>?E{lOJMyHBAN8iBCk13rEd2}e3 z7qUeVF3*!DEIJIoB#T>X8fnQ!!Wx-pk-b7Mc=&N`;^IOu%d!N&|j0emr2W7;`|ISuKocvi^ z6LDMfY2-B5y*1%_ZITy*kjDSAh0iICj2kF^##TgV{KVFTa~ezFlU7AfUm=M?k;%$S zUG<8irTW|&qV$nUGRhW3ungOpaE@igv|smIUdP9YT&=#4Sj6-wc6Lmwj`~(benY0~ zCAdyvizB$U+nR8WYYg@_`qhHJ$E(Uc6&f!ZjliD9&X6gvXz41qUJ=EF*pu1n2(ewZ zCY%%7fcfwEa1CDzKDOlVX~0@J*$$JR3<+Mx&Y&s5Q^PAS@~d2K4hYF>*(wRiy|yNt zlU&s5*3(RN?-Dxo^U)5JD!C5U$Sj-C9PE#!P;H!`O+kw`3jDykPr?InUT2D{P>~dr|d?J!c zJbwyX9`XDmZB4|LfJ#i(78RD$R^YG`FgHGU8gKM>pN-_d5 zy_KB_Q%sLEuTcrP1k{_@0tu)$*qU$-N_8Aln;ldj0{T2#yeXjUxv0#yR9?68KiJ|3 zoPV`7p~JE06UN<2?4wwih*h}iR*otnJLQUgdW5b=Vww3P+ouOmX2k1T9t_1D2NRlH zQlTGWiz8*`0cm0$dz8s`GgViU5$OAapXjO^|5)0f_V@inzjk#4TPMM=*49K^X;w)S zbAFwIYC==2R3LIXlP%t~j-0~gl-InT&elj6ooZ`BXJpYf#?32MSPK)e3RlhR$a&SE zt z+I?fG7%vEbh_b;Lb3k-i0wtw z5)vK9AtfY~MQZE!*;+`U_>MF&kBw!rmzk=F$q01y;>%riKh=fXiN%MoGNP;d_BIb< z%OYO?t1Y;=68ss#{eNg9k74KEv<5_%spQmviM-bFD7GYm};AkK4Tsf+s%~YCp5`~!(O&P!l7epBCaUcAj-LJL6?;p zwV)2`>e`ZBA{;T@&d!1bEfQ0H)EHR1p@L@Y@r0?Cv8nQ2RR(H3sb(=4&W}o^b08!gUEi& z&W0(n+<|wxRZNN{oPNkwM>u`Y)`W9Tn;`4;MqRZxCAeF<-U;g6MptVi`z1t*L%Up! z3C=RBczv=$pe|vHB~S-!O*n_TM)$<-Ze1HXXD30B6YPwcR>zF(4_R-&O!)-iacr3c z;Z|D{&Jk|VebDq^qh=@2diVLs5as3U44R@m!>&$7*&sMCWlJSEFSa$|9OwU|?#u(^ zD9Zkyn9ZHtTm+IpAP~?XvLNDtf{5I5N)Rt_oSo_2?al1WFh>p)6%_?pKs1Q=eW56V zfOy~fdZUPn!Ykg_>+5~s`K#`yd%CN>)wA_H+1l5ieLQ zk%>|}9IG2Gt5HYbwy|At1(zNj4+AXvPogduXtmk;fL2p#!aJ>fT0ym2QJW8U)OPcw z?O|s;*C*9`GyN|mmak?z>dNx*Zb{hJ9YA{>TP~p8B{gB96%4mzBQ`W3ATW`spb;B^ ziA)8pRu-7ZRM0*g0uz}E+GIswB2)1->4qYa3UzG6gJ*Z!oR!J0743pkBM#~9m-8$3 z(`wJp$ieCM%lSF`UXWZriA~JnI9GmK9R0gM?l=i*>Xw{4Gw%Qp%4zn?S;)RGgmpk_ zBFm7uv56UFw7ZM9v# z+T}2MxlEMfQmaGQYJlNpsR{24)9q6z5OYfEbc1RwXj%E`*Pd@)OYF{JyXwl$JozJG zmncSVXR(z4x6`F2ymQ;z$i3q8TJ$Ter?qTjyXnfz+^s5M7OO>KZMF^|)|8rXCMI4W z1*frSzgbY5^wHV=xT0AJO(6ogj(v9kvMV+r=oN_Ly!3c#%}-FjL;6dRRzvaqFxx}d z`1Za`<8RDxUSSfkY-^m`*^(gWw@QO9U2`^2X*7+)aHxo)d=J|t*C-#7zG?Q4@^GvV zLVY(|7lis-QWIH&b0^W7>92|_)bgLna;NvP${60kYGO6#Nxr(uy_vz&umat{?Vr-1 zzgWHUk(+;$n()qTF#1pzb)rXG(Gh>zuY^%Yt{mu%SnuQgG{khpfNOuYKuEKFr6#=N z+N)V^wFCMdtiCO_8k413ST{fA7hXjOPhq>~+CcWk!>2)5k0ue-5W2hq#D1{Fp6#COE_bfWr^iT7biUOHDX)5Wg5N zJSP~#m*QS2;aGH@ukL^ElnP5)q{@KEe6}1Q@_^WcU<5+?GCXZ3;$XH@u8(|gAI#zy z4;OSG9>^92k=`UVkv;nx3D!)%3mcsT{hdnxsirhIgYAkdqTap((;@10cOWUAz!nFn zPL`T5Q3-zev(5vhrocp|!dK^E3;t-uMf7-F%nqOHQ_!V0e<4nJi(?#iJzan_a_^n?sMk1YL& zEfe_MCpD2}%)Nworh6dMb_$m~*;mKAU#BohmZDDKLbfPCG9WeK9SOTrILvm%HIMpr z3N=KdiabJmDZNp;q=8?C7~Z9qWyc zoXM1z;YdvaG5dV{bhZq54xSR5n8jbY73FGGO+#-7t!?FK^K|o5B~(+ypUZaDHR9%H zxHd_}U*eNxUCBZhLIMY95Ly`aLH(h<3RD0MMq|8d<^cJ==uAF*%`zHMl zlT%J7P~N2}@(GMz@JAp;k$-mOsT;C%=D7s7V1z%h;-b(qgFz@012pN_F*%Wk53Oh|$B? zuDB+Y`68w#6fq)|faq|xC_r?m)P#3LQ6hEKH2m#BI5yrf7@3s>>B($oTp>-9NO_$= z>0Gudp!7tk3GbBF>y)DXWh!b1L$$?>G4r$Eg+%XJY$sjmJ-ql;X}FJmAi0CB5J+~V zCcKjz9BFjx)wXs2Nu!QeV>VV!4q$a%6Tb3;Ti>F-cugQ89ky`pxUp@HV@m4lcw4{YJ z0-r@pP1nw!Gj~q(zYEgg!)#vse5S3Q3GbkWg1UM|R@)!Y?}CoXtRz^6v7K>EtA4#i zLnq*R2wN4T)4@^`-YG2!+MQ4>JAorG^A$vA3)=}-I=$TD*`fSNKJSr zGo-R7KDnr5c@s7(iO_boGp>aC<&U8gD0SGXfYOCh6W%Ee#w$p|Mrkyx!El9MM~q&> zcEy!ZzrMMy1Q5NFEea4_D>dOA(W=<_Xy7l}jL4EqwhIW>$JtJ~!aB6ii?2m55c>#Q z84&xR)P#3p^V9-0y}uBb5R)IVWxF!jw-1v@5kR<)Ed?OlD>dOALcBd{G8o$~l{?mc zT+u~@YT?s-b-w$(CPk|oExLi;09zf-_+iXW&0h(+0^PjN#`T#te zY>mKkQfk6G&%L!+G{b0zDLc--R}#(Z*lxPgd_=zvO8$v7JOG%x*b)KeHBuAaVXjjz zVr_@a>~kG~{4m=^SCHna;I{U*vxynulECUS(cVyktX0Uhph!T{PRDS z!@_#~qNtmqzf|{1iN=H25pc~EbNNr6Myd>e9LSafK=zlK@SYe$iQD$MXhtRBIgRa% zE1rIXyM`2?bP8J)P&!d+!aJqG)O*XIp;1SS8f;fw8TES;(^UeZ8e0?~3Z*8zBO0hf zvqnV(lCcM*F5`6`1yT5iOb1s7hJQ&+&m)xrjn@xEGMu90hZ&WCcI-=p&UDRdBWB{2dwjv;NxzvPr zGK(Y6ZH-%2PF&v4cEOcPzs4<70a)J076e${BQ@b2%aC%MmPT8eHnWldeVgr!E1-S@ zF@{c{^i8%Zp!9XA3Gb8^DDTzUCHN*irz(I;jco5Qdb?>NE9Zvr)(OMrI|UIiBr|Yl`%3 zxOAOB>2YjTK6FwN$?A-UT6j@ld==|+9kCl@yXeZU-?!*u^?C?8)c3)0f)=jT7bi)QWM@eEQ<%ytS>lv6>+(d?T9Ov zev7{}jlkyywkF_nz0`zvJ}b%hFk1O#TU2DJCsKE^U2-L5p3{&Y(LuF<>khUs!1WcW z3GcX;QUEM(rCJRk`U~3)S48H~TZo7h0-QgxB>|k@Nlkc%vxhoayAsW0Yjiu!ZpXI3 z$bJdo+WQ&4`r(K5`J!Oe4BXbTwE?#^QWLYnt=VWrD=N!1`yMF~H@t+n9m{siHO;2s zhJt|G(QIwN?I@`U@7$KF)s~%bbZ4n)8$X3wqE%tL<4UXF7HpsBATG79Ctp}JqTx!BQlYvfX zj$Bg_u_&>HyRzsv4jO9$4(GGA0EeeaO?c<9SEr@kQ{sbcqoI+K+S|Jv9YL%&UWHc? ztrxJ}bES1epAmv0Dhz*||(~ zDv-cLred0NnWP8qPIJ=t7(0BfkH(lLowf3<6jPEL21w033sR{oC`k(QA1zY&; z5wM@`eHmK?5V%xo!kK{hY_jF2Mmx-O)hDM${Uo(_r2gs#wbXLn$o9t-j5#-)Qp3~; zt>q1DLBQzx*u*TJUbZY<$Wu}SYkJuADvJ3XY!_W)Zf;^{jrp*q765#OtquTuQEDPf zlFt*vnLfL0Q0-|HZK~gnCqL+@j5=cVN46`jtjunqot3T^VEv9Q4`BVT)P#3fD`Mw! zlT~zHSWU>*mVNc)&9g=NAj_);YHQdEf!Zpm3GdXFcg9tlMRS?USfGubY=Vly|Un+{v58^B$_ zmI&aUCN<$5T#`+yu_+bjT2@QYUdVRG6&fekGF5==61G4<_8h4R6Pe&`H+x~Gj;O#y zroz_?^Olp&I9E^dfw+hskz3f|bA3eYFU;|+;W@a8tpc8d_exE8C$Kp3xpewjXP&HM z$dwa|@338PWns>!p*A+t1wig%O93F?kecugWY0=1P+RX#hV8cc0W1YOXdRqRr|2R= z^$)gluBgln3J6t!V&HZ^TNiNqv($ulZp$m}ZKY15;H3^~iPXjsU+)O^VGmOyAlkqd z1c=s4O?XGNbS$U`t*}yRs`Ki#y;ijvLURJ!4c8-XBcfL z(GCh4KeLipO|hMEWo6E9BUXk&;Bz@!5Ab=B)WmG@=`?GW5yym2uafw@kL}D%@ac&F zKJQ`c0Y2}Nn()rYzTi#!jj~@ttiH*1%$1e70YZ{XwiSFp?CWe*K zW2*r&drD1sC$m(!P5rx|u6OOU8shRewi~V)WAA|HwE>o6*kS<7qopRiV_Da8J>F!n zye;~4Z&{(x=Q=`GWxME#Y}h)%rC2urEVHEnz-LHJcn7#pO}L0o_R({yS4wa$W6O7i zvyT-{stHhB$`%4Bo-Z}w9R)2-!0wTbrtm64bpzWyS5)Q%Z0an;OBXDf0p0a%VSw)K zQWM_M?P&=Ej1(1JMBwgVJLd|{KJr?i7`T0ftqZt)QEI|FH_JK(?ZwGbPqhBXcFC2N zx$z67nZpW)-?1eDsQ;Ck@D6H_0$_b-GU^D>+N!Uvxw$Ge4=B|KS+s^N2jHxdn(z)M z$rAMiqZ3RvYw8e4{U^&(PmqpgyEGG7l-CKMj$%s!P>+zBFhL1EyJuhScbyw0fWSni zVy@Igrs5}Cc3Z^{V-w|xSI(O==i*(PCtj`ozF^nHYw54o(_e3-zup}Gl@N(kbVnu* zQDucQX(x)NXNu=AuWs%PCcDFNtFWFF?W)PNFnU8L#~<4Jjps!^^TLw_Ah ze;r4E9UuQ?fBqvEr+1@$^cxvgU zcBUeE;I2`3qx-&rec!~yD|;qhD>N~+xE8iMrBF4+37}WSmS!Bo-nj>Tcr0kdCo^v( zFglmMS5m+}&-T_8)IOU?(xEoF>6R-QxzDhL0J%>}O?*QALkfo{Wcz3_*gmI3t(1+K zaq0Q`rKIv#Y)5ww+IA9w(0XUHQ^oF`aHDppwvp6r`y=C#&b`!w5CS8N+>zidi!QL=r4tpvDzRBFPRn|NGe`5Eym z^G5ZpuY-r9KrQ9e&)ELB!dY#BgQ@gm_Wc3L4`LIuI6SfF#2wA^%IXkN{UtanrqC`L z_0<@1rD0}Cf&z1sFet^_O_Tc}UKot$hNk@woom}qPaP*1^$3xf- zxIXCxIC}R790#+d0FDPrO*rEaw?m6h4jNnHO&;|8$dwb3v)Mkl5=l2sG!i5e21w3e zYXKxrh)v95JG7+I*ru8xH7cx+KTqW=DAX6S-E$3fx?_7$sE2bRA?kIuEQtD6sfjER z#tGa^w@Yd~J;hT}$HKJ29eO@k>Iv4hY?oYNrF(BUz)B?pvzM{e0kf;6CT5jcvk}%i z#s`Qzv!tGweUR;v4`zuZV0If@9WZ;p)P#3t3+ml5wMs}&uC$Qo+{+g4N++GnQJ7JQ zI6(4Uwj@CEZK(nfu7J`B?uBV%=mAs{Y-xaMOlrbA zs>PjFx!#V>UeuE(S5APgVY}c8C|w9w45%j&*j&X{1#GU6n()qMN%RKPP?L!!jgEA_ zg6Q1JcEXj;w6CZ+k$~rBwk*JNqtt|nC!6c%JgIBxNVir~XS_sHhLYJrG)}>{HId1g zW+YEMbToKYs~XnT?&hbpLSs_hlu<_y%}?2(b$w{kb>c=!>jL3v`4L+WJT3RdCT4LM zX<4OQtCU97%)ds<$WpzELcVa^SLZ$5$GTt0hc&Sf{{gl*i2q!vi7Y+-MF?kpKq&`G z9S0p6$EmN($A;<&*&%F~T(czIZPFPT$pUVh*$RQ%K~fXmxvhy0Kr6+AGx5sFQZ;Ig zwRI>3*ATz6*si(qJIek-rnPdzcKN{Ybhb)hc&gNdcZPeYE_Rf0X*+3oE3jWe1lw%K zTnRqPg`ibD0Bo{F0>DYB2@{~8dvdpGbt&5P6Dlx~sqodqS-hngRJMeZM;L8)uAF>$ z3pi#SCbC@kFtMBI?#znVbv0Y18q%@h`Y$3@6RV%I9dcz=(El`8 z#3+4ht*MbbUR|iZ3I6hqE1YO}B!cOO|eh4gknQ*&+etgQX_C zgIp4-Qx)Sm&~xaD`PchL*v~X#qS>WXl3PXG%?&cm%z|tYb;N7=ejQ z#WWpD@g%q{&GGXra}s;S^nkpO9Y)s&q+k+U@BZ*mT*8(D55;q&CY*7IM=}@2Fw9RaVJZq>yp62{V7xgtF^fI?Wg~%l;MF$Rt#Z}q;_Fou>@TogbPaYv z+n5A6ov$=0B^qrshOSUM0 z^E0Ul?{M;S*;w-vS5C}wmp;uToKOkrG{n{h#Fk1;%qlU~?8Jw&U2;vXf|oE0v7)(& zN3gX4vBRV$yc3%r*0+_$^*64hjGDctvZcF{Dfobyk|AM`Kj*Ra0F5nD6W(b|GcECA zws2P#>5fvyP({kZJ}L1cwj4mQU24KRilJ7c6R(+9TT=$3M)%FEq*Qqm+Zk6p>Bffz zcnqnKGp}b015mG#n(z+Fm?5h5;*2`N^jWqmu9ynO($g%OX71>x*t&qy$E7B`QxZ&9 z`~}+?S5DKU({z&+f65jHpnfDZVS*A2^<<+(^hPW&k*V-CTBK(C9HDlBH>Mje>J?Ke zuiNUYqhByu)Vn{lv3s(mz(cWGYQj5?m1FH%I8r(yo*Xi&CQBLvy_R|cbPU@i*BpM3 z{iPX&0;xx{H36wdN=65 zI?PH!^cA)#h9~Vzs8`tBY=Y)k0P|xqxgHTNWT& zE;ZpDS<xLSPa_owbl%L?19aXXHQ}93(v66mS3g^7 zkg22gnby1O_DhJ>7ub%uvg+H@FysQV&#`3zvQJA*ct^IHe#oL2sJG0@IHr~L3Ig_P zwp*^ijOFh7511|%c>R*C3V8iYYQj6OwHdE=Fjkmm#g`Gem6N_Y^~Q)3k~3rjz9F_W zz_(Ot!aKg+Z%~mWe$D{jVYW)(dx+G8cfLdIpwn#@ebF{5iQ1Fc&bU%5_`YMP06^!k z1p%P5q$a!r8YJ)255~rvHWpNKU` z_DhJ>>)4LDvMLy7w#o%$uV%{vWY8P0YEkvf#^ zmTPJij6B<<0IvtLRROODNlkd?wcZRt(f96t7ZSZEvYm9LXY4kXzK->g4iL{|ivx(K zNlkc%xHgM~@ljIyVJp6j0Cw39x&kctmRcwq@U_^|0AE9D!aKg+ua@mwY8oS|BT%nm zyW$FJnqRbnZ>g_f>j641mzwZSXVvyr*ijq8sVS`KNV5JZ)ph}K`Y78eS5C${rnE)0 zNQG4T5L*<$`he7gcUYUU2wc>t2c^kwtrIqDp_;x`K+ox45Y8X4-FC%UFy83m4DkJL zwo2gpJ*f$2zT(ZoR-WDNoTN^gKB?Ad?mVMW4U83PmU@CWzv=5OKws-b8%MT>Q74dk z09z7}`gcrf7IzX;hh&7cYN@G~2pTg=v>J-}1KAF`Cd?7qm$Sl{59ifF{5P@XLHsvL zO=Ov}AvQ5F)BA}nPFy#4&(D<;q$jXlaD}9I+oy%3lmeoY+2R1v2~rc@5e-DUI_rH2 zDk3m-ws2Q4`tBgbU}Am1WGh=2Fd3Jc@Xll<{pPA8vbL97)l#cl*8y4TiPp>5F1gau zCw>&uLgj$&YPLu~HzhS;q7!WPk?j{kzk31`nTl!l3laBFR%~gi-I~g^bAr*J6~KYh zSWVB*r`U0IeSQwI{#JrYcvwEpRs;{rM`9DR*gcuQBTy^Y^ps7?D9k@%JLMW?d!uTG zd6)!3uhp?s3bgsl=fXZgJE}(Ld)P#R3J1tbGjHsN&mOjH&NDNRpovjO~oGLZp zoyx);m7uN`0qWV(D2CcLv*n&b_(X<7|2`Y_uKS4IzY>H-hv#R8|>+3JAPtx^;IIVF#$ zg;TzUIDMb(#td`HO9D>!u+;&lyQL<)b6T`5Xm!Ghx$R9>OoZmOeD$aGFS&)kL_L{+ zXAWB$;Q8l&ES|l~olYwpY1699dU-Nhsi4l1Dp$>}L*FY2;)B?ca!s@2+?vCF&H&c~ z*=m97{!$a(({6}D(N*7pt6{}hp=nkUz0=svxY9e4L$4@y@_$Vih5sQ$s02dM6sn(&TlNwn2ru%n{}_HnucQI2DL=(?`(Hmsp;Ev6~0NC zqJi08*$RQ#pQI+dGmEn+`PO5jW~nD``*nPE*BRNS3kP!hur&g?by5@F$;J5;&#AI; zgIc0?Jlh=)*_Ia#%pS*92+WR=n()qSWi6VVVrQWM@e4JNLu4I{he7?V*)tnOsH;>yZ#h+P*9wC-T* z16p5^n($6*Ali*sZ|tau*!+bp+?9>vH>y}3Ao(L(79jbZ)P#2=tJF4+L9L{o%BTmS zev8cSyS57m*WO)UeRcb~DHvL}NC$Xp*&+en8mS5I@CNFYhOvs8iU`oLY~eGUPq92e zax_~OAUR5EViu6J8s^H-dQ?P6Ds161jU>_nNS?`-1xPNCn(&TfNuw2xh4oUcVGN4ry_-)tET4q;}}n`v-FOdolsfqu9~_ z&%>oAyyJ;>-!8RjLx$vEZ>2?Q$t#nuH>J}Wihol3USQ=2H36%(7^ zupMw^Gq5SF9*$!_|HHl^1pOCM6W)V9L|bb`(~xluhFM7fR&V#!n|7RpY)FPIS-}C;$CcKAS+s{D@yH-P?e>&R@PUw%o z&_9KJLkRtoq$a$Fe%ejXp39cb3HQS=+|Op;5W>AvYGM|`jk{#br@gf3>3$1aIw#ym zVz}SPz9EGBby5@F!#!_D&G8J!Wp`exFnd@RMn9?*UeLZe1@$G zczjZ7VitIGfrly~&ws_1J<~jt6yWi5wkqK96R8RBJQgMsjrI5FUMX=|zQb2{G0g>L<$=6WmAI5gWH6^0ZKLSI4IQxbW`a`8AyoY|;twf*9 zmd*+HBQf0PvTq3CexlUGEQH(A*U9fj`7E|{PPiY1;oiZ%A%wdtHQ_zn^L9dCCn}+a zqJtLU}k-t*RoXsk5@@e%mNSS>+J0Hbw16OJ<~jFeVtFRRRNEWN==wN z1pBjRTfEcW;Q|wxifOiZpO{!UXU@Tw?7Dc@=7}p+zkPhm#8v9A3wBL3iYl&A^;0X# z&1P*Ut!yf(^YV3$G$%FJQapip)M?tVlDUdmdXDpu1RV!aKTEbgtJ#xuTX8s^7Fq zdz(J;W4nNWy@TzPE3gMybIhU?SiP0430S>JYQj6KO_g%366ICU+NRC~YSc?Xy&X)B zs6Bx%?3U{twK=fScXav}MDt5*w_R!47f!kO0(k$6EfVm4R%*gK-c@l(T9fUd$JIzh z%LN4QH*BX|;n`EsA_Q3d4_gzk`i0blcUJq1hAq`vHd<;Y?ou;QFKsFtJK@&xM!jJ) zp#3i;eycC?^=4pStld{VkX*r52P6ljCcKl}V=QR2)ns<{LKmvXx)dE!XtbjCONiVf z*^aqpo_!R}suqYH$yNo#9x65Ao!CA`4C2FMOL6bO+!nw8r9|-QY)4%Q+HzQ@xGFo6p;kS?Y<_!`Lpl=G6nOtsLqEP=~W60jNWzCcJ}M-wS|xZq>O4 zK|AXE#s8r59(Yf#{mR z0MWrx6W$T!Rx~3avyuR9VLRgrXp^<&%T_nX3W3krY(2o|45^9P;&V}>9u)HFRT7`= zY-eVIPfrB!>9F+xp9`fXyz^OoQP656pPKETn5|b3sn@XGawTQ&L)f$eu2-@}0j_JM zCcNWXUanS4%}@<%Oq52ubz^%^s3lAvXS?HyN$)u{nw@YmyeOVMG;9h5P9I^b0!|;4 zn()qP9=!U-CB)-LY}u|n)@eULjdrQ4=54iyBQX&F``9WV{`X2vc#r>}nsw3Yl(sio zTLW{=gHcCJ7GCVDcdd5{dQ5bIKxcrh2k6X|n($6%bu+9r)DnkASnsq;od)&{tXB}J zL)dP)W{=*QsqfE}JvOnxYcpFF@H$9p!aJ`$R4k%TRS~Lcxltn3(kn0@h}`+QCN@+;C8Wv0k~_VCcJ|iq}ThTh145G zqmD3rnC*%yCcUT8!=wuYI=8d+0G(Tk=JQg=&DIFs5eU8%hA>~`m*R>Rn1wO2}5<~_$(H(j4MmVS;OPDKHXIcy~W zPH?Ee$B_ zD>ad&#NM%q8OOEG#Fb1!svF$;Tt@&;VY}!GP(Sr@T7a1YK=MSkMj-iksR{2ShvLz& za`f8Nf$<2ll9<)l&bTr&dUMI#wzQ?AfxAp9fC|~d0Mw||gm+Lhg1R#vTa6Y$>b(W1 zB~&kEyW@)LP;ETVnqJMESYUM}TN$u=iPVI5R!eEkZoEQA=cLsTqg&W+xH2+cY0Y%X ziv&J5u~h+|_exE8=d(C*y=|PZlPf1S-(kDp%Estx7qC$Rfy`ZOO+e-wQWM_E3`FY? z^^~C^;_(l*a917&^l#5%S%Bhxwj4n5XQ>JADE0}}0i6!TmJE*SDjJ^kAuQ{e?B@+ox$_Lqs;7PenYQj6CWU^+wmmbWuH7kkG zeQal33F%X94MIJM0O($}AOQ4TsR{3ZR`fpoM_-lOlg6TAtR_eU&-2wK)!*)vpk%@Z z=mbu4*_wdUzy5PMMUg1}?!N-6iPL6wOk9(xh!Y3_P6x3y0jC3`CcNj48&VQA47i~OMe|le;ptHl@N(kbVnw>{s46nOwo79 z!tYw*PrZZ3oqg)YbJD}qwTFM=3?+{q`fH45&fw7_J=-lZfZ%n zUKwx1i^6w}Z%MqbtM99HudCSiN=$@36IaA0ilex0alr=ODKrQF7O1zspx)UP1Djf? z{<9UTZ5c1vb>`ISa<|hcH^al4JA=vYVPU-*?AZ09UFE4|VZBo8Rs-5=LTwm+n-W@{ zS~OYSQ4N}%@m){&xBB1I!b(|9-&S8d&YT(;t2IV;T|Tv7JgioOy85rd#73`RQN24k z610NquG3U(J7Gud4%w*JcJ8`tYKaGb^{-A_K$zYk#q=Rd-$RFX&EkUQ%KCuU22Q@_=Kn*_H%dhL>Sqei2m5B{s<;) ziJ64({OGFaDL6BxdM4Tben0zGbM`!s!a7}^-^0Eap4^{izGcsh_cpnfPHg_^4!=&VU%Zn zZp^~tYvp#^+~c-aN>Sa+cFZ-Zi#An~`$IGjV&4>^d4SYJ*68g=KxX{uUVM^T#uWYV z7|(UCoML}E+XvU!FWD3=r6LUw{ZrYuh3KC|p)2ZN&HS^xEZX{TYp_!-f2kT_)vGA7 zlWZ4VBfC%Xc9H(R3~Rz5))QOoY&7A30swRsTMPiYLTbW0pe516seaS|O}>Jl+{$*s70P-ON=_8uxS1^j zaNH<0;T^}|nT^i5<6--Zpfld6>d9i%5tO^xuDC);=LYwhF8q^s3kW4WV_?aWWB=#-dOcWc^5_Wy5c{%40EoS%CcJ~#qZN#)ZQs=n{@W_;(zrSus%CB= zZoh;8oyc~~HDS_ETfN52DimNno-GJq9V<2A9oAB{LSFs!t1ZIo#xIRlLuf*_8?I>f zHghL03y6%ebpVly)P#2;dnF@M$kNnr4Rr@$W zzY7W4U2G>^p{1+(Q=!9sL<7EWuw?nkLu%Ex&|5_76$I>l zwp*^i(uKzwFjFRw`ZHS(kotqvgm+R)o3*ameY#buHjL#DS`Cre@B&}Gap+OxWdV`( zY#l&kFR2OdLse#e5tZZEuDIq4j3?;QfXidqN`T8_q$a#`S*bZ+ zYPQTJ`dCkV0=7%8e9}oVz4i}vasbl{q$a#$GE%8gGhSvb^#tiXY?oXiO_NHIL}2tTwi;mc4yg(6j8-S9 z6!qk)LA%llM~nu~dIfR%I@>K*PSAz3$plhgW$OV_Uy_>ePHL?s1f^P|ZhW{azKodt ziS3{(vviYGvjNOMS|k|2{hlof!2L#Q!aKMH1Wx~jN(%|fx)=KDaqkIZcyYHATe&^i z5&*$!sR{20;%NryID-yCuOb}BupM#D5$g5{WX~(u z_kv`;EH*Ja^XvViIrGyE2ezDe;t5)7GDSrc$G^I|bnV^OC|l0)@mrU$FLoAZ6VT){M7@k zLOYxn1z~R(3*OLNG;5_#2;YpYio0!EW za9*i8s`vNe5{l=8*bcd7SUOs)n!Gfg!;un*>49ts5YzpoCbH*xU&1oe&D}h;!HyC9 zsDy%l8e8`61V6ej1pgGa1PK0#QWM^Tzo?`(oNTtt*PX1GFf`Z>xME1>C}}y6Y62ED zwhmwsN=Zvb%%TLyr*Nory?KumUP77$cQAil?zzk49)-T>k|Y#9LJE~yFcAk;ui zr_~Lr;WiV7RzpDk&33~T$iCPCRqu(JGJx_AwiJMJztn_xD5^nNW4i?el@f@P+45bp!VZG&4IoZn%K#9^NlkbMp{Dt? z1GO!c_F*qABn(^G;$1Q1M(un3r$i4>7-y>h3W3ywcM1cQMsugmfQpE~)okIe1opE0 zJj7Zc{!?rX5dX`iCcMXgP^~;}mFsP_@ARaZ3`QLhc|Y3~S0d>Oy!_jpt`5+=k1Yn! zyhm!nJDSBkFnWIE$_dH0*)F&uN$14nkz}d>$2ZwR0LRy*CcNVq8Yx${s{Uh=EIJdj zlED0(?Tjm!bh3ONjG+(c{FSW+==@1)!aE)M=3egLn21(GT=u)nS3lSO(xK)FB+3BF zK5QufWu4T7cPIl*b$Gj;C{#okj%N#ZO%L0Td#nZG|2Vb=i2pHC6W-&$G+y~T64uKt zqmQN45REam8?H2LNs(6uP^xSx0HrK7;T_7N*!4<#o1Pe1G2ys^?SLx|+bDWZ6R^09 ztpiwGDmCGq#R9eYJ&;HXiNlR-@vaw0LNFPCcNVqiW`I8(HMF&WmXcHzp$Ng1!L>z8v20FAK7Yv&hMlq z{L?9omAmbBSgx1wY{ns@lIZMxxv%c7jgBD!=&WU{0Xl1>CcM*GAMa_@-d0t+6Er4* zc&p&jWLa&#zeCTXeist8W7$r+=2UuCaQ@}7k8U7%G+P-EJW6W9JHdV8NCe?nP@j%r z|4WHth3%*-#q>Bipjf0GI6jlD4LDvPHQ}A(P_(&NHP{x~4%Rd)iPekQ&bYF&wUdTE zpz}hu8lZED)P#3B%ggOjyt>fZI6^J)c{ke~S3c&Nt0B7MRFX_?q**`m7OkVA8hu2QU4qtUnpn zE9f!V^b%h$3F#hYDsOE9;ZfPhRsxU8hSacN%wq`WOiw0X(i{)fjyRQ0!`Li1UqO(@*-p4ZO1I{*XUA|( zBESjQq5#f_)P#39i=xS4BjvWS11y)bB>|QfNlkdivasH$ zUlg<&`tM4wl%TwiE#DPNdLDK`wxr^K$$Qv}fXTb0CQK%RzC+gipkFqDiA;sB?#Ifr z%dPTcpbj#ycRnoj^tjx|j;!nBlKa$!;@}~x0Ee=v>KdA}t;8w=qy7e%cENP);sV8KovR!g*;BrIx1;{kH!0RNoGT?Q*)P#3l zD`H-g;f|mh(=s-|$7-TA$#%$<)F;)?0uX)*O=0;8+gs({fIQWM@8nX_CqNMsQi=d zfGd^U7ZjvYJfQJ6wjhA=7pV#FP!^Y~6Q#O(Z#8-cxpKm?|CF!SrM}6M$%ACsm#ql6 z>@79noy$OX#5`e-iipOEY~ij+l3Rv^NfL_!5RYez0T9PZP51{hg#Q=zk)P#2sOKXj>Qm9TQ+F>3Ks?`vdE7@+iVo47)wD!C55`oT3*qVUOi=`&K z(-|C%-!rXzj5;E86WbM6LjB)gG?~EYy=+y$=-pBi-We@SCS~e_ZM{~tqK@DMry)4qbysZtv!r|ux2Gu3fRuLQgTZr5(=0`*s=gqNovBxB>487 zeLkn}vjP*D3i^C5Fp;UCa~A|AG8JEyo-vTAptCe~OT~M3Uj?0;vRf)H-5nL`$emyQ zpB=e#?LfhiJNn|_^vIph_RFE<$eq_mCf}g`5FEMlsebkuNA5%co$kn;kF)OuP0B}N z6NQs4X7sQm`H?%nWIJka(jllWNACQLeRGKNk7ql|y~B01QSx3X{ZKBwYG(SJ7}Y*U z?kr;86rwplHZhCO?1B?pt@Fyp1lP2XV)+oZTkcP2a)j8n;Y3CfknFsjgV{HSm_D$7 zOlSV+m-QueSZ29eYD|Wmj+$JcPNJ@c@mf#gBSOCmDfVZu-FJ=sA?a7_H1=xu%R<$F z}z))xNsH`$6}?PzUMqFKJ-Ih3b{{I(6{u#NVYRytCOOTCN|o)apt# zU%T8Xk48%!^d5u#5_0SyZ=8CQM|uflRSL8YU@L-5+D~f2JFN{-48m$q?}Vdac_gfb zot;J4rn#2*oyvCAwE^s{mC*PVYX^`gv84gXVfbm|b3GXoGo~)<94_Blq@Ex{h z*XUykoQx@O7yI@Q^>0W`c#rzfXxM6ZN@~7HP$`?oz?qel41Z@kNQrN5D4wVRs)3ANlkbsv^?=YU8Ai7 zf?DEpJlh@D{OQfIHu?1Q0iMUP#Q>gTq$d31X$O@?y;{(^EjMck&luYsIUZ91;Hk33 z0G_hcgm*kE6ZeZ6I7>Ytx`OSJE27?>b-fHijR5H~wj6+TsnmpbNGs9+^x6>Z3o=#{ zq8r%`xgx?JAy5c}ZeXhcLf1=8cqcT}84p`UjhtCYbnaw3<4Om+aE3Zya|c@su=$GA zgm*S8$orxe4y%dJU)T=0^1NK4OMLde##a{`yKtsH zz_XSu2Jozrn(&TiFdB5OcWbqH-)OymZPXE(W7)2_rcUp~m0sUK*9KsYW=jDuM@dbX zU<7kPvq`Nq7f4_tQ?W_<5h_zbv%Gdo#RGO<1OT`CwM}?Z; z`_=!m3BK<&C-}~fC&X!uLprk)wg+KLVZv^_>x^0@7g?ZrTVO_0(_PF>w;YqjiQQpdhgOL-I1m`wukn{8=^V2Y?C@VzNZ74>QAz7459s4|IkjXTU-#oSE?{ZQ9VmNfhwF& zq`$V%Ur(aHo*e%*wLnc`P~Ts7#mP6NR`jX8!&F!=*tPl0slCeGPNUom4~vuGFm*o0 zVez`zT`$^o=G0ofGCH;`SG%uK+iG+got@2~ox8>`-)!d%tkVs2J7F#Rx53o1uwJQk zs{!poq|{FQN(F1;*U=xpiT?Oq^v566AHme3$?}eB(Cm!wI&I$6!b-W>2^)3g!N6Fp zF|zCOsRiRzHEnd)X)2dGVW$?f8}-`GU6)NQQMaZF#-!;)x|Ms*!=+V7L zsqc=IC){Z!DzZkMo}k@o)IS`nnE6TiOH>w5HmY3}QLUMs%2!YzR=(BHKOJ0H@<-dVtmHoSrm@_2=1QAl9Fen#cx5 zK1sNWT9e&?60b{A{d9oRs~{-<%XVV7q4X30lwYyM0F<9gP0Sh;SRJ5JC=N+fK~Pq` zoZF|E87S5T0?XNA0Ln6{3GYw_li1tV2N-pP<`HaHT+yV9pP&(=d?{F@_As^{pmVs? z#H`V2jI?1jVM-^dBRWrGyD~F$5)sg#J(;Zs=$tDx;hj#pLNNQjY4o#f7Z9iCu$^+{ zlpg+5kVUy=#Q^JBY*7Gfht!03SVQR~PutF{W+g#-Gus(gNa^Z91Ck*T0KI`N2mrlS zYQj69Xml4ExwMdge2y*N6-c@o!vK;<0uY~O3jq+Hkecuh!uD&kK>L~bC0qP%f++fZ z`59XXfcUZ0gm(~w_4-96<+-iJGU_NlhIaYtHW&2Gbb*j2OWAsW&LXJ^|8$~lv{eIP zrK8soog>(;xF$^j9bE*_IgG6b=sZMf!aJRL@Rk{u5Q+2HvRz4}=hqs)Bi8rIEo>DK z|FfkgyvKiOVW(KDAs!d8-EifRPWUr;wG6?S6;^Z+0edjpIoFg*&xtltsz5S;dk|X~fICoX!aF!M=oPnq zy>H`sZecaSI+N{?E3CpXF+(O`I*ly}Fr6Yb;T_XHMFx|%Z z%!JXah|7Po9dYH7?gV6X7c_Z*=6h^8faW_=6SIZJHa8|gqs@?cz;(X5-hI&&&6D}J zG_Ki3+W(N6@Q!BB=)6L;Lq^m)3hU|xNp-uUWwY8!qN0l^lOE^|S3$?SKr-aiCblrh zsf|(--oedNTZrhC;u1pg1h#C~#;xF!O(X{5e==JI#Qy}T3GeY=qh<(o)&8f{YLry# zj?0(|t|3Nswrj48(xZlqA0WG6pthB*3#g4tP57r4ZM+jrJgc1v0p>fUdDE9 zH>ugh0JW>xx`5i0)P#3xE2AJ(+KnP&mU^Of8`~vUS`V_nr=n0G^?tS{AoV_}3Gbxh z(;HNisJ>WCHUqVhhU!ZcQJdykBKTdltF8neR!p$i0igM9wmzWwO{oc!reG6*Yzq-u z{3|e#sd!`m1*ziY?en)(#=FL3yQGXBk|nS7^}?_$c}PeB&|k>DG0-1~O%!exP`tcd zy1sXHv(Z*_7yEp(vR*-vK7{SCYdWUOu;WM%+cZM-H?ze+^beAn$e#KG2;fXF!(Nqz z!2a3Fb^$>;i|v#vq;$y1gw#_Aj81230Y;}vO?YRtN5&|d8mzyR*)JhZZMI{soYJY= zCa0WAAk}260aBAv6W&RseR}#tUut(JC(Es!I;w(eh}Cs$*IZepV^YK_>Fygk0oE?I z9DsF=)P#3f@yTszlVWFOzl2bInC+M=Dm|?tRG<<_-Og46q;8d(@J?#qlIrM3TM3S+ zjr&wjKiV=S`;2c)@1E{rqW68av##{gVG&BSX_N!Ud)T^wQ zxu%AK+Z~*4R(u(;oA)YT{qS^FQ`SgE$8&&Mpf-oC2&nz@KbBhD2*!h1xg6Ag)9jZJ ztp~AV}klJ5r!h3$LPkz=*W35KFskNCpuYMO2wbR&6x>8Grw6YwF zs96*PyHnVjfZd5w6W-ayjb2&p99xaolZTVdMysf8v|dKs8f*t$xnbLAl>yXhY(+pV zl$!8PEl#9XFxl8P4Yz(561kVNopdFK8CRef*u9jk3D{jJHQ}Azs;IS$#$c)KsfHEf zOQ-DuV)X&GQ?9J^p%05fU~~&x3oyD#YQj6CjZp;hPb;x1Y8|J)ocMi@?XWAqL-QlK z!_%qYE1n?^Zs{0CbXaJ*k?!aK(e z^H3P1>}q zeistHli5zXCZXPS>7y9foxs)v?2eO~Fxd${cV=Hq>C2tKM5e;m7t;kNwp!@ZeK}(S36k8o4?v51CwjnqJYWEq$aX-xSFue^rzG{W+W_A#0#z=WFKL>=87!c zeXIak!M?p8Wa|N1w@FQSr;yX}>lS7Avc&+T?@CR0 zhqOASQ`it%uOLhVukqEXOb4D5Og53w^vz|f0Z#w=kL9#94FP14RzqAivm@b}KIyD+ zgNrq94q~eS9tTKGc+Z=ml!xls!-l<-^5+r@U(6<#`Km4jC-94KDH)&&%=lA7>NacR1> zO{r`Q^J_Ik=ytXnu7r%AsJtlPaVuK|@VHrO!aI+-=V;@E6AxagTJ!7FNuv|@ur<2| zf4MefFnkWKCA*t_ZwUFfq$a$FeDSHJ$*|sSm-Nt2<;sb`zu7Lh64*;8kO_i^{~v4( zK;nL>3GXE4o~%FoKU|SM{F`3u>!rnb_)o@%eE1EWi9NP}EBo-G8xJWguDJD3&ZefEa0qrqx|Gr@Mq70$kT8wK(J&lp<@ z;HgSYc*nCm@xNf-IkT4FT*Y?B70w1LoSryfa|K%ou(?cX!atjK5Y105Xw;UQwZ!IT zwmU*L=Iw#ajcg^r<_4(=?`&2k?iaOdmU`mzEw)RpeA2l^$=e|c1VVSRwE&?zq$a!* zT9F2z*LrB5y|J43{Eh99D<9*B2jl^szp$kMoC<7^E;A&{Dw4GNRpnuP+D5{0YT@^^y*-54lLu{8jN z%cUl~Q&?Imbz0q^8g4U5Xf;IR{cJZ}Y4lz{vX&{61w`J*)&WG`BQ@cjh-!z_nmCh2 zuaro9n=Rj!gylDaut5+*QA*SMjhc8W4q#tqc?Dy{zB{0fJ>FF z1h|x?CcJZ5+@qp5O}TR7aRu82S025YqbZL}60o?8tpZqFDmCGq#n4E(vQ>3rlQhwR zn3Y83Mz%AqRC?cRQ!0iyKyw3I3ZS`OYQj4j^$MZBaFjbZN3|Bq||5d7asO?VIf(s-lFk+2S% zz-l#wV(&Nl>eoU#cOr{o*DLL9`maV-Oe~INJK&lYFfiT|1R#!P zivSQuNlkbMv7pmXM{O7w(n5kzVT*T#055up7(nn$wgf) zcEJ?{_Gx>PfW-^hDuBf$QWM@;48_erwcII>M(2C$t&>?vRNl>Y#+3^6X$^6J=ACRQ zfaYyd6aLYZ#>(AxJ1nacW?JR#1{$N1(0q;Uj2Mk^e?aqPwiH0~1*r+|Xx2w>8>RNP zXrr9QL{N!02UjNxw!$4crhXR^vOln$bVZi#K$gz#8SWz*@cou83-J9~YQj6ded0(2 z;g~updK!TJFC~O~y~$SxIz1p#M_42rAnw5y1`tGgCa}9pE3~_+wQEVxI=HXHk-q9>Cw@b~P!DP2&YZIZC;FQ?zxWefzKS_tmOnE@( ze6|*#^Hiw`?{rqGjakYq)vc%mRz8+`BJ={bORj`^`x>Q$P$DpT9$O7Cx>#z$JEH~B z8Nl_j-kC@XiN-tF;$3OLTWTT(5WJNw0T8@NYQjVym|2}o&ZfD+0uz}EUz4*}o}I3z zNIL=gLt&|>=i}S#$htls=};0XgD2#hY!UE;d_6WXi}SWu#c!Q`-o0!WP|W|zcGorL z=>|Ix^I?lx!0;!wF2L}6sfjE_enTW@dbal-aRk)6SGhjctwq~f!P~O^5@NQ`n|<}( z(=FKxn3;-!+B&u}pth&fgm-EyV`|-c7|l4aAhXmHug9@na?P>yg}H#2rWIfv!9D*+0RkecvM zp@tOVBBJn2w(#Aj5Z@jsT)g1jbo0S`9IIA=?dCCI=RF zg!B3U%_VF>faW<;6W-Abj>a#ORyal-;dv+96<0ic-cvM<0O)OONdV~0QWM?*ElfU) z>!Z!RQiAejwtQD8>47BpYm({$9A99I0UV!`n(&V!sH=gpiqZL9n3fWb-?HWJK8{oY z;P^FL4B+^s)P#2&L#4#;QoB+%H_kCD3C-x~F zPkQz?b?}^A&Xxhs$%|qW|9^Z7e-GPT*Qlr8RV`6>`WpT&wlV|eBsJlkOg2)jeN@PbiOQeY z4!BZD5974uP4N)+@7a2Q$#0}4yfax?Z`3aeS`EE-*efL>>)z(8=bjE<+lZvfAW8OQ z%K;#(r6x=ug3dzL-=JSJfr(6oul~k@Gc1ca(n5MFPG!f?HBZw6rV|bD0G!0WGduvt z$0la6Be5{7cY-l<7`9hRA)R15<{Hv9$qy2RbT}0Qp&er@fY4T@CbFlxOmt?tYp|f& z=)x|)X(17~iY?xiK)NhHA&^J`09UXD0D#M+CcFa}sEnJ3)>9D$|7Nyu*WjMkM(P;<4}B zef5#k)dmTVoFss;H(Lb2SSvN*A4XG6ZLbCOvCg>uwz4EwK`djP1B_$Y zA^^tGQWO4RG)CHX7`Y09G0JvA2qSlAfKg$K02t4dn(z)|s2*%Dq4u7ielPUL zvgM0&qF^WV$FLOugGWhC%mxEGokaV!HqI;=VapdYATf{!CAI=!aK6;UY%rjs5KIPC zO6}2$*z&~;=(fP%1#AVt;CWIL-WjYIFIUTxWpxs)nsHj~R?HDYs3t1!VmstY#TYS! z`vabLu%!T=w@OWT$DLCuJ1*FR-P%Mm{|sI*B|9fd~F`?E6FXKP@%kKl*9|dWb%iQS^V!mM)Aw z-4vq#OZNRC`ahGJ@E`s5c)4jTWSmcB6#bRg`|73|&pzE0qCdpGKSY12)P(ow57aA- zn*N?ZMHKsovxU1p`R4CzBm+W!1pD?7`op9qW+C*ghOv$RKs_p=&_9(eTpIf5o)G%; z*tdt!Z;_ht9{RcGYLEQH#KCD7=VG>I*Qgs^oO3bc7qRaRA>S@F;XUNESyr>rHk$fe zIX(ApWV_%>z!;T?cg~6h!NdPLwgw>aYN-kDB<6*6DkO>tm&OUq)l2EZYS{<>zdtT&biRYb4!&i$s9*6Sf$D z^h2o$?~s<}BB1?7OldWQW!bxY^-PT^0C`csV=-F=@K_)<;ho2tW;^Is8*1C8ZKXb$md8+cn|r4XnP3rV?kO-E&IW2@ve`%(XuB}lIYbJrcK-f*#ZE- zCaDSU09KExO+rg*$Wjd$N1M5Z6?4C7>lK9N47OXYXwu!lla~RTNZ|AYwi@7cveblk zPHHa)3!7dk@wkvJ-<5|kAukmJ4C-tJz+kJ?gm(t>IId1IV45&?0)jeU2B`}?IPyvKc@(rE6~ z15ZU1`Fq*IT_bP2_r@|H^xtLQ9zy?ZsR{p~$E{Td;vx$D+#7uLN{!IR_k_^@OB%PZ z*R}e))P(oYFAR354_fB3gkC8DILI3S1VEOGe++CK5QE#8v~Gj+dJ7&S`;~XQeg{(SIY-LZUIr7Vk>Kc!^7- z0DuX$001y1HQ^n=V&ye$@uM@yl@o?**ej zVW?xtiV4U5@A38GVJtc9$pH-evLyh9y`?6+V^|VDh87U{3PN!r+X>gqFuwceBms=a zvqb=mW2Gki!-!jJ8;o29!3fz-2w~*z3@}F7A^=83YQj5=2UJJ(rYS0*;9toW?Hc?Q z`czAGM+o{$*f)ouzgTL*d(emC+^?29<RuYVx*v`1ZFxK4~(g4hR*+Kx! zyQL<)gQ1@T%Neox3IcK$+X+`7w%-Yo1TenA76CB6CN<$5#xm7eDaaAMih$hDcElA( zdTdVe?NpNmNdC;00Z9HJHQ^n}0ySN=F*+HS&=YV5J9@5(xFqQ}M0W@7Phj5`xSt%Gn8jlz z7L?Q!kuk4NqnUn)RnZ(ybU;wIvhNN-9haKO9^-(x%=7^hi=$0=EkE|T za$@i@whOKd(l#w(kf{L-SF;5GhAF8D{}`fuZEYB`a>8&M+XV@R?5=>}{cHh%;eApQ z-Z2c+)dQ%%ick>+|GRAAuE9@dY(_Z{Yk;_an|*JH`!}T~W+CqCi8JFK6;a&(#TG7# zdvrsH``_93hPeM#YQlTm7sYS7&9?FDn-x?154h1+e>R&{Z=N?;;W;jKG+d_$k9J~b~Yp$Fg`cJy2mFzwDp_X6%`v2P39PmfK^Vs~~? zSXHYZ!;Ue#Ei0yow%M+^Ml?++7180I76@&VeSZk;q|`+AFi#MjneNmM;Q5eLM6th) zE!;Ks>Cbo+`&a|SeHZ)Q5cg}OCcMXeS*4*Slt%|Xml}2b=UlHM4j*Pa;>sbNI1+Kt zGy#v>*&2Yytx^--dCa%GQ6yzV;QMUpt_0H08xetmf!ll7H;2&QEj8gi^y*-aX02Q? zzH9VKDg5)^=c`|p&b5ugpDKZ8e-8Wh0N|hhu>h8+A?j|ug403r6@=kI>^Qh)K|13$ z!jRJgBo1UN022F4O?Xd*<#ESeyZ0`@U~mxo z{=nb>sR{2nv8WY{*%Bx`wR;2wf#L;hoUD=D4wrIxZmwx3gut zG8oD+h!jB3Z)M*af_}5qg!iBiDVGa(wl^yY#XW3iTv2Fmg}N@FayMHAQ2CbBgm)_I zbSkBK^a4@~FES5t?QNC;W=!1wO_TtD{n@vNyxCW3!aIO@5d64=0)GlywrjhU{|Si{K+sQQ-x`Ac zc&Q2RL0?dh$4T_Dk+hIPUt^1R4Sl-GEqdk?9T5GHeRqidsMLh_=r63-FN(UfdiZ;# z6#tj9<-5i|-{z!B0Kk>(+XH}?NKJSLFi?l+QxS#!7PfHL(5Ex9<3~Tz0CB&GeQ${S zd!;74$9++>n5!3h46~B*VB;;m-URY*ZH6w$h7D{LKxMtu zgm)@SqLHPsR-@ZAevR`LMB@au6Rw$&PF;w86>@rj#Bpo|K;p4d6W&P-C0!9K4YQJn zjI*6_C6dn%Ll;m9*eZa^h}49CD(c6&6#Z!1a%9M;Bq~#EXCzdNTLYEL*(!j_i=-yJ zQ(00(Bws-^-p6*rl}5hR%IN_T?_nzd67Q0l@J?bG%{Q=Rhh9ZAzR7mPl}7%9wx$Vq ze4VWUczjiA!aI+ppa4l}7&iU0xBu_!C@BWAw)N@U2{d0t^$o3FS|-Wbv;`Q zpnAL1gm+X+f_5jgO|s2b5R^OEPPjtJzlG%V0Ew@#6#$7ZN=8acP*?3(S#PYe$(93!FmOeIhyU3Yi6XYo}(WNn?ite6k7^FdW6)3cSw6u z2--m<-q^v;spuj?^-Q*NuBg((?lM%M6KGw)Rs*!2CN<%m)*jJ_Z#yVO`+2Jg$u+go zwr;GnwO>M{UdVRLmDGN@32au4!08gU7U1+8sfk(R)GT+#OXab8L-ofh`lt1jlYR+t zdMDel-Qc7P08VdXYXMGgmYVR+X>}?7?pqx##S_M(Y%;z!TCX5VUuL`IN=f^PvM2;d zUtmiCNS~9M@D6DmL88&xKB)R!N5Fo|cF`4Be*SK;TEO;ewjjXvOQ{L(*oI2AhU$WE z3tG{(9Y^S`pIJ$O_V|FW?s)#Qv7rm7tYoVIDnn8e-l+^kP;?elL@lq-|`iYbdeAoNtW5+HP*)P#3J zdzHFPwfH%DSEZHE$-9Q0EQMDQtLL%Zb7ht8Hx$1M6(|K@7qjI6u#2Q7yaQXVX6<#t z(VeBH?IS!T-Ejq!?iU#WGL-?CH?d^^nAb~9cn7mOZA-%FKWtyqtXB}A|6;r4 z3P|f!(|dF?uMi-8mMsM!eM)M=JEZ01YPF>N4=(IlcH@9rg7ZIYcUPZ3wkxhU@^b-n zReO*I<(K1djGwKM+&)KfHLdpMr=&B%Be!><3NPZ|a;T_35wY9UJ zA8`qVf7u6p^=k853q%Sa=!@C6hM+Hyn(!X<1?rtw<$xadw2(r7I9t5ylb_#OAkhKQ zKa_oUi2j46CcH;~y_!|sRclO>!%ftN=Ix52q4pLtPJruoAyGP)?W8NEgL0qo`{)I7 zPh=|sa%W0Scqg}iv@Sl6ZbS_^>KiI)izpnfCyqPVF1vDkNRH$5>H%k$EevqBq$a%M zTwYQ=foN|b`!PdiEunfX+Z|U_T0hND24G&rmH}X1AvNJ0%wCo!wHBKDl^0$`pgzHN z&lQyR0R~%ySSbMeC|eEy`;gRxcVN*f<6OI_cPz|G!t-OcGp=~@U-%7OK;;K)6+q>` zr6#;n*`wKNL@zpO(uJBJXl>-|mk^;vAM(}v&d(9FY6MR6*;;_p1EeOrbK0{~u2s6V z=!s6=S zGLs#viO|_>hg=Eej}`!Jz~&6L4q)>HsR{3F)51=kRz z4%;{CEFQSIQcGvaciJ*En5Xp zd70FNcPfMN?B}piicY~beu0cSV)7BTE3QoPBc!@2K=MJh2tab1)P#2=s{5>_XQYP= z8BEMdf^r|*8CNLzL@{&$m3!GLfXa8JCcINw5qYoH2wE(A+q$a#`8jMa|RRg4@wmNmiXy1%FBGY8M z;z}mp7tmD!l1a7*Kr$gU;T_2mVo_?f4IcRlg0YM3ge#2vjKG{8AaM;_0g$*#YQj5- zrHSX0LAkCEcWO1n;&!$ht}ODKnB^4#j9b|f0LINy6W(E@Pyb}uSOT4|AQbnoop42A zNsCAikhq(z07!gGYQj5-^x5nr8QOIpN=0LDM1k<6W&A~oS1#8*@=oM{->~oyEaYv6_>FFi2I4` zdqdnGFE!yk?t9T2ml{JUwY!s(<<`zpr#w;3J2~TMZ(bT;nJAA9l?}*j+ zVHa&#C6CLM$rBaJ7^1Sbz)CprdG!6X;=?q z_GhUH=ggJ`51LVR(x5tx%k&I1l4N2v+tcy^A^O#LwI z{!@Z>JnX1#)7m@Hw7P0${Eme+F@7(UnsClT>OK!?Bt7NDyhQ%;c7fDSx zN3~rL1hr>2Jhmg}HI18dvVKIcJ_b8y3oE%3F+(S#bt9~X(fY8|gmYS}nrbFPH7s=u zXmB`wZrpEr>f9nPy&jSIE8=xO?4B*JfkSPlbruhfJN%r9LvZ_b?8Pp&DQ z-ZN*;#>vtd^k)P8Ig9?B9sX&nIHzyU9E$6CHw7I#fr(hfb?*vHlnTn`J{L>|IxbOq z#x|5LRev_B|F@KpQ>tcVT&Dh)6U){|yNOPyH2iTk6K2RKpL#y)sz`nua(S& z3uLw7A7O1MNMrG2NX2A_OeiRS;i{S!U*pHE#a31!a)^J5X+&oO8U>9NGw!xks473a**%7!z7VW&$g!^ch zd9=eMiuNV2a46b_aqZjT5?Qq0DmCFe+6&9oQT6$8!QbN7(=X6e4#oZ^*bON5hGFbS z;SyQwKO!~ZJobx6!l9Ef`dS{v{{h$)DE@}=>$l+&S^U2#HQ_w|ed^nxk~-`?UGF=o z6!WKH`B2Oa!__~-C9;_RQEI|{%-df?YcY>fDdxM}>8jTlzEKTh)E(gxS(0c58CJ{Imb_S~ThC%8J;Sw2v=SxjEC$P9fprB6CN!NWXkK*scu0ZiO zj8=!?5?TCnQWMVOA6N45jJ7lYJ(CDr3_Al6FbrKUf=grs-Yhkt6A&yl#;cXIN+vK7 zt8leix#INU&R6#4^-AL}s7l-lM;EFRhGF%m;1XGt_(W*p{~t@1KY-mu0(`JxB>g?O zL>A!tr6%IqejhQ)TAP*pJHQ_w+ z@ib`a2-i57qQ1xHUG{SiHjJmA1DD96zO&SX^QiY#s+IHodNuue@1#=9PlV+|HQg|V zJ`OIC#r#E56MD=AFXi~XPOlPyiCBfJ_xil!s+-c-!xTck3636w+%SZ1z$G%|wa~;Y zzPI~}1;5f%H{zt@7$;LOuY_HLf@v6=Pr@a#U|uFQ5!c*HiN{R8qkH%Ri;^hXx52`p zXdA}ix4`JqPNR6w`-0Q{kYUU{2QHDt`-a#knrl_^!Dg)Vdo-Crkv|1?0*U+~hSBaxaEUDPCrC{=kNlEwH=_D@-gq`S z%OwU?*d2(0Vcc7WOJod+QWMS@tc(~$Ct2qBCnOoaAs+9BU4wWSM#S%gOJqDQk(zMM zV=&~QHh>z>MbzdSTCi?i2#>Le`CcIyx@jAQ zc{Wj41IGla^M(=hUT}$w!X8o+&g=Z*Hif#MX9%=BivOvwD^UCmqvn(05?TCDl$vlJ z|ABn9s%~!J!B1vT<~y)^VAN;kU%1n2+aAC(|)I6i295)e2J?Tq$Zpb zP%HGoMe^FLKqr-AzNgceA7&WK?hcp8YW=QK6YgW4Q+G+!bGh`3wl7Mhn4bj8N1il? z8Ah`wz$LPnzgTL*dCV8)f+c}s(;wI4p30%vmti-c*c-;SMYu#3`w^)L=do8cyH?$- z$GwwEF@Gm4ABwqQBzp;5B8&Okr6y)0=8e{{A?B1yG5;hiABwqQ^m-FqB8&M)r6y)0 z=D|rd{JBo46!Y)H@}Za;#;p&)C9;@*TWZ32%$JU-lVjD*IO?Y1dbQBfKd|T7MB(qS zLl6bS81`wnL`LDyQWMT8sAu_jt8A$Glu9w*?aMBE?uQvhue-n{vY78EHQ_ww{kdG- z5cD{iqJBK=094ZrBi3W#5?Rz=C^g|c>I+maU5%F-o3ulk66qB8G1vtt?uK!y50}W| zJ}foiJnn<4syEebAYQpRTJg2u>zPDg0(J%=aJXUAdNEugBXE(_gmVJR{Iak1QDs6f zh3Um!Ix3(>AuwcA2^DnSxO z`y;S$DB6ZW=nZg*EZWygO*oJC+%r=ZyHwgI>XdvF)(i#OFzUPqE|CTLZm9|9fu@7q zYt@GK;7+7d1%DED0g3w&hOy@F;1XHfe=RlPJnr*~75#{nAcbPR-B(?91dlL`Cs)8F zvRE&bns6TL<-w;?Vx&ITZVIU^k%H8-|~+f=gtvf4S6z^VqMfHT+hgs*We$q^?08uT?8*s!MAK8NVSO zZ-QNeco>GKZ-h%^JYFX?;he`n%p=_tk{J~F55i7Bkv9xfKLD4=B7dFKg!9PvG|sDQ zUms8sMf+>8a46b_Vd_`l5?QqGlA3TH?cR}Axt!A;G*Kc&{I{@pDB^~p>aXAuS;U`^ zns6TRx#?h+Ub;3KJT1T5X~)tqN?ih%$O64cYQlY>Td5~^>59(O`C+hTs74z`sRzR) zvOuquns6WJ^jN9%nNFZz4r@jNeWYPndKO$F3-n8+CY%SlH#k61pEQpW>0$j^SUePQ z!{BraTq28jOKQS-#8-@{8*6fEB2i7|23H3a^M3mAJz@L>vA7m?3u0jyo?Zi&$XHx0 zHQ}6v`oP@~|4u5!{EM)BDCUM?>78(iEarDeO*oJF{BXZZ>ODHqPNR7Ld`jt;&zm!M zPVgUf4}9w8cPQ|N@#kYx7Oj`a0{>&F3Fm>Ir{-ePVGmO%=nKE*vZHvUVZ_-Fm&js0 zUuwd6tb6j+nsyR2B~i53!os1NZ5VOx50}WIy^qv{`)KoPfqKFuiuMLrI23KeXmdSW zB8&DqsR`%N?(?^(OM~@o>77)H`DR!?6m!GCvk8~TVt$^~g!7oM2;>FrdPowG8UIhO*m)KtF{!WLmkqu(kPK4{yA7Y67i!9gVfvM5?RD= zm6~uK@dYa4bc{(l=!tZS`%hsPptu``s*l1YvbaAiHQ_w&3&XWjQ{+=Q6#KrfyX-+8 zWf+?Fz$LQS&y|{RAN#sGHpy7)7bbHk_WQwZK=s}*L|p@y$YQ^j)P(ccFAeMcNVTr| zITd~uJxSjY{7 z*fLxs3wcp$LJzs%jQ9AMcsjFOU?NuG>X`U>>l^yYSCB%?uZ5$BF*gjJuYrqX%&!hj z%;E|0gX+-K%u9ImOp5HCu$x$94Flpk;38RMKO;2}*W*tUoS8l_eyKV+vdhh2Jex2) z20MgdFpQ9Y3>V2TJR&vW9K*6&y;!ZQtvhOhXxs~KLrr&M4fzD3{~NA)vech444CJ` zMKTcENKH5g5!^Z3tgBsAL8mIsAe}|w-ye1aYX^o=^FDBqEd0-vns6TeesyAyA@p%F zg?&Bj02X$`5PBV4Bn$gXq$b>lz3z{i!rn=yus2}`u&^7(&F8^IvanaACY*;|ovEr$ z$?}@~l-pDe1^+784Jh~r8Ai^R!$q>-ze{SudGMEqlhVPl{lVechRF-l4+zEWuu~Wc z!zlVzxJZWLQ&JPoQOv82X$vx83I+R7ST+`H!}$4OxJVZ4A4p9&5B8u6cILO6dL|+0 zxz}kY^B}`;b}n2bL-3DhErK1=2y&I6(_b#0uOHag<#z;T4ICw`Z5T$wd%;CAFndT% zIBy$$!J(DvsQ+}wzmrNqKNXgb1>G?AJsB>N1^q;+3Fkqd#{wRvP_S#TY%JJ@@$EQV zBnx&)YQlN2dn@5gL%M=Ti4^ilSUeVT!5wJsomNqHyo&INXMD>h5rntd{R8HQ_wm zgKBo(&wGugX}UnqBm^hH&Oi^EgAJqC6W}5lf)`6oI7cuLOxBFntF4;$K22s&=*zGZ zSm+I-)*@Ua3;l@Hg!9l3w)-*0@avg`;GM8D7y`ri^b)v8hT!c|6YdeHH*9znqp1Z9 zYMF%Kldv-w0>fzaCb&q3;G^Escy<@KNEZAZ zr6!yQe?c(5GK4;nPJurjb^)vPhLP>DaFHzVFO-^a9{9!4Th$PJEsp?_H`V-f9s&3m>=zz4LueyPJm?M!7q{MY3>j zCpFeV7D+BhViZk7s+6pBQ@b1#&!gw;pc<>_!&I1end## z20Mo#F^q!W0vE}Uyh&=pIg)LI$iCM!@^O|EhZ?sZX-J`r2NBSp(<6E#}5RO9)Bjs1)j!`Ojf(41)(Bwc<-V7C9Z)2{8IhEerO zxJU+OxzvPnFoU^rRdsDQ`Spf+tsk6zT=;aFGnf z8>A+jqga(|)zp`z!RU@YGi{G9(v2bWR|Mq-*gXu2Vbpy+TqJ|?eyIuPP?o4oUCrXi z)?CfBw9Ik|!acA%7zD#;`);^M2I0$66V4&5h#peKaD$6s&B6E!g7JIUEewWXoc$ZP zNCxAVQWMT$Eb$73T&P`feg#}43;fHZ zCY%R;al@+=n_F}FF+YE<$LOFh2fIxJU-!Kcps{Lzo{vebQrsb{YlztFUw|;D!iXR;}9)3C-(ZCowdJvF=v5NQP#U z)P!?1y9eJ6C)o_E*|;v})L8tUKwSsB41qe_Ffx80TqFba9;pfEpqAv+`#CrzrQL|q z&o7os5blEA!5|n$$)AUdWDq_pHQ^k>Dnn~27xnXqGk--;o`BuMpcqEZkHbYWC_j;! za1JH-{4((bOE(NXlMpO=z-b5eaKpHH0bC?Q&?`0J9Kp7=dNmk3sC9j{H_Z5iGXI2d z90)sxwFtwg`T)2{hGSo;3FkPr%X{T~s~jwM*8F<2H5{%bXtR1*KO!V&!p>nx3}fsw z;3654)1)SxBUw7!DwYfCoN3kZHS8|t*@R&W>=1^*Fw$9!9{S5 z;Jk0O8KB%6uV@V;<2MB48rU@qh+#B-HC!YEa)s1{b0C9$aPoTI&(R!|_VU&;3BjGP zGZ+HH===`2NQU4uQWMS*EDq1(Emm{E<%n8a(DDetW3Ve20K*vk$8eDhz#~!<&H<=C zs#^4qZsgDi=$V9||GQ2*utyk1*Yn{b8G>!3CY&Q!8X!>LJ4ZKiXb^ZdVb~vb2x@e-*C=OBX7THbFo za@AHdgN6Aggrf;NhT$-bug`;vWH>5P6V7og9;M@va}9NAf%a_D@(93Huqzk`hLAJy*q zcteYQDu;r<295+)?+s((z2G8Q@b{3Ka9-~hwI4ba9{qF{h5uC85iITJq(IrEPNSUB!hCP)WmE-2@YYK8VdgvLAeEX4})SD7vBsQ$)J2(YQi~` z!CbMCQ?sOv;5$Nn-Cxfn1P{T^Uu*7cVJ=bf;d7#zcR{1UiG2IuWk6VBnR3?{&X@kw62 z_eXtol*V{bp9s(R4FUNi>>38dFhaixE|LNHsMLgWAln5%+9z~3a$eK8eV|7|T=%Iddp>SKkv1 zCA71zB>Xd+heCaH;7#T!Et-gIK&O<%=rmrXksS4d67v3PfALKutIaOtsaRQ1TI zIB;g)IWZyON)4Q@T;o&=d@BS{5Q1!if;Ce=FCr)AfOWi z>f5ogsyc~u+@BoV&~Awp6&dp7WpG*Sk+LB)F+1c!I!o473$3!h+i~iEDt`Ph%OyuI zfE~3A>crC3hhau~^>;i(s+7 zqkF8^WIoyjPxkQnFpvvRmkl=Qrh9bB42tn%uzR*KUY!-=p@cdX?jOT?ShydNnuy=z zKO|yVuiR;H3C}gH>B|9%ODBW4^#7pCD|R}sz=>{LIwBaC`LG_wWgDr9S>sa5S6O=r zLlR^Vm;GTUW}>}xL@+MMGbNTU7f^*_IM&8L!J>U5wYeq$Zs6(l01a-Rz@h5~tf?XKXp`oykeNW1TU& z71qQUeM)M=IiuhJAojG25{b#9uy|W0&&g!cR>fF63~OO5ejqjBo`vbn)0=wb^!(6O zPdb}L*1Kjdtc9`o$Fr6Nd>7>!I2yLCB)hkkz7vx*mc3v}4A34@6V4lpb`@^w-J<2u zW9(GemFWX&FBG=#QD%Tnh9xmTCrV8?2Q-fjK*JP*QG;dM!q_?Uof7C_2*zO{3_(e1 z!Z`wUO?pO8Ih8|5CSf;hk?fg?B&m;~xeOM>&|E4t;T+ATEKY#6FcvSCnsCly{&^flJB>(` zVd=IccFlY?ktPPB2uoogMx-X3gXj&N-jeQcMTvysov?UY6xofUt%|X@1lGb>yj^O- zIg6ex&GR%8ltdgp2@AL7uuEo>2o*63H^E96g^x;2IHxc;UJfn@+~n1ZYJZuw%|g#4 zGT(=tu_cq;#nn_YMi0Q67^81XO*m(?gf8|~6XR;{9~&>ST%z=M*d1F+Ycku9u9IPU z8WzPc{aI?lIi^8!KkD zns5$c0o{he=GGGF1mu3$1zRB5Q=PFk2IW3j4uf*9)P!>=i`tGG_R^%Y2+dz%M{LpT zmH8}7DP(m11S?{6o|KxHEjp&%HElYnJvKW(;;KJB6`ib|HaozI7@h5;CY;mRuAuHv zQ60LVvsA38fs^WstNT>aJ%p?uQG0q3?3`_T%I=M4sAete1+X^8?s-xZ&e?Unz_9?! zwxyE2#G}5>=Sw^uEQBFAM{2@30(EOgcncIgJA%8?_?J={zoEwSHrO>=OhcJ3BeQ1K zcHRO@W8mH-HQ^py=V%Xgs#}xe449UREz?&)r$bCj#ro-}PzQ@V^)EYEL9379WHVjTo!w*oD!ONwjVCC6?WA8 zh-X2yIb38DTsVtzW45E*IYuP)6-FnOUYzfN9kY#Um%~M_gbQWSoSf}w_MXb0ClgU3 z#qu`TE!$Xb8+AfL4MS}y?V!X54c!6@U~#>%eagr%@Je^+WE?nHfuP|frSBJ;{sZ6PpBAqaCG?egZF&YNwbb6!pg zLZF3({~2lQ68p2ICY*=AXR}w=rdcV80PN`uzz(Ko%uuL?5!fA8!5YA>QWMSz^p@2D z?hS3uCrTs=C&A)vpG7+vDYO+a4ky4m7>5^2P3Rm1!;g40N3R`$iCD$QrA<0kL7j?e zskm(VDyXA4EfuxttDr%_v{all9TjR!`NMzNnDS|TOu2x@lxh(y)k}#6ls9y1MC}3P zHuKeha-RA}Frd7?n|wQtP^ z5*hUD{s!!y`Pt24Ik9kcBUZtl)n9|lWpVvV_qdjJSdcn%H9pwY{utqj;FJ+{;OAu6 z`X>6+f2|kQ0hJpkPn%fnwVGA0R$Lo4hPA3O1pk*)XP_6#etm7RQt-D-UNq@VEGky= zsQb-PEkQrehXj@kAq65*k*6GyH+OWmW~ zSXZ7#qxY$#cQmuJhN-uu95WQ(VQSYJ(kk#cE zH=m{Jz5Kb}Xx4GBJez{D?T=?>G{BqEveh+G)aAg}WpL@NK@El`1P?0d@r<3>mu_=1 zhry27KB2nI$s7z9%A&beY9j8$?N2~vyn-({u~jjhO_oTfD*cLt50}rIGj~q#AG;IS zZ6OS-9_ff_$1tA4d0C=-yJ8uJ4HUzyQ@$O%eS6n=>MfytF%~6KT+fI7vJIztRD=hK zt5?xbTL+8kX1H_~)uuG6;zxzLM9kM$Fedt8u1 zxzt4b?06Ru%6fK4IrJ8)Eq424lt>gl2aC6*5Y37uD757;2)Dx`7=&A;CY(d)Q8(>q zhp|u+0r)8_+!nxg?CCNTN@3xD6qdlk|FG1A^YAYnE>?njSjT*?pzdueq({LMJe#oe z{lr!OFxu>%XctT(L(~JyVTk5RO*luixb1)4&oe$+9>LiUcEz^ML|@Z$IBju^%ooEYs+oF(g>Vv?1m{Pv zGt-2VP{80k1dCyC9+aAJ4yTX4=+ovxI;n(X-cMciiw&dpND$*N2Ufv2JoD`3kjpii z^;|Cfg71q`iNoG-1ZG${`-7z;4*` zhz^!Ybbph=7?YD=C5*`lQWMUZs8&#`Zq|8pQi(+smT$|#*fvNI<4}fGFb+kjiP_+w z7K{xXD3v(88i8Kw*_K+bx_fa!{1>QjKkAX6V5sG=W=xeh&Y)@tbWYZKx0QfDDOyOD0YKo zFciB;O*lufKy~cZ*Q||Ah4fe>kxnrFAMAo{vxqhgr+T-MC`RLWSO=qVtki^a8iT4; zG;_s{e;uElNTNZ}3_D~87|HohnEc`c0 zO*jw#+;r?qj~uABvR*k){qsFovu)&8@VEIE{@vC6aQQ6k_eo7S5Bq$tkvmv>U$oOG z`hSC^+eUvCk3NZE75^`=21ej1sR{Q4npIAK(ulyWkGty48VFFqjKEH?21a0esR{Q4 z8e?8f`|xc(r4fM_!_ueG2B=_0;22l~BXG3TgmVHtm3+0F?zB=8#eW1AZX18oyEl-+ z!k>pFu<*Y|YGM|`U$1V~!yhD3_}>l-pHBFLa#{G_3QJ(&zffwzdHCm^nTma>v`^Ia z`6#T}Hu8q9&zU^zAA!qfVZT9Y!g<&iH0xfaQL8qzM}H!n>i>PP3$_#tGZ?Wb)(Gx} zbub!Vmzr=+V_vbcDb)-n!W1I#Cs?*Eft9=y8VF&re-c)}V*fj-3Fon29-K?3zEW(c z!|-z>_3C&|`<&GD10u7-&s=p*ql;P-Qxpc74Age8AO>oM)P!?T3lkAYHD|Q8N}u-uoJdy3_H^kvKWvz!9p02H%d)72h!6x zudc0nQxZY=5G>plf?@agbJMSk(o5GygTOz)nr&lm7z8$W*Y~$@`7G?elA3TI_Ezc@P`aYC z?6B?6UG;1YVQ=xUFN4cxVIP#5a3A*cSg-V%PS}rtHBY0q8wPrZ!sWBDA0##5JnX&a z`}L~6+!H0zlm3;kc-#0Jp7d=w?Fh!lL7WYXU=Yrfns5$b#fUm=C#R-4)kL!zH57_@ zbb0;U#%hbygmYHv+zA7pPAV~ZKP=ytiDBbk zB#3di7FNMHTq8B%oWuNZV^g}7w9|;emtpC)3=E5;B!&_ABCLTCxKnDvIf1UXEBz9d zZ5w^V{H&U4;=QGx!wOjJACsDJ9{ZkrwYD`~`6-F&{@@d?da8!;aVUjV{e`du7XE&z z3HRX-u2<8-A0|=w4~B)?R(?bH!*W^p*TNE5`1hBZa320Xe~Vfa(?5&oq!NUe!SZb( z7~a8=AjV+>tb%b^FE!zu!-`;zzfkl>s}--D+vMk)Rqbpd<1dKM`LJ8Id<^|olTgNL zGpvZQYD!HwXVt6rajAn&(hsdDk%;^UEZ&xgVW8KR!yvpD7QrA~B{kt3!UE+r9Z!_z zkVq#KUw~b(MPb@t&=JLGd=A#ZXxuI};he_8aADiTBb7r$eg?Z?OT@5tniR&E{1jHg zm^>;q;hsrdovdbTBMXx`#AM(XuDZDfCduL%lRj7pW6~ov;hf3RusW)%kAs~^{Q6d& zO>~A}hiqGn;d4|bks;a-mctOOk(zLhs9(KUyhgrQOutv+WJ2;%*a2H4hR+~7k{F89 zVHpg?sZtZpQ4EAt(EuZvK{z(SPT1lw>?KIZVn7-HHQ^k{HieP&V=71>0H1|L+X9Ggj!P_Ns8U(rZ-WJ}z~3S@;XLqz z;WHrPGH*STfcyk@#ukX-n_x{IgYzR;41@EK)P!?5^m?eMOV+iuzhnkM>HVdves9)$ zgJdxv^I#zi$Q-E&=Rg*z4opT{NoNt1ePKszTS{~-W8%@3lE$#?4NGBI_LQ1%j-^-4 zwfYt91Jx*zP@D#fw?$!?$ZE@B5Ke(bFbF3}P3RB=ckRU2_0WY60u!-{^TGq&OIbG( zh_CCJcU(2SZD=A)p<3_;IC{3VU@-BCNT7hRe=S@zW4|RdF^f0y^cU5YQ|f?1ZCNo+ zrl?*EyJj2JQF}y3-2|s1{U6{r6%G^eFYK9s?^eJHhO|BQ0e|LB~kpp2n)B3 ze{vII5dTmIi~XH&`7HK#NKH78{i1wT?d}O~W6M>wyJyo`#Ny|$BepD(v-AOrlrF~P zF<1v<@?)t9=S=1sy2R}?qOkB+u6o1CF=Id>qZix{3t-`&FE!yj{OZK!TG`8MU&MA& z3Bg)ezHLKDZg>a~L~2+Y*dLa_AnYSG;T*z%>hG(o()iqaGJ{ZTfSs^Kk=*tWph#$9 zG}gl^7>#vO6V7QY3Fj%)$?lu|;^>(EWgp8WE}LO@Y`G+dhXEH|9fQ+^r7$?>NliG1 zvodnuuW$0gH+e9IPxq%XenX(%3%h0uD*63#5>#3#!*vxbh~c_iYQi}#nsrncz6@7e zm4Y_&rDqbL&%w^v0!ps>b^yiN7@6B)C5+6iQWMU}tOy&D@?OnIHiNs)STi#If*Ac2 zcFUGga_u+CD5;WxdK8w!Ks_uq;T+VW5R|cdkj^4BeZO|qUDgLJDP4?753GYRnJYEn zoXM)-vWKY4rnX3q`;~?oRcg<-%wG|y{b2WO8_{#q6Ier8Y8kXOup|a;FR2OV&=yBC zPPuaNTy4OkZYQi~|V2o4qy}G*RKHZH=W)P4D z?1U{4!{8^<#AwuD6^zEX)Pzn$Fl`^t{L>t?z(lOV+06e-^4cWnydZ_D!3W^z*;a$( z%zscGV}Bi7G-Ln1(8Mgx{P*V6N2f*YxQ!^0B6=6>mTg3npL3~*4z;zgzh79G-w(u;q|k*$g{hif6eyK|sULE|js{T2xR15zN?eEgfzR{eywFl9i81O3jv1&aSa!ihG zU@dz`xGdJPSA`~K2V;n8$vSmwW!c~DxN1dh)nyChdM-gb0d~|fs1r+9kB52=P;W5O z!=n6RxNsKbW4cFqO=fG6b`1KCD|?NG{|LBp5&a14nr%d%lj!IsUN1x0U9KBoEiBB}OHIU;{QX2KtCCBfEz$TQMI%Zi z7T<)$+p$BT`hY;{T`OZfc-{l!Wx*Q6C1uCde?c)dNF-@;Y5^3Tvq+AO9yp29mt8Sw$s+c zP;3h;VJMb`CNjI1Gyk$shix|fQLXPk(MhEc9|=2VTlJI2OC=vq6yl*s91Hbfup$=f zgQX_oTECWP&G-wWP;CvDwM`Sf?L;E-YFNB2k>tio1Ch202ILj6AO_@RQWMUB^yJ6b z;z^i9F#Zh|ZVMy1LC*jq)WA@@9+tyUoG&%u9K}*PVk$SLjy4K5QfhO#Jev^x7wnKN zqM?kJcl!y(T&SF>;gEJsC;T}$~pTr0!kwI_{g`Jq8mXlDz;2Z>tVsM6}CY-|= ztoWOAiK>~JfYCDv(#v6IY$5I6{qd$rWuVT2g)vYsm716xP>E{FK_xN?)azhpW)M_D z4Fk0m7REqrlA3T1N`0!_P?J*Hd`u^m&|C-0w?#9>Y(XTBF?k=Xh%tGO)P#E`xm=@J z&*jpcgT5%0nA`=+pCKlZ494X1up-9fvr-eznJml&UjY@H{aE?p8RK{CnLldG@ zf^!Wl-xki4k2R9Pm|P7jVoa`(nsCmfKbNZ;pv1{UA~oTh%%Ey5&0Mh& zUd`xhc=SwS)c>Tb-t!bQX_{09YCbHCf!aoD!a1mA?QKn2tPJ^tYk$}!+a|TPdz0dV z8L@p}ZH(A+r6!yc>rpem+I$)%5tsF_a9b`@>@5goF%;`yISj>1q$ZrBn49KMdgMU$ z!9w_vLKD_(OCf8oYBd9W6SpdvNl9Kn3Ak<)KbZ>JH7D`DxjBvyBSPLVLy zASPiwjK*bB6YgoK(_$D6N+TM#!O~}l28m!aZh`eM8aGQ#xTnz=^J?0z!1+ zQ3OeZVsF@Y+h#Epia-NHu_r8tq1at&!g;HhduED5skBejg*pY+Y)fH^F4UPE!AYgfz&%@B9%j!egL~+i)o526-kkd&-Y+ejL-d26VCap ztTp^rp{hWK1b9w`D5Ymk+ znL(WPc*@ntW~xo*oeIX^SI>b(F*rL*O*n_s(>SlLedk0;1mr|mxNXCkVtfcy}=ca`eI3xNN= zK~KSg7?3|mO*jX#Vx(9pViL``Z4aws z+*V3WIOnF$9x<@$q!OQFVEML9X^IV#kvPWWXjlg|HgN;R2}% z=Nx+S)tYw0Hzg5?kHErhNlY;=4`neFH^6cjitD8&+@mPetF?5S=n0bu#W!K$GlU}4 zz);)+%V8+)mYQ&mqR-!=7WnkfQaY&wu?&RO(f}rz zL1L4i?5hZIzmE4vJnUhO2dx83gD9uoJd`rg)TvfJh>Pa~&*- z!Fiw5gmXBHROct74W+XP&{tqbYyo9mEYkkCI3<%Ix(k-Y5Pe>1!a1T|H4p4pw1X_7 zM8fhbSiCKkDJIt1(io5@U_lJX<5CkkkZIk;NSB-lOvEatxT;Bf-^@I9MQ{487p72+ z*y*pXdh9!9^g{wQtR8F+%V70jWoTj+ubJu1sVz7~ZPhwTr05DY!|L?WHwyk<&YbQf&l zWIYT6II%j$XCo|#@p+}xgnKN(0n;WdaXMkS5O!fEu*51DmJ47(49gp&CY)pGsi+E> zemhVS!MFhyZVMx8!+Fp~LQRar^{^Jk;r&t*v%o>sK%GO7L>%sch0h3wKmg-#H>`zm z__EZ5a}NFC2(Q-AUh8o(@%TOLfGv-#y@5fy=xAe1ego@aOnxafp)(P5g5x{QsTU_O z5vy?4k3O!N{v=^yUXVf+Vz;MfyB{5t&FaA}unblYb_`9-Vn4dSSWutU7Mt3(!#J5D zd@StRbeo)t@K8q^3-t?OJuKAEmzszx`;kOzraRF+!RHm)>nKbj7CtQ8mPJ;#AYc&+ zU>t^FEsR4>YQi~(Me0uDLNT~ZJXfuxd8D(5%f+xGwp_Ay=mcC+8X2F9U`>qAo24e4 z^Olufv1y7IPQbx+v3RD zzaHR-l(DvPFD!=v`MT7Eb07n1F1}UC^L;nT48rm!*a=%KSzD(AED3##%#*MpM&@@? z6VAyj2?r8taJShnj*d08aVN_qMmzk?RS!37Nhe^WD`lXzgJm&LE2JizgIXE6@7FhZ zVJ{$9gG=`VGJZqQUI4ph+px0Ec1l7^t7hn)2a99qj*yygj*hk+sL8eAYO7MvnwXwR zusqlqTUc2;cssCSos7~suqsCBRZ6))Hs(hTn2X3fj^3*z+_*ezRLS^Gqj zypoC;v^T-h7_>J^O*n_PD1>J0TBNfG(}!S3Y%z5k6s0sWJ|BcNF+LxVnsCl%Rd7*Y z)b&$eJB|C5h8n?Y&%n%I5w&l??%7g1IAdWtXk1y!8Njc>@)*FcNKH5gxHy{i%9V@f zY6B`Qk6`@)cEuJ})_$x2R$3)P^jlaIL-Z@D3FnA{F;va>>J5EbD49WUR{o!>j{6jY zt4JRsvm92$$SjeX(8&mv8sgOonkyHWh*i*Pg}_9t;=OZPZyrIdve>J{L>| zURCzh@r5A__N z=JQDpi}ICl;VjCNvmNEWA(HvM8 zRF31kdRbc@OlD9_AA;Spjp?>g4?aJXki+8pAS{5z_q*NWyC(B-UMgk10IFFT;WJ^O znu$@vC4YQa|5`PbLlJ%kcF;D$oyDbOgoC|{9c?Vye}l!aX#YiOBJM&wMZmJ&vSKW1 zvb^JZ>I4#!_D4%6J{()1q{osuo#AAC#ebNSQb=T<2m(tVOg77NTd^z z6JQr?TgtAcXVFlsivf8tEQJ9%Mry)6kYMIV&GuAAQ$5a!1#vn7DZ(yH4@g`-12O_j zVL)Ws>hU=SB6V7oBY_1oZeok%A)?WC@41)7C?1U|x&KAWKoP;vQy>bCqF-GjSnzAjvB^{BPW0uyTCnj<+3guE zSBEDrp6DGb7StCT>c1^+8|WnTt8wqJU-t`>r>Od?zUfegtCjNB$+t}msIG3TvK_PS zRV2c7gNdWop4IT{jkTrCW9kjR_IQ6&TYjOMZ>%+V6uL1~S08GHOAe z5WZ`E8_?IP*9<%E~kdG(KG(xZeF*a@gsr# zQQ0&82i@!g&-fs^Q$6Fq2baYjM)!v%X7L#>TGiU-A6;I|(+R50s@6ZJALYN#c9a*K zsP?}F4`%H}nnn!8JABD3bs{Mt*;h+5l9JyRwqi6VJC z?2@@h!W#a>PVMQK;K`PFybXnVSZI%hb+FLBP--F`$336u%=AJ_-$-$^Q9NJUbk<2F z5@WD@TN2T1J|odq#bEfb6b55hYQi~;9&cE`m4%WB!vrkc7DHBp2=y=q7sEOjgNvjl zoHN)l?CvyLf;)kUs zoKsvm>a`k;qE|@~OuuU~enaf;hh4K}7ad8dvokAZ*zSXcF>LorO*qG4 z*UrdEW)Psi!cN!%ijMly0VT9CDu062Fe*<xpRV)KG%`Xj zfE6)9&y$*PPN-kCn%qIn>OtwZPn=9t3a|sVRHF4Wok~X+{$LCYcXqsvUZzU{^!CNjh;T+z|V8o-YdvD}A6ur8(%QoXT z1ny|qHCu35GYDqI4BJt#Fox}LsR`%UmUU=#y0@wAg@$~>^%~eETU^oR!pwHoQOZzl zgheq_uauf_j%slbkX*^j=bFBL{;QTph%SU(u|*UeAg&`yD`a>sfCVu;Z;+bM@d$P< z#9J_E=}%xHRuq;CXS(cTDB zuQsdAtu?S6gez%I;-UjlcDJ7kOOdG?P!CY_&uVzI#=!b;* zShQaVi(=7!zSKnA3XUXTGu=~MGU`|Sx_VU7(yi7TVY!6KhuyKoWPWre8pDvTm*Eb6AOy<>FT3(SFv@sxB`JXS$_52mH!p6RGi zJ6In2m+fHrcYO!TZfpljXN#b^gKDf=NVU&sJImwUYIS=%%T+;9SIf4u{G^+GU^`0? z=&81|{0J_KHS&i-6Pc5WGe3nZI@M&LXJ&e?ET}fyS^D6@S(JN16SElQ&c>Bg=cbcN z@2UM@$IS039@Q?}S=PXXvS{u#+tD11_J^$F&y@*1lVW=+?51sO4fCzqbFZ_Vr7gD| zz<49e$*?%a;Kc3>vbPjT`&-HVDyd4H%%Iqh!|vI}{`tKB)jjrI_p_8>oeaaM)I{7n zDiFz;UZq%=%X{T~PECF5_pGLJ+C6624O?PI!^D!hSi>ASk^fP=fhQ%^`$4E^$#}_Torv?X6BEc%c z;%#9a0mI5##UFt+GDdl+37wH(rZJwZq!FUPM6BXC>C-+|L30JuQnAPMRnYv;v{cNU zz6zS_o0f_Pr8ABJ5jB1Mmw(yx@eTU)@lt*IIQ6`bri<_H)|T4S#Ww|}KQ5at{&F|_ zz;tmC*r}$AzX+Gb9xZo>lp7`JFnfbp_+lX4N&M$p-jl!DJ z%HFwiN~?l@c2NKL|C$>WjzMlzhy$nJtFSHgL3))V*s(9|$`ZfRsIpA!IsQ1+x2#D& zacf<=-pik>&MeHhikW9qP>zCwV%u_;t*()xE|)PM4wuf_?;)Xy%;)0FFQavqa#G#h zPAY}jdar-f>ka2rNYQj0Ur6ITR z;ugOUQqz{~cs5b%??Zc^w$uz~JY+mClcE{4`LI9+Z5ycx=g=01&}v?tE{aQa-LyQy zwLk2NEiS`HU>UeNav7_AV0DbubEPJnvl<*$&iVAThAY~x5K1B{kHW%jsThu6;P0JK90T$&EQ$g7fz*V1 zAY~3DOd=pX{jPf1RzN}}49Hwq6a(^)XDyJ0tg3X>c2@%=(q%XN#gA&v|cPQ5v!<&>nt8UyKDQnHLMOB09Y;W zp9YI$Oiq!Sh#SyJgmtEu_XmPo8#k#@fc`m4GJ{~XU?*&089sP6!AeMF4W|x^W00y+ z6V4&UbLgpd6(Lh0d?-1i>KC2fU+mouYjd7Jnxp8aE_<1Qmvft*Q@FNWha&3 z+yTqCg=3glH^GTSvZnJHSQ%sUX{iaFji8SacT?!iComDKaMewjcU*N-x_X2uR5gAB zN6)rutmJbnfe=;&ehDjJRp95LiCOG+^cM?$rCDrlO~*A(rr-_^xEdquY7FjBM;43n zLRboma=+9>T*c>yCNg{C(q3Q>e*#hxVK^8TZd=E9G-C+mFa&F184SVxQWMS*EDApA zRd)eZD>?np!*muQc^T}8Et1X!>2Y^KJ$8mt@)(*8upEYFz0`zrH1iE>@9i{#aXu{F z7RFBI_CbOeh0U-IMxiM+;haKW-LI8BHm@F~5{dtS<=c|j#Y`e9p0Ri@tc0<+N@~J6 zi-Be}*Q(@$eUj-Hcrt^Cd;xaCmWbh+kKjR-kj9{V4i>|p+%7fY9Lkb#`)YN#vDq(< zjy1K-LM)f?{0w%-7LQ>TEx@CTWQ=|aD`Jcum6~wQXl3MnH0_yFmnx+%cAd!h4e=US z=&DC-Skg`MN(*Mx`e0p*T94F(b83Up#_-{4t5VP!mYzwRhG1uG+m+$YgAOOP8N4l# z0oo51!~m_4ns5$iMFfaG5;Qa_<1Yx+OJTQcQ5kMRNTN!LWxP&@RWV+tN=-QDwJ79e z>_Mcnh|nh35nDnTgPoK-hNc0_VQ6Yn6VB1B3ePiZqsbMDVDEw5 zvjt|DFAbVhmT1QAN>~|VHz_sYoZaGR`YKm0o~!lDwLIeVS=bd@PKM8;15RmyjLvPa z9!BRDsR`$Fg0WG}_v#IOekYkhTz&#OVap|Z&=g5yP<{l9VNf2Dn$V#L)(7Gx2Abs- zn21%lT4Ly3=cx~!x1DxQKv)cm@&Qs4ab4e+K+W`~h2BE7HLTAYMTrFCY*@T4 zjOS!k^`W*Z#^Ov^3uAGH)P!>uJ^3-Oo_^?15^;DfEZmlZVK%khC_+Vy!WLKwqtKF? za86a;exiQpJx-DYhj0M=@`bXZ8}UTBXtd|ijlfnYQj0G#cluV zzB)Qz!=&XAqc6g)*fM%<=94U~kpa3Bmc#(vAvNI~&_KRg&6(&VGYHMkVJB?S7{)%) z!!1_FxI6~yVO)MJHQ}C%nwiX%{mN)_Ed9C}NMsO~g^OMFbF;Z5L@+M>upY)`zSM+! zE^4{R%q5XQT-L%)OuxM(L@+M1n^7^Cx~CY&=;n?}^Si5FCzX)&4Z7+oXFs?iT?|JLEQaBjD>X42I2x^C0}e_h9Q(oYZCgk;9HfBZSObe; zIQEj7m<=4k=SuuNLaBt~bXfjO;2;GI$EmOwhT~+Z3FkPLj(G)d+^ZLI>d22mOFxx_ zXA__X?2s*>>vnN+m8=!t!mobbF4G0)}G}7Q=8{ zCN*J+a}uGsL(#nFnd zk>UyGujs;3ep5rsXLx*B10p%4_$C_D#N!YJ%4HQ}7X+%r=MN~L|GPST06X4__v-AOu= zNB=lj28;fSq$Zq4pDt>tRU6uCA(2k4p$NNR3nP0vB)qjH)W({`2rP#|$xBT*hcd5N z(GR8yQV7H)uxwipJ7*53gVGs-x5Gjhg11UdI7hHtEuiPdy)BJmWi%IlHmZFoY5D;H zx(Rm57Legn{`RoKpqBCaD6EU|`iRtob6yJ*5lFpHCQ>;>=mFRbTSD1e9+LVPns38` z7@BWNO*luhvexihg{rzIWK%9*9j{d@Y8FvzO&Px-Tu;NU+2S%Bq1kRwX3Y%TpJ8bX z+#jVToP!&PgOF}g$qYiY%Q9DeCMeUuwcRh2D`?xt!CUV^JcJ@L}<`B(lGEY^!1{hG8v?MNVqM zIg7bz45gQ@jmD4{!m{CRW{EQ3Y=%~BKYqu)xs9!ghqmO1_h);#^_w|MkF z1j}I2|De=_`{<{KilxtVqW?`;^Yo){7$@EX%V5#JTWZ32^m~INZS}>CD3RU*Pr~AD zF=W35+N#=ttaPp}`5mlc-rm}-u35$9CW>w=ah|+e;UG-oM zSKzh>87939*$P+~L$*|E!Z|W^T89BsCza4V50-D+fU@^3M!Fb|BVaKM$DvXa&T-5S zH(;gS<`eBSLU9f(-4;ccCG%iY7=Nk13YNk^yj*I+If!{`x+skyOd$wwf@Rx6$esZX z^su(@Mpy_#@H(jp=LmZ8)tYvV0VNTH55mH2A!LuTLq&|j2VfX&Gj)bd`kfvn z5}3nb@wV+Jdz{!-#aJ8yYhf%7l$vnPVuA9S4pU0wNTd^ySHdpX66vz%p`(pKIUAP4 zpqwc+;T+1saFN}FC6z;9-T=E{3nqI3H>r=Ic`Yo6q1hre;T}z09bji{F$j%I0CE7j%a!B#1LZ41vPNMD8>vW1lWg)URdNPQ7j#Yo*L zHQ}68zk2C-jeN0~e&@u=MCO;U1GZ$czXa_lV?=%qt6@YQlbUc&WFV}O1}4c2qB6Lx zt3Gq~_J)Ky#$_R_hjHnbnsCo0e4LxOBr=H0!LSpyttFdFLImTo7S_YK>@PLpoXa+a zk@T}GNFWX`gGJkNFkF<^UOZ8Sv-od-b+Guamzr=Mf4Yz;xL0bVuCAC#v(Ph%%lWV~ zwp_A5k`A9+u}a2hGpva*YD!HwXGE_K!@ZKp3}W*iuoJdyvY%Zcn~X~)-wW$uT&|Lu zaL#3s>f^+9GIgS3I*ZtR0d~Zejp6pZ_VX;IkhPl6!HO82+odL))9F>yxPC?ZR5wZ_ zB0q!0+Y-s19BZp$EPe`WVJse%n$TGYZas-FPN6G11SVn?t}afQx4xk-fCed48Ma&L zsylADn>{F&Re=?-0#*fJ0O2D$qlkL>Bi$ zVL>eJ2T4uDm41lO&GZc_OVx3{>FYRChuNJ#(Dh40N@o$D55SJt@-Zy;1bkBR7@F%~ISkGFq$ZrB=~oA^8L-63gybu*1GY#E z`^^F*9a#*=U9c2}~fjzA2H|Cz7~7XLG( zCY;BAP`RA>!MC1CR9*`^V@oA#yqT8B0BwNkny?+#IX232CHE4|FP7B^Z55x!g-l=Q-~6Y z!opRqddh|mXM&c{mc&5x!$KH{`BD?kLG)EB=Lh}cG>J|sp;!ycw`~v^52Z*HW3fN1 zgt6F1YQi~-o(cm&Nkm}-EZmlY;dF$sK?HIbg7vTrhG3o4gmVP_m0-N+6-w#$5GNCg z&9DQuC^BZcIFI8SQAIgXyd?=%WZA`I_^h1+7tY7v1PhTtk#219VU)P!>c zgKFW#&wGugX$D2lBrKnUow3D|F)-95GC;S(f*7D%r6!yM8VF{$M(fp9O?!zaGYHI2 zVJB?C7(V(7-XIBS49cUh7zX8GsR`#$2HQOtBN#oC;Ph?ps=u4jYBY%qP!BAK0h%i{ z;U18B$A@=2n_AAGmPvs2gPpN$JsE&B6%5cCSP%oWm(+xFK;fPACOF9q0&_a-ge{ni z$6C-wY)fNMPKCuVC?`uzIES)`c2k(Jq_YT219rp~OvW00N*+T~gXJ(Z<5CmO(JUn2 z4a;1q90GGC?1n9vj8({_IL2iXR>QblCN<%l%YxuxVW5&oCnmSSF4!_LY?=)^XR$Cw zOUAJIKOmD9$ml!->tS>rl$vl(XF)J#Fpx>4 z6P0;8xau!ww3k>IBQgip!iYTctR=EI8Ymd3Xn90tZ#Wv$Y%ghnjLx309!6((sR`%p zWo2-BWNx!x935+BZFI@_4beIUcFmTS;Rb}@>1GzpsGS7sV$@EMns82Sz;85*rv1pt z4B}ITov`JTF}g`eV^GSl7zU*%HQ^jePqV1+rl2GO@oreSEr^WS=1>kp@J?6;LvV@I zgmVP_YJ-G2zNDaiyc{PJiciB1*rLc-f9c3#I6etWVK{D*nsAO|1^HbrR{UTKPjI8K zHpy)K1wr~D?3OJg!?`8Fi^U|C@%lcjit&0tYQj0M?I;8dKOcPQl7TGiM?~$PuyeN5 z3`Z-+)R<%j?(eWL2JUI83FqLp4d$X7elGZmwH}-k7&N)`!l(Hs1a0+>uEs)!Tf0MO zCb`VaI0lw(+hPnyPY0c862vGR z4eMYOj*^;iPC;u62c>ziTq2W)-LWNO)lv@PU)8+^R>T->l$vnPXz_5hRVg&oa&1jr z2a+bFK@&7_XAoE?e#PLjv|k*hyPp2b$+RyGUmce-Dde5Pu^z;T+=b!FdKriogZyN-xJbRySVCIA7Ms1wIht|wy;JOlrb8qg94VTQ2J7jM`FI7o)aV zYQj0SK|i>lB=6^F+D+?eXqm+62-q3hhLzFN&{Qx$hr)swpo63)oC8`Mp3q*b=7M{h zwdSPd5uR7UuGr$qn6OO?WOQB*>tS@xlA3T%NA<WOD`A<_aFokg%7 zMrVQ4gmXFrge6yRXlRld#N`m!3ERe#u}Lx^jX^mO7Q>(%AT{9}%EGqiIwHQ}5~)F#F~?IZPM22pt}?1U|q?u{jo#-MD0#V{x> zsR`#$q9#!vZ)j9fIfUg}*bQ4O-5X0Nj&ZpLR>QblEj8hs%cAykqQb$Xvxv+WVMlDq zWE`5BlE={83Cm$rj;5zpu4=pQD2Y(~92RbiBIE1VP!2=z7%YP!__5T4a|Eks zB&Q~Ca*fvbxL4nrYkI?F?Fp9oD+0A}H&;F7jP>y>(TrU`tc3$S!I#YXb`(96NNs?fu_cu;WvWSJ zfY!r;7@&1h6V3tc5W;DWd6n>3&yZ5xE3y;Vy8Mo?ZH8U6#g=jSNw#oCunB8p1kaP2 za87XhI1uU!*-U`hKO=tcg&nlzH)P&snJJs$y9$=Z@Leu7;T+$x;PEr6&ZAUEvY8%| zhI|6{IoKszU>TF)Tqq-TJFJP3x>ahzIjNl^N>e{AyZ@B<{S#r_So$DO9I)qyQ^WrK^7o2@r+{+td4PdPqfZ4Io8QQJ#u!a23=f*`0J#o-|=Ij?Elj+FHy z;&wXhoGrJE&6ydJ8MsqnVGP{KQWMU>t!k=y6V=?*5wgK84Y_f@>8WF?y!1x0%wG|@ z2JD_Kx{Q%{mT1PV1}kIi#-%27c7Exqd2{BxesWFe^qx6$Hcpn#pg$Yv&sp^6?C?)p z#W{U*=1_>2t_$NZKs*7< zwgqAMzPJq{(8CZs4hvxjej+vD96`@!udXfZQxZW~w1=zyyW!sXHiS?Sqp$#0!YK4g zO*p5}TUPUm`kHZ+NF)w~#oIOq!y!~rb0AfW#R0Gu#$sQo37v)DO&)g!=rtrT5vy?3 z85lgF((uRCGTb`9IabvU2-Y*Hio6DnscjW8Z1m9Nu{yF57Q^buD?<~r*hv^Ds^iCs zUb%R_w)mOMpjcl3yJs6~!{9g_>!E~77WOy5x>(p>D>V@}iY-KLruz#E)uHa?e6HY^ z^>I}yhX`E{yJ1VnFbviRC3P}H?}vpkMAu49I7hUATvuzm>5g3@o$%ZZyI_mQu>aYJ zC(y{?d>NL-;CxYP!a1DfbkKgV@b0(YE@?`p9}uqJz)soXI)o2AbzI>M1}W8y+b>~# zjN8wpCT4+KFvT|RHG*}p=2&n+o6e2@fVeGN?WzNB!HtVz+y-HNjN3w~3Fq9Fb{>Yo z>Sdv-P3`b(Vs;qpkZoHtESl(TEqucUZLO`C5jz-G$B38%XF6=2iK3aDgoJzm}SC9{R-KHFZv7BAs3~ z+wJM9w;en|4&h;60T;-^zEo<$dDu;((Nz3RKOhj#gPlU^|Dhbj5paPF#Gz6XvjBpR zAB5dO5FEh~d;l(xA-GOz!a0Iyh?k0dlt=;p8Y~_O_>nx|Ux5o`0l!OX zLJzoLL>3R~XhBFTHTNUl9Z!M2%!QbGB+SUTYs(D&mn{GvI zaUv~$fwiy-@>FPI7Dsyv)dv_OzSk(S??F>JXzYIMJ{<<~NJlt0wzrMDDGygSO<33`Db>3fxHkF$v|EpHQ^lO zjyg!Sn$;{;TGf`;>bm`qV15L4(iZ0PP?!lP7|t7DnGEOkQWMT`ZmZ)ww^h-04x4{M z2)_wCW{dDp6k#Zw!Mg_*$l%>AHQ^lIV7^}6TnKJ1YN~6Cw4HT&CIR~+?2Iii(+Ah7 zSDz-9q53^6jiLID)P!?X;fH1QV&mLgW2`vR^b5I7Ub&?q%lZ+aTlL&{5MbBGwncaN zRBbJogEWX|2)BhLGK9;dCY&SOx$YNQ>KJp?*z)J*HW!;?iI}7p%DexRa6TV))E4K_ zQ{n9D2!nbgER;b#Olm@hDwsQor(bA{FEA0Sa5eqXcfzJ>`Pj1d=Ix|XjX4*NqHT>a z+|C#YV%1>`R>7)+ADWoO$(H0U^VG~{GJ}FV0lQ}#WWzx~?I3sEcYZM}h{gLNsfoDa zznPHD^wRsHTtVG(lpFEX%>&xb%XAjO`55eoEgZv{=@FciMAjT`ge5UNAC{VMj;BB8 z)$?P;OFeLX%O*luA*cp*}k|oj!%3om@Y@rxV z*zQ1yHo5XuoIk;O7?mfbCY)1=R>)FFqD11c^WLty@1}3as5>!~wNG*fSPX-)oz#R5 zL(n0M`*hTm5SWNnxa!j_KA~J}`o|YXMuOIn9w2CWR6|aMBWl}X4NGciaqJm-GOUGF zkP|}_v)Idv+FO@KI1amI8)8!jBFR6qDVAVKEYhP=6LBLb5UiQ*-z`)vpsp^42rjZr zw}4a*)&I+2H*Db;7B^G%e<&%FwS`MzU5wB>q$Zpb8l>*6x;HzVpG!m1Gl|p9urs!t z40DKSPMTPT>f^99hU$N%CUjJSms0#*qlc%!M6ANqdrkjDKJ~cOGpWWr4#(8C#ss@} zkK&!ppTGsOdhnyr#4NtPl5-KMm?kqQp1o^a^(E)4cphZ=WbGROA!sROe5FU4Y_l`10Zu zxI`BBlcXk`$KABUKLx<_10qp}oq|XhKE|lRB{C9asfk%2!MFHpB=`@A#1*hp5DCNA z81IHlWF+1xHQ}5D-{qg;z_W?MXJCgQ3WiTHJ`I=1D11_C!Z`)D&p!o$Nz;$0W7*Td>q@YhLA#Es=8#BZjDjr}9lx}R5P2c_FboJ?(EGwgsZ zA;TRtrsg};(a9P|6V}G~oF_Hmo{v}Y%3B*nZDVqOCz<%X7j|H#`E&#^K3Boo7@y0f zCYJRm6~wQ&7yPoQ&_w$ zBf}R^Mn+jH-;ct|7@3EqCUi1_9$?&wq)v&zM6AM9CvxGjjfT1trR+8JgV|C!)SOrB z>uMZfI0!o_j5XS&uo6}^7KbKgv8ULl-tD{#(n+Nd9|}8W+Zy-cPs~J!gN{cij)nT5 z|BtydfwQBi_J1Igecux{fhYuo34|R{S%YjL5H=BLZszvP+{vALFLy~Y5fBs=bwtr1 zxZ#FAT%P*W=e{EDxbOQ0h>D7ePyep}>OS}0?y6sP-#TZeM)^DwURQDM@0@Q}b#-;s zDYhaAb-&a^a{oV`XifIGaM4g$3F~SfVos&!-d0j0#~GPXOej4VB7nvp3MSY6Ck z2COcWn()qQ@n}^gJyrt{wHu>ZQm2LLV|gqlTGz52a;0VY1fHe^ask&ETN>cnE;ZpD zm&4HGwQTXOXeDmCF9#cCSGs8hq#y3j&2`l^w@KmgD$C{VL$Uku3o5 zUoSOb2427}!GF=r=s_hgk*K&JHc^xjq9{8y6WGp|%7UGbqwdock=G#bt5J9TO*&3e&7r0k8mXC)X^)Vs#W#Jrjc6wXKeqQdG@X5 z?Cm`Y@AzDmQe(JUkB*$(J-h{)_Se|I1x@?Qv5BeS7_(Y*UbWaNhx?zVQuc%u^T=u_ zC20R(JL(wJ@x^P^gH-7`Jj%Wk>wzf$mHp!odNo_HhQAVMCGTTen8`d)09d~y9 zTBa}@2AsrJ1aUt>8h7ciO3Om21zjtlkYCMq&^6?iMJSe#=O?GUlC2KHf4S5|^31=K z2u}9L1|tEbM#?B7RyVO-ab;yWx5~zewX*s8;yqoQUD<#XKAseMm zrbf%$#2eY#fY9ruCcG2M&OoU(t?X*l%I;%3=*sU&`08onXXf?m=G$zuNES$bjx7~% zep+h6JI=f;lp4^yiiGqLwv(<%PnIFgOIrCGwpf7rE2#QiEp}61L;n&bYQeORiBHwq6M=k6{Z0U>l?+yaUV1 zO{sy)szd;Ed|Crkl1>yP3LI>Ara#Q@kZMA($7F&d;QiLTGVL7f`6b%688+)RqI`wNU5o4f+yW7DW9E zQWHrd_$+~&>}eT2d!+_QFCs*LVmsoB=&-C_w41Uooq@9*;pFA;4{Tuo>332S-XSG< zEHyyMWrDM&-`D%Xx}I<{lU%N3ivlpqq$a$BNhSZ#$fT|lnkTYdaBVl1lU($!t$Ut1 zK2J_$s{%I1NlkcXV2Eu9>;+*{a+;5KX`X$?lE)~by_P&)#QrVlQ!a>22nI*ue*Lq2 zcBM7CHB=uNcAujfNVC*bzfrA}$9CU5KDS=jkwl@*WVMQPyfS(G`1Kbz!g^!<$c|x^ zUVQ!O;r4cPiq(O}dUP!gP=8%@B=MT&a~9KGd3%gjxK^A@(A_bi9(#MNV)DNgEZ(dR zQ!5N*tx3jW^62yIptv^GC2MP>C}&mjXV||E4fbA&=aj7Br;jo&tCD}scFZ-bIjfQ% zV*e;a^MU+mdRUb_FIq#dy0+6**QV`t7}rYZZk>Nb&LChS59#rhoeAVdvp=n<9iikI zGlwk!f;=-eA$WF6KXJ_qf=1XZG@JDxFk+l8p%@>|cF^_K?e5>yW89zC0P#MQEeGO# zu+&8I&hc16Ht7=^L7!t-Vh-Ik;cpqd- z19NQAUIgVMoKY z^<<3^bcKNDAhsaDvsP-tdo!95JFoX;?Q4YNJhpUK9Q$N67*Yfn&Snb%3};GBm>2|e zTge9vda)IlNK{OZoBQNXk4@ifd{-Ecuh5-v9Xooicf!Kdoe(Ji_Pf|W4eWQsCZ=#Y zVWAo;skfr&K+5P9NpE6iAw~F3wwtaI?&hXd<2e=KenT4s{FQ7a5b!&sCX(CyHexo} z^9hU9mf*0yy%5dHTi9SR(Yc51kSm?;67n`3&=Z(1UE3TnTm0yzaE0DATT{5D@*GEd~(%L~6o2qWSGlZIb{&@)e`=utl2Y#V)SuJ5wNop%`b~^2InpsFVe#Umj6$iX9 z>e_(JkJ(Cq%nzg{ypvg@lL;!()N{GCORdt?d!atXuQ&j0o`=A zC_pz&YQj6ZS!zi~P&Xem-K&JEh&!8ac%kqQX>IqaW@z&}N5!h7InR^q7?y_f1-r100-;$6eP!g!*$wE%%CTLBO# zOHFtuFsoA86%BWF1l_B|;AL$2t_;lnr=tcSyp$~gAiP*=!aIb%3J{=66#fsgg}a7- znb`y)9T5BXv40<8{~oCc@3Eg+;Pg+!&ocE**8*-i{?KxP|T36L3> z;!4E)F0SbUCf{c304Cp%n()qK9{Fzh-lUZfk$q2ynekYQj6NxnZMOQrop)EpNJjz zzGlf>x&tGBZqn;xHuw1G+!5MFF};q$a$hTNsq9 zYIwd~WztmdsvGp4&@3cKdmZO%sBV6OHnahmRcs|dX1UaacQSnu7@Y@QA_`Ap3wLcQ z=1Xg=17iOK_U}XNkC&S89{Z*35nWA9nbc~Q@tmy!aoNgt%9V?`bIYO;C5V3{7V&xx+dWra-NSS`?@AeJ0ot?Jk^tHbQWM^xEmA83 zo29|Apk`Y%0Hp-!Eo^sOA?;^;7&X-aod04=0XTO_O?Zd1tkb8IqJOb{k+aqyNMB;R zOREG>Utr4tP@k2W@D6HGp;!!*|KT+)%SQ$%B|v{*yW zp2ijeST2*A@Q!6s`+l@f0zfIDc_!N(S2X6s#8d}xp23y^aITe_@D8VE!pb+XrMrSy zZj6mH5>>u|Edav*TB!-|;WzI7BlUMnD5bXXdA2*QNb=jpk+{$7Gi)gU=U%A^?{F4u zt+pz~hWcPytJa%38l#NB{Fd#CD;V>6qAP(^`{faM{n3GY~@t3wEM81WTCu=E69 zBVIH8L8Jl#zliEj7l&uQrT_iQ( zo!$Y_zWp@5t!kQ5ODv=}ErP)fL7&vwTZ7mVKwbpYozY$*WeRZu!Ll0CB)!TL1YJy%%p0S9}B zSS>*NBwG?d`?%DEcW4XsDCiB!EF?s~VmsrC$XqdCXah38U@HMK_e)K9C$qd(uSPFI zY9WVOglO&X>{W=;!V`Us%*}O(R-M3V9$OEvnk_Zqoz=>LLV2K7E~ukoYGJ+E+8Tex zG3G?GDiN;@Z0B5CmAT3{Ln{zl&sGG)4wIVjPHgekR;gT6Iq=nh-m-cgi;2=DY=>Ma znWrd#KH#&3tp)gOmYVR+XGQeUu~{!v8qqNdYE4MFHCizmR7M>lwTtbVD=Bk_oLwp4 z+QAkCxSCQE-f=AqqvI|H!hmMVjA4XPNPzBSJL3w-97Y&F4`g1+Rsv-1kecvLWj)s+F3kFkXSmJdr!c*mlK)@sQ|=ZqzzV>1g0%!6!a zT)~(P#?S_2e$G|`WPTzw;hoIl$a|G9pmWBO!3K+o&fF*X8gH8;1JDP2X0f#ZpFXJx z?|hcf0+3Rr)@s^1HA^`W>Sw#;+J4OQl~5rddOTYUAX+Ci;T=)*UOEsq8bP(y%%Ehi zLZB{WJLU??^6H8@fz|nJJ;3T*sR{3_;*-wRq-xMmXVe>gol!<~wzFMvrDKi{qSMb4 zWq_r@6mr`h)ApDa{c~mKLt>JBwZ^p7HQ}AlTmlo+8wQ+o0g-tH+X+`P=JLaoCctt# zTLrY1-BJ_YY0PVTJ{lG(`b&;hLOedocEgp2nT;*23V?iwEdzkOUuwcTkWMQYErdFc zbOGV`8QTe092w0c(gZYq%vJ$3ejqjBokph-)JGcz4y}ZE%sk20h&iKq#Hs+ubhZot zGEHj2JCOP9o}q$7^ddrXDBBU&CSqpv)N}!pgV{QO$z!D^yff*mm8-^zD!N1rHnD}f zGBCG-#yTMO=dgbtV*eDW3GcCAMXzRR;w5ObMn?L5p%Y_J)SF2-?fo?oA9D>HugjWlI8RFP56{4sBsjY6RtI5vqpu0Xf0a|G_C!mCcGosr-P?=$CzK0 zz@5!@)U|JXywSvRbpyjQ*}8z?=~5Hk8SbscBCm(ctw{tcY&TsA9-2qcPz?aLv1I|k zVW|o409Qm){?W_XfEt|)g(_9gXvrLwWYi&OFJZgp3e8;gV^<2eUc?pzxW=U>yyIFK z1wk#+jq{8Ig{F1&PgW%Y_FlGguE5M?j~QBl*t^+^fY>{wCcG0{)l@Scs)4Dzq0xx| z!D!el6bsFQK6jp3ieqZ#l zapVH}YYY8#G5vLE{8wAWRkNl|quA~^G8&o-Oe89%A0af6sCZz#&_tr*XR(RGL}GF8 z#8qsUnn+@Cfz(72i%k=WMQiKGPxn-}N1*PJLvGw1-B&ME_t)^2kz3SX>h>yT6s#K= z$tW4AW))ni?#k4UjQnwi`sMcT+^T+Vd`)dE9BuX2)Mi8Vzvx}2S+AC%3sLHBSJg*O zd{WMv=DLxR`ni$6N5An8^+!08(?qU_s#et>n?`E!pRxVp^6Xp9*}I}B!sBz*#?IlY z%K1DR?jGI(eazA9--15o$k@cxpp0=XI>r0HKXt03JnOV_q0tD91sdI}6xAEpj=4s4_S%8=??W`NWB({bb60*e z*JbuUg6=22XXlR=cB+o0yrWcX4(kIGy@=xaI<||haZOKV8J%6frV+w^CtDMQ{gqM^ z$z$gZBA4}ikn-uczlX2+?7XqA*{(G)&v~xVQT>nACsE!&S6gEb8znY zo6gi>q^=W@U$R|rC6azAwhaot+bjB9F%gU%jq%LN==1MA^Zo}-p?5Y9Xg=}$v?tG~U@90)`f^f+x z7oK&>spp zdo|lBS89jnP_yU-Vy|E;17f#JO?W4^B#J;zH)$y+UY}sQ4#wep^ za46dq*JhA6ifcCmLKJk=LZdjCEe=pUR%*gTCHVG}e5|0RDKL?!=rL;~?<}`$FIqaw z&Q-cgE@wy4^)AWlEIS(DzPOYv2kwiDViQx?SsJZR8}S)s6!WLEopgXU5uT(O;)GaT<=%kltvw~w=B0?3a@O_(4BkF4aWM}4foM54mi zQ*U1M1R6uqDAg@|)Pi6g~mx1?ucLmCc}8 z4+4FjQ7fUCFF(cCtHXwzwwj80e_AcXe+gS2#DAgGMAACu#U>{GORvJAR^?@YN=gXS z@oYC-8;3inL=%8IhAj_3ZIGJq4k}Ir5-su>tVfGu^f6_$uretlSXZ!JafNlXW2@3t z0j{UA1p=;1q$a%Mn%`dH8INLgT6z(|x`FM8E3Bg&VQG4S)pcxrz-pJ&gm+e%LrJZX zW!55scd^}bCHN#qg3g1^*RiDn%sZtfyu<7psMd_5A?Ole`Yckf@FBDVNcUn6qOHG(q1alb4Gzh(D3rr*`Xc|OdB2hu>g#{)O6+5Jh`w|tj27E#)E}6IrTJJw0 z75x)Yq4th^^WV02Wd2OGVdtcXU1WjQoYI2SWZQsfpy#`v;^Rby zAk!!!HSO?K)LuxEu@74Y5LgqN$n2;m|E6E4CSKH(Y?N0fnuFCIrsHNIxp^|%P1hSg zeH5h;?S4Zi1pkR_RS^6Wr6!Ww|2X28b^A-VOf62GCt%@(#YC#ecF2{KIbYQ2qEx%+ z2d%)Wz}5wcH+3d!6pnuerl1U_$OYXUxRlA7?&XLe(I(ZVLVOkBRg zcEFWO)@Us1>bly1$rssrfXU~jCj2w0heI|d-OI$}QMLn~Sg8r`#HOp0-t}=?e1(`?&6e%TBz+<{ya6di0O3lu z6o7EK)P#2k3l#*F$z!xutzhyQvyjl-#CFCNP43I7t`jKT$W{fEu9uqdPHBx!DX2u> z%gd!*p|PzeryjApo9&`2JM#lE;iWP{X%cOZQgph1@i#T9Jft|-hTIaYzJI<gs*m{7;E~yFcO!^|fbsltySlq=H?#d#!ZA6-Y z!|T{ufWw_q6W%#2R3Bo(fr9#wZkc#73yIBV+0MAK$$N`5bONPMu~h-3d!#13Q<|$f zZFR&+wN*2o?&$)e^Dx^9S32pO@8c_K(vfUcK&fA9!aJq8(J;;0 z%cTm4&Sh*TTD-r>xNx(y4O)O7-LFWUuI zFlH+0^n^g74zPTJEeNoDRBFOImIa+>tp$xyMtB}#yW)z+%omi#qpJi&53of6qMu1k zctQWQl$@Dc#=CTdCL^NK-7Vb(T zZ_YZ_1RP$@)&d+}CN<%m!)&z@qoCG359;3!lFP*7<7@|9dE|Z1>1qQeA7SeOCLffV z@Xllz`5mQmj25Mh;*vCL4I*_v+bvg8{W*&SY>ENePubD{+K;3ryhB?_A!vjH(bFb_ zTUI4PH~T5RM&9NT6zOq7hHjuYgRKte?I|_ko!;_xVy|$g+Mrk|l+}UG6(fC|y$X># zjP00fqg$WX=+tzBIv-cp4BQT3YXfcvNli={w_2e&92ABsRW;rl&>v+TZh95swwdkN zBy!UQ0k`wm+JM{HQWM^}EeqoB9s#_A?V>Bdy!F@F>H*_zY=MCBB~laKF)j?sRW;<_9@ZObq_RQpEzLp# z_F=X&uE6p>;2JuC(g)b8fYN)VCcIPXiy-N2=n@h630t@;k-Qh=SQBvgAzKS@_@2~+ zcMeP2Bg2}Ss;Sj1<3d{nV$^q*ukn1|jyj88Ahriv84&yDe=M<8L93=xj70CwaYlQU zV?BN+(;HJ}En>Hh9U<5DWhU{~-o-MM1Hi|zDaJg0-9Njw`IZ)@3RMP`9#W0jOJ~CcJ~1+wEM8 z4dUqn!t;K%6RvpjK3=8t0h#}0D*`g_lA7>NW==o}l+CXusq4h$2W%Hyx#X>-NYnwA z@3I8}mTyT-c*nA!QK*!fW5K|1IIzuF+;5Z-o@riBq|CQLX0Mww=gm+L2wpLq}VncmUtySwy z9g|T;kY3Do#T8QC^G8<+-P#M;q5#qJr6#;1nyyaU(!s=62*-QavR!fHrMid|0fhfy zO92RPmzwYnVWxTqS8YV+(78w`zQY#piXt!7MOzop_$FHo(D<6vgm)Tysa5eUwP%?Y zxHf`fsWG7F6e|!2O3HN)G2IP0P0Cn6W&3svfR1ll6kB^W-S6Y%y!QeT_KAX05D|B1Atql zCcFb&s7FC>Xl5bd8fQD>iYsr0grO5CJ&&ylC_P7N!aJqqwR$yr(Nc?8)PhZGr)aN2 z#NNqv%#~Q)noX-_;P&5aZNTj_YJml zuI%zwN@wT>dS7L$1A1SQn($6<@zz$UTvVq{sFwuG&IBwbVt-*fgb>gQtp3E- z1+4xcHQ}Aris%DsvtFn)q9a1odYE!+v|=>0j5j{tqLfeC^g}o(tGXO9`3pIJzdZe}~<3MsE489IT| zv)QVE(lezdyi-~nd9QLng!S#V(I6HRskgBmawU~FssOFP>dkCj!0Js>6W&=Zp~Wwy zO0Ctjb$pg`qV*NFORlu?&K*Owfa{BFVSwxNQWM^BMeo1^VWSaLTg?n&_9_JJQMO~Q z(0aTkqh{dtd$u;<_FJh5@7xxs4};M(YS3sFn#Oa>C?is<&+|1{&l_9lDgn_7wkSZf zRBFOIqPfH-s5cBg=>mdt659#aW|X%=Go=s6oWNEDWR8`Z@J?o4+w;+|V4RAnl@Ob& z*>1S9$xGswRtRveWJ>}#mrG4}htp{tqXlD&eY$|q+{AXm6^%o)iSz-P8`+A0%=JF7m-=RUS0u6Xhi z$7&jZ&*#{hfX}C;CcN|MtCg$9;xf8KJRV^Scjb|{V>i|W9Dc*r0vvuNHQ}AZDthBn zb3Q?%H9A_Tj|I)b*0Rx|W!55E%QyKNOy_-m&r%Kmm$2mlz=cv1Qw1H3D*^DXIydR&AA#nfzngi zs({iZQWM@Ot%(UW)&BE1t7(j?UMN+}$%dSI#O?;Ri>~bQ(z#@-2aMOT1p>xhQWM@W zu1*4>cJ*db%&tZd?_xXX3h}6%HJ+K;f#K`e`hek`QWM@8E{Qs#A(e?!WuLS4nU->* z_F1+|uGI3T)KM+q`V?Cj;JQa@!aJ^gI;eVgp7~V?;=^o5T_GNq)AVwE0G_{OYXqJT zN=5DKuGEBgm@A?$64A@wfEw8hg(_py zXvrMXWYi&mN3&gXZH{@1p6sdt-H~i@fUaL^!aKT^Q4rLc<~XfPP-t2gKV?-Sc$cxA zbA^|;GBiUs(7Twe4(MGdHQ}A!s-~K0Q4LR}q>T=<2u8zZLFM5p=xf(9YZ1R|+3vaW z%X@9lQVsye*zy42cBu&yU^sHe^l8(s+r4h&{Jv?^uG~Fx0sXaw{<@g{x-|Z)t>UU~ ziikC*MNcV#iA2R8P8FI+RD45fB2n?x*hFC>v3U2yRlIZJDqb*g6*o^rMQiKG&-a`* zZ6r|l$RRiGj_#`$s{3nr%g8P2FLiqrGYZy?jAWFIRI>`MRCi_SM@AkuQ~h%LcWzZb zH@>Vk7LK<1)!C@g|AJPtRMx+-D_ys%iX%VBYfkG%O6rG3`XlS>)gR$VPUE;Ds#;Zl zY#OP>f5!H|n`hr@&fXzKu^peQcGV77Rf6%+aQE;Q=v}_W{w?TTz8;&H8i+BdMdwwE zt#Y{kX)4(w9#jCO1Z|JalQTw!ppGwII~wabTup?K9*FWkCm!X$_7e`w-2?uzwWp*?puYlBd9$*hFU7C0$e;_c^S&0706uPW#fAa6;`J;uM(O#4trDAhf9~|mM z6xkuRi>{G1Q$fTJnSMKct$A4;__U!8?Ic;gr?M9(h2~}O(f>F zzG-_*i~iwHZzkD*Z)7{xmgAidH4pX4Agkqciipr#*^amp%4sYAK99~jgy)ZJ zXI$}^=l8Y;1Mz3Tu0r7R2wM;E`Hj?scRrR4&|2O_OF2w<_C$W;JMrqqQh8mENSXs}Z&*upM-5T;@4nge_Aw&^w;3 z4CoyrHQ}9JyBTF-vy>CBfbEhiue@fK)(N1lU`ql}PnDYR4r*Btt&dR~aki<|t))t_ zq_+0!EzDYjU_FcNmMbiCS9`m0S+oMK8`z=%*L6}8-f^`@5T%NGcY-%svydSD7uy+E zNO{AHu0r5*7h4bTd7adRcRowni>xB|)ht-4VoU{D%8Ak!*eR3P~ARTl-34V7PG|wmIYE1-m%bvQOseaBg(8rxQ=7H=h~Xg zeKItPh!>PvGy}S$*}?$bkx~=h(XF(E0UB3UB?9*}wsWrF%nWat;3CDq?J~A5;C8Xp zgm-R>Rnpk8px&ye(RxFjrngORYgkOIp22p=m6e&Sr9D(PRRX1J*@}SDnAC)KN_$0b zDxLE^+TS_Uk(@YY&#Oq}-oSR!m7JO6jmYIF2Zpa@YXgR_mYVR+a836sP@Md-uszCV zAK-FM3@3ZZ_JVkMkG&T&%81oYwkxiz%ng=yR=Qq*)ndy7Saqoh@359MhwIfH@iS*f zpl5w<`;_CcwMsY0fbo(I{;l2Sr;KieH2$P!h6?5Avjfb2(76W)<6j-L(6 z`%apAy_sP#L7RQCuTi;q>a4w;foh;OgRKy#?I|_ko!S!h(i;x6)M;+fD5PAf7~i-o z<;3kUwo9&!&F!Ivx&hoFY>5EwAgKxO;1;OU1q#IoSVT^5YepFX+st;w6`0%5QP&Hw z&ST31SZ7O3c!#w(ah^Izofj4pvL@RhS7dH|Bd7*y*RT}=wTje)NllOrCCOz%FX;jk zi3(5KdhN%%XcJ!cDs&Is#SX9QJ!Gb4rRS^lyc)P~UdL7i_syMB6DBbM^yI#w{r3VB zi3-YoATW`rpo|s*6N!qyNb_nWDkyu&gj9TGA}Ul4oJam`IdGn2=D=Cr&VdtU!U@_F z2KsZllLY54d3SO<3C@|3`KQQ|;QT4iJ|hWE6x?1(aQ?vlEoj)ki%n$Cmrj0~5J+>Q zb*@lK{p!A#OwM}?1l1)8&R*;vhbXU#P4J^E%}z0YbJP$nwN0GqMHJpAu|2duwlKWi z9Z6cl?APSM^Xds~IS}yUr6!U-`WUH+q>m=-6Y8UvPh15x@d>GTSlZD*|VLRm-$wloAzm0)@648!EMRmLM+rMJ}HU#t+`2n5$PwLBR z)rQLMU#ixlZ4qiGfck&5-dd%+wA3Jn7hXC!Lq+TiI{Rxn!S1(-008sYdH}#|sflDT zGb1*UIhvP_GnXbI7_6!Vl?8J?+Q4?owNZ4&oEA=9k$`DETMl44OlrbArsWBy zT0K;o3ZtcydW*7GAyk*J9dkw1Stn#cl@bcDwy*^Otj$sr-eGkT?o+?lIo3<3l~IRq z?P9y;imS7>yo)PdoZRW6yHbJI4z?oT)s&j>&TDxTgigoOje$0@vR5Hmcd{LGr3H=4 zDimP7k}U{e-61vM9oBw9loCws1l_78(ADzsIO9=wkfF~Qj`yle0PkTt>k6>5oV?rI zdWZ*#OLHdWGa_cUZI`O2Hbc zS6j6lWO)?{TtC}M*Oqpu@fzG8wX_JFMKs`hJX;pvTPHQ)9ber06^g~M7$00&8m(39 zS)HV{8lk(8?Vu|jbMI9mn6^$NCAuDD=g#3B+f-OiQ+n08A|c*nGV z6oK>&OSrPS$==lo;YZmHyCUoyi>Hk#a~%MnA7YCGpzoKO@D6mp2x#*7$j8~MGQs>A z+gVqb@P5}rJfQqBTN1h~ohlWYn&=NY?Jg&a!lY6k+ zD)XfS$LVZkz;T+?gm;d6CBcAqDQ%W4uObmVl$@Wh;RYFOr%_Zt(fBiOgj^ z@*Diju(~DAC!^z-lUyetC$N2Ry}>&tCmBEz0f6LKwiZBg6vcDu?)sUP){uJZ*Q4CI zNFlzQ?UrkZJE@4XLfqdL2a&#%EeRrhk<>(T^It%)vU+#vnzp&y3u@`%kXm|F;OHE7#k|7(C z_#z?s6<+03@67rdrE*zsOmvCJ+`|^`N~W`NHnX#fg#nC@u>}E)4@*t>hfyn6jftJU_!7Z* zkS%=DV8ki_#?RS;0LD+GCcML#6+}Zh^VPI_m5|K6!q+(aVEgQHcQUiTBM+#|Vrv2_ zeNq!96~Sldfa?TS}uFihAEUC%H~H$cgMoy0+Pj+3rLD+!)8P zwZM&WbZjE?tMKH1uUs@#FBOAQLoK-+Qir`6o4TQtg8nqNqpm^k9JpdUwYov?H{}9^ z%h=ig!o^Y(NxQg^I8ORRc&VbcRu#&nUB;HGxv2tT^bEEWu8cAU=n12g7Qk~YTNdCM zlbZ03XI`;h*bx-!^}?9(p+YMmKyP5X;R>j8a7tz)O3MU7uVw23La&yZ@J?uT+Yo_9Nk(DgW<4TGw1xWs}Z};upM+|cf5UAk~FSN2LR-~Y>@!+6H*i2K`xH(;nZGJ zeVl;B1n$>thg`vRwgKcfy0l;b_7Gbf0DC}c!UQH56($2q8pQ}qBr1FjEEk+#FP18W z`q(-3(va~GHOlCY+51XgZy=rIHO3vI3xu0wHCqqdBr9SQQ#i<6p-y2O*rv`CR&!SI z@o+)0WTvIbs6!A=Vms~HY)`QFw;F_gyL>=%0$U}ZIaX>SX*ov`-N_z$E*U742ZF)E zK(ktp_Ry7#H#18)vAde>k}JDT67ReQ)Q_@(+m&pE!0mFW3GdwI#ceH!!eEX`(5Xh{K{8!i=5imh|GO#>8@ll7C0I0Czc0HKF3xCOg=3&;ho9MpiwPvH%4-ui$vuS zws==68B-w!6%q(oe#4dpSbiln;T_BT^mA8#H`9v<(ekT&jj}V|%XCDVSOB$zEe=2} zl$!7kYDqja=nm|v^&y>>rJRr*&vwbRRXL68P&RNohOH2|ZIGJq&TX+CfYF+c4U37| z6>NuGsdbL^%N>n_Xn^)qwm^V(iPVI5XtRPH)%rGbwy}Ga5Z%C*?}{j6Vp#7BI}(A+ zb!=TgW|!22cQVV>V!e23weyBM2uC*Cs}Qrh*p9g}%a{<>nOVgHz}K-w0>C?^CQN{W z$-!iPktVPNCK46C<`-vO5^Ys6KU;UN(%te6b`)LjmW(}f9Z_&ge3h*PZiz3&CZ=#U z(O8hJZTB?FDBOQyJLwwkj1ILO?*1N2wg14@1;PKF)I`z}9wvU1JzpqTs=X%g^%8KH zeU`VwsnNU@k?n|UW6F4$?m*IHLPvHY zTNWTXPHMtCqJ{OUN~;kiM;@q#g9aqCkT4b4&bVUg(UuIU0O~5XFaY&5sR{3(W|S)1 zgJJzmtbL8JJew`u6-&m~#7^rWVL;=VY&}5Z8B!D8Y0%nUZG4bk?)zr8a90+ctd7R3 zaEFC`vG1GMasb5}q$a$hNN-JG?FRZH+a*^>8DFG2kdj>{o$X7|cs|e82E;xiHQ}Au zilCvE;5JI5^jh3b(XV%H8Fi?u`#sw=S8_+?kLK*cf!=S~`hecAr6#=7TND&(wencd zc^d27pp=NMxZ2nFJ7ZI8r>&V{fz?vBGGMhxYQj6Kbl=F@)^!5gCD#__-Zy4#>N=LK z4Tv2jHQ}9DdOM^xyhs;No4S(ige#*?n)ZxAM)LgH4bACtwkUvesnmpbINcpY8kz1@ z!g3>9zAKi_flL`#GIteS&sGFXo-Q?EG7-$ICX=%?A1E-9sPHv8JNt~CwfLE%KMs@2 zbaVU&(<&kCUuMgKu-_*&ku-zPNlhe;k>LMlHpVjxEn8#kUZy)_&w{T(X+~r0 z{yy9l|B~`3bxhW89xD3mW6TQWHsI+)HXAX^aH_KeI7z zsSVp2WA`#O#x3k1xOUeWjj{Xt&=@zfrNCWrUToq&v@tf>{<(%aqcL_uo!=O*VatNB zSEMGA#<)#tB58~S-)Cc#4xkp-!$L7T%@r0?+q{b%6W1FiBaI)(gf{v*wj{V&?v$EH zZk8Tz6H-iz)RuY0X?rI%ss2s@r3C4-YxQ_8qj zU8VSI(m}JK@ewtzB9VKT?W8NYj1|f`(gETx+2R1=gHjXTA+Dy5W+)tOB&{vjIclU; z%C1HL7jO0TB9V~@FjF?*TfmkE_~uGYc*nP7s8KF$4K~CZKL=F~XJfC1rJR5r&34JP z#bxCFL7_nENVX;*)h{*Soz&8DH5z!t4K3VRiyE8$P_tDaR+q7za%Gh<&25niur6kc z0$3MHO?ZbjTP;-Hpt=~N7fdb_m}}V%xPr-eW9!NS9Aj)LfMdJVgm)ZEqr;6GVS67< zP%c%BZq8PL5WSY|lq;f)wqubBuwKm;1+ZQrHDSUMyrd=Xd30)uz(k^g&a4rbNL0`{ zL;@3uiZ@A5kV#a~nNt%|as5P8sH4li^=~`6?0NY|m+c7RFLv4kuXA+Sqj`67`{=Tl zMCM;AJG$)mdG;Acmqo$tb#&Qp*}nx1``57v{=Av^?3+dBRg0~1xc_O@N;8DCw@^ym z>Y9Pc8Sg_-U5+kW$^LPO^0L^(6h=99RF?MXJawHSdm`I8`vVIjn|p-TaqORkm>!)U z)5%Y-IDfRTQ!Uyq?0b5$=;wcbMxk4<9Qcp9sYRdBH?%jTf8fdee{I@9UN^*0ODG<5CAbIHQ^n^Y~}T) zQ_eeMi;g0>OfcTScEA;e@g|dkL9zhHYuQo&$E&3#yyMt+v|1c2S9b)p?S*onwnjC> z(OS9CRPQ#hCALRh0{0oVtFGXTg}@zf`3?Zhd)fMc<|m{kywiM4Cm6%ktr5&pg(kzJ z%|ytvXKf<-YqqfaybQg~0RysR{2)59~4>E2@9(jn(BpAhL^!z6Q<4 zK6W6R?Fzu1&lU-A=SWR>hr7B1H~xGuOku&6nq7@R9>sRhwTT*EJ4}!u8}J>$mIn9^ zmzwa7Z&f2~wras>v#c^)23uQ2b(CmO-D-Sp%&bN5E@ivt3eVVx(SesG8rWULRtD@Y zkecw$Zb`7CUQ*+nHazvH)JLS2a^m%Lwo9(Oj4xFkUMLhu?PO~LQZ1E7~cXr*U7h%bMx+UerYhLK< zouJ3lEfE5wX0tT`sTooe-bpQ|&StpS2%4qQuo2cv=6W@I72>s??U-x(GFA?EMk7|a zfb1}~EI@XM)P#Rz)W_r^vsNKwTiA|G5SdjAAluBA1<1~mn(&V7fPn7XTD8%v)vE(x zqY>osbEr=+cd%V{g?XI)t3hu^fNYbk5y)O6H8BNb>GpOf+p|8AeI?uFiIVLp49MQW z)(B*8lbY~Oc0ux}fjJeUjEH@V?TRZgBi|A}3nPI*=fiA0K<5Ke6W-}8D;Lz9*EZFi z27~qLXs`{=)w0$gQa@+Aj64@NKN>sLw#Li&@zAA?&~^bMCb8rS6o|A1|3}>&{@aU19ToIHQ}EQ z-I^T&+WbemU3JQc&iQOtCP+sY2z1V6>j64vNlkdCv$}I@*45Z}C|d8Vww*M?x-p@j zU5%JE*bcff%lL?vDI4(B*wO&ssMLgae0wEvP@fYAhBE=?RV0MBvz>HBXl&c-3@dV^ z1H|2IaRBjFsR{28S9dy~9&IqQ8Ug$e+d)@=8Er6A58!)0TN>c|U#SW2_?9Pe$m%}r zRS4UU*^aql%b0|;$^~RUV9Nqz-<6v1j;uQ>tmBFUql_R;ADZIve9}2u-;SrT^#GlJ z{O8h1?$E5at;QgcBRU7Oqv6_wGRE_|KxjLUW$OVt2S`nLZ#(m8Lumw{)zp`L=tacl z9JV8_Y%&(=Yw`fiQ`m9<%^6Y?j%aRMJ8h3?(LY!2mS)^omUJI9EQEGstwF@9Z2w$| zWy~+x!~(c7TNQvSNliF{n*gt*L&@T0t3kY8%64mlylhf{*NfSzfY%G9CcN`n+gW-V zJ%@%0L%kOkJN*~L^L=c$U3nTucz0gJT$};E?_sM1zW*aN;hpb7>XH=M5C@26AyNB2 z+Zk7CkF|fxG$aC`@2~{{pl?b|_y#4A%&(}NPyN3`+CR9XhDVw0JJ|_5CGa&YQj69wX}jeVz8@PQA@d7cO?b_Bd9$_xd0N>NuDuM4QQWM_!E=@wx*_No|v{fK_+t^OI z(lgSmbOs<6sQ_!3Eec?TQWGXDL0*s~wFvF37nn#?94Xy!m#Cne2@_JWc;YH3KgNVq zJS5H15ye8KS^4(AEzQd7%`_{^YGdJOtDnw87!J#7QHcH-u9IcuV|h1nJIl&jBkOOI zWm)-fo_$7^l_<8ovaEc7{aetk-y55l!u%hQM@9Rn4W-np-p_W_-m5}TU9znFl>Orn zsxwNHGib!9&R#pv{(X2B?aBU8h~~efK$54x-w8-o zwhwU*p;Q(d?Y>T3r`R9D4uk6*otxd}AokBf^w-8FGJA~4znAqCHd)5sq*DRp<33i&Wvp|O9Lv6N3mYmp+9xU z?Nx|Wh3%LtDSfiML&~ZaI;d@IRX}W5YQj6QeT*2?)G_jPwSl};E`~-L*8HkO@Fi?V zT?rm(ZELyef#i$W>VV|9)P#4EGlKeP!^r;GzD8``%a-oSW-lun(gP6Q&6WWW-YGTV z9l~1G>kUM+++lq?9i1Ik8sX^Hau{6GDpZ;(m66dOI{g;}@Ox~xT>iQO)23u{8m!zer7ZXSKIV zfLJM(;`8fO)+x1`EZA15?kI;UH;N7|wYQj6l z+1tvr(NJfWTqZyp*$%iiuYIi#x2`InaT;3*&^TFY!aI$T0_%wYFNQPR~$-Lwl>suSyjE%68P1qJ6?6S3QvYGg}>yd{k<}JIUpZ zlFASq2F+Tz+YXJFYkL(Ux8F8jLwfsUqg5>s+ncQlh^>~I@J?)5I|eEV6cMwmq$Z|X z4dQhw+b!3YW`EGxv;wXtvqb@}lcXlREg z_joJ248_3hxolm)?Ix)S@7(qp8Bj0RQ96?z?D8rSzqhlUbmeDndpXL1;al0-fZ^Rz z6W$rFRNbbUDK3`k!Jzu6R#HH5{y(b{vHKd^IahY}wwIw8xP6(e3%K1UHQ}AxlJ3*P zj?7X{wEmCnk}EC!EbPuJ9qI&7e`HGnP>)DWcn7ub4)vK@Eo%yfT2UH3b@K-Ci+Dq| zxdCH4WskZ9aIdnjcY+PpM_s;tfVqk-4`42rnsA0GUMjU@bECOYJ@Gb{tF^K7s>RSa zW7JYk)SkrlX=2R|bpoj;uq6Sh<6{$3xU6bnJE3D|JGi#6$}FVF2W&T8Bd?#x(&_LZ z^8JQp0C5FdA%J+Q)I`!`E+Lkaz1(V1%KPZat&i%Vl$bq>?T#z6W37E$F0)uQK)Zo0 z5ujZsH8Ew-x*N8%cGiK`EhT9G#dgONw5~3Ib{AVBKzp6kgm-9*+bu2H*cA=vji!dh zgzXD#hg`AgIeB~bkBM&J_F1+@;Pxr03GdwIcevqjLN6j-zhgV%%1b{pt~ak(FR*%; ztq)lJQfk6GtGPjRXwiWBl4cCu(*?w7*{H8^y8Ww*VVP$sR{3t76-L@ zs7{(v{a~S9FN{G?XciN(N&O{|etpmZ5q9Z7x4fqP}eELwYPGV$WbZd zH*mX_tr575Nlkd?wy4c*s9sVBW185Yl&HOd?T#xo=QlB;)>RGAUdxsU&|WPy;T@V9 zS46m?=YrnM(glR-Gi)bZQ8{;siB_O=FIyc@`h?VkcS^GZ_2m*7C6|fNuh|Z`60(2g zvJ443I)TqaY;C~j0jY^8;G-6k+W2%X6Q4yDUqg0hJ{>{8XFgjS@R=hu;hoQt_A78a zTDRm6vXm3Cqu4IFwkZ2{BTI{-v|K6WfbIykNI-YE)P#3*%i8Et`^k*gEo%+JcPZN~ zS9~Y(=6N;s0PrHVOaOR+)P#3{ebEMJeQZIO2+`Bo!d(&BzwToTi}eAMoorpeq$M@s zoyoj-bF+G4sg2G$8m)u~y@u_EDO(1Oh`&8Ao@6493c9L)P#3L3xYzi7=%0387Gy3F;`)f5vpIXU2#QqwCh7j zQw+H7XA1;eKb4yBj%x|M?yJRUI|}t;v>a6L04?Q&ZC=&a;N5j7h{^%oY_>>1H$!T| zJ395c8!sl@rV~pp6R7oU2VC2i^IKL&C-6Cptqu4bA~oTi&%!pJT47tE*)#?gW+AcK z!gj`$mGj~mV%5Y-&nUhU4yBY!g7~4TtpikxjML&S&huKM$d%K&g+d z4k+y*H8EwB8l&*3l0p&{5T(bnop5bUOiGa?ptO#y4k$fNYGTSL4Oh2jv?WzQl+I^6 z;fYeD6)2s{RtJ>MlA4%0N@K7Rs@=V)0;1GlJK>9xk_41$Y;{0sRBFOIrB&_c(vE_f zTvQ23Rn|7kiR78Jh~MpO_gwisi8mqyF97Clwp4(5tJH)EQ*eerau@>bTN0Q^RCvlx zyoS!GiN|_{N|DyDT3-Eg>d|fW8FrvuZ!7(9;LbAfY~64h-OH8+x6vo0CcFb&rM}u# z2coT>1H-M#wkY}B29+(q`0$Wfi{Sm5?Vc;V!>oNmmS#Zr5L+0adq8T!JG#A$FsNhl zLY1}$_rvE^B!G*q@%0*FpX1I^4h-kBwE@F9QWM@8F6%}jp{I{?tu=_+QEazdo1Hy< zk4-D!I)W_T%Zo5mW-Di`a^Q(gjiz-YG3g zJ>28nuo@&NB}PwYyW`48Ki$tV0EsmMqMd9(fT$%k;T_RRLZtHEhV{}w#^;8tN(AgR zZ0B5o*;jgHCSB-h*n1u>mBP!>zrB5;y5(8AuV(S7b8>J@vQ`s5K^yp)=8FY=P z)Y#G|nF@&kDx+*&KxIT~!atQU3l+LXRCcqaPcjt}15|Eh>jEmbNKJUBGHd5RSW#=A z^w!e7N@U*8mhVc&euxx4TiPOl&Hu8M0h@P8P55UsW@Xd4N^E|>mOsgCI&y%`ciGB- z&9|f`ytA3tZa3JcX(hyHTGQ7M*uGdgtNp}cfzv;vK`zajHjNzotJH*lPVL*%!YN%s zZanti%_%JjI32)N2kmJ;sR{3#W^WJc&5~Mmr$302%S7lYYzJI>JNueQXh~g}faeUh zG{AGJ)P#RL^=dOdY2Luoy-aw@YzHP8Pgf7%DY2yio7qT@1x#vqwcqccvS}zTiDnYqApf{~_0kL`y+X+`z z_5?J*DkT*l{SR9lKzh5>gm*}@i{W5FH7)bu)V)lAzQcCF6_De@sjCO@e3LB=@O({b z!aJS?suO8dO2PI*S)I_NHzT8rK>eNViYqAlR$ypJx?rI7|JeF~*8fROc&9b5?R--u zbkbRAB}8if*1!49DJ>Q_?aNjNoc5BM@Xl%a&a$!PCB8yjPG`$@Z9(>9jiCia;(*5~ zY*oPHNm3L3d5i&%=nC-|X3L&r9+4E_5wcYQkF8P@{(0$k9Q=OjDM+PHeux zcEOd6eWx8fOA?_#=&NjPKgLoB)t?n~uC)5rmWoHi^LeQW?_m1M)q=4ikuDLGN7=$%p*Rk;V|jq&_iR~!ASKc6rVK*A;BFz;&tAgm^ z1oS*mn@DKCLYxRl?;CqnCb}oFopo)Vr@PVZ;SvBofh`ySA1gKC9q_y$O4=9Q@Ij!{ z)Jh25)oeFh(K#-?ON#|gSF+Usr^}@#ymOjS4;3e!Q2QFOxrr^^m5t+T76}9@H?nmB zmFuM@yi=JOSg>?15|_K#;$68o4iVcD0n8iO!T`+cr6#8sSM<|%IRC4zDvTevF} z$01^@1(1A>Eenu*T54hnkPK9fZ+v}~=n^4$ge`oskwjVm$#2-Q0LibUCcGn=83f@q z=5xPuk+3Y^>1!_;BL zT#+UhP#wdT2dFkkO?XGOAoe}l5z(lH#520}xk^$ItY=Ho5m(+xJV9V88Ptc%T3EjB| zy+^cHA$WJO9dm_eKT#RrS>*%5*Rf>+!aJoVyd#_)s8^60%r7LD3DsxW4!ELnT=3nM z33xummIiq4k(%(1XGU{aO#tals(p>XJj|Bv3dV6}fdm4TU$S)pl?SCJ{8QG$) z?99CPE#=e#j$pgw+5#Mt1EBDB*el)4Gl#P^0=Yw_CcKlI*A9Rsc}=>6XkEm1!`8_$V5}*NlkbsG&eeMRb>KDNeFdB=>lT( zakdk#j2!cyq@)6*kFdo7qz_6>c!#tw@;vI-gL0{2X4o(b3D*5=XIx=9eu6S21F)a6 z1p=@iNlkbMwj}btJ>sSVX!XWrDJN{RpYChq?KtE^;XrN%TO*L$Q)*&r$hC*+ndJ0x zB6k?uCD#VWAg2oha)+=r0=a{vCcKkd6yLg0K5!d1C?#r}+3vVfJH~BgxhWc$oyS%P z%+8jYm>OpBca%(K=~80WWV_>mSy~n_yN0b0m{p`EyfaIk*N4lO%t9h|2iqA}Vvd86 ze$QA}*z9%&?HDF33`Y1jIqb?GdzH5CA-5Fq% z8UV8gTNr@(=YK4iBtf;-u_c!Y&N_AsTw6`F7UKjA`s3I?2thwkYQlSKSP;*C+aD=L z8HN8`wkxjTcU-`%3x+0g7F!?C+9)+)(h5iJm_BXVb-UM%oZmNX+LgOUE}*}*&|eqR zUzf&zwN+fz{TxH*VhKznDo!ay{n*H4AJw!xdTxvsh6~ZTjX|R|I$EfY>HVp_3f)21 zvcv0o2iX%|TGhgRGsad0_sw>x3Gc)f1?3GyY)7@e&D@j^rNrsAYXANH_Q1iK}?`L{zB$ z@oRgk!&d^;@g8#H?x?eVq3X1Uw~XAP{!)E*R_`e}{P9ZF)u(=Bk$WSnpHhE>BRNl+E264Z^~a`>TKs2h z|Mz+Jt>)~FK8or1+(KnwxLQ{`*}~n!TcADvmi=4Mo_`&i$b`3UQt8En=5A?iRmm_~ zW%J`rx`0@%xo&b^BOsRJ^VT*J6%f~z?B9jBE{jcM#&u-P9PLI#EK zYL4|9%{WzJ>UY=LG&V*}gW>r6Qe~jrQYRXytUPgiM^09FM^26YI6eAfWAw*UqCd`# z{x~=KW0U$L9G^W}*jWr~&EehW>@hxTpr9g9t?b@3-ZxaPZr%O7@tMP=Vlk|!e_PQ0 zk?w`rmDcE1l`W>Y`yAC1G)qnOIMbL)j23GVjIw_4p&aep*^;`)ml5sr}^ z!=+}p{`7Er`!*<62O8^bUdMd<>kV}?MSa;hOQ``KseEW{ozUZn8iL?Cn>-{J$0{bj zCtAF@FtDvK6z+doRUJE6F^|;1VhT!y9TdkNetgN=8Ywz_prZ)-rfuwBhrVf;;+gfp z&hGO^MzWsq)vSWJ%b#^xxzK2s{eAZ;h4n>j$6UiYd+k8`_aU0&>>q__K2K^Qxxt@9 zKqhmb6vute##Ut@>TvX-(0J-P#s1xFA6#QUckN(T14RFw?4O0`|2Kth>h9(FszVP{ zu8SR|qUzMOi8j56BKr-ti>{GfqkUA>R6(G>%2ok^{*u&0a+`mFSY_Sj(%xnM=4i-& zX`x;zRg4tkdJz%%3)>MH5ls~k`4d|O5cz}Dgm)s->#d63G~z21|9!6KjwxK@zgTM# zkp>9+8upJv*jGwTcn|vmHMp-g1Dc^Sx_qOIP@K$m#TCU~8VX$(KzSlt20%GcYQj4d zHIW;?bd?(FNYO$$D5{~m!Ny*N5Ea>uxgt71Lu6G6qzY^`KM_tM3v-G*Df#9>*s(|1#r6#-+oDtMV8^+d$_BCSiHnwzECQG%} zL^>e&Z)X2I1piG^6W)WrR`qTJ(Fd)tzCE7d3BpPv9NnrC8DG;XRMdp5`2y(lUl6#j zu-$eAr?0nn@d2=Yku46eeqL(AJJzLfNTPPt<*5(tY!wLFqim;Kp*==xZ5D;V==W?b z!05M96W$r^tzJkg#ZvU9AE^IR)55{FN_9s$S^%uW%B@M%RzJhn_j(W>dJlFJ0!l{VQ&_eutXG3-UA-{{&C<5vYNfhe2V$*3Z0=^e z<;rG%t%I{^1Wa#aivdipmzwa7X|+lzIJ86c9mDidG#WC*XW{6qva1oW``8Y;0(+bW zEK@B|`y5*lQ2Vshgm-HD43^Xv+IH)zh3X7IHQ2MavizzV>#jE4R;WR1ozxXRkuUmfzrOXs%BNT2%t6C2Tc7YN6DGcT&sR zF(_5qhcc;&4IPxV25~x`?UrlH(jR0tjezMGwiv*)L2AM~roC$IWj)aoELtYlh-RST zf6-~>RU~Lvu$^>;cChv^%TWyMp32q)>@Ja-@Xl^!e3DcxY&6vxF7>spq29LCY8;(g zRwZI~1KT-QW@|NO8A^fIb!0!1@u9WuEdPLL*fPTrA0)QTrn(z*2-yQ0+uv*j@47Eb_r@Cna zI!xAbFh-BM1a9$-zQ*?YnOga}0pbF-EPyyyYQhN1QqLaG6a>5uWorXo2TM(O=QTH=Tyd=pMSa+p zE+9r1u$^#a)U!1u8iCFxwkn`=j?{#AI*Z$M)Y3oo4McrW)EQwhk!rCWawXNXH4&*q zFYv0fwE?fH)P#3li`u;6+|_^!N{QCX+3vW~>iH&>FEud*T+|{=8<);y;xgk|zJ}gCxpag8mp$2bA@bS;~pjA?&EQwjh1mjim*}3*ys?0og%pae!>C)P(n@w5&bbklIgWye?U55VrH! zZn z6W&?Oi#PA8Cz9I8t0U1$h|FzlH(bf+A7GJ8S|tE_30oEbdXdzGcR+L6uB*IL+OCb% zb%OH&whOLs^!+rMaFjwo^Io?3GZkY1chQTJ`GE)t2E{oj50#>L$)ifi1eJ_ zh)7oon7+pr2AIArHQ^o85_(Nni@J6c>cwaorQVV(<%DaGn|uwzdkyVSF(CV=G~T6d z_UFGzO?XG9UQ^@6c-wSR$z=lcIB$S@z9n_^0ha^WnxH8?Mry)4mxXOEwZgVSvuO+% z%tB&x7TX!uKCkDZ6k^oT3Zyo&l>w>Kq$a$R>U3`lh$p zL@)3fVQT|kLsApod97)`%r?W(np)#uR!28l!R6E=g153=bR~Ga+afTF2Y~Vxwn#wv z0;vh_C|9>pHfk!zOE$^uY6S9s*$%pb)K3Vs3>K*=qJH4`F1AMC_ztNF?;Ph-8>Ga| z`anc4B3|ERJL1Z#=W3R?uW0K8M&DxV0!Ck#n()qOZo91w7mcaEbOG`C2ipl(K0QA| zQG1Fs0-e9IRRNtpOHE7}9hER#Z$BM6Q3268;Mu<3wH)b0Qh?5WY*j#KZ>fnXqcg0s ztZP$C9Xe3~(K&+N)qb~|P3h1=(Y zY)NqYyiaPvJGfQqt66m*+S)iU+^TGglA&!-yWouv0hzT3+E3Z;xkA$qK+Dn!$bQ5Y z1jxQGHQ^oEUPc(yAxoi3GlToQ^C}X!8PD-GVAbcIa})!+J=vOo-M{{0*)8ivBB7>_ zTdg&S)*5}?J{C5~#5bU+u!R7cZBi57(X6E4tNf~A zy)=+SK^p{SRU%Ngv7K`TrLUaIPztenhORRMn+;=1>*BK zZ+!Fv3@i$P(Sd9&&~_dpHQ}Ang7)6H;vjBhG|Gs~S!`Eado=yzDqR;q*~pdwP)?JY z@D61qO~nruwuMpBIQ#m-tV)DwlZx=U1wrNKeV zw0%}BB6cg=Jy&A-lwFnzKz0jT5FmSj)P#Rz^s>!;A=reJk4)FYytpxD=IW{qcY5QjE#1p03*C@RE-R$dyMt|O$ z;q5135ahktdLYQFr6!U#up%~*d49e0+~XNz78-PoXq?KH?s~KLpg|&l#*^84fW}Eu z6aH!JjAmr?xBeM)jc5$ArB4zK5&<*@*m{7*)lw7wX^dHD&^4m*e75vSqCp~n#&g+v zfW}Qy6W(de+Bpzb)LJ6Fb#$*1k+-wuyAsh46T)XlTO2TXD_apTxm#+&Ka(*llg?FQ z@-?>nNn+BG0ZhKkRs>A$lbZ0(WL~?SVBe*c5S#yFyWz@4UsRmcUSg5J=Z|bvz~>RE z3IBZBx21(ox`g=b^*mo=;U0X_Qh?7YwkqJWTx!BQpV`~Pdb6Zft?3V%R|M|_2NoXD2# z+G6x$X`#hLqJYD3Y&F2)XsHSR9L9h{bcHxv#g;uu93lz8;c09&z~M5f3I7~+wxB(z zE5zZMY}u2>mUS zJEbPPV^~VN$W^BvP4HVs%x_pJt19h0|kr(3S>3KFJmYKt3)t;T=d{xmqw*^wA}P@hi4)R~S8p&ao^&@e8&b zKykm+gm)BsM;Vz*Tib)QN?|lgYZ45Hg`)Y@GPfpyTR84(kbOe_h%L_v;5m=25_ry* zn()qZKb1?U7_=Hgjez=nwW)&E@y7{k^gggxWum%)?W}8aJSm@Q4_5%}dbUsidzjRO zcd+wc;_>t9x6UMnf5hevWqR< zl}V4+B@zZScChsTji%IucN#MT3y#i3;&CTiyep3$L%y~&0P;$J=xa>ftD7Qe0Av{}I~_PUw%r(0`x(gAn@fNKN<;Jtr^7o-gt_P&^((|1W9W zlI+ocmzwY%`X$ll>|kf+y>BU}7I2U^3_Ui~%5}h;Is({bwQWM_!%#71*82LIo7l}%RE#8$%kB>=h zY0zM{u>}E;VW|o4K;|^-g-SzZ_#V|KgHqQC%S+fUxMJzCcPkMHWM0J91Z2jgCcKlG z8y&o6R5UvxHel zkiN%u#uZYJPe+DS0QGIQFaY%psR{3(mPFpSN6>Ttsot0@<%H{>Y?oYd^%%mTU?BE4 zwl*O47paMi}{QV=7Rl1Z|jj-LBL{@1zz-oxC3|JMVCcLvs zo#%$jh|EGFbqm`WS5iF&8OiByu~Y!{0=6&!b+gojcTn@AKBb%AQ=1;uiwM%Y*p9eD z>aikElL?64!IlMx-X=BS9ns9l_qwqZuXB;Ge2XpK6-$qahqg2T@^!W#0P+>7iKzin z8qr7NF%Wf;fc%v$eiDHw6#(SVY(W6zQK<>T8r8t&KPVgT6QW z2O;RIr6#-weL*}cZGUtaWfcBX*{-;@i5?4ub)nESp3K$-lunYGFe!y2cTAr)?YiCT zM$Ye>HtovYBNxzLTj;Ng>90%UzuGFU>V6)fbCm=p5*1&#E@~o^e&o;c=s7W37%oKT z`UQ>F=xCunruU!rDs<=6+2M7)bM!=wR+VtSRM~3aekn^$cqg?eC~qKAJF4|<<|cJ0 zB|a}>yW`47KS0~m254T&76NEqEH&YbMtpYM^wX-w5y0^kV(>w>ZdV42wMmIc1H}D( z>>r1?zb7^^g@?k;E)~`HiBi+(hmy+_*YC4kbB*g7Gp_wzMG)ZcuvI{SzbQ45+}K|u zMn(0+IpoIO(NpS$>S;B+W#kt1mwH%@WEE7i3T{>Z;dtM!uwK=l26TxC{EIEzm4JRE zrMk2GVUJ zTVK`bsv{kfJWsCNr>fp>d23&@HDKQV$GQ-Wd;hZTaInyu_J)J@aDXgekYZ!Y*EQ9N z#+N7s8=x6uWplPBjFnsJLNw0GOX!fKzc{DADIOlpRz0;;T+8Yur3)kA;cOKc0T0xL zXqFiIxWoh zN{p}%)rDxBuq&h!=sMrfC5nEaqUfKqjY*|wQ@Au)?HD(o%+`f*^Y?Wj8t3L}!wq%C z=oQ#NQ3}RTEN%MwruxaaBvTe7V#(E$QFU{+LX4^#>OwS5mAXA7*ipbBMb1OnhNKoD zK8TT-$Y{n`xj$PI#>zc)AsT1py0(~6)wa_`6|2QyLgP&DHLGYW#nn=kdEaos1uwcl z{BI>RL!L?+ZWR?JDyBH&Z=p)Npi+##PF;w``McaqNYPf&lV1PZ3sYRZCEJu#iZ-&Q z33X${yg6GHM$DJig=m}@G58r3^ag-GW8(%WN`5HYh*U~~qavdfe_2N<3VrWJ#vFVikTOmZK^N%5^;w{;FW<3vL!O_|I4SW z_b)#F^ohqFf9gpr8s%5z^KIE4NG%Q5KvQOPVFlp!Yy}tvx7LM7E&#RHGB0_w7@nAp z*ZjN7obQTzc%ynw5l7Y){ysPm0k+RB8F zm7Z;BotT4r*_tp1FV=;yS-q<`I^V0CFFVy4_l8T<7JK6!H$XA+e6|s(j9?3F8F6%D ztbA3rHjI_8tP9aNE6yQN*fZ#SSDgGvwh5`6;M}$4q~g@*f67*dQSyUzAsVNIF6Qk| z1Gn|ckBW@n$W}g;j98VWbz+6%e`agKnD~{t5HAE1OCcuYN5#bd$yWZ{nULZc6aSd4 z31i|Dbs-vO;_?X%;o6-^dq|fHjIyYv^xTrI5TobDx)6=i za|MmG>FrqR#Ri^#@dFh>4`&;bT5#@wafKzw(~&XsK(G|tdf-J$5?bOzmm zwzbk*OsBCB1;Q0i%h@KS@`SU^mM5+$qpHYOicvLR7ou^hUS=JD9Jv9Cob_xYQpt&R zlpWm|D_@bV4P)iY>OwTmid#TN!`{H(@(~DE9K9#oq*RU)3Xs;6QT6U@r5IK3tP9aN zRToZregEYV`AHG-;cUfI3Bi>BTcb(gjDrtl>%lnqU|opjIT-ba-mJ@o@{{7=li7-& zD+f|IA0dgm?kxo3E5NO7$l6WWv%~zvB&6S*Y^$YPO-NzQ)alrLSAQmdy7n*(x#LFV%&x zrC>qPciyL>djmS?(dqY|^Ol8Q<^(8ezBb#4RBEt?ZK-ioV61#~wl<8Fx73AboRv!# z#+_%?l{VfA0fZ=Cel**RR9^g-{B(8Ma&Qy)czgc7Ib-Lx>w;Di`%)d1FVm8Z2*YlMpXpckvb7<)VACEvISZUNzbsJn_cO zkvPitPi7mPT1fZCecuXe%@}!AwsMS-SJs8F<>a^`^?Y|Cuj+RC-S%RqI~|TE@*-KB ztqp`L%Es9yrBa3$yg_kkacvo0!)(PEUH!Tcjnj3hELm;g2ETXdhA4`@G24t(if}-N z6uEjZa=t!WB}UHkbs=5=a%f#a)q;#1Cq$9+W7%e$7jhgu7&-qrTO~%$kJN=|oSc`m z`Q7sRn*2Z(jnlOO9k4pWBE^FhS%YkY zQpv*W6tP$_T^Uy=v-M(JE!Tx;oU4o5p=~lrAmum3grg{`-j{7yDpfc)i&O=5X1u*OTQ|nrd+I{?yw#YnvvYoG zcBux0)uO36zl&cZ4rlm_!sS<$N&Y0;i>a9ekAfLpSWft(Yz0_O_dO8V8;(s^z6(wCbbZ3 zfeBYb&#Ya`bJ_YZe(tCX(KtVs&C}t2IvU&_cNV;fAq-O7Je6%oDmOUxHQa!9thRbQ zTN}pAV|5`KXXXm|)S|d7vpXynJ#PF!#m*$#m{fMcCCAf{@iWTShw-yo7ou@~UeX(! zYA@pmN`F&qd{egSschh)ohd6yA4bC2Y&94OUt1TVaT3&yS7=n+O|btpTk%v1@JeMv zL3j)7f63N@G4P{xAsT1E-Ibqp?B(~eO-khkSAYyR_FRQIp2LdD?_{gQi2BXC5RDV{ ziuQyyrcQdRY7|SGRp^Y;Jl0>4II2E-@wYbBZN?Xw3}In?8EF?}tHntBmrq&Ju55Qk zqyCw;X=#xz7OIHamhF+$l7%NTOQJj-8AZ2eE5sC<;tx9vyLsjD%+$~nr2pj zu|rF*%vOmJb-XS_<3zcK`=H0*2B;D=%r+vG8oc<_lpxzS@fR=p*}5=JdUYWh=cGI; z1B8@c6&qilt$r#S_~?saBYJ%1`D{fP6<<{s!l$CfB&VGxRdaMTAgmTm&6B?L`00@( z0-HMfS>=hJ&h}$!p1`L%N^LZggYpfmpUl>PWriP@5Yeg9^Zq#2rS0YRB3+Uyp5Q?D zLWuJ4A7|T_>S3JfF&=KY$}w;MLAEx`+rL{E!e;v4t_xv{p5pvdv*%M4dYh)>a?cPQJwdPUE8xVq@=&(?-{drw^mTl99-g|J0W zasH`U^iGVHLq)IrtcqTd?S#~ffkm$r%ZlE7wgxOSv?au+Xwmz!Z2M9@jIEyWaCOo9 z;%sf0x8GD3!WO+Z)`hS|PjTL4(X0C;+=g+{S%_b)#Di5yd|$RtQu7Jk5684)CGx%5 zny@_bp1Ke=kIWbj)Me*NI^|9DoH7?%Vy~5jDsFx&+niKxaJ=a2#;E!AY(*F~AFd10 zI5jsa^VOwfYh$`QZYm~}*QWksa#ax(VgH(KWGZ2}Z?{5Y#@#<>>%_SGWL=2Hxw~Ff zD@(=d#Fnl0=~eICYCM|a?Y3`gY9xR+%tf_jtZmBHh_QBaU5Lh6yJ~6D@6EUO$FAHD zge#sN$u=pqeBli=Tt7z8L)oe@g6^*i(Ktab?+-;^LKdsy^hgvfY_LKR6h$l9Mx|1O zGtL1G8AnUmx-gCw>OwTm(MxIj?tZFJysFNAR(yPQwgIVp;7F^~hOzLLYz-I-Z>|f` zI14Xt(`6TvLZ2L=M}G$1k*iPy#mo<98p$CDn0ou1Fi7$8YuSdR@**-XP&>iE=1#=f7xgMB=g=n0M7mwGx)miyPdH*Z3)lT)kY-RWJR`$!YB{E-sNnMD>eSLZR z*>P`Lv=gH(YW_7div=$YjMp-h# z_y0@D>L(v~;`j;d=iASUgB!o2sU9cJMU>hw7Ou`k+Jc*x)6=CaWy@%OI@dmmwWMCSSLsteJ$=dV!PF~mTQcR&3= z#lX*J8jrk!eM2!AeH55~*S)P-oAm+PkEGwsoM*exdXGV|JWv=(~*HyTSZ z^;g;Er80&00mO7=lzk*yAx7Ds)rDxBvdZPVmw#82UC;f2O)^(zOJu&jtS&_3zV?>r zpv~~YR8csbZAxlU5S`Kef#uT!*%F!m_tu4I-2b>n2kyrp<^6w`ZAg~)1B<7x&6dc# z|LVFBjeFm$)Pdj4N9FPVkga@{#{&zd|2|tH^Z4ny5RH4>uFQeY?Puln@5weG%ji`7xTL)$iZUv)-Y#m|4wHZ_$WyveFs zZ_R-1$^LYtP9aNiJOcwmWT6VCbc(EC&$DEmz>6#u@#Yjk!@}&k@$?jEZrHE zf10frqwW-}#$BJ_&yZmNHbx1=Xnz&O1$TQ|n(OY1^3&gu2W zBl4`>vLcq3%Xl=!-~Ma^Qwt=X2#jjWSlg4W5o2vvU5Lh6yH0HgS)KOjO~dy5+QL#X zZ4c+Y%Yvh^6lbr=HZPSkoF0hj$|yULtq`N^>ADb&Q+Ac!ajH3^W)L@j1;Q0g&t;pG zN)s-`bNv`W&t|K_2%6S~Xq=$y)NPit%2X_tBI`S{%}XT<%T!DYM%jPNR)|sd&2=G~ zr%YEZi%HcWi=`<0sciG=Q5MscQTG35E5s=K|LQ_CPnpiSj0_l9o3hAaDa!sN+q`;| z#dKwq{ZY0;jI!Ua3(+`bWy_@4h__reT+#Hh?`f)2A8xsE_ zs%3d$(w_EKi%Bu=`FF8|qbQnM*@mT-CcOG8s3l`+XSOzsskyok%`>HHluD+8Q4~|p zWE)nGsh|pssi(5FVN5+<7ou^dZf&dVIvP%O#B#gsNPfPUy_|iqLP(Hn}FDGxcGFep9w;jL@@nAsQ$2a@(G;nGY{a5%m+< zrlb;u8>dk(M#_K9R)dl9U+O|MPRiB&4oyg%q&l;`I3BLHPmT)jDx_cxMbIB+o0Uos zZk!J3$Y}b#Y-Jctzf%{Yahfi-Ej%wf7^bMX=zE*$%469<*^HDIXRE|{TQ+dM@7gd9?_MfvgVyyj0U5Lh6yT1Ktfk;8U7%5w_)nFxMb6tqWNx4kjWGwu?HpMLz7^Eoq^lU>?>o9x?7_?%H zJesWqW8~qw5REf(wXGvmzm64_U<}32Als}|esInwq$8v0WVSMlrscX2&C_ISjTlX# z7>cH^%QmYXO(6{!O<$9(45MkiE=1!rZ82LMMbom>S(?4eHYtUo@qO85r_#6=7N1gV zV6?tBTRBGSd+I_oPU{t_0wO{S-ox_)6azc+l3*$`LcCDCEHBQ>`}nb=cZG6I8K`)LNre6%dJboEQTT|(pIvKN+k{7ke{7ou^F@MJZt zL}8HP<*Ty|N##Wzc@Nxn^_Faj%=>Sy3(>guUtFB_rW@oJ3nZMJplh98v9uh@N3xyq!NHvC70ST7JfBb1IEHH*M(@Dg-hFX z&Wti2KNtuhii>~BHY1gb>!C2XIx#x_AzKMX$KTb3Xq=7>^q}SH@Khn+wHyxmXRyb` zgB39w-_}$I8Q-pD+A(%+%GQLjbA4Ti#@VU725tfE%Fl{SRgvk?MJTy~)u?JJQ~B@&nmAFfQIv z7ou@4HY{|eokg(`O)x@kKt_15;^kMe4NBz&+hnF4W9OH$HDT=hLS2Z)*||)-om|MN zM1Mm%1}RoPk!?sSE4XS5S}{icHd_nE$j9qKG|tEdT~m}9(HI_Kft4`9; ztyx$?Vv$rina?&Zl@WZID55DVE$wW57+B;mFI){cOr~Im@cxSf$sZ?A8^HoL{#=+aO6<{2^RYI(^Bi@)>S%`$x z_3Tx&p;*iY(p(SS$Viv@d1o&I;mX?|$Tl|B+gn54ZgD-C|KFdj67&Dh)`hTT;HMOY z=bNZ2sfHfVjSyW~Q=j&x3R$Qk>0{aEq>_Z!80jL>@^xeseKcDkM$wf7r=G)~mD^u+Ng@lqg-PSxGp?S)>~+sqP+q$vBjY~xZX+Zilo5sevdKa;H& zv z(i5H{^>t(v{baU6jG`Z}3(+`58-)3EuDlqZ3avKqU`5iOW*ZbI=^L)S;6)dR|E<5Q zGw3c4#{=5yb9OyE3&+%Dbp3I*YK*Qws0-0JT{rmlt2JzPb$CTQMc}3HY^pbnx8<51 zTwHHP;7hYrV+6jWE=1!5ULhPo69&P1)%-w3)t+o);$;nsSa=%M(~$AAD_bAN&-S_y zjq`JLTV6OK-o2Qf8HFCi3&v1PoyayTm8rS#Y)(K|M%UBXN-?^gs0-0JT{OE!y>PK2 z(Uwn@&?b?k>9V&v5sRcqdp6s+RMKKoa6ye3Z_{kO7;kT=3*qxttb8-=`VgB$p1M%( z4*3lA&k6Otq02;-(44?abB*`PiE1yN71GIdW&twPB@C<=mXh?rE-MV z>jkxBOuau_8^+Yn)`e)Csh7(odt#?O9E%r7i7PBcp$Lklk7XN`$`T%L4`|3Z`e?Q; zjH5rV3(+`7SE{&B?rkgW)#=JL7_jZ<@}-bK35 zTlC5egeXdmWSfy%V7%kMYR|3IiP3Q|TM0(TzPb=T9W}ZXwpa3Q*`=rfVYT?Ox)4^2 z<+>17iw$)ltQH^nd{NlX$rpbnAv*Q?iPW|GlTV}WVr-XfwBuK2|MKgpm+I_zUit7v zBw25ImV2A8ADt;y*IH}SUVqXWj_9O7?>RX)1}z)yl>R2_6R*6bDmTxq^hnyu{}Lj8 zm;Mwh6+PzHiKs*R^W@4%{+0M|uEGz(g-g9~FpU0}Pq4!MrP;Dr;r=2C@xm}E*}C$H z;lf(K*!<9NFf9gCZ|KKD6>0CuHZ;+x>l?(SfuzswuF;2i`Q6#VnU~-BLVNj=NAwiF z=U?Z$^6ZDQjZ5|H%eI(hgur#lAIuiZeENZEpPv7n{#QKp^zmmNc=c1yv|szwGY_AD z+GX7jRNnoYY%^26ixW(4t=IC@Wcm4H*-A0rf3z-yZ3h2b(RaRg`EMw#Kk?a5Jowb( z9!)%05q0gm&QJH5)!OSjp!*yq%Baeyx;k4eM%9M85RFr%))RZQnzbTV9I-&LP({>T z+2*8{sO@p09wsx=_27VocHH&`uHb9qi$y#3s@ zP+g;Q(&7uwMGNX>f8yV)m;IN1FZ&wkWl!3&yA4Iw^sv9WDktk6_9q1VKh*1CzoiO4 zqlYb=JFAEN=4@H4V1KEE_*Cy|PtSi3yYgzuzt1)()sMKJc&O{PR`8C=zsc5ux%FdpA#5A_ zQN_&p?mXMeBq2-N&nidX@b2@|S>|;G?vgB-h+j5&ZMG_mj;rfJe5%lKPqqQ61>-`x zb0lz)+g;fLnbVKdh4@r)dMDd}9H$5FZ~NSAfz0WjQx~Ffr^Dr`Q1?KHs(!vS+l*A# z<8pCX{Z!ts`bF8=FkZf(E=1$J@C#Id93HF~`u=Q#QW?U7jg}$*8d&XPd*$BSvQ=U< zeQ#Ze#%Zd&X!RGf)lMZN(r*$6V444aK3fas|DUT1(YXJ;3s#|wcwwr5{B^b|shr>z z(XxQx?XZlUzslB!vGb9-5I#FKCS2^yks7kpfUsJ8plYunIq8{z^D^ z*8J6d*|J!{zFR_ks?T4oWE&c;QJGUy=C78rg)=WNywF~D=Bc2)a=xp!>MhyErFyn< z{_4%yVwq3BwA!cVfBx#S)y`=;)YyNvx8T3ifkDc%@5(kY)w8$3T9&EpTA-eGm+c~d zf3`Zz<8P}AVVmFYRivHosjZ8ie!Bh06R(B^i*@r+(ecaK%BRwSTPRC9v`UPKU(8m7 z5%KeNAsQ#*rDXM!4?J<)TbQ?>6&Zh*Z9pm+ILGLcq10k@{B^b}jE=vm3(+_o*Q!S^ zN4@@VIvVz9Lsolf+*`nM5{sm$y8eeM`b+21pIP4(uEruNGvZ#6tr#Qj<#iz%C+-@` zx$@pox@13cb$B?6BJJ*M!&1u^Zg?rnS5RF>*`3*HG0G0tg=n0ztH}rA=tZ2dU<^gr z=VhCfN*Hc@aS3C}GQM7)trg?z-_?a^oG*QG8+l~1+!^sFzXIWkt1rnmDV3{j;Tln$ z%5W8BOnqUtPK>GlP#2TBzBNbJC)7?pmmsu5;4j&U4X%CQSe8-)zEkQ}u-DWGZhifpLM^@VEIr z6$1c0VLB(^x7V96y`>62V}K!?J8Q!9#%x)vU|%O8KGi2ok7pYiu2-2;QzlFw%ofhP zd|$Pf&;R7!#Sis6lS$zns42gy)@m`^uvD+UbW2yuXN^%eTPXA9=hcO<&A{swA+d?O zI!FKHfiY)7x_YAeE3<7#^?l_;^_ORhW#0c1<+|8$%JZL>V^1c5gJY3YPXB>yvs1n8 z&HCt;M?8%YdJ2k_hIeEu#`yTYx)8QBe2=0uRvPNQHsimc1cdp4il|@8HYSxQyq|AW z5`}L-F@k;}TOmf!|ELR*Oi=CWs(e!ks42fHPX0Dq|5Q%!vCEPZeXSecm-F##O&A$} zDIuc$o%3IN1)fv_X9vQS+pqag6&=-c=}4~c!Fd@|6wGaU32!f5m8}~i;)=Quwp3gy zA)@E^>sDLrbtT}7g(|j=W}A~*NOo7VMJ)|H6vb+=L)n@!uJ+f3Xq+ql$`X*pgB4q^ z%Qh&LExe%8uhLrV%_v6KXJ@O%=z2|Eh!>15`_562E*q@qdQ-MR=a(+452Ndi*{U(R zo~sMdI9>kBOF)(%sHl2twlS$xMS9v!t5x|v(|2bp#)$fkx)6;MRsHA^&{iE)k@)^> zLsLn-tGY4Ed#LGWv(;pj{#0Fv#wiUv!~{GB!WDTR%{D2OyczvQwQ5xPV$+{z>&6)S zle!R%GZuS{322K&QrvC$;imfh2dfKS>eEe^XDi4Ed|6$H#tDQ6nt(M3QB)nwHY2r| zc}so%@T~F;r+wL4F@9QgAsXjr#v4wbm92OxBi;^GpONrmPM?{r4x{3kx)6<1;XK*| z1;zQUipl3^n~+M1x4XnArQ%hmGub*ZW=_?GXq*{%y9u~~5XH`SWt)-8&WuW|@_DCk z&(@0Z^R0Cu8t2D(#tGPQzAJ8iCfkHmZf2C6ig%v=TeeP&nV+Z&(Ks{kniFsXA&Q+p z%QhpGomnNP>cOY~ovjt)=MU>bG|mru(g`?Xp^B%=-rH1=ZZh=1Z44G#n#?zgHlV?jConX7CcnN=-QpF8l!7RU5Li%s(8@}c&mt~ z2z+(6iKzr0sUE=k1lp&o7?Yoots`Ud$+{4YGwHwb1oZiVinG(%#-wsKW6D-mk@mGJ zM$}riVvMM9U5Lhs3cmCNYz1Q|%DyeztW?VORTs0+16GW;Z^_n<@%D{%AsXi`_TCdv z7mK9W`^jwMQrVk1%a`^#)Q@K?$O!zgx)45rHFh}Lt=ek0M-2$8MN@mD)NFaq>rfxh zc4=z1iR_K?RAlA+FS8Y5+2k+kLNrcL_zfs1<>4r*j9>F3O^rLeBa3F{@`Y~?al&ufr=8bhB8s}z% zu06^uiu~|k#nIcd4NB!GR)(0KjHkC|>%@5a?z#{@Pc?eywu7&lw;B*ui>vAyVzp2m zmvhqM<8`}FRtwchJtr;Re=b_kVCN6320Q1!ud(H|MZY^-EjCYwn@4AkKk=D=_@-}} zY+e|4*H-EARryNptx!l;PKYs!e988c*|dj?r7(Hc2;}dxWwGM+H+3OwM*olh2 zo_^f=WUpInetbAQ*&8fv?hF<-KQ&$`#>K+shg~9ohxOD%imMyncYeCxjH~r+TLz^4 z?!MA~H)+3nsI=b=*@z!4zAjrQ#_2V6AsXlO<&O;($S)H~=d?55_b&zsMNnMbn{8BL zxmv$^OHXMfY0_G;rtMg^e8$Vsx)44uHR@tfov+QWtzS7EcH4us)izaR;~u&Hjc4ap zHq#T|uX@qSt?HlK)IVF)KewxY?oj`175`kivQ7MR;mVx)XS?|4qLm%$pPk~LOIJQb z^%r6OlKAJsmERQqym;j=)IT2-|GZ@7kJYcgQ~&(7`uJz!p9@!hSAG1j_~+ABeqa6i zi2CPu)Ia|q{<(1Fx74qXsb7Dfe*I7N&mXCe9~b|8+RC4*kAJT|{+jyr>+099h<{!p zA802%#T#4nkGR&H{t=h;kRUP0reC74zv{(n>sJU*PKqu$bG{f0N2*VYy(Q<<28uNu z7X^)5yZyBV?Qr4E*PmV4ddUSBbXJ5hzr^ru)6P8Q$awue5wL;QIs{V7(2_r&-6 z-$3y7=?B`12(FDc06Eh${ytV$e7)z7S>^L^@2JTN~zRh+fIA1wOAXX*D#dJ9xD(tljK zP|(#;W7*lGpBGcJJ?N~8Bv%iEp7aPW}*vNped zF}>GZbf==4SotLB_o@rl)-UNUcLqxZUCb+N*4AI#9gSpy@1_=K5Mne$y>iR_XaBYc1o|-C{*}aonEwbqT2cu(t7Bnr#>WSfVwL(P0hsF) z42xe>_C<+YFrm#TC!*rYTKi2|5g`9)#L2Jl^r zGUfV}uKUCn=>cu|%`t;1BDwA=CAS!~Ogu=vt21fK812;#btz8IZQne{Qfyu=rpvJCqJ z#=Xv%ffg#8gyppdOg=wr30+i;&`~)rpG}$+dX29qClE$PtDU>z$uyI zO~oD-d~+!{kOVOIik2-E+lwI8&%otZ%Orr~yIB!O?QBeTw>w!*PzFZIyy7Cdj&jb!fsl!PeL; z0pOLqf#xiY%>}kaWRXfNkg%__u+o-&%o!qr9E5~uIFND>dk-^2m6gZ#k^?RX5kcDS zVnBVIE*2h!V4Zebg%}M5_=gsIr)fMT`{4yWt;=MxMEqUKZvWSdggd zLTaUHB)LZ8RbCs;wAR*zEUM}&dgvhk9%=E~peK6gHH7b)pi?b(9>9!(A9Xk|F|1q6DA&*B1z3G`mVWyP>VNocMIwzzK z*82UnDEXBxGh=v=QYJ;z;c9O>rGCOn*X`k&ly04BMl~j^%FDeHcg z@BP$(y~~7_R#k$VdAZUnEIFWWpDy>ti7sJ$Vp_o6p~BJ}!hG!|2IQSOFwGsz*M4fi z-dfK8b*igdR$`ol1~Js`^*esMM_=4I+}j@S4T{eAfkCH#X40Fm;ak5$?Vp~#?sr-k z8<6&kkrnyhA6>CjZkf`fcov5SZw?s9IZ8om)FRMr7gQzC;>ARCnr$9sShj-Jbta1; zq;W`)dS7QW>Yr(!?2MP!2E~Amkr$_m_N3bpi$Y1l52_Y3AiM$@bfDFVqT@7i49Z%n z>unaJB_ROyPBGU@EkSQMrmm~#j?1Z?B%4|>o5GkNcjscU-03e?$>DW7H%OrX@&VI> z$;yhl7aowH`$Gy1B(;nrr|n<>ZhqIbC5vN$0Kc?k((cg$Q=+eCTB6WE(pKsdRKb^x ziLQyb1q1+y2SqQVJ8|AiO~Ti6AVGYD18MgQX;dZ9gW#*9V%ig(!B9_%;o35DD3+Nk zp+O7TpA&UpcQVP89<7^%bOs4h=b*>MEp&=els$p~%mb_af_!P3nJVfW_-M{UG>~*m zgq7`fyO>OSR0CC3=#Io_4HQNN$@>R`=VYGo<^wA|7;Uit4CL%oO;w@~b+3mh(NZXg zOn!rYF-zrEK2c^rxQi#8TgP{qHQkWKo1_pYk49Lw36+T2fkTypvcZt?SYZ|ur_lAsa zK(t>YK|t4?@{_Zzpwb`FvY}7_d5>6cG#$_?ca)8$o*7^ov|OZ+jM?QoRYstU?^4od za9Rq8_FYOCuweV?=^IkLuU87We*}g>mE)UhaE}U2k1o-{r1h&|pNjtKj&n9XE@SfX zjr>ewoU3)~FMrdbr^Iy;ZX;BOY&V|mLzt108``3T#s)d(cSnER>A7p26oIVJLM@nH zHG|NWN(*>%Vu`#EM+|1_XUdN&uvI1p)Gg0;#_nLlO=5M_>)oiMNNKP_qtex`KPsg! zuCF&fF&s>bhdSf_kTJH}wQQ|+TQ(*j?44z1w-6ilDCh|Rh|qEn5Skj|bA)yf z0GjsexNFj&5A!*Mw&We&t`F5LpV;AMa|I-^fI3$|sGPhUh~%K*F3}w=Cje(6>pCNV zY^EOpeTUd#J}x?aXFLUI-6kxkbqN5t`>Dq$R!c-9YN(s3yKav`KJ{f75J=jtE?l8H zHEKdxq3SjV1=#ckSC*igvEGvzx-H5X5i~iweiPJxUZ|5RR(9V=dt5au7_IfR*fIH9 z1qrgfeSs5Jdg+TJWBrEUPQ=zk6x=JMQ2Otdvma5fq{z_|?cAU_WGW!-rSWiW)b6d0 zhU2N&BWDRSscjC{qbCy}GjALTwB0Xqw2hl4Pv?z@Jd434i5X!zI_>mnx`nFV8J2}4 zg)l&pwBPh}+mp4`RoJv=)VBW9eRmTOOb-%dJz!Z9%P&>@=o|<5m>STfd@8Fn0u{$i zXeSx0?HHJ9{r&VHLC#Ky7%G>g%F(kEMuAdKjsnu;S`ta46(pL*Ng+h{7=1wq05a|p z`FLEc(*E@P@}t-CL0lCUNS3~IW83^+l_x^J45NUwBl7<6)ges*8#zw-trDpB43tJz z0)fnfHmaYiss#EL#!*08YedEsa$IN1a6pPP3F4P7o*wY`*zht>RtwO(2TIQc06@ZC zCqiIkYH-}v+^>N`2u6l%5fgU$>vrXuhlZ#8ZX`8Dm#C(Q0u8S0ozw1aT|>9s^<;rj zw%mGeEPbOr+%`7g?3FuQL{Cp|mXs5M*3Saa+drhcp#nCT81Q$PD9;wXrDeCf5kS`a z2V7*G3^0}0GE>afSogt7Tuchs+oiq~VSczaSa5nr0d@7L!i80-0CAf{TpM(80Vsgy z@a3ifv`uR9eClj&7f4PqdwH2*S|%I(k=% zz)*)~+&M{fCoggBujtG)B(VpcIEelSfCEyzC3mcbV1i2}dKnxuh zFmA%_vYrEUUs4z$;z7zAVq24KAJoKvblbf{CAPep=+=q4|84>*&!f!Y{>>^_U8YJ{ zObOa<#FHo{iB(b0YM213t-LcRJGj7+nqUR#pEXfm1N($bG9T4v zS?W`!M_onFZXs~Wq{J?Zl#hxj0d+eZ4+&&Qr{>f1b4FeZk^<^RJXvB=SjPPv$fUu6P(VSY)lyZgVBm%TK6F^;EYFt^SZ~!rB0O^7JAIbemX$iZbC~p#9>YEO!j#AY+&c5Vz@lf3wX;pL)pvb4~{)7>_>n(*f!hv&W`0peA@&y8=Yi!%k=`2=>*k(9^yt((JbKfB#@IF+2n45y=@v<(7V$v-O#>>T zNhv8Fq}(j#F1^v2Cs_A$gy1qVVi0ILMj06}HsOBEz$AwrV_`hN*aKTFtvfJ@>r*`& zgb^xsY#s`3uZ&76D+b*@a5*#fYTli;R}*nC-GU7l<-(nl-m3IFfp|YU#)#$2j&*)pv)#!*TX1REJFZ;`$T6aviA~` z+We?@I$^L}-~b?ybB~ZCs<#=tE)mqq+#f7zm7pMB_B=(^7};+blN+vCgj@y&^6pr1 z&$f8zPOB4Ui4p_aUfdNKCEo!K=PJYj8ArqWAX8+8>yI!V$lL>4AyY(!TNHo+33HU^ zpl=wn(d!Tfq)Y-(H{*(s)}^G(Q^a2lx^cldkL9P;>y29e!Xn7sv@l$g zSKmc_pa;Yzxr`1FH;bVO%c~L9MO;X|z39s=uF2)G-I)6>Ge zFv15zyHspI*hJsP9e00}51|L?MjsU&F!nFf?JMo^@Y(KAY#Wo8Av0I`%Nx2kpMeEdGoMzy|3(RvLtg{{0dvcpg%0hjToAV?EUZ_%x8{EVRRos9)Ql!ZslQ($$y*7->a)M64N|lmNL&ja7IyD_x1T zgN_auH}T5UDT+9#z^T+tljY9H*&N5SI1UPAbdbGq@`f>Qz8vvbMnr5#e+zA@2nruXp4C%!-jxfU|gx zx3Xgt$I(Mnt4x^&(6-3CzG(MuCC8p+VsnZ}0!TNlF=ZT*F^{cbN?uJwQ)0Vy6E79f zlvwj_7F!Sl*~&?dOsM#v_w7&F-NNPGAbuygW+D(7&~}?kP^}vSN$h02uImDE!dGdk zEBxRx#cD}jr(bW!56P1~YCs0dXermbNHqZBPOs?6ra&wM1^D~yb*I%9Ippfe$dCw& z22#$$#qoMe)=tj!(`MsAnUBf}LHEg7j4;b`UDq4Fu%rQBvp5zAys!_qw_1NQ0Gm_w zQWc9)b!qz!c1bErZUyLp0EQn$FjwmOI@XzF-vE=vZ;yPcEohz=YO{5cLxKr$WcS#8xmTjOOYl zT{<2_N0gmIQGly^JzB*`TG014b&!jeJH)wew7txVj^z*?knWK0e^GToI*Q9LJXT6D zCtv$10B=RB0*tf|8#x!>?22ExF=0BzAdkhXt{4rp=c#`u7BtG;Us&Q=Q?ClBhW0HKqO4Jdb*eF6T?E)U7k2~z-? znlA4R+ulv+0?g~tIGIGYr6v*J?KYhWIrr!HV>zg)gvnl>5|FQlNpzuKnd2^3Gw8x; z6bVwv>2qEDC$!_Xnnx#-iFt0G?(8?*e8Gz@5dU)z|LqWwsm7((#l%BlbR3bc=8a`! zrydL@1NCncQD{P_EvBAghz>Cj9gq$z(fpJ+3_m3u^&T?14uNjQVI|qgj;7}?JX?_- zB<+WoqaL*jvjv&Hq%R1QgPgteFd@B6m}8FV2a%L$Vvw=NMng@oB6U2bNA)5v#?pd> z8~VeJvt|$ZTF<#iEQ$r`HtOA4QAPBuoJg#rLi#(*Zm;wf7E=J)?ItWx;vMHWITaEB zmbbxNc*KEld@^f;c|}SptRQof*qScNd(@qJmQuP?#|4C226C@cG`(KGQR&65C`f%P z_5Re=7+xWFEO(j)8yiqU+vGhb=s7W=h5+H~om-uLXl3i^H1mEEQ0i9Utr?ljG0nyX zl+EIMb>?zRDo22tB)b+s3P9V7XzhWxin!l<&fUr30Xy|)G%?7y0gtB&xY9>XNoI#q zx2TPSGqR1-fpg=72HRAKbJKFHJE+$oMbThs0P;2!T%8;Bl7vO$MQuB=PLFI)+QYT! zXpJ6%5Krx5z9^lgx6mj!o(l96xT3R9{Y=_2P(4sVT$T!OZ>KF&)868l_9(;L1u~b<}IkmQl3II1!-wHh*E{PNk(%m-c48+?J-Q}Wt z(nBcqcUn7$4oEkPt1X!pC5F?A>xhtgWh*T0BX5f+Zx$rsgurTZw|V%*~P! zN)0mPD7GBMv-{;dd_9V_Huxz4d%GAjio7i=b(({6cUF1Gy0x&#m=18I&LVTlxfOP& zoQKSG_EQ43+-xRdD4mle=%K9DJ)9oo$aDM@53hl<<=!(wmwXU`Ndf(c8m*_?Am<6v z%OF;edykv)NhG&Q&$+Q}yr@~sbK&%0n{$Jkkr2E!&NECev?#;u^=$zNkhaf8_47@d zRq^y7XQ#a-&6Fu0->FQYwD6RGyvv4{`KRfuw-F+lf$~V5$IzvKUIM(2JFA z@SOQAcxCEpXWF9ww`qQ6p)c-?)>o?Pt3&-@cRua*hv@DsZy$!ha`EVxyY6Uu0WC@l z=I<-3SGZsZfagor1uqb@lpI>YP>+X}IaYQ>vn&qh2Ek!DAM6a-!QS!_Pk(AYV1#e3UMUa6 zEX68Rka$F|A$u3#1yFo>75T}<1ZjKBO?A}{bt)V^_ZZNpy!8$w95oWW3-kg2zCq@) zW-OGFHvk2BySyQDNRDHUuH*u-0vGVnfi>>}zCb!#z;#m{h!eb^Zx8L+V;A%V5?cYf zBN>PzZmZH*;2j}@UEmi;pDH7C!QW2Nt542Px&aI5z%Ky2UEIe}yUwP>;EMxlG01)W z!q-B`^H!)L`JZCyQte%3vQx|;$GssWIN34c4S~*PqIcp|Cn4Pvk*RJ4&+H|Y{fW>S z@OsuR5GyhVyjOIhW|URWITbQWFtB6W6TzdEfo5JW5&G(@^nkh3IeHn6p1`NV^^Rul z_Ks#o(8~d9BqwTYespt>cXTs?@8p>o64=qsJJnuhezY?p%3UarG}77{9~dY(tV3x6 z&pG;;AX5+d!!cWc3Icb#M?=G@VG}^4A<(w5qoW7-(a{7Uk=D`(2_G%pg-1&xV#+Kx z!w~=I=??ek=`8F>ORV)@z@w>q@MvlT+s$8*%EQ3(qpQ2{=xRhnqHEaE);rk=)p#4B zbhJ%!E8K(ErZ8a0x-I|c>nyvf{T4VHyN4Z(oh2gDjWPBFj?O+9IQTl#fBwmuy_IeZ zNB~D3mAwb=%t*viuM1IKQY|jXQzH>oC(Sg~6BrqZXa`Gy3K%5bEd#`KyZ20Xnw9#h znQ&y)S1PeV?tM}&&3%aNdw6s=494z&t!_(vco42=ozX8*j`Mql3AFO{xxN0;--wA$ zt6W_o14o0!Ea}WFQ&wbpW+to$+xv7(%rAQ9f74}E7r_8DaCJaLU`LC?ird<=x-dA9 zAnO4qfE_)Ki1aI*$jr+VvXd&894d|`AN7wIr+c}&IQY0=_8xY0Ibv&>eX1+-(9!0- z-qGfW1h+AtC4?V+-VNtWBO;taR$U7GqtS=)kaIdU)s-Zo0x>N-Iz8K<>aH6*T1_vq zgpO86IMY%#-&{?YB$0FG{-GroVc`+yjz zXS=Jqp|X(Rssq-AW^+r#95*vmi?Wa)YQH^`onDe=ND3bvzsE`n?sSXOrXE4z?E zaIo}Fd7eA16X~}m)dpK&5O|mTp4f}@C04hb4l;<;FRMC-aA$e7x^}01EY$4dLEW7d zGi*<&IUuJHM<&@+#1?MC+2b8;kLd28S5KPoqwg&|`aWAgbsfZx#_y&nU-t}iL_nFR ztM!*)!0%rAl$L`_5qcwbSQ9aIE%89gKKh!NZA&qc1(7FIf2#xpIYK%dy$>sCyOY(u zafJpF4$`-B;FDgzMHbJjF(oEQJ0Q`)AaK{DbE+_3LS%YTyUETMdpC`q5%@cE{OV<(uo_5OG>h zFMv+1Pl{E#ozKHG*2^=c2g*pxMW4>UiQG6_pst;x0u>erI#ddx*11h}^}aMwx(^c- zWzay_o%Rw5@n(^;>ua`cdT`CKh)yn4Z4wl%sZz$9245U@`QSm|`zQ#$vT#*nU*f+7qO2)S1ZF?(3)v}wsuqTBSG zP*7qJ7Q~B}qiFppJ-6!$lZh)~0G{reqEmR{Xib{?^rA)xU7`TExDPj@@aTHb$2K4U zEjraRjXgRK4!QW52wTFnNcxm zEp*PV->9Gs3Iw{NiN>Vr`=qy`MPHmzOf+Sl3>?htr%Cm0$KCLdAjZ_uF;9u-6$B`F zj2)LH0u`bXth_ z&1si*acW5A0FGiujk3HR4dhrim$hP2K$q5d&AH||L9D6iqoxxeNH{QA9S*0qk9^iN5kAS@K}0mFAXtaaxEDK0R)I9b~l{ z%948*BeyzbjN=C?IIc#tx*X2c1?xD;_{J#XRG+V{on6

    ^aXW++}yq@qTYwJltDc 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�u^6NL%&`E?sD| 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

    wPCk@Zv_ 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

    PJ*O%urp>_m7-B* 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 oGb>Ra{BPTne}+R5}1qf4r%YzyJUM diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree index a94dd16c25bc9cdac281d2d82ba3fc8ba0c6c85e..c913728166ddbc8d59b381ccc1ec5be375401af6 100644 GIT binary patch delta 1508 zcmb7DOK1~O6zz~q+DRMJ(ll7|>1)-HwkD-J3(}3X3u_~S@dMUoni+d1HZx&51D0Z= z3sGsAh;31PqnC^u5~AhAXIcMA_#8WbmM)Ke`!RQ@1FbaJLjJB?hM=>;xD>y zboD&pf4h#yQc6}Phc)e-qRtahmBz7il*AoI%7l7`p0%U=sr|z zH|HQRc*(wC$APydr$t32I8R?91x-;6VrV2&SUNbJZhVUD7fnMG3(81gNzR)iiYm$H zlVIc*#xt`?he#Zm1h$)JU$!gn1&Z4?KnT*nfeXSZh@_CAU78R>m*`{`t@92MX ztL&(|BxtEnrtwX@lhY!imy43p`I^BAH& zFB|pBLVww?U383&GSA42!vZ}>NDWyKVF1Ihy9$%7a17x%!WFilGg6C|x4LP`p5n?J zgBF*p`Q~S*I+JKh=%bxNN70AH3Fe$RCqtEAOvU&n7E19=V0;r=DZWi9J2y!Ah76km|TP-CCh9NT5MGg09ZjF9$lkl3eM8WbX>Vl%y z#|qY{kMHAN(*!@pzr;Z=nCbKiALZWC(bggQo)7t+LD(7Djrdc1SLwd?Hc%$=B>tFl_xIbO)60-mR delta 565 zcmbQEHD8Iffn}DsC^VSkDaZ~L06Ob5ibFvn$3Y^j;IN*o%WJ_I3X6xy^6a9Mn|b|NcuJE} zC!gbz-u#jGHzT9Y=C}NdnAx+~7#SF{m?zH@783`v{Yo`_. + Indices and tables ================== diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index b6e312896..b3088cf60 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -82,6 +82,7 @@

    @@ -152,6 +153,10 @@

    Welcome to pyEMU’s documentation! +

    Nice Looking and Ordered Documentation

    +

    Main entry point to pyEMU Documentation.

    +

    Indices and tables

      diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index 509fad54bbbf6568db3d366adaf5088807c7415a..e78f052bd1c66f4577ae884549fadb53334d8e26 100644 GIT binary patch delta 143 zcmV;A0C4}(V#{K%#V~)~Y>RY8soejg|3i!Vt=+I|HQT*7b*kF^>U4U0!f?R9?DZg? z4>cxh!11#0qKuR0crD%>rh?O_&HQjUT^yydcY9Bg*-6!Ck~L0v7_n!PA4l`qF@B6v xb6FU!?W~o>AXU4SI!V`cUZa+ENUKC#C7WS&w7+Z9d~5&y#XJZ5{{aFwN`MdbO~3#E delta 150 zcmV;H0BQfrV$x!;#V~)`^w$%>HJq?5(wV%L`+xL*Xi>klTYhb>+ub~MD%<_)bb@@s zaKOLp^)w(KYD`v<pyemu._version","pyemu.en","pyemu.ev","pyemu","pyemu.la","pyemu.logger","pyemu.mat","pyemu.mat.mat_handler","pyemu.mc","pyemu.plot","pyemu.plot.plot_utils","pyemu.prototypes.da","pyemu.prototypes.ensemble_method","pyemu.prototypes","pyemu.prototypes.moouu","pyemu.pst","pyemu.pst.pst_controldata","pyemu.pst.pst_handler","pyemu.pst.pst_utils","pyemu.pyemu_warnings","pyemu.sc","pyemu.utils.geostats","pyemu.utils.gw_utils","pyemu.utils.helpers","pyemu.utils","pyemu.utils.optimization","pyemu.utils.os_utils","pyemu.utils.pp_utils","pyemu.utils.pst_from","pyemu.utils.smp_utils","Welcome to pyEMU\u2019s documentation!","Welcome to pyEMU\u2019s documentation!","pyemu","pyemu package","pyemu.mat package","pyemu.plot package","pyemu.prototypes package","pyemu.pst package","pyemu.utils package"],titleterms:{"class":[6,7,8,9,10,11,12,13,14,17,18,19,20,21,22,23,24,26,27,29,30,34],"function":[6,12,13,16,24,27,28,29,30,31,32,33,34,35],_version:6,api:5,content:[6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,39,40,41,42,43,44],document:[36,37],ensemble_method:[18,42],errvar:1,geostat:[27,44],gw_util:[28,44],helper:[29,44],indic:[36,37],logger:[11,39],mat:[12,13,40],mat_handl:[13,40],modul:[6,7,8,10,11,13,14,16,17,18,20,22,23,24,25,26,27,28,29,31,32,33,34,35,39,40,41,42,43,44],moouu:[20,42],optim:[31,44],os_util:[32,44],packag:[9,12,19,21,30,39,40,41,42,43,44],parameterensembl:2,plot:[15,16,41],plot_util:[16,41],pp_util:[33,44],prototyp:[17,18,19,20,42],pst:[3,21,22,23,24,43],pst_controldata:[22,43],pst_from:[34,44],pst_handler:[23,43],pst_util:[24,43],pyemu:[0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],pyemu_warn:[25,39],refer:5,schur:4,smp_util:[35,44],submodul:[9,12,15,19,21,30,39,40,41,42,43,44],subpackag:[9,39],tabl:[36,37],util:[27,28,29,30,31,32,33,34,35,44],welcom:[36,37]}}) \ No newline at end of file +Search.setIndex({docnames:["_autosummary/pyemu","_autosummary/pyemu.ErrVar","_autosummary/pyemu.ParameterEnsemble","_autosummary/pyemu.Pst","_autosummary/pyemu.Schur","autoapi/index","autoapi/pyemu/_version/index","autoapi/pyemu/en/index","autoapi/pyemu/ev/index","autoapi/pyemu/index","autoapi/pyemu/la/index","autoapi/pyemu/logger/index","autoapi/pyemu/mat/index","autoapi/pyemu/mat/mat_handler/index","autoapi/pyemu/mc/index","autoapi/pyemu/plot/index","autoapi/pyemu/plot/plot_utils/index","autoapi/pyemu/prototypes/da/index","autoapi/pyemu/prototypes/ensemble_method/index","autoapi/pyemu/prototypes/index","autoapi/pyemu/prototypes/moouu/index","autoapi/pyemu/pst/index","autoapi/pyemu/pst/pst_controldata/index","autoapi/pyemu/pst/pst_handler/index","autoapi/pyemu/pst/pst_utils/index","autoapi/pyemu/pyemu_warnings/index","autoapi/pyemu/sc/index","autoapi/pyemu/utils/geostats/index","autoapi/pyemu/utils/gw_utils/index","autoapi/pyemu/utils/helpers/index","autoapi/pyemu/utils/index","autoapi/pyemu/utils/optimization/index","autoapi/pyemu/utils/os_utils/index","autoapi/pyemu/utils/pp_utils/index","autoapi/pyemu/utils/pst_from/index","autoapi/pyemu/utils/smp_utils/index","index","modules","pyemu","pyemu.mat","pyemu.plot","pyemu.prototypes","pyemu.pst","pyemu.utils"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,sphinx:56},filenames:["_autosummary/pyemu.rst","_autosummary/pyemu.ErrVar.rst","_autosummary/pyemu.ParameterEnsemble.rst","_autosummary/pyemu.Pst.rst","_autosummary/pyemu.Schur.rst","autoapi/index.rst","autoapi/pyemu/_version/index.rst","autoapi/pyemu/en/index.rst","autoapi/pyemu/ev/index.rst","autoapi/pyemu/index.rst","autoapi/pyemu/la/index.rst","autoapi/pyemu/logger/index.rst","autoapi/pyemu/mat/index.rst","autoapi/pyemu/mat/mat_handler/index.rst","autoapi/pyemu/mc/index.rst","autoapi/pyemu/plot/index.rst","autoapi/pyemu/plot/plot_utils/index.rst","autoapi/pyemu/prototypes/da/index.rst","autoapi/pyemu/prototypes/ensemble_method/index.rst","autoapi/pyemu/prototypes/index.rst","autoapi/pyemu/prototypes/moouu/index.rst","autoapi/pyemu/pst/index.rst","autoapi/pyemu/pst/pst_controldata/index.rst","autoapi/pyemu/pst/pst_handler/index.rst","autoapi/pyemu/pst/pst_utils/index.rst","autoapi/pyemu/pyemu_warnings/index.rst","autoapi/pyemu/sc/index.rst","autoapi/pyemu/utils/geostats/index.rst","autoapi/pyemu/utils/gw_utils/index.rst","autoapi/pyemu/utils/helpers/index.rst","autoapi/pyemu/utils/index.rst","autoapi/pyemu/utils/optimization/index.rst","autoapi/pyemu/utils/os_utils/index.rst","autoapi/pyemu/utils/pp_utils/index.rst","autoapi/pyemu/utils/pst_from/index.rst","autoapi/pyemu/utils/smp_utils/index.rst","index.rst","modules.rst","pyemu.rst","pyemu.mat.rst","pyemu.plot.rst","pyemu.prototypes.rst","pyemu.pst.rst","pyemu.utils.rst"],objects:{"":{pyemu:[9,0,0,"-"]},"pyemu.Cov":{_get_uncfile_dimensions:[9,2,1,"id36"],condition_on:[9,2,1,"id27"],from_observation_data:[9,2,1,"id32"],from_obsweights:[9,2,1,"id31"],from_parameter_data:[9,2,1,"id34"],from_parbounds:[9,2,1,"id33"],from_uncfile:[9,2,1,"id35"],identity:[9,2,1,"id25"],identity_like:[9,2,1,"id37"],names:[9,2,1,"id28"],replace:[9,2,1,"id29"],to_pearson:[9,2,1,"id38"],to_uncfile:[9,2,1,"id30"],zero:[9,2,1,"id26"]},"pyemu.Ensemble":{__add__:[9,2,1,""],__getattr__:[9,2,1,""],__mul__:[9,2,1,""],__pow__:[9,2,1,""],__repr__:[9,2,1,""],__str__:[9,2,1,""],__sub__:[9,2,1,""],__truediv__:[9,2,1,""],_df:[9,3,1,""],_gaussian_draw:[9,2,1,""],_get_eigen_projection_matrix:[9,2,1,""],_get_svd_projection_matrix:[9,2,1,""],as_pyemu_matrix:[9,2,1,""],back_transform:[9,2,1,""],copy:[9,2,1,""],covariance_matrix:[9,2,1,""],dropna:[9,2,1,""],from_binary:[9,2,1,""],from_csv:[9,2,1,""],from_dataframe:[9,2,1,""],get_deviations:[9,2,1,""],istransformed:[9,2,1,""],plot:[9,2,1,""],pst:[9,3,1,""],reseed:[9,2,1,""],to_binary:[9,2,1,""],to_csv:[9,2,1,""],transform:[9,2,1,""]},"pyemu.ErrVar":{G:[9,2,1,""],I_minus_R:[9,2,1,""],R:[9,2,1,""],__load_omitted_jco:[9,2,1,""],__load_omitted_parcov:[9,2,1,""],__load_omitted_predictions:[9,2,1,""],first_forecast:[9,2,1,""],first_parameter:[9,2,1,""],first_prediction:[9,2,1,""],get_errvar_dataframe:[9,2,1,""],get_identifiability_dataframe:[9,2,1,""],get_null_proj:[9,2,1,""],omitted_jco:[9,2,1,""],omitted_parcov:[9,2,1,""],omitted_predictions:[9,2,1,""],second_forecast:[9,2,1,""],second_parameter:[9,2,1,""],second_prediction:[9,2,1,""],third_forecast:[9,2,1,""],third_parameter:[9,2,1,""],third_prediction:[9,2,1,""],variance_at:[9,2,1,""]},"pyemu.Jco":{__init:[9,2,1,""],from_pst:[9,2,1,""],nobs:[9,2,1,""],npar:[9,2,1,""],obs_names:[9,2,1,""],par_names:[9,2,1,""]},"pyemu.LinearAnalysis":{__fromfile:[9,2,1,""],__load_jco:[9,2,1,""],__load_obscov:[9,2,1,""],__load_parcov:[9,2,1,""],__load_predictions:[9,2,1,""],__load_pst:[9,2,1,""],adj_par_names:[9,2,1,""],adjust_obscov_resfile:[9,2,1,""],apply_karhunen_loeve_scaling:[9,2,1,""],clean:[9,2,1,""],drop_prior_information:[9,2,1,""],fehalf:[9,2,1,""],forecast_names:[9,2,1,""],forecasts:[9,2,1,""],forecasts_iter:[9,2,1,""],get:[9,2,1,""],get_cso_dataframe:[9,2,1,""],get_obs_competition_dataframe:[9,2,1,""],get_par_css_dataframe:[9,2,1,""],jco:[9,2,1,""],mle_covariance:[9,2,1,""],mle_parameter_estimate:[9,2,1,""],nnz_obs_names:[9,2,1,""],obscov:[9,2,1,""],parcov:[9,2,1,""],predictions:[9,2,1,""],predictions_iter:[9,2,1,""],prior_forecast:[9,2,1,""],prior_parameter:[9,2,1,""],prior_prediction:[9,2,1,""],pst:[9,2,1,""],qhalf:[9,2,1,""],qhalfx:[9,2,1,""],reset_obscov:[9,2,1,""],reset_parcov:[9,2,1,""],reset_pst:[9,2,1,""],xtqx:[9,2,1,""]},"pyemu.Matrix":{"char":[9,3,1,"id42"],"double":[9,3,1,"id41"],T:[9,2,1,"id69"],__add__:[9,2,1,"id55"],__getitem__:[9,2,1,"id52"],__mul__:[9,2,1,"id57"],__pow__:[9,2,1,"id53"],__rmul__:[9,2,1,"id58"],__set_svd:[9,2,1,"id59"],__str__:[9,2,1,"id51"],__sub__:[9,2,1,"id54"],align:[9,2,1,"id84"],as_2d:[9,2,1,"id64"],binary_header_dt:[9,3,1,"id43"],binary_rec_dt:[9,3,1,"id44"],coo_rec_dt:[9,3,1,"id45"],copy:[9,2,1,"id86"],df:[9,2,1,"id98"],drop:[9,2,1,"id87"],element_isaligned:[9,2,1,"id61"],extend:[9,2,1,"id102"],extract:[9,2,1,"id88"],find_rowcol_indices:[9,2,1,"id82"],from_ascii:[9,2,1,"id96"],from_binary:[9,2,1,"id92"],from_dataframe:[9,2,1,"id99"],from_fortranfile:[9,2,1,"id94"],from_names:[9,2,1,"id100"],full_s:[9,2,1,"id77"],get:[9,2,1,"id85"],get_diagonal_vector:[9,2,1,"id89"],get_maxsing:[9,2,1,"id73"],get_maxsing_from_s:[9,2,1,"id72"],hadamard_product:[9,2,1,"id56"],indices:[9,2,1,"id83"],integer:[9,3,1,"id40"],inv:[9,2,1,"id71"],mult_isaligned:[9,2,1,"id60"],ncol:[9,2,1,"id67"],new_obs_length:[9,3,1,"id49"],new_par_length:[9,3,1,"id48"],newx:[9,2,1,"id62"],nrow:[9,2,1,"id68"],obs_length:[9,3,1,"id47"],par_length:[9,3,1,"id46"],pseudo_inv:[9,2,1,"id75"],pseudo_inv_components:[9,2,1,"id74"],read_ascii:[9,2,1,"id97"],read_binary:[9,2,1,"id93"],reset_x:[9,2,1,"id50"],s:[9,2,1,"id78"],shape:[9,2,1,"id66"],sqrt:[9,2,1,"id76"],to_2d:[9,2,1,"id65"],to_ascii:[9,2,1,"id95"],to_binary:[9,2,1,"id91"],to_coo:[9,2,1,"id90"],to_dataframe:[9,2,1,"id101"],transpose:[9,2,1,"id70"],u:[9,2,1,"id79"],v:[9,2,1,"id80"],x:[9,2,1,"id63"],zero2d:[9,2,1,"id81"]},"pyemu.ObservationEnsemble":{add_base:[9,2,1,"id22"],from_gaussian_draw:[9,2,1,"id20"],nonzero:[9,2,1,"id23"],phi_vector:[9,2,1,"id21"]},"pyemu.ParameterEnsemble":{_enforce_drop:[9,2,1,"id17"],_enforce_reset:[9,2,1,"id18"],_enforce_scale:[9,2,1,"id16"],add_base:[9,2,1,"id8"],adj_names:[9,2,1,"id9"],back_transform:[9,2,1,"id6"],enforce:[9,2,1,"id15"],fixed_indexer:[9,2,1,"id13"],from_gaussian_draw:[9,2,1,"id1"],from_mixed_draws:[9,2,1,"id4"],from_parfiles:[9,2,1,"id5"],from_triangular_draw:[9,2,1,"id2"],from_uniform_draw:[9,2,1,"id3"],lbnd:[9,2,1,"id11"],log_indexer:[9,2,1,"id12"],project:[9,2,1,"id14"],transform:[9,2,1,"id7"],ubnd:[9,2,1,"id10"]},"pyemu.Pst":{__reset_weights:[9,2,1,"id161"],__setattr__:[9,2,1,"id110"],_adjust_weights_by_list:[9,2,1,"id162"],_adjust_weights_by_phi_components:[9,2,1,"id160"],_cast_df_from_lines:[9,2,1,"id141"],_cast_prior_df_from_lines:[9,2,1,"id142"],_is_greater_const:[9,2,1,"id186"],_is_less_const:[9,2,1,"id183"],_load_version2:[9,2,1,"id143"],_parse_external_line:[9,2,1,"id139"],_parse_path_agnostic:[9,2,1,"id140"],_parse_pestpp_line:[9,2,1,"id145"],_parse_pi_par_names:[9,2,1,"id148"],_read_df:[9,2,1,"id136"],_read_line_comments:[9,2,1,"id137"],_read_section_comments:[9,2,1,"id138"],_stats_mae:[9,2,1,"id177"],_stats_mean:[9,2,1,"id176"],_stats_nrmse:[9,2,1,"id179"],_stats_rmse:[9,2,1,"id178"],_stats_rss:[9,2,1,"id175"],_update_control_section:[9,2,1,"id146"],_write_df:[9,2,1,"id151"],_write_version1:[9,2,1,"id155"],_write_version2:[9,2,1,"id153"],add_observations:[9,2,1,"id171"],add_parameters:[9,2,1,"id170"],add_pi_equation:[9,2,1,"id149"],add_transform_columns:[9,2,1,"id167"],adj_par_groups:[9,2,1,"id125"],adj_par_names:[9,2,1,"id130"],adjust_weights:[9,2,1,"id163"],adjust_weights_discrepancy:[9,2,1,"id159"],bounds_report:[9,2,1,"id156"],build_increments:[9,2,1,"id166"],calculate_pertubations:[9,2,1,"id165"],control_data:[9,3,1,"id107"],enforce_bounds:[9,2,1,"id168"],estimation:[9,2,1,"id134"],forecast_names:[9,2,1,"id122"],from_io_files:[9,2,1,"id169"],from_par_obs_names:[9,2,1,"id111"],get:[9,2,1,"id157"],get_adj_pars_at_bounds:[9,2,1,"id190"],get_par_change_limits:[9,2,1,"id189"],get_res_stats:[9,2,1,"id174"],greater_than_obs_constraints:[9,2,1,"id187"],greater_than_pi_constraints:[9,2,1,"id188"],less_than_obs_constraints:[9,2,1,"id184"],less_than_pi_constraints:[9,2,1,"id185"],load:[9,2,1,"id144"],nnz_obs:[9,2,1,"id118"],nnz_obs_groups:[9,2,1,"id124"],nnz_obs_names:[9,2,1,"id132"],nobs:[9,2,1,"id119"],npar:[9,2,1,"id121"],npar_adj:[9,2,1,"id120"],nprior:[9,2,1,"id117"],obs_groups:[9,2,1,"id123"],obs_names:[9,2,1,"id131"],observation_data:[9,3,1,"id105"],par_groups:[9,2,1,"id126"],par_names:[9,2,1,"id129"],parameter_data:[9,3,1,"id104"],parrep:[9,2,1,"id158"],phi:[9,2,1,"id112"],phi_components:[9,2,1,"id113"],phi_components_normalized:[9,2,1,"id114"],plot:[9,2,1,"id180"],prior_groups:[9,2,1,"id127"],prior_information:[9,3,1,"id106"],prior_names:[9,2,1,"id128"],process_output_files:[9,2,1,"id173"],proportional_weights:[9,2,1,"id164"],rectify_pgroups:[9,2,1,"id147"],rectify_pi:[9,2,1,"id150"],reg_data:[9,3,1,"id109"],res:[9,2,1,"id116"],sanity_checks:[9,2,1,"id152"],set_res:[9,2,1,"id115"],svd_data:[9,3,1,"id108"],tied:[9,2,1,"id135"],try_parse_name_metadata:[9,2,1,"id191"],write:[9,2,1,"id154"],write_input_files:[9,2,1,"id172"],write_obs_summary_table:[9,2,1,"id182"],write_par_summary_table:[9,2,1,"id181"],zero_weight_obs_names:[9,2,1,"id133"]},"pyemu.Schur":{__contribution_from_parameters:[9,2,1,""],get_added_obs_group_importance:[9,2,1,""],get_added_obs_importance:[9,2,1,""],get_conditional_instance:[9,2,1,""],get_forecast_summary:[9,2,1,""],get_obs_group_dict:[9,2,1,""],get_par_contribution:[9,2,1,""],get_par_group_contribution:[9,2,1,""],get_parameter_summary:[9,2,1,""],get_removed_obs_group_importance:[9,2,1,""],get_removed_obs_importance:[9,2,1,""],next_most_important_added_obs:[9,2,1,""],next_most_par_contribution:[9,2,1,""],posterior_forecast:[9,2,1,""],posterior_parameter:[9,2,1,""],posterior_prediction:[9,2,1,""]},"pyemu._version":{HANDLERS:[6,4,1,""],LONG_VERSION_PY:[6,4,1,""],NotThisMethod:[6,5,1,""],VersioneerConfig:[6,1,1,""],get_config:[6,6,1,""],get_keywords:[6,6,1,""],get_versions:[6,6,1,""],git_get_keywords:[6,6,1,""],git_pieces_from_vcs:[6,6,1,""],git_versions_from_keywords:[6,6,1,""],plus_or_dot:[6,6,1,""],register_vcs_handler:[6,6,1,""],render:[6,6,1,""],render_git_describe:[6,6,1,""],render_git_describe_long:[6,6,1,""],render_pep440:[6,6,1,""],render_pep440_old:[6,6,1,""],render_pep440_post:[6,6,1,""],render_pep440_pre:[6,6,1,""],run_command:[6,6,1,""],versions_from_parentdir:[6,6,1,""]},"pyemu.en":{Ensemble:[7,1,1,""],Iloc:[7,1,1,""],Loc:[7,1,1,""],ObservationEnsemble:[7,1,1,""],ParameterEnsemble:[7,1,1,""],SEED:[7,4,1,""]},"pyemu.en.Ensemble":{__add__:[7,2,1,""],__getattr__:[7,2,1,""],__mul__:[7,2,1,""],__pow__:[7,2,1,""],__repr__:[7,2,1,""],__str__:[7,2,1,""],__sub__:[7,2,1,""],__truediv__:[7,2,1,""],_df:[7,3,1,""],_gaussian_draw:[7,2,1,""],_get_eigen_projection_matrix:[7,2,1,""],_get_svd_projection_matrix:[7,2,1,""],as_pyemu_matrix:[7,2,1,""],back_transform:[7,2,1,""],copy:[7,2,1,""],covariance_matrix:[7,2,1,""],dropna:[7,2,1,""],from_binary:[7,2,1,""],from_csv:[7,2,1,""],from_dataframe:[7,2,1,""],get_deviations:[7,2,1,""],istransformed:[7,2,1,""],plot:[7,2,1,""],pst:[7,3,1,""],reseed:[7,2,1,""],to_binary:[7,2,1,""],to_csv:[7,2,1,""],transform:[7,2,1,""]},"pyemu.en.Iloc":{__getitem__:[7,2,1,""],__setitem__:[7,2,1,""]},"pyemu.en.Loc":{__getitem__:[7,2,1,""],__setitem__:[7,2,1,""]},"pyemu.en.ObservationEnsemble":{add_base:[7,2,1,""],from_gaussian_draw:[7,2,1,""],nonzero:[7,2,1,""],phi_vector:[7,2,1,""]},"pyemu.en.ParameterEnsemble":{_enforce_drop:[7,2,1,""],_enforce_reset:[7,2,1,""],_enforce_scale:[7,2,1,""],add_base:[7,2,1,""],adj_names:[7,2,1,""],back_transform:[7,2,1,""],enforce:[7,2,1,""],fixed_indexer:[7,2,1,""],from_gaussian_draw:[7,2,1,""],from_mixed_draws:[7,2,1,""],from_parfiles:[7,2,1,""],from_triangular_draw:[7,2,1,""],from_uniform_draw:[7,2,1,""],lbnd:[7,2,1,""],log_indexer:[7,2,1,""],project:[7,2,1,""],transform:[7,2,1,""],ubnd:[7,2,1,""]},"pyemu.ev":{ErrVar:[8,1,1,""]},"pyemu.ev.ErrVar":{G:[8,2,1,""],I_minus_R:[8,2,1,""],R:[8,2,1,""],__load_omitted_jco:[8,2,1,""],__load_omitted_parcov:[8,2,1,""],__load_omitted_predictions:[8,2,1,""],first_forecast:[8,2,1,""],first_parameter:[8,2,1,""],first_prediction:[8,2,1,""],get_errvar_dataframe:[8,2,1,""],get_identifiability_dataframe:[8,2,1,""],get_null_proj:[8,2,1,""],omitted_jco:[8,2,1,""],omitted_parcov:[8,2,1,""],omitted_predictions:[8,2,1,""],second_forecast:[8,2,1,""],second_parameter:[8,2,1,""],second_prediction:[8,2,1,""],third_forecast:[8,2,1,""],third_parameter:[8,2,1,""],third_prediction:[8,2,1,""],variance_at:[8,2,1,""]},"pyemu.la":{LinearAnalysis:[10,1,1,""]},"pyemu.la.LinearAnalysis":{__fromfile:[10,2,1,""],__load_jco:[10,2,1,""],__load_obscov:[10,2,1,""],__load_parcov:[10,2,1,""],__load_predictions:[10,2,1,""],__load_pst:[10,2,1,""],adj_par_names:[10,2,1,""],adjust_obscov_resfile:[10,2,1,""],apply_karhunen_loeve_scaling:[10,2,1,""],clean:[10,2,1,""],drop_prior_information:[10,2,1,""],fehalf:[10,2,1,""],forecast_names:[10,2,1,""],forecasts:[10,2,1,""],forecasts_iter:[10,2,1,""],get:[10,2,1,""],get_cso_dataframe:[10,2,1,""],get_obs_competition_dataframe:[10,2,1,""],get_par_css_dataframe:[10,2,1,""],jco:[10,2,1,""],mle_covariance:[10,2,1,""],mle_parameter_estimate:[10,2,1,""],nnz_obs_names:[10,2,1,""],obscov:[10,2,1,""],parcov:[10,2,1,""],predictions:[10,2,1,""],predictions_iter:[10,2,1,""],prior_forecast:[10,2,1,""],prior_parameter:[10,2,1,""],prior_prediction:[10,2,1,""],pst:[10,2,1,""],qhalf:[10,2,1,""],qhalfx:[10,2,1,""],reset_obscov:[10,2,1,""],reset_parcov:[10,2,1,""],reset_pst:[10,2,1,""],xtqx:[10,2,1,""]},"pyemu.logger":{Logger:[11,1,1,""]},"pyemu.logger.Logger":{log:[11,2,1,""],lraise:[11,2,1,""],statement:[11,2,1,""],warn:[11,2,1,""]},"pyemu.mat":{Cov:[12,1,1,""],Jco:[12,1,1,""],Matrix:[12,1,1,""],concat:[12,6,1,""],mat_handler:[13,0,0,"-"],save_coo:[12,6,1,""]},"pyemu.mat.Cov":{_get_uncfile_dimensions:[12,2,1,""],condition_on:[12,2,1,""],from_observation_data:[12,2,1,""],from_obsweights:[12,2,1,""],from_parameter_data:[12,2,1,""],from_parbounds:[12,2,1,""],from_uncfile:[12,2,1,""],identity:[12,2,1,""],identity_like:[12,2,1,""],names:[12,2,1,""],replace:[12,2,1,""],to_pearson:[12,2,1,""],to_uncfile:[12,2,1,""],zero:[12,2,1,""]},"pyemu.mat.Jco":{__init:[12,2,1,""],from_pst:[12,2,1,""],nobs:[12,2,1,""],npar:[12,2,1,""],obs_names:[12,2,1,""],par_names:[12,2,1,""]},"pyemu.mat.Matrix":{"char":[12,3,1,""],"double":[12,3,1,""],T:[12,2,1,""],__add__:[12,2,1,""],__getitem__:[12,2,1,""],__mul__:[12,2,1,""],__pow__:[12,2,1,""],__rmul__:[12,2,1,""],__set_svd:[12,2,1,""],__str__:[12,2,1,""],__sub__:[12,2,1,""],align:[12,2,1,""],as_2d:[12,2,1,""],binary_header_dt:[12,3,1,""],binary_rec_dt:[12,3,1,""],coo_rec_dt:[12,3,1,""],copy:[12,2,1,""],df:[12,2,1,""],drop:[12,2,1,""],element_isaligned:[12,2,1,""],extend:[12,2,1,""],extract:[12,2,1,""],find_rowcol_indices:[12,2,1,""],from_ascii:[12,2,1,""],from_binary:[12,2,1,""],from_dataframe:[12,2,1,""],from_fortranfile:[12,2,1,""],from_names:[12,2,1,""],full_s:[12,2,1,""],get:[12,2,1,""],get_diagonal_vector:[12,2,1,""],get_maxsing:[12,2,1,""],get_maxsing_from_s:[12,2,1,""],hadamard_product:[12,2,1,""],indices:[12,2,1,""],integer:[12,3,1,""],inv:[12,2,1,""],mult_isaligned:[12,2,1,""],ncol:[12,2,1,""],new_obs_length:[12,3,1,""],new_par_length:[12,3,1,""],newx:[12,2,1,""],nrow:[12,2,1,""],obs_length:[12,3,1,""],par_length:[12,3,1,""],pseudo_inv:[12,2,1,""],pseudo_inv_components:[12,2,1,""],read_ascii:[12,2,1,""],read_binary:[12,2,1,""],reset_x:[12,2,1,""],s:[12,2,1,""],shape:[12,2,1,""],sqrt:[12,2,1,""],to_2d:[12,2,1,""],to_ascii:[12,2,1,""],to_binary:[12,2,1,""],to_coo:[12,2,1,""],to_dataframe:[12,2,1,""],transpose:[12,2,1,""],u:[12,2,1,""],v:[12,2,1,""],x:[12,2,1,""],zero2d:[12,2,1,""]},"pyemu.mat.mat_handler":{Cov:[13,1,1,""],Jco:[13,1,1,""],Matrix:[13,1,1,""],concat:[13,6,1,""],get_common_elements:[13,6,1,""],save_coo:[13,6,1,""]},"pyemu.mat.mat_handler.Cov":{_get_uncfile_dimensions:[13,2,1,""],condition_on:[13,2,1,""],from_observation_data:[13,2,1,""],from_obsweights:[13,2,1,""],from_parameter_data:[13,2,1,""],from_parbounds:[13,2,1,""],from_uncfile:[13,2,1,""],identity:[13,2,1,""],identity_like:[13,2,1,""],names:[13,2,1,""],replace:[13,2,1,""],to_pearson:[13,2,1,""],to_uncfile:[13,2,1,""],zero:[13,2,1,""]},"pyemu.mat.mat_handler.Jco":{__init:[13,2,1,""],from_pst:[13,2,1,""],nobs:[13,2,1,""],npar:[13,2,1,""],obs_names:[13,2,1,""],par_names:[13,2,1,""]},"pyemu.mat.mat_handler.Matrix":{"char":[13,3,1,""],"double":[13,3,1,""],T:[13,2,1,""],__add__:[13,2,1,""],__getitem__:[13,2,1,""],__mul__:[13,2,1,""],__pow__:[13,2,1,""],__rmul__:[13,2,1,""],__set_svd:[13,2,1,""],__str__:[13,2,1,""],__sub__:[13,2,1,""],align:[13,2,1,""],as_2d:[13,2,1,""],binary_header_dt:[13,3,1,""],binary_rec_dt:[13,3,1,""],coo_rec_dt:[13,3,1,""],copy:[13,2,1,""],df:[13,2,1,""],drop:[13,2,1,""],element_isaligned:[13,2,1,""],extend:[13,2,1,""],extract:[13,2,1,""],find_rowcol_indices:[13,2,1,""],from_ascii:[13,2,1,""],from_binary:[13,2,1,""],from_dataframe:[13,2,1,""],from_fortranfile:[13,2,1,""],from_names:[13,2,1,""],full_s:[13,2,1,""],get:[13,2,1,""],get_diagonal_vector:[13,2,1,""],get_maxsing:[13,2,1,""],get_maxsing_from_s:[13,2,1,""],hadamard_product:[13,2,1,""],indices:[13,2,1,""],integer:[13,3,1,""],inv:[13,2,1,""],mult_isaligned:[13,2,1,""],ncol:[13,2,1,""],new_obs_length:[13,3,1,""],new_par_length:[13,3,1,""],newx:[13,2,1,""],nrow:[13,2,1,""],obs_length:[13,3,1,""],par_length:[13,3,1,""],pseudo_inv:[13,2,1,""],pseudo_inv_components:[13,2,1,""],read_ascii:[13,2,1,""],read_binary:[13,2,1,""],reset_x:[13,2,1,""],s:[13,2,1,""],shape:[13,2,1,""],sqrt:[13,2,1,""],to_2d:[13,2,1,""],to_ascii:[13,2,1,""],to_binary:[13,2,1,""],to_coo:[13,2,1,""],to_dataframe:[13,2,1,""],transpose:[13,2,1,""],u:[13,2,1,""],v:[13,2,1,""],x:[13,2,1,""],zero2d:[13,2,1,""]},"pyemu.mc":{MonteCarlo:[14,1,1,""]},"pyemu.mc.MonteCarlo":{draw:[14,2,1,""],get_nsing:[14,2,1,""],get_null_proj:[14,2,1,""],num_reals:[14,2,1,""],obsensemble:[14,3,1,""],parensemble:[14,3,1,""],project_parensemble:[14,2,1,""],write_psts:[14,2,1,""]},"pyemu.plot":{plot_utils:[16,0,0,"-"]},"pyemu.plot.plot_utils":{_get_page_axes:[16,6,1,""],_process_ensemble_arg:[16,6,1,""],abet:[16,4,1,""],ensemble_change_summary:[16,6,1,""],ensemble_helper:[16,6,1,""],ensemble_res_1to1:[16,6,1,""],figsize:[16,4,1,""],font:[16,4,1,""],gaussian_distribution:[16,6,1,""],phi_progress:[16,6,1,""],plot_id_bar:[16,6,1,""],plot_jac_test:[16,6,1,""],plot_summary_distributions:[16,6,1,""],pst_helper:[16,6,1,""],pst_prior:[16,6,1,""],res_1to1:[16,6,1,""],res_phi_pie:[16,6,1,""]},"pyemu.prototypes":{Assimilator:[19,1,1,""],Cov:[19,1,1,""],EliteDiffEvol:[19,1,1,""],EnsembleKalmanFilter:[19,1,1,""],EnsembleMethod:[19,1,1,"id9"],EvolAlg:[19,1,1,""],Logger:[19,1,1,""],Matrix:[19,1,1,""],ObservationEnsemble:[19,1,1,""],ParameterEnsemble:[19,1,1,""],ParetoObjFunc:[19,1,1,""],Pst:[19,1,1,""],da:[17,0,0,"-"],ensemble_method:[18,0,0,"-"],moouu:[20,0,0,"-"],sm:[19,4,1,""]},"pyemu.prototypes.Assimilator":{enkf:[19,2,1,""],enks:[19,2,1,""],forcast:[19,2,1,""],generate_priors:[19,2,1,""],model_evalutions:[19,2,1,""],model_temporal_evolotion:[19,2,1,""],run:[19,2,1,""],smoother:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.Cov":{_get_uncfile_dimensions:[19,2,1,""],condition_on:[19,2,1,""],from_observation_data:[19,2,1,""],from_obsweights:[19,2,1,""],from_parameter_data:[19,2,1,""],from_parbounds:[19,2,1,""],from_uncfile:[19,2,1,""],identity:[19,2,1,""],identity_like:[19,2,1,""],names:[19,2,1,""],replace:[19,2,1,""],to_pearson:[19,2,1,""],to_uncfile:[19,2,1,""],zero:[19,2,1,""]},"pyemu.prototypes.EliteDiffEvol":{_drop_by_crowd:[19,2,1,""],iter_report:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.EnsembleKalmanFilter":{analysis:[19,2,1,""],analysis_evensen:[19,2,1,""],forecast:[19,2,1,""],initialize:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.EnsembleMethod":{_calc_delta:[19,2,1,"id11"],_calc_obs:[19,2,1,"id12"],_calc_obs_condor:[19,2,1,"id15"],_calc_obs_local:[19,2,1,"id16"],_get_master_thread:[19,2,1,"id14"],_load_obs_ensemble:[19,2,1,"id13"],initialize:[19,2,1,"id10"],update:[19,2,1,"id17"]},"pyemu.prototypes.EvolAlg":{_archive:[19,2,1,""],_calc_obs:[19,2,1,""],_drop_failed:[19,2,1,""],initialize:[19,2,1,""],update:[19,2,1,""]},"pyemu.prototypes.Logger":{log:[19,2,1,""],lraise:[19,2,1,""],statement:[19,2,1,""],warn:[19,2,1,""]},"pyemu.prototypes.Matrix":{"char":[19,3,1,""],"double":[19,3,1,""],T:[19,2,1,""],__add__:[19,2,1,""],__getitem__:[19,2,1,""],__mul__:[19,2,1,""],__pow__:[19,2,1,""],__rmul__:[19,2,1,""],__set_svd:[19,2,1,""],__str__:[19,2,1,""],__sub__:[19,2,1,""],align:[19,2,1,""],as_2d:[19,2,1,""],binary_header_dt:[19,3,1,""],binary_rec_dt:[19,3,1,""],coo_rec_dt:[19,3,1,""],copy:[19,2,1,""],df:[19,2,1,""],drop:[19,2,1,""],element_isaligned:[19,2,1,""],extend:[19,2,1,""],extract:[19,2,1,""],find_rowcol_indices:[19,2,1,""],from_ascii:[19,2,1,""],from_binary:[19,2,1,""],from_dataframe:[19,2,1,""],from_fortranfile:[19,2,1,""],from_names:[19,2,1,""],full_s:[19,2,1,""],get:[19,2,1,""],get_diagonal_vector:[19,2,1,""],get_maxsing:[19,2,1,""],get_maxsing_from_s:[19,2,1,""],hadamard_product:[19,2,1,""],indices:[19,2,1,""],integer:[19,3,1,""],inv:[19,2,1,""],mult_isaligned:[19,2,1,""],ncol:[19,2,1,""],new_obs_length:[19,3,1,""],new_par_length:[19,3,1,""],newx:[19,2,1,""],nrow:[19,2,1,""],obs_length:[19,3,1,""],par_length:[19,3,1,""],pseudo_inv:[19,2,1,""],pseudo_inv_components:[19,2,1,""],read_ascii:[19,2,1,""],read_binary:[19,2,1,""],reset_x:[19,2,1,""],s:[19,2,1,""],shape:[19,2,1,""],sqrt:[19,2,1,""],to_2d:[19,2,1,""],to_ascii:[19,2,1,""],to_binary:[19,2,1,""],to_coo:[19,2,1,""],to_dataframe:[19,2,1,""],transpose:[19,2,1,""],u:[19,2,1,""],v:[19,2,1,""],x:[19,2,1,""],zero2d:[19,2,1,""]},"pyemu.prototypes.ObservationEnsemble":{add_base:[19,2,1,""],from_gaussian_draw:[19,2,1,""],nonzero:[19,2,1,""],phi_vector:[19,2,1,""]},"pyemu.prototypes.ParameterEnsemble":{_enforce_drop:[19,2,1,""],_enforce_reset:[19,2,1,""],_enforce_scale:[19,2,1,""],add_base:[19,2,1,""],adj_names:[19,2,1,""],back_transform:[19,2,1,""],enforce:[19,2,1,""],fixed_indexer:[19,2,1,""],from_gaussian_draw:[19,2,1,""],from_mixed_draws:[19,2,1,""],from_parfiles:[19,2,1,""],from_triangular_draw:[19,2,1,""],from_uniform_draw:[19,2,1,""],lbnd:[19,2,1,""],log_indexer:[19,2,1,""],project:[19,2,1,""],transform:[19,2,1,""],ubnd:[19,2,1,""]},"pyemu.prototypes.ParetoObjFunc":{crowd_distance:[19,2,1,""],dominates:[19,2,1,""],get_risk_shifted_value:[19,2,1,""],is_feasible:[19,2,1,""],is_nondominated_continuous:[19,2,1,""],is_nondominated_kung:[19,2,1,""],is_nondominated_pathetic:[19,2,1,""],obs_obj_signs:[19,2,1,""],reduce_stack_with_risk_shift:[19,2,1,""]},"pyemu.prototypes.Pst":{__reset_weights:[19,2,1,""],__setattr__:[19,2,1,""],_adjust_weights_by_list:[19,2,1,""],_adjust_weights_by_phi_components:[19,2,1,""],_cast_df_from_lines:[19,2,1,""],_cast_prior_df_from_lines:[19,2,1,""],_is_greater_const:[19,2,1,""],_is_less_const:[19,2,1,""],_load_version2:[19,2,1,""],_parse_external_line:[19,2,1,""],_parse_path_agnostic:[19,2,1,""],_parse_pestpp_line:[19,2,1,""],_parse_pi_par_names:[19,2,1,""],_read_df:[19,2,1,""],_read_line_comments:[19,2,1,""],_read_section_comments:[19,2,1,""],_stats_mae:[19,2,1,""],_stats_mean:[19,2,1,""],_stats_nrmse:[19,2,1,""],_stats_rmse:[19,2,1,""],_stats_rss:[19,2,1,""],_update_control_section:[19,2,1,""],_write_df:[19,2,1,""],_write_version1:[19,2,1,""],_write_version2:[19,2,1,""],add_observations:[19,2,1,""],add_parameters:[19,2,1,""],add_pi_equation:[19,2,1,""],add_transform_columns:[19,2,1,""],adj_par_groups:[19,2,1,""],adj_par_names:[19,2,1,""],adjust_weights:[19,2,1,""],adjust_weights_discrepancy:[19,2,1,""],bounds_report:[19,2,1,""],build_increments:[19,2,1,""],calculate_pertubations:[19,2,1,""],control_data:[19,3,1,""],enforce_bounds:[19,2,1,""],estimation:[19,2,1,""],forecast_names:[19,2,1,""],from_io_files:[19,2,1,""],from_par_obs_names:[19,2,1,""],get:[19,2,1,""],get_adj_pars_at_bounds:[19,2,1,""],get_par_change_limits:[19,2,1,""],get_res_stats:[19,2,1,""],greater_than_obs_constraints:[19,2,1,""],greater_than_pi_constraints:[19,2,1,""],less_than_obs_constraints:[19,2,1,""],less_than_pi_constraints:[19,2,1,""],load:[19,2,1,""],nnz_obs:[19,2,1,""],nnz_obs_groups:[19,2,1,""],nnz_obs_names:[19,2,1,""],nobs:[19,2,1,""],npar:[19,2,1,""],npar_adj:[19,2,1,""],nprior:[19,2,1,""],obs_groups:[19,2,1,""],obs_names:[19,2,1,""],observation_data:[19,3,1,""],par_groups:[19,2,1,""],par_names:[19,2,1,""],parameter_data:[19,3,1,""],parrep:[19,2,1,""],phi:[19,2,1,""],phi_components:[19,2,1,""],phi_components_normalized:[19,2,1,""],plot:[19,2,1,""],prior_groups:[19,2,1,""],prior_information:[19,3,1,""],prior_names:[19,2,1,""],process_output_files:[19,2,1,""],proportional_weights:[19,2,1,""],rectify_pgroups:[19,2,1,""],rectify_pi:[19,2,1,""],reg_data:[19,3,1,""],res:[19,2,1,""],sanity_checks:[19,2,1,""],set_res:[19,2,1,""],svd_data:[19,3,1,""],tied:[19,2,1,""],try_parse_name_metadata:[19,2,1,""],write:[19,2,1,""],write_input_files:[19,2,1,""],write_obs_summary_table:[19,2,1,""],write_par_summary_table:[19,2,1,""],zero_weight_obs_names:[19,2,1,""]},"pyemu.prototypes.da":{Assimilator:[17,1,1,""],EnsembleKalmanFilter:[17,1,1,""],sm:[17,4,1,""]},"pyemu.prototypes.da.Assimilator":{enkf:[17,2,1,""],enks:[17,2,1,""],forcast:[17,2,1,""],generate_priors:[17,2,1,""],model_evalutions:[17,2,1,""],model_temporal_evolotion:[17,2,1,""],run:[17,2,1,""],smoother:[17,2,1,""],update:[17,2,1,""]},"pyemu.prototypes.da.EnsembleKalmanFilter":{analysis:[17,2,1,""],analysis_evensen:[17,2,1,""],forecast:[17,2,1,""],initialize:[17,2,1,""],update:[17,2,1,""]},"pyemu.prototypes.ensemble_method":{EnsembleMethod:[18,1,1,""]},"pyemu.prototypes.ensemble_method.EnsembleMethod":{_calc_delta:[18,2,1,""],_calc_obs:[18,2,1,""],_calc_obs_condor:[18,2,1,""],_calc_obs_local:[18,2,1,""],_get_master_thread:[18,2,1,""],_load_obs_ensemble:[18,2,1,""],initialize:[18,2,1,""],update:[18,2,1,""]},"pyemu.prototypes.moouu":{EliteDiffEvol:[20,1,1,""],EvolAlg:[20,1,1,""],ParetoObjFunc:[20,1,1,""]},"pyemu.prototypes.moouu.EliteDiffEvol":{_drop_by_crowd:[20,2,1,""],iter_report:[20,2,1,""],update:[20,2,1,""]},"pyemu.prototypes.moouu.EvolAlg":{_archive:[20,2,1,""],_calc_obs:[20,2,1,""],_drop_failed:[20,2,1,""],initialize:[20,2,1,""],update:[20,2,1,""]},"pyemu.prototypes.moouu.ParetoObjFunc":{crowd_distance:[20,2,1,""],dominates:[20,2,1,""],get_risk_shifted_value:[20,2,1,""],is_feasible:[20,2,1,""],is_nondominated_continuous:[20,2,1,""],is_nondominated_kung:[20,2,1,""],is_nondominated_pathetic:[20,2,1,""],obs_obj_signs:[20,2,1,""],reduce_stack_with_risk_shift:[20,2,1,""]},"pyemu.pst":{ControlData:[21,1,1,""],Pst:[21,1,1,""],pst_controldata:[22,0,0,"-"],pst_handler:[23,0,0,"-"],pst_utils:[24,0,0,"-"]},"pyemu.pst.ControlData":{__getattr__:[21,2,1,""],__setattr__:[21,2,1,""],_parse_value:[21,2,1,""],copy:[21,2,1,""],formatted_values:[21,2,1,""],get_dataframe:[21,2,1,""],parse_values_from_lines:[21,2,1,""],write:[21,2,1,""],write_keyword:[21,2,1,""]},"pyemu.pst.Pst":{__reset_weights:[21,2,1,""],__setattr__:[21,2,1,""],_adjust_weights_by_list:[21,2,1,""],_adjust_weights_by_phi_components:[21,2,1,""],_cast_df_from_lines:[21,2,1,""],_cast_prior_df_from_lines:[21,2,1,""],_is_greater_const:[21,2,1,""],_is_less_const:[21,2,1,""],_load_version2:[21,2,1,""],_parse_external_line:[21,2,1,""],_parse_path_agnostic:[21,2,1,""],_parse_pestpp_line:[21,2,1,""],_parse_pi_par_names:[21,2,1,""],_read_df:[21,2,1,""],_read_line_comments:[21,2,1,""],_read_section_comments:[21,2,1,""],_stats_mae:[21,2,1,""],_stats_mean:[21,2,1,""],_stats_nrmse:[21,2,1,""],_stats_rmse:[21,2,1,""],_stats_rss:[21,2,1,""],_update_control_section:[21,2,1,""],_write_df:[21,2,1,""],_write_version1:[21,2,1,""],_write_version2:[21,2,1,""],add_observations:[21,2,1,""],add_parameters:[21,2,1,""],add_pi_equation:[21,2,1,""],add_transform_columns:[21,2,1,""],adj_par_groups:[21,2,1,""],adj_par_names:[21,2,1,""],adjust_weights:[21,2,1,""],adjust_weights_discrepancy:[21,2,1,""],bounds_report:[21,2,1,""],build_increments:[21,2,1,""],calculate_pertubations:[21,2,1,""],control_data:[21,3,1,""],enforce_bounds:[21,2,1,""],estimation:[21,2,1,""],forecast_names:[21,2,1,""],from_io_files:[21,2,1,""],from_par_obs_names:[21,2,1,""],get:[21,2,1,""],get_adj_pars_at_bounds:[21,2,1,""],get_par_change_limits:[21,2,1,""],get_res_stats:[21,2,1,""],greater_than_obs_constraints:[21,2,1,""],greater_than_pi_constraints:[21,2,1,""],less_than_obs_constraints:[21,2,1,""],less_than_pi_constraints:[21,2,1,""],load:[21,2,1,""],nnz_obs:[21,2,1,""],nnz_obs_groups:[21,2,1,""],nnz_obs_names:[21,2,1,""],nobs:[21,2,1,""],npar:[21,2,1,""],npar_adj:[21,2,1,""],nprior:[21,2,1,""],obs_groups:[21,2,1,""],obs_names:[21,2,1,""],observation_data:[21,3,1,""],par_groups:[21,2,1,""],par_names:[21,2,1,""],parameter_data:[21,3,1,""],parrep:[21,2,1,""],phi:[21,2,1,""],phi_components:[21,2,1,""],phi_components_normalized:[21,2,1,""],plot:[21,2,1,""],prior_groups:[21,2,1,""],prior_information:[21,3,1,""],prior_names:[21,2,1,""],process_output_files:[21,2,1,""],proportional_weights:[21,2,1,""],rectify_pgroups:[21,2,1,""],rectify_pi:[21,2,1,""],reg_data:[21,3,1,""],res:[21,2,1,""],sanity_checks:[21,2,1,""],set_res:[21,2,1,""],svd_data:[21,3,1,""],tied:[21,2,1,""],try_parse_name_metadata:[21,2,1,""],write:[21,2,1,""],write_input_files:[21,2,1,""],write_obs_summary_table:[21,2,1,""],write_par_summary_table:[21,2,1,""],zero_weight_obs_names:[21,2,1,""]},"pyemu.pst.pst_controldata":{CONTROL_DEFAULT_LINES:[22,4,1,""],CONTROL_VARIABLE_LINES:[22,4,1,""],ControlData:[22,1,1,""],FFMT:[22,4,1,""],IFMT:[22,4,1,""],REG_DEFAULT_LINES:[22,4,1,""],REG_VARIABLE_LINES:[22,4,1,""],RegData:[22,1,1,""],SFMT:[22,4,1,""],SFMT_LONG:[22,4,1,""],SvdData:[22,1,1,""],max_colwidth:[22,4,1,""]},"pyemu.pst.pst_controldata.ControlData":{__getattr__:[22,2,1,""],__setattr__:[22,2,1,""],_parse_value:[22,2,1,""],copy:[22,2,1,""],formatted_values:[22,2,1,""],get_dataframe:[22,2,1,""],parse_values_from_lines:[22,2,1,""],write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_controldata.RegData":{write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_controldata.SvdData":{parse_values_from_lines:[22,2,1,""],write:[22,2,1,""],write_keyword:[22,2,1,""]},"pyemu.pst.pst_handler":{Pst:[23,1,1,""],max_colwidth:[23,4,1,""]},"pyemu.pst.pst_handler.Pst":{__reset_weights:[23,2,1,""],__setattr__:[23,2,1,""],_adjust_weights_by_list:[23,2,1,""],_adjust_weights_by_phi_components:[23,2,1,""],_cast_df_from_lines:[23,2,1,""],_cast_prior_df_from_lines:[23,2,1,""],_is_greater_const:[23,2,1,""],_is_less_const:[23,2,1,""],_load_version2:[23,2,1,""],_parse_external_line:[23,2,1,""],_parse_path_agnostic:[23,2,1,""],_parse_pestpp_line:[23,2,1,""],_parse_pi_par_names:[23,2,1,""],_read_df:[23,2,1,""],_read_line_comments:[23,2,1,""],_read_section_comments:[23,2,1,""],_stats_mae:[23,2,1,""],_stats_mean:[23,2,1,""],_stats_nrmse:[23,2,1,""],_stats_rmse:[23,2,1,""],_stats_rss:[23,2,1,""],_update_control_section:[23,2,1,""],_write_df:[23,2,1,""],_write_version1:[23,2,1,""],_write_version2:[23,2,1,""],add_observations:[23,2,1,""],add_parameters:[23,2,1,""],add_pi_equation:[23,2,1,""],add_transform_columns:[23,2,1,""],adj_par_groups:[23,2,1,""],adj_par_names:[23,2,1,""],adjust_weights:[23,2,1,""],adjust_weights_discrepancy:[23,2,1,""],bounds_report:[23,2,1,""],build_increments:[23,2,1,""],calculate_pertubations:[23,2,1,""],control_data:[23,3,1,""],enforce_bounds:[23,2,1,""],estimation:[23,2,1,""],forecast_names:[23,2,1,""],from_io_files:[23,2,1,""],from_par_obs_names:[23,2,1,""],get:[23,2,1,""],get_adj_pars_at_bounds:[23,2,1,""],get_par_change_limits:[23,2,1,""],get_res_stats:[23,2,1,""],greater_than_obs_constraints:[23,2,1,""],greater_than_pi_constraints:[23,2,1,""],less_than_obs_constraints:[23,2,1,""],less_than_pi_constraints:[23,2,1,""],load:[23,2,1,""],nnz_obs:[23,2,1,""],nnz_obs_groups:[23,2,1,""],nnz_obs_names:[23,2,1,""],nobs:[23,2,1,""],npar:[23,2,1,""],npar_adj:[23,2,1,""],nprior:[23,2,1,""],obs_groups:[23,2,1,""],obs_names:[23,2,1,""],observation_data:[23,3,1,""],par_groups:[23,2,1,""],par_names:[23,2,1,""],parameter_data:[23,3,1,""],parrep:[23,2,1,""],phi:[23,2,1,""],phi_components:[23,2,1,""],phi_components_normalized:[23,2,1,""],plot:[23,2,1,""],prior_groups:[23,2,1,""],prior_information:[23,3,1,""],prior_names:[23,2,1,""],process_output_files:[23,2,1,""],proportional_weights:[23,2,1,""],rectify_pgroups:[23,2,1,""],rectify_pi:[23,2,1,""],reg_data:[23,3,1,""],res:[23,2,1,""],sanity_checks:[23,2,1,""],set_res:[23,2,1,""],svd_data:[23,3,1,""],tied:[23,2,1,""],try_parse_name_metadata:[23,2,1,""],write:[23,2,1,""],write_input_files:[23,2,1,""],write_obs_summary_table:[23,2,1,""],write_par_summary_table:[23,2,1,""],zero_weight_obs_names:[23,2,1,""]},"pyemu.pst.pst_utils":{FFMT:[24,4,1,""],IFMT:[24,4,1,""],InstructionFile:[24,1,1,""],SFMT:[24,6,1,""],SFMT_LONG:[24,4,1,""],_get_marker_indices:[24,6,1,""],_parse_ins_string:[24,6,1,""],_populate_dataframe:[24,6,1,""],_try_run_inschek:[24,6,1,""],_write_chunk_to_template:[24,6,1,""],clean_missing_exponent:[24,6,1,""],csv_to_ins_file:[24,6,1,""],generic_pst:[24,6,1,""],get_phi_comps_from_recfile:[24,6,1,""],max_colwidth:[24,4,1,""],parse_ins_file:[24,6,1,""],parse_tpl_file:[24,6,1,""],process_output_files:[24,6,1,""],pst_config:[24,4,1,""],read_parfile:[24,6,1,""],read_resfile:[24,6,1,""],res_from_en:[24,6,1,""],res_from_obseravtion_data:[24,6,1,""],str_con:[24,6,1,""],try_process_output_file:[24,6,1,""],try_process_output_pst:[24,6,1,""],write_input_files:[24,6,1,""],write_parfile:[24,6,1,""],write_to_template:[24,6,1,""]},"pyemu.pst.pst_utils.InstructionFile":{_execute_ins_line:[24,2,1,""],_readline_ins:[24,2,1,""],_readline_output:[24,2,1,""],obs_name_set:[24,2,1,""],read_ins_file:[24,2,1,""],read_output_file:[24,2,1,""],throw_ins_error:[24,2,1,""],throw_ins_warning:[24,2,1,""],throw_out_error:[24,2,1,""]},"pyemu.pyemu_warnings":{PyemuWarning:[25,5,1,""]},"pyemu.sc":{Schur:[26,1,1,""]},"pyemu.sc.Schur":{__contribution_from_parameters:[26,2,1,""],get_added_obs_group_importance:[26,2,1,""],get_added_obs_importance:[26,2,1,""],get_conditional_instance:[26,2,1,""],get_forecast_summary:[26,2,1,""],get_obs_group_dict:[26,2,1,""],get_par_contribution:[26,2,1,""],get_par_group_contribution:[26,2,1,""],get_parameter_summary:[26,2,1,""],get_removed_obs_group_importance:[26,2,1,""],get_removed_obs_importance:[26,2,1,""],next_most_important_added_obs:[26,2,1,""],next_most_par_contribution:[26,2,1,""],posterior_forecast:[26,2,1,""],posterior_parameter:[26,2,1,""],posterior_prediction:[26,2,1,""]},"pyemu.utils":{Cov:[30,1,1,""],EPSILON:[30,4,1,""],ExpVario:[30,1,1,""],FFMT:[30,4,1,"id19"],GauVario:[30,1,1,""],GeoStruct:[30,1,1,""],IFMT:[30,4,1,"id18"],OrdinaryKrige:[30,1,1,""],PP_FMT:[30,4,1,"id24"],PP_NAMES:[30,4,1,"id25"],PstFrom:[30,1,1,""],PstFromFlopyModel:[30,1,1,""],PyemuWarning:[30,5,1,"id32"],SFMT:[30,6,1,"id17"],SpatialReference:[30,1,1,""],SpecSim2d:[30,1,1,""],SphVario:[30,1,1,""],Vario2d:[30,1,1,""],_apply_postprocess_hds_timeseries:[30,6,1,""],_check_diff:[30,6,1,""],_check_var_len:[30,6,1,""],_condition_on_par_knowledge:[30,6,1,""],_date_parser:[30,6,1,""],_eigen_basis_to_factor_file:[30,6,1,""],_get_datetime_from_str:[30,6,1,""],_get_tpl_or_ins_df:[30,6,1,""],_istextfile:[30,6,1,""],_l2_maha_worker:[30,6,1,""],_parse_factor_line:[30,6,1,""],_process_chunk_fac2real:[30,6,1,""],_process_chunk_model_files:[30,6,1,""],_process_model_file:[30,6,1,""],_read_structure_attributes:[30,6,1,""],_read_variogram:[30,6,1,""],_regweight_from_parbound:[30,6,1,""],_remove_readonly:[30,6,1,""],_rmse:[30,6,1,""],_setup_postprocess_hds_timeseries:[30,6,1,""],_write_df_tpl:[30,6,1,"id22"],_write_direct_df_tpl:[30,6,1,""],_write_mflist_ins:[30,6,1,""],_write_mtlist_ins:[30,6,1,""],apply_array_pars:[30,6,1,""],apply_gage_obs:[30,6,1,""],apply_genericlist_pars:[30,6,1,""],apply_hds_obs:[30,6,1,""],apply_hds_timeseries:[30,6,1,""],apply_hfb_pars:[30,6,1,""],apply_list_and_array_pars:[30,6,1,""],apply_list_pars:[30,6,1,""],apply_mflist_budget_obs:[30,6,1,""],apply_mtlist_budget_obs:[30,6,1,""],apply_sfr_obs:[30,6,1,""],apply_sfr_parameters:[30,6,1,""],apply_sfr_reach_obs:[30,6,1,""],apply_sfr_seg_parameters:[30,6,1,""],apply_sft_obs:[30,6,1,""],apply_temporal_diff_obs:[30,6,1,""],bin_path:[30,4,1,""],build_jac_test_csv:[30,6,1,""],calc_observation_ensemble_quantiles:[30,6,1,""],calc_rmse_ensemble:[30,6,1,""],dataframe_to_smp:[30,6,1,""],ext:[30,4,1,""],fac2real:[30,6,1,""],first_order_pearson_tikhonov:[30,6,1,""],geostatistical_draws:[30,6,1,""],geostatistical_prior_builder:[30,6,1,""],geostats:[27,0,0,"-"],get_maha_obs_summary:[30,6,1,""],gslib_2_dataframe:[30,6,1,""],gw_utils:[28,0,0,"-"],helpers:[29,0,0,"-"],jco_from_pestpp_runstorage:[30,6,1,""],kl_apply:[30,6,1,""],kl_setup:[30,6,1,""],last_kstp_from_kper:[30,6,1,""],load_sfr_out:[30,6,1,""],load_sgems_exp_var:[30,6,1,""],max_colwidth:[30,4,1,"id16"],modflow_hob_to_instruction_file:[30,6,1,""],modflow_hydmod_to_instruction_file:[30,6,1,""],modflow_pval_to_template_file:[30,6,1,""],modflow_read_hydmod_file:[30,6,1,""],modflow_sfr_gag_to_instruction_file:[30,6,1,""],optimization:[31,0,0,"-"],os_utils:[32,0,0,"-"],parse_dir_for_io_files:[30,6,1,""],parse_tpl_file:[30,6,1,""],pilot_points_to_tpl:[30,6,1,""],pp_file_to_dataframe:[30,6,1,"id15"],pp_tpl_to_dataframe:[30,6,1,""],pp_utils:[33,0,0,"-"],pst_config:[30,4,1,"id20"],pst_from:[34,0,0,"-"],pst_from_io_files:[30,6,1,""],pst_from_parnames_obsnames:[30,6,1,""],read_pestpp_runstorage:[30,6,1,""],read_sgems_variogram_xml:[30,6,1,""],read_struct_file:[30,6,1,""],run:[30,6,1,"id29"],setup_fake_forward_run:[30,6,1,""],setup_gage_obs:[30,6,1,""],setup_hds_obs:[30,6,1,""],setup_hds_timeseries:[30,6,1,""],setup_mflist_budget_obs:[30,6,1,""],setup_mtlist_budget_obs:[30,6,1,""],setup_pilotpoints_grid:[30,6,1,""],setup_sfr_obs:[30,6,1,""],setup_sfr_reach_obs:[30,6,1,""],setup_sfr_reach_parameters:[30,6,1,""],setup_sfr_seg_parameters:[30,6,1,""],setup_sft_obs:[30,6,1,""],setup_temporal_diff_obs:[30,6,1,""],simple_ins_from_obs:[30,6,1,""],simple_tpl_from_pars:[30,6,1,""],smp_to_dataframe:[30,6,1,""],smp_to_ins:[30,6,1,""],smp_utils:[35,0,0,"-"],srefhttp:[30,4,1,""],start_workers:[30,6,1,"id30"],try_process_output_file:[30,6,1,""],wildass_guess_par_bounds_dict:[30,4,1,""],write_array_tpl:[30,6,1,""],write_const_tpl:[30,6,1,""],write_grid_tpl:[30,6,1,""],write_hfb_template:[30,6,1,""],write_hfb_zone_multipliers_template:[30,6,1,""],write_list_tpl:[30,6,1,""],write_pp_file:[30,6,1,""],write_pp_shapfile:[30,6,1,""],write_zone_tpl:[30,6,1,""],zero_order_tikhonov:[30,6,1,""]},"pyemu.utils.Cov":{_get_uncfile_dimensions:[30,2,1,""],condition_on:[30,2,1,""],from_observation_data:[30,2,1,""],from_obsweights:[30,2,1,""],from_parameter_data:[30,2,1,""],from_parbounds:[30,2,1,""],from_uncfile:[30,2,1,""],identity:[30,2,1,""],identity_like:[30,2,1,""],names:[30,2,1,""],replace:[30,2,1,""],to_pearson:[30,2,1,""],to_uncfile:[30,2,1,""],zero:[30,2,1,""]},"pyemu.utils.ExpVario":{_h_function:[30,2,1,""]},"pyemu.utils.GauVario":{_h_function:[30,2,1,""]},"pyemu.utils.GeoStruct":{__gt__:[30,2,1,""],__lt__:[30,2,1,""],__str__:[30,2,1,""],covariance:[30,2,1,""],covariance_matrix:[30,2,1,""],covariance_points:[30,2,1,""],nugget:[30,3,1,""],plot:[30,2,1,""],same_as_other:[30,2,1,""],sill:[30,2,1,""],to_struct_file:[30,2,1,""],transform:[30,3,1,""],variograms:[30,3,1,""]},"pyemu.utils.OrdinaryKrige":{_calc_factors_mp:[30,2,1,""],_calc_factors_org:[30,2,1,""],_cov_points:[30,2,1,""],_dist_calcs:[30,2,1,""],_form:[30,2,1,""],_solve:[30,2,1,""],_worker:[30,2,1,""],calc_factors:[30,2,1,""],calc_factors_grid:[30,2,1,""],check_point_data_dist:[30,2,1,""],to_grid_factors_file:[30,2,1,""]},"pyemu.utils.PstFrom":{_flopy_mg_get_xy:[30,2,1,""],_flopy_sr_get_xy:[30,2,1,""],_generic_get_xy:[30,2,1,""],_load_listtype_file:[30,2,1,""],_next_count:[30,2,1,""],_par_prep:[30,2,1,""],_pivot_par_struct_dict:[30,2,1,""],_prep_arg_list_lengths:[30,2,1,""],_setup_dirs:[30,2,1,""],add_observations:[30,2,1,""],add_observations_from_ins:[30,2,1,""],add_parameters:[30,2,1,""],add_py_function:[30,2,1,""],build_prior:[30,2,1,""],build_pst:[30,2,1,""],draw:[30,2,1,""],initialize_spatial_reference:[30,2,1,""],parfile_relations:[30,2,1,""],parse_kij_args:[30,2,1,""],write_forward_run:[30,2,1,""]},"pyemu.utils.PstFromFlopyModel":{_add_external:[30,2,1,""],_get_count:[30,2,1,""],_grid_prep:[30,2,1,""],_kl_prep:[30,2,1,""],_list_helper:[30,2,1,""],_parse_k:[30,2,1,""],_parse_pakattr:[30,2,1,""],_pp_prep:[30,2,1,""],_prep_mlt_arrays:[30,2,1,""],_setup_array_pars:[30,2,1,""],_setup_hds:[30,2,1,""],_setup_hfb_pars:[30,2,1,""],_setup_hob:[30,2,1,""],_setup_hyd:[30,2,1,""],_setup_list_pars:[30,2,1,""],_setup_model:[30,2,1,""],_setup_mult_dirs:[30,2,1,""],_setup_observations:[30,2,1,""],_setup_sfr_obs:[30,2,1,""],_setup_sfr_pars:[30,2,1,""],_setup_smp:[30,2,1,""],_setup_spatial_list_pars:[30,2,1,""],_setup_temporal_list_pars:[30,2,1,""],_setup_water_budget_obs:[30,2,1,""],_write_const_tpl:[30,2,1,""],_write_grid_tpl:[30,2,1,""],_write_u2d:[30,2,1,""],build_prior:[30,2,1,""],build_pst:[30,2,1,""],draw:[30,2,1,""],write_forward_run:[30,2,1,""]},"pyemu.utils.SpatialReference":{__eq__:[30,2,1,""],__repr__:[30,2,1,""],__setattr__:[30,2,1,""],_parse_units_from_proj4:[30,2,1,""],_reset:[30,2,1,""],_set_vertices:[30,2,1,""],_set_xycentergrid:[30,2,1,""],_set_xygrid:[30,2,1,""],attribs_from_namfile_header:[30,2,1,""],attribute_dict:[30,2,1,""],bounds:[30,2,1,""],defaults:[30,3,1,""],epsg:[30,2,1,""],from_gridspec:[30,2,1,""],from_namfile:[30,2,1,""],get_extent:[30,2,1,""],get_grid_lines:[30,2,1,""],get_ij:[30,2,1,""],get_rc:[30,2,1,""],get_vertices:[30,2,1,""],get_xcenter_array:[30,2,1,""],get_xedge_array:[30,2,1,""],get_ycenter_array:[30,2,1,""],get_yedge_array:[30,2,1,""],length_multiplier:[30,2,1,"id0"],lenuni:[30,2,1,""],lenuni_text:[30,3,1,""],lenuni_values:[30,3,1,""],load:[30,2,1,""],model_length_units:[30,2,1,""],ncol:[30,2,1,""],nrow:[30,2,1,""],origin_loc:[30,3,1,""],proj4_str:[30,2,1,""],read_usgs_model_reference_file:[30,2,1,""],reset:[30,2,1,""],rotate:[30,2,1,""],rotation:[30,3,1,""],set_spatialreference:[30,2,1,""],theta:[30,2,1,""],transform:[30,2,1,""],units:[30,2,1,""],vertices:[30,2,1,"id9"],write_gridspec:[30,2,1,""],xcenter:[30,2,1,"id5"],xcentergrid:[30,2,1,"id8"],xedge:[30,2,1,"id1"],xgrid:[30,2,1,"id3"],xll:[30,2,1,""],xul:[30,2,1,""],ycenter:[30,2,1,"id6"],ycentergrid:[30,2,1,"id7"],yedge:[30,2,1,"id2"],ygrid:[30,2,1,"id4"],yll:[30,2,1,""],yul:[30,2,1,""]},"pyemu.utils.SpecSim2d":{draw_arrays:[30,2,1,""],draw_conditional:[30,2,1,""],grid_is_regular:[30,2,1,""],grid_par_ensemble_helper:[30,2,1,""],initialize:[30,2,1,""]},"pyemu.utils.SphVario":{_h_function:[30,2,1,""]},"pyemu.utils.Vario2d":{__str__:[30,2,1,""],_apply_rotation:[30,2,1,""],_specsim_grid_contrib:[30,2,1,""],bearing_rads:[30,2,1,""],covariance:[30,2,1,""],covariance_matrix:[30,2,1,""],covariance_points:[30,2,1,""],inv_h:[30,2,1,""],plot:[30,2,1,""],rotation_coefs:[30,2,1,""],same_as_other:[30,2,1,""],to_struct_file:[30,2,1,""]},"pyemu.utils.geostats":{EPSILON:[27,4,1,""],ExpVario:[27,1,1,""],GauVario:[27,1,1,""],GeoStruct:[27,1,1,""],OrdinaryKrige:[27,1,1,""],SpecSim2d:[27,1,1,""],SphVario:[27,1,1,""],Vario2d:[27,1,1,""],_parse_factor_line:[27,6,1,""],_read_structure_attributes:[27,6,1,""],_read_variogram:[27,6,1,""],fac2real:[27,6,1,""],gslib_2_dataframe:[27,6,1,""],load_sgems_exp_var:[27,6,1,""],read_sgems_variogram_xml:[27,6,1,""],read_struct_file:[27,6,1,""]},"pyemu.utils.geostats.ExpVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.GauVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.GeoStruct":{__gt__:[27,2,1,""],__lt__:[27,2,1,""],__str__:[27,2,1,""],covariance:[27,2,1,""],covariance_matrix:[27,2,1,""],covariance_points:[27,2,1,""],nugget:[27,3,1,""],plot:[27,2,1,""],same_as_other:[27,2,1,""],sill:[27,2,1,""],to_struct_file:[27,2,1,""],transform:[27,3,1,""],variograms:[27,3,1,""]},"pyemu.utils.geostats.OrdinaryKrige":{_calc_factors_mp:[27,2,1,""],_calc_factors_org:[27,2,1,""],_cov_points:[27,2,1,""],_dist_calcs:[27,2,1,""],_form:[27,2,1,""],_solve:[27,2,1,""],_worker:[27,2,1,""],calc_factors:[27,2,1,""],calc_factors_grid:[27,2,1,""],check_point_data_dist:[27,2,1,""],to_grid_factors_file:[27,2,1,""]},"pyemu.utils.geostats.SpecSim2d":{draw_arrays:[27,2,1,""],draw_conditional:[27,2,1,""],grid_is_regular:[27,2,1,""],grid_par_ensemble_helper:[27,2,1,""],initialize:[27,2,1,""]},"pyemu.utils.geostats.SphVario":{_h_function:[27,2,1,""]},"pyemu.utils.geostats.Vario2d":{__str__:[27,2,1,""],_apply_rotation:[27,2,1,""],_specsim_grid_contrib:[27,2,1,""],bearing_rads:[27,2,1,""],covariance:[27,2,1,""],covariance_matrix:[27,2,1,""],covariance_points:[27,2,1,""],inv_h:[27,2,1,""],plot:[27,2,1,""],rotation_coefs:[27,2,1,""],same_as_other:[27,2,1,""],to_struct_file:[27,2,1,""]},"pyemu.utils.gw_utils":{PP_FMT:[28,4,1,""],PP_NAMES:[28,4,1,""],_apply_postprocess_hds_timeseries:[28,6,1,""],_setup_postprocess_hds_timeseries:[28,6,1,""],_write_mflist_ins:[28,6,1,""],_write_mtlist_ins:[28,6,1,""],apply_gage_obs:[28,6,1,""],apply_hds_obs:[28,6,1,""],apply_hds_timeseries:[28,6,1,""],apply_hfb_pars:[28,6,1,""],apply_mflist_budget_obs:[28,6,1,""],apply_mtlist_budget_obs:[28,6,1,""],apply_sfr_obs:[28,6,1,""],apply_sfr_parameters:[28,6,1,""],apply_sfr_reach_obs:[28,6,1,""],apply_sfr_seg_parameters:[28,6,1,""],apply_sft_obs:[28,6,1,""],last_kstp_from_kper:[28,6,1,""],load_sfr_out:[28,6,1,""],max_colwidth:[28,4,1,""],modflow_hob_to_instruction_file:[28,6,1,""],modflow_hydmod_to_instruction_file:[28,6,1,""],modflow_pval_to_template_file:[28,6,1,""],modflow_read_hydmod_file:[28,6,1,""],modflow_sfr_gag_to_instruction_file:[28,6,1,""],setup_gage_obs:[28,6,1,""],setup_hds_obs:[28,6,1,""],setup_hds_timeseries:[28,6,1,""],setup_mflist_budget_obs:[28,6,1,""],setup_mtlist_budget_obs:[28,6,1,""],setup_sfr_obs:[28,6,1,""],setup_sfr_reach_obs:[28,6,1,""],setup_sfr_reach_parameters:[28,6,1,""],setup_sfr_seg_parameters:[28,6,1,""],setup_sft_obs:[28,6,1,""],write_hfb_template:[28,6,1,""],write_hfb_zone_multipliers_template:[28,6,1,""]},"pyemu.utils.helpers":{PstFromFlopyModel:[29,1,1,""],SpatialReference:[29,1,1,""],_condition_on_par_knowledge:[29,6,1,""],_eigen_basis_to_factor_file:[29,6,1,""],_l2_maha_worker:[29,6,1,""],_process_chunk_fac2real:[29,6,1,""],_process_chunk_model_files:[29,6,1,""],_process_model_file:[29,6,1,""],_regweight_from_parbound:[29,6,1,""],_rmse:[29,6,1,""],_write_df_tpl:[29,6,1,""],apply_array_pars:[29,6,1,""],apply_genericlist_pars:[29,6,1,""],apply_list_and_array_pars:[29,6,1,""],apply_list_pars:[29,6,1,""],apply_temporal_diff_obs:[29,6,1,""],build_jac_test_csv:[29,6,1,""],calc_observation_ensemble_quantiles:[29,6,1,""],calc_rmse_ensemble:[29,6,1,""],first_order_pearson_tikhonov:[29,6,1,""],geostatistical_draws:[29,6,1,""],geostatistical_prior_builder:[29,6,1,""],get_maha_obs_summary:[29,6,1,""],jco_from_pestpp_runstorage:[29,6,1,""],kl_apply:[29,6,1,""],kl_setup:[29,6,1,""],max_colwidth:[29,4,1,""],parse_dir_for_io_files:[29,6,1,""],pst_from_io_files:[29,6,1,""],pst_from_parnames_obsnames:[29,6,1,""],read_pestpp_runstorage:[29,6,1,""],setup_fake_forward_run:[29,6,1,""],setup_temporal_diff_obs:[29,6,1,""],simple_ins_from_obs:[29,6,1,""],simple_tpl_from_pars:[29,6,1,""],srefhttp:[29,4,1,""],wildass_guess_par_bounds_dict:[29,4,1,""],write_const_tpl:[29,6,1,""],write_grid_tpl:[29,6,1,""],write_zone_tpl:[29,6,1,""],zero_order_tikhonov:[29,6,1,""]},"pyemu.utils.helpers.PstFromFlopyModel":{_add_external:[29,2,1,""],_get_count:[29,2,1,""],_grid_prep:[29,2,1,""],_kl_prep:[29,2,1,""],_list_helper:[29,2,1,""],_parse_k:[29,2,1,""],_parse_pakattr:[29,2,1,""],_pp_prep:[29,2,1,""],_prep_mlt_arrays:[29,2,1,""],_setup_array_pars:[29,2,1,""],_setup_hds:[29,2,1,""],_setup_hfb_pars:[29,2,1,""],_setup_hob:[29,2,1,""],_setup_hyd:[29,2,1,""],_setup_list_pars:[29,2,1,""],_setup_model:[29,2,1,""],_setup_mult_dirs:[29,2,1,""],_setup_observations:[29,2,1,""],_setup_sfr_obs:[29,2,1,""],_setup_sfr_pars:[29,2,1,""],_setup_smp:[29,2,1,""],_setup_spatial_list_pars:[29,2,1,""],_setup_temporal_list_pars:[29,2,1,""],_setup_water_budget_obs:[29,2,1,""],_write_const_tpl:[29,2,1,""],_write_grid_tpl:[29,2,1,""],_write_u2d:[29,2,1,""],build_prior:[29,2,1,""],build_pst:[29,2,1,""],draw:[29,2,1,""],write_forward_run:[29,2,1,""]},"pyemu.utils.helpers.SpatialReference":{__eq__:[29,2,1,""],__repr__:[29,2,1,""],__setattr__:[29,2,1,""],_parse_units_from_proj4:[29,2,1,""],_reset:[29,2,1,""],_set_vertices:[29,2,1,""],_set_xycentergrid:[29,2,1,""],_set_xygrid:[29,2,1,""],attribs_from_namfile_header:[29,2,1,""],attribute_dict:[29,2,1,""],bounds:[29,2,1,""],defaults:[29,3,1,""],epsg:[29,2,1,""],from_gridspec:[29,2,1,""],from_namfile:[29,2,1,""],get_extent:[29,2,1,""],get_grid_lines:[29,2,1,""],get_ij:[29,2,1,""],get_rc:[29,2,1,""],get_vertices:[29,2,1,""],get_xcenter_array:[29,2,1,""],get_xedge_array:[29,2,1,""],get_ycenter_array:[29,2,1,""],get_yedge_array:[29,2,1,""],length_multiplier:[29,2,1,"id0"],lenuni:[29,2,1,""],lenuni_text:[29,3,1,""],lenuni_values:[29,3,1,""],load:[29,2,1,""],model_length_units:[29,2,1,""],ncol:[29,2,1,""],nrow:[29,2,1,""],origin_loc:[29,3,1,""],proj4_str:[29,2,1,""],read_usgs_model_reference_file:[29,2,1,""],reset:[29,2,1,""],rotate:[29,2,1,""],rotation:[29,3,1,""],set_spatialreference:[29,2,1,""],theta:[29,2,1,""],transform:[29,2,1,""],units:[29,2,1,""],vertices:[29,2,1,"id9"],write_gridspec:[29,2,1,""],xcenter:[29,2,1,"id5"],xcentergrid:[29,2,1,"id8"],xedge:[29,2,1,"id1"],xgrid:[29,2,1,"id3"],xll:[29,2,1,""],xul:[29,2,1,""],ycenter:[29,2,1,"id6"],ycentergrid:[29,2,1,"id7"],yedge:[29,2,1,"id2"],ygrid:[29,2,1,"id4"],yll:[29,2,1,""],yul:[29,2,1,""]},"pyemu.utils.optimization":{OPERATOR_SYMBOLS:[31,4,1,""],OPERATOR_WORDS:[31,4,1,""],add_pi_obj_func:[31,6,1,""]},"pyemu.utils.os_utils":{_istextfile:[32,6,1,""],_remove_readonly:[32,6,1,""],bin_path:[32,4,1,"id1"],ext:[32,4,1,""],run:[32,6,1,""],start_workers:[32,6,1,""]},"pyemu.utils.pp_utils":{PP_FMT:[33,4,1,""],PP_NAMES:[33,4,1,""],max_colwidth:[33,4,1,""],pilot_points_to_tpl:[33,6,1,""],pp_file_to_dataframe:[33,6,1,""],pp_tpl_to_dataframe:[33,6,1,""],setup_pilotpoints_grid:[33,6,1,""],write_pp_file:[33,6,1,""],write_pp_shapfile:[33,6,1,""]},"pyemu.utils.pst_from":{PstFrom:[34,1,1,""],_check_diff:[34,6,1,""],_check_var_len:[34,6,1,""],_get_datetime_from_str:[34,6,1,""],_get_tpl_or_ins_df:[34,6,1,""],_write_direct_df_tpl:[34,6,1,""],write_array_tpl:[34,6,1,""],write_list_tpl:[34,6,1,""]},"pyemu.utils.pst_from.PstFrom":{_flopy_mg_get_xy:[34,2,1,""],_flopy_sr_get_xy:[34,2,1,""],_generic_get_xy:[34,2,1,""],_load_listtype_file:[34,2,1,""],_next_count:[34,2,1,""],_par_prep:[34,2,1,""],_pivot_par_struct_dict:[34,2,1,""],_prep_arg_list_lengths:[34,2,1,""],_setup_dirs:[34,2,1,""],add_observations:[34,2,1,""],add_observations_from_ins:[34,2,1,""],add_parameters:[34,2,1,""],add_py_function:[34,2,1,""],build_prior:[34,2,1,""],build_pst:[34,2,1,""],draw:[34,2,1,""],initialize_spatial_reference:[34,2,1,""],parfile_relations:[34,2,1,""],parse_kij_args:[34,2,1,""],write_forward_run:[34,2,1,""]},"pyemu.utils.smp_utils":{_date_parser:[35,6,1,""],dataframe_to_smp:[35,6,1,""],smp_to_dataframe:[35,6,1,""],smp_to_ins:[35,6,1,""]},pyemu:{Cov:[9,1,1,"id24"],Ensemble:[9,1,1,""],ErrVar:[9,1,1,""],Jco:[9,1,1,""],LinearAnalysis:[9,1,1,""],Matrix:[9,1,1,"id39"],ObservationEnsemble:[9,1,1,"id19"],ParameterEnsemble:[9,1,1,"id0"],Pst:[9,1,1,"id103"],Schur:[9,1,1,""],_version:[6,0,0,"-"],en:[7,0,0,"-"],ev:[8,0,0,"-"],la:[10,0,0,"-"],logger:[11,0,0,"-"],mat:[12,0,0,"-"],mc:[14,0,0,"-"],plot:[15,0,0,"-"],prototypes:[19,0,0,"-"],pst:[21,0,0,"-"],pyemu_warnings:[25,0,0,"-"],sc:[26,0,0,"-"],utils:[30,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","data","Python data"],"5":["py","exception","Python exception"],"6":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:data","5":"py:exception","6":"py:function"},terms:{"0001":[14,27,30,38,43],"001":[29,30,43],"100":[7,9,14,19,20,21,22,23,24,26,27,28,29,30,33,34,38,41,42,43],"1000":[14,27,30,38,43],"10000000000":[27,30,34,43],"1970":[28,30,43],"1to1":[9,16,19,21,23,38,40,42],"200":[9,12,13,19,38,39],"2003":[17,19,41],"2005":[29,30,43],"2011":[30,32],"2darrai":[29,30,43],"30k":[29,30,43],"358183147":7,"4004":[17,18,19,20,30,32,41,43],"512":[30,32],"695":[27,30,43],"boolean":[7,9,19,27,30,34,38,43],"byte":[30,32],"case":[9,10,12,13,16,19,21,23,24,26,28,29,30,32,34,38,39,40,42,43],"char":[9,12,13,19,22,29,30,32,34,37,38,39,42,43],"class":[1,2,3,4,25,38,39,41,42,43],"default":[1,3,4,6,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],"final":[9,14,19,21,23,38,42],"float":[1,4,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,34,35,38,39,40,41,42,43],"function":[7,9,17,19,20,21,22,23,36,38,39,40,41,42,43],"import":[9,10,14,16,26,27,30,34,38,40,43],"int":[7,8,9,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],"long":[6,27,29,30,35,43],"new":[3,7,9,10,12,13,14,19,21,23,24,26,29,30,32,34,38,39,42,43],"null":[7,8,9,14,19,38,41],"return":[6,7,8,9,10,12,13,14,16,17,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],"short":6,"static":[7,9,12,13,16,19,20,21,22,23,27,29,30,38,39,40,42,43],"super":[19,20,41],"throw":[16,24,40,42],"true":[1,3,4,7,8,9,10,12,13,14,16,18,19,20,21,23,24,26,27,28,29,30,32,34,38,39,40,41,42,43],"try":[6,9,17,19,21,23,24,27,30,34,35,38,41,42,43],"var":[8,9,29,30,34,38],"while":[7,9,19,21,23,38,42],Adding:[29,30],BAS:[28,30,43],BCs:[28,30,43],But:[7,9,19,38],DIS:[28,30,43],For:[1,4,7,8,9,10,12,13,19,21,23,26,28,29,30,32,33,34,38,39,42,43],IES:[24,42],NOT:[30,34,43],Not:[7,8,9,19,30,34,38],OBS:[28,30,43],One:[9,26,30,34,38,43],RHS:[27,30,43],That:[9,10,26,29,30,38,43],The:[6,7,9,10,11,12,13,14,17,19,21,23,26,27,28,29,30,34,35,38,39,41,42,43],These:[0,9,12,19,22,29,30,38,39,41,42,43],Use:[17,19,30,33,34,41,43],Used:[9,16,19,21,23,24,27,28,29,30,38,40,42,43],Useful:[9,26,28,30,38,43],Uses:[9,10,28,30,32,43],VCS:6,__add__:[7,9,12,13,19],__contribution_from_paramet:[9,26],__eq__:[29,30],__fromfil:[9,10],__getattr__:[7,9,21,22],__getitem__:[7,9,12,13,19],__gt__:[27,30],__init:[9,12,13],__load_jco:[9,10],__load_obscov:[9,10],__load_omitted_jco:[8,9],__load_omitted_parcov:[8,9],__load_omitted_predict:[8,9],__load_parcov:[9,10],__load_predict:[9,10],__load_pst:[9,10],__lt__:[27,30],__mul__:[7,9,12,13,19],__mult__:[9,12,13,19,38,39],__obscov:[9,10,38],__parcov:[9,10,38],__pow__:[7,9,12,13,19],__re:[9,19,21,23,38,42],__repr__:[7,9,29,30],__reset_weight:[9,19,21,23],__rmul__:[9,12,13,19],__set_svd:[9,12,13,19],__setattr__:[9,19,21,22,23,29,30],__setitem__:7,__str__:[7,9,12,13,19,27,30],__sub__:[7,9,12,13,19],__truediv__:[7,9],__x:[9,12,13,19,38,39],_add_extern:[29,30],_adjust_weights_by_list:[9,19,21,23],_adjust_weights_by_phi_compon:[9,19,21,23],_apply_postprocess_hds_timeseri:[28,30],_apply_rot:[27,30],_archiv:[19,20],_backup_:[28,30,43],_bak:[29,30,43],_calc_delta:[18,19],_calc_factors_mp:[27,30],_calc_factors_org:[27,30],_calc_ob:[18,19,20],_calc_obs_condor:[18,19],_calc_obs_loc:[18,19],_cast_df_from_lin:[9,19,21,23],_cast_prior_df_from_lin:[9,19,21,23],_check_diff:[30,34],_check_var_len:[30,34],_condition_on_par_knowledg:[29,30],_cov_point:[27,30],_date_pars:[30,35],_df:[7,9,38],_dist_calc:[27,30],_drop_by_crowd:[19,20],_drop_fail:[19,20],_eigen_basis_to_factor_fil:[29,30],_enforce_drop:[7,9,19],_enforce_reset:[7,9,19],_enforce_scal:[7,9,19],_execute_ins_lin:24,_flopy_mg_get_xi:[30,34],_flopy_sr_get_xi:[30,34],_form:[27,30],_gaussian_draw:[7,9],_generic_get_xi:[30,34],_get_count:[29,30],_get_datetime_from_str:[30,34],_get_eigen_projection_matrix:[7,9],_get_marker_indic:24,_get_master_thread:[18,19],_get_page_ax:16,_get_svd_projection_matrix:[7,9],_get_tpl_or_ins_df:[30,34],_get_uncfile_dimens:[9,12,13,19,30],_grid_prep:[29,30],_h_function:[27,30],_hp:[29,30,43],_ins_linecount:[24,42],_is_greater_const:[9,19,21,23],_is_less_const:[9,19,21,23],_istextfil:[30,32],_jj:[9,10,38],_kl_prep:[29,30],_l2_maha_work:[29,30],_list_help:[29,30],_load_listtype_fil:[30,34],_load_obs_ensembl:[18,19],_load_version2:[9,19,21,23],_next_count:[30,34],_num_at_lb:[9,19,21,23,38,42],_num_at_ub:[9,19,21,23,38,42],_par_prep:[30,34],_parse_external_lin:[9,19,21,23],_parse_factor_lin:[27,30],_parse_ins_str:24,_parse_k:[29,30],_parse_pakattr:[29,30],_parse_path_agnost:[9,19,21,23],_parse_pestpp_lin:[9,19,21,23],_parse_pi_par_nam:[9,19,21,23],_parse_units_from_proj4:[29,30],_parse_valu:[21,22],_pivot_par_struct_dict:[30,34],_populate_datafram:24,_pp_prep:[29,30],_prep_arg_list_length:[30,34],_prep_mlt_arrai:[29,30],_process_chunk_fac2r:[29,30],_process_chunk_model_fil:[29,30],_process_ensemble_arg:16,_process_model_fil:[29,30],_read_df:[9,19,21,23],_read_line_com:[9,19,21,23],_read_section_com:[9,19,21,23],_read_structure_attribut:[27,30],_read_variogram:[27,30],_readline_in:24,_readline_output:24,_regweight_from_parbound:[29,30],_remove_readonli:[30,32],_reset:[29,30],_rmse:[29,30],_set_vertic:[29,30],_set_xycentergrid:[29,30],_set_xygrid:[29,30],_setup_:[28,30,43],_setup_array_par:[29,30],_setup_dir:[30,34],_setup_hd:[29,30],_setup_hfb_par:[29,30],_setup_hob:[29,30],_setup_hyd:[29,30],_setup_list_par:[29,30],_setup_model:[29,30],_setup_mult_dir:[29,30],_setup_observ:[29,30],_setup_postprocess_hds_timeseri:[28,30],_setup_sfr_ob:[29,30],_setup_sfr_par:[29,30],_setup_smp:[29,30],_setup_spatial_list_par:[29,30],_setup_temporal_list_par:[29,30],_setup_water_budget_ob:[29,30],_solv:[27,30],_specsim_grid_contrib:[27,30],_stats_ma:[9,19,21,23],_stats_mean:[9,19,21,23],_stats_nrms:[9,19,21,23],_stats_rms:[9,19,21,23],_stats_rss:[9,19,21,23],_total_at_bound:[9,19,21,23,38,42],_transform:[7,9,38],_try_run_inschek:24,_update_control_sect:[9,19,21,23],_version:[5,9,19,21,23,38,42],_worker:[27,30],_write_chunk_to_templ:24,_write_const_tpl:[29,30],_write_df:[9,19,21,23],_write_df_tpl:[29,30],_write_direct_df_tpl:[30,34],_write_grid_tpl:[29,30],_write_mflist_in:[28,30],_write_mtlist_in:[28,30],_write_u2d:[29,30],_write_version1:[9,19,21,23],_write_version2:[9,19,21,23],abet:16,abil:[28,30,43],about:[9,17,19,25,26,29,30,38,41,43],abov:[14,27,28,29,30,38,43],abs_drop_tol:[29,30,43],absolut:[9,12,13,19,21,23,29,30,34,38,39,42,43],accept:[7,9,16,19,38,40],access:[9,17,19,21,22,23,26,29,30,38,41,42,43],accommod:[9,12,13,19,38,39],accord:[7,9,19,27,28,29,30,33,38,43],account:[9,19,21,23,26,28,29,30,38,42,43],across:[28,29,30,32,43],act:[29,30,43],activ:[1,8,9,19,21,23,28,29,30,33,38,42,43],actual:[21,22,30,32,42,43],add:[7,9,12,13,14,16,19,21,23,24,27,28,29,30,32,34,35,38,40,42,43],add_bas:[7,9,19,37,38],add_observ:[9,19,21,23,30,34,37,38,42,43],add_observations_from_in:[30,34,38,43],add_paramet:[9,19,21,23,30,34,37,38,42,43],add_pi_equ:[9,19,21,23,37,38,42],add_pi_obj_func:[31,38,43],add_py_funct:[30,34,38,43],add_transform_column:[9,19,21,23,37,38,42],added:[7,9,19,21,23,26,27,28,29,30,34,38,42,43],adding:[30,34,43],addit:[7,9,12,13,16,19,26,27,29,30,34,35,38,40,43],addition:[7,9,19,38],addkeyword:[7,9,38],addreg:[24,42],address:[30,32,43],adj_nam:[7,9,19,37,38],adj_par_group:[9,19,21,23,37,38,42],adj_par_nam:[9,10,19,21,23,37,38,42],adjust:[7,8,9,10,19,21,23,26,29,30,38,42,43],adjust_obscov_resfil:[9,10,37,38],adjust_weight:[9,19,21,23,37,38,42],adjust_weights_discrep:[9,19,21,23,37,38,42],adjust_weights_resfil:[9,10,38],adust:[9,26,38],advanc:[30,32,43],after:[9,19,21,23,27,29,30,34,38,42,43],against:[9,19,21,23,26,30,34,38,42,43],aggreg:[28,30,43],aggregr:[28,30,43],agnost:[30,32,43],aka:[9,10,38],algebra:[9,12,13,17,19,30,38,39,41],algin:[30,34],algorithm:[19,20,41],alia:[38,39],alias_map:[9,19,21,23],align:[9,12,13,17,19,29,30,34,37,38,39,41,43],all:[3,7,8,9,10,12,13,16,19,21,23,24,26,27,28,29,30,32,33,34,38,39,40,42,43],allow:[7,9,19,21,23,27,29,30,34,38,42,43],alon:[9,19,21,23,38,42],along:[7,9,12,13,19,29,30,38,39,43],alreadi:[6,9,19,21,23,27,30,34,38,42,43],also:[0,1,4,6,7,8,9,10,14,16,17,19,21,23,24,26,27,28,29,30,32,33,34,38,40,41,42,43],alt_inst_str:[30,34,43],alter:[1,8,9,38],altern:[29,30,34,43],alwai:[6,30,35,43],amount:[9,19,21,23,38,42],analys:[0,4,9,10,14,19,21,23,26,38,41],analysi:[1,4,8,9,10,11,14,17,19,26,38,41],analysis_evensen:[17,19,38,41],analyz:[1,4,8,9,26,38],andor:[9,12,13,19,38,39],angl:[27,30,43],ani:[7,9,16,17,19,21,23,26,29,30,34,38,40,41,42,43],anisotropi:[27,30,43],anlaysi:[17,19,41],anoth:[29,30],anyon:[16,40],anyth:[30,34,43],anywai:6,apart:[30,34,43],api:[],appar:[30,32],appear:6,append:[9,19,21,22,23,24,28,29,30,34,38,42,43],appl_array_par:[29,30,43],appli:[1,4,7,8,9,10,12,13,16,17,19,26,27,28,29,30,34,38,39,40,41,43],applic:[9,12,13,19,38,39],apply_array_par:[29,30,38,43],apply_gage_ob:[28,30,38,43],apply_genericlist_par:[29,30,38,43],apply_hds_ob:[28,30,38,43],apply_hds_timeseri:[28,30,38,43],apply_hfb_par:[28,30,38,43],apply_karhunen_loeve_sc:[9,10,37,38],apply_kl:[29,30,43],apply_list_and_array_par:[29,30,38,43],apply_list_par:[29,30,38,43],apply_mflist_budget_ob:[28,30,38,43],apply_mtlist_budget_ob:[28,30,38,43],apply_sfr_ob:[28,30,38,43],apply_sfr_paramet:[28,30,38,43],apply_sfr_reach_ob:[28,30,38,43],apply_sfr_reach_paramet:[28,30,43],apply_sfr_seg_paramet:[28,30,38,43],apply_sft_ob:[28,30,38,43],apply_temporal_diff_ob:[29,30,38,43],approach:[27,30,43],appropri:[6,30,32,43],approx:[29,30,43],approxim:[1,4,8,9,10,12,13,19,26,29,30,34,38,39,43],aquier:[28,30,43],aquif:[28,29,30,43],arang:[27,30,43],arbitrari:[29,30,43],archiv:6,area:[30,33,43],arg:[1,4,6,7,8,9,10,11,12,13,16,18,19,20,21,22,23,24,26,30,34,38,39,40,41,42,43],argument:[1,4,7,8,9,10,12,13,14,16,17,19,21,22,23,26,27,29,30,34,38,40,41,42,43],aris:[8,9,38],arithmat:[7,9,27,30,38,43],arithmet:[27,30,43],around:[7,8,9,12,13,16,17,19,26,27,28,29,30,38,39,40,41,43],arr_par:[29,30,43],arr_par_fil:[29,30,43],arr_shap:[29,30,43],arrai:[7,9,12,13,19,27,28,29,30,33,34,38,39,43],arrang:[27,30,43],as_2d:[9,12,13,19,37,38,39],as_pyemu_matrix:[7,9,37,38],ascii:[1,4,8,9,10,12,13,19,21,22,26,28,29,30,34,38,39,42,43],aspect:[15,40],assess:[9,26,38],assign:[7,9,10,19,21,23,26,27,29,30,34,38,42,43],assimil:[17,19,38,41],assist:[30,34,43],associ:[14,27,29,30,38,43],assum:[1,4,7,8,9,10,12,13,16,19,21,23,26,27,28,29,30,32,33,34,38,39,40,42,43],astyp:[9,10,38],async:[30,32,43],atleast:[9,19,21,23,38,42],atplotlib:[16,40],attempt:[21,22,24,29,30,34,42,43],attr:[29,30],attr_nam:[27,30,43],attribs_from_namfile_head:[29,30,38,43],attribut:[1,7,8,9,10,12,13,17,19,21,22,23,24,27,28,29,30,38,39,41,42,43],attribute_dict:[29,30,38,43],auto:[5,13,29,30,34,39,43],autoalign:[9,12,13,19,30,38,39],autoapi:5,autodetect:[9,12,13,19,38,39],automat:[9,22,26,38,42],avail:[7,9,16,17,19,21,23,28,29,30,38,40,41,42,43],avoid:[29,30,43],awai:[27,30,43],axes:[9,12,13,16,19,38,39,40],axi:[9,12,13,16,19,27,30,34,38,39,40,43],ayman:[17,19,41],back:[7,9,19,21,23,38,42],back_transform:[7,9,19,37,38],backup:[28,29,30,43],backward:6,bak_suffix:[29,30,43],balanced_group:[9,19,21,23,38,42],bar:[8,9,16,26,38,40],bas6:[27,30,43],bas:[28,30,43],base:[0,1,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,34,38,39,40,41,42,43],base_ensembl:[16,40],base_obslist:[9,26,38],base_values_fil:[27,30,43],basemodel:[28,30,43],basenam:[30,34,43],basi:[29,30,43],basic:[9,11,19,21,23,24,38,42],basis_fil:[29,30,43],bay:[26,38],bear:[27,30,43],bearing_rad:[27,30,38,43],becaus:[9,19,21,23,26,29,30,38,42,43],becom:[9,12,13,19,26,30,38,39],been:[6,7,9,10,14,19,21,22,23,26,30,34,38,42,43],befor:[1,7,8,9,12,13,19,29,30,34,38,39,43],behavior:[4,9,25,26,27,30,38,43],being:[7,8,9,16,19,21,22,23,26,27,28,29,30,34,38,40,42,43],bell:[30,34,43],better:[29,30,43],between:[7,9,12,13,14,16,19,27,29,30,34,38,39,40,43],bewar:[9,17,19,21,23,24,29,30,38,41,42,43],bin:[7,9,16,38,40],bin_fil:[28,30,43],bin_path:[30,32],binari:[1,4,7,8,9,10,12,13,19,26,28,29,30,32,34,38,39,43],binary_header_dt:[9,12,13,19,37,38,39],binary_rec_dt:[9,12,13,19,37,38,39],bizarr:[9,12,13,19,38,39],block:[30,32],blocksiz:[30,32],bool:[1,2,3,4,7,8,9,10,11,12,13,14,16,19,20,21,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],both:[6,7,9,10,12,13,16,19,26,29,30,38,39,40,43],bound:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,26,27,29,30,34,38,39,40,41,42,43],bound_report:[9,19,21,23,38,42],bound_tol:[7,9,19,38],boundari:[17,19,29,30,41,43],bounds_report:[9,19,21,23,37,38,42],box:[29,30,43],bring:[9,19,21,23,38,42],btn:[28,30,43],budget:[28,30,43],buget:[29,30],build:[6,27,29,30,34,43],build_incr:[9,19,21,23,37,38,42],build_jac_test_csv:[16,29,30,38,40,43],build_prior:[29,30,34,38,43],build_pst:[29,30,34,38,43],built:[30,34,43],by_group:[7,9,19,38],bygroup:[9,19,21,23,29,30,38,42,43],c_char:[30,34],calc:[8,9,12,13,18,19,38,39],calc_factor:[27,30,38,43],calc_factors_grid:[27,30,38,43],calc_factors_mp:[27,30,43],calc_factors_org:[27,30,43],calc_observation_ensemble_quantil:[29,30,38,43],calc_rmse_ensembl:[29,30,38,43],calcul:[1,4,8,9,10,11,12,13,19,20,21,23,26,27,29,30,34,38,39,41,42,43],calculate_pertub:[9,19,21,23,37,38,42],calibr:[9,26,38],call:[6,7,9,10,12,13,14,16,17,19,21,23,24,27,28,29,30,32,34,38,39,40,41,42,43],can:[0,1,4,7,8,9,10,12,13,14,16,17,19,21,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],candid:[19,20,41],captur:[27,30,43],care:[7,9,19,38],carlo:[7,8,9,14,19,38,41],cast:[1,4,8,9,10,21,22,24,26,28,30,38,42,43],caus:[9,11,19,21,23,27,30,38,42,43],caution:[28,30,43],cbb:[28,30,43],cell:[27,28,29,30,34,43],center:[7,9,19,27,29,30,38,43],center_on:[7,9,19,38],centimet:43,certain:[7,9,29,30,38],chang:[7,9,16,17,19,21,22,23,26,27,29,30,38,40,41,42,43],chart:[9,16,19,21,23,38,40,42],cheap:[9,19,21,23,27,30,38,42,43],check:[6,9,12,13,19,21,22,23,24,27,30,38,39,42,43],check_point_data_dist:[27,30,38,43],chi:[29,30,43],chile:[27,30,43],chunk:[9,12,13,19,24,29,30,34,38,39,43],chunk_len:[29,30,43],cinact:[28,30],cinit:[28,30,43],classic:[7,9,38],classmethod:[7,9,12,13,19,21,23,29,30,38,39,42,43],clean:[6,9,10,24,37,38,42],clean_filenam:[24,42],clean_missing_expon:[24,38,42],cleanup:[30,32,43],clear:[9,10,12,13,19,30,38,39],clockwis:[29,30,43],close:[11,19,38],closer:[27,30,43],cls:[7,9,12,13,19,21,23,29,30],cmd_str:[30,32,43],code:[9,12,13,19,29,30,38,39,43],coef:[9,12,13,19,30,38,39],coef_dict:[9,19,21,23,38,42],coeffic:[27,30,43],coeffici:[9,12,13,19,21,23,27,29,30,38,39,42,43],cofactor:[9,10,38],col:[9,12,13,16,17,19,29,30,34,38,39,40,41,43],col_:[9,12,13,19,38,39],col_nam:[9,10,12,13,19,30,38,39],collect:[1,4,8,9,10,16,26,38,40],coloc:[9,19,21,23,38,42],colon:[9,19,21,23,38,42],color:[7,9,38],colum:[9,12,13,19,38,39],column:[2,7,8,9,10,12,13,16,19,20,21,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],columnn:[16,40],combiat:[24,42],combin:[16,30,34,40,43],come:[9,12,13,19,38,39],command:[6,17,18,19,20,28,29,30,32,34,41,43],comment:[29,30,34,43],comment_char:[30,34,43],common:[9,12,13,19,21,23,30,34,38,39,42,43],commun:[17,18,19,20,41],companion:[28,29,30,43],compar:[9,26,38],compat:[9,12,13,19,24,28,30,35,38,39,42,43],competit:[9,10,38],complement:[9,10,12,13,19,30,38,39],complet:[9,14,17,18,19,20,21,23,30,32,34,36,38,41,42,43],complex:[29,30,43],compliant:[30,35,43],complic:[29,30,43],compliment:[26,38],compon:[8,9,10,12,13,14,16,17,19,21,23,24,28,30,34,36,38,39,40,41,42,43],composit:[9,10,19,21,23,38,42],compress:[29,30,43],concat:[12,13,29,30,38,39,43],concaten:[12,13,39],concentr:[28,30,43],conceptu:[9,26,38],cond:[29,30,43],condit:[9,12,13,17,19,26,27,29,30,38,39,41,43],condition_on:[9,12,13,19,30,37,38,39],conditional_factor:[27,30,43],conditioning_el:[9,12,13,19,30,38,39],condor_submit:[17,18,19,20,41],conduct:[28,29,30,43],confid:[1,4,8,9,10,12,13,19,21,23,26,29,30,34,38,39,42,43],config:[28,29,30,43],config_fil:[28,29,30,43],configur:[6,28,29,30,43],conjunct:[16,40],connect:[29,30,43],consid:[16,40],consider:[29,30,43],consist:[28,30,43],consolid:24,const_prop:[29,30,43],constant:[29,30,34,43],constant_head:[28,30,43],constraint:[9,19,20,21,23,38,41,42],construct:[1,3,4,7,8,9,10,12,13,14,17,18,19,20,21,23,24,26,28,29,30,32,34,38,39,41,42,43],constructor:[9,12,13,19,21,23,24,27,29,30,38,42,43],construtor:[9,19,21,23,38,42],constuctor:[9,12,13],contain:[5,6,7,9,12,13,15,16,19,21,22,23,24,26,27,28,29,30,33,34,35,38,39,40,42,43],content:37,continu:[19,20,27,30,41,43],contorl:[29,30,43],contribut:[8,9,19,21,23,24,26,27,30,38,42,43],control:[1,2,3,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,38,39,40,41,42,43],control_data:[3,9,19,21,23,29,30,37,38,42,43],control_default_lin:22,control_variable_lin:22,controldata:[9,19,21,22,23,38,42],contructor:[9,12,13,19,30,38,39],convention:6,convert:[9,12,13,19,21,23,29,30,38,39,43],coo:[12,13,29,30,34,39,43],coo_rec_dt:[9,12,13,19,37,38,39],cool:[29,30,43],coordin:[27,29,30,43],coorespond:[28,30,43],copi:[7,9,12,13,19,21,22,23,29,30,32,34,37,38,39,42,43],core:[30,32,43],corner:[29,30,43],correctli:[29,30,43],correl:[7,9,12,13,19,27,29,30,34,38,39,43],correspond:[3,6,7,9,12,13,17,19,21,23,24,27,28,29,30,38,39,41,42,43],could:[13,39],count:16,counter:[7,9,19,21,23,29,30,35,38,43],countless:[30,32,43],cov:[1,4,7,8,9,10,12,13,14,17,18,19,20,26,27,29,30,34,37,38,39,41,43],covari:[1,4,7,8,9,10,12,13,17,18,19,20,26,27,29,30,34,38,39,41,43],covariance_matrix:[7,9,17,19,27,30,37,38,41,43],covariance_point:[27,30,38,43],covarinc:[27,30,43],covmat_fil:[9,12,13,19,30,38,39],crazi:[30,32,43],creat:[5,6,7,9,10,11,12,13,16,19,21,22,23,24,27,28,29,30,35,38,39,40,42,43],critic:[29,30,43],cross_over_bas:[19,20,41],crowd:[19,20,41],crowd_dist:[19,20,38,41],crude:[9,12,13,19],cryptic:[28,30,43],cso_j:[9,10,38],csv:[7,9,12,13,16,19,24,28,29,30,34,38,39,40,42,43],csv_filenam:[24,42],csv_to_ins_fil:[24,30,34,38,42],csvin:[16,40],csvout:[16,40],current:[6,7,9,16,17,19,21,22,23,24,29,30,34,38,40,41,42,43],cwd:[6,24,30,32,43],cycl:[17,19,41],cycle_fil:[17,19,41],dai:[28,29,30,43],danger:[29,30,35,43],dat:[9,19,21,23,27,28,29,30,33,34,38,42,43],data:[0,4,9,12,13,19,21,22,23,24,26,27,28,29,30,34,38,39,42,43],datafram:[2,7,8,9,10,12,13,14,16,17,19,20,21,22,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],dataframe_to_smp:[30,35,38,43],datafran:[16,40],dataset:[9,26,38],datatim:[30,34,43],dataworth:[4,9,26,38],datetim:[9,19,21,23,28,29,30,34,35,38,42,43],datetime_col:[30,35,43],datetime_format:[30,35,43],deal:[3,9,19,21,22,23,27,30,38,42,43],debug:[29,30,32,43],decomposit:[9,12,13,19,21,22,23,38,39,42],decor:[6,9,10,12,13,19,38,39],decreas:[9,26,38],dedic:[30,43],deduc:[9,10,30,35,43],def:[30,34,43],defaul:[9,12,13,19,38,39],default_dict:24,defaut:[27,30,43],defin:[1,4,7,8,9,10,12,13,19,26,27,28,29,30,34,38,39,43],definit:[14,22,30,34,38,42,43],degre:[27,29,30,43],delc:[29,30,43],delfin:[27,30,43],deli:[27,30,43],delimit:[30,34,43],delr:[29,30,43],delx:[27,30,43],demystifi:[16,40],denot:[30,34,43],dens:[9,12,13,19,30,38,39],depart:[27,30,43],depend:[7,9,13,17,19,21,23,26,27,30,38,39,41,42,43],derinc:[9,19,21,23,38,42],deriv:[9,10,12,13,14,16,19,21,23,27,30,35,38,39,40,42,43],dervi:[30,33,43],describ:[6,7,9,19,27,30,38,43],descript:[29,30,43],design:[0,9,19,27,30,38,41,43],desir:[27,30,34,43],destroi:[29,30,34,43],detail:[29,30,43],detect:[30,32,43],deter_rang:[16,40],deter_v:[16,40],determin:[6,8,9,14,19,20,27,28,30,38,41,43],determinist:[16,40],dev0:6,devdist:6,develop:[16,18,29,30,40,41,43],deviat:[1,4,7,8,9,10,12,13,16,17,19,21,23,26,27,29,30,34,38,39,40,41,42,43],devnul:[30,32,43],dfault:[30,34,43],dfs:[30,34,43],diag:[9,12,13,19,38,39],diagon:[7,9,12,13,17,18,19,20,26,30,38,39,41],dict:[7,8,9,10,12,13,14,16,19,21,22,23,24,26,27,28,29,30,33,38,40,42,43],dictionari:[1,4,7,8,9,10,14,16,19,21,23,24,26,28,29,30,33,34,38,40,42,43],dif:[29,30,43],diff_df:[29,30,43],differ:[1,4,8,9,10,18,19,21,23,26,28,29,30,34,38,42,43],differen:[7,9,19,38],differenc:[9,26,29,30,38,43],dimens:[9,12,13,14,19,27,28,30,34,38,39,43],dimension:[0,9,29,30,34,38,43],dir:[28,29,30,32,43],direct:[29,30,34,43],directli:[0,9,10,12,13,16,17,18,19,20,21,23,27,30,34,38,40,41,43],directori:[6,9,16,17,18,19,20,21,23,28,29,30,32,33,34,38,40,41,42,43],dirti:[6,28,30],dis:[28,30,43],discrep:[9,19,21,23,38,42],discret:[29,30,43],displai:[11,19,38],dist_typ:[30,34,43],distanc:[6,7,9,19,20,21,23,27,29,30,38,41,42,43],distrbut:[16,40],distribut:[1,4,7,8,9,10,12,13,14,16,19,26,30,34,38,39,40,43],divid:[9,10,38],divis:[27,30,34,43],document:[5,9,10,14,38],doe:[7,9,19,21,23,24,27,29,30,38,42,43],doesnt:[9,19,21,23,38,42],domin:[19,20,38,41],don:[6,9,17,19,21,23,28,30,38,41,42,43],done:[28,30,43],dont:[30,32,43],dot:[9,12,13,19,38,39],doubl:[9,12,13,19,28,30,37,38,39,43],downstream:[28,30,43],draw:[7,9,14,17,19,27,29,30,34,37,38,41,43],draw_arrai:[27,30,38,43],draw_condit:[27,30,38,43],drawn:[7,9,19,38],drop:[7,9,10,12,13,14,17,19,27,29,30,37,38,39,41,43],drop_prior_inform:[9,10,37,38],dropna:[7,9,37,38],droptol:[9,12,13,19,29,30,34,38,39,43],dry:[28,30],dtemp:[38,39],dtype:[24,38,39,43],dubiou:[25,30],duplic:[9,19,21,23,24,38,42],dure:[9,11,12,13,19,21,23,26,27,28,29,30,38,39,42,43],dv_ensembl:[19,20,41],dv_name:[19,20,41],dynam:[17,19,41],each:[7,8,9,12,13,16,17,19,20,21,23,24,26,27,28,29,30,33,34,38,39,40,41,42,43],easi:[9,12,13,14,19,38,39],easiest:[7,9,16,19,38,40],east:[27,30,43],echo:[11,19,24,27,30,32,38,42,43],edg:[16,27,29,30,40,43],eexcept:6,effect:[7,9,10,19,21,23,26,27,30,38,42,43],eigen:[7,9,19,38],eigenvector:[29,30,43],eigthresh:[7,8,9,12,13,19,38,39],either:[9,12,13,19,26,27,29,30,34,35,38,39,43],elaps:[11,19,38],element:[9,10,12,13,19,28,29,30,34,38,39,43],element_isalign:[9,12,13,19,37,38,39],elementwis:[9,12,13,19],eli:[30,32],elitediffevol:[19,20,38,41],ellips:[27,30,43],els:[6,27,30,34,43],empir:[7,9,17,19,38,41],empti:[9,12,13,27,30,38,39,43],enbsembl:[9,19,21,23,38,42],encapsul:[9,10,12,13,19,21,22,38,39,42],encourag:[9,19,21,23,38,42],end:[24,28,29,30,43],endswith:[28,30,43],enfil:[24,42],enfor:[17,19,41],enforc:[7,9,14,19,21,23,37,38,42],enforce_bound:[7,9,14,17,19,21,23,37,38,41,42],engin:[29,30,43],enk:[17,19,38,41],enkf:[17,19,38,41],ens:[29,30,43],ensembl:[0,2,7,9,14,16,17,18,19,20,21,23,24,27,29,30,34,37,38,40,41,42,43],ensemble1:[16,40],ensemble2:[16,40],ensemble_change_summari:[16,38,40],ensemble_help:[7,9,16,38,40],ensemble_method:[5,9,17,19,20,37,38],ensemble_res_1to1:[16,38,40],ensemblekalmanfilt:[17,19,38,41],ensemblemethod:[17,18,19,20,38,41],ensemblesmooth:[10,38],ensur:[30,34,43],enter:[29,30,43],entir:[9,12,13,19,26,27,28,29,30,38,39,43],entri:[4,7,8,9,12,13,19,21,22,23,24,26,27,28,29,30,34,36,38,39,42,43],enumer:[16,40],env:6,environment:[0,9,19,38,41],epsg:[29,30,38,43],epsilon:[14,27,30,38,43],eqs:[9,19,21,23,38,42],equal:[7,9,10,12,13,19,21,23,27,28,29,30,34,38,39,42,43],equat:[9,10,19,21,23,24,26,27,29,30,38,42,43],equival:[28,30,43],err:[8,9,38],err_var:[27,30],error:[1,8,9,10,24,27,29,30,38,42,43],errvar:[0,7,8,9,10,16,19,37,38,40,41],especi:[29,30,43],estim:[9,10,19,21,23,26,27,30,37,38,42,43],estmat:[9,19,21,23,38,42],etc:[7,9,16,27,28,29,30,33,38,40,43],euclidean:[7,9,38],evalu:[7,9,17,18,19,20,21,23,26,29,30,38,41,42,43],even:[7,9,19,21,23,38,42],evensen:[17,19,41],event:[11,19,38],eventu:[30,34],everi:[9,26,27,29,30,32,34,38,43],every_n_cel:[30,33,43],evolalg:[19,20,38,41],exactli:[9,12,13,38,39],exampl:[1,2,3,4,7,8,9,10,12,13,14,16,19,21,23,24,26,27,28,29,30,32,33,34,35,36,38,39,40,42,43],exce:[29,30,43],except:[6,9,11,12,13,19,24,25,27,29,30,32,38,39,42,43],exchang:[29,30,43],excinfo:[30,32],exclus:[9,19,21,23,30,34,38,42,43],exe:[30,32,43],exe_rel_path:[30,32,43],execut:[28,29,30,32,34,43],exist:[1,3,4,7,8,9,10,14,19,21,23,24,26,27,28,29,30,32,33,34,35,38,42,43],existing_jco:[14,38],exit:[30,32,43],expand:6,expans:[29,30],expect:[21,22,28,30,34,42,43],experiment:[9,19,21,23,27,29,30,38,42,43],explicitli:[7,9,19,22,29,30,38,42,43],explod:[30,32,43],exponenti:[27,30],expos:[14,38],express:[9,26,29,30,38,43],expvario:[27,30,38,43],exst:[30,34,43],ext:[30,32],extend:[4,9,12,13,19,26,37,38,39],extens:[1,4,8,9,10,26,29,30,34,38,43],extent:[29,30,43],extern:[29,30],external_ins_out_pair:[29,30,43],external_path:[29,30],external_tpl_in_pair:[29,30,43],extra:[7,9,19,29,30,38,43],extra_model_cmd:[29,30,43],extra_post_cmd:[29,30,43],extra_pre_cmd:[29,30,43],extract:[1,6,8,9,12,13,19,24,28,30,34,37,38,39,42,43],fac2real:[27,29,30,38,43],fac_fil:[29,30,43],facecolor:[7,9,16,38,40],facili:[30,34,43],factor:[7,9,19,21,23,27,29,30,38,42,43],factors_fil:[27,29,30,43],fail:[24,27,30,42,43],failed_run:[19,20],failur:[28,30,43],fake:[29,30,43],fals:[1,2,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],famili:[29,30,43],fast:[9,12,13,19,27,29,30,38,39,43],faster:[7,9,12,13,19,38,39],feasibl:[19,20,41],feet:[29,30,43],fehalf:[9,10,37,38],few:[30,32,43],ffmt:[22,24,30,38,42],fft:[27,30,43],field:[9,19,21,23,27,30,38,42,43],fieldnam:[9,19,21,23],fig:[16,40],figsiz:[16,40],figur:[16,40],file:[1,2,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],file_nam:[30,34,43],fileanm:[16,40],filenam:[1,3,4,7,8,9,10,11,12,13,14,16,17,18,19,20,21,23,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],filetyp:[16,40],fill:[7,9,12,13,14,19,24,28,29,30,34,38,39,42,43],fill_valu:[27,29,30,34,43],filter:[7,9,38],find:[9,12,13,19,21,23,24,26,27,28,29,30,38,39,42,43],find_rowcol_indic:[9,12,13,19,37,38,39],finit:[9,19,21,23,38,42],first:[1,4,8,9,10,11,12,13,14,16,19,24,26,27,28,30,38,39,40,42,43],first_forecast:[8,9,37,38],first_order_pearson_tikhonov:[29,30,38,43],first_paramet:[8,9,37,38],first_predict:[8,9,37,38],fit:[29,30,43],fix:[7,9,19,21,23,24,27,30,35,38,42,43],fixed_index:[7,9,19,37,38],flag:[1,2,3,4,7,8,9,10,11,12,13,16,19,21,23,24,26,27,28,29,30,32,33,34,35,38,39,40,42,43],float1:[],float64:[38,39,43],flopi:[27,28,29,30,33,43],flow:[28,29,30,43],flush:[30,34,43],flux1:[9,26,38],flux2:[9,26,38],flux:[9,26,28,29,30,38,43],flux_fil:[28,30,43],flx_filenam:[28,30,43],fmt:[29,30,34,43],folder:[30,34,43],follow:[9,12,13,17,19,27,29,30,41,43],fom:[9,10,38],font:16,forc:[17,19,30,35,41,43],forcast:[17,19,38,41],fore1:[4,9,10,26,38],fore2:[4,9,10,26,38],forecast:[1,4,8,9,10,17,19,21,23,26,37,38,41,42],forecast_nam:[9,10,19,21,23,37,38,42],forecasts_it:[9,10,37,38],forgiv:[9,19,21,23,27,30,43],form:[0,1,7,8,9,10,12,13,14,16,19,21,23,24,26,27,28,29,30,34,35,38,39,40,41,42,43],format:[9,12,13,19,21,22,23,24,27,28,29,30,34,35,38,39,42,43],formatt:[9,19,21,23,30,34],formatted_valu:[21,22,38,42],former:[30,34,43],formul:[9,10,17,19,29,30,38,41,43],fortran:[9,12,13,19,24,38,39,42],forward:[17,18,19,28,29,30,34,41,43],forward_run:[28,29,30,43],fosm:[0,1,4,8,9,16,19,26,38,40,41],found:[1,4,8,9,10,19,21,23,24,26,27,28,29,30,32,38,42,43],frac_tol:[9,19,21,23,38,42],fraction:[9,19,21,23,38,42],fraction_stdev:[9,19,21,23,38,42],framework:[30,43],free:[14,29,30,34,35,38,43],freez:[29,30,43],freyberg_jac:[16,40],from:[1,4,6,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,38,39,40,41,42,43],from_ascii:[9,12,13,19,29,30,37,38,39,43],from_binari:[7,9,12,13,16,19,37,38,39,40],from_csv:[7,9,37,38],from_datafram:[7,9,12,13,19,37,38,39],from_fortranfil:[9,12,13,19,37,38,39],from_gaussian_draw:[2,7,9,19,37,38],from_gridspec:[29,30,38,43],from_io_fil:[9,19,21,23,29,30,37,38,42,43],from_mixed_draw:[7,9,19,37,38],from_nam:[9,12,13,19,37,38,39],from_namfil:[29,30,38,43],from_observation_data:[7,9,12,13,19,30,37,38,39],from_obsweight:[9,12,13,19,30,37,38,39],from_par_obs_nam:[9,19,21,23,37,38,42],from_parameter_data:[7,9,12,13,19,29,30,37,38,39,43],from_parbound:[9,12,13,19,30,37,38,39],from_parfil:[7,9,19,37,38],from_pst:[9,12,13,37,38,39],from_triangular_draw:[7,9,19,37,38],from_uncfil:[9,12,13,19,30,37,38,39],from_uniform_draw:[7,9,19,37,38],front:[9,19,21,23,24,29,30,34,35,38,42,43],full:[9,12,13,19,29,30,38,39,43],full_:[9,12,13,19,37,38,39],func:[30,32],func_dict:[7,9,16,38,40],function_nam:[30,34,43],gage:[28,30,43],gage_fil:[28,30,43],gage_num:[28,30,43],gage_output_fil:[28,30,43],gain:[9,26,38],gather:[9,26,30,34,38,43],gaussian:[7,9,14,16,19,27,30,34,38,40,43],gaussian_distribut:[16,38,40],gauvario:[27,30,38,43],gener:[0,5,7,9,14,16,17,19,21,22,23,24,27,29,30,33,34,35,38,40,41,42,43],generate_prior:[17,19,38,41],generic_pst:[24,38,42],geo:[30,34,43],geostast:[29,30,43],geostat:[5,9,29,30,34,37,38],geostatist:[27,29,30,43],geostatistical_draw:[29,30,38,43],geostatistical_prior_build:[29,30,38,43],geostatitical_draw:[29,30,43],geostruct:[27,29,30,34,38,43],get:[6,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,34,37,38,39,40,41,42,43],get_added_obs_group_import:[9,26,37,38],get_added_obs_import:[9,26,37,38],get_adj_pars_at_bound:[9,19,21,23,37,38,42],get_common_el:[13,38,39],get_conditional_inst:[9,26,37,38],get_config:6,get_contribution_datafram:[9,26,38],get_cso_datafram:[9,10,37,38],get_datafram:[21,22,38,42],get_devi:[7,9,17,19,37,38,41],get_diagonal_vector:[9,12,13,19,37,38,39],get_errvar_datafram:[1,8,9,37,38],get_ext:[29,30,38,43],get_forecast_summari:[4,9,26,37,38],get_grid_lin:[29,30,38,43],get_identifi:[16,40],get_identifiability_datafram:[8,9,16,37,38,40],get_ij:[29,30,38,43],get_keyword:6,get_maha_obs_summari:[29,30,38,43],get_maxs:[8,9,12,13,19,37,38,39],get_maxsing_from_:[9,12,13,19,37,38,39],get_ns:[14,37,38],get_null_proj:[7,8,9,14,19,37,38],get_obs_competition_datafram:[9,10,37,38],get_obs_group_dict:[9,26,37,38],get_par_change_limit:[9,19,21,23,37,38,42],get_par_contribut:[9,26,37,38],get_par_css_datafram:[9,10,37,38],get_par_group_contribut:[9,26,37,38],get_parameter_contribut:[4,9,26,38],get_parameter_summari:[9,26,37,38],get_phi_comps_from_recfil:[24,38,42],get_proj4:[29,30,43],get_rc:[29,30,38,43],get_removed_obs_group_import:[9,26,37,38],get_removed_obs_import:[9,26,37,38],get_res_stat:[9,19,21,23,37,38,42],get_risk_shifted_valu:[19,20,38,41],get_vers:6,get_vertic:[29,30,38,43],get_xcenter_arrai:[29,30,38,43],get_xedge_arrai:[29,30,38,43],get_xi:[30,34,43],get_ycenter_arrai:[29,30,38,43],get_yedge_arrai:[29,30,38,43],ghex:6,git:6,git_describ:6,git_get_keyword:6,git_pieces_from_vc:6,git_versions_from_keyword:6,give:[7,9,19,28,30,38,43],given:[6,8,9,12,13,14,16,19,26,27,28,29,30,32,38,39,40,43],glm:[16,40],global:[7,9,28,29,30,38,43],glue:[14,19,38,41],goal:6,good:[9,19,21,23,38,42],gov:[29,30,43],govern:[30,34],gpname:[24,30,34,42,43],gr_df:[27,30,43],gracefulli:[30,32,43],greater:[9,12,13,19,21,23,26,27,28,29,30,33,34,38,39,42,43],greater_than_obs_constraint:[9,19,21,23,37,38,42],greater_than_pi_constraint:[9,19,21,23,37,38,42],grid:[27,29,30,33,34,43],grid_geostruct:[29,30,43],grid_is_regular:[27,30,38,43],grid_par_ensemble_help:[27,30,38,43],grid_prop:[29,30,43],gridspec_fil:[29,30,43],groundwat:[9,19,21,23,38,42],group1:[9,19,21,23,38,42],group:[7,9,16,19,21,23,24,26,28,29,30,32,34,38,40,42,43],group_nam:[9,19,21,23,38,42],grouper:[7,9,16,40],grp:[9,19,21,23,38,42],gslib:[27,30,43],gslib_2_datafram:[27,30,38,43],guess:[30,32],gw_filenam:[28,30,43],gw_prefix:[28,30,43],gw_util:[5,9,29,30,37,38],gwutils_compli:[30,35,43],h_function:[27,30,43],hadamard_product:[9,12,13,19,37,38,39],half:[9,10,38],hand:[9,19,21,23,38,42],handl:[7,9,11,12,17,19,21,22,23,24,27,28,29,30,38,39,41,42,43],handler:6,handoff:[16,40],happen:[11,16,19,28,30,38,40,43],hard:[9,10,21,22,38,42],has:[7,9,10,17,19,21,23,26,27,29,30,33,38,41,42,43],hash:6,hasn:6,have:[6,7,9,12,13,14,16,17,19,21,22,23,24,27,28,29,30,32,33,34,38,39,40,41,42,43],hcond1:[28,30,43],hcond2:[28,30,43],hds:[9,19,21,23,26,28,30,38,42,43],hds_file:[28,29,30,43],hds_kperk:[29,30,43],hds_timeseri:[28,30,43],head1:[9,26,38],head2:[9,26,38],head:[28,29,30,43],head_lines_len:[24,42],head_row:[29,30,43],header:[24,27,29,30,34,42,43],headfil:[28,30,43],headsav:[28,30,43],heavi:[9,10,12,13,19,38,39],help:[7,9,19,29,30,35,38,43],helper:[5,9,15,16,19,21,23,24,30,32,34,37,38,40,42],here:[9,17,19,21,23,29,30,38,41,42,43],hessian:[8,9,38],heurist:[30,32],hex:6,hexbin:[16,40],hfb6:[28,30,43],hfb6_par:[28,30,43],hfb:[28,29,30,43],hfb_par:[28,29,30,43],hide_stderr:6,high:[0,9,29,30,34,38,43],highli:[30,34,43],hill:[9,10,38],hist:[24,42],histogram:[7,9,16,19,21,23,38,40,42],hk1:[9,26,38],hk2:[9,26,38],hk_0001:[30,33,43],hk_0002:[30,33,43],hk_:[30,33,43],hk_layer_1:[27,30,43],hkpp:[27,29,30,43],hob:[28,29,30,43],hob_fil:[28,30,43],hold:[16,40],horizont:[29,30,43],horribl:[28,30,43],hostnam:[30,32,43],how:[7,9,14,17,19,21,23,29,30,36,38,41,42,43],how_dict:[7,9,19,38],howev:[9,12,13,19,29,30,36,38,39,43],htcondor:[17,18,19,20,41],html:[29,30,43],http:[29,30,32,43],huge:[9,10,38],hydmod:[28,29,30,43],hydmod_fil:[28,30,43],hydmod_outfil:[28,30,43],hydraul:[29,30,43],hymod_fil:[28,30,43],hyperlink:[],i_minus_r:[8,9,37,38],ibound:[27,29,30,33,43],icod:[9,12,13,19,38,39],icount:[38,39],id_df:[16,40],idea:[9,19,21,23,38,42],ident:[8,9,10,12,13,19,30,37,38,39],identifi:[6,8,9,16,19,20,21,23,28,29,30,34,38,40,41,42,43],identifiabi:[8,9,38],identity_lik:[9,12,13,19,30,37,38,39],idiom:[29,30,43],idist:[27,30],idx:7,idx_str:[30,34],ies:[30,32,43],ifact:[27,30],ifmt:[22,24,30,38,42],ignor:[7,9,12,13,17,18,19,20,38,39,41],ij_in_idx:[30,34,43],iloc:[7,37,38],implement:[6,9,12,13,17,19,21,22,23,24,27,29,30,32,34,38,39,41,42,43],impli:[1,4,7,8,9,10,12,13,16,19,21,23,26,27,29,30,34,38,39,40,42,43],impliment:[30,34,43],in_fil:[9,19,21,23,24,29,30,38,42,43],in_filenam:[30,34],inact:[28,29,30,34,43],inact_abs_v:[28,30,43],inam:[27,30],includ:[0,6,7,9,10,12,13,16,17,19,21,23,24,26,27,29,30,33,34,38,39,40,41,42,43],include_path:[9,12,13,19,28,29,30,38,39,43],include_prior_result:[9,26,38],include_temporal_par:[28,29,30,43],include_zero:[16,40],include_zero_weight:[29,30,43],includes_head:[24,42],includes_index:[24,42],increas:[9,19,21,23,26,38,42],increment:[9,19,21,23,38,42],indent:[30,34,43],independ:[0,9,19,30,38,41,43],index:[2,7,8,9,12,13,16,19,20,21,23,24,26,27,28,29,30,33,34,35,36,38,39,40,41,42,43],index_col:[29,30,34,43],indic:[2,7,9,12,13,19,21,23,24,27,28,29,30,34,37,38,39,42,43],indici:[30,34,43],individu:[9,19,21,23,27,28,30,38,42,43],inequ:[9,19,21,23,38,42],influenti:[9,26,38],info:[9,12,13,19,21,23,24,27,28,29,30,33,34,38,39,42,43],inform:[6,9,10,14,19,21,22,23,24,26,27,28,29,30,32,33,34,35,38,42,43],inherit:[9,12,13,14,19,30,38,39],inhert:[7,9,38],initi:[7,9,17,18,19,20,26,27,30,34,38,41,43],initialize_spatial_refer:[30,34,38,43],inplac:[9,12,13,14,19,38,39],input:[9,16,17,19,21,22,23,24,28,29,30,32,34,38,40,41,42,43],input_fil:[29,30,43],input_filenam:[30,34,43],ins:[9,17,19,21,23,24,28,29,30,34,35,38,41,42,43],ins_fil:[9,19,21,23,24,28,29,30,34,38,42,43],ins_filenam:[24,28,30,35,42,43],ins_lcount:24,ins_lin:24,inschek:[9,19,21,23,24,29,30,34,38,42,43],insfil:[30,34,43],insfilenam:[29,30,43],insid:6,inst:[30,34,43],instanc:[1,2,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,38,39,40,41,42,43],instani:[9,12,13,19,30,38,39],instanti:[9,12,13,17,18,19,20,21,22,27,29,30,38,39,41,42,43],instead:[7,9,12,13,16,19,30,32,34,35,38,39,40,43],instruct:[9,17,19,21,23,24,28,29,30,34,35,38,41,42,43],instruction_set:[24,42],instructionfil:[9,19,21,23,24,30,34,38,42,43],int32:[38,39],integ:[7,9,12,13,19,27,30,33,37,38,39,43],interest:[9,10,19,21,23,28,30,43],interfac:[0,9,17,18,19,20,21,23,29,30,34,38,41,42,43],intergroup:[9,19,21,23,38,42],internet:[29,30,43],interp_cov:[27,30],interpl:[27,30,43],interpol:[27,29,30,43],interpolatino:[27,30,43],interpret:[9,12,13,19],intuit:[9,12,13,38,39],inv:[9,12,13,17,19,37,38,39,41],inv_h:[27,30,38,43],invers:[7,9,12,13,17,19,21,23,26,27,29,30,38,39,41,42,43],invert:[27,30,43],iobj:[16,40],irun:[29,30,43],is_domin:[19,20,41],is_feas:[19,20,38,41],is_nondominated_continu:[19,20,38,41],is_nondominated_kung:[19,20,38,41],is_nondominated_pathet:[19,20,38,41],is_pre_cmd:[30,34,43],isdiagon:[9,12,13,19,30,38,39],isdiaon:[9,12,13,19,30,38,39],isdigon:[9,12,13,19,30,38,39],isfropt:[29,30],iskeyword:[21,22,42],islog:[29,30,43],issu:[17,18,19,20,24,27,29,30,32,41,42,43],istransform:[2,7,9,19,37,38],item:[7,9,10,12,13,19,21,22,23,24,30,35,38,42],itemp1:[38,39],itemp2:[38,39],iter:[9,10,17,19,21,23,24,26,29,30,38,41,42,43],iter_report:[19,20,38,41],ithread:[27,30],its:[9,19,21,23,27,30,38,42,43],itself:[28,30,33,43],jacobian:[1,8,9,10,12,13,14,16,19,29,30,38,39,40,43],jactest:[16,29,30,40,43],jactest_in_fil:[16,40],jactest_out_fil:[16,40],jcb:[1,4,7,8,9,10,12,13,14,16,19,26,27,29,30,38,39,40,43],jco:[1,4,7,8,9,10,12,13,14,16,19,26,29,30,37,38,39,40,43],jco_from_pestpp_runstorag:[29,30,38,43],join:[9,16,19,21,23,30,32,34,38,40,42,43],junk:[27,30,43],just:[6,7,8,9,19,24,26,30,34,38,42,43],k_zone_dict:[29,30,43],kalman:[7,9,38],karg:[16,40],karhuen:[9,10,38],karhuenen:[29,30,43],karhunen:[1,8,9,10,29,30,38,43],keep:[8,9,19,21,23,38,42],kei:[7,9,16,19,21,22,23,26,28,29,30,34,38,40,42,43],kept:[28,30,43],keyword:[6,7,9,14,16,21,22,27,30,38,40,42,43],kij:[30,34,43],kij_dict:[28,30,43],kind:[8,9,16,19,21,23,24,26,29,30,38,40,42,43],kl_appli:[29,30,38,43],kl_factor:[29,30,43],kl_geostruct:[29,30,43],kl_num_eig:[29,30,43],kl_prop:[29,30,43],kl_setup:[29,30,38,43],know:[9,12,13,19,21,23,26,30,38,39,42],knowledg:[9,10,26,38],known:[9,12,13,19,26,30,38,39],kper:[28,29,30,43],kperk:[29,30],kperk_pair:[28,30,43],krige:[27,30,43],kstp:[28,30,43],kung:[19,20,41],kwarg:[1,4,7,8,9,10,12,13,14,16,18,19,20,21,22,23,26,27,29,30,34,38,40,41,42,43],l1_crit_val:[29,30,43],l2_crit_val:[29,30,43],label:[8,9,16,26,29,30,38,40,43],label_post:[16,40],label_prior:[16,40],lag:[24,42],lai:[29,30,43],lambda:[9,19,21,22,23,28,30,42,43],lambda_mult:[18,19,41],larg:[7,9,16,19,27,29,30,38,40,43],large_cov:[9,12,13,19,38,39],larger:[27,30,43],largest:[8,9,12,13,14,19,26,38,39],last:[7,9,19,24,26,28,30,38,42,43],last_kstp_from_kp:[28,30,38,43],later:[24,42],latest:[29,30],latex:[9,19,21,23,38,42],layer:[28,29,30,33,43],lbnd:[7,9,19,37,38],lbound:[30,34,43],lcount:[24,42],lead:[9,19,21,23,27,30,38,42,43],learn:[9,26,38],least:[9,19,21,23,30,33,38,42,43],leav:[9,19,21,23,38,42],leave_zero:[9,19,21,23,38,42],left:[9,12,13,19,29,30,34,38,39,43],legend:[27,30,43],len:[27,28,30,34,43],length:[9,12,13,19,29,30,38,39,43],length_multipli:[29,30,38,43],lenuni:[29,30,38,43],lenuni_text:[29,30,38,43],lenuni_valu:[29,30,38,43],less:[7,9,12,13,19,21,23,26,27,28,29,30,34,36,38,39,42,43],less_than_obs_constraint:[9,19,21,23,37,38,42],less_than_pi_constraint:[9,19,21,23,37,38,42],level:[6,9,19,21,23,29,30,32,34,38,42,43],lieu:[29,30,43],lift:[29,30,43],like:[6,9,12,13,19,24,29,30,32,34,38,39,42,43],likelihood:[9,10,38],limit:[9,19,21,23,30,34,38,42,43],linalg:[9,12,13,19,38,39],line:[9,16,19,21,22,23,24,27,28,29,30,32,34,35,38,40,42,43],linear:[9,10,11,12,13,17,19,26,30,38,39,41],linearanalsi:[1,8,9,38],linearanalysi:[1,4,8,9,10,14,26,37,38],list1:[13,39],list2:[13,39],list:[1,4,7,8,9,10,12,13,16,19,21,22,23,24,26,27,28,29,30,33,34,38,39,40,42,43],list_filenam:[28,30,43],load:[1,3,4,7,8,9,10,12,13,16,17,19,21,23,24,26,27,28,29,30,33,35,37,38,39,40,41,42,43],load_listtyp:[30,34],load_sfr_out:[28,30,38,43],load_sgems_exp_var:[27,30,38,43],loc:[7,9,19,21,23,26,37,38,42],local:[6,7,9,17,18,19,20,27,30,32,38,41,43],localhost:[30,32,43],locat:[9,12,13,14,19,21,23,27,28,29,30,33,34,38,39,42,43],lock:[27,30],loev:[1,8,9,10,29,30,38,43],log10:[7,9,16,38,40],log:[1,2,4,7,8,9,10,11,12,13,19,21,23,26,27,29,30,32,34,37,38,39,42,43],log_:[7,9,19,38],log_index:[7,9,19,37,38],logger:[5,7,9,16,19,20,27,30,37,40,41,43],london:[27,30,43],long_nam:[29,30,43],long_version_pi:6,longer:[29,30,34,43],longnam:[24,29,30,33,34,42,43],look:[6,9,12,13,19,24,28,29,30,38,39,42,43],loop:[17,19,41],lose:[9,26,38],lost:[9,26,30,34,38,43],lot:[16,28,29,30,40,43],low:[9,19,21,23,38,42],lower:[1,4,7,8,9,10,12,13,19,21,23,24,26,29,30,34,38,39,42,43],lower_bound:[29,30,34,43],lower_lim:[27,30,43],lpf:[29,30,43],lrais:[11,19,37,38],machin:[17,18,19,20,30,32,41,43],made:[16,27,30,32,40,43],magnitud:[9,26,38],mahalanobi:[29,30,43],mai:[9,12,13,17,19,21,23,24,27,28,30,33,34,38,39,41,42,43],main:[9,19,21,23,27,29,30,36,38,42,43],major:[29,30,43],make:[7,9,10,12,13,16,19,21,22,23,27,28,29,30,32,34,38,39,40,42,43],manag:[17,18,19,20,41],mani:[9,12,13,19,21,23,29,30,38,39,42,43],manipul:[3,9,19,21,23,38,42],manual:[9,19,21,23,38,42],map:[27,29,30,43],mark:[6,16,28,30,40,43],marker:[24,42],martrix:[9,12,13,19,38,39],mass:[28,30,43],master:[30,32,43],master_dir:[30,32,43],mat:[1,4,5,8,9,10,19,26,30,37,38],mat_handl:[5,9,12,19,30,37,38],mat_inv:[9,12,13,19,38,39],match:[21,22,27,30,42,43],matplotlib:[7,9,16,19,21,23,27,30,38,40,42,43],matric:[7,9,12,13,17,19,29,30,38,39,41,43],matrix:[1,4,7,8,9,10,12,13,14,17,18,19,20,26,27,29,30,34,37,38,39,41,43],matrx:[8,9,38],matter:[7,9,19,29,30,38,43],max:[9,12,13,19,21,23,29,30,38,39,42,43],max_colwidth:[22,23,24,28,29,30,33],max_name_len:[30,35,43],maximum:[9,10,16,19,21,23,27,28,30,38,40,42,43],maxoutputpag:[16,40],maxpts_interp:[27,30,43],maxs:[7,8,9,12,13,19,21,23,38,39,42],mayb:[29,30,43],mbase:[28,29,30,33,43],mc_:[14,38],mean:[6,7,9,14,16,17,18,19,24,27,29,30,38,40,41,42,43],mean_valu:[7,9,27,30,43],meaning:[9,19,21,23,28,29,30,38,42,43],measur:[14,17,18,19,20,24,29,30,38,41,42,43],mechan:[3,9,19,21,23,38,42],member:[9,19,21,23,29,30,38,42,43],memori:[7,9,12,13,19,27,29,30,38,39,43],meshgrid:[29,30,43],mess:[7,38],messag:[11,19,24,30,32,38,42,43],met:[9,19,21,23,38,42],meta:[9,19,21,23,38,42],metadata:[9,19,21,23,38,42],meter:[29,30,43],method:[6,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,34,38,39,40,41,42,43],mf_skip:[30,34,43],mfhyd:[29,30,43],mfile_fmt:[30,34,43],mfile_skip:[30,34,43],mflist_waterbudget:[29,30,43],mfnwt:[29,30,43],might:[13,39],mimic:[27,30,43],min:[8,9,12,13,19,21,23,38,39,42],min_dist:[19,20],minim:[24,42],minimum:[8,9,27,29,30,38,43],mininum:[28,30,43],minpts_interp:[27,30,43],minu:[8,9,38],miss:[7,9,19,21,23,24,28,30,35,38,42,43],mix:[9,10],mixtur:[7,9,19,29,30,38,43],mle_covari:[9,10,37,38],mle_parameter_estim:[9,10,37,38],mlt_df:[29,30],mlt_file:[29,30,43],mode:[17,19,30,32,41,43],model:[0,9,16,17,18,19,20,21,23,24,27,28,29,30,33,34,38,40,41,42,43],model_evalut:[17,19,38,41],model_exe_nam:[29,30,43],model_fil:[29,30,43],model_length_unit:[29,30,38,43],model_temporal_evolot:[17,19,38,41],model_w:[28,29,30,43],modflow:[27,28,29,30,33,34,43],modflow_hob_to_instruction_fil:[28,30,38,43],modflow_hydmod_to_instruction_fil:[28,30,38,43],modflow_pval_to_template_fil:[28,30,38,43],modflow_read_hydmod_fil:[28,30,38,43],modflow_sfr_gag_to_instruction_fil:[28,30,38,43],modflownwt:[29,30,43],modflowsfr:[28,30,43],modif:[9,19,21,23,38,42],modifi:[9,19,21,23,28,30,38,42,43],modul:[0,9,12,15,19,21,30,36,37],moment:[7,9,16,19,38,40],monster:[29,30,43],mont:[7,8,9,14,19,38,41],montecarlo:[10,14,37,38],month:[29,30,43],moouu:[5,9,19,37,38],more:[9,12,13,16,19,21,23,26,27,28,29,30,32,34,36,38,39,40,42,43],most:[7,9,12,19,21,23,26,29,30,32,34,38,39,42,43],move:[17,19,41],mozorov:[9,19,21,23,38,42],mt3d:[28,30,43],mt3dusg:[28,30,43],mtlist_gw:[28,30,43],mtlist_sw:[28,30,43],mult2model_info:[29,30,43],mult:[28,29,30,34,43],mult_isalign:[9,12,13,19,37,38,39],mult_well_funct:[30,34,43],multi:[8,9,16,26,38,40],multiobject:[19,20,41],multipag:[7,9,16,19,21,23,38,40,42],multipl:[7,9,12,13,19,29,30,34,38,39,43],multipli:[9,12,13,19,28,29,30,34,38,39,43],multiprocess:[24,27,29,30,42,43],multivari:[7,9,19,38],must:[7,9,12,13,14,16,17,19,21,23,26,27,28,29,30,33,34,38,39,40,41,42,43],mut_bas:[19,20,41],my_inv:[9,12,13,19,38,39],my_new:[3,9,19,21,23,30,34,38,42,43],my_p:[29,30,43],my_pp:[30,33,43],my_reg:[29,30,43],my_tri_p:[7,9,19,38],my_uni_p:[7,9,19,38],mymodel:[27,29,30,43],nadj_par:[8,9,14,38],nam:[27,29,30,33,43],nam_fil:[28,29,30,43],name:[1,2,3,4,6,7,8,9,10,12,13,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,37,38,39,40,41,42,43],name_col:[30,35,43],name_prefix:[30,33,43],namefil:[29,30,43],namfil:[29,30,43],nan:[7,9,19,24,27,28,30,35,38,42,43],nane:[30,35,43],ncol:[9,12,13,19,29,30,33,37,38,39,43],ncomp:[28,30,43],ndarrai:[1,4,7,8,9,10,12,13,16,19,26,27,28,29,30,33,34,38,39,40,43],ndarri:[28,30,43],ndrop:[19,20],nearbi:[27,30],nearli:[7,9,19,38],necessari:[9,10,38],need:[1,4,6,7,8,9,10,14,16,19,22,24,26,28,29,30,32,34,38,40,42,43],nest:[9,24,26,27,29,30,38,42,43],net:[30,32],never:[9,19,21,23,38,42],new_cwd:[29,30,43],new_d:[30,34,43],new_filenam:[9,19,21,23,38,42],new_group:[9,19,21,23,38,42],new_model_w:[29,30,43],new_ob:[9,19,21,23,30,34,38,42,43],new_obs_length:[9,12,13,19,37,38,39],new_par:[9,19,21,23,38,42],new_par_length:[9,12,13,19,37,38,39],new_pst_nam:[29,30,43],new_val:[29,30,43],newx:[9,12,13,19,37,38,39],next:[9,24,26,38],next_most_important_added_ob:[9,26,37,38],next_most_par_contribut:[9,26,37,38],ninst:[30,33,43],niter:[9,26,38],nnz_ob:[8,9,19,21,23,37,38,42],nnz_obs_group:[9,19,21,23,37,38,42],nnz_obs_nam:[7,9,10,19,21,23,26,37,38,42],nob:[9,12,13,19,21,23,37,38,39,42],node:[27,30,43],nois:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,26,29,30,38,39,40,41,43],nomin:[8,9,38],non:[7,8,9,10,12,13,19,20,21,23,26,28,29,30,32,34,38,39,41,42,43],none:[1,3,4,6,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,31,32,33,34,35,38,39,40,41,42,43],nont:[14,38],nonzero:[7,9,19,21,23,37,38,42],noptmax:[3,9,14,19,21,23,29,30,38,42,43],norm:[7,9,19,38],normal:[1,4,8,9,10,12,13,19,21,23,26,30,38,39,42],north:[27,30,43],note:[1,4,6,8,9,11,12,13,19,21,22,23,26,27,29,30,32,33,34,38,42,43],notebook:36,noth:[16,40],notion:[9,26,38],notthismethod:6,notus:[27,30,43],npar:[9,10,12,13,19,21,23,37,38,39,42],npar_adj:[9,19,21,23,37,38,42],nprior:[9,19,21,23,37,38,42],nrow:[9,12,13,19,21,23,29,30,33,37,38,39,43],nsing:[14,38],nsmc:[14,38],nsv:[16,40],nugget:[27,30,38,43],nul:[30,32],num_dv_real:[19,20,41],num_eig:[29,30,43],num_eig_kl:[30,34,43],num_par_r:[19,20,41],num_pt:[16,40],num_real:[7,9,14,17,19,20,27,29,30,34,37,38,41,43],num_step:[29,30,43],num_thread:[27,30,43],num_work:[17,18,19,20,30,32,41,43],number:[7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,32,33,34,38,39,40,41,42,43],numer:[7,9,12,13,19,21,23,30,38,39],numpi:[1,4,7,8,9,10,12,13,16,19,26,27,29,30,33,34,38,39,40,43],nwt_x64:[29,30,43],o2nam:[29,30],obf:24,obgnm:[9,19,21,23,38,42],obj_func_dict:[19,20,31,41,43],obj_function_dict:[19,20,41],object:[1,4,6,7,8,9,10,11,12,13,14,18,19,20,21,22,23,24,26,27,28,29,30,34,38,39,41,42,43],obs1:[9,19,21,23,24,38,42],obs2:[9,19,21,23,24,38,42],obs:[7,9,10,14,16,19,20,21,23,24,26,28,29,30,34,38,40,41,42,43],obs_base_dev:[7,9,38],obs_df:[19,20,41],obs_dict:[9,19,21,23,38,42],obs_ensembl:[19,20],obs_group:[9,19,21,23,37,38,42],obs_idx:[9,19,21,23],obs_length:[9,12,13,19,37,38,39],obs_nam:[9,10,12,13,19,21,23,24,37,38,39,42],obs_name_set:[24,38,42],obs_obj_sign:[19,20,38,41],obs_point:[27,30,43],obs_v_sim:[9,19,21,23,38,42],obscov:[1,4,8,9,10,12,13,17,18,19,20,26,30,37,38,39,41],obsenembl:[17,19,41],obsensembl:[14,17,19,37,38,41],obseravt:[9,17,19,21,23,38,41,42],observ:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,34,35,38,39,40,41,42,43],observation_data:[7,9,12,13,19,21,23,24,30,37,38,39,42],observationensembl:[7,9,14,17,19,29,30,37,38,41,43],obseur:[22,42],obsev:[1,4,8,9,10,26,38],obsgp:[30,34,43],obsgrp_dict:[9,19,21,23,26,38,42],obslist:[9,19,21,23],obslist_dict:[9,26,38],obsnam:[29,30,43],obsnm:[28,30,34,43],obssim_smp_pair:[29,30,43],obsval:[7,9,19,20,21,23,24,28,30,34,38,41,42,43],obsviou:[9,19,21,23,38,42],oe1:[7,9,19,38],oe2:[7,9,19,38],oe3:[7,9,19,38],oe_dev:[7,9,38],off:[9,19,21,23,29,30,34,38,42,43],offend:[7,9,19,38],offset:[1,4,7,8,9,10,12,13,19,21,23,24,26,29,30,34,38,39,42,43],ofile_sep:[30,34,43],ofile_skip:[30,34,43],ogw:[29,30,43],ok_var:[27,30,43],older:6,omit:[1,8,9,38],omitted_jco:[8,9,37,38],omitted_par:[8,9,38],omitted_paramet:[1,8,9,38],omitted_parcov:[1,8,9,37,38],omitted_predict:[1,8,9,37,38],onc:[9,12,13,17,19,30,32,34,38,39,41,43],one:[6,7,9,11,12,13,16,19,21,23,24,26,27,28,29,30,32,34,38,39,40,42,43],onerror:[30,32],ones:[29,30],onli:[1,4,6,7,8,9,10,12,13,16,19,21,22,23,24,26,27,28,29,30,32,33,34,38,39,40,42,43],only_col:[24,42],only_row:[24,42],onlyus:[1,4,8,9,10,26,38],open:[9,11,19,21,22,23,27,30,38,42,43],oper:[7,8,9,10,12,13,14,16,19,24,28,29,30,32,38,39,40,42,43],operator_symbol:31,operator_word:31,opt:[20,41],optim:[5,9,30,37,38],optino:[27,30,43],option:[1,3,4,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],opton:[16,40],ora:[27,30,43],order:[9,12,13,19,24,29,30,38,39,42,43],ordinari:[27,30,43],ordinarykirg:[27,30,43],ordinarykrig:[27,30,38,43],org:[29,30,43],org_arr:[30,34],org_cwd:[29,30,43],org_fil:[29,30,43],org_model_w:[29,30,43],org_val:[29,30,43],orgin:[30,34,43],orient:[27,30,43],orig:[29,30,43],origin:[28,29,30,34,43],origin_loc:[29,30,38,43],original_ceil:[9,19,21,23,38,42],original_d:[30,34,43],os_util:[5,9,30,37,38],other:[7,9,10,12,13,19,21,23,27,28,29,30,34,38,39,42,43],otherwis:[9,12,13,14,19,20,21,23,24,26,29,30,38,39,41,42,43],our:6,out:[6,7,9,11,12,13,19,21,23,28,29,30,34,38,39,42,43],out_fil:[9,19,21,23,24,27,29,30,34,38,42,43],out_filenam:[28,30,43],out_pst_nam:[31,43],outer:[30,34,43],outfil:[29,30,43],outflow:[28,30,43],output:[1,4,8,9,10,16,17,19,21,23,24,26,28,29,30,32,34,38,40,41,42,43],output_fil:[24,29,30,42,43],outputdirectori:[16,40],over:[17,19,29,30,41,43],overflow:[],overload:[9,12,13,19,38,39],overrid:[7,9,29,30,38,43],overwrit:[14,30,34,38,43],own:[30,32,43],packag:[28,29,37],page:[5,16,36,40],pair:[8,9,10,17,19,21,23,24,27,28,29,30,33,34,38,41,42,43],pak:[29,30],pakattr:[29,30],panda:[2,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],par1:[7,9,19,21,23,24,38,42],par2:[9,19,21,23,24,38,42],par:[7,9,10,14,16,19,21,23,24,28,29,30,34,38,40,42,43],par_:[9,12,13,19,30,38,39],par_bounds_dict:[29,30,43],par_col:[28,29,30,43],par_en:[14,38],par_ensembl:[19,20,41],par_fil:[14,28,29,30,38,43],par_group:[9,19,21,23,29,30,37,38,42,43],par_knowledge_dict:[29,30],par_length:[9,12,13,19,37,38,39],par_nam:[9,10,12,13,19,20,21,23,24,29,30,37,38,39,41,42,43],par_name_bas:[30,34,43],par_styl:[30,34,43],par_to_file_dict:[29,30,43],par_typ:[30,34,43],parallel:[17,18,19,20,41],param:[28,30,34,43],paramerteris:[30,34,43],paramet:[1,2,3,4,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],parameter:[28,29,30,33,34,43],parameter_data:[7,9,14,19,21,23,24,37,38,42],parameter_group:[9,19,21,23,38,42],parameter_nam:[9,26,38],parameterensembl:[7,9,14,16,17,19,27,29,30,34,37,38,40,41,43],parameteris:[30,34,43],paramt:[28,30,43],paranm:[30,34],parbound:[29,30,43],parchglim:[9,19,21,23,38,42],parcov:[1,4,8,9,10,17,18,19,20,26,37,38,41],paremet:[30,34],parensembl:[14,17,18,19,37,38,41],parensmebl:[17,19,41],parent:[6,9,10,38],parentdir_prefix:6,parenthes:[30,34,43],pareto:[19,20,41],paretoobjfunc:[19,20,38,41],parfil:[7,9,19,21,23,24,38,42],parfile_nam:[7,9,19,38],parfile_rel:[30,34,38,43],pargp:[9,19,21,23,27,30,34,38,42,43],pargroup:[30,34],parlbnd:[7,9,19,21,23,38,42],parlbnd_tran:[9,19,21,23,38,42],parlist_dict:[9,26,38],parmaet:[17,18,19,20,41],parnam:[29,30,34,43],parnm:[9,19,21,23,24,29,30,33,38,42,43],parrep:[9,19,21,23,37,38,42],pars:[16,22,24,27,28,29,30,34,40,42,43],parse_dir_for_io_fil:[29,30,38,43],parse_filenam:[28,30,43],parse_ins_fil:[24,38,42],parse_kij_arg:[30,34,38,43],parse_namefil:[28,30,43],parse_tpl_fil:[24,30,38,42],parse_values_from_lin:[21,22,38,42],parser:[30,35],part:[9,12,13,17,19,21,22,23,24,26,28,30,34,38,39,41,42,43],parti:[9,19,21,23,38,42],partial:[7,9,19,38],particular:6,partran:[2,7,9,19,21,23,38,42],parubnd:[7,9,19,21,23,38,42],parubnd_tran:[9,19,21,23,38,42],parval1:[7,9,14,19,21,23,24,27,28,29,30,33,38,42,43],parval1_tran:[9,19,21,23,38,42],parval:[24,42],pass:[1,4,7,8,9,10,11,12,13,16,19,21,22,23,26,27,28,29,30,34,35,38,39,40,42,43],path:[9,14,16,17,18,19,20,21,23,24,28,29,30,32,33,34,35,38,40,41,42,43],patheic:[19,20,41],pdf:[7,9,16,19,21,23,38,40,42],pe_proj:[7,9,19,38],peak:[16,40],pearson:[9,12,13,19,29,30,38,39,43],peic:[17,19,41],peopl:[30,32,43],per:[16,24,28,29,30,40,42,43],percent:[9,26,38],percent_reduct:[9,26,38],perfect:[9,26,38],perfectli:[9,26,38],perform:[1,8,9,14,16,17,19,29,38,40,41,43],period:[9,19,21,23,28,29,30,38,42,43],peripher:[24,42],perl:[30,32],pertub:[9,19,21,23,29,30,38,42,43],pest:[0,1,2,3,4,7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,35,38,39,40,41,42,43],pest_control_fil:[16,40],pest_obj:[16,40],pest_object:[16,40],pestchek:[29,30,43],pestmod:[9,19,21,23,38,42],pestpp:[9,16,19,21,23,24,30,32,38,40,42,43],pestpp_opt:[1,4,8,9,16,26,38,40],phi:[7,9,10,16,19,21,23,24,37,38,40,42],phi_compon:[9,19,21,23,37,38,42],phi_components_norm:[9,19,21,23,37,38,42],phi_pi:[9,19,21,23,38,42],phi_progress:[16,38,40],phi_vector:[7,9,19,37,38],phimlim:[9,19,21,23,38,42],phrase:[11,19,38],physic:[30,34,43],pi_obgnm:[9,19,21,23,38,42],pictur:36,pie:[9,16,19,21,23,38,40,42],piec:6,pilbl:[9,19,21,23,38,42],pilot:[27,29,30,33,34,43],pilot_point:[30,33,43],pilot_points_to_tpl:[30,33,38,43],pilotpoint:[30,34,43],pipe:[30,32,43],place:[1,7,8,9,12,13,19,27,30,38,39,43],placehold:[24,42],platform:[30,32,43],pleas:[24,42],plot:[5,7,8,9,19,21,23,24,26,27,30,37,38,42,43],plot_col:[7,9,16,38,40],plot_hexbin:[16,40],plot_id_bar:[16,38,40],plot_jac_test:[16,38,40],plot_summary_distribut:[16,38,40],plot_util:[5,7,9,15,37,38],plt:[9,16,26,38,40],plu:[9,26,38],plus_or_dot:6,point:[4,7,8,9,16,19,21,23,24,26,27,28,29,30,33,34,36,38,40,42,43],point_cov:[27,30],point_cov_df:[27,30],point_data:[27,30,43],point_pair:[27,30],points_fil:[27,30,43],polici:[29,30,43],poor:[24,42],popul:[6,9,19,21,23,24,29,30,34,38,42,43],port:[17,18,19,20,30,32,41,43],portion:[9,19,21,23,38,42],posit:[7,9,12,13,19,21,23,38],possibl:[9,19,21,23,38,42],post:[6,9,16,26,28,29,30,34,38,40,43],post_cov:[9,26,38],post_mean:[16,40],post_py_cmd:[30,34,43],post_stdev:[16,40],postdist:6,posterior:[9,10,16,26,38,40],posterior_forecast:[9,26,37,38],posterior_paramet:[9,26,37,38],posterior_predict:[9,26,37,38],postprocess_inact:[28,30,43],potenti:[9,26,27,30,38,43],pow:[7,9],power:[9,12,13,19],pp_df:[27,30,33,43],pp_dir:[30,33,43],pp_file:[27,29,30,33,43],pp_file_to_datafram:[27,30,33,38,43],pp_filenam:[30,33,43],pp_fmt:[28,30,33],pp_geostruct:[29,30,43],pp_name:[28,30,33,43],pp_prop:[29,30,43],pp_space:[29,30,34,43],pp_tpl_file_to_datafram:[30,33,43],pp_tpl_to_datafram:[30,33,38,43],pp_util:[5,9,27,29,30,37,38],practic:[9,26,27,30,38,43],pre:[29,30,34,43],pre_py_cmd:[30,34,43],precent_reduct:[9,26,38],precis:[28,30,43],precison:[28,30,43],precondit:[8,9,38],predict:[1,4,8,9,10,26,37,38],prediction_nam:[8,9,38],predictions_it:[9,10,37,38],predunc:[4,9,26,38],prefer:[29,30,43],prefix:[6,14,24,28,29,30,32,33,34,35,38,42,43],prefix_dict:[30,33,43],prepar:[9,17,19,21,23,27,29,30,38,41,42,43],prepend:[9,19,21,23,24,28,29,30,33,34,38,42,43],prepend_path:[29,30,43],preprocess:[29,30,34,43],present:[9,19,21,23,38,42],preserv:[9,19,21,23,29,30,34,38,42,43],prevent:[7,9,19,38],previou:[17,19,28,30,41,43],previous:[27,28,30,43],primari:[3,4,8,9,12,19,21,23,26,38,39,42],primarili:[9,26,38],princip:[9,19,21,23,38,42],print:[4,9,10,19,21,23,26,30,32,38,42,43],prior:[1,4,7,8,9,10,16,17,18,19,20,21,23,24,26,29,30,34,38,40,41,42,43],prior_forecast:[9,10,37,38],prior_group:[9,19,21,23,37,38,42],prior_inform:[9,19,21,23,37,38,42],prior_mean:[16,40],prior_nam:[9,19,21,23,37,38,42],prior_paramet:[9,10,37,38],prior_predict:[9,10,37,38],prior_stdev:[16,40],privat:[8,9,10,12,13,19,21,23,24,26,27,28,30,34,38,39,42],prj:[29,30,43],probabl:[7,9,19],problem:[27,29,30,43],process:[7,9,10,19,21,23,24,26,27,28,29,30,32,33,34,38,42,43],process_:[29,30,43],process_output_fil:[9,19,21,23,24,37,38,42],processor:[28,30,43],produc:[28,29,30,43],product:[9,12,13,19,38,39],program:[16,24,40,42],progress:[7,9,11,19,38],proj4:[29,30,43],proj4_str:[29,30,38,43],proj_par:[7,9,19,38],project:[6,7,8,9,14,19,27,30,37,38,43],project_parensembl:[14,37,38],projectin:[7,9,19,38],projection_matrix:[7,9,19,38],prop:[27,30,43],propag:[9,12,13,18,19,30,38,39],proper:[29,30,43],properti:[7,8,9,10,12,13,14,19,20,21,22,23,24,26,27,29,30,34,38,39,41,42,43],proport:[9,19,21,23,29,30,38,42,43],proportional_weight:[9,19,21,23,37,38,42],protect:[21,22,42],prototyp:[5,9,37,38],provid:[3,7,9,19,21,23,27,30,34,36,38,42,43],proxi:[9,12,13,19,30,38,39],pseudo:[9,12,13,19,38,39],pseudo_inv:[9,12,13,19,37,38,39],pseudo_inv_compon:[9,12,13,19,37,38,39],pst:[1,2,4,5,7,8,9,10,12,13,14,16,17,18,19,20,26,27,29,30,31,32,34,37,38,39,40,41,43],pst_config:[24,30],pst_controldata:[5,9,19,21,23,37,38],pst_file:[9,12,13,19,30,38,39],pst_filenam:[9,19,21,23,24,29,30,38,42,43],pst_from:[5,9,30,37,38],pst_from_io_fil:[9,19,21,23,29,30,38,42,43],pst_from_parnames_obsnam:[29,30,38,43],pst_handler:[5,9,21,37,38],pst_helper:[16,38,40],pst_path:[9,19,21,23,24,29,30,34,38,42,43],pst_prior:[16,38,40],pst_rel_path:[9,19,21,23,30,32,43],pst_util:[5,9,21,30,34,37,38],pstfrom:[9,19,21,23,29,30,34,38,42,43],pstfromflopi:[27,29,30,43],pstfromflopymodel:[29,30,38,43],pt0:[27,30,43],pt1:[27,30,43],pt_color:[16,40],pt_name:[27,30],pt_zone:[27,30,43],ptname:[27,30],ptx_arrai:[27,30],pty_arrai:[27,30],purpos:[7,9,12,13,19,30,38,39],put:[16,29,30,40,43],pval:[28,30,43],pval_fil:[28,30,43],py_import:[30,34,43],pyemi:[9,12,13,19,29,30,38,39,43],pyemu:5,pyemu_warn:[5,9,37],pyemupilot:[27,30,43],pyemuwarn:[24,25,30,37,38,42],pyplot:[16,27,30,40,43],pyshp:[30,33,43],python:[0,9,19,21,23,27,28,29,30,32,34,35,38,41,42,43],qhalf:[9,10,37,38],qhalfx:[9,10,37,38],quantifi:[29,30,43],quantil:[29,30,43],quantile_idx:[29,30,43],queri:[30,34,43],quickli:[9,12,13,19,30],r_factor:[27,30,43],radian:[27,30,43],rais:[6,9,11,12,13,19,24,27,29,30,32,38,39,42,43],random:[7,9,12,13,16,17,19,27,30,38,39,40,41,43],randomli:[7,9,19,38],rang:[1,4,8,9,10,12,13,16,19,21,23,26,27,29,30,38,39,40,42,43],rank:[9,26,38],rather:[9,10,38],ratio:[8,9,12,13,14,19,27,30,38,39,43],ravel:[29,30,43],raw:[21,22,42],rc11:[29,30,43],rch:[29,30,43],reach:[9,12,13,19,28,30,38,39,43],reach_par:[28,30,43],reachdata:[28,30,43],reachinput:[28,29,30,43],read:[9,12,13,19,21,23,24,27,28,29,30,32,33,34,38,39,42,43],read_ascii:[9,12,13,19,37,38,39],read_binari:[9,12,13,19,37,38,39],read_csv:[7,9,12,13,19,38,39],read_ins_fil:[24,38,42],read_output_fil:[24,38,42],read_parfil:[24,38,42],read_pestpp_runstorag:[29,30,38,43],read_resfil:[24,38,42],read_sgems_variogram_xml:[27,30,38,43],read_struct_fil:[27,30,38,43],read_usgs_model_reference_fil:[29,30,38,43],readonli:[30,32],reads_struct_fil:[27,30,43],real:[27,29,30,34,43],real_nam:[7,9,19,38],realist:[7,9,19],realiz:[2,7,9,14,17,19,20,24,27,29,30,34,38,41,42,43],realli:[9,19,21,23,24,29,30,38,42,43],realm:[2,7,9,12,13,19,27,32,38,39,43],realzat:[7,9,19,38],reappli:[30,34,43],rebuild_pst:[30,34,43],recfil:[24,42],rech1:[9,26,38],rech2:[9,26,38],rech:[9,26,29,30,38,43],recharg:[29,30,43],recommend:[28,30,34,43],record:[24,28,30,42,43],recreat:[27,30,43],rectifi:[9,19,21,23,27,30,38,42,43],rectify_group:[9,19,21,23,38,42],rectify_pgroup:[9,19,21,23,37,38,42],rectify_pi:[9,19,21,23,37,38,42],red:[16,40],redirect:[29,30,43],redirect_forward_output:[29,30,43],reduc:[29,30,43],reduce_stack_with_risk_shift:[19,20,38,41],reduct:[9,26,38],ref:[27,30,43],ref_var:[1,4,8,9,10,26,38],refer:[1,4,8,9,10,12,13,19,26,27,29,30,33,34,38,39,43],reffil:[29,30,43],reg_data:[9,19,21,23,37,38,42],reg_default_lin:22,reg_variable_lin:22,regdata:[9,19,21,22,23,38,42],region:[27,30,43],register_vcs_handl:6,registri:[29,30,43],regular:[9,10,19,21,22,23,27,29,30,38,42,43],regularli:[30,33,43],rel:[9,19,21,23,29,30,32,38,42,43],rel_path:[30,32,43],relat:[7,9,19,28,29,30,34,38,43],releas:6,relev:[14,38],reli:[7,9,19,38],remain:[9,26,38],remov:[7,9,12,13,19,21,23,26,29,30,32,38,39,42,43],remove_exist:[29,30,34,43],remove_readonli:[30,32],render:6,render_git_describ:6,render_git_describe_long:6,render_pep440:6,render_pep440_old:6,render_pep440_post:6,render_pep440_pr:6,reorder:[9,12,13,17,19,38,39,41],rep:[27,30],repeat:[27,30,43],repeatedli:[17,19,29,30,41,43],replac:[7,9,12,13,19,21,23,24,29,30,34,37,38,39,42,43],replic:[4,9,19,21,23,26,27,30,38,42,43],repo:36,report:[9,19,21,23,29,30,38,42,43],repr:[7,9,29,30],repres:[1,4,7,8,9,10,12,13,19,21,23,26,27,29,30,34,38,39,42,43],represent:[9,12,13,19,27,30,34,38,39,43],repsect:[9,19,21,23,29,30,38,42,43],request:[6,9,16,19,21,23,29,30,38,40,42,43],requir:[9,12,13,16,19,21,23,26,28,29,30,33,34,38,39,40,42,43],requisit:[30,34,43],res:[9,19,21,23,37,38,42],res_1to1:[16,38,40],res_fil:[9,10,38],res_from_en:[24,38,42],res_from_obseravtion_data:[24,38,42],res_idx:[9,19,21,23],res_phi_pi:[16,38,40],rese:[7,9,37,38],reset:[7,9,10,12,13,14,17,19,21,23,26,27,28,29,30,38,39,41,42,43],reset_obscov:[9,10,37,38],reset_parcov:[9,10,37,38],reset_pst:[9,10,37,38],reset_x:[9,12,13,19,37,38,39],reset_zero_weight:[9,26,38],resfil:[3,9,10,19,21,23,24,38,42],resid:[9,19,21,23,24,29,30,34,38,42,43],residu:[3,9,10,16,19,21,23,24,38,40,42],resol:[9,12,13,19,38,39],resolut:[8,9,38],resolution_matrix:[9,12,13,19,38,39],resort:[24,42],resourc:[29,30,43],respec:[1,8,9,38],respect:[7,9,19,20,26,27,28,30,34,38,41,43],restart_obsensembl:[17,19,41],restrict:[30,34,43],resulat:[29,30,43],result:[7,9,12,13,16,17,19,21,23,24,26,27,28,29,30,38,39,40,41,42,43],retain:[9,12,13,19,29,30,38,39,43],return_obs_fil:[28,30,43],return_typ:[27,30,43],reuse_mast:[30,32,43],revers:[9,12,13,19],reweight:[9,19,21,23],rewrit:[30,34,43],rewritten:6,rhs:[9,19,21,23,27,30,38,42],right:[8,9,12,13,19,21,23,38,39,42],risk:[19,20,41],riv:[29,30,43],river:[29,30,43],rmse:[9,19,21,23,29,30,38,42,43],rmtree:[30,32],rnj:[29,30,43],rnj_filenam:[29,30,43],root:[6,9,10,12,13,19,29,30,32,38,39,43],rotat:[27,29,30,38,43],rotation_coef:[27,30,38,43],round:[9,19,21,23,38,42],rout:[29,30,43],routin:[28,29,30,43],row:[1,4,7,8,9,10,12,13,17,19,20,21,23,24,26,27,29,30,33,34,38,39,41,42,43],row_:[9,12,13,19,38,39],row_nam:[9,10,12,13,19,26,30,38,39],run:[7,9,14,16,17,18,19,20,21,23,24,27,28,29,30,32,34,38,40,41,42,43],run_command:6,run_subset:[18,19,41],runtim:[25,28,30,43],runtimewarn:[25,30,38],s_1:[8,9,12,13,19,38,39],same:[1,4,8,9,10,12,13,16,19,21,23,24,26,27,28,29,30,33,38,39,40,42,43],same_as_oth:[27,30,38,43],sampl:[7,9,19,35,38,43],sanity_check:[9,19,21,23,37,38,42],save:[9,11,12,13,16,19,27,28,29,30,33,34,38,39,40,43],save_coo:[12,13,38,39],save_setup_fil:[28,30,43],sbow:[16,40],scalar:[9,12,13,19,28,29,30,38,39,43],scale:[1,4,7,8,9,10,12,13,18,19,21,23,24,26,27,29,30,34,38,39,42,43],scale_offset:[1,4,8,9,10,12,13,19,26,29,30,34,38,39,43],scaling_matrix:[18,19],scatter:[16,40],scenario:6,schur:[0,9,10,12,13,19,21,23,26,30,37,38,39,41],scratch:[30,34,43],screen:[1,4,8,9,10,11,19,24,26,29,30,38,42,43],script:[28,29,30,34,43],sdt:[30,34],seamlessli:[0,9,19,38,41],search:[6,9,12,13,19,27,29,30,36,38,39,43],search_radiu:[27,30,43],second:[7,8,9,11,12,13,16,19,27,30,32,38,39,40,43],second_forecast:[8,9,37,38],second_paramet:[8,9,37,38],second_predict:[8,9,37,38],section:[9,12,13,19,21,22,23,24,30,38,39,42],see:[9,14,19,21,23,26,29,30,38,42,43],seed:[7,9,27,30,38,43],seek:[9,26,38],seem:[9,19,21,23,38,42],seen:[14,38],seg_group_dict:[28,30,43],seg_par:[28,30,43],seg_reach:[28,30,43],segement:[28,30,43],segment:[28,29,30,43],select:[16,28,29,30,34,40,43],self:[7,8,9,10,11,12,13,14,17,18,19,20,21,22,23,24,26,27,29,30,34,38,39,41,42,43],sensit:[1,4,8,9,10,26,29,30,38,43],sentiv:[9,10,38],seo:[9,10,38],sep:[24,29,30,34,42,43],separ:[9,16,19,21,23,27,28,29,30,34,38,40,42,43],sequenc:[27,29,30,43],sequenti:[9,26,30,34,38],seri:[7,9,10,14,19,20,21,22,23,24,28,30,38,41,42,43],serial:[17,18,19,20,29,30,41,43],set:[1,4,8,9,10,12,13,14,16,17,18,19,20,21,23,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],set_index:[30,34],set_r:[9,19,21,23,37,38,42],set_spatialrefer:[29,30,38,43],setattr:[9,19,21,22,23,29,30],setup:[0,9,19,21,23,28,29,30,33,34,38,41,42,43],setup_fake_forward_run:[29,30,38,43],setup_gage_ob:[28,30,38,43],setup_hds_ob:[28,30,38,43],setup_hds_timeseri:[28,30,38,43],setup_mflist_budget_ob:[28,30,38,43],setup_mtlist_budget_ob:[28,30,38,43],setup_pilot_points_grid:[29,30,43],setup_pilotpoints_grid:[30,33,38,43],setup_setup_temporal_diff_ob:[29,30,43],setup_sfr_ob:[28,30,38,43],setup_sfr_reach_ob:[28,30,38,43],setup_sfr_reach_paramet:[28,30,38,43],setup_sfr_seg_paramet:[28,30,38,43],setup_sft_ob:[28,30,38,43],setup_temporal_diff_ob:[29,30,38,43],sever:[0,9,19,22,38,41,42],sfmt:[22,24,30,38,42],sfmt_long:[22,24,38,42],sfr:[28,29,30,43],sfr_ob:[28,29,30,43],sfr_out_fil:[28,30,43],sfr_par:[29,30,43],sfr_reach_ob:[28,30,43],sfr_seg_par:[28,30,43],sfr_temporal_par:[29,30,43],sft:[28,30,43],sft_file:[28,30,43],sft_out_fil:[28,30,43],sgem:[27,30,43],shape:[9,12,13,19,27,29,30,34,37,38,39,43],shapefil:[30,33,43],shapenam:[30,33,43],share:[13,39],shell:[9,19,21,23,38,42],shift:[19,20,41],shit:[28,30,43],should:[2,7,8,9,12,13,17,18,19,20,21,23,26,27,28,29,30,32,34,35,38,39,41,42,43],shouldn:[6,7,9,19,22,38,42],show:[9,16,26,38,40],shp:[30,33,43],shutil:[30,32],side:[9,12,13,19,21,23,38,39,42],sigma:[1,4,8,9,10,12,13,19,26,30,38,39],sigma_:[8,9,38],sigma_rang:[1,4,7,8,9,10,12,13,19,21,23,26,27,29,30,34,38,39,42,43],sigma_theta:[8,9,38],silent_mast:[30,32,43],sill:[27,29,30,38,43],sim:[9,19,21,23,38,42],sim_en:[29,30,43],similar:[28,30,43],similarli:[9,19,21,23,38,42],similiar:[17,19,41],simpl:[24,29,30,32,42,43],simple_ins_from_ob:[29,30,38,43],simple_tpl_from_par:[29,30,38,43],simpli:[9,12,13,17,19,21,23,29,30,38,39,41,42,43],simul:[9,16,19,21,23,24,27,28,29,30,34,38,40,42,43],sinc:[7,9,12,13,14,19,21,22,23,28,30,33,38,39,42,43],singl:[7,9,10,12,13,16,19,21,23,26,28,29,30,32,33,34,38,39,40,42,43],singluar:[8,9,38],singular:[7,8,9,12,13,14,16,19,21,22,23,27,30,38,39,40,42,43],singular_valu:[8,9,16,38,40],site:[30,35,43],site_nam:[28,30,43],size:[16,27,29,30,32,40,43],skip:[9,17,19,21,23,27,28,29,30,34,38,41,42,43],skip_group:[16,40],skip_row:[30,34],slect:[29,30,43],slighlti:[29,30,43],slight:[9,19,21,23,38,42],slightli:[9,10,38],slow:[19,20,27,28,30,41,43],slower:[7,9,19,38],smaller:[9,12,13,19,27,30,34,38,39,43],smallest:[8,9,12,13,14,19,38,39],smoother:[7,9,17,19,27,30,38,41,43],smp:[29,30,35,43],smp_filenam:[30,35,43],smp_to_datafram:[30,35,38,43],smp_to_in:[30,35,38,43],smp_util:[5,9,30,37,38],so_:[],softwar:6,sol1:[19,20,41],sol2:[19,20,41],solut:[8,9,14,19,20,27,30,38,41,43],solv:[17,19,41],some:[7,9,12,13,17,19,21,23,24,26,29,30,38,39,41,42,43],someth:[1,4,8,9,10,11,12,13,19,21,23,26,38,42],sometim:[27,30,43],somewhat:[9,19,21,23,29,30,38,42,43],sort:[6,9,12,13,19,29,30,38,39,43],sort_by_nam:[29,30,43],sought:[1,3,4,8,9,10,19,21,23,26,38,42],sound:[29,30,43],sourc:[6,29,30,34,43],space:[2,7,8,9,14,19,21,23,27,29,30,33,34,38,41,42,43],span:[16,40],spars:[12,13,39],spatail:[27,30,43],spatial:[27,28,29,30,33,34,43],spatial_bc_prop:[29,30,43],spatial_list_geostruct:[29,30,43],spatial_list_par:[29,30,43],spatial_list_prop:[29,30,43],spatial_refer:[27,29,30,34,43],spatialrefer:[27,29,30,33,34,38,43],spawn:[29,30,43],spec2dsim:[27,30,43],special:[24,28,30,42,43],specif:[7,9,29,30,34,38,43],specifi:[9,10,16,19,21,23,29,30,34,38,40,42,43],specifif:[28,30,43],specsim2d:[27,30,38,43],spectral:[27,29,30,34,43],spectrum:[8,9,12,13,19,38,39],speed:[27,29,30,43],speedup:[9,12,13,19,38,39],spheric:[27,30,43],sphinx:5,sphvario:[27,30,38,43],split:[9,19,21,23,24,29,30,34,38,42,43],spread:[30,32,43],sqradiu:[27,30],sqrt:[9,12,13,19,37,38,39],squar:[9,10,12,13,19,21,23,29,30,38,39,42,43],squential:[30,34],srefhttp:[29,30],stack:[16,40],stand:[9,19,21,23,38,42],standalon:[30,34,43],standard:[1,4,7,8,9,10,11,12,13,16,19,21,23,26,27,28,29,30,34,38,39,40,42,43],start:[9,11,14,19,21,23,24,29,30,32,34,38,42,43],start_datatim:[28,30,43],start_datetim:[28,30,34,43],start_work:[30,32,38,43],startsiwth:[9,19,21,23,38,42],stat:[9,10,19,21,23,29,30,38,42,43],state:[9,19,21,23,38,42],statement:[11,19,37,38],statist:[9,10,19,21,23,38,42],statu:[7,9,19,29,30,38,43],std:[16,40],std_window:[16,40],stdev:[16,40],stdout:[27,29,30,32,43],step:[17,19,27,28,29,30,41,43],still:[19,20,41],stochast:[7,9,14,17,19,38,41],storag:[9,12,13,19,29,30,38,39,43],store:[7,9,12,13,16,19,24,38,39,40,42],str:[1,3,4,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],str_con:[24,38,42],straight:[29,30,43],strang:[9,19,21,23,38,42],strategi:[7,9,19,38],stream:[29,30,43],stress:[9,17,19,21,23,28,29,30,38,41,42,43],strhc1:[28,30,43],string:[6,9,12,13,16,19,21,22,23,24,27,28,29,30,33,34,35,39,40,42,43],strptime:[30,35,43],struct1:[27,30,43],struct:[27,29,30,43],struct_dict:[29,30,43],struct_fil:[27,30,43],structur:[27,29,30,43],structuredgrid:[27,30,43],strutur:[29,30,43],style:[1,4,6,7,8,9,10,12,13,19,21,22,24,26,27,28,29,30,34,35,38,39,42,43],sub:[1,8,9,12,13,19,28,30,38,39,43],subdirectori:[30,32,43],submit:[17,18,19,20,41],submit_fil:[17,18,19,20,41],submodul:37,subpackag:37,subplot:[16,40],subsequ:[9,19,21,23,29,30,38,42,43],subset:[7,9,10,19,21,23,29,30,38,42,43],subset_obsgroup:[29,30,43],subset_obsnam:[29,30,43],subset_r:[29,30,43],subspac:[14,29,30,38,43],subst:6,subtract:[9,12,13,19,30,34,43],success:[9,19,21,23,30,34,38,42,43],successfulli:[24,42],suffix:[9,19,21,23,28,29,30,32,34,38,42,43],sum:[9,19,21,23,29,30,38,42,43],summar:[8,9,26,27,29,30,33,38,43],summari:[9,19,21,23,26,38,42],suppli:[27,30,43],support:[0,3,6,9,12,13,14,19,21,23,27,28,29,30,33,34,35,38,39,41,42,43],sure:[7,9,16,19,21,22,23,29,30,34,38,40,42,43],surfac:[27,30,43],svd:[7,9,12,13,17,19,22,38,39,41,42],svd_data:[9,19,21,23,37,38,42],svddata:[9,19,21,22,23,38,42],sw_filenam:[28,30,43],sw_prefix:[28,30,43],sweep:[16,18,19,29,30,40,43],sweep_output_csv_fil:[16,40],sweep_parameter_csv_fil:[16,40],sweet:[9,19,21,23,38,42],swp:[16,40],sync_bin:[16,40],synchron:[9,19,21,23,38,42],synonym:[9,10,38],system:[17,18,19,20,29,30,32,41,43],tabl:[9,19,21,23,38,42],tabular:[30,34,43],tag:[6,28,30,43],tag_prefix:6,take:[7,9,12,13,16,19,38,40],tarbal:6,target:[9,19,21,23,27,30,43],target_phi:[9,19,21,23],targetob:[16,40],task:[29,43],tbe:[16,40],tcp:[17,18,19,20,41],teh:[9,19,21,23,38,42],tempchek:[29,30,43],templat:[9,17,18,19,20,21,23,24,28,29,30,32,33,34,38,41,42,43],template_fil:[9,19,21,23,29,30,38,42,43],tempor:[17,19,30,34,41,43],temporal_bc_prop:[29,30,43],temporal_list_geostruct:[29,30,43],temporal_list_par:[29,30,43],temporal_list_prop:[29,30,43],temporal_sfr_par:[29,30,43],temporari:[29,30,43],term:[1,4,8,9,10,12,13,19,26,30,38,39],terribl:[24,42],test:[8,9,16,26,27,29,30,32,38,40,43],tex:[9,19,21,23,38,42],text:[16,28,29,30,32,40,43],tha:[16,40],than:[6,7,9,10,12,13,16,19,21,23,26,27,28,29,30,32,33,34,38,39,40,42,43],thegreenplac:[30,32],thei:[9,10,19,21,23,29,30,38,42,43],them:[28,29,30,43],therefor:[7,9,19,29,30,34,38,43],theta:[29,30,38,43],thi:[0,1,3,4,5,6,7,8,9,10,12,13,14,16,17,19,21,22,23,24,26,27,28,29,30,32,33,34,35,36,38,39,40,41,42,43],thin:[7,8,9,12,13,19,26,28,30,38,39,43],thing:[3,9,12,13,16,19,21,23,38,39,40,42],third:[8,9,38],third_forecast:[8,9,37,38],third_paramet:[8,9,37,38],third_predict:[8,9,37,38],those:[7,9,19,30,32,38,43],thread:[29,30,43],three:[8,9,38],threshold:[14,29,30,38,43],through:[29,30,34,43],throw_ins_error:[24,38,42],throw_ins_warn:[24,38,42],throw_out_error:[24,38,42],tie_hcond:[28,30,43],tied:[7,9,19,21,23,37,38,42],tikhonov:[24,42],time:[9,11,17,19,21,23,28,29,30,38,41,42,43],time_index:[17,19,41],timedelta:[30,34,43],timeseri:[28,30,43],timestamp:[28,30,43],tmp_file:[29,30,43],to_2d:[9,12,13,19,37,38,39],to_ascii:[9,12,13,19,26,37,38,39],to_binari:[7,9,12,13,19,27,29,30,37,38,39,43],to_coo:[9,12,13,19,29,30,37,38,39,43],to_csv:[7,9,19,29,30,37,38,43],to_datafram:[9,12,13,19,37,38,39],to_datetim:[28,30,43],to_grid_factor_fil:[27,30,43],to_grid_factors_fil:[27,30,38,43],to_pearson:[9,12,13,19,30,37,38,39],to_struct_fil:[27,30,38,43],to_uncfil:[9,12,13,19,30,37,38,39],todo:[30,34,43],togeth:[28,30,43],tol:[27,30,43],toler:[9,12,13,19,21,23,27,29,30,38,39,42,43],ton:[28,30,43],too:[29,30,43],total:[9,10,19,21,23,38,42],totim:[28,30,43],tpl:[9,17,19,21,23,24,28,29,30,33,34,38,41,42,43],tpl_dir:[29,30,33,43],tpl_file:[9,19,21,23,24,28,29,30,33,38,42,43],tpl_filenam:[30,33,34,43],tpl_marker:[29,30],tpl_str:[30,33,43],tplfilenam:[29,30,43],tradtion:[29,30,43],trail:[27,30,43],transfer:[9,10,29,30,38,43],transform:[7,9,19,21,23,27,29,30,34,37,38,42,43],transgress:[17,19,41],transpar:[16,40],transport:[28,30,43],transpos:[9,12,13,19,37,38,39],trash:[9,12,13,19,38,39],travi:[30,32,43],treat:[1,2,7,8,9,16,19,21,23,26,28,29,30,33,34,38,40,42,43],tree:6,tri:[9,10,12,13,19,21,23,24,29,30,38,39,42,43],triangular:[7,9,19,38],truncat:[8,9,12,13,19,38,39],try_parse_name_metadata:[9,19,21,23,37,38,42],try_process_output_fil:[24,30,38,42],try_process_output_pst:[24,38,42],tupl:[9,12,13,16,19,21,23,28,29,30,34,38,39,40,42,43],two:[6,9,12,13,16,17,19,27,28,29,30,38,39,40,41,43],txt:[29,30,43],typ:[7,9,30,34,38],type:[7,8,9,10,12,13,14,16,17,18,19,20,21,22,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],typic:[16,40],u2d:[29,30],u_1:[8,9,38],ubnd:[7,9,19,37,38],ubound:[30,34,43],ucn:[28,30,43],ucnfil:[28,30,43],uint8:[38,39],ult_lbound:[30,34,43],ult_ubound:[30,34,43],ultim:[9,16,19,21,23,29,30,34,38,40,42,43],unabl:6,unari:[16,40],unc:[1,4,8,9,10,12,13,19,26,30,38,39],unc_fil:[9,12,13,19,30,38,39],uncert:[9,10],uncertainti:[0,1,4,8,9,10,12,13,19,20,26,27,29,30,38,39,41,43],uncfil:[29,30,43],uncondit:[6,27,30,43],undefin:[9,12,13,19,30,38,39,43],under:[1,4,8,9,20,26,38,41],underli:[7,9],underscor:[9,19,21,23,38,42],undertak:[27,30,43],unformat:[28,30,43],uniform:[7,9,14,19,29,30,38,43],union:[16,40],uniqu:[9,16,19,21,23,27,28,29,30,34,38,40,42,43],unit:[28,29,30,38,43],unknown:[27,30,43],unless:[1,4,8,9,19,21,23,26,27,30,32,38,42,43],unpack:6,unqiue_onli:[16,40],unstabl:[27,30,43],untag:6,until:[9,10,38],untransform:[7,9,19,27,30,38,43],updat:[7,9,17,18,19,20,21,23,26,30,34,38,41,42,43],upgrad:[14,38],upper:[1,4,7,8,9,10,12,13,19,21,23,26,29,30,34,38,39,42,43],upper_bound:[29,30,34,43],upper_lim:[27,30,43],uppermost:[30,32,43],upw:[29,30,43],use:[7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,32,33,34,35,38,39,40,41,42,43],use_approx:[18,19,41],use_approx_prior:[18,19,20,41],use_col:[29,30,34,43],use_generic_nam:[30,35,43],use_ibound_zon:[30,33,43],use_pp_zon:[29,30,34,43],use_pst_path:[9,19,21,23],use_row:[30,34,43],use_specsim:[29,30,34,43],used:[0,1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],useful:[9,14,16,17,19,21,23,28,29,30,32,34,38,40,41,42,43],usefulness:[9,19,21,23,38,42],user:[7,9,12,13,17,19,21,22,23,24,26,29,30,32,34,38,39,41,42,43],uses:[7,9,12,13,19,21,23,24,29,30,32,34,38,39,42,43],usg:[28,29,30,43],using:[1,4,7,8,9,10,12,13,16,17,18,19,20,21,22,23,24,26,27,28,29,30,32,34,38,39,40,41,42,43],usual:[9,10,16,19,21,23,27,30,32,38,40,43],usum:[16,40],utiil:[27,30,43],util2d:[29,30],util:[4,5,9,10,19,21,23,26,37,38,42],v2_proj:[14,38],v2v2:[8,9,14,38],v_1:[8,9,12,13,19,38,39],val:[7,9,19,21,23,29,30,34,38,42,43],valid:[6,7,9,19,30,34,38,43],valu:[2,7,8,9,10,12,13,14,16,19,20,21,22,23,24,26,27,28,29,30,33,34,35,38,39,40,41,42,43],value_col:[30,35,43],value_format:[30,35,43],var1:[27,30,43],var_filenam:[27,30,43],var_mult:[9,12,13,19,30,38,39],varainc:[9,12,13,19,30,38,39],vari:[1,4,8,9,10,16,26,28,29,30,34,38,40,43],variabl:[7,9,19,21,22,23,28,29,30,38,42,43],varianc:[1,4,8,9,10,12,13,19,26,27,29,30,34,38,39,43],variance_at:[8,9,37,38],variat:[27,30,43],vario2d:[27,30,38,43],variogram:[27,30,38,43],variou:[9,15,16,19,21,23,24,36,38,40,42],vcs:6,vec:[9,10],vector:[1,4,7,8,9,10,12,13,17,19,26,27,29,30,38,39,41,43],verbos:[1,4,6,8,9,10,17,18,19,20,24,26,27,29,30,32,38,41,42,43],veri:[9,10,12,13,19,29,30,38,43],version:[6,9,19,21,23,29,30,34,38,42,43],versioneerconfig:6,versionfile_ab:6,versions_from_parentdir:6,vertic:[16,29,30,38,40,43],via:[29,30,43],view:[16,40],violat:[7,9,19,21,23,38,42],vka:[29,30,43],vol:[28,30,43],vol_fil:[28,30,43],vol_filenam:[28,30,43],volum:[28,29,30,43],wai:[7,9,19,24,26,38,42],wait:[17,19,41],want:[1,4,8,9,26,29,30,32,38,43],warn:[11,16,19,24,25,27,29,30,34,37,38,40,42,43],water:[28,29,30,43],wave:[27,30,43],weigh:[9,26,38],weight:[1,4,7,8,9,10,12,13,14,16,17,18,19,20,21,23,24,26,27,29,30,38,39,40,41,42,43],wel1:[1,8,9,28,30,38,43],wel2:[1,8,9,38],wel:[29,30,43],well:[3,9,17,19,21,23,29,30,38,41,42,43],were:[6,9,19,21,23,28,30,34,38,42,43],what:[9,19,21,23,24,38,42],whatev:[16,40],when:[1,4,8,9,10,24,26,28,29,30,34,38,42,43],where:[7,9,14,19,21,23,24,26,27,28,29,30,32,34,38,42,43],whether:[7,9,27,29,30,32,38,43],which:[7,9,10,12,13,16,19,20,21,23,27,28,29,30,34,35,38,39,40,41,42,43],whistl:[30,34,43],whitespac:[24,30,35,43],who:[9,12,13,19,30,32,38,39,43],whole:[29,30,43],wholesal:[29,30,43],why:[16,40],wick:[29,30,43],width:[7,9,19,22,29,30,38,42,43],wildass_guess_par_bounds_dict:[29,30,43],wilei:[27,30,43],window:[16,17,19,30,32,40,41],wise:[8,9,10,12,13,19,38,39],with_metadata:[29,30,43],within:[16,29,30,32,34,40,43],without:[16,24,29,30,34,40,42,43],wmax:[9,19,21,23,38,42],work:[0,9,12,13,16,19,21,22,23,28,29,30,38,39,40,41,42,43],worker:[17,18,19,20,27,30,32,41,43],worker_dir:[17,18,19,20,30,32,41,43],worker_root:[30,32,43],workspac:[28,30,43],world:[29,30,34,43],worri:[17,19,41],worth:[0,4,9,19,21,23,26,38],would:[9,16,26,29,30,32,33,34,38,40,43],wrap:[30,43],wrapper:[7,8,9,12,13,19,26,27,28,30,34,38,39,43],write:[3,7,9,11,12,13,14,19,21,22,23,24,27,28,29,30,33,34,35,37,38,39,42,43],write_array_tpl:[30,34,38,43],write_const_tpl:[29,30,38,43],write_forward_run:[29,30,34,38,43],write_grid_tpl:[29,30,38,43],write_gridspec:[29,30,38,43],write_hfb_templ:[28,29,30,38,43],write_hfb_zone_multipliers_templ:[28,30,38,43],write_input_fil:[9,19,21,23,24,37,38,42],write_keyword:[21,22,38,42],write_list_tpl:[30,34,38,43],write_obs_summary_t:[9,19,21,23,37,38,42],write_par_summary_t:[9,19,21,23,37,38,42],write_parfil:[24,38,42],write_pp_fil:[30,33,38,43],write_pp_shapfil:[30,33,38,43],write_pst:[14,37,38],write_to_templ:[24,38,42],write_zone_tpl:[29,30,38,43],writen:[30,34,43],written:[1,4,8,9,10,11,12,13,19,21,23,24,26,28,29,30,33,38,39,42,43],writtendefault:[9,19,21,23,38,42],wrt:[13,39],www:[29,30,43],x_a:[27,30,43],x_idx:[27,30,43],xcenter:[29,30,38,43],xcentergrid:[29,30,38,43],xedg:[29,30,38,43],xgrid:[29,30,38,43],xlim:[16,40],xll:[29,30,38,43],xmax:[29,30,43],xmin:[29,30,43],xml:[27,30,43],xml_file:[27,30,43],xorigin:[29,30,43],xother:[27,30,43],xtqt:[8,9,38],xtqx:[8,9,10,14,37,38],xul:[29,30,38,43],xy_in_idx:[30,34,43],y_idx:[27,30,43],y_z:[27,30,43],ycenter:[29,30,38,43],ycentergrid:[29,30,38,43],yeah:[9,19,21,23,38,42],year:[29,30,43],yedg:[29,30,38,43],yet:[9,10,19,21,23,26,30,34,38,42,43],ygrid:[29,30,38,43],yield:[7,9,12,13,19,26,27,28,30,38,39,43],yll:[29,30,38,43],ymax:[29,30,43],ymin:[29,30,43],yorigin:[29,30,43],yother:[27,30,43],you:[1,4,6,8,9,17,19,21,23,26,29,30,32,35,38,41,42,43],your:[29,30,32,43],yuck:[28,29,30,43],yul:[29,30,38,43],yyi:[30,35,43],yyyi:[30,35,43],zero2d:[9,12,13,19,37,38,39],zero:[7,8,9,10,12,13,16,19,21,23,24,26,28,29,30,32,33,34,37,38,39,40,42,43],zero_bas:[30,34,43],zero_order_tikhonov:[29,30,38,43],zero_weight_obs_nam:[9,19,21,23,37,38,42],zn_arrai:[29,30,43],zone:[27,28,29,30,33,34,43],zone_arrai:[27,30,34,43],zone_fil:[27,30,43],zone_prop:[29,30,43],zval:[30,34]},titles:["pyemu","pyemu.ErrVar","pyemu.ParameterEnsemble","pyemu.Pst","pyemu.Schur","API Reference","pyemu._version","pyemu.en","pyemu.ev","pyemu","pyemu.la","pyemu.logger","pyemu.mat","pyemu.mat.mat_handler","pyemu.mc","pyemu.plot","pyemu.plot.plot_utils","pyemu.prototypes.da","pyemu.prototypes.ensemble_method","pyemu.prototypes","pyemu.prototypes.moouu","pyemu.pst","pyemu.pst.pst_controldata","pyemu.pst.pst_handler","pyemu.pst.pst_utils","pyemu.pyemu_warnings","pyemu.sc","pyemu.utils.geostats","pyemu.utils.gw_utils","pyemu.utils.helpers","pyemu.utils","pyemu.utils.optimization","pyemu.utils.os_utils","pyemu.utils.pp_utils","pyemu.utils.pst_from","pyemu.utils.smp_utils","Welcome to pyEMU\u2019s documentation!","pyemu","pyemu package","pyemu.mat package","pyemu.plot package","pyemu.prototypes package","pyemu.pst package","pyemu.utils package"],titleterms:{"class":[6,7,8,9,10,11,12,13,14,17,18,19,20,21,22,23,24,26,27,29,30,34],"function":[6,12,13,16,24,27,28,29,30,31,32,33,34,35],_version:6,api:5,content:[6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,38,39,40,41,42,43],document:36,ensemble_method:[18,41],errvar:1,geostat:[27,43],gw_util:[28,43],helper:[29,43],indic:36,logger:[11,38],look:36,mat:[12,13,39],mat_handl:[13,39],modul:[6,7,8,10,11,13,14,16,17,18,20,22,23,24,25,26,27,28,29,31,32,33,34,35,38,39,40,41,42,43],moouu:[20,41],nice:36,optim:[31,43],order:36,os_util:[32,43],packag:[9,12,19,21,30,38,39,40,41,42,43],parameterensembl:2,plot:[15,16,40],plot_util:[16,40],pp_util:[33,43],prototyp:[17,18,19,20,41],pst:[3,21,22,23,24,42],pst_controldata:[22,42],pst_from:[34,43],pst_handler:[23,42],pst_util:[24,42],pyemu:[0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],pyemu_warn:[25,38],refer:5,schur:4,smp_util:[35,43],submodul:[9,12,15,19,21,30,38,39,40,41,42,43],subpackag:[9,38],tabl:36,util:[27,28,29,30,31,32,33,34,35,43],welcom:36}}) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 61b7c052a..bc844d39b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,11 @@ This documentation is more-or-less complete. However, the example notebooks in provide a more complete picture of how the various components of pyEMU function. +Nice Looking and Ordered Documentation +====================================== + +Main entry point to `pyEMU Documentation `_. + Indices and tables ================== diff --git a/pyemu/utils/pst_from.py b/pyemu/utils/pst_from.py index a9dc8bcd4..d579d3c43 100644 --- a/pyemu/utils/pst_from.py +++ b/pyemu/utils/pst_from.py @@ -48,7 +48,7 @@ def _check_var_len(var, n, fill=None): # noinspection PyProtectedMember class PstFrom(object): - """ + """construct high-dimensional PEST(++) interfaces with all the bells and whistles Args: original_d (`str`): the path to a complete set of model input and output files From 804d510fa638707ecf45ded331eb016cc538f3ec Mon Sep 17 00:00:00 2001 From: White Date: Wed, 16 Sep 2020 14:28:03 -0600 Subject: [PATCH 5/5] fix for direct par pstfroj test --- autotest/pst_from_tests.py | 15 ++++++++------- pyemu/utils/pst_from.py | 8 +++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/autotest/pst_from_tests.py b/autotest/pst_from_tests.py index 79d33416a..712251cd6 100644 --- a/autotest/pst_from_tests.py +++ b/autotest/pst_from_tests.py @@ -1203,7 +1203,7 @@ def mf6_freyberg_direct_test(): df = pd.read_csv(os.path.join(tmp_model_ws, "sfr.csv"), index_col=0) pf.add_observations("sfr.csv", insfile="sfr.csv.ins", index_cols="time", - use_cols=["gage_1","headwaters","tailwaters"],ofile_sep=",") + use_cols=["GAGE_1","HEADWATER","TAILWATER"],ofile_sep=",") v = pyemu.geostats.ExpVario(contribution=1.0, a=1000) gr_gs = pyemu.geostats.GeoStruct(variograms=v,transform="log") rch_temporal_gs = pyemu.geostats.GeoStruct(variograms=pyemu.geostats.ExpVario(contribution=1.0, a=60)) @@ -1221,7 +1221,7 @@ def mf6_freyberg_direct_test(): recharge_files = ["recharge_1.txt","recharge_2.txt","recharge_3.txt"] pf.add_parameters(filenames=arr_file, par_type="grid", par_name_base="rch_gr", pargp="rch_gr", zone_array=ib, upper_bound=1.0e-3, lower_bound=1.0e-7, - geostruct=gr_gs,par_style="direct") + par_style="direct") for arr_file in arr_files: kper = int(arr_file.split('.')[1].split('_')[-1]) - 1 @@ -1245,7 +1245,7 @@ def mf6_freyberg_direct_test(): kper = int(list_file.split(".")[1].split('_')[-1]) - 1 #add spatially constant, but temporally correlated wel flux pars pf.add_parameters(filenames=list_file, par_type="constant", par_name_base="twel_mlt_{0}".format(kper), - pargp="twel_mlt".format(kper), index_cols=[0, 1, 2], use_cols=[3], + pargp="twel_mlt_{0}".format(kper), index_cols=[0, 1, 2], use_cols=[3], upper_bound=1.5, lower_bound=0.5, datetime=dts[kper], geostruct=rch_temporal_gs) # add temporally indep, but spatially correlated wel flux pars @@ -1268,6 +1268,8 @@ def mf6_freyberg_direct_test(): # build pest pst = pf.build_pst('freyberg.pst') + cov = pf.build_prior(fmt="non") + cov.to_coo("prior.jcb") pst.try_parse_name_metadata() df = pd.read_csv(os.path.join(tmp_model_ws, "heads.csv"), index_col=0) pf.add_observations("heads.csv", insfile="heads.csv.ins", index_cols="time", use_cols=list(df.columns.values), @@ -1289,8 +1291,7 @@ def mf6_freyberg_direct_test(): pe.to_binary(os.path.join(template_ws, "prior.jcb")) assert pe.shape[1] == pst.npar_adj, "{0} vs {1}".format(pe.shape[0], pst.npar_adj) assert pe.shape[0] == num_reals - cov = pf.buid_prior() - cov.to_uncfile("prior.unc") + pst.control_data.noptmax = 0 pst.pestpp_options["additional_ins_delimiters"] = "," @@ -1465,5 +1466,5 @@ def mf6_freyberg_varying_idomain(): #mf6_freyberg_test() #mf6_freyberg_shortnames_test() # mf6_freyberg_da_test() - # mf6_freyberg_direct_test() - mf6_freyberg_varying_idomain() + mf6_freyberg_direct_test() + #mf6_freyberg_varying_idomain() diff --git a/pyemu/utils/pst_from.py b/pyemu/utils/pst_from.py index d579d3c43..8b2472d48 100644 --- a/pyemu/utils/pst_from.py +++ b/pyemu/utils/pst_from.py @@ -46,7 +46,6 @@ def _check_var_len(var, n, fill=None): return var -# noinspection PyProtectedMember class PstFrom(object): """construct high-dimensional PEST(++) interfaces with all the bells and whistles @@ -62,7 +61,6 @@ class PstFrom(object): """ - # TODO: doc strings!!! def __init__( self, original_d, @@ -134,6 +132,7 @@ def __init__( self.ult_ubound_fill = 1.0e30 self.ult_lbound_fill = -1.0e30 + @property def parfile_relations(self): if isinstance(self._parfile_relations, list): @@ -267,6 +266,7 @@ def initialize_spatial_reference(self): ) self.spatial_reference = self._spatial_reference + def write_forward_run(self): """write the forward run script @@ -341,9 +341,7 @@ def _pivot_par_struct_dict(self): struct_dict[gs] = par_dfs return struct_dict - def build_prior( - self, fmt="ascii", filename=None, droptol=None, chunk=None, sigma_range=6 - ): + def build_prior(self, fmt="ascii", filename=None, droptol=None, chunk=None, sigma_range=6): """Build the prior parameter covariance matrix Args: