diff --git a/README.md b/README.md index f86090a..5dcc0ea 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ year = {2023} * Windows, OSX or Linux * [EPANET 2.2](https://github.com/OpenWaterAnalytics/epanet) +Linux: `sudo cp libepanet2.so /lib/x86_64-linux-gnu/libepanet.so` + ↑ [Back to top](#table-of-contents) ## How to install @@ -81,6 +83,16 @@ year = {2023} >>> d.getNodeElevations() ``` +**Minumun Example using custom Library:** +```python +>>> from epyt import epanet +>>> +>>>epanetlib=os.path.join(os.getcwd(), 'epyt','libraries','win','epanet2.dll') +>>>msxlib=os.path.join(os.getcwd(), 'epyt','libraries','win','epanetmsx.dll') +>>>d = epanet(inpname, customlib=epanetlib, msx=True) +>>>d.loadMSXFile(msxname, customMSXlib=msxlib) +``` + **More examples:** [https://github.com/KIOS-Research/EPYT/tree/main/epyt/examples](https://github.com/KIOS-Research/EPYT/tree/main/epyt/examples#readme) diff --git a/epyt/__init__.py b/epyt/__init__.py index f48588b..ea63f58 100644 --- a/epyt/__init__.py +++ b/epyt/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- __author__ = """Marios S. Kyriakou""" __email__ = "kiriakou.marios@ucy.ac.cy" -__version__ = "1.0.9-beta.4" +__version__ = "1.0.9-beta.6" __msxversion__ = "2.0.0" __copyright__ = """Copyright 2022, KIOS Research and Innovation Center of Excellence (KIOS CoE), University of Cyprus (www.kios.org.cy).""" diff --git a/epyt/epanet.py b/epyt/epanet.py index 33b69ca..e476d4b 100644 --- a/epyt/epanet.py +++ b/epyt/epanet.py @@ -502,9 +502,14 @@ def isList(var): class epanet: - """ EPyt main functions class """ + """ EPyt main functions class - def __init__(self, *argv, version=2.2, ph=False, loadfile=False, msx=False): + Example with custom library + epanetlib=os.path.join(os.getcwd(), 'epyt','libraries','win','epanet2.dll') + d = epanet(inpname, msx=True,customlib=epanetlib) + """ + + def __init__(self, *argv, version=2.2, ph=False, loadfile=False, msx=False, customlib=None): # Constants # Demand model types. DDA #0 Demand driven analysis, # PDA #1 Pressure driven analysis. @@ -565,7 +570,7 @@ def __init__(self, *argv, version=2.2, ph=False, loadfile=False, msx=False): # Initial attributes self.classversion = __version__ - self.api = epanetapi(version, msx=msx) + self.api = epanetapi(version, msx=msx, customlib=customlib) print(f'EPANET version {self.getVersion()} ' f'loaded (EPyT version {self.classversion}).') @@ -10373,9 +10378,14 @@ def unload(self): safe_delete(self.TempInpFile) cwd = os.getcwd() - tmp_files = list(filter(lambda f: os.path.isfile(os.path.join(cwd, f)) - and f.startswith("s") and 6 <= len(f) <= 8 and "." not in f and "_" in f, - os.listdir(cwd))) + files = os.listdir(cwd) + tmp_files = [ + f for f in files + if os.path.isfile(os.path.join(cwd, f)) and + (f.startswith('s') or f.startswith('en')) and + 6 <= len(f) <= 8 and + "." not in f + ] tmp_files_paths = [os.path.join(cwd, f) for f in tmp_files] safe_delete(tmp_files_paths) @@ -11157,14 +11167,20 @@ def __setNodeDemandPattern(self, fun, propertyCode, value, *argv): """MSX Functions""" - def loadMSXFile(self, msxname, ignore_properties=False): + def loadMSXFile(self, msxname, customMSXlib=None, ignore_properties=False): """Loads an msx file Example: d.loadMSXFile('net2-cl2.msx') - """ + + Example using custom msx library : + msxlib=os.path.join(os.getcwd(), 'epyt','libraries','win','epanetmsx.dll') + + d = epanet(inpname, msx=True,customlib=epanetlib) + d.loadMSXFile(msxname,customMSXlib=msxlib)""" + self.msxname = msxname[:-4] + '_temp.msx' copyfile(msxname, self.msxname) - self.msx = epanetmsxapi(self.msxname) + self.msx = epanetmsxapi(self.msxname, customMSXlib=customMSXlib) print(f'MSX version {__msxversion__}.') if ignore_properties: @@ -11200,16 +11216,6 @@ def loadMSXFile(self, msxname, ignore_properties=False): self.msx.MSXSourceNodeNameID = self.getMSXSourceNodeNameID() self.msx.MSXPattern = self.getMSXPattern() - # options - self.solver = self.getMSXSolver() - self.areaunits = self.getMSXAreaUnits() - self.rateunits = self.getMSXRateUnits() - self.rtol = self.getMSXRtol() - self.atol = self.getMSXAtol() - self.timestep = self.getMSXTimeStep() - self.coupling = self.getMSXCoupling() - self.compiler = self.getMSXCompiler() - return self.msx def unloadMSX(self): @@ -12753,13 +12759,17 @@ def getMSXComputedQualitySpecie(self, *args): specie = self.getMSXSpeciesNameID() else: specie = args[0] - - link_indices = np.arange(1, self.getLinkCount() + 1) - node_indices = np.arange(1, self.get_node_count() + 1) + msx_time_step = self.getMSXTimeStep() + link_indices = range(1, self.getLinkCount() + 1) + node_indices = range(1, self.getNodeCount() + 1) speciename = self.getMSXSpeciesIndex(specie) + time_steps = int(self.getTimeSimulationDuration() / msx_time_step) + link_count = self.getLinkCount() + node_count = self.getNodeCount() + value = {} - node_quality = np.empty((1, len(node_indices), len(speciename))) - link_quality = np.empty((1, len(node_indices), len(speciename))) + value["NodeQuality"] = [np.zeros((node_count, 1)) for _ in range(time_steps + 1)] + value["LinkQuality"] = [np.zeros((link_count, 1)) for _ in range(time_steps + 1)] self.solveMSXCompleteHydraulics() self.initializeMSXQualityAnalysis(0) @@ -12767,37 +12777,49 @@ def getMSXComputedQualitySpecie(self, *args): k = 1 tleft = 1 t = 0 - time = [0] + value["Time"] = [[0]] if node_indices[-1] < link_indices[-1]: for i in range(len(speciename)): for lnk in link_indices: - print(lnk) - link_quality[k, lnk - 1, i] = self.getMSXNodeInitqualValue([lnk])[speciename[i]] + link_init_qual_value = self.getMSXLinkInitqualValue([lnk]) + value["LinkQuality"][k - 1][lnk - 1][i - 1] = link_init_qual_value[0][0] if lnk < node_indices[-1] + 1: - node_quality[k, lnk - 1, i] = self.getMSXNodeInitqualValue([lnk])[speciename[i]] + node_init_qual_value = self.getMSXNodeInitqualValue([lnk]) + value["NodeQuality"][k - 1][lnk - 1][i - 1] = node_init_qual_value[0][0] + else: for i in range(len(speciename)): for lnk in node_indices: - node_quality[k, lnk - 1, i] = self.MSXNodeInitqualValue([lnk])[speciename[i]] + node_init_qual_value = self.getMSXNodeInitqualValue(lnk) + value["NodeQuality"][k - 1][lnk - 1][i - 1] = node_init_qual_value[0][0] if lnk < link_indices[-1] + 1: - link_quality[k, lnk - 1, i] = self.getMSXLinkInitqualValue([lnk])[speciename[i]] - time_sim = self.getTimeSimulationDuration() - while tleft > 0 and time_sim != t: + link_init_qual_value = self.getMSXLinkInitqualValue([lnk]) + value["LinkQuality"][k - 1][lnk - 1][i - 1] = link_init_qual_value[0][0] + + time_smle = self.getTimeSimulationDuration() + while tleft > 0 and time_smle != t: k = k + 1 t, tleft = self.stepMSXQualityAnalysisTimeLeft() - for i in range(len(speciename)): - if node_indices[-1] < link_indices[-1]: + if node_indices[-1] < link_indices[-1]: + for i in range(len(speciename)): for lnk in link_indices: - link_quality[k, lnk - 1, i] = self.getMSXSpeciesConcentration(1, lnk, speciename[i]) + value["LinkQuality"][k - 1][lnk - 1, i - 1] = self.getMSXSpeciesConcentration(1, lnk, + speciename[i]) if lnk < node_indices[-1] + 1: - node_quality[k, lnk - 1, i] = self.getMSXSpeciesConcentration(0, lnk, speciename[i]) - else: + value["NodeQuality"][k - 1][lnk - 1, i - 1] = self.getMSXSpeciesConcentration(0, lnk, + speciename[i]) + + else: + for i in range(len(speciename)): for lnk in node_indices: - node_quality[k, lnk - 1, i] = self.getMSXSpeciesConcentration(0, lnk, speciename[i]) + value["NodeQuality"][k - 1][lnk - 1][i - 1] = self.getMSXSpeciesConcentration(0, lnk, + speciename[i]) if lnk < link_indices[-1] + 1: - link_quality[k, lnk - 1, i] = self.getMSXSpeciesConcentration(1, lnk, speciename[i]) - time.append(t) - return {'NodeQuality': node_quality, 'LinkQuality': link_quality, 'Time': np.array(time)} + value["LinkQuality"][k - 1][lnk - 1][i - 1] = self.getMSXSpeciesConcentration(1, lnk, + speciename[i]) + value["Time"].append([t]) + + return value class epanetapi: @@ -12807,7 +12829,7 @@ class epanetapi: EN_MAXID = 32 # toolkit constant - def __init__(self, version=2.2, msx=False): + def __init__(self, version=2.2, msx=False, loadlib=True, customlib=None): """Load the EPANET library. Parameters: @@ -12822,17 +12844,27 @@ def __init__(self, version=2.2, msx=False): # Check platform and Load epanet library # libname = f"epanet{str(version).replace('.', '_')}" - libname = f"epanet2" - ops = platform.system().lower() - if ops in ["windows"]: - self.LibEPANET = resource_filename("epyt", os.path.join("libraries", "win", f"{libname}.dll")) - elif ops in ["darwin"]: - self.LibEPANET = resource_filename("epyt", os.path.join("libraries", f"mac/lib{libname}.dylib")) - else: - self.LibEPANET = resource_filename("epyt", os.path.join("libraries", f"glnx/lib{libname}.so")) + if customlib is not None: + if not os.path.isabs(customlib): + self.LibEPANET = os.path.join(os.getcwd(), customlib) + else: + self.LibEPANET = customlib + loadlib = False + self._lib = cdll.LoadLibrary(self.LibEPANET) + self.LibEPANETpath = os.path.dirname(self.LibEPANET) + + if loadlib: + libname = f"epanet2" + ops = platform.system().lower() + if ops in ["windows"]: + self.LibEPANET = resource_filename("epyt", os.path.join("libraries", "win", f"{libname}.dll")) + elif ops in ["darwin"]: + self.LibEPANET = resource_filename("epyt", os.path.join("libraries", f"mac/lib{libname}.dylib")) + else: + self.LibEPANET = resource_filename("epyt", os.path.join("libraries", f"glnx/lib{libname}.so")) - self._lib = cdll.LoadLibrary(self.LibEPANET) - self.LibEPANETpath = os.path.dirname(self.LibEPANET) + self._lib = cdll.LoadLibrary(self.LibEPANET) + self.LibEPANETpath = os.path.dirname(self.LibEPANET) if float(version) >= 2.2 and not msx: self._ph = c_uint64() @@ -12872,8 +12904,8 @@ def ENaddcontrol(self, conttype, lindex, setting, nindex, level): self.errcode = self._lib.EN_addcontrol(self._ph, conttype, int(lindex), c_double(setting), nindex, c_double(level), byref(index)) else: - self.errcode = self._lib.ENaddcontrol(conttype, int(lindex), c_double(setting), nindex, - c_double(level), byref(index)) + self.errcode = self._lib.ENaddcontrol(conttype, int(lindex), c_float(setting), nindex, + c_float(level), byref(index)) self.ENgeterror() return index.value @@ -13344,14 +13376,16 @@ def ENgetcontrol(self, cindex): """ ctype = c_int() lindex = c_int() - setting = c_double() nindex = c_int() - level = c_double() if self._ph is not None: + setting = c_double() + level = c_double() self.errcode = self._lib.EN_getcontrol(self._ph, int(cindex), byref(ctype), byref(lindex), byref(setting), byref(nindex), byref(level)) else: + setting = c_float() + level = c_float() self.errcode = self._lib.ENgetcontrol(int(cindex), byref(ctype), byref(lindex), byref(setting), byref(nindex), byref(level)) @@ -15651,7 +15685,15 @@ def ENwriteline(self, line): class epanetmsxapi: """example msx = epanetmsxapi()""" - def __init__(self, msxfile='', loadlib=True, ignore_msxfile=False): + def __init__(self, msxfile='', loadlib=True, ignore_msxfile=False, customMSXlib=None): + if customMSXlib is not None: + self.MSXLibEPANET = customMSXlib + loadlib = False + + self.msx_lib = cdll.LoadLibrary(self.MSXLibEPANET) + self.MSXLibEPANETPath = os.path.dirname(self.MSXLibEPANET) + self.msx_error = self.msx_lib.MSXgeterror + self.msx_error.argtypes = [c_int, c_char_p, c_int] if loadlib: ops = platform.system().lower() if ops in ["windows"]: diff --git a/epyt/libraries/glnx/epanetmsx.so b/epyt/libraries/glnx/epanetmsx.so index bb761b5..8674dcf 100644 Binary files a/epyt/libraries/glnx/epanetmsx.so and b/epyt/libraries/glnx/epanetmsx.so differ diff --git a/epyt/tests/test_unit_MSX.py b/epyt/tests/test_unit_MSX.py index fb04820..4046cf6 100644 --- a/epyt/tests/test_unit_MSX.py +++ b/epyt/tests/test_unit_MSX.py @@ -17,7 +17,7 @@ def setUp(self): def tearDown(self): """Call after every test case.""" - self.msxClass.MSXclose() + self.epanetClass.unloadMSX() self.epanetClass.unload() """ ------------------------------------------------------------------------- """ @@ -216,13 +216,13 @@ def test_MSXgetindex(self): def test_MSXaddpattern(self): y = self.msxClass.MSXgetcount(7) - self.msxClass.MSXaddpattern("Johnnys") + self.msxClass.MSXaddpattern("pat-test-2") self.assertEqual(self.msxClass.MSXgetcount(7), y + 1, 'Wrong error add patter output') def test_MSXsetpatter(self): - self.msxClass.MSXaddpattern("JohnLegend") - x = self.msxClass.MSXgetindex(7, "JohnLegend") + self.msxClass.MSXaddpattern("pat-test-1") + x = self.msxClass.MSXgetindex(7, "pat-test-1") mult = [0.5, 0.8, 1.2, 1.0, 0.7, 0.3] self.msxClass.MSXsetpattern(x, mult, 6) @@ -240,7 +240,7 @@ def test_MSXsetpatter(self): 'Wrong set/get patternvalue comment output') def test_MSXsavesmsxfile(self): - filename = "JohntheLegend.msx" + filename = "net-test-1.msx" self.msxClass.MSXsavemsxfile(filename) full_path = os.path.join(os.getcwd(), filename) if os.path.exists(full_path): diff --git a/requirements.txt b/requirements.txt index c08a4bf..b11249e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -numpy -matplotlib -pandas -xlsxwriter +numpy>=1.24.4 +matplotlib>=3.7.5 +pandas>=2.0.3 +XlsxWriter>=3.2.0 setuptools \ No newline at end of file