From 72f48ae97ee785861b3e514046e291454e1a00a1 Mon Sep 17 00:00:00 2001 From: domfournier Date: Thu, 19 Sep 2024 12:16:37 -0400 Subject: [PATCH 01/12] Add scale misfit multipliers --- simpeg/directives/__init__.py | 1 + simpeg/directives/directives.py | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/simpeg/directives/__init__.py b/simpeg/directives/__init__.py index b0d750d869..9157896b38 100644 --- a/simpeg/directives/__init__.py +++ b/simpeg/directives/__init__.py @@ -120,6 +120,7 @@ VectorInversion, SaveIterationsGeoH5, ProjectSphericalBounds, + ScaleMisfitMultipliers ) from .pgi_directives import ( diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index 8b47d36d29..1d465c981c 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3426,3 +3426,57 @@ def endIter(self): for directive in directiveList: if not isinstance(directive, SaveIterationsGeoH5): directive.endIter() + + +class ScaleMisfitMultipliers(InversionDirective): + """ + Scale the misfits by a factor. + """ + + def __init__(self, chifact_target, out_group: SimPEGGroup, **kwargs): + self.last_beta = None + self.chifact_target = chifact_target + self.out_group = out_group + + dirpath = Path(self.out_group.workspace.h5file).parent + self.filepath = dirpath / "ChiFactors.log" + + super().__init__(**kwargs) + + def initialize(self): + self.last_beta = self.invProb.beta + self.multipliers = self.invProb.dmisfit.multipliers + + with open(self.filepath, "w", encoding="utf-8") as f: + f.write( + "Iterations\t" + '\t'.join(objfct.name for objfct in self.invProb.dmisfit.objfcts) + ) + f.write("\n") + + + def endIter(self): + ratio = self.invProb.beta / self.last_beta + chi_factors = [] + phi_ds = [] + for (mult, objfct), pred in zip(self.invProb.dmisfit, self.invProb.dpred): + residual = objfct.W * (objfct.data.dobs - pred) + phi_d = mult * np.vdot(residual, residual) + chi_factors.append(phi_d / objfct.nD) + phi_ds.append(phi_d) + + phi_ds = np.asarray(phi_ds) + chi_factors = np.asarray(chi_factors) + # ratios = np.ones_like(chi_factors) * np.min([1, ratio]) + # scalings = np.max(np.c_[ratios, chi_factors/chi_factors.max()], axis=1) + scalings = chi_factors/chi_factors.min() + multipliers = scalings + # self.invProb.beta *= (phi_ds * scalings).sum() / phi_ds.sum() + + with open(self.filepath, "a", encoding="utf-8") as f: + f.write( + f"{self.opt.iter}\t" + '\t'.join("%.3f" % chi for chi in chi_factors) + "\n" + ) + + self.invProb.dmisfit.multipliers = multipliers.tolist() + self.last_beta = self.invProb.beta + From aee9567d1640c23bf9496a17670b7118fb13bfc1 Mon Sep 17 00:00:00 2001 From: domfournier Date: Thu, 19 Sep 2024 12:17:18 -0400 Subject: [PATCH 02/12] Remove legacy 0.5 multipliers --- simpeg/dask/data_misfit.py | 2 +- simpeg/dask/inverse_problem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simpeg/dask/data_misfit.py b/simpeg/dask/data_misfit.py index 6ac67a6031..f01d646248 100644 --- a/simpeg/dask/data_misfit.py +++ b/simpeg/dask/data_misfit.py @@ -14,7 +14,7 @@ def dask_call(self, m, f=None): Distributed :obj:`simpeg.data_misfit.L2DataMisfit.__call__` """ R = self.W * self.residual(m, f=f) - phi_d = 0.5 * da.dot(R, R) + phi_d = da.dot(R, R) if not isinstance(phi_d, np.ndarray): return compute(self, phi_d) return phi_d diff --git a/simpeg/dask/inverse_problem.py b/simpeg/dask/inverse_problem.py index e62b856971..f1bd70f725 100644 --- a/simpeg/dask/inverse_problem.py +++ b/simpeg/dask/inverse_problem.py @@ -139,7 +139,7 @@ def dask_evalFunction(self, m, return_g=True, return_H=True): phi_d = 0 for (mult, objfct), pred in zip(self.dmisfit, self.dpred): residual = objfct.W * (objfct.data.dobs - pred) - phi_d += 0.5 * mult * np.vdot(residual, residual) + phi_d += mult * np.vdot(residual, residual) phi_d = np.asarray(phi_d) # print(self.dpred[0]) From 1e8495cba21dda30f2fdf0836adccebd348176c9 Mon Sep 17 00:00:00 2001 From: domfournier Date: Thu, 19 Sep 2024 12:17:46 -0400 Subject: [PATCH 03/12] Create alias to _G to avoid re-computing --- simpeg/dask/simulation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simpeg/dask/simulation.py b/simpeg/dask/simulation.py index 3c78f9e98e..2e94bfcc4a 100644 --- a/simpeg/dask/simulation.py +++ b/simpeg/dask/simulation.py @@ -172,6 +172,7 @@ def Jmatrix(self): if getattr(self, "_Jmatrix", None) is None: if self.workers is None: self._Jmatrix = self.compute_J() + self._G = self._Jmatrix else: client = get_client() # Assumes a Client already exists From 9ea7edb36b283e104be7289662aac05e48518902 Mon Sep 17 00:00:00 2001 From: domfournier Date: Thu, 19 Sep 2024 13:12:57 -0400 Subject: [PATCH 04/12] Another attempt --- simpeg/directives/directives.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index 1d465c981c..e82f48932e 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3455,12 +3455,12 @@ def initialize(self): def endIter(self): - ratio = self.invProb.beta / self.last_beta + # ratio = self.invProb.beta / self.last_beta chi_factors = [] phi_ds = [] for (mult, objfct), pred in zip(self.invProb.dmisfit, self.invProb.dpred): residual = objfct.W * (objfct.data.dobs - pred) - phi_d = mult * np.vdot(residual, residual) + phi_d = np.vdot(residual, residual) chi_factors.append(phi_d / objfct.nD) phi_ds.append(phi_d) @@ -3468,9 +3468,9 @@ def endIter(self): chi_factors = np.asarray(chi_factors) # ratios = np.ones_like(chi_factors) * np.min([1, ratio]) # scalings = np.max(np.c_[ratios, chi_factors/chi_factors.max()], axis=1) - scalings = chi_factors/chi_factors.min() - multipliers = scalings - # self.invProb.beta *= (phi_ds * scalings).sum() / phi_ds.sum() + scalings = chi_factors/chi_factors.max() + multipliers = self.invProb.dmisfit.multipliers * scalings * phi_ds.sum() / (phi_ds * scalings).sum() + with open(self.filepath, "a", encoding="utf-8") as f: f.write( From acd8f4182919f3779b0a1c2823f280a5b9e539ff Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 10:48:03 -0400 Subject: [PATCH 05/12] Add rescaling based on total phid --- simpeg/directives/directives.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index e82f48932e..b7b8050670 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3449,16 +3449,15 @@ def initialize(self): with open(self.filepath, "w", encoding="utf-8") as f: f.write( - "Iterations\t" + '\t'.join(objfct.name for objfct in self.invProb.dmisfit.objfcts) + "Iterations\t" + '\t'.join(f"[{objfct.name}]" for objfct in self.invProb.dmisfit.objfcts) ) f.write("\n") - def endIter(self): - # ratio = self.invProb.beta / self.last_beta + ratio = self.invProb.beta / self.last_beta chi_factors = [] phi_ds = [] - for (mult, objfct), pred in zip(self.invProb.dmisfit, self.invProb.dpred): + for objfct, pred in zip(self.invProb.dmisfit.objfcts, self.invProb.dpred): residual = objfct.W * (objfct.data.dobs - pred) phi_d = np.vdot(residual, residual) chi_factors.append(phi_d / objfct.nD) @@ -3466,15 +3465,17 @@ def endIter(self): phi_ds = np.asarray(phi_ds) chi_factors = np.asarray(chi_factors) - # ratios = np.ones_like(chi_factors) * np.min([1, ratio]) - # scalings = np.max(np.c_[ratios, chi_factors/chi_factors.max()], axis=1) scalings = chi_factors/chi_factors.max() - multipliers = self.invProb.dmisfit.multipliers * scalings * phi_ds.sum() / (phi_ds * scalings).sum() + # Force beta ratio scaling if below target + scalings[chi_factors < 1] *= ratio + + # Normalize total phi_d with scalings + multipliers = self.multipliers * scalings * phi_ds.sum() / (self.multipliers * phi_ds * scalings).sum() with open(self.filepath, "a", encoding="utf-8") as f: f.write( - f"{self.opt.iter}\t" + '\t'.join("%.3f" % chi for chi in chi_factors) + "\n" + f"{self.opt.iter}\t" + '\t'.join(f"{multi:.2e}*{chi:.2e}" for multi, chi in zip(multipliers, chi_factors)) + "\n" ) self.invProb.dmisfit.multipliers = multipliers.tolist() From 196b271869ea794b8dd1c4d3a46312d5998bbaff Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:23:56 -0400 Subject: [PATCH 06/12] Update simpeg/directives/directives.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/directives.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index b7b8050670..322723eb0f 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3449,7 +3449,10 @@ def initialize(self): with open(self.filepath, "w", encoding="utf-8") as f: f.write( - "Iterations\t" + '\t'.join(f"[{objfct.name}]" for objfct in self.invProb.dmisfit.objfcts) + "Iterations\t" + + "\t".join( + f"[{objfct.name}]" for objfct in self.invProb.dmisfit.objfcts + ) ) f.write("\n") From ce14f318fbabec1df4dfa4383be09ad8b21d43a8 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:24:02 -0400 Subject: [PATCH 07/12] Update simpeg/directives/directives.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/directives.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index 322723eb0f..6e94918a7f 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3474,7 +3474,12 @@ def endIter(self): scalings[chi_factors < 1] *= ratio # Normalize total phi_d with scalings - multipliers = self.multipliers * scalings * phi_ds.sum() / (self.multipliers * phi_ds * scalings).sum() + multipliers = ( + self.multipliers + * scalings + * phi_ds.sum() + / (self.multipliers * phi_ds * scalings).sum() + ) with open(self.filepath, "a", encoding="utf-8") as f: f.write( From 06702102334fc6f1ffc9db83b57e89a1816189c3 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:24:08 -0400 Subject: [PATCH 08/12] Update simpeg/directives/directives.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/directives.py | 1 - 1 file changed, 1 deletion(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index 6e94918a7f..a6bd6fdf46 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3488,4 +3488,3 @@ def endIter(self): self.invProb.dmisfit.multipliers = multipliers.tolist() self.last_beta = self.invProb.beta - From 111d61985a2f431dcb81dbea5c005307dcaea49e Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:24:18 -0400 Subject: [PATCH 09/12] Update simpeg/directives/directives.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/directives.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index a6bd6fdf46..c67b93737d 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3483,7 +3483,12 @@ def endIter(self): with open(self.filepath, "a", encoding="utf-8") as f: f.write( - f"{self.opt.iter}\t" + '\t'.join(f"{multi:.2e}*{chi:.2e}" for multi, chi in zip(multipliers, chi_factors)) + "\n" + f"{self.opt.iter}\t" + + "\t".join( + f"{multi:.2e}*{chi:.2e}" + for multi, chi in zip(multipliers, chi_factors) + ) + + "\n" ) self.invProb.dmisfit.multipliers = multipliers.tolist() From c3cc8c0104333a76adfd921b0e970de36b18e775 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:24:24 -0400 Subject: [PATCH 10/12] Update simpeg/directives/directives.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/directives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index c67b93737d..e8e949488d 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3468,7 +3468,7 @@ def endIter(self): phi_ds = np.asarray(phi_ds) chi_factors = np.asarray(chi_factors) - scalings = chi_factors/chi_factors.max() + scalings = chi_factors / chi_factors.max() # Force beta ratio scaling if below target scalings[chi_factors < 1] *= ratio From 283a3946bf39d3d8cea91ad03ad859aff4d40423 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 12:24:30 -0400 Subject: [PATCH 11/12] Update simpeg/directives/__init__.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- simpeg/directives/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpeg/directives/__init__.py b/simpeg/directives/__init__.py index 9157896b38..45c97db0f4 100644 --- a/simpeg/directives/__init__.py +++ b/simpeg/directives/__init__.py @@ -120,7 +120,7 @@ VectorInversion, SaveIterationsGeoH5, ProjectSphericalBounds, - ScaleMisfitMultipliers + ScaleMisfitMultipliers, ) from .pgi_directives import ( From 93a2f24de66302b67f73fd01f036bc06d7fea3c0 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 23 Sep 2024 13:50:13 -0400 Subject: [PATCH 12/12] Beef up docstring. Aff ChiFactors to list of saved files --- simpeg/directives/directives.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/simpeg/directives/directives.py b/simpeg/directives/directives.py index b7b8050670..5f74a99114 100644 --- a/simpeg/directives/directives.py +++ b/simpeg/directives/directives.py @@ -3169,7 +3169,7 @@ def save_log(self): with fetch_active_workspace(self._geoh5, mode="r+") as w_s: h5_object = w_s.get_entity(self.h5_object)[0] - for file in ["SimPEG.out", "SimPEG.log"]: + for file in ["SimPEG.out", "SimPEG.log", "ChiFactors.log"]: filepath = dirpath / file if not filepath.is_file(): @@ -3430,16 +3430,26 @@ def endIter(self): class ScaleMisfitMultipliers(InversionDirective): """ - Scale the misfits by a factor. + Scale the misfits by the relative chi-factors of multiple misfit functions. + + The goal is to reduce the relative influence of the misfit functions with + lowest chi-factors so that all functions reach a similar level of fit at + convergence to the global target. + + Parameters + ---------- + + path : str + Path to save the chi-factors log file. """ - def __init__(self, chifact_target, out_group: SimPEGGroup, **kwargs): + def __init__(self, path: Path | None, **kwargs): self.last_beta = None - self.chifact_target = chifact_target - self.out_group = out_group - dirpath = Path(self.out_group.workspace.h5file).parent - self.filepath = dirpath / "ChiFactors.log" + if path is None: + path = Path() + + self.filepath = path / "ChiFactors.log" super().__init__(**kwargs)