diff --git a/README.md b/README.md index c2919a4..fc32a51 100644 --- a/README.md +++ b/README.md @@ -531,7 +531,11 @@ If you want to contribute, please check out our [Code of Conduct](https://github |loadMSXFile|Opens the EPANET-MSX toolkit system| |unloadMSX|Closes the EPANET-MSX toolkit system| |addMSXPattern|Adds a new, empty MSX source time pattern to the project| +|writeMSXFile|Write a new MSX file| |initializeMSXQualityAnalysis|Initializes the MSX system before solving for water quality results in step-wise fashion| +|getMSXComputedQualitySpecie|Retrieves the quality values for specific specie (e.g getMSXComputedQualitySpecie('CL2'))| +|getMSXComputedLinkQualitySpecie|Returns the link quality for specific specie| +|getMSXComputedNodeQualitySpecie|Returns the node quality for specific specie| |stepMSXQualityAnalysisTimeLeft|Advances the water quality solution through a single water quality time step when performing a step-wise simulation| |saveMSXFile|Saves the data associated with the current MSX project into a new MSX input file| |saveMSXQualityFile|Saves water quality results computed for each node, link and reporting time period to a named binary file| @@ -601,6 +605,9 @@ If you want to contribute, please check out our [Code of Conduct](https://github |setMSXParametersPipesValue|Assigns a value to a particular reaction parameter for given pipes| |setMSXParametersTanksValue|Assigns a value to a particular reaction parameter for given tanks| |setMSXConstantsValue|Assigns a new value to a specific reaction constant| +|setMSXLinkInitqualValue|Assigns an initial concentration of chemical species to links| +|setMSXNodeInitqualValue|Assigns an initial concentration of chemical species to nodes| +|setMSXSources|Sets the attributes of an external source of a particular chemical species to a specific node of the pipe network| |useMSXHydraulicFile|Uses a previously saved EPANET hydraulics file as the source of hydraulic information| diff --git a/epyt/epanet.py b/epyt/epanet.py index 4a9933d..e8fe012 100644 --- a/epyt/epanet.py +++ b/epyt/epanet.py @@ -11203,6 +11203,10 @@ def loadMSXFile(self, msxname, customMSXlib=None, ignore_properties=False): copyfile(msxname, self.msxname) self.msx = epanetmsxapi(self.msxname, customMSXlib=customMSXlib) + + + + #message to user if he uses ph with msx if self.api._ph is not None: print("In order for LoadMSX to work remove from epanet the ph") @@ -11762,16 +11766,22 @@ def getMSXPatternsIndex(self, varagin=None): setMSXPattern, setMSXPatternMatrix, setMSXPatternValue.""" x = self.getMSXSpeciesCount() value = {} + flag = 0 if varagin is None: varagin = {} varagin = self.getMSXPatternsNameID() y = varagin else: + if isinstance(varagin, str): + flag = 1 y = varagin MSX_PATTERN = self.ToolkitConstants.MSX_PATTERN if x > 0: - for i in y: - value[i] = self.msx.MSXgetindex(MSX_PATTERN, i) + if flag == 1: + value[0] = self.msx.MSXgetindex(MSX_PATTERN, y) + else: + for i in y: + value[i] = self.msx.MSXgetindex(MSX_PATTERN, i) output = list(value.values()) return output @@ -12735,6 +12745,18 @@ def addMSXPattern(self, *args): return index def getMSXComputedQualitySpecie(self, species=None): + """ Returns the node/link quality for specific specie. + + Example : + d = epanet('net2-cl2.inp') + d.loadMSXFile('net2-cl2.msx') + MSX_comp = d.getMSXComputedQualitySpecie(['CL2']) + MSX_comp.NodeQuality row: time, col: node index + MSX_comp.LinkQuality row: time, col: link index + MSX_comp.Time + + See also getMSXComputedQualityNode, getMSXComputedQualityLink. + """ if self.getMSXSpeciesCount() == 0: return -1 if species is None: @@ -12793,6 +12815,222 @@ def getMSXComputedQualitySpecie(self, species=None): range(int(self.getTimeSimulationDuration() / msx_time_step) + 1)] return value + def getMSXComputedNodeQualitySpecie(self, node_indices, species_id): + """ Returns the node quality for specific specie. + + Example : + d = epanet('net2-cl2.inp') + d.loadMSXFile('net2-cl2.msx') + node_indices = [1,2,3] + MSX_comp = d.getMSXComputedNodeQualitySpecie(node_indices, 'CL2') + MSX_comp.NodeQuality row: time, col: node index + MSX_comp.Time + + See also getMSXComputedQualitySpecie, getMSXComputedLinkQualitySpecie.""" + merged = [] + MSX_comp = self.getMSXComputedQualitySpecie([species_id]) + MSX_comp_node = MSX_comp.NodeQuality + for i in node_indices: + column = MSX_comp_node[i] + merged.append(column) + value = EpytValues() + value.NodeQuality, value.Time = {}, {} + value.NodeQuality = merged + value.Time = MSX_comp.Time + return value + + def getMSXComputedLinkQualitySpecie(self, node_indices, species_id): + """ Returns the link quality for specific specie. + + Example : + d = epanet('net2-cl2.inp') + d.loadMSXFile('net2-cl2.msx') + node_indices = [1,2,3,4] + MSX_comp = d.getMSXComputedLinkQualitySpecie(node_indices, 'CL2') + MSX_comp.LinkQuality row: time, col: node index + MSX_comp.Time + + See also getMSXComputedQualitySpecie, getMSXComputedNodeQualitySpecie.""" + merged = [] + MSX_comp = self.getMSXComputedQualitySpecie([species_id]) + MSX_comp_Link = MSX_comp.LinkQuality + for i in node_indices: + column = MSX_comp_Link[i] + merged.append(column) + value = EpytValues() + value.LinkQuality, value.Time = {}, {} + value.LinkQuality = merged + value.Time = MSX_comp.Time + return value + + def setMSXLinkInitqualValue(self, value): + """" + Sets all links initial quality value. + + Example: + linkIndex=0 + speciesIndex=0 + values = [[0] * linkIndex for _ in range(speciesIndex)] + values=d.getMSXLinkInitqualValue() + values[linkIndex][speciesIndex]=1500 + d.setMSXLinkInitqualValue(values) + x=d.getMSXLinkInitqualValue() + + See also getMSXLinkInitqualValue, setMSXNodeInitqualValue. + """ + for i in range(len(value)): + for j in range(len(value[0])): + self.msx.MSXsetinitqual(1, i+1, j+1, value[i][j]) + + def setMSXSources(self, nodeID, speciesID, sourcetype, concentration, patID ): + """ Sets the attributes of an external source of a particular chemical species + to a specific node of the pipe network. + + Example: + d = epanet('net2-cl2.inp'); + d.loadMSXFile('net2-cl2.msx') + srcs = d.getMSXSources() + d.addMSXPattern('PatAsIII',[2, .3, .4, 6, 5, 2, 4]) + d.setMSXSources(d.NodeNameID{2}, d.MSXSpeciesNameID{1}, Setpoint', 0.5, 'PatAsIII') % Sets the second node as setpoint. + d.setMSXSources(d.getNodeNameID(2), d.getMSXSpeciesNameID([1]),'FLOWPACED', 0.5, 'PatAsIII') + srcs = d.getMSXSources() + + See also getMSXSources, getMSXSourceNodeNameID, getMSXSourceType + getMSXSourceLevel, getMSXSourcePatternIndex.""" + MSXTYPESOURCE = {'NOSOURCE', 'CONCEN', 'MASS', 'SETPOINT', 'FLOWPACED'} + node = self.getNodeIndex(nodeID) + species = self.getMSXSpeciesIndex(speciesID) + species=species[0] + pat = self.getMSXPatternsIndex(patID) + pat = pat[0] + if sourcetype == 'NOSOURCE' or sourcetype == 'nosource': + type = -1 + elif sourcetype == 'CONCEN' or sourcetype == 'concern': + type = 0 + elif sourcetype == 'MASS' or sourcetype == 'mass': + type = 1 + elif sourcetype == 'SETPOINT' or sourcetype == 'setpoint': + type = 2 + elif sourcetype == 'FLOWPACED' or sourcetype == 'flowpaced': + type = 3 + + self.msx.MSXsetsource(node, species, type, concentration, pat) + + def setMSXNodeInitqualValue(self, value): + """ + Sets all nodes initial quality value. + + Example: + linkIndex=0 + speciesIndex=0 + values = [[0] * linkIndex for _ in range(speciesIndex)] + values=d.getMSXNodeInitqualValue() + values[linkIndex][speciesIndex]=1500 + d.setMSXNodeInitqualValue(values) + x=d.getMSXNodeInitqualValue() + See also getMSXNodeInitqualValue, setMSXLinkInitqualValue. + """ + for i in range(len(value)): + for j in range(len(value[0])): + self.msx.MSXsetinitqual(0, i+1, j+1, value[i][j]) + def setMSXWrite(self): + value = EpytValues() + value.FILENAME = "" + value.TITLE = "" + value.AREA_UNITS = "" + value.RATE_UNITS = "" + value.SOLVER = "" + value.TIMESTEP = {} + value.COMPILER = "" + value.COUPLING = "" + value.RTOL = {} + value.ATOL = {} + + value.SPECIES = {} + value.COEFFICIENTS = {} + value.TERMS = {} + value.PIPES = {} + value.TANKS = {} + value.SOURCES = {} + value.GLOBAL = {} + value.QUALITY = {} + value.PARAMETERS = {} + value.PATERNS = {} + + + return value + def writeMSXFile(self, msx): + filename = msx.FILENAME + with open(filename, 'w')as f: + f.write("[TITLE]\n") + f.write(msx.TITLE) + #OPTIONS + f.write("\n\n[OPTIONS]") + ans = msx.AREA_UNITS + f.write("\nAREA_UNITS\t{}".format(ans)) + ans = msx.RATE_UNITS + f.write("\nRATE_UNITS\t{}".format(ans)) + ans = msx.SOLVER + f.write("\nSOLVER\t\t{}".format(ans)) + ans = msx.TIMESTEP + f.write("\nTIMESTEP\t{}".format(ans)) + ans = msx.COMPILER + f.write("\nCOMPILER\t{}".format(ans)) + ans = msx.COUPLING + f.write("\nCOUPLING\t{}".format(ans)) + ans = msx.RTOL + f.write("\nRTOL\t\t{}".format(ans)) + ans = msx.ATOL + f.write("\nATOL\t\t{}".format(ans)) + + + f.write("\n\n[SPECIES]\n") + ans = list(msx.SPECIES) + for item in ans: + f.write("{}\n".format(item)) + + f.write("\n\n[COEFFICIENTS]\n") + ans = list(msx.COEFFICIENTS) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[TERMS]\n") + ans = list(msx.TERMS) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[PIPES]\n") + ans = list(msx.PIPES) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[TANKS]\n") + ans = list(msx.TANKS) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[SOURCES]\n") + ans = list(msx.SOURCES) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[QUALITY]\n") + ans = list(msx.QUALITY) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[GLOBAL]\n") + ans = list(msx.GLOBAL) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[PARAMETERS]\n") + ans = list(msx.PARAMETERS) + for item in ans: + f.write("{}\n".format(item)) + f.write("\n\n[PATTERNS]\n") + ans = list(msx.PATERNS) + for item in ans: + f.write("{}\n".format(item)) + + + f.write('\n[REPORT]\n') + f.write('NODES ALL\n') + f.write('LINKS ALL\n') + class epanetapi: """ @@ -16245,6 +16483,7 @@ def MSXsetsource(self, node, species, type, level, pat): pat: the index of the time pattern used to add variability to the source's baseline level ( use 0 if the source has a constant strength) """ level = c_double(level) + pat = c_int(pat) type = c_int(type) err = self.msx_lib.MSXsetsource(node, species, type, level, pat) diff --git a/epyt/tests/test_unit_MSXOptions.py b/epyt/tests/test_unit_MSXOptions.py new file mode 100644 index 0000000..44f7737 --- /dev/null +++ b/epyt/tests/test_unit_MSXOptions.py @@ -0,0 +1,100 @@ +from epyt import epanet +import unittest +import os + + +class MSXtest(unittest.TestCase): + + def setUp(self): + """Call before every test case.""" + # Create EPANET object using the INP file + inpname = os.path.join(os.getcwd(), 'epyt', 'networks', 'msx-examples', 'net2-cl2.inp') + msxname = os.path.join(os.getcwd(), 'epyt', 'networks', 'msx-examples', 'net2-cl2.msx') + self.epanetClass = epanet(inpname) + self.msxClass = self.epanetClass.loadMSXFile(msxname) + + def tearDown(self): + """Call after every test case.""" + self.msxClass.MSXclose() + self.epanetClass.unload() + + """ ------------------------------------------------------------------------- """ + #new functions - Read MSX file + def test_MSXOptions(self): + self.assertEqual(self.epanetClass.getMSXTimeStep(), + (300), 'Wrong get timestep comment output') + + self.epanetClass.setMSXTimeStep(4200) + self.assertEqual(self.epanetClass.getMSXTimeStep(), + (4200), 'Wrong get timestep comment output') + + self.assertEqual(self.epanetClass.getMSXAreaUnits(), + ("FT2"), 'Wrong get Area Units comment output') + + self.epanetClass.setMSXAreaUnitsFT2() + self.assertEqual(self.epanetClass.getMSXAreaUnits(), + ("FT2"), 'Wrong get Area Units comment output') + self.epanetClass.setMSXAreaUnitsM2() + self.assertEqual(self.epanetClass.getMSXAreaUnits(), + ("M2"), 'Wrong get Area Units comment output') + self.epanetClass.setMSXAreaUnitsCM2() + self.assertEqual(self.epanetClass.getMSXAreaUnits(), + ("CM2"), 'Wrong get Area Units comment output') + + self.epanetClass.setMSXRateUnitsSEC() + self.assertEqual(self.epanetClass.getMSXRateUnits(), + ("SEC"),"Wrong get Rate Units coments output") + self.epanetClass.setMSXRateUnitsMIN() + self.assertEqual(self.epanetClass.getMSXRateUnits(), + ("MIN"), "Wrong get Rate Units coments output") + self.epanetClass.setMSXRateUnitsDAY() + self.assertEqual(self.epanetClass.getMSXRateUnits(), + ("DAY"), "Wrong get Rate Units coments output") + + self.epanetClass.setMSXSolverEUL() + self.assertEqual(self.epanetClass.getMSXSolver(), + ("EUL"), "Wrong get Solver coments output") + + self.epanetClass.setMSXSolverRK5() + self.assertEqual(self.epanetClass.getMSXSolver(), + ("RK5"), "Wrong get Solver coments output") + self.epanetClass.setMSXSolverROS2() + self.assertEqual(self.epanetClass.getMSXSolver(), + ("ROS2"), "Wrong get Solver coments output") + + self.epanetClass.setMSXCouplingFULL() + self.assertEqual(self.epanetClass.getMSXCoupling(), + ("FULL"), "Wrong get Coupling coments output") + self.epanetClass.setMSXCouplingNONE() + self.assertEqual(self.epanetClass.getMSXCoupling(), + ("NONE"), "Wrong get Coupling coments output") + + self.epanetClass.setMSXCompilerVC() + self.assertEqual(self.epanetClass.getMSXCompiler(), + ("VC"), "Wrong get Compiler coments output") + self.epanetClass.setMSXCompilerGC() + self.assertEqual(self.epanetClass.getMSXCompiler(), + ("GC"), "Wrong get Compiler coments output") + self.epanetClass.setMSXCompilerNONE() + self.assertEqual(self.epanetClass.getMSXCompiler(), + ("NONE"), "Wrong get Compiler coments output") + + self.epanetClass.setMSXAtol(0.2) + self.assertEqual(self.epanetClass.getMSXAtol(), + (0.2), "Wrong get ATOL coments output") + self.epanetClass.setMSXRtol(0.2) + self.assertEqual(self.epanetClass.getMSXRtol(), + (0.2), "Wrong get RTOL coments output") + + + def test_Parameters(self): + self.assertEqual(self.epanetClass.getMSXEquationsTerms(), + (["Kf 1.5826e-4 * RE^0.88 / D"]), 'Wrong get Equations comment output') + self.assertEqual(self.epanetClass.getMSXEquationsPipes(), + (["RATE CL2 -Kb*CL2 - (4/D)*Kw*Kf/(Kw+Kf)*CL2"]), 'Wrong get Equations comment output') + self.assertEqual(self.epanetClass.getMSXEquationsTanks(), + (["RATE CL2 -Kb*CL2"]), 'Wrong get Equations comment output') + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file