From 038423973eb5e2196432f011c643e7e56885cfc4 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 9 Aug 2024 13:25:12 +1000 Subject: [PATCH 01/16] merge fix --- .../cython/petsc_generic_snes_solvers.pyx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/underworld3/cython/petsc_generic_snes_solvers.pyx b/src/underworld3/cython/petsc_generic_snes_solvers.pyx index afac030bc..9434052f0 100644 --- a/src/underworld3/cython/petsc_generic_snes_solvers.pyx +++ b/src/underworld3/cython/petsc_generic_snes_solvers.pyx @@ -1,5 +1,6 @@ from xmlrpc.client import Boolean +from abc import ABC, abstractmethod import sympy from sympy import sympify @@ -190,6 +191,12 @@ class SolverBaseClass(uw_object): return + # Deprecate in favour of properties for solver.F0, solver.F1 + # + @timing.routine_timer_decorator + def _setup_problem_description(self): + raise RuntimeError("Contact Developers - shouldn't be calling SolveBaseClass _setup_problem_description") + @timing.routine_timer_decorator def add_condition(self, f_id, c_type, conds, label, components=None): """ @@ -295,24 +302,11 @@ class SolverBaseClass(uw_object): ## Solvers over-ride these to describe the problem type @property def F0(self): - - f0 = uw.function.expression( - r"\mathbf{f}_0\left( \mathbf{u} \right)", - None, - "Pointwise force term: f_0(u)", - ) - - return f0 + raise RuntimeError("Contact Developers - SolveBaseClass F0 is being used") @property def F1(self): - f1 = uw.function.expression( - r"\mathbf{F}_1\left( \mathbf{u} \right)", - None, - "Pointwise flux term: F_1(u)", - ) - - return f1 + raise RuntimeError("Contact Developers - SolveBaseClass F0 is being used") @property def u(self): From 15ba04fd5fb8907505b3ae8e09ec53718daac903 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 9 Aug 2024 14:06:34 +1000 Subject: [PATCH 02/16] Remove obsolete F0, F1 from generic_solvers --- src/underworld3/cython/petsc_generic_snes_solvers.pyx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/underworld3/cython/petsc_generic_snes_solvers.pyx b/src/underworld3/cython/petsc_generic_snes_solvers.pyx index 9434052f0..9274b1206 100644 --- a/src/underworld3/cython/petsc_generic_snes_solvers.pyx +++ b/src/underworld3/cython/petsc_generic_snes_solvers.pyx @@ -433,16 +433,11 @@ class SNES_Scalar(SolverBaseClass): self.Unknowns.DuDt = DuDt self.Unknowns.DFDt = DFDt - # self.u = u_Field - # self.DuDt = DuDt - # self.DFDt = DFDt - self.name = solver_name self.verbose = verbose self._tolerance = 1.0e-4 ## Todo: this is obviously not particularly robust - if solver_name != "" and not solver_name.endswith("_"): self.petsc_options_prefix = solver_name+"_" else: @@ -482,8 +477,6 @@ class SNES_Scalar(SolverBaseClass): self.petsc_options.delValue("snes_monitor_short") self.petsc_options.delValue("snes_converged_reason") - self._F0 = sympy.Matrix.zeros(1,1) - self._F1 = sympy.Matrix.zeros(1,mesh.dim) self.dm = None @@ -1079,8 +1072,6 @@ class SNES_Vector(SolverBaseClass): vtype=uw.VarType.VECTOR, degree=degree ) - self._F0 = sympy.Matrix.zeros(1, self.mesh.dim) - self._F1 = sympy.Matrix.zeros(self.mesh.dim, self.mesh.dim) self.dm = None ## sympy.Matrix From 3017f3ef0d040d3a4fedc38b8ed5aa6ddc002da1 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 9 Aug 2024 14:07:05 +1000 Subject: [PATCH 03/16] Another r string warning removal --- src/underworld3/discretisation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/underworld3/discretisation.py b/src/underworld3/discretisation.py index 8b5110f84..792d208c6 100644 --- a/src/underworld3/discretisation.py +++ b/src/underworld3/discretisation.py @@ -1650,7 +1650,7 @@ def __init__( self.symbol = symbol if mesh.instance_number > 1: - invisible = "\,\!" * mesh.instance_number + invisible = r"\,\!" * mesh.instance_number self.symbol = f"{{ {{ {invisible} }} {symbol} }}" self.clean_name = re.sub(r"[^a-zA-Z0-9_]", "", name) From 729bc4cd2ab56cc13ea0240738bc336b9ef91c12 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 9 Aug 2024 18:57:11 +1000 Subject: [PATCH 04/16] Removing obsolete Unknown attributes on the generic solver --- .../cython/petsc_generic_snes_solvers.pyx | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/underworld3/cython/petsc_generic_snes_solvers.pyx b/src/underworld3/cython/petsc_generic_snes_solvers.pyx index 9274b1206..1c65b2a83 100644 --- a/src/underworld3/cython/petsc_generic_snes_solvers.pyx +++ b/src/underworld3/cython/petsc_generic_snes_solvers.pyx @@ -1,10 +1,9 @@ from xmlrpc.client import Boolean -from abc import ABC, abstractmethod import sympy from sympy import sympify -from typing import Optional, Union +from typing import Optional, Union, TypeAlias from petsc4py import PETSc import underworld3 @@ -38,14 +37,6 @@ class SolverBaseClass(uw_object): self.Unknowns = self._Unknowns(self) - self._u = self.Unknowns.u - self._DuDt = self.Unknowns.DuDt - self._DFDt = self.Unknowns.DFDt - - self._L = self.Unknowns.L # grad(u) - self._E = self.Unknowns.E # sym part - self._W = self.Unknowns.W # asym part - self._order = 0 self._constitutive_model = None @@ -192,10 +183,9 @@ class SolverBaseClass(uw_object): # Deprecate in favour of properties for solver.F0, solver.F1 - # @timing.routine_timer_decorator def _setup_problem_description(self): - raise RuntimeError("Contact Developers - shouldn't be calling SolveBaseClass _setup_problem_description") + raise RuntimeError("Contact Developers - shouldn't be calling SolverBaseClass _setup_problem_description") @timing.routine_timer_decorator def add_condition(self, f_id, c_type, conds, label, components=None): @@ -208,6 +198,7 @@ class SolverBaseClass(uw_object): ---------- f_id: int Index of the solver's field (equation) to apply the condition. + Note: The solvers field id is usually different to the mesh's field ids. c_type: string BC type. Either dirichlet (essential) or neumann (natural) conditions. conds: array_like of floats or a sympy.Matrix @@ -296,17 +287,13 @@ class SolverBaseClass(uw_object): """ self.add_condition(0, 'dirichlet', conds, boundary, components) - - ## Properties that are common to all solvers - ## F0 and F1 are the force / flux terms, respectively - ## Solvers over-ride these to describe the problem type @property def F0(self): - raise RuntimeError("Contact Developers - SolveBaseClass F0 is being used") + raise RuntimeError("Contact Developers - SolverBaseClass F0 is being used") @property def F1(self): - raise RuntimeError("Contact Developers - SolveBaseClass F0 is being used") + raise RuntimeError("Contact Developers - SolverBaseClass F0 is being used") @property def u(self): From 0c9dc1fc030f20a61beb08478e2069662a40d6b1 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 9 Aug 2024 18:58:06 +1000 Subject: [PATCH 05/16] Getting rid of the problem_description method in every solver --- src/underworld3/systems/solvers.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/underworld3/systems/solvers.py b/src/underworld3/systems/solvers.py index 1e385e7cc..eef470f84 100644 --- a/src/underworld3/systems/solvers.py +++ b/src/underworld3/systems/solvers.py @@ -81,9 +81,6 @@ def __init__( if solver_name == "": solver_name = "Poisson_{}_".format(self.instance_number) - # Register the problem setup function - self._setup_problem_description = self.poisson_problem_description - # default values for properties self.f = sympy.Matrix.zeros(1, 1) @@ -210,9 +207,6 @@ def __init__( if solver_name == "": self.solver_name = "Darcy_{}_".format(self.instance_number) - # Register the problem setup function - self._setup_problem_description = self.darcy_problem_description - # default values for properties self._f = sympy.Matrix([0]) self._k = 1 @@ -458,8 +452,6 @@ def __init__( self._bodyforce = sympy.Matrix([[0] * self.mesh.dim]) - self._setup_problem_description = self.stokes_problem_description - # this attrib records if we need to setup the problem (again) self.is_setup = False @@ -842,7 +834,6 @@ def __init__( if solver_name == "": self.name = "SProj_{}_".format(self.instance_number) - self._setup_problem_description = self.projection_problem_description self.is_setup = False self._smoothing = 0.0 self._uw_weighting_function = 1.0 @@ -974,7 +965,6 @@ def __init__( if solver_name == "": solver_name = "VProj{}_".format(self.instance_number) - self._setup_problem_description = self.projection_problem_description self.is_setup = False self._smoothing = 0.0 self._penalty = 0.0 @@ -1298,8 +1288,6 @@ def __init__( self.is_setup = False self.restore_points_to_domain_func = restore_points_func - self._setup_problem_description = self.adv_diff_slcn_problem_description - ### Setup the history terms ... This version should not build anything ### by default - it's the template / skeleton @@ -1636,8 +1624,6 @@ def __init__( ) self.restore_points_to_domain_func = restore_points_func - self._setup_problem_description = self.navier_stokes_problem_description - self._bodyforce = sympy.Matrix([[0] * self.mesh.dim]) self._constitutive_model = None From a99c1884dc30810de3d25142df44f63728b37b8f Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Tue, 13 Aug 2024 15:15:46 +1000 Subject: [PATCH 06/16] Removing some commented out code. --- src/underworld3/cython/petsc_generic_snes_solvers.pyx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/underworld3/cython/petsc_generic_snes_solvers.pyx b/src/underworld3/cython/petsc_generic_snes_solvers.pyx index 1c65b2a83..3641210d9 100644 --- a/src/underworld3/cython/petsc_generic_snes_solvers.pyx +++ b/src/underworld3/cython/petsc_generic_snes_solvers.pyx @@ -482,7 +482,6 @@ class SNES_Scalar(SolverBaseClass): self.mesh._equation_systems_register.append(self) self.is_setup = False - # super().__init__() @property def tolerance(self): @@ -1083,8 +1082,6 @@ class SNES_Vector(SolverBaseClass): self.mesh._equation_systems_register.append(self) - # super().__init__() - @property def tolerance(self): return self._tolerance From 490d6042dcebb140f7cb7ab0dfaa0c2a9833715b Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Thu, 15 Aug 2024 12:10:37 +1000 Subject: [PATCH 07/16] Clean up the expression's initialisation code --- src/underworld3/function/expressions.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/underworld3/function/expressions.py b/src/underworld3/function/expressions.py index b9c2343bc..569fbbb68 100644 --- a/src/underworld3/function/expressions.py +++ b/src/underworld3/function/expressions.py @@ -87,7 +87,7 @@ class UWexpression(Symbol, uw_object): ```{python} alpha = UWexpression( r'\\alpha', - value=3.0e-5, + sym=3.0e-5, description="thermal expansivity" ) print(alpha.sym) @@ -96,17 +96,22 @@ class UWexpression(Symbol, uw_object): """ - def __new__(cls, name, value, description="No description provided"): + def __new__(cls, name, *args, **kwargs,): obj = Symbol.__new__(cls, name) - obj.sym = sympy.sympify(value) - obj.symbol = name return obj - def __init__(self, name, value, description="No description provided"): - - self._sym = sympy.sympify(value) - self._description = description + def __init__(self, name, sym=None, description="No description provided", value=None): + if value is not None and sym is None: + import warnings; + warnings.warn(message=f"DEPRECATION warning, don't use 'value' attribute for expression: {value}, please use 'sym' attribute") + sym = value + if value is not None and sym is not None: + raise ValueError("Both 'sym' and 'value' attributes are provided, please use one") + + self.sym = sympy.sympify(sym) + self.symbol = name + self.description = description return From 6ef880501c8577c72c9a49d7c4271ac14261e250 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 16 Aug 2024 11:51:29 +1000 Subject: [PATCH 08/16] simplifying the uw_counter_object idea to keep a total_instances and instance_number --- src/underworld3/utilities/_api_tools.py | 54 +++++++++++++++++-------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/underworld3/utilities/_api_tools.py b/src/underworld3/utilities/_api_tools.py index a0abc8b47..173f324ac 100644 --- a/src/underworld3/utilities/_api_tools.py +++ b/src/underworld3/utilities/_api_tools.py @@ -35,24 +35,44 @@ def newfunc(*args, **kwargs): return newfunc -class counted_metaclass(type): - def __init__(cls, name, bases, attrs): - super().__init__(name, bases, attrs) - cls._total_instances = 0 +# class counted_metaclass(type): +# def __init__(cls, name, bases, attrs): +# super().__init__(name, bases, attrs) +# cls._total_instances = 0 +# +# +#class uw_object_counter(object, metaclass=counted_metaclass): +# def __init__(self): +# try: +# self.__class__.mro()[1]._total_instances += 1 +# except AttributeError: +# pass +# # print(f"{self.__class__.mro()[1]} is not a uw_object") +# +# super().__init__() +# +# self.__class__._total_instances += 1 +# self.instance_number = self.__class__._total_instances + +class uw_object_counter(object): + _total_instances = 0 - -class uw_object_counter(object, metaclass=counted_metaclass): def __init__(self): - try: - self.__class__.mro()[1]._total_instances += 1 - except AttributeError: - pass - # print(f"{self.__class__.mro()[1]} is not a uw_object") - + self.instance_number = uw_object_counter._total_instances + uw_object_counter._total_instances += 1 super().__init__() - self.__class__._total_instances += 1 - self.instance_number = self.__class__._total_instances + @classmethod + @property + def total_instances(self): + return uw_object_counter._total_instances + + def __del__(self): + uw_object_counter._total_instances -= 1 + + def __str__(self): + s = super().__str__() + return f"{self.__class__.__name__} instance {self.instance_number}, {s}" class uw_object(uw_object_counter): @@ -118,9 +138,9 @@ def view(self_or_cls, class_documentation=False): self_or_cls._object_viewer() - @classmethod - def total_instances(cls): - return cls._total_instances + #@classmethod + #def total_instances(cls): + # return cls._total_instances # placeholder def _object_viewer(self): From d927218523be93d4650feb84e00b565064c54cf8 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Fri, 16 Aug 2024 18:22:43 +1000 Subject: [PATCH 09/16] Removing the metaclass counter idea - now simpler. Need to add a stress test --- src/underworld3/utilities/_api_tools.py | 47 +++++++------------------ 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/src/underworld3/utilities/_api_tools.py b/src/underworld3/utilities/_api_tools.py index 173f324ac..2d81f4d4f 100644 --- a/src/underworld3/utilities/_api_tools.py +++ b/src/underworld3/utilities/_api_tools.py @@ -34,48 +34,31 @@ def newfunc(*args, **kwargs): return newfunc - -# class counted_metaclass(type): -# def __init__(cls, name, bases, attrs): -# super().__init__(name, bases, attrs) -# cls._total_instances = 0 -# -# -#class uw_object_counter(object, metaclass=counted_metaclass): -# def __init__(self): -# try: -# self.__class__.mro()[1]._total_instances += 1 -# except AttributeError: -# pass -# # print(f"{self.__class__.mro()[1]} is not a uw_object") -# -# super().__init__() -# -# self.__class__._total_instances += 1 -# self.instance_number = self.__class__._total_instances - -class uw_object_counter(object): - _total_instances = 0 +class uw_counter(object): + _object_count = 0 # variable to count the number of objects def __init__(self): - self.instance_number = uw_object_counter._total_instances - uw_object_counter._total_instances += 1 + self._uw_id = uw_counter._object_count + uw_counter._object_count += 1 super().__init__() - @classmethod @property - def total_instances(self): - return uw_object_counter._total_instances + def uw_object_counter(self): + """ Number of uw_object instances created """ + return uw_counter._object_count - def __del__(self): - uw_object_counter._total_instances -= 1 + @property + def instance_number(self): + """ Unique number of the uw_object instance """ + return self._uw_id def __str__(self): s = super().__str__() return f"{self.__class__.__name__} instance {self.instance_number}, {s}" -class uw_object(uw_object_counter): +#class uw_object(object, metaclass=uw_count_as_meta): +class uw_object(uw_counter): """ The UW (mixin) class adds common functionality that we wish to provide on all uw_objects such as the view methods (classmethod for generic information and instance method that can be over-ridden) @@ -138,10 +121,6 @@ def view(self_or_cls, class_documentation=False): self_or_cls._object_viewer() - #@classmethod - #def total_instances(cls): - # return cls._total_instances - # placeholder def _object_viewer(self): from IPython.display import Latex, Markdown, display From 159848e8898fd49b0df3e3f87628682779b75731 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Mon, 19 Aug 2024 21:00:36 +1000 Subject: [PATCH 10/16] Adding _version.py file. Also starting a runtime record class, currently in __init__.py --- setup.cfg | 3 +- src/underworld3/__init__.py | 62 ++++++++++++++++++++----------------- src/underworld3/_version.py | 1 + 3 files changed, 37 insertions(+), 29 deletions(-) create mode 100644 src/underworld3/_version.py diff --git a/setup.cfg b/setup.cfg index 75cbb3b2f..e2adfd252 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = underworld3 -version = 0.98b +version = attr: underworld3.__version__ #[build_ext] #inplace = True @@ -20,6 +20,7 @@ install_requires = pint psutil typing_extensions + package_dir = = src packages = find: diff --git a/src/underworld3/__init__.py b/src/underworld3/__init__.py index 6fe29f59e..5cba114ad 100644 --- a/src/underworld3/__init__.py +++ b/src/underworld3/__init__.py @@ -75,6 +75,10 @@ PETSc.Sys.popErrorHandler() +try: + from ._version import __version__ +except ImportError: + __version__ = "Unknown" # check src/underworld3/_version.py def view(): from IPython.display import Latex, Markdown, display @@ -116,6 +120,36 @@ def view(): import underworld3.visualisation import numpy as _np +class runtime_record(): + + def __init__(self): + import sys + import datetime + import subprocess + + # get the start time of this piece of code + start_t = datetime.datetime.now() + + # get the git version + gv = None + try: + gv = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') + except Exception as e: + return f"Error: Underworld can't retrieving commit hash: {e}" + + self._data = { + "uw_object_count": 0, + "python_versions": sys.version, + "git_version": gv, + "uw_version": None, + "petsc_version": PETSc.Sys.getVersion(), + "petsc_dir": None, + "execution_start": start_t, + } + + def get_metadata(): + return self._metadata + # Info for JIT modules. # These dicts should be populated by submodules # which define cython/c based classes. @@ -131,31 +165,6 @@ def view(): _libdirs = _OD() _incdirs = _OD({_np.get_include(): None}) - -# def _is_notebook() -> bool: -# """ -# Function to determine if the python environment is a Notebook or not. - -# Returns 'True' if executing in a notebook, 'False' otherwise - -# Script taken from https://stackoverflow.com/a/39662359/8106122 -# """ - -# try: -# shell = get_ipython().__class__.__name__ -# if shell == "ZMQInteractiveShell": -# return True # Jupyter notebook or qtconsole -# elif shell == "TerminalInteractiveShell": -# return False # Terminal running IPython -# else: -# return False # Other type (?) -# except NameError: -# return False # Probably standard Python interpreter - - -# is_notebook = _is_notebook() - - ## ------------------------------------------------------------- # pdoc3 over-rides. pdoc3 has a strange path-traversal algorithm @@ -175,6 +184,3 @@ def view(): # child class modifications __pdoc__["systems.constitutive_models.Constitutive_Model.Parameters"] = False - - -## Add an options dictionary for arbitrary underworld things diff --git a/src/underworld3/_version.py b/src/underworld3/_version.py new file mode 100644 index 000000000..286d177dd --- /dev/null +++ b/src/underworld3/_version.py @@ -0,0 +1 @@ +__version__ = "0.98.1b" From c4059eb8253dd96329733e04b2098ca4a282acba Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Tue, 20 Aug 2024 17:43:52 +1000 Subject: [PATCH 11/16] Object counter function to class function + property --- src/underworld3/utilities/_api_tools.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/underworld3/utilities/_api_tools.py b/src/underworld3/utilities/_api_tools.py index 2d81f4d4f..98cfa5e6d 100644 --- a/src/underworld3/utilities/_api_tools.py +++ b/src/underworld3/utilities/_api_tools.py @@ -42,10 +42,13 @@ def __init__(self): uw_counter._object_count += 1 super().__init__() + # to order of the following decorators matters python + # see - https://stackoverflow.com/questions/128573/using-property-on-classmethods/64738850#64738850 + @classmethod @property - def uw_object_counter(self): + def uw_object_counter(cls): """ Number of uw_object instances created """ - return uw_counter._object_count + return cls._object_count @property def instance_number(self): From d237f337ec10f6269717998ffc2f30840b178f07 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Tue, 20 Aug 2024 17:45:50 +1000 Subject: [PATCH 12/16] Introducting uw_record. uw_record gives information on the installation and runtime Access via: import underworld3 as uw uw.uw_record.get_installation_data uw.uw_record.get_runtime_data * uw_record lives in underworld/utilities/__init__.py * uw_record collects data only on proc 0 and broadcasts to all procs. For get_runtime_data be aware of this design. As calling often could cause an operational overhead. While get_installation_data only bcasts at initialisation. * Coming idea is to serialised the output get_installation_data into hdf5 files --- src/underworld3/__init__.py | 31 +------- src/underworld3/utilities/__init__.py | 110 ++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 30 deletions(-) diff --git a/src/underworld3/__init__.py b/src/underworld3/__init__.py index 5cba114ad..47abc6ff2 100644 --- a/src/underworld3/__init__.py +++ b/src/underworld3/__init__.py @@ -101,6 +101,7 @@ def view(): from ._var_types import * from .utilities._petsc_tools import * from .utilities._nb_tools import * +from .utilities import uw_record # grab record class from underworld3.utilities import _api_tools @@ -120,36 +121,6 @@ def view(): import underworld3.visualisation import numpy as _np -class runtime_record(): - - def __init__(self): - import sys - import datetime - import subprocess - - # get the start time of this piece of code - start_t = datetime.datetime.now() - - # get the git version - gv = None - try: - gv = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') - except Exception as e: - return f"Error: Underworld can't retrieving commit hash: {e}" - - self._data = { - "uw_object_count": 0, - "python_versions": sys.version, - "git_version": gv, - "uw_version": None, - "petsc_version": PETSc.Sys.getVersion(), - "petsc_dir": None, - "execution_start": start_t, - } - - def get_metadata(): - return self._metadata - # Info for JIT modules. # These dicts should be populated by submodules # which define cython/c based classes. diff --git a/src/underworld3/utilities/__init__.py b/src/underworld3/utilities/__init__.py index bef031533..36937d1b8 100644 --- a/src/underworld3/utilities/__init__.py +++ b/src/underworld3/utilities/__init__.py @@ -21,3 +21,113 @@ def _append_petsc_path(): from .read_medit_ascii import read_medit_ascii, print_medit_mesh_info from .create_dmplex_from_medit import create_dmplex_from_medit +class _uw_record(): + + def __init__(self): + """ + A class to record runtime information about the underworld3 execution environment. + """ + try: + import mpi4py + comm = mpi4py.MPI.COMM_WORLD + except ImportError: + raise ImportError("Can't import mpi4py for runtime information.") + + + # rank 0 only builds the data and then broadcasts it + self._install_data = None + self._runtime_data = None + if comm.rank == 0: + + import sys + import datetime + import subprocess + import warnings + + # get the start time of this piece of code + start_t = datetime.datetime.now().isoformat() + + # get the git version + try: + gv = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') + except Exception as e: + gv = None + warnings.warn( f"Warning: Underworld can't retrieving commit hash: {e}" ) + + # get petsc information + try: + import petsc4py as _petsc4py + from petsc4py import PETSc as _PETSc + petsc_version = _PETSc.Sys.getVersion() + petsc_dir = _petsc4py.get_config()['PETSC_DIR'] + except Exception as e: + petsc_version = None + petsc_dir = None + warnings.warn( f"Warning: Underworld can't retrieving petsc installation details: {e}" ) + + # get h5py information + try: + import h5py as _h5py + h5py_dir = _h5py.__file__ + h5py_version = _h5py.version.version + hdf5_version = _h5py.version.hdf5_version + except Exception as e: + h5py_dir = None + h5py_version = None + hdf5_version = None + warnings.warn( f"Warning: Underworld can't retrieving h5py installation details: {e}" ) + + # get mpi4py information + try: + import mpi4py as _mpi4py + mpi4py_version = _mpi4py.__version__ + except Exception as e: + mpi4py_version = None + warnings.warn( f"Warning: Underworld can't retrieving mpi4py installation details: {e}" ) + + # get just the version + from underworld3 import __version__ as uw_version + + self._install_data = { + "git_version": gv, + "uw_version": uw_version, + "python_versions": sys.version, + "petsc_version": petsc_version, + "petsc_dir": petsc_dir, + "h5py_version": h5py_version, + "hdf5_version": hdf5_version, + "h5py_dir": h5py_dir, + "mpi4py_version": mpi4py_version, + } + + self._runtime_data = { + "start_time": start_t, + "uw_object_count": 0, + } + + # rank 0 broadcast information to other procs + self._install_data = comm.bcast(self._install_data, root=0) + + @property + def get_installation_data(self): + return self._install_data + + @property + def get_runtime_data(self): + import datetime + import mpi4py + comm = mpi4py.MPI.COMM_WORLD + + if comm.rank == 0: + now = datetime.datetime.now().isoformat() + self._runtime_data.update({"current_time": now}) + + from underworld3.utilities._api_tools import uw_object + object_count = uw_object.uw_object_counter + self._runtime_data.update({"uw_object_count": object_count}) + + self._runtime_data = comm.bcast(self._runtime_data, root=0) + return self._runtime_data + +uw_record = _uw_record() + From 0f4e0076f3b9fe7df67c3c0ebe00c56eccef3a47 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Wed, 21 Aug 2024 13:35:38 +1000 Subject: [PATCH 13/16] Rename uw_record -> auditor * access via underworld3.auditor * test included in test_0005_utils.py --- src/underworld3/__init__.py | 3 +- src/underworld3/utilities/__init__.py | 113 +--------------------- src/underworld3/utilities/_utils.py | 130 ++++++++++++++++++++++---- tests/test_0005_utils.py | 112 ++++++++++++++++++++++ 4 files changed, 229 insertions(+), 129 deletions(-) create mode 100644 tests/test_0005_utils.py diff --git a/src/underworld3/__init__.py b/src/underworld3/__init__.py index 47abc6ff2..c6e9de3c2 100644 --- a/src/underworld3/__init__.py +++ b/src/underworld3/__init__.py @@ -101,9 +101,10 @@ def view(): from ._var_types import * from .utilities._petsc_tools import * from .utilities._nb_tools import * -from .utilities import uw_record # grab record class +from .utilities._utils import auditor from underworld3.utilities import _api_tools +from .utilities import auditor import underworld3.adaptivity import underworld3.coordinates diff --git a/src/underworld3/utilities/__init__.py b/src/underworld3/utilities/__init__.py index 36937d1b8..ffab01a90 100644 --- a/src/underworld3/utilities/__init__.py +++ b/src/underworld3/utilities/__init__.py @@ -16,118 +16,7 @@ def _append_petsc_path(): from .uw_petsc_gen_xdmf import Xdmf, generateXdmf, generate_uw_Xdmf from .uw_swarmIO import swarm_h5, swarm_xdmf -from ._utils import CaptureStdout, h5_scan, mem_footprint, gather_data +from ._utils import CaptureStdout, h5_scan, mem_footprint, gather_data, auditor from .read_medit_ascii import read_medit_ascii, print_medit_mesh_info from .create_dmplex_from_medit import create_dmplex_from_medit - -class _uw_record(): - - def __init__(self): - """ - A class to record runtime information about the underworld3 execution environment. - """ - try: - import mpi4py - comm = mpi4py.MPI.COMM_WORLD - except ImportError: - raise ImportError("Can't import mpi4py for runtime information.") - - - # rank 0 only builds the data and then broadcasts it - self._install_data = None - self._runtime_data = None - if comm.rank == 0: - - import sys - import datetime - import subprocess - import warnings - - # get the start time of this piece of code - start_t = datetime.datetime.now().isoformat() - - # get the git version - try: - gv = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') - except Exception as e: - gv = None - warnings.warn( f"Warning: Underworld can't retrieving commit hash: {e}" ) - - # get petsc information - try: - import petsc4py as _petsc4py - from petsc4py import PETSc as _PETSc - petsc_version = _PETSc.Sys.getVersion() - petsc_dir = _petsc4py.get_config()['PETSC_DIR'] - except Exception as e: - petsc_version = None - petsc_dir = None - warnings.warn( f"Warning: Underworld can't retrieving petsc installation details: {e}" ) - - # get h5py information - try: - import h5py as _h5py - h5py_dir = _h5py.__file__ - h5py_version = _h5py.version.version - hdf5_version = _h5py.version.hdf5_version - except Exception as e: - h5py_dir = None - h5py_version = None - hdf5_version = None - warnings.warn( f"Warning: Underworld can't retrieving h5py installation details: {e}" ) - - # get mpi4py information - try: - import mpi4py as _mpi4py - mpi4py_version = _mpi4py.__version__ - except Exception as e: - mpi4py_version = None - warnings.warn( f"Warning: Underworld can't retrieving mpi4py installation details: {e}" ) - - # get just the version - from underworld3 import __version__ as uw_version - - self._install_data = { - "git_version": gv, - "uw_version": uw_version, - "python_versions": sys.version, - "petsc_version": petsc_version, - "petsc_dir": petsc_dir, - "h5py_version": h5py_version, - "hdf5_version": hdf5_version, - "h5py_dir": h5py_dir, - "mpi4py_version": mpi4py_version, - } - - self._runtime_data = { - "start_time": start_t, - "uw_object_count": 0, - } - - # rank 0 broadcast information to other procs - self._install_data = comm.bcast(self._install_data, root=0) - - @property - def get_installation_data(self): - return self._install_data - - @property - def get_runtime_data(self): - import datetime - import mpi4py - comm = mpi4py.MPI.COMM_WORLD - - if comm.rank == 0: - now = datetime.datetime.now().isoformat() - self._runtime_data.update({"current_time": now}) - - from underworld3.utilities._api_tools import uw_object - object_count = uw_object.uw_object_counter - self._runtime_data.update({"uw_object_count": object_count}) - - self._runtime_data = comm.bcast(self._runtime_data, root=0) - return self._runtime_data - -uw_record = _uw_record() - diff --git a/src/underworld3/utilities/_utils.py b/src/underworld3/utilities/_utils.py index cc68c73e9..f42350250 100755 --- a/src/underworld3/utilities/_utils.py +++ b/src/underworld3/utilities/_utils.py @@ -7,23 +7,121 @@ from collections import UserString from contextlib import redirect_stdout, redirect_stderr +class _uw_record(): + """ + A class to record runtime information about the underworld3 execution environment. + """ -# # Capture the stdout to an object -# class CaptureStdout(list): -# def __enter__(self, split=True): -# self._stdout = sys.stdout -# self.split = split -# sys.stdout = self._stringio = StringIO() -# return self - -# def __exit__(self, *args): -# if split: -# self.extend(self._stringio.getvalue().splitlines()) -# else: -# self.extend(self._stringio.getvalue() -# del self._stringio # free up some memory -# sys.stdout = self._stdout - + def __init__(self): + try: + import mpi4py + comm = mpi4py.MPI.COMM_WORLD + except ImportError: + raise ImportError("Can't import mpi4py for runtime information.") + + # rank 0 only builds the data and then broadcasts it + self._install_data = None + self._runtime_data = None + if comm.rank == 0: + + import sys + import datetime + import subprocess + import warnings + + # get the start time of this piece of code + start_t = datetime.datetime.now().isoformat() + + # get the git version + try: + gv = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') + except Exception as e: + gv = None + warnings.warn( f"Warning: Underworld can't retrieving commit hash: {e}" ) + + # get petsc information + try: + import petsc4py as _petsc4py + from petsc4py import PETSc as _PETSc + petsc_version = _PETSc.Sys.getVersion() + petsc_dir = _petsc4py.get_config()['PETSC_DIR'] + except Exception as e: + petsc_version = None + petsc_dir = None + warnings.warn( f"Warning: Underworld can't retrieving petsc installation details: {e}" ) + + # get h5py information + try: + import h5py as _h5py + h5py_dir = _h5py.__file__ + h5py_version = _h5py.version.version + hdf5_version = _h5py.version.hdf5_version + except Exception as e: + h5py_dir = None + h5py_version = None + hdf5_version = None + warnings.warn( f"Warning: Underworld can't retrieving h5py installation details: {e}" ) + + # get mpi4py information + try: + import mpi4py as _mpi4py + mpi4py_version = _mpi4py.__version__ + except Exception as e: + mpi4py_version = None + warnings.warn( f"Warning: Underworld can't retrieving mpi4py installation details: {e}" ) + + # get just the version + from underworld3 import __version__ as uw_version + + self._install_data = { + "git_version": gv, + "uw_version": uw_version, + "python_versions": sys.version, + "petsc_version": petsc_version, + "petsc_dir": petsc_dir, + "hdf5_version": hdf5_version, + "h5py_version": h5py_version, + "h5py_dir": h5py_dir, + "mpi4py_version": mpi4py_version, + } + + self._runtime_data = { + "start_time": start_t, + "uw_object_count": 0, + } + + # rank 0 broadcast information to other procs + self._install_data = comm.bcast(self._install_data, root=0) + + @property + def get_installation_data(self): + ''' + Get the installation data for the underworld3 installation. + ''' + return self._install_data + + @property + def get_runtime_data(self): + ''' + Get the runtime data for the underworld3 installation. + Note this requires a MPI broadcast to get the data. + ''' + import datetime + import mpi4py + comm = mpi4py.MPI.COMM_WORLD + + if comm.rank == 0: + now = datetime.datetime.now().isoformat() + self._runtime_data.update({"current_time": now}) + + from underworld3.utilities._api_tools import uw_object + object_count = uw_object.uw_object_counter + self._runtime_data.update({"uw_object_count": object_count}) + + self._runtime_data = comm.bcast(self._runtime_data, root=0) + return self._runtime_data + +auditor = _uw_record() class CaptureStdout(UserString, redirect_stdout): """ diff --git a/tests/test_0005_utils.py b/tests/test_0005_utils.py new file mode 100644 index 000000000..3aad1599e --- /dev/null +++ b/tests/test_0005_utils.py @@ -0,0 +1,112 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py:percent +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.16.2 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% +import underworld3 as uw +import sympy + +# %% +mesh = uw.meshing.StructuredQuadBox(elementRes=(10,) * 2) +x, y = mesh.X + +# %% +v = uw.discretisation.MeshVariable( r"mathbf{u}", mesh, mesh.dim, vtype=uw.VarType.VECTOR, degree=2) +p = uw.discretisation.MeshVariable( r"mathbf{p}", mesh, 1, vtype=uw.VarType.SCALAR, degree=1) + +def bc_1(solver): + s1 = solver + s1.add_dirichlet_bc((0.0, 0.0), "Bottom") + s1.add_dirichlet_bc((y, 0.0), "Top") + + s1.add_dirichlet_bc((sympy.oo, 0.0), "Left") + s1.add_dirichlet_bc((sympy.oo, 0.0), "Right") + +def bc_2(solver): + s1 = solver + s1.add_dirichlet_bc((0.0, sympy.oo), "Bottom") + s1.add_dirichlet_bc((0.0, sympy.oo), "Top") + + s1.add_dirichlet_bc((0.0, 0.0), "Left") + s1.add_dirichlet_bc((0.0, x), "Right") + + +# %% +def vis_model(mesh): + import pyvista as pv + import underworld3.visualisation as vis + + v = mesh.vars['mathbfu'] + pl = pv.Plotter(window_size=(1000, 750)) + + pvmesh = vis.mesh_to_pv_mesh(mesh) + pvmesh.point_data["V"] = vis.vector_fn_to_pv_points(pvmesh, v.sym) + pvmesh.point_data["Vmag"] = vis.scalar_fn_to_pv_points(pvmesh, v.sym.dot(v.sym)) + pvmesh.point_data["V1"] = vis.scalar_fn_to_pv_points(pvmesh, v.sym[1]) + + pl.add_mesh( + pvmesh, + cmap="coolwarm", + edge_color="Black", + show_edges=True, + scalars="Vmag", + use_transparency=False, + opacity=1.0, + ) + + velocity_points = vis.meshVariable_to_pv_cloud(v) + velocity_points.point_data["V"] = vis.vector_fn_to_pv_points(velocity_points, v.sym) + arrows = pl.add_arrows(velocity_points.points, velocity_points.point_data["V"], mag=3e-1, opacity=0.5, show_scalar_bar=False, cmap="coolwarm") + + pl.show(cpos="xy") + + + +# %% +stokes = uw.systems.Stokes(mesh, velocityField=v, pressureField=p) +stokes.constitutive_model = uw.constitutive_models.ViscousFlowModel +stokes.constitutive_model.Parameters.shear_viscosity_0 = 1 + +# %% +bc_1(stokes) + +# %% +stokes.solve() + +# %% +# vis_model(mesh) + +# %% +s1 = uw.systems.Stokes(mesh, velocityField=v, pressureField=p) +s1.constitutive_model = uw.constitutive_models.ViscousFlowModel +s1.constitutive_model.Parameters.shear_viscosity_0 = 1 +# stokes._rebuild_after_mesh_update() +bc_2(s1) + +# %% +#stokes.solve() +s1.solve() + +# %% +# vis_model(mesh) + +def test_auditor(): + # assert not values are in install data are None + for v in uw.auditor.get_installation_data.values(): + assert v is not None + + # assert 7 uw_objects are created + assert uw.auditor.get_runtime_data.get('uw_object_count') == 7 + +# %% From 8b0ab18f197ef8162bd7ecb5ae5020c9232779f8 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Thu, 22 Aug 2024 08:14:02 +1000 Subject: [PATCH 14/16] Modify test script name for uw_counter --- src/underworld3/__init__.py | 5 +++-- test.sh | 7 ++++--- tests/{test_0005_utils.py => test_0050_utils.py} | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) rename tests/{test_0005_utils.py => test_0050_utils.py} (97%) diff --git a/src/underworld3/__init__.py b/src/underworld3/__init__.py index c6e9de3c2..8f58abc47 100644 --- a/src/underworld3/__init__.py +++ b/src/underworld3/__init__.py @@ -103,8 +103,9 @@ def view(): from .utilities._nb_tools import * from .utilities._utils import auditor -from underworld3.utilities import _api_tools -from .utilities import auditor +from .utilities import _api_tools +#from underworld3.utilities import _api_tools +from .utilities._utils import auditor import underworld3.adaptivity import underworld3.coordinates diff --git a/test.sh b/test.sh index f4ecda87b..32872fafa 100755 --- a/test.sh +++ b/test.sh @@ -9,7 +9,8 @@ PYTEST="pytest -c tests/pytest.ini" # Run simple tests -$PYTEST tests/test_00*py +$PYTEST tests/test_00[0-4]*py +$PYTEST tests/test_0050*py # Spatial / calculation tests $PYTEST tests/test_01*py tests/test_05*py tests/test_06*py @@ -21,5 +22,5 @@ $PYTEST tests/test_100[0-9]*py $PYTEST tests/test_1010*py tests/test_1011*py tests/test_1050*py # Diffusion / Advection tests -$PYTEST tests/test_1100*py -$PYTEST tests/test_1110*py # Annulus version \ No newline at end of file +# $PYTEST tests/test_1100*py +# $PYTEST tests/test_1110*py # Annulus version diff --git a/tests/test_0005_utils.py b/tests/test_0050_utils.py similarity index 97% rename from tests/test_0005_utils.py rename to tests/test_0050_utils.py index 3aad1599e..52c498de6 100644 --- a/tests/test_0005_utils.py +++ b/tests/test_0050_utils.py @@ -18,6 +18,8 @@ import sympy # %% +uw.utilities._api_tools.uw_counter._reset() # reset the counter + mesh = uw.meshing.StructuredQuadBox(elementRes=(10,) * 2) x, y = mesh.X From 477795fda1e868cb8b5a7584653c873119f4e992 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Thu, 22 Aug 2024 08:59:22 +1000 Subject: [PATCH 15/16] Clean up the interface of 'auditor' --- src/underworld3/utilities/_api_tools.py | 8 ++++++-- src/underworld3/utilities/_utils.py | 3 ++- tests/test_0050_utils.py | 8 +++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/underworld3/utilities/_api_tools.py b/src/underworld3/utilities/_api_tools.py index 98cfa5e6d..c625671f9 100644 --- a/src/underworld3/utilities/_api_tools.py +++ b/src/underworld3/utilities/_api_tools.py @@ -45,10 +45,9 @@ def __init__(self): # to order of the following decorators matters python # see - https://stackoverflow.com/questions/128573/using-property-on-classmethods/64738850#64738850 @classmethod - @property def uw_object_counter(cls): """ Number of uw_object instances created """ - return cls._object_count + return uw_counter._object_count @property def instance_number(self): @@ -59,6 +58,11 @@ def __str__(self): s = super().__str__() return f"{self.__class__.__name__} instance {self.instance_number}, {s}" + @staticmethod + def _reset(): + """ Reset the object counter """ + uw_counter._object_count = 0 + #class uw_object(object, metaclass=uw_count_as_meta): class uw_object(uw_counter): diff --git a/src/underworld3/utilities/_utils.py b/src/underworld3/utilities/_utils.py index f42350250..a37b55e42 100755 --- a/src/underworld3/utilities/_utils.py +++ b/src/underworld3/utilities/_utils.py @@ -114,8 +114,9 @@ def get_runtime_data(self): now = datetime.datetime.now().isoformat() self._runtime_data.update({"current_time": now}) + #object_count = uw.utilities._api_tools.uw_counter.uw_object_counter from underworld3.utilities._api_tools import uw_object - object_count = uw_object.uw_object_counter + object_count = uw_object.uw_object_counter() self._runtime_data.update({"uw_object_count": object_count}) self._runtime_data = comm.bcast(self._runtime_data, root=0) diff --git a/tests/test_0050_utils.py b/tests/test_0050_utils.py index 52c498de6..6a4de9918 100644 --- a/tests/test_0050_utils.py +++ b/tests/test_0050_utils.py @@ -13,14 +13,12 @@ # name: python3 # --- -# %% +## %% + import underworld3 as uw import sympy -# %% -uw.utilities._api_tools.uw_counter._reset() # reset the counter - -mesh = uw.meshing.StructuredQuadBox(elementRes=(10,) * 2) +mesh = uw.meshing.StructuredQuadBox(elementRes=(5,) * 2) x, y = mesh.X # %% From de7abe98a7adae0499b152b83a0695c54c604011 Mon Sep 17 00:00:00 2001 From: Julian Giordani Date: Thu, 22 Aug 2024 15:04:40 +1000 Subject: [PATCH 16/16] Removing the uw_count class --- src/underworld3/utilities/_api_tools.py | 33 +++++++++++-------------- src/underworld3/utilities/_utils.py | 1 - 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/underworld3/utilities/_api_tools.py b/src/underworld3/utilities/_api_tools.py index c625671f9..c0d2bd320 100644 --- a/src/underworld3/utilities/_api_tools.py +++ b/src/underworld3/utilities/_api_tools.py @@ -34,20 +34,27 @@ def newfunc(*args, **kwargs): return newfunc -class uw_counter(object): - _object_count = 0 # variable to count the number of objects +class uw_object(): + """ + The UW (mixin) class adds common functionality that we wish to provide on all uw_objects + such as the view methods (classmethod for generic information and instance method that can be over-ridden) + to provide instance-specific information + """ + + _obj_count = 0 # a class variable to count the number of objects def __init__(self): - self._uw_id = uw_counter._object_count - uw_counter._object_count += 1 - super().__init__() + super().__init__ + + self._uw_id = uw_object._obj_count + uw_object._obj_count += 1 # to order of the following decorators matters python # see - https://stackoverflow.com/questions/128573/using-property-on-classmethods/64738850#64738850 @classmethod def uw_object_counter(cls): """ Number of uw_object instances created """ - return uw_counter._object_count + return uw_object._obj_count @property def instance_number(self): @@ -61,19 +68,7 @@ def __str__(self): @staticmethod def _reset(): """ Reset the object counter """ - uw_counter._object_count = 0 - - -#class uw_object(object, metaclass=uw_count_as_meta): -class uw_object(uw_counter): - """ - The UW (mixin) class adds common functionality that we wish to provide on all uw_objects - such as the view methods (classmethod for generic information and instance method that can be over-ridden) - to provide instance-specific information - """ - - def __init__(self): - super().__init__() + uw_object._obj_count = 0 @class_or_instance_method def _ipython_display_(self_or_cls): diff --git a/src/underworld3/utilities/_utils.py b/src/underworld3/utilities/_utils.py index a37b55e42..d3d5fd1d9 100755 --- a/src/underworld3/utilities/_utils.py +++ b/src/underworld3/utilities/_utils.py @@ -114,7 +114,6 @@ def get_runtime_data(self): now = datetime.datetime.now().isoformat() self._runtime_data.update({"current_time": now}) - #object_count = uw.utilities._api_tools.uw_counter.uw_object_counter from underworld3.utilities._api_tools import uw_object object_count = uw_object.uw_object_counter() self._runtime_data.update({"uw_object_count": object_count})