From 2964591ac9cdd1036e6754a3dda57b88b245bad3 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Wed, 5 Aug 2026 15:48:20 +0200 Subject: [PATCH 01/15] Initial 05082026 --- .vscode/settings.json | 3 + test.json | 41 +++++++++++++ test.py | 41 +++++++++++++ timflow/steady/aquifer.py | 3 +- timflow/steady/element.py | 4 +- timflow/steady/export.py | 125 ++++++++++++++++++++++++++++++++++++++ timflow/steady/model.py | 30 ++++++++- 7 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 test.json create mode 100644 test.py create mode 100644 timflow/steady/export.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4ec39be7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.enabled": false +} \ No newline at end of file diff --git a/test.json b/test.json new file mode 100644 index 00000000..a1d797ba --- /dev/null +++ b/test.json @@ -0,0 +1,41 @@ +{ + "_type": "ModelXsection", + "naq": 2, + "elementlist": [ + { + "_type": "HeadDiffLineSink1D", + "xls": -50.0, + "label": null + }, + { + "_type": "ConstantStar", + "hstar": 5, + "label": null + }, + { + "_type": "HeadDiffLineSink1D", + "xls": 50.0, + "label": null + }, + { + "_type": "FluxDiffLineSink1D", + "xls": -50.0, + "label": null + }, + { + "_type": "ConstantStar", + "hstar": 4.5, + "label": null + }, + { + "_type": "FluxDiffLineSink1D", + "xls": 50.0, + "label": null + }, + { + "_type": "ConstantStar", + "hstar": 4, + "label": null + } + ] +} \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 00000000..f6534977 --- /dev/null +++ b/test.py @@ -0,0 +1,41 @@ +import numpy as np + +import timflow.steady as tfs + +ml = tfs.ModelXsection(naq=2) +tfs.XsectionMaq( + ml, + x1=-np.inf, + x2=-50, + kaq=[1, 2], + z=[4, 3, 2, 1, 0], + c=[1000, 1000], + npor=0.3, + topboundary="semi", + hstar=5, +) +tfs.XsectionMaq( + ml, + x1=-50, + x2=50, + kaq=[1, 2], + z=[4, 3, 2, 1, 0], + c=[1000, 1000], + npor=0.3, + topboundary="semi", + hstar=4.5, +) +tfs.XsectionMaq( + ml, + x1=50, + x2=np.inf, + kaq=[1, 2], + z=[4, 3, 2, 1, 0], + c=[1000, 1000], + npor=0.3, + topboundary="semi", + hstar=4, +) +ml.solve() +print(ml.elementlist) +ml.to_json("./test.json") diff --git a/timflow/steady/aquifer.py b/timflow/steady/aquifer.py index 5f884ca1..2782f5fb 100644 --- a/timflow/steady/aquifer.py +++ b/timflow/steady/aquifer.py @@ -13,11 +13,12 @@ import pandas as pd from timflow.steady.constant import ConstantStar +from timflow.steady.export import ExportBase __all__ = ["Aquifer", "SimpleAquifer"] -class AquiferData: +class AquiferData(ExportBase): def __init__(self, model, kaq, c, z, npor, ltype, model3d=False): """Initialize aquifer data. diff --git a/timflow/steady/element.py b/timflow/steady/element.py index 8095f98e..dd74b2b1 100644 --- a/timflow/steady/element.py +++ b/timflow/steady/element.py @@ -11,10 +11,12 @@ def initialize(self): import numpy as np +from timflow.steady.export import ExportBase + __all__ = ["Element"] -class Element: +class Element(ExportBase): """Base class for all timflow.steady elements. Elements represent physical features in the aquifer system such as wells, diff --git a/timflow/steady/export.py b/timflow/steady/export.py new file mode 100644 index 00000000..f331d186 --- /dev/null +++ b/timflow/steady/export.py @@ -0,0 +1,125 @@ +import inspect +import json + + +# TODO Print logs for insight to where we get during run. +class ExportBase: + # Registry for all subclasses. + _registry = {} + + def __init_subclass__(cls) -> None: + """Add the subclass to the registry on creation.""" + cls._registry[cls.__name__] = cls + + def to_json(self, filepath) -> None: + """ + Write the contructor arguments and potential additional attributes to a JSON-file. + + :param filepath: Filepath to the to be created JSON-file. + """ + data = self.to_dict() + print("Test:") + print(data) + with open(filepath, "w") as f: + f.write(json.dumps(data, indent=4)) + + def to_dict(self): + """ + Collect the contructor arguments and potential additional attributes into a dict. + + :return: _description_ + """ + sig = inspect.signature(self.__init__) + data = {"_type": self.__class__.__name__} + for name in sig.parameters: + if name == "model": # reference to parent object + continue + # TODO Reference to other object, inhomogenities need to go to JSON + if name in ["aq", "aqin", "aqout"]: + continue + if name != "self": + value = getattr(self, name) + data[name] = self._serialize(value) + data.update(self.extra_to_dict()) + return data + + def extra_to_dict(self): + """Add the addition attributes to the dict. + + May be overloaded in the subclass. + + :return: Dict with addition parameters. + """ + return {} + + @classmethod + def from_json(cls, filepath) -> dict: + """ + Read the contructor arguments and potential addition attributes from a JSON-file. + + :param filepath: Filepath to the to be created JSON-file. + """ + with open(filepath, "r") as f: + data = json.loads(f) + return data + + @classmethod + def from_dict(cls, data: dict): + """Factory method to create an instance of this (sub)class. + + :param data: Dict with parameters + :return: Instance of this (sub)class. + """ + type_name = data.pop("_type") + subclass = cls._registry[type_name] + sig = inspect.signature(subclass.__init__) + constructor_args = {} + + for name in sig.parameters: + if name == "model": + constructor_args[name] = cls + if name != "self" and name in data: + constructor_args[name] = cls._deserialize(data.pop(name)) + obj = subclass(**constructor_args) + obj.extra_from_dict(data) + + return obj + + def extra_from_dict(self, data) -> None: + """Add the additional attributes to the (sub)class. + + May be overloaded in the subclasses. + + :param data: Dict with additional parameters. + """ + pass + + @classmethod + def _serialize(cls, value): + """Convert python objects to exportable types. + + :param value: Object for export. + :return: Object in exportable form. + """ + if isinstance(value, cls): + return value.to_dict() + + if isinstance(value, list): + return [cls._serialize(v) for v in value] + if isinstance(value, dict): + return {k: cls._serialize(v) for k, v in value.items()} + return value + + @classmethod + def _deserialize(cls, value): + """Convert a dict of values to the right python objects. + + :param value: Imported object + :return: Object as correct python-type. + """ + if isinstance(value, dict) and "_type" in value: + return cls.from_dict(value) + if isinstance(value, list): + return [cls._deserialize(v) for v in value] + if isinstance(value, dict): + return {k: cls._deserialize(v) for k, v in value.items()} diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 30caf3f4..85c5668e 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -20,6 +20,7 @@ from timflow.steady.aquifer import Aquifer, SimpleAquifer from timflow.steady.aquifer_parameters import param_3d, param_maq from timflow.steady.constant import ConstantStar +from timflow.steady.export import ExportBase from timflow.steady.plots import PlotSteady from timflow.version import check_tqdm_parallel @@ -42,7 +43,7 @@ def _compute_velocity_mp(args): return i, vv -class Model: +class Model(ExportBase): """Create a model consisting of an arbitrary sequence of aquifers and leaky layers. Notes @@ -82,6 +83,32 @@ def __init__(self, kaq, z, c, npor, ltype, model3d=False): self.initialized = False + def extra_from_dict(self, data) -> None: + """Add the additional attributes to the (sub)class. + + May be overloaded in the subclasses. + + :param data: Dict with additional parameters. + """ + if "elementlist" in data: + for e in data.elementlist: + print(e) + self.add_element(e) + + def extra_to_dict(self): + """Add the addition attributes to the dict. + + May be overloaded in the subclass. + + :return: Dict with addition parameters. + """ + extra_data = {} + if self.elementlist != []: + extra_data.update( + {"elementlist": [e.to_dict() for e in self.elementlist]} + ) + return extra_data + def initialize(self): # remove inhomogeneity elements (they are added again) self.elementlist = [e for e in self.elementlist if not e.inhomelement] @@ -1097,6 +1124,7 @@ class ModelXsection(Model): """ def __init__(self, naq=1): + self.naq = naq self.elementlist = [] self.elementdict = {} # only elements that have a label self.aq = SimpleAquifer(self, naq) From 7c5d50f8b78a1b0ea2c324004fe1b441a3fe16c4 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Thu, 6 Aug 2026 13:44:53 +0200 Subject: [PATCH 02/15] testing --- test.json | 123 +++++++++++++++++++++++++++++- test_in.py | 6 ++ test.py => test_out.py | 1 - timflow/steady/export.py | 41 ++++++---- timflow/steady/inhomogeneity1d.py | 2 + timflow/steady/model.py | 19 +++-- 6 files changed, 169 insertions(+), 23 deletions(-) create mode 100644 test_in.py rename test.py => test_out.py (96%) diff --git a/test.json b/test.json index a1d797ba..04c579e9 100644 --- a/test.json +++ b/test.json @@ -37,5 +37,126 @@ "hstar": 4, "label": null } - ] + ], + "aq": { + "_type": "SimpleAquifer", + "ml": null, + "naq": 2 + }, + "inhomdict": { + "inhom00": { + "_type": "XsectionMaq", + "x1": -Infinity, + "x2": -50, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] + }, + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] + }, + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 5, + "N": null, + "name": "inhom00" + }, + "inhom01": { + "_type": "XsectionMaq", + "x1": -50, + "x2": 50, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] + }, + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] + }, + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 4.5, + "N": null, + "name": "inhom01" + }, + "inhom02": { + "_type": "XsectionMaq", + "x1": 50, + "x2": Infinity, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] + }, + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] + }, + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 4, + "N": null, + "name": "inhom02" + } + } } \ No newline at end of file diff --git a/test_in.py b/test_in.py new file mode 100644 index 00000000..2b946146 --- /dev/null +++ b/test_in.py @@ -0,0 +1,6 @@ +import timflow.steady as tfs + +ml = tfs.ModelXsection.from_json("./test.json") +print(ml.elementlist) +ml.initialize() +# ml.solve() \ No newline at end of file diff --git a/test.py b/test_out.py similarity index 96% rename from test.py rename to test_out.py index f6534977..a5e6efb5 100644 --- a/test.py +++ b/test_out.py @@ -37,5 +37,4 @@ hstar=4, ) ml.solve() -print(ml.elementlist) ml.to_json("./test.json") diff --git a/timflow/steady/export.py b/timflow/steady/export.py index f331d186..acafdec5 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/export.py @@ -1,11 +1,16 @@ import inspect import json +from typing import Any + +from numpy import array, ndarray # TODO Print logs for insight to where we get during run. class ExportBase: # Registry for all subclasses. _registry = {} + # Storage for model object + _model = None def __init_subclass__(cls) -> None: """Add the subclass to the registry on creation.""" @@ -18,8 +23,6 @@ def to_json(self, filepath) -> None: :param filepath: Filepath to the to be created JSON-file. """ data = self.to_dict() - print("Test:") - print(data) with open(filepath, "w") as f: f.write(json.dumps(data, indent=4)) @@ -32,18 +35,17 @@ def to_dict(self): sig = inspect.signature(self.__init__) data = {"_type": self.__class__.__name__} for name in sig.parameters: - if name == "model": # reference to parent object + if name == ("model" or "ml"): # reference to parent object continue - # TODO Reference to other object, inhomogenities need to go to JSON if name in ["aq", "aqin", "aqout"]: continue if name != "self": - value = getattr(self, name) + value = getattr(self, name, None) data[name] = self._serialize(value) data.update(self.extra_to_dict()) return data - def extra_to_dict(self): + def extra_to_dict(self) -> dict[Any, Any]: """Add the addition attributes to the dict. May be overloaded in the subclass. @@ -53,15 +55,17 @@ def extra_to_dict(self): return {} @classmethod - def from_json(cls, filepath) -> dict: + def from_json(cls, filepath): """ Read the contructor arguments and potential addition attributes from a JSON-file. :param filepath: Filepath to the to be created JSON-file. """ with open(filepath, "r") as f: - data = json.loads(f) - return data + data = json.load(f) + obj = cls.from_dict(data) + obj.extra_from_dict(data) + return obj @classmethod def from_dict(cls, data: dict): @@ -72,17 +76,20 @@ def from_dict(cls, data: dict): """ type_name = data.pop("_type") subclass = cls._registry[type_name] + print(subclass) sig = inspect.signature(subclass.__init__) constructor_args = {} - + for name in sig.parameters: - if name == "model": - constructor_args[name] = cls + if name == ("model" or "ml"): + constructor_args[name] = cls._model + if name == ("aq", "aqin", "aqout"): + constructor_args[name] = cls._model.aq if name != "self" and name in data: constructor_args[name] = cls._deserialize(data.pop(name)) obj = subclass(**constructor_args) - obj.extra_from_dict(data) - + if cls._model is None: + cls._model = obj return obj def extra_from_dict(self, data) -> None: @@ -103,11 +110,12 @@ def _serialize(cls, value): """ if isinstance(value, cls): return value.to_dict() - if isinstance(value, list): return [cls._serialize(v) for v in value] if isinstance(value, dict): return {k: cls._serialize(v) for k, v in value.items()} + if isinstance(value, ndarray): + return {"ndarray": value.tolist()} return value @classmethod @@ -119,7 +127,10 @@ def _deserialize(cls, value): """ if isinstance(value, dict) and "_type" in value: return cls.from_dict(value) + if isinstance(value, dict) and "ndarray" in value: + return array(value["ndarray"]) if isinstance(value, list): return [cls._deserialize(v) for v in value] if isinstance(value, dict): return {k: cls._deserialize(v) for k, v in value.items()} + return value diff --git a/timflow/steady/inhomogeneity1d.py b/timflow/steady/inhomogeneity1d.py index 6baa2e0c..41f1f315 100644 --- a/timflow/steady/inhomogeneity1d.py +++ b/timflow/steady/inhomogeneity1d.py @@ -379,6 +379,7 @@ def __init__( N=None, name=None, ): + self.topboundary = topboundary if c is None: c = [] if z is None: @@ -459,6 +460,7 @@ def __init__( N=None, name=None, ): + self.topboundary = topboundary if z is None: z = [1, 0] ( diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 85c5668e..99686fe5 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -90,10 +90,13 @@ def extra_from_dict(self, data) -> None: :param data: Dict with additional parameters. """ + if "inhomdict" in data: + if self.aq is not None: + for name, inhom in data["inhomdict"].items(): + self.aq.inhomdict.update({name: self.from_dict(inhom)}) if "elementlist" in data: - for e in data.elementlist: - print(e) - self.add_element(e) + for e in data["elementlist"]: + self.elementlist.append(self.from_dict(e)) def extra_to_dict(self): """Add the addition attributes to the dict. @@ -104,9 +107,12 @@ def extra_to_dict(self): """ extra_data = {} if self.elementlist != []: - extra_data.update( - {"elementlist": [e.to_dict() for e in self.elementlist]} - ) + extra_data.update({"elementlist": [e.to_dict() for e in self.elementlist]}) + if self.aq is not None: + if self.aq.inhomdict != {}: + extra_data.update( + {"inhomdict": {k: v.to_dict() for k, v in self.aq.inhomdict.items()}} + ) return extra_data def initialize(self): @@ -1009,6 +1015,7 @@ class ModelMaq(Model): """ def __init__(self, kaq=1, z=None, c=None, npor=0.3, topboundary="conf", hstar=None): + self.topboundary = topboundary if c is None: c = [] if z is None: From 752a680d0545d63494934293aa4efa03acb47e2f Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Thu, 6 Aug 2026 14:29:44 +0200 Subject: [PATCH 03/15] werkend voorbeeld --- test.json | 5 ----- test_in.py | 16 +++++++++++++--- test_out.py | 11 +++++++++++ timflow/steady/export.py | 14 ++------------ timflow/steady/model.py | 15 --------------- 5 files changed, 26 insertions(+), 35 deletions(-) diff --git a/test.json b/test.json index 04c579e9..2951052c 100644 --- a/test.json +++ b/test.json @@ -38,11 +38,6 @@ "label": null } ], - "aq": { - "_type": "SimpleAquifer", - "ml": null, - "naq": 2 - }, "inhomdict": { "inhom00": { "_type": "XsectionMaq", diff --git a/test_in.py b/test_in.py index 2b946146..41fa1c1e 100644 --- a/test_in.py +++ b/test_in.py @@ -1,6 +1,16 @@ +import matplotlib.pyplot as plt +import numpy as np + import timflow.steady as tfs ml = tfs.ModelXsection.from_json("./test.json") -print(ml.elementlist) -ml.initialize() -# ml.solve() \ No newline at end of file +ml.solve() + +x = np.linspace(-200, 200, 101) +h = ml.headalongline(x, np.zeros(101)) +plt.plot(x, h[0], label="layer 0") +plt.plot(x, h[1], label="layer 1") +plt.xlabel("x (m)") +plt.ylabel("head (m)") +plt.legend(loc="best") +plt.grid() diff --git a/test_out.py b/test_out.py index a5e6efb5..977d558b 100644 --- a/test_out.py +++ b/test_out.py @@ -1,3 +1,4 @@ +import matplotlib.pyplot as plt import numpy as np import timflow.steady as tfs @@ -37,4 +38,14 @@ hstar=4, ) ml.solve() + +x = np.linspace(-200, 200, 101) +h = ml.headalongline(x, np.zeros(101)) +plt.plot(x, h[0], label="layer 0") +plt.plot(x, h[1], label="layer 1") +plt.xlabel("x (m)") +plt.ylabel("head (m)") +plt.legend(loc="best") +plt.grid() + ml.to_json("./test.json") diff --git a/timflow/steady/export.py b/timflow/steady/export.py index acafdec5..52c8df5a 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/export.py @@ -64,7 +64,8 @@ def from_json(cls, filepath): with open(filepath, "r") as f: data = json.load(f) obj = cls.from_dict(data) - obj.extra_from_dict(data) + for _,v in data["inhomdict"].items(): + cls.from_dict(v) return obj @classmethod @@ -83,8 +84,6 @@ def from_dict(cls, data: dict): for name in sig.parameters: if name == ("model" or "ml"): constructor_args[name] = cls._model - if name == ("aq", "aqin", "aqout"): - constructor_args[name] = cls._model.aq if name != "self" and name in data: constructor_args[name] = cls._deserialize(data.pop(name)) obj = subclass(**constructor_args) @@ -92,15 +91,6 @@ def from_dict(cls, data: dict): cls._model = obj return obj - def extra_from_dict(self, data) -> None: - """Add the additional attributes to the (sub)class. - - May be overloaded in the subclasses. - - :param data: Dict with additional parameters. - """ - pass - @classmethod def _serialize(cls, value): """Convert python objects to exportable types. diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 99686fe5..1e8638a5 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -83,21 +83,6 @@ def __init__(self, kaq, z, c, npor, ltype, model3d=False): self.initialized = False - def extra_from_dict(self, data) -> None: - """Add the additional attributes to the (sub)class. - - May be overloaded in the subclasses. - - :param data: Dict with additional parameters. - """ - if "inhomdict" in data: - if self.aq is not None: - for name, inhom in data["inhomdict"].items(): - self.aq.inhomdict.update({name: self.from_dict(inhom)}) - if "elementlist" in data: - for e in data["elementlist"]: - self.elementlist.append(self.from_dict(e)) - def extra_to_dict(self): """Add the addition attributes to the dict. From f6d6495de579786c6c333469f9912a111f65e220 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Thu, 6 Aug 2026 15:03:25 +0200 Subject: [PATCH 04/15] inhomogenen werken, losse elementen nog niet --- test.json | 37 ------------------------------------- test_in.py | 24 ++++++++++++++++++++++++ test_out.py | 39 +++++++++++++++++++++++++++++++-------- timflow/steady/export.py | 13 +++++++++---- timflow/steady/model.py | 8 ++++---- 5 files changed, 68 insertions(+), 53 deletions(-) diff --git a/test.json b/test.json index 2951052c..77eb6262 100644 --- a/test.json +++ b/test.json @@ -1,43 +1,6 @@ { "_type": "ModelXsection", "naq": 2, - "elementlist": [ - { - "_type": "HeadDiffLineSink1D", - "xls": -50.0, - "label": null - }, - { - "_type": "ConstantStar", - "hstar": 5, - "label": null - }, - { - "_type": "HeadDiffLineSink1D", - "xls": 50.0, - "label": null - }, - { - "_type": "FluxDiffLineSink1D", - "xls": -50.0, - "label": null - }, - { - "_type": "ConstantStar", - "hstar": 4.5, - "label": null - }, - { - "_type": "FluxDiffLineSink1D", - "xls": 50.0, - "label": null - }, - { - "_type": "ConstantStar", - "hstar": 4, - "label": null - } - ], "inhomdict": { "inhom00": { "_type": "XsectionMaq", diff --git a/test_in.py b/test_in.py index 41fa1c1e..cec7fdaf 100644 --- a/test_in.py +++ b/test_in.py @@ -14,3 +14,27 @@ plt.ylabel("head (m)") plt.legend(loc="best") plt.grid() + +# x = np.linspace(-100, 100, 101) +# h = ml.headalongline(x, np.zeros_like(x)) +# Qx, _ = ml.disvecalongline(x, np.zeros_like(x)) + +# plt.figure(figsize=(10, 3)) +# plt.subplot(121) +# plt.title("head") +# plt.plot(x, h[0], label="layer 0") +# plt.plot(x, h[1], label="layer 1") +# plt.plot(x, h[2], label="layer 2") +# plt.xlabel("x (m)") +# plt.ylabel("head (m)") +# plt.legend(loc="best") +# plt.grid() +# plt.subplot(122) +# plt.title("Qx") +# plt.plot(x, Qx[0], label="layer 0") +# plt.plot(x, Qx[1], label="layer 1") +# plt.plot(x, Qx[2], label="layer 2") +# plt.xlabel("x (m)") +# plt.ylabel("$Q_x$ (m$^2$/d)") +# plt.legend(loc="best") +# plt.grid() \ No newline at end of file diff --git a/test_out.py b/test_out.py index 977d558b..06eb9e49 100644 --- a/test_out.py +++ b/test_out.py @@ -37,15 +37,38 @@ topboundary="semi", hstar=4, ) + ml.solve() -x = np.linspace(-200, 200, 101) -h = ml.headalongline(x, np.zeros(101)) -plt.plot(x, h[0], label="layer 0") -plt.plot(x, h[1], label="layer 1") -plt.xlabel("x (m)") -plt.ylabel("head (m)") -plt.legend(loc="best") -plt.grid() + +# ml = tfs.ModelMaq(kaq=[1, 2, 4], z=[5, 4, 3, 2, 1, 0], c=[5000, 1000]) +# uf = tfs.Uflow(ml, 0.002, 0) +# rf = tfs.Constant(ml, 100, 0, 20) +# ld1 = tfs.ImpermeableWall1D(ml, xld=0, layers=[0, 1]) + +# ml.solve() +# x = np.linspace(-100, 100, 101) +# h = ml.headalongline(x, np.zeros_like(x)) +# Qx, _ = ml.disvecalongline(x, np.zeros_like(x)) + +# plt.figure(figsize=(10, 3)) +# plt.subplot(121) +# plt.title("head") +# plt.plot(x, h[0], label="layer 0") +# plt.plot(x, h[1], label="layer 1") +# plt.plot(x, h[2], label="layer 2") +# plt.xlabel("x (m)") +# plt.ylabel("head (m)") +# plt.legend(loc="best") +# plt.grid() +# plt.subplot(122) +# plt.title("Qx") +# plt.plot(x, Qx[0], label="layer 0") +# plt.plot(x, Qx[1], label="layer 1") +# plt.plot(x, Qx[2], label="layer 2") +# plt.xlabel("x (m)") +# plt.ylabel("$Q_x$ (m$^2$/d)") +# plt.legend(loc="best") +# plt.grid() ml.to_json("./test.json") diff --git a/timflow/steady/export.py b/timflow/steady/export.py index 52c8df5a..825ff185 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/export.py @@ -5,7 +5,6 @@ from numpy import array, ndarray -# TODO Print logs for insight to where we get during run. class ExportBase: # Registry for all subclasses. _registry = {} @@ -64,8 +63,15 @@ def from_json(cls, filepath): with open(filepath, "r") as f: data = json.load(f) obj = cls.from_dict(data) - for _,v in data["inhomdict"].items(): - cls.from_dict(v) + if "inhomdict" in data: + for _,v in data["inhomdict"].items(): + cls.from_dict(v) + if "elementlist" in data: + for e in data["elementlist"]: + try: + cls.from_dict(e) + except AttributeError: + pass return obj @classmethod @@ -77,7 +83,6 @@ def from_dict(cls, data: dict): """ type_name = data.pop("_type") subclass = cls._registry[type_name] - print(subclass) sig = inspect.signature(subclass.__init__) constructor_args = {} diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 1e8638a5..662cc9b1 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -84,20 +84,20 @@ def __init__(self, kaq, z, c, npor, ltype, model3d=False): self.initialized = False def extra_to_dict(self): - """Add the addition attributes to the dict. + """Add the additional attributes to the dict. - May be overloaded in the subclass. + Adds the inhomogenities to the export dict. :return: Dict with addition parameters. """ extra_data = {} - if self.elementlist != []: - extra_data.update({"elementlist": [e.to_dict() for e in self.elementlist]}) if self.aq is not None: if self.aq.inhomdict != {}: extra_data.update( {"inhomdict": {k: v.to_dict() for k, v in self.aq.inhomdict.items()}} ) + # if self.elementlist != []: + # extra_data.update({"elementlist": [e.to_dict() for e in self.elementlist]}) return extra_data def initialize(self): From 8e72a14bfb8c4e249717da774c7cdda5001b5187 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Thu, 6 Aug 2026 16:12:30 +0200 Subject: [PATCH 05/15] wip 06082026 --- test.json | 3 ++- test_in.py | 23 ----------------------- test_out.py | 33 +-------------------------------- timflow/steady/export.py | 10 ++++------ timflow/steady/model.py | 14 ++++++++++++-- 5 files changed, 19 insertions(+), 64 deletions(-) diff --git a/test.json b/test.json index 77eb6262..59a1040a 100644 --- a/test.json +++ b/test.json @@ -116,5 +116,6 @@ "N": null, "name": "inhom02" } - } + }, + "elementlist": [] } \ No newline at end of file diff --git a/test_in.py b/test_in.py index cec7fdaf..faaeef26 100644 --- a/test_in.py +++ b/test_in.py @@ -15,26 +15,3 @@ plt.legend(loc="best") plt.grid() -# x = np.linspace(-100, 100, 101) -# h = ml.headalongline(x, np.zeros_like(x)) -# Qx, _ = ml.disvecalongline(x, np.zeros_like(x)) - -# plt.figure(figsize=(10, 3)) -# plt.subplot(121) -# plt.title("head") -# plt.plot(x, h[0], label="layer 0") -# plt.plot(x, h[1], label="layer 1") -# plt.plot(x, h[2], label="layer 2") -# plt.xlabel("x (m)") -# plt.ylabel("head (m)") -# plt.legend(loc="best") -# plt.grid() -# plt.subplot(122) -# plt.title("Qx") -# plt.plot(x, Qx[0], label="layer 0") -# plt.plot(x, Qx[1], label="layer 1") -# plt.plot(x, Qx[2], label="layer 2") -# plt.xlabel("x (m)") -# plt.ylabel("$Q_x$ (m$^2$/d)") -# plt.legend(loc="best") -# plt.grid() \ No newline at end of file diff --git a/test_out.py b/test_out.py index 06eb9e49..58afbfcc 100644 --- a/test_out.py +++ b/test_out.py @@ -40,35 +40,4 @@ ml.solve() - -# ml = tfs.ModelMaq(kaq=[1, 2, 4], z=[5, 4, 3, 2, 1, 0], c=[5000, 1000]) -# uf = tfs.Uflow(ml, 0.002, 0) -# rf = tfs.Constant(ml, 100, 0, 20) -# ld1 = tfs.ImpermeableWall1D(ml, xld=0, layers=[0, 1]) - -# ml.solve() -# x = np.linspace(-100, 100, 101) -# h = ml.headalongline(x, np.zeros_like(x)) -# Qx, _ = ml.disvecalongline(x, np.zeros_like(x)) - -# plt.figure(figsize=(10, 3)) -# plt.subplot(121) -# plt.title("head") -# plt.plot(x, h[0], label="layer 0") -# plt.plot(x, h[1], label="layer 1") -# plt.plot(x, h[2], label="layer 2") -# plt.xlabel("x (m)") -# plt.ylabel("head (m)") -# plt.legend(loc="best") -# plt.grid() -# plt.subplot(122) -# plt.title("Qx") -# plt.plot(x, Qx[0], label="layer 0") -# plt.plot(x, Qx[1], label="layer 1") -# plt.plot(x, Qx[2], label="layer 2") -# plt.xlabel("x (m)") -# plt.ylabel("$Q_x$ (m$^2$/d)") -# plt.legend(loc="best") -# plt.grid() - -ml.to_json("./test.json") +ml.to_json("./test.json") \ No newline at end of file diff --git a/timflow/steady/export.py b/timflow/steady/export.py index 825ff185..ca3f97f4 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/export.py @@ -64,14 +64,12 @@ def from_json(cls, filepath): data = json.load(f) obj = cls.from_dict(data) if "inhomdict" in data: - for _,v in data["inhomdict"].items(): + for v in data["inhomdict"].values(): cls.from_dict(v) if "elementlist" in data: for e in data["elementlist"]: - try: - cls.from_dict(e) - except AttributeError: - pass + obj.aq.add_element(cls.from_dict(e)) + return obj @classmethod @@ -85,7 +83,7 @@ def from_dict(cls, data: dict): subclass = cls._registry[type_name] sig = inspect.signature(subclass.__init__) constructor_args = {} - + for name in sig.parameters: if name == ("model" or "ml"): constructor_args[name] = cls._model diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 662cc9b1..fc97f8b9 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -96,8 +96,18 @@ def extra_to_dict(self): extra_data.update( {"inhomdict": {k: v.to_dict() for k, v in self.aq.inhomdict.items()}} ) - # if self.elementlist != []: - # extra_data.update({"elementlist": [e.to_dict() for e in self.elementlist]}) + if self.elementlist != []: + no_export_list = ["HeadDiffLineSink1D", "FluxDiffLineSink1D", "ConstantStar"] + + extra_data.update( + { + "elementlist": [ + e.to_dict() + for e in self.elementlist + if e.__class__.__name__ not in no_export_list + ] + } + ) return extra_data def initialize(self): From e39324287c95265f5c6c5638e89dabbb0f6d98b6 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Fri, 7 Aug 2026 08:48:29 +0200 Subject: [PATCH 06/15] Rework export and import with __new__ --- test.json | 231 +++++++++++++++++++-------------------- test_out.py | 2 +- timflow/steady/export.py | 33 ++++-- timflow/steady/model.py | 27 ----- 4 files changed, 140 insertions(+), 153 deletions(-) diff --git a/test.json b/test.json index 59a1040a..013c1ef8 100644 --- a/test.json +++ b/test.json @@ -1,121 +1,120 @@ { - "_type": "ModelXsection", - "naq": 2, - "inhomdict": { - "inhom00": { - "_type": "XsectionMaq", - "x1": -Infinity, - "x2": -50, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 5, - "N": null, - "name": "inhom00" + "object0": { + "_type": "ModelXsection", + "naq": 2 + }, + "object1": { + "_type": "XsectionMaq", + "x1": -Infinity, + "x2": -50, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] + }, + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] + }, + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 5, + "N": null, + "name": "inhom00" + }, + "object2": { + "_type": "XsectionMaq", + "x1": -50, + "x2": 50, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] }, - "inhom01": { - "_type": "XsectionMaq", - "x1": -50, - "x2": 50, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 4.5, - "N": null, - "name": "inhom01" + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] }, - "inhom02": { - "_type": "XsectionMaq", - "x1": 50, - "x2": Infinity, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 4, - "N": null, - "name": "inhom02" - } + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 4.5, + "N": null, + "name": "inhom01" }, - "elementlist": [] + "object3": { + "_type": "XsectionMaq", + "x1": 50, + "x2": Infinity, + "kaq": { + "ndarray": [ + 1.0, + 2.0 + ] + }, + "z": { + "ndarray": [ + 4, + 3, + 2, + 1, + 0 + ] + }, + "c": { + "ndarray": [ + 1000.0, + 1000.0 + ] + }, + "npor": { + "ndarray": [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + }, + "topboundary": "semi", + "hstar": 4, + "N": null, + "name": "inhom02" + } } \ No newline at end of file diff --git a/test_out.py b/test_out.py index 58afbfcc..0a0de70e 100644 --- a/test_out.py +++ b/test_out.py @@ -39,5 +39,5 @@ ) ml.solve() - +print(ml._obj_list) ml.to_json("./test.json") \ No newline at end of file diff --git a/timflow/steady/export.py b/timflow/steady/export.py index ca3f97f4..a3bebe0f 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/export.py @@ -3,13 +3,24 @@ from typing import Any from numpy import array, ndarray +from typing_extensions import Self class ExportBase: - # Registry for all subclasses. + # Registry for all subclasses. _registry = {} # Storage for model object _model = None + # Registry for all created objects + _obj_list = [] + + def __new__(cls, *args, **kwargs) -> Self: + instance = super().__new__(cls) + frame = inspect.currentframe() + caller = frame.f_back + if caller.f_code.co_name == "": + cls._obj_list.append(instance) + return instance def __init_subclass__(cls) -> None: """Add the subclass to the registry on creation.""" @@ -21,7 +32,11 @@ def to_json(self, filepath) -> None: :param filepath: Filepath to the to be created JSON-file. """ - data = self.to_dict() + data = {} + i = 0 + for obj in self._obj_list: + data.update({f"object{i}": obj.to_dict()}) + i += 1 with open(filepath, "w") as f: f.write(json.dumps(data, indent=4)) @@ -60,16 +75,16 @@ def from_json(cls, filepath): :param filepath: Filepath to the to be created JSON-file. """ + obj = None with open(filepath, "r") as f: data = json.load(f) - obj = cls.from_dict(data) - if "inhomdict" in data: - for v in data["inhomdict"].values(): + for k, v in data.items(): + if k == "object0": + obj = cls.from_dict(v) + else: cls.from_dict(v) - if "elementlist" in data: - for e in data["elementlist"]: - obj.aq.add_element(cls.from_dict(e)) - + if obj is None: + raise ImportError return obj @classmethod diff --git a/timflow/steady/model.py b/timflow/steady/model.py index fc97f8b9..109aae92 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -83,33 +83,6 @@ def __init__(self, kaq, z, c, npor, ltype, model3d=False): self.initialized = False - def extra_to_dict(self): - """Add the additional attributes to the dict. - - Adds the inhomogenities to the export dict. - - :return: Dict with addition parameters. - """ - extra_data = {} - if self.aq is not None: - if self.aq.inhomdict != {}: - extra_data.update( - {"inhomdict": {k: v.to_dict() for k, v in self.aq.inhomdict.items()}} - ) - if self.elementlist != []: - no_export_list = ["HeadDiffLineSink1D", "FluxDiffLineSink1D", "ConstantStar"] - - extra_data.update( - { - "elementlist": [ - e.to_dict() - for e in self.elementlist - if e.__class__.__name__ not in no_export_list - ] - } - ) - return extra_data - def initialize(self): # remove inhomogeneity elements (they are added again) self.elementlist = [e for e in self.elementlist if not e.inhomelement] From 0917c41c9c8a341b159b33ea628dedd0b7fdfabf Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Fri, 7 Aug 2026 09:39:04 +0200 Subject: [PATCH 07/15] Cleanup of test files --- test.json | 120 ----------------------- test_in.py | 17 ---- test_out.py | 43 -------- timflow/steady/aquifer.py | 4 +- timflow/steady/{export.py => base_io.py} | 31 +++--- timflow/steady/constant.py | 1 + timflow/steady/element.py | 4 +- timflow/steady/model.py | 4 +- 8 files changed, 20 insertions(+), 204 deletions(-) delete mode 100644 test.json delete mode 100644 test_in.py delete mode 100644 test_out.py rename timflow/steady/{export.py => base_io.py} (86%) diff --git a/test.json b/test.json deleted file mode 100644 index 013c1ef8..00000000 --- a/test.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "object0": { - "_type": "ModelXsection", - "naq": 2 - }, - "object1": { - "_type": "XsectionMaq", - "x1": -Infinity, - "x2": -50, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 5, - "N": null, - "name": "inhom00" - }, - "object2": { - "_type": "XsectionMaq", - "x1": -50, - "x2": 50, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 4.5, - "N": null, - "name": "inhom01" - }, - "object3": { - "_type": "XsectionMaq", - "x1": 50, - "x2": Infinity, - "kaq": { - "ndarray": [ - 1.0, - 2.0 - ] - }, - "z": { - "ndarray": [ - 4, - 3, - 2, - 1, - 0 - ] - }, - "c": { - "ndarray": [ - 1000.0, - 1000.0 - ] - }, - "npor": { - "ndarray": [ - 0.3, - 0.3, - 0.3, - 0.3 - ] - }, - "topboundary": "semi", - "hstar": 4, - "N": null, - "name": "inhom02" - } -} \ No newline at end of file diff --git a/test_in.py b/test_in.py deleted file mode 100644 index faaeef26..00000000 --- a/test_in.py +++ /dev/null @@ -1,17 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -import timflow.steady as tfs - -ml = tfs.ModelXsection.from_json("./test.json") -ml.solve() - -x = np.linspace(-200, 200, 101) -h = ml.headalongline(x, np.zeros(101)) -plt.plot(x, h[0], label="layer 0") -plt.plot(x, h[1], label="layer 1") -plt.xlabel("x (m)") -plt.ylabel("head (m)") -plt.legend(loc="best") -plt.grid() - diff --git a/test_out.py b/test_out.py deleted file mode 100644 index 0a0de70e..00000000 --- a/test_out.py +++ /dev/null @@ -1,43 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -import timflow.steady as tfs - -ml = tfs.ModelXsection(naq=2) -tfs.XsectionMaq( - ml, - x1=-np.inf, - x2=-50, - kaq=[1, 2], - z=[4, 3, 2, 1, 0], - c=[1000, 1000], - npor=0.3, - topboundary="semi", - hstar=5, -) -tfs.XsectionMaq( - ml, - x1=-50, - x2=50, - kaq=[1, 2], - z=[4, 3, 2, 1, 0], - c=[1000, 1000], - npor=0.3, - topboundary="semi", - hstar=4.5, -) -tfs.XsectionMaq( - ml, - x1=50, - x2=np.inf, - kaq=[1, 2], - z=[4, 3, 2, 1, 0], - c=[1000, 1000], - npor=0.3, - topboundary="semi", - hstar=4, -) - -ml.solve() -print(ml._obj_list) -ml.to_json("./test.json") \ No newline at end of file diff --git a/timflow/steady/aquifer.py b/timflow/steady/aquifer.py index 2782f5fb..008839ab 100644 --- a/timflow/steady/aquifer.py +++ b/timflow/steady/aquifer.py @@ -13,12 +13,12 @@ import pandas as pd from timflow.steady.constant import ConstantStar -from timflow.steady.export import ExportBase +from timflow.steady.base_io import BaseIO __all__ = ["Aquifer", "SimpleAquifer"] -class AquiferData(ExportBase): +class AquiferData(BaseIO): def __init__(self, model, kaq, c, z, npor, ltype, model3d=False): """Initialize aquifer data. diff --git a/timflow/steady/export.py b/timflow/steady/base_io.py similarity index 86% rename from timflow/steady/export.py rename to timflow/steady/base_io.py index a3bebe0f..18b8b5f1 100644 --- a/timflow/steady/export.py +++ b/timflow/steady/base_io.py @@ -6,7 +6,7 @@ from typing_extensions import Self -class ExportBase: +class BaseIO: # Registry for all subclasses. _registry = {} # Storage for model object @@ -19,7 +19,7 @@ def __new__(cls, *args, **kwargs) -> Self: frame = inspect.currentframe() caller = frame.f_back if caller.f_code.co_name == "": - cls._obj_list.append(instance) + cls._obj_list.append((instance, kwargs)) return instance def __init_subclass__(cls) -> None: @@ -34,17 +34,18 @@ def to_json(self, filepath) -> None: """ data = {} i = 0 - for obj in self._obj_list: - data.update({f"object{i}": obj.to_dict()}) + for item in self._obj_list: + obj, kwargs = item + data.update({f"object{i}": obj.to_dict(**kwargs)}) i += 1 with open(filepath, "w") as f: f.write(json.dumps(data, indent=4)) - def to_dict(self): + def to_dict(self, **kwargs): """ - Collect the contructor arguments and potential additional attributes into a dict. + Collect the contructor arguments into a dict. - :return: _description_ + :return: Dict with the arguments. """ sig = inspect.signature(self.__init__) data = {"_type": self.__class__.__name__} @@ -54,20 +55,14 @@ def to_dict(self): if name in ["aq", "aqin", "aqout"]: continue if name != "self": - value = getattr(self, name, None) + # For kwargs as inputs + value = kwargs.get(name, None) + # If not used as input -> collect from attributes + if value is None: + value = getattr(self, name, None) data[name] = self._serialize(value) - data.update(self.extra_to_dict()) return data - def extra_to_dict(self) -> dict[Any, Any]: - """Add the addition attributes to the dict. - - May be overloaded in the subclass. - - :return: Dict with addition parameters. - """ - return {} - @classmethod def from_json(cls, filepath): """ diff --git a/timflow/steady/constant.py b/timflow/steady/constant.py index dde0baba..38fd6951 100644 --- a/timflow/steady/constant.py +++ b/timflow/steady/constant.py @@ -33,6 +33,7 @@ def __init__( ) # Defined here and not in Element as other elements can have multiple parameters # per layers: + self.layer = layer self.nparam = 1 self.nunknowns = 0 self.xr = xr diff --git a/timflow/steady/element.py b/timflow/steady/element.py index dd74b2b1..09e58f4e 100644 --- a/timflow/steady/element.py +++ b/timflow/steady/element.py @@ -11,12 +11,12 @@ def initialize(self): import numpy as np -from timflow.steady.export import ExportBase +from timflow.steady.base_io import BaseIO __all__ = ["Element"] -class Element(ExportBase): +class Element(BaseIO): """Base class for all timflow.steady elements. Elements represent physical features in the aquifer system such as wells, diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 109aae92..4c45e840 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -20,7 +20,7 @@ from timflow.steady.aquifer import Aquifer, SimpleAquifer from timflow.steady.aquifer_parameters import param_3d, param_maq from timflow.steady.constant import ConstantStar -from timflow.steady.export import ExportBase +from timflow.steady.base_io import BaseIO from timflow.steady.plots import PlotSteady from timflow.version import check_tqdm_parallel @@ -43,7 +43,7 @@ def _compute_velocity_mp(args): return i, vv -class Model(ExportBase): +class Model(BaseIO): """Create a model consisting of an arbitrary sequence of aquifers and leaky layers. Notes From 5a5fb1765d4d3e6a08ae9e45bfc01bb68e28e58f Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Fri, 7 Aug 2026 09:46:26 +0200 Subject: [PATCH 08/15] Spellcheck and documentation --- timflow/steady/base_io.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index 18b8b5f1..fe11fa20 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -1,6 +1,5 @@ import inspect import json -from typing import Any from numpy import array, ndarray from typing_extensions import Self @@ -11,10 +10,18 @@ class BaseIO: _registry = {} # Storage for model object _model = None - # Registry for all created objects + # Registry for all created objects with their kwargs _obj_list = [] def __new__(cls, *args, **kwargs) -> Self: + """Register created objects in script. + + When a object is created in the script, register this object with the + constructor kwargs. If the object is made inside of another class or function + don't register it. + + :return: Created object. + """ instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back @@ -23,14 +30,14 @@ def __new__(cls, *args, **kwargs) -> Self: return instance def __init_subclass__(cls) -> None: - """Add the subclass to the registry on creation.""" + """Add the subclass to the registry on inheritance.""" cls._registry[cls.__name__] = cls def to_json(self, filepath) -> None: """ - Write the contructor arguments and potential additional attributes to a JSON-file. + Write the constructor arguments to a JSON-file. - :param filepath: Filepath to the to be created JSON-file. + :param filepath: Filepath for the to be created JSON-file. """ data = {} i = 0 @@ -43,7 +50,7 @@ def to_json(self, filepath) -> None: def to_dict(self, **kwargs): """ - Collect the contructor arguments into a dict. + Collect the constructor arguments into a dict. :return: Dict with the arguments. """ @@ -66,7 +73,7 @@ def to_dict(self, **kwargs): @classmethod def from_json(cls, filepath): """ - Read the contructor arguments and potential addition attributes from a JSON-file. + Read the constructor arguments and potential addition attributes from a JSON-file. :param filepath: Filepath to the to be created JSON-file. """ @@ -74,7 +81,7 @@ def from_json(cls, filepath): with open(filepath, "r") as f: data = json.load(f) for k, v in data.items(): - if k == "object0": + if k == "object0": # Model object is always first created. obj = cls.from_dict(v) else: cls.from_dict(v) From 79a494593b9b5c4c75bc8d8f89039b47e319d45c Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Fri, 7 Aug 2026 10:07:45 +0200 Subject: [PATCH 09/15] reorder of methods --- timflow/steady/base_io.py | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index fe11fa20..2c156661 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -13,15 +13,19 @@ class BaseIO: # Registry for all created objects with their kwargs _obj_list = [] + def __init_subclass__(cls) -> None: + """Add the subclass to the registry on inheritance.""" + cls._registry[cls.__name__] = cls + def __new__(cls, *args, **kwargs) -> Self: """Register created objects in script. - + When a object is created in the script, register this object with the constructor kwargs. If the object is made inside of another class or function don't register it. :return: Created object. - """ + """ instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back @@ -29,10 +33,6 @@ def __new__(cls, *args, **kwargs) -> Self: cls._obj_list.append((instance, kwargs)) return instance - def __init_subclass__(cls) -> None: - """Add the subclass to the registry on inheritance.""" - cls._registry[cls.__name__] = cls - def to_json(self, filepath) -> None: """ Write the constructor arguments to a JSON-file. @@ -70,6 +70,23 @@ def to_dict(self, **kwargs): data[name] = self._serialize(value) return data + @classmethod + def _serialize(cls, value): + """Convert python objects to exportable types. + + :param value: Object for export. + :return: Object in exportable form. + """ + if isinstance(value, cls): + return value.to_dict() + if isinstance(value, list): + return [cls._serialize(v) for v in value] + if isinstance(value, dict): + return {k: cls._serialize(v) for k, v in value.items()} + if isinstance(value, ndarray): + return {"ndarray": value.tolist()} + return value + @classmethod def from_json(cls, filepath): """ @@ -83,10 +100,10 @@ def from_json(cls, filepath): for k, v in data.items(): if k == "object0": # Model object is always first created. obj = cls.from_dict(v) - else: - cls.from_dict(v) - if obj is None: - raise ImportError + if obj is None: # No model in json + raise ImportError + cls.from_dict(v) + return obj @classmethod @@ -111,23 +128,6 @@ def from_dict(cls, data: dict): cls._model = obj return obj - @classmethod - def _serialize(cls, value): - """Convert python objects to exportable types. - - :param value: Object for export. - :return: Object in exportable form. - """ - if isinstance(value, cls): - return value.to_dict() - if isinstance(value, list): - return [cls._serialize(v) for v in value] - if isinstance(value, dict): - return {k: cls._serialize(v) for k, v in value.items()} - if isinstance(value, ndarray): - return {"ndarray": value.tolist()} - return value - @classmethod def _deserialize(cls, value): """Convert a dict of values to the right python objects. From 8ad63370256e39113355cbcf39aeb0ec269ea461 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Mon, 10 Aug 2026 08:42:47 +0200 Subject: [PATCH 10/15] .vscode removed --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4ec39be7..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cSpell.enabled": false -} \ No newline at end of file From 425130b32924f026e279c13870aee32043e5f1b0 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Mon, 10 Aug 2026 11:55:19 +0200 Subject: [PATCH 11/15] Positional args --- timflow/steady/base_io.py | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index 2c156661..93a9066b 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -17,20 +17,13 @@ def __init_subclass__(cls) -> None: """Add the subclass to the registry on inheritance.""" cls._registry[cls.__name__] = cls + # TODO FIXME Add classes per model in separate lists. def __new__(cls, *args, **kwargs) -> Self: - """Register created objects in script. - - When a object is created in the script, register this object with the - constructor kwargs. If the object is made inside of another class or function - don't register it. - - :return: Created object. - """ instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back if caller.f_code.co_name == "": - cls._obj_list.append((instance, kwargs)) + cls._obj_list.append((instance, args, kwargs)) return instance def to_json(self, filepath) -> None: @@ -42,32 +35,42 @@ def to_json(self, filepath) -> None: data = {} i = 0 for item in self._obj_list: - obj, kwargs = item - data.update({f"object{i}": obj.to_dict(**kwargs)}) + obj, args, kwargs = item + data.update({f"object{i}": obj.to_dict(args, kwargs)}) i += 1 with open(filepath, "w") as f: f.write(json.dumps(data, indent=4)) - def to_dict(self, **kwargs): + def to_dict(self, args, kwargs): """ Collect the constructor arguments into a dict. :return: Dict with the arguments. """ + pos_args = list(args) sig = inspect.signature(self.__init__) + # Reference to class for recreation data = {"_type": self.__class__.__name__} for name in sig.parameters: - if name == ("model" or "ml"): # reference to parent object - continue - if name in ["aq", "aqin", "aqout"]: + if name in ("model", "ml"): # reference to model object + pos_args.pop(0) continue - if name != "self": - # For kwargs as inputs + # For positional args as input + if pos_args != []: + value = pos_args.pop(0) + # For kwargs as inputs + else: value = kwargs.get(name, None) - # If not used as input -> collect from attributes - if value is None: - value = getattr(self, name, None) - data[name] = self._serialize(value) + # Defaults from signature. + if ( + value is None + and sig.parameters[name].default is not inspect.Parameter.empty + ): + value = sig.parameters[name].default + # If not used as input -> collect from attributes + if value is None: + value = getattr(self, name, None) + data[name] = self._serialize(value) return data @classmethod From 8a28ba9259a3eea940a8660a0fcc705adea08946 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Mon, 10 Aug 2026 13:36:23 +0200 Subject: [PATCH 12/15] Storage and Loading of multiple models per script. --- timflow/steady/base_io.py | 47 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index 93a9066b..b0aaaf4a 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -8,22 +8,38 @@ class BaseIO: # Registry for all subclasses. _registry = {} - # Storage for model object - _model = None - # Registry for all created objects with their kwargs - _obj_list = [] + # Registry for all created objects with their kwargs for storing. + _obj_lists = {} + # Registry for model instance for storing. + _models = {} + # Storage for model object for loading. + _setup_model = None def __init_subclass__(cls) -> None: """Add the subclass to the registry on inheritance.""" cls._registry[cls.__name__] = cls - # TODO FIXME Add classes per model in separate lists. def __new__(cls, *args, **kwargs) -> Self: instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back if caller.f_code.co_name == "": - cls._obj_list.append((instance, args, kwargs)) + # If a new Model object create a new list before adding it. + if "Model" in str(cls.__name__): + m = f"model{len(cls._obj_lists)}" + cls._models.update({instance: m}) + cls._obj_lists.update({m:[]}) + cls._obj_lists[m].append((instance, args, kwargs)) + # Other objects are added to the list of the model they have been + # added to. + else: + if args != (): + m_inst = args[0] + else: + m_inst = kwargs.get("model", None) + if m_inst is None: + m_inst = kwargs.get("ml") + cls._obj_lists[cls._models[m_inst]].append((instance, args, kwargs)) return instance def to_json(self, filepath) -> None: @@ -34,7 +50,7 @@ def to_json(self, filepath) -> None: """ data = {} i = 0 - for item in self._obj_list: + for item in self._obj_lists[self._models[self]]: obj, args, kwargs = item data.update({f"object{i}": obj.to_dict(args, kwargs)}) i += 1 @@ -97,16 +113,17 @@ def from_json(cls, filepath): :param filepath: Filepath to the to be created JSON-file. """ - obj = None + # reset the reference to the Model instance for setup. + if cls._setup_model is not None: + cls._setup_model = None with open(filepath, "r") as f: data = json.load(f) for k, v in data.items(): if k == "object0": # Model object is always first created. obj = cls.from_dict(v) - if obj is None: # No model in json + if "obj" not in locals(): # No model in json raise ImportError cls.from_dict(v) - return obj @classmethod @@ -116,19 +133,19 @@ def from_dict(cls, data: dict): :param data: Dict with parameters :return: Instance of this (sub)class. """ - type_name = data.pop("_type") + type_name = data["_type"] subclass = cls._registry[type_name] sig = inspect.signature(subclass.__init__) constructor_args = {} for name in sig.parameters: - if name == ("model" or "ml"): - constructor_args[name] = cls._model + if name in ("model", "ml"): + constructor_args[name] = cls._setup_model if name != "self" and name in data: constructor_args[name] = cls._deserialize(data.pop(name)) obj = subclass(**constructor_args) - if cls._model is None: - cls._model = obj + if cls._setup_model is None: + cls._setup_model = obj return obj @classmethod From 96cf7a50413218de34739ce35c9aa6e96bb67947 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Mon, 10 Aug 2026 13:57:50 +0200 Subject: [PATCH 13/15] Use inspect.signature.bind for argument input --- timflow/steady/base_io.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index b0aaaf4a..5f0f6a1b 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -28,7 +28,7 @@ def __new__(cls, *args, **kwargs) -> Self: if "Model" in str(cls.__name__): m = f"model{len(cls._obj_lists)}" cls._models.update({instance: m}) - cls._obj_lists.update({m:[]}) + cls._obj_lists.update({m: []}) cls._obj_lists[m].append((instance, args, kwargs)) # Other objects are added to the list of the model they have been # added to. @@ -63,30 +63,17 @@ def to_dict(self, args, kwargs): :return: Dict with the arguments. """ - pos_args = list(args) sig = inspect.signature(self.__init__) + bound = sig.bind(*args, **kwargs) # Reference to class for recreation data = {"_type": self.__class__.__name__} - for name in sig.parameters: - if name in ("model", "ml"): # reference to model object - pos_args.pop(0) - continue - # For positional args as input - if pos_args != []: - value = pos_args.pop(0) - # For kwargs as inputs - else: - value = kwargs.get(name, None) - # Defaults from signature. - if ( - value is None - and sig.parameters[name].default is not inspect.Parameter.empty - ): - value = sig.parameters[name].default - # If not used as input -> collect from attributes - if value is None: - value = getattr(self, name, None) - data[name] = self._serialize(value) + data.update( + { + k: self._serialize(v) + for k, v in bound.arguments.items() + if k not in ("model", "ml") + } + ) return data @classmethod From 8b2699c1e9502dce87df533083d08515393c0613 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Mon, 17 Aug 2026 14:18:48 +0200 Subject: [PATCH 14/15] Remove some unneeded logic and clarify var names --- timflow/steady/base_io.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index 5f0f6a1b..0c258d2c 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -7,29 +7,31 @@ class BaseIO: # Registry for all subclasses. - _registry = {} + _class_registry = {} # Registry for all created objects with their kwargs for storing. - _obj_lists = {} + _obj_registry = {} # Registry for model instance for storing. - _models = {} - # Storage for model object for loading. - _setup_model = None + _model_registry = {} def __init_subclass__(cls) -> None: """Add the subclass to the registry on inheritance.""" - cls._registry[cls.__name__] = cls + cls._class_registry[cls.__name__] = cls def __new__(cls, *args, **kwargs) -> Self: + """Add all newly created object to a registry if they are created directly. + + :return: instance of the (sub)class + """ instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back if caller.f_code.co_name == "": # If a new Model object create a new list before adding it. if "Model" in str(cls.__name__): - m = f"model{len(cls._obj_lists)}" - cls._models.update({instance: m}) - cls._obj_lists.update({m: []}) - cls._obj_lists[m].append((instance, args, kwargs)) + m = f"model{len(cls._obj_registry)}" + cls._model_registry.update({instance: m}) + cls._obj_registry.update({m: []}) + cls._obj_registry[m].append((instance, args, kwargs)) # Other objects are added to the list of the model they have been # added to. else: @@ -39,7 +41,7 @@ def __new__(cls, *args, **kwargs) -> Self: m_inst = kwargs.get("model", None) if m_inst is None: m_inst = kwargs.get("ml") - cls._obj_lists[cls._models[m_inst]].append((instance, args, kwargs)) + cls._obj_registry[cls._model_registry[m_inst]].append((instance, args, kwargs)) return instance def to_json(self, filepath) -> None: @@ -50,7 +52,7 @@ def to_json(self, filepath) -> None: """ data = {} i = 0 - for item in self._obj_lists[self._models[self]]: + for item in self._obj_registry[self._model_registry[self]]: obj, args, kwargs = item data.update({f"object{i}": obj.to_dict(args, kwargs)}) i += 1 @@ -83,8 +85,6 @@ def _serialize(cls, value): :param value: Object for export. :return: Object in exportable form. """ - if isinstance(value, cls): - return value.to_dict() if isinstance(value, list): return [cls._serialize(v) for v in value] if isinstance(value, dict): @@ -100,14 +100,13 @@ def from_json(cls, filepath): :param filepath: Filepath to the to be created JSON-file. """ - # reset the reference to the Model instance for setup. - if cls._setup_model is not None: - cls._setup_model = None + cls._setup_model = None with open(filepath, "r") as f: - data = json.load(f) + data: dict = json.load(f) for k, v in data.items(): if k == "object0": # Model object is always first created. obj = cls.from_dict(v) + continue if "obj" not in locals(): # No model in json raise ImportError cls.from_dict(v) @@ -120,8 +119,8 @@ def from_dict(cls, data: dict): :param data: Dict with parameters :return: Instance of this (sub)class. """ - type_name = data["_type"] - subclass = cls._registry[type_name] + type_name: str = data["_type"] + subclass = cls._class_registry[type_name] sig = inspect.signature(subclass.__init__) constructor_args = {} From 168d1510f8f74c606042083fce8189d7df61a900 Mon Sep 17 00:00:00 2001 From: Vincent Jilesen Date: Thu, 27 Aug 2026 09:34:08 +0200 Subject: [PATCH 15/15] Lint error fixed --- timflow/steady/aquifer.py | 2 +- timflow/steady/base_io.py | 8 +++++--- timflow/steady/model.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/timflow/steady/aquifer.py b/timflow/steady/aquifer.py index 008839ab..8f5a8781 100644 --- a/timflow/steady/aquifer.py +++ b/timflow/steady/aquifer.py @@ -12,8 +12,8 @@ import numpy as np import pandas as pd -from timflow.steady.constant import ConstantStar from timflow.steady.base_io import BaseIO +from timflow.steady.constant import ConstantStar __all__ = ["Aquifer", "SimpleAquifer"] diff --git a/timflow/steady/base_io.py b/timflow/steady/base_io.py index 0c258d2c..faff6e22 100644 --- a/timflow/steady/base_io.py +++ b/timflow/steady/base_io.py @@ -21,7 +21,7 @@ def __new__(cls, *args, **kwargs) -> Self: """Add all newly created object to a registry if they are created directly. :return: instance of the (sub)class - """ + """ instance = super().__new__(cls) frame = inspect.currentframe() caller = frame.f_back @@ -41,7 +41,9 @@ def __new__(cls, *args, **kwargs) -> Self: m_inst = kwargs.get("model", None) if m_inst is None: m_inst = kwargs.get("ml") - cls._obj_registry[cls._model_registry[m_inst]].append((instance, args, kwargs)) + cls._obj_registry[cls._model_registry[m_inst]].append( + (instance, args, kwargs) + ) return instance def to_json(self, filepath) -> None: @@ -108,7 +110,7 @@ def from_json(cls, filepath): obj = cls.from_dict(v) continue if "obj" not in locals(): # No model in json - raise ImportError + raise ImportError("No main model found in the JSON-file.") cls.from_dict(v) return obj diff --git a/timflow/steady/model.py b/timflow/steady/model.py index 4c45e840..34c13ea4 100644 --- a/timflow/steady/model.py +++ b/timflow/steady/model.py @@ -19,8 +19,8 @@ from timflow.steady.aquifer import Aquifer, SimpleAquifer from timflow.steady.aquifer_parameters import param_3d, param_maq -from timflow.steady.constant import ConstantStar from timflow.steady.base_io import BaseIO +from timflow.steady.constant import ConstantStar from timflow.steady.plots import PlotSteady from timflow.version import check_tqdm_parallel