diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ccd7a2..3f435387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. The format - Add `newtonrhapson.callback(dx, x, iteration, xnorm, fnorm, success)` to provide a callback after each completed Newton iteration. This is available as `Job.evaluate(callback=newton_callback)`, whereas a callback after each completed substep has to be provided in `Job(callback=substep_callback)`. - Add a flag to plot MPC and point load either on the deformed (default) or undeformed mesh-points, `MultiPointConstraint.plot(..., deformed=True)`, `MultiPointContact.plot(..., deformed=True)`, `PointLoad.plot(..., deformed=True)`. - Add `math.svd()` for the singular value decomposition of arrays with trailing axes. +- Add `newtonrhapson(..., progress_bar=None)` which re-uses an existing tqdm progress bar. If provided, this progress bar must be manually closed. If None and `verbose` is True, a new progress bar is created. ### Changed - Allow field containers to be included in the list of `fields` in `FieldContainer(fields)`. If a field container is included, its sub-fields are unpacked. diff --git a/docs/tutorial/examples/extut02_job.py b/docs/tutorial/examples/extut02_job.py index 7ce20c38..475ff497 100644 --- a/docs/tutorial/examples/extut02_job.py +++ b/docs/tutorial/examples/extut02_job.py @@ -50,7 +50,7 @@ # :class:`~felupe.CharacteristicCurve`-job logs the displacement and sum of reaction # forces on a given boundary condition. job = fem.CharacteristicCurve(steps=[uniaxial], boundary=boundaries["move"]) -job.evaluate(filename="result.xdmf", verbose=True) +job.evaluate(filename="result.xdmf") field.plot("Principal Values of Logarithmic Strain").show() diff --git a/docs/tutorial/examples/extut03_multiple-solids.py b/docs/tutorial/examples/extut03_multiple-solids.py index ea2f2579..78e09cad 100644 --- a/docs/tutorial/examples/extut03_multiple-solids.py +++ b/docs/tutorial/examples/extut03_multiple-solids.py @@ -52,7 +52,7 @@ # This step is now added to a :class:`~felupe.Job`. The top-level field ``x0`` is passed # to :meth:`~felupe.Job.evaluate`. job = fem.Job(steps=[uniaxial]) -job.evaluate(x0=x0, verbose=True) +job.evaluate(x0=x0) plotter = solid_1.plot(style="wireframe") plotter = solid_3.plot(style="wireframe", plotter=plotter) diff --git a/src/felupe/mechanics/_job.py b/src/felupe/mechanics/_job.py index 593b61f1..0926d737 100644 --- a/src/felupe/mechanics/_job.py +++ b/src/felupe/mechanics/_job.py @@ -279,13 +279,28 @@ def evaluate( if verbose == 1: total = sum([step.nsubsteps for step in self.steps]) - progress_bar = tqdm(total=total, desc="Step ", unit="substep") + progress_bar = tqdm( + total=total, + desc="Step ", + unit="substep", + colour="green", + ) + progress_bar_newton = tqdm( + total=100, + desc="Substep", + colour="cyan", + unit="%", + ) + else: + progress_bar_newton = None for j, step in enumerate(self.steps): if verbose == 2: print(f"Begin Evaluation of Step {j + 1}.") - substeps = step.generate(verbose=verbose, **kwargs) + substeps = step.generate( + verbose=verbose, progress_bar=progress_bar_newton, **kwargs + ) for i, substep in enumerate(substeps): self.fnorms.append(substep.fnorms) if verbose == 2: @@ -318,5 +333,6 @@ def evaluate( if verbose == 1: progress_bar.close() + progress_bar_newton.close() return self diff --git a/src/felupe/tools/_newton.py b/src/felupe/tools/_newton.py index d838435e..e34515f5 100644 --- a/src/felupe/tools/_newton.py +++ b/src/felupe/tools/_newton.py @@ -223,6 +223,7 @@ def newtonrhapson( verbose=None, callback=None, callback_kwargs=None, + progress_bar=None, ): r"""Find a root of a real function using the Newton-Raphson method. @@ -277,6 +278,9 @@ def newtonrhapson( An optional callback function with function signature ``callback = lambda dx, x, iteration, xnorm, fnorm, success: None``, which is called after each completed iteration. Default is None. + progress_bar : str or None, optional + A progress bar if verbose is True. If None and verbose is True, a new bar is + created. Returns ------- @@ -400,7 +404,13 @@ def newtonrhapson( verbose = 2 # pragma: no cover if verbose == 1: - progress_bar = tqdm(total=100, desc="Solver", leave=False, unit="%") + if progress_bar is None: + progress_bar = tqdm(total=100, colour="yellow", unit="%") + close_bar = True + else: + progress_bar.reset() + close_bar = False + progress0 = 0 progress = 0 @@ -476,15 +486,17 @@ def newtonrhapson( callback(dx, x, iteration, xnorm, fnorm, success) # update progress bar if norm of residuals is available - if verbose == 1 and fnorm > 0.0: + if verbose == 1: + completion = 0.1 - # initial log. ratio of first residual norm vs. tolerance norm - if decades is None: - decades = max(1.0, np.log10(fnorm) - np.log10(tol)) + if fnorm > 0.0: + # initial log. ratio of first residual norm vs. tolerance norm + if decades is None: + decades = max(1.0, np.log10(fnorm) - np.log10(tol)) - # current log. ratio of first residual norm vs. tolerance norm - dfnorm = np.log10(fnorm) - np.log10(tol) - completion = 1 - dfnorm / decades + # current log. ratio of first residual norm vs. tolerance norm + dfnorm = np.log10(fnorm) - np.log10(tol) + completion += 0.9 * (1 - dfnorm / decades) # progress in percent, ensure lower equal 100% progress = np.clip(100 * completion, 0, 100).astype(int) @@ -513,7 +525,9 @@ def newtonrhapson( ) if verbose == 1: - progress_bar.close() + progress_bar.update(100 - progress) + if close_bar: + progress_bar.close() if verbose == 2: runtimes.append(perf_counter())