diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a64fdc12..69279e12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ stages: variables: MAGE_8_VERSION: "permalink/latest" ADISTS_VERSION: "permalink/latest" + RUBAR_VERSION: "permalink/latest" dl-mage8-doc: stage: downloads @@ -104,6 +105,37 @@ dl-adists-windows: paths: - adists-windows/adists.exe +dl-rubar-linux: + stage: downloads + tags: + - linux + rules: + - if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG + script: + - mkdir -p rubar-linux + - cd rubar-linux + - curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_linux.tgz + - tar xf rubar.tgz + artifacts: + paths: + - rubar-linux/rubarbe + +dl-rubar-windows: + stage: downloads + tags: + - linux + rules: + - if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG + script: + - mkdir -p rubar-windows + - cd rubar-windows + - curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_windows.tgz + - tar xf rubar.tgz + + artifacts: + paths: + - rubar-windows/rubarbe.exe + ############# # CONFIGURE # ############# @@ -266,6 +298,8 @@ build-linux: needs: - job: dl-adists-linux artifacts: true + - job: dl-rubar-linux + artifacts: true - job: dl-mage8-linux artifacts: true - job: dl-mage8-doc @@ -307,6 +341,9 @@ build-linux: # Copy adists - mkdir -p pamhyr/adists - cp -v ../adists-linux/* pamhyr/adists/ + # Copy rubar + - mkdir -p pamhyr/rubar + - cp -v ../rubar-linux/* pamhyr/rubar/ # Copy Pamhyr - cp -r dist/pamhyr/* pamhyr/ # Pamhyr script to force x11 @@ -372,6 +409,8 @@ build-windows: artifacts: true - job: dl-adists-windows artifacts: true + - job: dl-rubar-windows + artifacts: true - job: dl-mage8-doc artifacts: true - job: set-version diff --git a/packages/make-windows-dir.bat b/packages/make-windows-dir.bat index 806eb643..d47c532d 100644 --- a/packages/make-windows-dir.bat +++ b/packages/make-windows-dir.bat @@ -46,6 +46,10 @@ copy /y ..\mage8-windows\mage_extraire.exe pamhyr\mage8\ copy /y ..\mage8-windows\mailleurTT.exe pamhyr\mage8\ copy /y ..\mage8-windows\libbief.dll pamhyr\mage8\ +rem rubar +mkdir pamhyr\rubar +copy /y ..\rubar-windows\rubarbe.exe pamhyr\rubar\ + rem adists mkdir pamhyr\adists copy /y ..\adists-windows\adists.exe pamhyr\adists\ diff --git a/src/Solver/AdisTS.py b/src/Solver/AdisTS.py index 1f41bd2f..bd44bbd4 100644 --- a/src/Solver/AdisTS.py +++ b/src/Solver/AdisTS.py @@ -108,6 +108,187 @@ class AdisTS(CommandLineSolver): name = self._study.name return f"{name}.TRA" + def _export_ST(self, study, repertory, qlog, name="0"): + files = [] + + if qlog is not None: + qlog.put("Export ST file") + + os.makedirs(os.path.join(repertory, "net"), exist_ok=True) + + # Write header + edges = study.river.enable_edges() + for edge in edges: + name = f"Reach_{edge.id + 1:>3}".replace(" ", "0") + + with adists_file_open( + os.path.join(repertory, "net", f"{name}.ST"), + "w+" + ) as f: + files.append(str(os.path.join("net", f"{name}.ST"))) + + cnt_num = 1 + for profile in edge.reach.profiles: + self._export_ST_profile_header( + f, files, profile, cnt_num + ) + cnt_num += 1 + + # Points + for point in profile.points: + self._export_ST_point_line( + f, files, point + ) + + # Profile last line + f.write(f" 999.9990 999.9990 999.9990\n") + + def _export_ST_profile_header(self, wfile, files, + profile, cnt): + num = f"{cnt:>6}" + c1 = f"{profile.code1:>6}" + c2 = f"{profile.code2:>6}" + t = f"{len(profile.points):>6}" + rk = f"{profile.rk:>12f}"[0:12] + pname = profile.name + if profile.name == "": + # Generate name from profile id prefixed with + # 'p' (and replace space char with '0' char) + pname = f"p{profile.id:>3}".replace(" ", "0") + name = f"{pname:<19}" + + # Generate sediment additional data if available + sediment = "" + if profile.sl is not None: + if not any(filter(lambda f: ".GRA" in f, files)): + files.append(self._gra_file) + + # Number of layers + nl = len(profile.sl) + sediment = f" {nl:>3}" + + # Layers data + for layer in profile.sl.layers: + sediment += ( + f" {layer.height:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Profile header line + wfile.write(f"{num}{c1}{c2}{t} {rk} {pname} {sediment}\n") + + def _export_ST_point_line(self, wfile, files, point): + x = f"{point.x:<12.4f}"[0:12] + y = f"{point.y:<12.4f}"[0:12] + z = f"{point.z:<12.4f}"[0:12] + n = f"{point.name:<3}" + + # Generate sediment additional data if available + sediment = "" + prev = point.z + if point.sl is not None: + # Number of layers + nl = len(point.sl) + sediment = f"{nl:>3}" + + # Layers data + for layer in point.sl.layers: + prev = round(prev - layer.height, 5) + sediment += ( + f" {prev:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Point line + wfile.write(f"{x} {y} {z} {n} {sediment}\n") + + def _export_NET(self, study, repertory, qlog=None, name="0"): + + if qlog is not None: + qlog.put("Export NET file") + + with adists_file_open( + os.path.join(repertory, f"{name}.NET"), "w+") as f: + edges = study.river.enable_edges() + + for e in edges: + name = f"Reach_{e.id + 1:>3}".replace(" ", "0") + id = name + + n1 = f"{e.node1.id:3}".replace(" ", "x") + n2 = f"{e.node2.id:3}".replace(" ", "x") + file = os.path.join("net", name + ".ST") + + f.write(f"{id} {n1} {n2} {file}\n") + + def _export_fake_INI(self, study, repertory, qlog=None, name="0"): + if qlog is not None: + qlog.put("Export fake INI file") + + with adists_file_open( + os.path.join( + repertory, "Mage_fin.ini" + ), "w+" + ) as f: + edges = study.river.enable_edges() + for i, edge in enumerate(edges): + lst = list(filter( + lambda f: f.is_full_defined(), + edge.frictions.frictions + )) + rk_min = 9999999.9 + rk_max = -9999999.9 + coeff_min = -1.0 + coeff_max = -1.0 + for s in lst: # TODO optimise ? + if s.begin_rk > rk_max: + rk_max = s.begin_rk + coeff_max = s.begin_strickler + if s.begin_rk < rk_min: + rk_min = s.begin_rk + coeff_min = s.begin_strickler + if s.end_rk > rk_max: + rk_max = s.end_rk + coeff_max = s.end_strickler + if s.end_rk < rk_min: + rk_min = s.end_rk + coeff_min = s.end_strickler + + print("min max", rk_min, rk_max) + + def get_stricklers_from_rk(rk, lst): + print("rk", rk) + + coeff = None + if rk > rk_max: + coeff = coeff_max + elif rk < rk_min: + coeff = coeff_min + else: + for s in lst: + if (rk >= s.begin_rk and rk <= s.end_rk or + rk <= s.begin_rk and rk >= s.end_rk): + coeff = s.begin_strickler # TODO: inerpolate + break + + # TODO interpolation if rk is not in frictons + + if coeff is None: + logger.error( + "Study frictions are not fully defined" + ) + return None + + return coeff.minor, coeff.medium + + for j, profile in enumerate(edge.reach.profiles): + coef_min, coef_moy = get_stricklers_from_rk(profile.rk, + lst) + f.write( + f" {i+1:3}{j+1:4}{' '*116}{coef_min:9}{coef_moy:9}\n") + def _export_REP_additional_lines(self, study, rep_file): lines = filter( lambda line: line.is_enabled(), @@ -129,7 +310,7 @@ class AdisTS(CommandLineSolver): ), "w+" ) as f: path = os.path.join("..", mage_rep, name) - f.write(f"NET {path}.NET\n") + f.write(f"NET {name}.NET\n") f.write(f"REP {path}.REP\n") for file in files: @@ -138,13 +319,25 @@ class AdisTS(CommandLineSolver): self._export_REP_additional_lines(study, f) - path_mage_net = os.path.join(os.path.abspath( - os.path.join(repertory, os.pardir) - ), os.path.join(mage_rep, "net")) - path_adists_net = os.path.join(repertory, "net") + self._export_ST(study, repertory, qlog, name=name) + self._export_NET(study, repertory, qlog, name=name) - if os.path.exists(path_mage_net): - shutil.copytree(path_mage_net, path_adists_net, dirs_exist_ok=True) + # fake mage_fin.ini: + + path_mage = os.path.join(os.path.abspath( + os.path.join(repertory, os.pardir)), mage_rep) + self._export_fake_INI(study, path_mage, + qlog, name=name) + + # path_mage_net = os.path.join(os.path.abspath( + # os.path.join(repertory, os.pardir) + # ), os.path.join(mage_rep, "net")) + # path_adists_net = os.path.join(repertory, "net") + + # if os.path.exists(path_mage_net): + # shutil.copytree(path_mage_net, + # path_adists_net, + # dirs_exist_ok=True) @timer def export(self, study, repertory, qlog=None): diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index 66c1d225..cd9b42b7 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -610,6 +610,28 @@ class Rubar3(CommandLineSolver): ) ts = set() + timestamp = next(filter( + lambda p: p.name == 'rubarbe_tinit', + study.river.get_params(self._type).parameters + )).value + if timestamp.count(':') == 3: + timestamp = old_pamhyr_date_to_timestamp(timestamp) + ts.add(timestamp) + + # add initial condition + for r, edge in enumerate(study.river.enable_edges()): + reach = edge.reach + ics = study.river.initial_conditions.get(edge) + q = ics.get_discharge() + z = ics.get_elevation() + k = 0 + for i, j in zip(z, q): + v = reach.profiles[k].speed(i, j) + set_and_compute_limites(reachs[r][0], k, i, j, v) + k += 1 + + # start read + end = False while True: line = f.readline() @@ -649,6 +671,97 @@ class Rubar3(CommandLineSolver): h, s, q, z = read_data_line(f) set_and_compute_limites(reach, ind, z+h, q, s) + @timer + def write_bin(self, study, fname, results, qlog=None, name="0"): + logger.info(f"write_bin: Start writing '{fname}' ...") + + with open(fname, "wb") as f: + def newline(j): return np.asarray([j], dtype=np.int32).tofile(f) + def endline(j): return np.asarray([j], dtype=np.int32).tofile(f) + + def write_int(i): + newline(len(i)*4) + np.array(i, dtype=np.int32).tofile(f) + endline(len(i)*4) + + def write_float(i): + newline(len(i)*4) + np.array(i, dtype=np.float32).tofile(f) + endline(len(i)*4) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_data(npts, t, a, val): + newline(npts*4 + 13) + np.array(npts, dtype=np.int32).tofile(f) + np.array(t, dtype=np.float64).tofile(f) + np.array(bytearray(a.encode()), dtype=np.byte).tofile(f) + np.array(val, dtype=np.float32).tofile(f) + endline(npts*4 + 13) + + ts_list = sorted(results.get("timestamps")) + profiles = [] + for r in results.river.reachs: + profiles += r.profiles + + # Meta data (1st line) + nb_reach = len(results.river) + nb_profile = len(profiles) + write_int([nb_reach, nb_profile, "82"]) + + # Reach information (2nd line) + is1 = [] + is2 = [] + ltmp = [] + i = 1 + for r in results.river.reachs: + j = i+len(r)-1 + ltmp += [i, j] + is1.append(i) + is2.append(j) + i += i+len(r) + write_int(ltmp) + + # X (3rd line) + rk = [] + for r in results.river.reachs: + rk += r.geometry.get_rk() + write_float(rk) + + # Z and Y (4th line) + # ltmp = [] + # for r in results.river.reachs: + # for p in r.prifiles: + # ltpm.append(p.rk) + write_float(3*nb_profile*[0.0]) + + # Data + + for timestamp in ts_list: + q = list( + map( + lambda p: p.get_ts_key(timestamp, "Q"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Q", q) + z = list( + map( + lambda p: p.get_ts_key(timestamp, "Z"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Z", z) + logger.info(f"write_bin: ... end with {len(ts_list)} timestamps") + @timer def results(self, study, repertory, qlog=None, name="0"): results = Results( @@ -670,6 +783,9 @@ class Rubar3(CommandLineSolver): logger_exception(e) return None + fname = os.path.join(repertory, f"{name}.BIN") + self.write_bin(study, fname, results, qlog, name) + return results diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py index 168937c7..75d1ba3e 100644 --- a/src/View/RunSolver/WindowAdisTS.py +++ b/src/View/RunSolver/WindowAdisTS.py @@ -82,7 +82,7 @@ class SelectSolverWindowAdisTS(PamhyrDialog): # solvers = self._config.solvers # solvers mage solvers = list(filter( - lambda x: "mage" not in x._type, self._config.solvers + lambda x: "adists" in x._type, self._config.solvers )) solvers_name = list( map( @@ -92,7 +92,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog): ) solvers_mage = list(filter( - lambda x: "mage" in x._type.lower(), self._config.solvers + lambda x: "mage" or "rubar" in x._type.lower(), + self._config.solvers )) solvers_mage_names = list(map(lambda x: x._name, solvers_mage)) diff --git a/src/config.py b/src/config.py index 3cd2a44f..3605ebd3 100644 --- a/src/config.py +++ b/src/config.py @@ -37,7 +37,7 @@ logger = logging.getLogger() class Config(SQL): def __init__(self): - self._version = '0.0.6' + self._version = '0.0.7' self.filename = Config.filename() self.set_default_value() @@ -101,6 +101,7 @@ class Config(SQL): # Add default solver posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -111,7 +112,7 @@ class Config(SQL): '', '', '', '', - '@install_dir/mage/mage{ext} @args @input', + '{path} @args @input', '' ) """) @@ -135,16 +136,19 @@ class Config(SQL): if int(release) < 5: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/mage8/mage{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-mage'" ) if int(release) < 6: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "adists", f"adists{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -155,16 +159,41 @@ class Config(SQL): '', '', '', '', - '@install_dir/adists/adists{ext} @args @input', + '{path} @args @input', '' ) """) self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/adists/adists{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-AdisTS'" ) + if int(release) < 7: + posix = os.name == 'posix' + ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "rubar", f"rubar3{ext}") + + self.execute(f""" + INSERT INTO solver VALUES ( + 'rubar3', + 'default-Rubar3', + 'Default Pamhyr2 Rubar 3 version', + + '', '', '', + + '', + '{path} @args @input', + '' + ) + """) + self.execute( + "UPDATE solver SET cmd_solver=" + + f"'{path} @args @input' " + "WHERE name='default-Rubar3'" + ) + self.execute( f"UPDATE info SET value='{self._version}' " + "WHERE key='version'" @@ -327,14 +356,24 @@ class Config(SQL): ctor = solver_type_list["mage8"] new = ctor("default-mage") new._description = "Default Pamhyr2 mage 8 version" - new._cmd_solver = f""""@install_dir/mage8/mage{ext}" @args @input""" + path = os.path.join("@install_dir", "mage8", "mage") + new._cmd_solver = f""""{path}{ext}" @args @input""" self._solvers.append(new) # AdisTS ctor = solver_type_list["adistswc"] new = ctor("default-AdisTS") new._description = "Default Pamhyr2 AdisTS version" - new._cmd_solver = f""""@install_dir/adists/adists{ext}" @args @input""" + path = os.path.join("@install_dir", "adists", "adists") + new._cmd_solver = f""""{path}{ext}" @args @input""" + self._solvers.append(new) + + # Rubar3 + ctor = solver_type_list["rubar3"] + new = ctor("default-Rubar3") + new._description = "Default Pamhyr2 Rubar 3 version" + path = os.path.join("@install_dir", "rubar", f"rubar3{ext}") + new._cmd_solver = f""""{path}" @args @input""" self._solvers.append(new) # Mage fake 7