Merge branch 'master' into mesh
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# Pamhyr .editorconfig
|
||||
|
||||
root = true
|
||||
|
||||
[*.py]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -4,8 +4,19 @@
|
||||
*.tar.xz
|
||||
*.zip
|
||||
*.spec
|
||||
*.pkl
|
||||
*.class
|
||||
|
||||
tmp
|
||||
|
||||
*.tar.*
|
||||
*.zip
|
||||
|
||||
pamhyrBuild
|
||||
wine-pamhyr-build*
|
||||
|
||||
_PAMHYR_
|
||||
__old__
|
||||
|
||||
### END CUSTOM ###
|
||||
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# .gitlab-ci.yml -- Pamhyr gitlab-ci
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
stages:
|
||||
- downloads
|
||||
- build
|
||||
- test
|
||||
- package
|
||||
- release
|
||||
|
||||
#############
|
||||
# DOWNLOADS #
|
||||
#############
|
||||
|
||||
dl-mage-linux:
|
||||
stage: downloads
|
||||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_linux.tgz
|
||||
- mkdir -p mage-linux
|
||||
- cd mage-linux
|
||||
- tar xvf ../mage.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- mage-linux/mage
|
||||
- mage-linux/mage_extraire
|
||||
- mage-linux/mailleurPF
|
||||
|
||||
dl-mage-windows:
|
||||
stage: downloads
|
||||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_windows.tgz
|
||||
- mkdir -p mage-windows
|
||||
- cd mage-windows
|
||||
- tar xvf ../mage.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- mage-windows/mage.exe
|
||||
- mage-windows/mage_extraire.exe
|
||||
- mage-windows/mailleurPF.exe
|
||||
|
||||
#########
|
||||
# BUILD #
|
||||
#########
|
||||
|
||||
set-version:
|
||||
stage: build
|
||||
tags:
|
||||
- linux
|
||||
script:
|
||||
- cd packages
|
||||
- ./version.sh "$CI_COMMIT_BRANCH" "$CI_COMMIT_TAG" "$CI_COMMIT_SHORT_SHA"
|
||||
artifacts:
|
||||
paths:
|
||||
- VERSION
|
||||
|
||||
build-lang:
|
||||
stage: build
|
||||
tags:
|
||||
- linux
|
||||
script:
|
||||
- cd ./src/lang/
|
||||
- ./create_ts.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- src/lang/*.qm
|
||||
|
||||
build-users-doc:
|
||||
stage: build
|
||||
tags:
|
||||
- linux
|
||||
needs:
|
||||
- job: set-version
|
||||
artifacts: true
|
||||
script:
|
||||
- cd ./doc/users/
|
||||
- ./build.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- doc/users/documentation.pdf
|
||||
|
||||
build-developers-doc:
|
||||
stage: build
|
||||
tags:
|
||||
- linux
|
||||
needs:
|
||||
- job: set-version
|
||||
artifacts: true
|
||||
script:
|
||||
- cd ./doc/dev/
|
||||
- ./build.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- doc/dev/documentation.pdf
|
||||
|
||||
build-linux:
|
||||
stage: build
|
||||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
needs:
|
||||
- job: dl-mage-linux
|
||||
artifacts: true
|
||||
- job: set-version
|
||||
artifacts: true
|
||||
- job: build-lang
|
||||
artifacts: true
|
||||
- job: build-developers-doc
|
||||
artifacts: true
|
||||
- job: build-users-doc
|
||||
artifacts: true
|
||||
script:
|
||||
- mkdir -p linux
|
||||
- cd linux
|
||||
# Setup virtual env
|
||||
- python3 -m venv linux-venv
|
||||
- . linux-venv/bin/activate
|
||||
- pip3 install -r ../requirements.txt
|
||||
# Run Pyinstaller
|
||||
- pyinstaller -y --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
|
||||
# Create directory
|
||||
- mkdir -p pamhyr
|
||||
- mkdir -p pamhyr/pamhyr
|
||||
# Copy Pamhyr info
|
||||
- cp -v ../VERSION pamhyr/pamhyr/
|
||||
- cp -v ../AUTHORS pamhyr/pamhyr/
|
||||
- cp -v ../LICENSE pamhyr/pamhyr/
|
||||
# Copy mage
|
||||
- mkdir -p pamhyr/mage
|
||||
- cp -v ../mage-linux/* pamhyr/mage/
|
||||
# Copy Pamhyr
|
||||
- cp -r dist/pamhyr pamhyr/
|
||||
# Copy Pamhyr resources
|
||||
- mkdir -p pamhyr/pamhyr/View
|
||||
- mkdir -p pamhyr/pamhyr/View/ui
|
||||
- mkdir -p pamhyr/pamhyr/View/ui/Widgets
|
||||
- cp -r ../src/View/ui/*.ui pamhyr/pamhyr/View/ui/
|
||||
- cp -r ../src/View/ui/ressources/ pamhyr/pamhyr/View/ui
|
||||
- cp -r ../src/View/ui/Widgets/*.ui pamhyr/pamhyr/View/ui/Widgets
|
||||
# Copy lang
|
||||
- mkdir -p pamhyr/pamhyr/lang
|
||||
- cp -r ../src/lang/*.qm pamhyr/pamhyr/lang
|
||||
# Copy tests cases
|
||||
- mkdir -p pamhyr/tests_cases/
|
||||
- mkdir -p pamhyr/tests_cases/Saar
|
||||
- cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/
|
||||
# Copy documentations
|
||||
- mkdir -p pamhyr/doc/
|
||||
- cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf
|
||||
- cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf
|
||||
# Create running script
|
||||
- echo "#!/bin/sh" > pamhyr/Pamhyr2
|
||||
- echo "./pamhyr/pamhyr $@" >> pamhyr/Pamhyr2
|
||||
- chmod u+x pamhyr/Pamhyr2
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr
|
||||
|
||||
build-windows:
|
||||
stage: build
|
||||
tags:
|
||||
- wine
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
needs:
|
||||
- job: dl-mage-windows
|
||||
artifacts: true
|
||||
- job: set-version
|
||||
artifacts: true
|
||||
- job: build-lang
|
||||
artifacts: true
|
||||
- job: build-developers-doc
|
||||
artifacts: true
|
||||
- job: build-users-doc
|
||||
artifacts: true
|
||||
script:
|
||||
- mkdir windows
|
||||
- cd windows
|
||||
# Setup env
|
||||
- python -m pip install -r ..\requirements.txt
|
||||
# Run Pyinstaller
|
||||
- pyinstaller -y ..\src\pamhyr.py
|
||||
# Create directory
|
||||
- mkdir pamhyr
|
||||
- dir
|
||||
- copy ..\packages\make-windows-dir.bat .\make-windows-dir.bat
|
||||
- .\make-windows-dir.bat
|
||||
artifacts:
|
||||
paths:
|
||||
- windows/pamhyr
|
||||
|
||||
#########
|
||||
# TESTS #
|
||||
#########
|
||||
|
||||
# test-windows:
|
||||
# stage: test
|
||||
# tags:
|
||||
# - wine
|
||||
# needs:
|
||||
# - job: set-version
|
||||
# artifacts: true
|
||||
# - job: build-windows
|
||||
# artifacts: true
|
||||
# script:
|
||||
# - cd windows\pamhyr
|
||||
# - pamhyr\pamhyr.exe hello
|
||||
|
||||
# test-linux:
|
||||
# stage: test
|
||||
# tags:
|
||||
# - linux
|
||||
# needs:
|
||||
# - job: set-version
|
||||
# artifacts: true
|
||||
# - job: build-linux
|
||||
# artifacts: true
|
||||
# script:
|
||||
# - cd linux/pamhyr
|
||||
# - ./Pamhyr2 hello
|
||||
|
||||
############
|
||||
# PACKAGES #
|
||||
############
|
||||
|
||||
linux-package-tar:
|
||||
stage: package
|
||||
tags:
|
||||
- release
|
||||
- linux
|
||||
needs:
|
||||
# - job: test-linux
|
||||
- job: build-linux
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr-gnulinux.tar.xz
|
||||
script:
|
||||
- cd linux
|
||||
- tar --xz -cf pamhyr-gnulinux.tar.xz ./pamhyr --checkpoint=.100
|
||||
|
||||
windows-package-zip:
|
||||
stage: package
|
||||
tags:
|
||||
- release
|
||||
- linux
|
||||
needs:
|
||||
# - job: test-windows
|
||||
- job: build-windows
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- windows/pamhyr-windows.zip
|
||||
script:
|
||||
- cd windows
|
||||
- zip -r pamhyr-windows.zip pamhyr
|
||||
|
||||
windows-package-exe:
|
||||
stage: package
|
||||
tags:
|
||||
- release
|
||||
- wine
|
||||
needs:
|
||||
# - job: test-windows
|
||||
- job: build-windows
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- windows/pamhyr-windows.exe
|
||||
script:
|
||||
- cd windows
|
||||
- copy ..\packages\make-windows-exe.bat make-windows-exe.bat
|
||||
- copy ..\packages\pamhyr.nsi pamhyr.nsi
|
||||
- .\make-windows-exe.bat
|
||||
|
||||
###########
|
||||
# RELEASE #
|
||||
###########
|
||||
|
||||
tag-release:
|
||||
stage: release
|
||||
tags:
|
||||
- release
|
||||
- linux
|
||||
needs:
|
||||
- job: linux-package-tar
|
||||
artifacts: true
|
||||
- job: windows-package-zip
|
||||
artifacts: true
|
||||
- job: windows-package-exe
|
||||
artifacts: true
|
||||
- job: build-users-doc
|
||||
artifacts: true
|
||||
- job: build-developers-doc
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr-gnulinux.tar.xz
|
||||
- windows/pamhyr-windows.zip
|
||||
- windows/pamhyr-windows.exe
|
||||
- doc/dev/documentation.pdf
|
||||
- doc/users/documentation.pdf
|
||||
script:
|
||||
- cd packages
|
||||
release:
|
||||
name: '$CI_COMMIT_TAG'
|
||||
description: 'Automatic release from tag $CI_COMMIT_TAG'
|
||||
tag_name: '$CI_COMMIT_TAG'
|
||||
ref: '$CI_COMMIT_SHA'
|
||||
assets:
|
||||
links:
|
||||
- name: 'GNU/Linux (tar.xz)'
|
||||
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/linux/pamhyr-gnulinux.tar.xz'
|
||||
filepath: '/linux/pamhyr-gnulinux.tar.xz'
|
||||
link_type: 'package'
|
||||
- name: 'Windows (exe)'
|
||||
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.exe'
|
||||
filepath: '/windows/pamhyr-windows.exe'
|
||||
link_type: 'package'
|
||||
- name: 'Windows (zip)'
|
||||
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.zip'
|
||||
filepath: '/windows/pamhyr-windows.zip'
|
||||
link_type: 'package'
|
||||
- name: 'User documentations (pdf)'
|
||||
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/users/documentation.pdf'
|
||||
filepath: '/doc/users/documentation.pdf'
|
||||
link_type: 'other'
|
||||
- name: 'Dev documentations (pdf)'
|
||||
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/dev/documentation.pdf'
|
||||
filepath: '/doc/dev/documentation.pdf'
|
||||
link_type: 'other'
|
||||
@@ -0,0 +1,3 @@
|
||||
Sylvain COULIBALY, INRAE, 2020 - 2022
|
||||
Théophile TERRAZ, INRAE, 2022 - 2023
|
||||
Pierre-Antoine ROUBY, INRAE, 2023
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
pamhyr
|
||||
Copyright (C) 2023 INRAE
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
pamhyr Copyright (C) 2023 INRAE
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1 +1,50 @@
|
||||
# PamHyr (Programme d’Aide à la Modélisation en Hydraulique des Rivières)
|
||||
# Pamhyr2
|
||||
|
||||
Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary
|
||||
modelling of rivers.
|
||||
|
||||
## Features
|
||||
|
||||
+ Edit a complex river hydraulics network
|
||||
+ Define the river geometry
|
||||
+ Define boundary conditions
|
||||
+ Define lateral contribution
|
||||
+ Edit river friction
|
||||
+ Generate initial conditions
|
||||
+ Edit river sedimentary layers
|
||||
+ Save the study in a unique file
|
||||
+ Run Mage 8 on a study and visualize results
|
||||
|
||||
## Install
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
Downloads [last
|
||||
realease](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/releases/permalink/latest)
|
||||
GNU/Linux binary archive.
|
||||
|
||||
```shell
|
||||
tar xf pamhyr-gnulinux-amd64.tar.xz
|
||||
cd pamhyr
|
||||
./pamhyr
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Downloads [last
|
||||
realease](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/releases/permalink/latest)
|
||||
Windows installer.
|
||||
|
||||
### From source code
|
||||
|
||||
```shell
|
||||
# Get source code
|
||||
git clone https://gitlab.irstea.fr/theophile.terraz/pamhyr/
|
||||
cd pamhyr
|
||||
|
||||
# Install dependencies
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
# Run pamhyr
|
||||
./src/pamhyr.py
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
#! /bin/sh
|
||||
|
||||
# Build the PDF
|
||||
|
||||
emacs -Q -q -nw --batch \
|
||||
--eval "(require 'org)" \
|
||||
--eval "(require 'ox-latex)" \
|
||||
--eval "(load-file \"../tools/setup.el\")" \
|
||||
--file ./documentation.org \
|
||||
-f org-latex-export-to-pdf
|
||||
@@ -0,0 +1 @@
|
||||
../tools/ref.bib
|
||||
@@ -0,0 +1,606 @@
|
||||
# documentation.org -- Pamhyr developers documentation
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#+STARTUP: indent
|
||||
|
||||
#+INCLUDE: ../tools/macro.org
|
||||
#+INCLUDE: ../tools/latex.org
|
||||
|
||||
#+TITLE: Developers documentation
|
||||
#+SUBTITLE: Version: {{{version}}}
|
||||
#+AUTHOR: {{{INRAE}}}
|
||||
|
||||
#+OPTIONS: toc:t
|
||||
#+LANGUAGE: UKenglish
|
||||
|
||||
#+NAME: attr_wrap
|
||||
#+HEADER: :var width="\\textwidth"
|
||||
#+HEADER: :var caption=""
|
||||
#+HEADER: :var smallcaption=""
|
||||
#+HEADER: :var name=""
|
||||
#+HEADER: :var data=""
|
||||
#+HEADER: :var float="nil"
|
||||
#+BEGIN_SRC sh :results output :exports none
|
||||
echo "#+CAPTION[$smallcaption]: $caption"
|
||||
echo "#+NAME: $name"
|
||||
echo "#+ATTR_LATEX: :width $width :float $float"
|
||||
echo "$data"
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_abstract
|
||||
This document is for the use of developers. It describes the project
|
||||
architecture, the tools available to assist development and
|
||||
debugging. It also describes the procedures for creating packages and
|
||||
the configurations required to set up the gitlab runners. Finally,
|
||||
this document explains how documentation is written and modified, and
|
||||
how to contribute to the project by modifying, improving or adding
|
||||
documentation, translations or code.
|
||||
#+END_abstract
|
||||
|
||||
* Introduction
|
||||
|
||||
Pamhyr2 is free and open source software (FOSS) graphical user
|
||||
interface (GUI) for 1D hydro-sedimentary modelling of rivers developed
|
||||
in Python (with version 3.8). It use PyQt at version 5 and matplotlib
|
||||
in version 3.4.1 or later for the user insterface (see
|
||||
{{{file(/requirements.txt)}}} for details). The architecture of
|
||||
project code follow the Qt Model/View architecture [fn:qt-arch] (see
|
||||
details in section [[Architecture]]). Pamhyr2 packages can be build
|
||||
manually (see section [[Building packages]]), but there are automatically
|
||||
build with the gitlab-ci (see the section [[Setup the CI
|
||||
environment]]). Documentation files are written with org-mode[fn:org],
|
||||
let see section [[Documentation files]]. Finally, to see the contribution
|
||||
rules, see the section [[How to contribute?]].
|
||||
|
||||
[fn:qt-arch] Qt Model/View documentation:
|
||||
https://doc.qt.io/qt-5/model-view-programming.html (last access
|
||||
2023-09-15)
|
||||
[fn:org] The org-mode website: https://orgmode.org/ (last access
|
||||
2023-09-15)
|
||||
|
||||
* TODO Architecture
|
||||
|
||||
Pamhyr2's architecture is based on Qt Model/View, see Figure
|
||||
[[graph-architecture]]. It is made up of several different components: the
|
||||
model (in blue), the graphical components (in red), the
|
||||
actions/delegates (in green), the commands (in purple), the solvers
|
||||
(in yellow) and the save file (in grey).
|
||||
|
||||
The model is a set of python classes and can be exported to a single
|
||||
SQLite3 format backup file. The view can be made up of various
|
||||
components, generally a Qt window with other view components, such as:
|
||||
a table, a text box, a button, a plot, and so on. The user can view
|
||||
the data using the view and interact with certain components. These
|
||||
components are linked to an action (such as a Python function) or to a
|
||||
delegate class. These actions or delegate can create a command (based
|
||||
on Qt UndoCommand class), this command must implement two functions:
|
||||
One to modify the model, one to reverte this modification and reset
|
||||
the model to previous state. All model modification must be perform by
|
||||
a command to be cancelled. The user can also run a solver and add some
|
||||
simulation results to model data.
|
||||
|
||||
#+name: graph-architecture
|
||||
#+header: :results drawer
|
||||
#+header: :exports results
|
||||
#+header: :post attr_wrap(width="9cm", data=*this*, name="graph-architecture", caption="Pamhyr2 Model/View architecture scheme (inspired by Qt Model/View architecture [[https://doc.qt.io/qt-5/model-view-programming.html]])", float="t")
|
||||
#+begin_src dot :file "images/graph-architecture.png" :cache no
|
||||
digraph {
|
||||
bgcolor="transparent";
|
||||
node[colorscheme=set19,shape=box,style="filled",fillcolor=white];
|
||||
|
||||
save[label="Pamhyr save",fillcolor="9",shape=note];
|
||||
model[label="Model",fillcolor="2"];
|
||||
view[label="View",fillcolor="1"];
|
||||
delegate[label="Delegate",fillcolor="3"];
|
||||
action[label="Action",fillcolor="3"];
|
||||
solver[label="Solver",fillcolor="6"];
|
||||
undocommand[label="Command",fillcolor="4"];
|
||||
user[label="User",shape=ellipse];
|
||||
|
||||
model -> save[label="Save"];
|
||||
save -> model[label="Load"];
|
||||
|
||||
model -> view[label="Rendering"];
|
||||
view -> delegate[label="Rendering"];
|
||||
delegate -> undocommand[label="Create"];
|
||||
action -> undocommand[label="Create"];
|
||||
action -> solver[label="Run"];
|
||||
solver -> model[label="Add results"];
|
||||
undocommand -> model[label="Modify"];
|
||||
|
||||
view -> user[label="Vizualize"];
|
||||
user -> delegate[label="Modify"];
|
||||
user -> action[label="Activate"];
|
||||
}
|
||||
#+end_src
|
||||
|
||||
All the model source code are in the directory {{{file(src/Model)}}}
|
||||
(let see section [[Model]] for more details), the View components,
|
||||
delegate and command are in {{{file(src/View)}}} (see section [[View]]). Solvers classes are
|
||||
in {{{file(src/Solver)}}} (see section [[Solver]]).
|
||||
|
||||
[fn:qt-mv] The Qt Model/View documentation web page:
|
||||
https://doc.qt.io/qt-5/model-view-programming.html
|
||||
|
||||
** TODO Model
|
||||
|
||||
- Model de donnée Python
|
||||
- Correspond à une sauvegarde SQL
|
||||
|
||||
#+name: graph-model
|
||||
#+header: :results drawer
|
||||
#+header: :exports results
|
||||
#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 model class dependencies", float="t")
|
||||
#+begin_src dot :file "images/graph-model.png" :cache no
|
||||
digraph {
|
||||
bgcolor="transparent";
|
||||
node[colorscheme=set19,shape=box,style="filled",fillcolor="2"];
|
||||
|
||||
study[label="Study"];
|
||||
river[label="River"];
|
||||
|
||||
subgraph cluster00 {
|
||||
label="Network"
|
||||
rnode[label="RiverNode"];
|
||||
redge[label="RiverReach"];
|
||||
}
|
||||
|
||||
frictionlist[label="FrictionList"];
|
||||
|
||||
subgraph cluster01 {
|
||||
label="Stricklers";
|
||||
stricklers[label="Stricklers"];
|
||||
stricklerslist[label="StricklersList"];
|
||||
}
|
||||
subgraph cluster02 {
|
||||
label="BoundaryCondition";
|
||||
boundaryconditionlist[label="BoundaryConditionList"];
|
||||
boundarycondition[label="BoundaryCondition"];
|
||||
}
|
||||
subgraph cluster03 {
|
||||
label="LateralContribution";
|
||||
lateralcontributionlist[label="LateralContributionList"];
|
||||
lateralcontribution[label="LateralContribution"];
|
||||
}
|
||||
subgraph cluster04 {
|
||||
label="InitialConditions";
|
||||
initialconditionsdict[label="InitialConditionsDict"];
|
||||
initialconditions[label="InitialConditions"];
|
||||
}
|
||||
|
||||
solverparameterslist[label="SolverParametersList"];
|
||||
|
||||
subgraph cluster05 {
|
||||
label="Sediment";
|
||||
sedimentlayerlist[label="SedimentLayerList"];
|
||||
sedimentlayer[label="SedimentLayer"];
|
||||
layer[label="Layer"];
|
||||
}
|
||||
|
||||
subgraph cluster06 {
|
||||
label="Greometry"
|
||||
georeach[label="Reach"];
|
||||
geocrosssection[label="Cross-section"];
|
||||
geopoint[label="Point"];
|
||||
}
|
||||
|
||||
subgraph cluster07 {
|
||||
label="Results"
|
||||
results[label="Results"]
|
||||
rriver[label="River"];
|
||||
rreach[label="Reach"];
|
||||
rcrosssection[label="Cross-section"];
|
||||
}
|
||||
|
||||
study -> river;
|
||||
river -> rnode;
|
||||
river -> redge;
|
||||
redge -> rnode;
|
||||
river -> boundaryconditionlist -> boundarycondition -> rnode;
|
||||
river -> lateralcontributionlist -> lateralcontribution -> redge;
|
||||
river -> initialconditionsdict -> initialconditions;
|
||||
initialconditions -> redge;
|
||||
river -> stricklerslist -> stricklers;
|
||||
river -> solverparameterslist;
|
||||
river -> sedimentlayerlist -> sedimentlayer -> layer;
|
||||
redge -> frictionlist -> stricklers;
|
||||
redge -> georeach -> geocrosssection -> geopoint;
|
||||
geocrosssection -> sedimentlayer;
|
||||
geopoint -> sedimentlayer;
|
||||
|
||||
results -> study[style="dashed"];
|
||||
results -> rriver;
|
||||
rriver -> river[style="dashed"];
|
||||
rriver -> rreach;
|
||||
rreach -> georeach[style="dashed"];
|
||||
rreach -> rcrosssection;
|
||||
rcrosssection -> geocrosssection[style="dashed"];
|
||||
}
|
||||
#+end_src
|
||||
|
||||
*** TODO SQL study file
|
||||
*** TODO List class
|
||||
*** TODO Dict class
|
||||
** TODO View
|
||||
*** TODO UI file
|
||||
*** TODO Translate
|
||||
*** TODO Window
|
||||
*** TODO UndoCommand
|
||||
*** TODO Table
|
||||
*** TODO Plot
|
||||
*** TODO Mainwindow
|
||||
** TODO Solver
|
||||
|
||||
#+name: graph-pipeline
|
||||
#+header: :results drawer
|
||||
#+header: :exports results
|
||||
#+header: :post attr_wrap(width="12cm", data=*this*, name="graph-pipeline", caption="Pamhyr2 solver execution pipeline architecture scheme", float="t")
|
||||
#+begin_src dot :file "images/graph-pipeline.png" :cache no
|
||||
digraph {
|
||||
bgcolor="transparent";
|
||||
node[colorscheme=set19,shape=box,style="filled",fillcolor=9];
|
||||
|
||||
subgraph cluster0 {
|
||||
label="Pamhyr2"
|
||||
config[label="Config",fillcolor=5];
|
||||
model[label="Model",fillcolor=2];
|
||||
obj[label="Solver",fillcolor=6];
|
||||
results[label="Results",fillcolor=2];
|
||||
view[label="View",fillcolor=1];
|
||||
|
||||
results -> model[style="dashed"];
|
||||
results -> obj[style="dashed"];
|
||||
}
|
||||
|
||||
config -> obj[label=""];
|
||||
obj -> model[style="dashed"];
|
||||
|
||||
subgraph cluster1{
|
||||
label="System";
|
||||
in[label="Solver input files",shape=note];
|
||||
out[label="Solver output files",shape=note];
|
||||
bin[label="Solver binary",shape=note];
|
||||
}
|
||||
|
||||
obj -> in[label="Write (1)"];
|
||||
obj -> bin[label="Execute (2)"];
|
||||
bin -> in[label="Read (2.1)"];
|
||||
bin -> out[label="Write (2.2)"];
|
||||
obj -> results[label="Create (3)"];
|
||||
obj -> out[label="Read (3.1)"];
|
||||
view -> model[style="dashed"];
|
||||
view -> results[style="dashed"];
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** TODO Unit tests
|
||||
** TODO The debug mode
|
||||
* TODO Build the project
|
||||
|
||||
The project uses gitlab-ci runners to build packages, but it is possible
|
||||
to build packages manually.
|
||||
|
||||
** Building packages
|
||||
|
||||
If you need an hand made package, you can script available in
|
||||
{{{file(packages)}}} directory.
|
||||
|
||||
*** GNU/Linux
|
||||
|
||||
On GNU/Linux building GNU/Linux packages is easy, you just need python
|
||||
in version 3.8 must be installed with venv and pyinstaller packages
|
||||
(see Listing [[linux-env-deb]] for Debian and derived system). Finally,
|
||||
run the {{{file(linux.sh)}}} script (see Listing [[linux-pkg]]).
|
||||
|
||||
#+NAME: linux-env-deb
|
||||
#+CAPTION: Install environment on GNU/Linux
|
||||
#+begin_src shell
|
||||
sudo apt install python3.8
|
||||
python3 -m pip install venv
|
||||
python3 -m pip install pyinstaller
|
||||
#+end_src
|
||||
|
||||
#+NAME: linux-pkg
|
||||
#+CAPTION: Build GNU/Linux package
|
||||
#+begin_src shell
|
||||
cd packages
|
||||
./linux.sh
|
||||
#+end_src
|
||||
|
||||
*** Windows
|
||||
|
||||
To make the Windows packages you have two choice: If you use Windows
|
||||
you can use the script {{{file(packages/windows.bat)}}}, other else
|
||||
you can use the script {{{file(packages/wine.sh)}}}. Each script need
|
||||
a specific software environment.
|
||||
|
||||
On windows, you needs python on version 3.8, pyinstaller and
|
||||
NSIS[fn:nsis] installed. On GNU/Linux you need wget, wine and
|
||||
winetricks installed.
|
||||
|
||||
[fn:nsis] The NSIS web site: https://sourceforge.net/projects/nsis/
|
||||
|
||||
** TODO Setup the CI environment
|
||||
* Documentation files
|
||||
|
||||
This document and the user documentation are org files. This text file
|
||||
format is formatted so that it can be exported in different formats:
|
||||
PDF (with latex), ODT, HTML, etc. It was originally designed for the
|
||||
GNUEmacs[fn:emacs] text editor, but can be edited with any text editor. Here we
|
||||
take a look at the different features used in these documents.
|
||||
|
||||
[fn:org] The org-mode website: https://orgmode.org/ (last access
|
||||
2023-09-15)
|
||||
[fn:emacs] The GNUEmacs project website: https://gnu.org/s/emacs/
|
||||
(last access 2023-09-15)
|
||||
|
||||
** Org-mode
|
||||
*** Document structure
|
||||
|
||||
Org uses the =*= character to define a new document section. To add a
|
||||
sub-section, you can add an additional =*= to the current section[fn::
|
||||
See document structure documentation:
|
||||
https://orgmode.org/org.html#Headlines (last access 2023-09-15)].
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
* Top level headline
|
||||
** Second level
|
||||
*** Third level
|
||||
some text
|
||||
*** Third level
|
||||
more text
|
||||
* Another top level headline
|
||||
#+END_EXAMPLE
|
||||
|
||||
*** Format
|
||||
|
||||
Org-mode is a markup file, using markup in the text to modify the
|
||||
appearance of a portion of text[fn:: See markup documentation:
|
||||
https://orgmode.org/org.html#Emphasis-and-Monospace (last access
|
||||
2023-09-15)].
|
||||
|
||||
| Markup | Results |
|
||||
|--------------------+------------------|
|
||||
| =*Bolt*= | *Bolt* |
|
||||
| =/Italic/= | /Italic/ |
|
||||
| =_underline_= | _underline_ |
|
||||
| ==verbatim== | =verbatim= |
|
||||
| =~code~= | ~code~ |
|
||||
| =+strike-through+= | +strike-through+ |
|
||||
|
||||
*** Source code blocks
|
||||
|
||||
You can add some code blocks[fn:: See org-mode documentation for
|
||||
source code: https://orgmode.org/org.html#Working-with-Source-Code
|
||||
(last access 2023-09-15)] in the document.
|
||||
|
||||
Here is an example for python source code:
|
||||
#+BEGIN_EXAMPLE
|
||||
#+CAPTION: Get os type name in Python code
|
||||
#+begin_src python
|
||||
import os
|
||||
|
||||
print(f"Document build on system: {os.name}")
|
||||
#+end_src
|
||||
#+END_EXAMPLE
|
||||
|
||||
If you use GNUEmacs, it is also possible to run the code inside a
|
||||
block and export (or not) the reuslts in the document.
|
||||
|
||||
#+OPTIONS: float:nil
|
||||
#+CAPTION: Get os type name in Python code
|
||||
#+begin_src python :python python3 :results output :exports both :noweb yes
|
||||
import os
|
||||
|
||||
print(f"Document build on system: {os.name}")
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: Document build on system: posix
|
||||
|
||||
*** LaTeX
|
||||
|
||||
If we export the file to PDF, org-mode use {{{latex}}}. So we can add
|
||||
some piece of {{{latex}}} into the document[fn:: See {{{latex}}} part
|
||||
in documentation: https://orgmode.org/org.html#Embedded-LaTeX (last
|
||||
access 2023-09-15)]. For exemple, we can add math formula like
|
||||
=$E=mc^2$= ($E=mc^2$) or =\[E=mc^2\]=:
|
||||
|
||||
\[E=mc^2\]
|
||||
|
||||
But we can also add every type of {{{latex}}}:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
# Add latex in line
|
||||
#+LATEX: <my line of latex>
|
||||
|
||||
# Add multiple line of LaTeX
|
||||
#+BEGIN_EXPORT latex
|
||||
<my latex here>
|
||||
#+END_EXPORT
|
||||
#+END_EXAMPLE
|
||||
|
||||
It is also possible to add specific {{{latex}}} file header with
|
||||
=#+LATEX_HEADER=. In this document we use the file
|
||||
{{{file(doc/tools/latex.org)}}} for all {{{latex}}} headers.
|
||||
|
||||
*** Macro
|
||||
|
||||
In this document, we use a few macros[fn:: See marcos documentation
|
||||
https://orgmode.org/org.html#Macro-Replacement (last access
|
||||
2023-09-15)] to simplify writing. They allow you to define sequences
|
||||
of text to be replaced, so that the macro name is replaced by its
|
||||
value. They are defined in the {{{file(doc/tools/macro.org)}}}
|
||||
file. Once defined, they can be used in the document as follows:
|
||||
={{{<macro-name>}}}=. You can also have macros with arguments, in this
|
||||
case: ={{{<macro-name>(arg1,...)}}}=. Les macros peuvent aussi
|
||||
utiliser du code emacs-lisp.
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
# Exemple of macro définition
|
||||
|
||||
#+MACRO: toto tata
|
||||
#+MACRO: add \(($1 + $2)\)
|
||||
#+MACRO: emacs-version (eval (nth 2 (split-string (emacs-version))))
|
||||
#+END_EXAMPLE
|
||||
|
||||
#+MACRO: toto tata
|
||||
#+MACRO: add \(($1 + $2)\)
|
||||
#+MACRO: emacs-version (eval (nth 2 (split-string (emacs-version))))
|
||||
|
||||
Macro apply:
|
||||
- Marco ={{{toto}}}=: {{{toto}}}
|
||||
- Marco ={{{add(x,y)}}}=: {{{add(x,y)}}}
|
||||
- Marco ={{{emacs-version}}}=: {{{emacs-version}}}
|
||||
|
||||
*** Footnotes
|
||||
|
||||
Footnote in org-mode is define with marker =[fn:...]=[fn:: Create
|
||||
footnotes in org-mode documentation
|
||||
https://orgmode.org/org.html#Creating-Footnotes (last access
|
||||
2023-09-15)]:
|
||||
#+BEGIN_EXAMPLE
|
||||
The Org website[fn:1] now looks a lot better than it used to.
|
||||
...
|
||||
[fn:1] The link is: https://orgmode.org
|
||||
#+END_EXAMPLE
|
||||
or:
|
||||
#+BEGIN_EXAMPLE
|
||||
The Org website[fn:: The link is: https://orgmode.org] now looks
|
||||
a lot better than it used to.
|
||||
...
|
||||
#+END_EXAMPLE
|
||||
|
||||
*** References
|
||||
|
||||
The references use the {{{latex}}} bibtex tools. The bib file is in
|
||||
{{{file(/doc/tools/ref.bib)}}} and use for developers and user
|
||||
documentation. In document, use ={{{cite(<name>)}}}= to cite a paper.
|
||||
|
||||
** Export
|
||||
|
||||
To export the files, a {{{file(build.sh)}}} script is available in the org
|
||||
files directories. On GNU/Linux system you can build the documentation
|
||||
PDF file with the command =./build.sh=. Texlive package must be
|
||||
installed, you can install only needed packages or all texlive
|
||||
packages, for example on Debian (and some derived system) use command
|
||||
Listing [[texlive-install]].
|
||||
|
||||
#+NAME: texlive-install
|
||||
#+CAPTION: Installation command for texlive full on Debian system
|
||||
#+begin_src shell
|
||||
sudo apt install texlive-full
|
||||
#+end_src
|
||||
|
||||
Some org-mode configuration used in documentations files are define in
|
||||
=/doc/tools/=:
|
||||
- {{{file(PamhyrDoc.cls)}}}: The {{{latex}}} document class
|
||||
- {{{file(macro.org)}}}: Available macro
|
||||
- {{{file(latex.org)}}}: {{{latex}}} configutation for documentations
|
||||
files
|
||||
- {{{file(setup.el)}}}: GNUEmacs configuration to build documentations
|
||||
- {{{file(ref.bib)}}}: Bibtex files for documentations files
|
||||
|
||||
* How to contribute?
|
||||
|
||||
Pamhyr2 is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License[fn:license], either
|
||||
version 3 of the License, or any later version.
|
||||
|
||||
[fn:license] The GPLv3 web page:
|
||||
https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
** Guidelines
|
||||
|
||||
To contribute to Pamhyr2, we expect a minimum of respect between
|
||||
contributors. We therefore ask you to respect the following rules
|
||||
regarding communication and contribution content:
|
||||
+ No gender, racial, religious or social discrimination
|
||||
+ No insults, personal attacks or potentially offensive remarks
|
||||
+ Pamhyr2 is free software, and intended to remain so, so take care
|
||||
with the licensing of libraries and external content you want to add
|
||||
to the project
|
||||
+ Humour or hidden easter eggs are welcome if they respect the
|
||||
previous rules
|
||||
|
||||
** Make a contribution
|
||||
|
||||
There are several ways to contribute: you can report a bug by creating
|
||||
an issue on the project's gitlab page[fn:p2-gitlab], or you can create
|
||||
a merge request on the same page with the changes you have made to the
|
||||
code, translation or documentation.
|
||||
|
||||
The Pamhyr2 copyright is owned by INRAE[fn:inrae], but we keep a
|
||||
record of each contributors. If you made a modification to pamhyr2
|
||||
software, please add your name at the end of {{{file(AUTHORS)}}} file
|
||||
and respect the Listing [[auth-format]] format. You can update this file
|
||||
information for following contribution.
|
||||
|
||||
#+NAME: auth-format
|
||||
#+CAPTION: =AUTHORS= file format
|
||||
#+begin_src text
|
||||
<first name> <last name> [(optional) email], <organisation>, <years>
|
||||
#+end_src
|
||||
|
||||
#+CAPTION: Current =AUTHORS= file
|
||||
#+INCLUDE: "../../AUTHORS" src text
|
||||
|
||||
[fn:p2-gitlab] The Pamhyr2 Gitlab project page:
|
||||
https://gitlab.irstea.fr/theophile.terraz/pamhyr
|
||||
[fn:inrae] The INRAE web site: https://www.inrae.fr/
|
||||
|
||||
** Translate
|
||||
|
||||
You can improve or add translation for the project. To contribute to
|
||||
Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open
|
||||
Qt-linguist and edite the translation ({{{file(.ts)}}}) file, finally,
|
||||
commit the new version of file and make a merge request.
|
||||
|
||||
If you want add a new language, edit the script
|
||||
{{{file(src/lang/create_ts.sh)}}} like Listing [[ts-it]]. Run the script
|
||||
and open the new file with Qt-linguist, setup target language (Figure
|
||||
[[qt-linguist-setup]]) and complete translation. Finally, commit the new
|
||||
file and make a merge request.
|
||||
|
||||
#+NAME: ts-it
|
||||
#+CAPTION: Example of modified {{{file(src/lang/create_ts.sh)}}} to add italian (it) translate for Pamhyr2
|
||||
#+begin_src shell
|
||||
...
|
||||
LANG="fr it"
|
||||
...
|
||||
#+end_src
|
||||
|
||||
#+NAME: qt-linguist-setup
|
||||
#+ATTR_LATEX: :width 8cm
|
||||
#+CAPTION: Qt linguist lang setup example with italian.
|
||||
[[./images/Qt-linguist-setup-lang.png]]
|
||||
|
||||
[fn:qt-linguist] The Qt linguist documentation web page:
|
||||
https://doc.qt.io/qt-5/qtlinguist-index.html (last access 2023-09-18)
|
||||
|
||||
** Code contribution
|
||||
|
||||
If you are developper you can improve and/or add features to
|
||||
Pamhyr2. Please, follow the architecture described in section
|
||||
[[Architecture]] as closely as possible. Keep the code simple, clear and
|
||||
efficient as possible. The master branch is reserved for the project
|
||||
maintainer; you can create a new branch or fork the project before the
|
||||
request.
|
||||
|
||||
|
||||
{{{biblio}}}
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,692 @@
|
||||
diff --git a/packages/linux.sh b/packages/linux.sh
|
||||
index 215047e..a19bfb6 100755
|
||||
--- a/packages/linux.sh
|
||||
+++ b/packages/linux.sh
|
||||
@@ -76,11 +76,11 @@ mv ../pamhyr-src.tar.gz ./
|
||||
|
||||
echo " *** MAKE BIN PACKAGE"
|
||||
|
||||
-OLD_PWD=$PWD
|
||||
-cd dist/
|
||||
-tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
|
||||
-cd $OLD_PWD
|
||||
+# OLD_PWD=$PWD
|
||||
+# cd dist/
|
||||
+# tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
|
||||
+# cd $OLD_PWD
|
||||
|
||||
-mv dist/pamhyr-gnulinux-amd64.tar.xz ./
|
||||
+# mv dist/pamhyr-gnulinux-amd64.tar.xz ./
|
||||
|
||||
echo " *** DONE"
|
||||
diff --git a/src/Model/Friction/Friction.py b/src/Model/Friction/Friction.py
|
||||
index 18167a8..5bf2302 100644
|
||||
--- a/src/Model/Friction/Friction.py
|
||||
+++ b/src/Model/Friction/Friction.py
|
||||
@@ -170,6 +170,7 @@ class Friction(SQLSubModel):
|
||||
@begin_strickler.setter
|
||||
def begin_strickler(self, strickler):
|
||||
self._begin_strickler = strickler
|
||||
+ self._end_strickler = strickler # HACK: Only one Strickler
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
diff --git a/src/Model/Stricklers/Stricklers.py b/src/Model/Stricklers/Stricklers.py
|
||||
index baa484f..a5670d2 100644
|
||||
--- a/src/Model/Stricklers/Stricklers.py
|
||||
+++ b/src/Model/Stricklers/Stricklers.py
|
||||
@@ -38,7 +38,7 @@ class Stricklers(SQLSubModel):
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
- Stricklers._id_cnt = max(Stricklers._id_cnt + 1, self.id)
|
||||
+ Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
|
||||
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py
|
||||
index c4ff8f8..172999a 100644
|
||||
--- a/src/View/BoundaryCondition/translate.py
|
||||
+++ b/src/View/BoundaryCondition/translate.py
|
||||
@@ -29,9 +29,9 @@ _translate = QCoreApplication.translate
|
||||
long_types = {
|
||||
"ND": _translate("BoundaryCondition", "Not defined"),
|
||||
"PC": _translate("BoundaryCondition", "Ponctual contribution"),
|
||||
- "TZ": _translate("BoundaryCondition", "Time over Z"),
|
||||
- "TD": _translate("BoundaryCondition", "Time over Discharge"),
|
||||
- "ZD": _translate("BoundaryCondition", "Z over Discharge"),
|
||||
+ "TZ": _translate("BoundaryCondition", "TimeZ"),
|
||||
+ "TD": _translate("BoundaryCondition", "TimeDischarge"),
|
||||
+ "ZD": _translate("BoundaryCondition", "ZDischarge"),
|
||||
"SL": _translate("BoundaryCondition", "Solid"),
|
||||
}
|
||||
|
||||
diff --git a/src/View/Frictions/PlotStricklers.py b/src/View/Frictions/PlotStricklers.py
|
||||
index 71ff6e6..b9060bc 100644
|
||||
--- a/src/View/Frictions/PlotStricklers.py
|
||||
+++ b/src/View/Frictions/PlotStricklers.py
|
||||
@@ -106,6 +106,8 @@ class PlotStricklers(APlot):
|
||||
)
|
||||
self.draw_frictions(lst, color="b")
|
||||
|
||||
+ self.canvas.axes.invert_xaxis()
|
||||
+
|
||||
self.canvas.figure.tight_layout()
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py
|
||||
index 4af3b66..f46fae3 100644
|
||||
--- a/src/View/Frictions/Window.py
|
||||
+++ b/src/View/Frictions/Window.py
|
||||
@@ -112,10 +112,10 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
|
||||
)
|
||||
|
||||
table.setItemDelegateForColumn(
|
||||
- 3, self._delegate_stricklers
|
||||
+ 2, self._delegate_stricklers
|
||||
)
|
||||
table.setItemDelegateForColumn(
|
||||
- 4, self._delegate_stricklers
|
||||
+ 3, self._delegate_stricklers
|
||||
)
|
||||
|
||||
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
diff --git a/src/View/Frictions/translate.py b/src/View/Frictions/translate.py
|
||||
index 21981ab..82071e9 100644
|
||||
--- a/src/View/Frictions/translate.py
|
||||
+++ b/src/View/Frictions/translate.py
|
||||
@@ -21,10 +21,10 @@ from PyQt5.QtCore import QCoreApplication
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
table_headers = {
|
||||
- "name": _translate("Frictions", "Name"),
|
||||
+ # "name": _translate("Frictions", "Name"),
|
||||
# "edge": _translate("Frictions", "Reach"),
|
||||
"begin_kp": _translate("Frictions", "Begin kp (m)"),
|
||||
"end_kp": _translate("Frictions", "End kp (m)"),
|
||||
- "begin_strickler": _translate("Frictions", "Begin strickler"),
|
||||
- "end_strickler": _translate("Frictions", "End strickler"),
|
||||
+ "begin_strickler": _translate("Frictions", "Strickler"),
|
||||
+ # "end_strickler": _translate("Frictions", "End strickler"),
|
||||
}
|
||||
diff --git a/src/View/Geometry/PlotAC.py b/src/View/Geometry/PlotAC.py
|
||||
index fe6d933..e43f39e 100644
|
||||
--- a/src/View/Geometry/PlotAC.py
|
||||
+++ b/src/View/Geometry/PlotAC.py
|
||||
@@ -67,18 +67,18 @@ class PlotAC(APlot):
|
||||
gl = self.data.profile(selected_profile).names()
|
||||
|
||||
self.canvas.axes.set_xlabel(
|
||||
- _translate("MainWindow_reach", "Abscisse en travers (m)"),
|
||||
+ _translate("MainWindow_reach", "Transverse abscissa (m)"),
|
||||
color='green', fontsize=12
|
||||
)
|
||||
self.canvas.axes.set_ylabel(
|
||||
- _translate("MainWindow_reach", "Cote (m)"),
|
||||
+ _translate("MainWindow_reach", "Height (m)"),
|
||||
color='green', fontsize=12
|
||||
)
|
||||
self.canvas.figure.tight_layout()
|
||||
|
||||
- label_before_plot_selected = _translate("MainWindow_reach", "Profil précédent")
|
||||
- label_plot_selected = _translate("MainWindow_reach", "Profil sélectionné")
|
||||
- label_after_plot_selected = _translate("MainWindow_reach", "Profil suivant")
|
||||
+ label_before_plot_selected = _translate("MainWindow_reach", "Previous cross-section")
|
||||
+ label_plot_selected = _translate("MainWindow_reach", "Cross-section")
|
||||
+ label_after_plot_selected = _translate("MainWindow_reach", "Next cross-section")
|
||||
color_before_plot_selected = "k" # 'grey'
|
||||
color_plot_selected = 'b'
|
||||
color_after_plot_selected = 'm'
|
||||
diff --git a/src/View/Geometry/PlotKPZ.py b/src/View/Geometry/PlotKPZ.py
|
||||
index eb7d5e2..64057aa 100644
|
||||
--- a/src/View/Geometry/PlotKPZ.py
|
||||
+++ b/src/View/Geometry/PlotKPZ.py
|
||||
@@ -158,6 +158,8 @@ class PlotKPZ(APlot):
|
||||
color='lightgrey'
|
||||
)
|
||||
|
||||
+ self.canvas.axes.invert_xaxis()
|
||||
+
|
||||
self.canvas.figure.tight_layout()
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py
|
||||
index 7776d73..1c8cb5d 100644
|
||||
--- a/src/View/Geometry/Window.py
|
||||
+++ b/src/View/Geometry/Window.py
|
||||
@@ -111,7 +111,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow):
|
||||
self.ui.btn_sort_desc.triggered.connect(self.sort_descending)
|
||||
self.ui.btn_move_up.triggered.connect(self.move_row_up)
|
||||
self.ui.btn_move_down.triggered.connect(self.move_row_down)
|
||||
- self.ui.btn_end_editing.triggered.connect(self.handleSave)
|
||||
+ # self.ui.btn_end_editing.triggered.connect(self.handleSave)
|
||||
self.ui.btn_add.triggered.connect(self.insert_row)
|
||||
self.ui.btn_delete.triggered.connect(self.delete_rows)
|
||||
self.ui.btn_edit.triggered.connect(self.edit_profile)
|
||||
diff --git a/src/View/Geometry/mainwindow_ui_reach.py b/src/View/Geometry/mainwindow_ui_reach.py
|
||||
index 897b698..60bcfa6 100644
|
||||
--- a/src/View/Geometry/mainwindow_ui_reach.py
|
||||
+++ b/src/View/Geometry/mainwindow_ui_reach.py
|
||||
@@ -95,29 +95,29 @@ class Ui_MainWindow(object):
|
||||
self.btn_edit.setIcon(icon_btn_edit)
|
||||
self.toolBar.addAction(self.btn_edit)
|
||||
|
||||
- self.btn_copy_selected_profile = QtWidgets.QAction(self.widget)
|
||||
- self.btn_copy_selected_profile.setObjectName("btn_copy_selected_profile")
|
||||
- icon_btn_copy_selected_profile = QtGui.QIcon()
|
||||
- icon_btn_copy_selected_profile.addPixmap(
|
||||
- QtGui.QPixmap(f"{icon_path}gtk-copy.png"))
|
||||
- self.btn_copy_selected_profile.setIcon(icon_btn_copy_selected_profile)
|
||||
- self.toolBar.addAction(self.btn_copy_selected_profile)
|
||||
-
|
||||
- self.btn_paste_selected_profile = QtWidgets.QAction(self.widget)
|
||||
- self.btn_paste_selected_profile.setObjectName("btn_paste_selected_profile")
|
||||
- icon_btn_paste_selected_profile = QtGui.QIcon()
|
||||
- icon_btn_paste_selected_profile.addPixmap(
|
||||
- QtGui.QPixmap(f"{icon_path}gtk-paste.png"))
|
||||
- self.btn_paste_selected_profile.setIcon(icon_btn_paste_selected_profile)
|
||||
- self.toolBar.addAction(self.btn_paste_selected_profile)
|
||||
-
|
||||
- self.btn_duplicate_selected_profile = QtWidgets.QAction(self.widget)
|
||||
- self.btn_duplicate_selected_profile.setObjectName("btn_duplicate_selected_profile")
|
||||
- icon_btn_duplicate_selected_profile = QtGui.QIcon()
|
||||
- icon_btn_duplicate_selected_profile.addPixmap(
|
||||
- QtGui.QPixmap(f"{icon_path}gtk-dnd-multiple.png"))
|
||||
- self.btn_duplicate_selected_profile.setIcon(icon_btn_duplicate_selected_profile)
|
||||
- self.toolBar.addAction(self.btn_duplicate_selected_profile)
|
||||
+ # self.btn_copy_selected_profile = QtWidgets.QAction(self.widget)
|
||||
+ # self.btn_copy_selected_profile.setObjectName("btn_copy_selected_profile")
|
||||
+ # icon_btn_copy_selected_profile = QtGui.QIcon()
|
||||
+ # icon_btn_copy_selected_profile.addPixmap(
|
||||
+ # QtGui.QPixmap(f"{icon_path}gtk-copy.png"))
|
||||
+ # self.btn_copy_selected_profile.setIcon(icon_btn_copy_selected_profile)
|
||||
+ # self.toolBar.addAction(self.btn_copy_selected_profile)
|
||||
+
|
||||
+ # self.btn_paste_selected_profile = QtWidgets.QAction(self.widget)
|
||||
+ # self.btn_paste_selected_profile.setObjectName("btn_paste_selected_profile")
|
||||
+ # icon_btn_paste_selected_profile = QtGui.QIcon()
|
||||
+ # icon_btn_paste_selected_profile.addPixmap(
|
||||
+ # QtGui.QPixmap(f"{icon_path}gtk-paste.png"))
|
||||
+ # self.btn_paste_selected_profile.setIcon(icon_btn_paste_selected_profile)
|
||||
+ # self.toolBar.addAction(self.btn_paste_selected_profile)
|
||||
+
|
||||
+ # self.btn_duplicate_selected_profile = QtWidgets.QAction(self.widget)
|
||||
+ # self.btn_duplicate_selected_profile.setObjectName("btn_duplicate_selected_profile")
|
||||
+ # icon_btn_duplicate_selected_profile = QtGui.QIcon()
|
||||
+ # icon_btn_duplicate_selected_profile.addPixmap(
|
||||
+ # QtGui.QPixmap(f"{icon_path}gtk-dnd-multiple.png"))
|
||||
+ # self.btn_duplicate_selected_profile.setIcon(icon_btn_duplicate_selected_profile)
|
||||
+ # self.toolBar.addAction(self.btn_duplicate_selected_profile)
|
||||
|
||||
self.btn_sort_asc = QtWidgets.QAction(self.widget)
|
||||
self.btn_sort_asc.setObjectName("btn_sort_asc")
|
||||
@@ -151,13 +151,13 @@ class Ui_MainWindow(object):
|
||||
self.btn_move_down.setIcon(icon_btn_move_down)
|
||||
self.toolBar.addAction(self.btn_move_down)
|
||||
|
||||
- self.btn_end_editing = QtWidgets.QAction(self.widget)
|
||||
- self.btn_end_editing.setObjectName("btn_end_editing")
|
||||
- icon_btn_save = QtGui.QIcon()
|
||||
- icon_btn_save.addPixmap(
|
||||
- QtGui.QPixmap(f"{icon_path}gtk-apply.png"))
|
||||
- self.btn_end_editing.setIcon(icon_btn_save)
|
||||
- self.toolBar.addAction(self.btn_end_editing)
|
||||
+ # self.btn_end_editing = QtWidgets.QAction(self.widget)
|
||||
+ # self.btn_end_editing.setObjectName("btn_end_editing")
|
||||
+ # icon_btn_save = QtGui.QIcon()
|
||||
+ # icon_btn_save.addPixmap(
|
||||
+ # QtGui.QPixmap(f"{icon_path}gtk-apply.png"))
|
||||
+ # self.btn_end_editing.setIcon(icon_btn_save)
|
||||
+ # self.toolBar.addAction(self.btn_end_editing)
|
||||
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.tableView = QtWidgets.QTableView(self.widget)
|
||||
@@ -314,30 +314,23 @@ class Ui_MainWindow(object):
|
||||
def retranslateUi(self, MainWindow_reach):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.name_reach = ""
|
||||
- self.mainwindow_title = _translate("MainWindow_reach", "Jeu de sections du Bief")
|
||||
+ self.mainwindow_title = _translate("MainWindow_reach", "Geometry reach edit")
|
||||
MainWindow_reach.setWindowTitle(self.mainwindow_title + f"{self.name_reach}")
|
||||
- self.text_section_order = _translate("MainWindow_reach", "\nOrdre des sections : "
|
||||
- "Amont --> Aval")
|
||||
# self.label_section_order.setText(f"{self.text_section_order}")
|
||||
self.vertical_slider_value = ""
|
||||
- self.vertical_slider_label.setText(_translate("MainWindow_reach", "Pk = ") + f"{self.vertical_slider_value}")
|
||||
- self.btn_open.setToolTip(_translate("MainWindow_reach", " Importer une géométrie"))
|
||||
- self.btn_add.setToolTip(_translate("MainWindow_reach", " Nouveau profil"))
|
||||
- self.btn_delete.setToolTip(_translate("MainWindow_reach", " Supprimer le profil sélectionné"))
|
||||
- self.btn_edit.setToolTip(_translate("MainWindow_reach", " Éditer le profil sélectionné"))
|
||||
- self.btn_copy_selected_profile.setToolTip(_translate("MainWindow_reach", " Copier le profil sélectionné"))
|
||||
- self.btn_paste_selected_profile.setToolTip(_translate("MainWindow_reach", "Coller le profil en fin de liste ("
|
||||
- "penser à modifier le Pk avant de "
|
||||
- "trier)"))
|
||||
- self.btn_duplicate_selected_profile.setToolTip(
|
||||
- _translate("MainWindow_reach", " Dupliquer la section sélectionnée"))
|
||||
+ self.vertical_slider_label.setText(_translate("MainWindow_reach", "Kp = ") + f"{self.vertical_slider_value}")
|
||||
+ self.btn_open.setToolTip(_translate("MainWindow_reach", " Import geometry"))
|
||||
+ self.btn_add.setToolTip(_translate("MainWindow_reach", " New profile"))
|
||||
+ self.btn_delete.setToolTip(_translate("MainWindow_reach", " Delete selected profile"))
|
||||
+ self.btn_edit.setToolTip(_translate("MainWindow_reach", " Editer selected profile"))
|
||||
+
|
||||
self.btn_sort_asc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre croissant des Pk"))
|
||||
self.btn_sort_desc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre décroissant des Pk"))
|
||||
self.btn_move_up.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le "
|
||||
"profil sélectionné vers le haut)"))
|
||||
self.btn_move_down.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le "
|
||||
"profil sélectionné vers le bas)"))
|
||||
- self.btn_end_editing.setToolTip(_translate("MainWindow_reach", " Terminer l'édition"))
|
||||
+ # self.btn_end_editing.setToolTip(_translate("MainWindow_reach", " Terminer l'édition"))
|
||||
self.tableView_header = [_translate("MainWindow_reach", "Name"),
|
||||
_translate("MainWindow_reach", "Kp (m)"),
|
||||
_translate("MainWindow_reach", "Type")]
|
||||
diff --git a/src/View/InitialConditions/PlotDKP.py b/src/View/InitialConditions/PlotDKP.py
|
||||
index 58a8321..445cc08 100644
|
||||
--- a/src/View/InitialConditions/PlotDKP.py
|
||||
+++ b/src/View/InitialConditions/PlotDKP.py
|
||||
@@ -71,6 +71,8 @@ class PlotDKP(APlot):
|
||||
color='b', marker='+', lw=1.
|
||||
)
|
||||
|
||||
+ self.canvas.axes.invert_xaxis()
|
||||
+
|
||||
self.canvas.figure.tight_layout()
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
diff --git a/src/View/InitialConditions/PlotDischarge.py b/src/View/InitialConditions/PlotDischarge.py
|
||||
index 1fe05de..0703c59 100644
|
||||
--- a/src/View/InitialConditions/PlotDischarge.py
|
||||
+++ b/src/View/InitialConditions/PlotDischarge.py
|
||||
@@ -65,6 +65,8 @@ class PlotDischarge(APlot):
|
||||
color='r', lw=1.
|
||||
)
|
||||
|
||||
+ self.canvas.axes.invert_xaxis()
|
||||
+
|
||||
self.canvas.figure.tight_layout()
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py
|
||||
index 370d4a2..3fe45f7 100644
|
||||
--- a/src/View/InitialConditions/translate.py
|
||||
+++ b/src/View/InitialConditions/translate.py
|
||||
@@ -21,11 +21,11 @@ from PyQt5.QtCore import QCoreApplication
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
table_headers = {
|
||||
- "name": _translate("LateralContribution", "Name"),
|
||||
+ # "name": _translate("LateralContribution", "Name"),
|
||||
"kp": _translate("LateralContribution", "KP (m)"),
|
||||
# "speed": _translate("LateralContribution", "Speed (m/s)"),
|
||||
"discharge": _translate("LateralContribution", "Discharge (m³/s)"),
|
||||
"elevation": _translate("LateralContribution", "Elevation (m)"),
|
||||
"height": _translate("LateralContribution", "Height (m)"),
|
||||
- "comment": _translate("LateralContribution", "Comment"),
|
||||
+ # "comment": _translate("LateralContribution", "Comment"),
|
||||
}
|
||||
diff --git a/src/View/SedimentLayers/Edit/translate.py b/src/View/SedimentLayers/Edit/translate.py
|
||||
index fe9e44a..f9f02cb 100644
|
||||
--- a/src/View/SedimentLayers/Edit/translate.py
|
||||
+++ b/src/View/SedimentLayers/Edit/translate.py
|
||||
@@ -6,7 +6,7 @@ _translate = QCoreApplication.translate
|
||||
|
||||
table_headers = {
|
||||
"name": _translate("SedimentLayers", "Name"),
|
||||
- "type": _translate("SedimentLayers", "Type"),
|
||||
+ # "type": _translate("SedimentLayers", "Type"),
|
||||
"height": _translate("Sedimentlayers", "Height"),
|
||||
"d50": _translate("Sedimentlayers", "D50"),
|
||||
"sigma": _translate("Sedimentlayers", "Sigma"),
|
||||
diff --git a/src/View/SedimentLayers/Reach/Plot.py b/src/View/SedimentLayers/Reach/Plot.py
|
||||
index a312a8c..5764abf 100644
|
||||
--- a/src/View/SedimentLayers/Reach/Plot.py
|
||||
+++ b/src/View/SedimentLayers/Reach/Plot.py
|
||||
@@ -78,6 +78,8 @@ class Plot(APlot):
|
||||
color='grey' if i == len(z_sl) - 1 else None
|
||||
)
|
||||
|
||||
+ self.canvas.axes.invert_xaxis()
|
||||
+
|
||||
self.canvas.figure.tight_layout()
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
diff --git a/src/lang/fr.ts b/src/lang/fr.ts
|
||||
index 8034809..031f349 100644
|
||||
--- a/src/lang/fr.ts
|
||||
+++ b/src/lang/fr.ts
|
||||
@@ -60,21 +60,6 @@
|
||||
<source>Ponctual contribution</source>
|
||||
<translation>Contributions ponctuelles</translation>
|
||||
</message>
|
||||
- <message>
|
||||
- <location filename="../View/BoundaryCondition/translate.py" line="32"/>
|
||||
- <source>Time over Z</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/BoundaryCondition/translate.py" line="33"/>
|
||||
- <source>Time over Discharge</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/BoundaryCondition/translate.py" line="34"/>
|
||||
- <source>Z over Discharge</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
<message>
|
||||
<location filename="../View/BoundaryCondition/translate.py" line="39"/>
|
||||
<source>Name</source>
|
||||
@@ -110,6 +95,21 @@
|
||||
<source>Solid</source>
|
||||
<translation type="unfinished">Solide</translation>
|
||||
</message>
|
||||
+ <message>
|
||||
+ <location filename="../View/BoundaryCondition/translate.py" line="32"/>
|
||||
+ <source>TimeZ</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/BoundaryCondition/translate.py" line="33"/>
|
||||
+ <source>TimeDischarge</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/BoundaryCondition/translate.py" line="34"/>
|
||||
+ <source>ZDischarge</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Checker</name>
|
||||
@@ -533,7 +533,7 @@
|
||||
<message>
|
||||
<location filename="../View/Frictions/translate.py" line="24"/>
|
||||
<source>Name</source>
|
||||
- <translation>Nom</translation>
|
||||
+ <translation type="obsolete">Nom</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Frictions/translate.py" line="26"/>
|
||||
@@ -548,12 +548,17 @@
|
||||
<message>
|
||||
<location filename="../View/Frictions/translate.py" line="28"/>
|
||||
<source>Begin strickler</source>
|
||||
- <translation>strickler de départ</translation>
|
||||
+ <translation type="obsolete">strickler de départ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Frictions/translate.py" line="29"/>
|
||||
<source>End strickler</source>
|
||||
- <translation>Strickler de fin</translation>
|
||||
+ <translation type="obsolete">Strickler de fin</translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Frictions/translate.py" line="28"/>
|
||||
+ <source>Strickler</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -587,7 +592,7 @@
|
||||
<context>
|
||||
<name>LateralContribution</name>
|
||||
<message>
|
||||
- <location filename="../View/InitialConditions/translate.py" line="24"/>
|
||||
+ <location filename="../View/LateralContribution/translate.py" line="35"/>
|
||||
<source>Name</source>
|
||||
<translation>Nom</translation>
|
||||
</message>
|
||||
@@ -602,7 +607,7 @@
|
||||
<translation>Lit moyen</translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/InitialConditions/translate.py" line="30"/>
|
||||
+ <location filename="../View/Stricklers/translate.py" line="31"/>
|
||||
<source>Comment</source>
|
||||
<translation>Commentaire</translation>
|
||||
</message>
|
||||
@@ -1715,7 +1720,7 @@ sur le plan défini par les deux points nommés extrêmes </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
-<context>
|
||||
+<context encoding="UTF-8">
|
||||
<name>MainWindow_reach</name>
|
||||
<message>
|
||||
<location filename="../View/Frictions/PlotStricklers.py" line="76"/>
|
||||
@@ -1723,7 +1728,7 @@ sur le plan défini par les deux points nommés extrêmes </source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="335"/>
|
||||
<source>Kp (m)</source>
|
||||
<translation>Pk (m)</translation>
|
||||
</message>
|
||||
@@ -1778,68 +1783,47 @@ sur le plan défini par les deux points nommés extrêmes </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="317"/>
|
||||
- <source>Jeu de sections du Bief</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="319"/>
|
||||
- <source>
|
||||
-Ordre des sections : Amont --> Aval</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="323"/>
|
||||
- <source>Pk = </source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="325"/>
|
||||
- <source> Nouveau profil</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="334"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="327"/>
|
||||
<source> Trier les profils par ordre croissant des Pk</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="341"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="334"/>
|
||||
<source>Name</source>
|
||||
<translation>Nom</translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="343"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="336"/>
|
||||
<source>Type</source>
|
||||
<translation>Type</translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="338"/>
|
||||
<source>Alt+Z</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="348"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="341"/>
|
||||
<source>Alt+E</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="351"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="344"/>
|
||||
<source>Alt+R</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="355"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="348"/>
|
||||
<source>Vue globale automatique (Alt+S)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="359"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="352"/>
|
||||
<source>Vue globale automatique (Alt+D)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="363"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="356"/>
|
||||
<source>Vue globale automatique (Alt+F)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -1889,68 +1873,33 @@ Seuls les 5 premiers seront édités.</source>
|
||||
<source>Profil sélectionné</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="324"/>
|
||||
- <source> Importer une géométrie</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="326"/>
|
||||
- <source> Supprimer le profil sélectionné</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="327"/>
|
||||
- <source> Éditer le profil sélectionné</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
<message encoding="UTF-8">
|
||||
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="328"/>
|
||||
- <source> Copier le profil sélectionné</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/>
|
||||
- <source>Coller le profil en fin de liste (penser à modifier le Pk avant de trier)</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="332"/>
|
||||
- <source> Dupliquer la section sélectionnée</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="335"/>
|
||||
<source> Trier les profils par ordre décroissant des Pk</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="336"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/>
|
||||
<source> Changer l'ordre des profils (en décalant le profil sélectionné vers le haut)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="338"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="331"/>
|
||||
<source> Changer l'ordre des profils (en décalant le profil sélectionné vers le bas)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="340"/>
|
||||
- <source> Terminer l'édition</source>
|
||||
- <translation type="unfinished"></translation>
|
||||
- </message>
|
||||
- <message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="346"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="339"/>
|
||||
<source>Vue isométrique (Alt+Z)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="349"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
|
||||
<source>Vue isométrique (Alt+E)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="352"/>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
|
||||
<source>Vue isométrique (Alt+R)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -1960,7 +1909,7 @@ Seuls les 5 premiers seront édités.</source>
|
||||
<translation type="unfinished">Altitude (m)</translation>
|
||||
</message>
|
||||
<message>
|
||||
- <location filename="../View/Geometry/PlotKPZ.py" line="66"/>
|
||||
+ <location filename="../View/Geometry/PlotAC.py" line="73"/>
|
||||
<source>Height (m)</source>
|
||||
<translation type="unfinished">Hauteur (m)</translation>
|
||||
</message>
|
||||
@@ -1969,6 +1918,56 @@ Seuls les 5 premiers seront édités.</source>
|
||||
<source>Select destination file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="317"/>
|
||||
+ <source>Geometry reach edit</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="321"/>
|
||||
+ <source>Kp = </source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="322"/>
|
||||
+ <source> Import geometry</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="323"/>
|
||||
+ <source> New profile</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="324"/>
|
||||
+ <source> Delete selected profile</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="325"/>
|
||||
+ <source> Editer selected profile</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/PlotAC.py" line="69"/>
|
||||
+ <source>Transverse abscissa (m)</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/PlotAC.py" line="79"/>
|
||||
+ <source>Previous cross-section</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/PlotAC.py" line="80"/>
|
||||
+ <source>Cross-section</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
+ <message>
|
||||
+ <location filename="../View/Geometry/PlotAC.py" line="81"/>
|
||||
+ <source>Next cross-section</source>
|
||||
+ <translation type="unfinished"></translation>
|
||||
+ </message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Network</name>
|
||||
@@ -2071,7 +2070,7 @@ Seuls les 5 premiers seront édités.</source>
|
||||
<message>
|
||||
<location filename="../View/SedimentLayers/Edit/translate.py" line="9"/>
|
||||
<source>Type</source>
|
||||
- <translation type="unfinished">Type</translation>
|
||||
+ <translation type="obsolete">Type</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/SedimentLayers/Reach/Profile/Window.py" line="68"/>
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,131 @@
|
||||
Pamhyr2: a graphical user interface for 1D hydro-sedimentary modelling of rivers
|
||||
Pierre-Antoine Rouby1, Benoît Camenen, Lionel Pénard, Léa Kieffer, Théophile Terraz1
|
||||
INRAE, UR RiverLy, centre de Lyon-Grenoble, 5 rue de la Doua, CS20244, 69625, Villeurbanne, France.
|
||||
pierre-antoine.rouby@inrae.fr, benoit.camenen@inrae.fr, lionel.penard@inrae.fr,
|
||||
lea.kieffer@inrae.fr, theophile.terraz@inrae.fr
|
||||
KEY WORDS
|
||||
1D modelling, sedimentary modelling, user interface
|
||||
Abstract
|
||||
In numerical river simulation, one-dimensional models are commonly used to study water level and discharge for large domains or long time series. These models are less time-consuming than two- and three-dimensional numerical models, and require fewer input parameters and allow ensemble runs. To build a one-dimensional hydraulic model, a pre- and post-processing tool is needed for creating reach geometry, specifying initial and boundary conditions, friction coefficients and other numerical parameters. Such a tool needs to ensure the consistency of the model and provide a user-friendly graphical user interface. In this article, we present Pamhyr2, the fully rebuilt version of the PAMHYR modelling platform. It is developed using Python, PyQt and Matplotlib. Pamhyr2 is free and open-source, multilingual, cross-platform (Linux, Windows) and is generic enough to accept various one-dimensional solvers. Pamhyr2 includes and enhances all the features from the previous version: multiple-reach modelling, geometry definition from cross-section data, initial and boundary conditions, friction coefficients, hydraulic structures, lateral inflow, punctual intake and the results visualization window. In addition, this version includes new features such as pollutants modelling, bed-load and bed evolution. We describe several windows: the creation of sedimentary layers in the river bed, the sediment characteristics for each layer, the sediment, and pollutant boundary conditions and the lateral inputs. These functionalities are illustrated with simple examples. We finally show the visualization windows for bed evolution.
|
||||
1. introduction
|
||||
Numerous tools are available to simulate river flow. Among them, one-dimensional (1D) numerical hydraulic models have the advantage of being accurate while being inexpensive in terms of computation time, making it possible to simulate long rivers over very long timescales. A lot of 1D numerical solvers for river hydraulics are available, including HEC-RAS2, Mike 113 and Mascaret4 for example. However, in order to investigate specific research issues it is essential to have the ability to edit the source code of numeric solvers and to have an ergonomic Graphical User Interace (GUI). Most of the widespread 1D modeling tools are either written with a proprietary source code (such as HEC-RAS and Mike 11) or are research tools with irregularly maintained GUI. On its side, INRAE has been developing two 1D solvers for over 40 years: Mage5 [5] and RubarBE6. In parallel, a GUI named PAMHyR has been developed to enable modelers to build river models, parameterize calculations, launch Mage and RubarBE solvers and post-process the results [3].
|
||||
|
||||
PAMHyR, developed in Java since the end of 90s (originally named X-NT), it is written with the following constraints in mind:
|
||||
• Multi-platform Windows, Unix-like,
|
||||
• no dependence on an external graphics library with a paid license for user,
|
||||
• generate as much code as possible from a UML diagram.
|
||||
Now, PAMHyR suffers from complexity due to highly specific interface, as well as a significant technological debt [4]: old external library no longer maintained, over dated project architecture, and numerous modifications by multiple developers (and internship) over short periods of time, with no vision of long-term maintainability. What's more, the software is only in French and the code is closed (the sources are not available), which is a hindrance to the notoriety of the software as well as to the vitality of development. In addition, no significant changes have been made in the application since 2016, even if PAMHyR is still often used for river modeling [2].
|
||||
INRAE needs a new tool to keep up with today’s technologies and with the development of its numerical solvers. In this paper we introduce the current work on the new graphical user interface Pamhyr2.
|
||||
2. Objective
|
||||
2.1 History and need
|
||||
PAMHyR is available for two solvers, with two different types of studies: Mage and RubarBE. These two different kinds of studies have mostly similar interfaces, but have their own specific features. They can both be used to: define reach geometry, boundary conditions, lateral inflows, overflows, friction, and structures. In addition, you can run the solver on the study you are editing, and view the solver's results. Firstly, although both Mage and RubarBE are multiple-reach compatible, only the Mage interface enables multiple-reach study editing. Secondly, many small details change from one interface to the next. In addition, the latest Mage features, for example, are not available for modeling in the interface.
|
||||
|
||||
A total overhaul of the Java code would be too much work, given the technological debt accumulated since the beginning of its development. Given the workload involved in modernizing PAMHyR, the decision is made to develop a new version of the software from scratch. This version should be equivalent to the Java version in terms of functionality, and add new features, notably the new Mage functionalities. The aim is also to have an interface that is more modern, more user-friendly, more flexible, available on multiple platforms and in different languages. The interface must also have a certain degree of generality and be able to adapt to different solvers without major changes to the interface. It is also important that the code is open to contributions, to keep the project as lively as possible.
|
||||
2.2 Technical choices
|
||||
From a technical point of view, choices were made on the basis of the above-mentioned objectives (modernity, maintainability, multiplatform, multilingual, user-friendliness, flexibility), as well as: the ease of learning the technology to facilitate contribution to the software, the popularity of the technology in the scientific community and the interoperability of this technology.
|
||||
|
||||
We chose Python7 as our programming language. It is a popular language in the scientific world, but also in many other fields, with lot of very active software libraries. Moreover, it is a simple and interpreted language, often recommended for beginners, and practiced by beginners and experienced developers. In addition, there is the PyQT8 software library that lets you use the Qt9 GUI library, which is ideal for this project. It is very popular and active, and available on many platforms (GNU/Linux, Windows, MacOS, Unix, BSD, Android, iOS). Qt also includes a number of tools for translating interfaces into different languages. Here we use the community version of Qt, with no paid features. For graphics and visualization, the choice fell on Matplotlib10, which is very popular in the scientific community and can be used to draw a wide variety of graphics. For saving studies, the choice was made to use SQLite11, unlike the previous version which used a text file formatted in XML. The advantages of a SQLite database, in addition to being supplied with Python, include ease of use, interoperability, fast, formal structuring by table and consistency checking between tables. We have chosen to version the database to allow it to evolve with the software, and to make it possible to add columns to tables or make other minor modifications to the database without breaking the compatibility of studies from previous versions. However, PAMHyR and Pamhyr2 study files will not be compatible anymore, and previous studies have to be recreated for this version.
|
||||
|
||||
This new version Pamhyr2 remains closely linked to the Mage solver with which it is supplied. But the ambition of flexibility and generalization gives us the opportunity to develop an interface that can be adapted to other solvers. There can be two solutions for using a solver with Pamhyr2: either the solver adapts to Pamhyr2, or Pamhyr2 adapts to the solver. So Pamhyr2 must integrate a generic output containing the study data that can be used by other solvers, which will be launched via an external executable, and then a generic result input that must be supplied by the solver. In addition, Pamhyr2 must be able to handle the execution and reading of results for specific solvers, and the addition of a solver to Pamhyr2 must be reasonably easy for the solver developer. We have opted for the free and open source software (FOSS) [6] license GPLv312, which guarantees freedom to access, study, modify, redistribute and share. In addition, it guarantees that Pamhyr2's code will remain free and is not allowed to be added any closed-source modifications. However, it will still be possible to use closed-source solvers, because solver is not a part of Pamhyr2.
|
||||
3. DEVELOPMENTS
|
||||
3.1 New version of PAMHYR
|
||||
Pamhyr2 currently features the following interfaces: Editing of river network, geometry, boundary conditions, lateral contribution, friction, initial conditions, solver numerical parameters, as well as the interface for launching a solver and displaying hydraulic and sediment results.
|
||||
3.1.1 River network
|
||||
The river network corresponds to the topology of the hydraulic network of the river. River network editing (Figure 1) is performed via a window featuring a graphical network area and two tables, one with a list of nodes (zone 3) and the other with a list of reaches (zone 2). The graphic area (zone 1) lets you add, delete and move nodes. It can also be used to link nodes in the network by arrows that represent reaches. The color of the node in the graphic area depends on whether it is a downstream, upstream or internal network node. If there is more than one reach, you can use this interface to select a reach, which will then be selected for editing in the following reach-dependent windows. It is also possible to deactivate reaches, in which case they will not be used for the solver run. Tables can be used to edit additional information on nodes and reaches, such as giving them names.
|
||||
|
||||
|
||||
|
||||
Figure 1: River network window for a dummy river with 8 reaches, 2 upstream node (yellow), 1 downstream node (green) and 5 internal node (blue).
|
||||
|
||||
3.1.2 Geometry
|
||||
Geometry editing (Figure 2) is linked to a reach; the reach used will be the last reach selected in the river network window. This window contains a table with the list of the reach’s cross-sections (zone 1) and three graphics. A top view based on the XY positions of the cross-sections’ points and guidelines (zone 2). A longitudinal view of all cross-sections with the height of the guidelines’ points for each profile (zone 3). Finally, a cross-sectional view is displayed (zone 4) showing the selected cross-section with the previous and the following one in dotted line. Each cross-section is defined by a Kilometer Point (KP) and an optional name. The KP represents the longitudinal coordinate of the cross-section in the numerical solver. It is possible to add cross-sections and then select them to access an editing window, or to import cross-sections from file. Cross-section editing window takes the form of a table showing the raw data of the points that make up the cross-section, and a graph showing the current cross-section.
|
||||
|
||||
|
||||
|
||||
Figure 2: Geometry window for Saar river on the unique reach of the Saar river study.
|
||||
|
||||
3.1.3 Boundary conditions
|
||||
Boundary conditions (BC) are associated with a network node. There are three different types: hydrograph, water level chart or rating curve, in three different categories: Liquid, solid and suspension. Boundary conditions can be added, deleted and edited. Editing a BC (Figure 3) takes the form of a two-column table (the type of column varies according to the type of BC) and a graph representing the data. Data can be copied and pasted from a spreadsheet, or edited by hand.
|
||||
|
||||
|
||||
|
||||
Figure 3: Edition window of the boundary conditions hydrograph for the Saar river upstream node.
|
||||
|
||||
|
||||
3.1.3 Lateral contribution
|
||||
The user interface for lateral contributions is similar to that for boundary conditions, except that it applies to a part of a reach between two KP, and not to a node.
|
||||
3.1.4 Friction
|
||||
Friction editing is linked to a reach, a friction coefficient is defined and then applied to a portion of the reach between two KP (Figure 4). Friction is defined by two Strickler coefficients, one for the river's main channel and one for the flood plain. The list of available Strickler coefficients is defined at the study level.
|
||||
|
||||
|
||||
|
||||
Figure 4: The reach portions for friction coefficient edition window for the Saar study.
|
||||
|
||||
3.1.5 Initial conditions
|
||||
The initial conditions (Figure 5) are also linked to the reach, corresponding to the water level and flow rate at the initial time of the simulation. These two values can be defined separately for each cross-section of the reach. It is also possible to use predefined functions to generate a water line and flow rate.
|
||||
|
||||
|
||||
|
||||
Figure 5: Initial conditions edition window for the Saar study and the Saar reach. Initial conditions initialized with minimal height of 3 meter and a constant flow rate of 100 m3/s.
|
||||
|
||||
3.1.6 Running a solver
|
||||
Solver numerical parameters are defined using a key/value table specific to each type of solver known to Pamhyr2. You can select and run the desired solver from the list of configured solvers. Then a window appears with a text area where you can follow the progress of file export and solver output in Figure 6 (zone 3). You can also pause, stop or restart the solver using buttons (zone 1). Once the simulation has finished, you can open a solver trace file, and if the simulation has finished correctly, you can open the results using the “results” button (zone 2).
|
||||
|
||||
|
||||
|
||||
Figure 6: Solver log window for “Elargissement” tests case with a mage solver named “src”.
|
||||
|
||||
3.1.7 Results processing and visualization
|
||||
The post processing window is made up of tables allowing you to select the reaches, profiles, and timestamps for visualizing the results. The results are presented in different tabs according to the type of data displayed. The Figure 7 shows hydraulic data with water level at a given reach, profile, and time. The graphs shown here are the same as those in the geometry window, with the addition of water level results at a given time.
|
||||
|
||||
|
||||
|
||||
Figure 7: Hydraulics results window for “Elargissement” tests case with a Mage solver at timestamps 2113200.0 sec for reach R1 and cross-section at KP 427.7778.
|
||||
|
||||
3.2 New features
|
||||
Some new features are already implemented in Pamhyr2. First of all, the software is multilingual with English and French currently available. An other very useful enhancement, is the possibility to use the “Ctrl+Z” or “Ctrl+Shift+Z” shortcuts to undo or redo the user actions.
|
||||
|
||||
Another novelty lies in the initial conditions window. Previously the water line could be generated by constant height or altitude. It is now possible to give a minimum height with an increasing function from downstream to upstream, thus avoiding physically unrealistic water holes and getting a little closer to a real and physically coherent water line (Figure 5).
|
||||
|
||||
An important new feature in this version is the presence of sediment layers. They can be defined as shown in Figure 8. Sediment layers can be applied separately for each cross-section (Figure 9). As with friction, they must first be defined and then applied to a profile. They can also be applied separately to each point of a cross-section. Layers are defined by a thickness, a median diameter (D50), a grain size sorting coefficient (Sigma) and a critical shear stress as described in Pierre Balayn PhD thesis [1]. The visualization of sedimentary results is currently done using a plot of the river profile, which shows the evolution of the bed bottom and the thickness of layers as a function of time (Figure 10).
|
||||
|
||||
|
||||
|
||||
Figure 8: Sediment layers definition window for Saar study. This sediment layers has three layers (L1, L2 and F).
|
||||
|
||||
|
||||
|
||||
|
||||
Figure 9: Sediment layers defined in Figure 8 applied to the reach Saar.
|
||||
|
||||
|
||||
|
||||
|
||||
Figure 10: Sedimentary results window for “Elargissement” tests case at timestamps 2113200.0 sec for reach R1 and cross-section at KP 427.7778. This example has only two sediment layers.
|
||||
|
||||
4. Future work
|
||||
Old PAMHyR functionalities and additional functionalities, allowed the definition of structures, traps, suspended solids or dissolved pollutants in water, have yet to be developed. Additional options for visualizing results also need to be added. One possibility would be a graph generation tool that would allow the user to select x-axis and y-axis values, as well as the time range, to create a customized graph. We also planned an advanced feature with code-editing window to enable the user to define his own Matplotlib drawing function, which would be saved in the study. Presentation work is also planned to harmonize the application's icons and visuals (images, graphics, etc.). The aim is to make the application clearer and more user-friendly.
|
||||
|
||||
Supporting new solvers and generic solvers will also be an important part of the remaining work. To use any solver, we need to define a generic output format for Pamhyr2 that is simple enough to be given as solver input without major code modification, or that can be processed by a simple Python script (for example). The same applies to the results file that Pamhyr2 will have to read. The main candidates for these generic formats are :
|
||||
• SQLite, because it is easy to use and the library is compatible with most programming languages,
|
||||
• HDF513, a hierarchical file format widely used in scientific circles and compatible with many programming languages.
|
||||
|
||||
As the project progresses, it becomes more and more complicated to test all the functionalities by hand. A suite of tests and unit tests is needed to prevent regression bugs and ensure that the code remains maintainable. For the moment, no such test has been defined. Unit tests will have to be developed on the model and each sub-module will have to include tests, but higher-level functionalities will also have to be tested, such as the recording and playback of a complete study. It will be difficult to test the graphical part automatically, so tests will continue to be carried out manually.
|
||||
|
||||
Full documentation must also be provided to give users an overview of the features offered by Pamhyr2. This documentation must include descriptions of the configuration options, as well as explanations and examples of how to use each feature. In addition, this documentation must include a section for contributors and developers, with a technical presentation of the application, as well as a presentation of the translation, development and debugging tools available.
|
||||
|
||||
For translations into other languages than English and French, we hope that external contributors will take on this work. But no languages other are currently planned by the development team. The application packages are currently available for GNU/Linux and Windows. Under GNU/Linux, a “.tar.xz” archive is available for download. For Windows, this is an installer for application and a compatible version of Mage. An installer for GNU/Linux systems is a work in progress. There are many choices for creating an installer, but their scope depends on the distribution for which they are intended. “.deb”, for example, is intended for Debian distributions and their derivatives (Ubuntu, Linux Mint, and so on). We do not exclude other, more generic options.
|
||||
5. Conclusion
|
||||
After a great deal of preparatory work, experimentation, technical selection, proof-of-concept and several months of development, Pamhyr2 is currently in version “v0.0.0”14 and contains almost 20,000 lines of Python code. It allows to create a minimal 1D hydro-sedimentary study, run a Mage simulation and visualize some of the results obtained, all with a more user-friendly interface. But the software is still unstable and under development, and many modifications and improvements are still to be made before it can really be used by everyone. Furthermore, even though many of the features of the original version of PAMHyR are already available (see 3.1 page 3), there are still many to be added to make Pamhyr2 a complete, useful and fully functional piece of software.
|
||||
ACKNOWLEDGEMENTS
|
||||
The results presented here partially come from the Pamhyr PITI project funded by the Carnot Eau & Environnement institute and the French National Research Agency. Carnot Eau & Environnement was accredited in March 2020 by the French Ministry of Research and Higher Education on the proposal of the Agence Nationale de la Recherche. The signatory members are INRAE, CNRS, Aix Marseille University, IFTS, INSA Lyon, IRD, Université de Montpellier and Université de Savoie Mont-Blanc.
|
||||
|
||||
The authors thank Sylvain Coulibaly for his preparatory work and his technical choices.
|
||||
REFERENCES and Citations
|
||||
[1] Balayn, P. (2001). Contribution à la modélisation numérique de l'évolution morphologique des cours d'eau aménagés lors de crues. Université Claude Bernard - Lyon 1
|
||||
[2] Camenen, B., Faure, J. Décanis, S. Diéval, L. (2021) A 1D numerical tool for real time modelling of a complex river network, SimHydro 2021
|
||||
[3] Faure, J. (2013). An Integrated Simulation platform - PAMHyR. In John Wiley & Sons, Inc. eBooks (pp. 245–252). https://doi.org/10.1002/9781118557891.ch21
|
||||
[4] Faure, J. (2017). Développement d’Adis-TS. OSR4 | Action V.1. https://hal.science/hal-03749339
|
||||
[5] Faure, J-B., (2019). Mage, Résolution des équations de Barré de St Venant 1D en réseaux complexes, Documentation théorique et mode d’emploi. Techreport (in French)
|
||||
[6] Walt, S. 2007. Free/open source software development. In Proceedings of the the 6th joint meeting of the European software engineering conference and the ACM SIGSOFT symposium on The foundations of software engineering (ESEC-FSE '07). Association for Computing Machinery, New York, NY, USA, 459–468. https://doi.org/10.1145/1287624.1287689
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
\ProvidesClass{Pamhyr}[Pamhyr class]
|
||||
\NeedsTeXFormat{LaTeX2e}
|
||||
\ProcessOptions\relax
|
||||
\LoadClass[11pt,a4paper]{article}
|
||||
|
||||
\usepackage[hmargin=2cm, vmargin=3cm]{geometry}
|
||||
\usepackage[british,UKenglish,USenglish,english,american]{babel}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{times}
|
||||
\usepackage[usenames,dvipsnames]{xcolor}
|
||||
\usepackage[absolute]{textpos}
|
||||
\usepackage{titlesec}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{tcolorbox}
|
||||
|
||||
\usepackage{enumitem}
|
||||
\setlist[enumerate]{itemsep=-0.5em}
|
||||
\setlist[itemize]{itemsep=-0.5em}
|
||||
|
||||
%%
|
||||
%% Bibtex
|
||||
%%
|
||||
|
||||
\bibliographystyle{plain}
|
||||
|
||||
%%
|
||||
%% Colors
|
||||
%%
|
||||
|
||||
\definecolor{primary}{HTML}{212121}
|
||||
\definecolor{lightheadings}{HTML}{5c5c5c}
|
||||
\definecolor{headings}{HTML}{212121}
|
||||
\definecolor{links}{HTML}{e12121}
|
||||
|
||||
%%
|
||||
%% Title
|
||||
%%
|
||||
|
||||
\setlength{\columnsep}{20pt}
|
||||
\setlength{\parskip}{0pt} %% inter paragraph space
|
||||
|
||||
\renewcommand{\maketitle}{
|
||||
{
|
||||
\centering
|
||||
{
|
||||
\fontsize{35}{40}
|
||||
\selectfont
|
||||
\scshape
|
||||
Pamhyr2 \\
|
||||
\medskip
|
||||
}
|
||||
{
|
||||
%\centering
|
||||
\scshape
|
||||
\fontsize{20pt}{24pt}
|
||||
\selectfont
|
||||
A graphical user interface for 1D hydro-sedimentary modelling
|
||||
of rivers \\
|
||||
\medskip
|
||||
}
|
||||
\rule{3cm}{0.4pt}\\
|
||||
\vspace{0.3cm}
|
||||
{
|
||||
%\centering
|
||||
\scshape
|
||||
\fontsize{26pt}{24pt}
|
||||
\selectfont
|
||||
\@title \\
|
||||
Document licence: GPLv3 \\
|
||||
\medskip
|
||||
\includegraphics[width=3cm]{../images/GPLv3_Logo.png} \\
|
||||
\medskip
|
||||
\fontsize{14pt}{16pt}
|
||||
\selectfont
|
||||
© \@author \\
|
||||
\medskip
|
||||
}
|
||||
{
|
||||
%\centering
|
||||
%\scshape
|
||||
\fontsize{12pt}{14pt}
|
||||
\selectfont
|
||||
\@date
|
||||
%\medskip
|
||||
}\\
|
||||
\vspace{0.3cm}
|
||||
\rule{3cm}{0.4pt}\\
|
||||
\vspace{0.3cm}
|
||||
{
|
||||
\begin{minipage}[h]{0.24\textwidth}
|
||||
\includegraphics[width=4cm]{../../src/View/ui/ressources/Logo-INRAE.png}
|
||||
\end{minipage}
|
||||
\begin{minipage}[h]{0.24\textwidth}
|
||||
\includegraphics[width=4cm]{../images/riverly.png}
|
||||
\end{minipage}
|
||||
}\\
|
||||
\vspace{0.3cm}
|
||||
%%\rule{\textwidth}{0.4pt}
|
||||
%%\newpage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%%
|
||||
%% Texttt
|
||||
%%
|
||||
|
||||
\usepackage[htt]{hyphenat} % Make texttt split at end of line
|
||||
|
||||
%%
|
||||
%% Verbatim
|
||||
%%
|
||||
|
||||
\AtBeginEnvironment{verbatim}{\footnotesize}
|
||||
|
||||
%% Add box arrond of verbatim
|
||||
\BeforeBeginEnvironment{verbatim}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
|
||||
\AfterEndEnvironment{verbatim}{\end{tcolorbox}}%
|
||||
|
||||
\makeatletter
|
||||
\AtBeginEnvironment{verbatim}{\dontdofcolorbox}
|
||||
|
||||
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
|
||||
\makeatother
|
||||
|
||||
%%
|
||||
%% Source code blocks
|
||||
%%
|
||||
|
||||
%% Use minted
|
||||
\usepackage{minted}
|
||||
\usemintedstyle{tango}
|
||||
\setminted[c]{fontsize=\footnotesize,encoding=utf8,linenos}
|
||||
\setminted[c++]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
|
||||
\setminted[python]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
|
||||
\setminted[shell]{breaklines,fontsize=\footnotesize,encoding=utf8}
|
||||
\setminted[scheme]{breaklines,fontsize=\footnotesize,encoding=utf8}
|
||||
\setminted[commun-lisp]{breaklines,fontsize=\footnotesize,encoding=utf8}
|
||||
\setminted[text]{breaklines,fontsize=\footnotesize,encoding=utf8}
|
||||
\setminted[llvm]{breaklines,fontsize=\footnotesize,encoding=utf8}
|
||||
|
||||
%% Add box arrond of minted
|
||||
\BeforeBeginEnvironment{minted}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
|
||||
\AfterEndEnvironment{minted}{\end{tcolorbox}}%
|
||||
|
||||
\makeatletter
|
||||
\AtBeginEnvironment{minted}{\dontdofcolorbox}
|
||||
|
||||
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
|
||||
\makeatother
|
||||
|
||||
|
||||
%%
|
||||
%% Water mark
|
||||
%%
|
||||
|
||||
%% FIXME: Delete me for the first realease
|
||||
\usepackage{draftwatermark}
|
||||
\SetWatermarkLightness{0.8}
|
||||
\SetWatermarkAngle{25}
|
||||
\SetWatermarkScale{3}
|
||||
\SetWatermarkFontSize{1cm}
|
||||
\SetWatermarkText{Work in progress}
|
||||
@@ -0,0 +1,19 @@
|
||||
# latex.org -- Pamhyr documentation latex config
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#+LaTeX_CLASS: PamhyrDoc
|
||||
@@ -0,0 +1,51 @@
|
||||
# macro.org -- Pamhyr documentation macro
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Constant name
|
||||
#+MACRO: oldPamhyr PAMHyR
|
||||
#+MACRO: Pamhyr Pamhyr2
|
||||
#+MACRO: Mage Mage
|
||||
#+MACRO: Rubarbe RubarBE
|
||||
#+MACRO: Inrae INRAE
|
||||
#+MACRO: latex \LaTeX
|
||||
|
||||
# Information
|
||||
#+MACRO: version (eval (pamhyr-version))
|
||||
#+MACRO: current-file (eval (pamhyr-current-file))
|
||||
|
||||
# Special format
|
||||
#+MACRO: file =$1=
|
||||
|
||||
# Biblio
|
||||
#+MACRO: cite [cite:$1]
|
||||
#+MACRO: biblio \bibliography{documentation}
|
||||
|
||||
# Wrapper
|
||||
#+NAME: attr_wrap
|
||||
#+HEADER: :var width="\\textwidth"
|
||||
#+HEADER: :var caption=""
|
||||
#+HEADER: :var smallcaption=""
|
||||
#+HEADER: :var name=""
|
||||
#+HEADER: :var data=""
|
||||
#+HEADER: :var float="nil"
|
||||
#+BEGIN_SRC sh :results output :exports none
|
||||
echo "#+CAPTION[$smallcaption]: $caption"
|
||||
echo "#+NAME: $name"
|
||||
echo "#+ATTR_LATEX: :width $width :float $float"
|
||||
echo "$data"
|
||||
#+END_SRC
|
||||
@@ -0,0 +1,66 @@
|
||||
(require 'org)
|
||||
(require 'subr-x)
|
||||
|
||||
;; LaTeX config
|
||||
|
||||
(add-to-list
|
||||
'org-latex-classes
|
||||
'("PamhyrDoc"
|
||||
"\\documentclass{../tools/PamhyrDoc}"
|
||||
("\\section{%s}" . "\\section*{%s}")
|
||||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||||
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
|
||||
("\\paragraph{%s}" . "\\paragraph*{%s}")
|
||||
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
|
||||
|
||||
(setq org-confirm-babel-evaluate nil)
|
||||
(setq org-latex-caption-above nil)
|
||||
|
||||
(org-babel-do-load-languages
|
||||
'org-babel-load-languages
|
||||
(add-to-list 'org-babel-load-languages '(shell . t)))
|
||||
|
||||
(org-babel-do-load-languages
|
||||
'org-babel-load-languages
|
||||
(add-to-list 'org-babel-load-languages '(dot . t)))
|
||||
|
||||
(org-babel-do-load-languages
|
||||
'org-babel-load-languages
|
||||
(add-to-list 'org-babel-load-languages '(python . t)))
|
||||
|
||||
(add-to-list 'org-latex-packages-alist '("" "minted"))
|
||||
(setq org-latex-listings 'minted)
|
||||
(setq org-src-fontify-natively t)
|
||||
|
||||
(setq org-latex-pdf-process
|
||||
'("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
|
||||
"bibtex documentation"
|
||||
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
|
||||
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
|
||||
|
||||
;; Citations
|
||||
|
||||
(org-add-link-type
|
||||
"cite" 'ebib
|
||||
(lambda (path desc format)
|
||||
(cond
|
||||
((eq format 'html)
|
||||
(format "(<cite>%s</cite>)" path))
|
||||
((eq format 'latex)
|
||||
(if (or (not desc) (equal 0 (search "cite:" desc)))
|
||||
(format "\\cite{%s}" path)
|
||||
(format "\\cite[%s][%s]{%s}"
|
||||
(cadr (split-string desc ";"))
|
||||
(car (split-string desc ";")) path))))))
|
||||
|
||||
;; Functools
|
||||
|
||||
(defun pamhyr-version ()
|
||||
"Return the contents of the pamhyr version file."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "../../VERSION")
|
||||
(buffer-string)))
|
||||
|
||||
(defun pamhyr-current-file ()
|
||||
(let ((lst (split-string (buffer-file-name) "/")))
|
||||
(string-join (nthcdr (- (length lst) 3) lst) "/")))
|
||||
@@ -0,0 +1,10 @@
|
||||
#! /bin/sh
|
||||
|
||||
# Build the PDF
|
||||
|
||||
emacs -Q -q -nw --batch \
|
||||
--eval "(require 'org)" \
|
||||
--eval "(require 'ox-latex)" \
|
||||
--eval "(load-file \"../tools/setup.el\")" \
|
||||
--file ./documentation.org \
|
||||
-f org-latex-export-to-pdf
|
||||
@@ -0,0 +1 @@
|
||||
../tools/ref.bib
|
||||
@@ -0,0 +1,20 @@
|
||||
#+STARTUP: indent
|
||||
|
||||
#+INCLUDE: ../tools/macro.org
|
||||
#+INCLUDE: ../tools/latex.org
|
||||
|
||||
#+TITLE: User documentation
|
||||
#+SUBTITLE: Version: {{{version}}}
|
||||
#+AUTHOR: {{{INRAE}}}
|
||||
|
||||
#+OPTIONS: toc:t
|
||||
#+LANGUAGE: UKenglish
|
||||
|
||||
* TODO Over view
|
||||
* TODO Get started
|
||||
** Installation
|
||||
*** Linux
|
||||
*** Windows
|
||||
* TODO Usage
|
||||
|
||||
{{{biblio}}}
|
||||
@@ -1,5 +1,21 @@
|
||||
#! /bin/sh
|
||||
|
||||
# linux.sh -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
echo " *** RM OLD ENV"
|
||||
|
||||
rm dist/ -rf
|
||||
@@ -28,13 +44,26 @@ cd $OLD_PWD
|
||||
echo " *** COPY DATA"
|
||||
|
||||
mkdir -p dist/pamhyr/View/ui
|
||||
mkdir -p dist/pamhyr/View/ui/Widgets
|
||||
cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/
|
||||
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/
|
||||
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/Widgets/
|
||||
cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/
|
||||
|
||||
mkdir -p dist/pamhyr/lang
|
||||
cp -r ../src/lang/*.qm dist/pamhyr/lang/
|
||||
|
||||
cp ../VERSION dist/pamhyr/
|
||||
cp ../AUTHORS dist/pamhyr/
|
||||
cp ../LICENSE dist/pamhyr/
|
||||
|
||||
mkdir -p dist/pamhyr/mage/
|
||||
cp ../mage/mage dist/pamhyr/mage/
|
||||
cp ../mage/mage_extraire dist/pamhyr/mage/
|
||||
cp ../mage/mailleurPF dist/pamhyr/mage/
|
||||
|
||||
mkdir -p dist/pamhyr/tests_cases/
|
||||
mkdir -p dist/pamhyr/tests_cases/Saar
|
||||
cp ../tests_cases/Saar/Saar.pamhyr dist/pamhyr/tests_cases/Saar/
|
||||
|
||||
echo " *** MAKE SRC PACKAGE"
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
rem windows.bat -- Pamhyr Windows batch for windows version building
|
||||
rem Copyright (C) 2023 INRAE
|
||||
rem
|
||||
rem This program is free software: you can redistribute it and/or modify
|
||||
rem it under the terms of the GNU General Public License as published by
|
||||
rem the Free Software Foundation, either version 3 of the License, or
|
||||
rem (at your option) any later version.
|
||||
rem
|
||||
rem This program is distributed in the hope that it will be useful,
|
||||
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
rem GNU General Public License for more details.
|
||||
rem
|
||||
rem You should have received a copy of the GNU General Public License
|
||||
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ECHO ON
|
||||
|
||||
mkdir pamhyr\pamhyr
|
||||
xcopy /s /e /y dist\pamhyr pamhyr\pamhyr
|
||||
|
||||
rem Copy data
|
||||
mkdir pamhyr\pamhyr\lang
|
||||
mkdir pamhyr\pamhyr\View\ui\ressources
|
||||
mkdir pamhyr\pamhyr\View\ui\Widgets
|
||||
|
||||
rem UI
|
||||
copy /y ..\src\View\ui\ressources\ pamhyr\pamhyr\View\ui\ressources
|
||||
copy /y ..\src\View\ui\Widgets\*.ui pamhyr\pamhyr\View\ui\Widgets
|
||||
copy /y ..\src\View\ui\*.ui pamhyr\pamhyr\View\ui\
|
||||
|
||||
rem Lang
|
||||
copy /y ..\src\lang\*.qm pamhyr\pamhyr\lang\
|
||||
|
||||
rem Information
|
||||
copy /y ..\VERSION pamhyr\pamhyr
|
||||
copy /y ..\AUTHORS pamhyr\pamhyr
|
||||
copy /y ..\LICENSE pamhyr\pamhyr
|
||||
|
||||
rem MAGE
|
||||
mkdir pamhyr\mage
|
||||
copy /y ..\mage-windows\mage.exe pamhyr\mage\
|
||||
copy /y ..\mage-windows\mage_extraire.exe pamhyr\mage\
|
||||
copy /y ..\mage-windows\mailleurPF.exe pamhyr\mage\
|
||||
|
||||
rem Copy tests_cases
|
||||
mkdir pamhyr\tests_cases
|
||||
mkdir pamhyr\tests_cases\Saar
|
||||
copy /y ..\tests_cases\Saar\Saar.pamhyr pamhyr\tests_cases\Saar\
|
||||
|
||||
rem Documentations
|
||||
mkdir pamhyr\doc
|
||||
copy /y ..\doc\users\documentation.pdf pamhyr\doc\Pamhyr2-users.pdf
|
||||
copy /y ..\doc\dev\documentation.pdf pamhyr\doc\Pamhyr2-dev.pdf
|
||||
@@ -0,0 +1,20 @@
|
||||
rem windows.bat -- Pamhyr Windows batch for windows version building
|
||||
rem Copyright (C) 2023 INRAE
|
||||
rem
|
||||
rem This program is free software: you can redistribute it and/or modify
|
||||
rem it under the terms of the GNU General Public License as published by
|
||||
rem the Free Software Foundation, either version 3 of the License, or
|
||||
rem (at your option) any later version.
|
||||
rem
|
||||
rem This program is distributed in the hope that it will be useful,
|
||||
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
rem GNU General Public License for more details.
|
||||
rem
|
||||
rem You should have received a copy of the GNU General Public License
|
||||
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ECHO ON
|
||||
|
||||
rem Make installer
|
||||
"C:\Program Files (x86)\NSIS\makensis.exe" pamhyr.nsi
|
||||
@@ -1,16 +1,21 @@
|
||||
!include "x64.nsh"
|
||||
|
||||
Name "PAMHYR"
|
||||
!define LIC_NAME "LICENSE"
|
||||
!define APP_NAME "PAMHYR"
|
||||
|
||||
OutFile "pamhyr-win-amd64.exe"
|
||||
Name "Pamhyr2"
|
||||
OutFile "pamhyr-windows.exe"
|
||||
LicenseData "pamhyr\pamhyr\LICENSE"
|
||||
LicenseText "I Agree"
|
||||
|
||||
RequestExecutionLevel admin
|
||||
|
||||
Unicode True
|
||||
InstallDir $PROGRAMFILES\PAMHYR
|
||||
InstallDir $PROGRAMFILES\Pamhyr2
|
||||
|
||||
InstallDirRegKey HKLM "Software\PAMHYR" "Install_Dir"
|
||||
InstallDirRegKey HKLM "Software\Pamhyr2" "Install_Dir"
|
||||
|
||||
Page license
|
||||
Page components
|
||||
Page directory
|
||||
Page instfiles
|
||||
@@ -18,35 +23,39 @@ Page instfiles
|
||||
UninstPage uninstConfirm
|
||||
UninstPage instfiles
|
||||
|
||||
Section "PAMHYR"
|
||||
Section "Pamhyr2"
|
||||
SectionIn RO
|
||||
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
File /r "dist\pamhyr\*"
|
||||
File /r "pamhyr\*"
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "DisplayName" "NSIS PAMHYR"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoRepair" 1
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "DisplayName" "NSIS Pamhyr2"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoRepair" 1
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "Start Menu Shortcuts"
|
||||
CreateDirectory "$SMPROGRAMS\Pamhyr2"
|
||||
CreateShortcut "$SMPROGRAMS\Pamhyr2\Uninstall.lnk" "$INSTDIR\uninstall.exe"
|
||||
CreateShortcut "$SMPROGRAMS\Pamhyr2\Pamhyr2.lnk" "$INSTDIR\pamhyr\pamhyr.exe"
|
||||
SectionEnd
|
||||
|
||||
CreateDirectory "$SMPROGRAMS\PAMHYR"
|
||||
CreateShortcut "$SMPROGRAMS\PAMHYR\Uninstall.lnk" "$INSTDIR\uninstall.exe"
|
||||
CreateShortcut "$SMPROGRAMS\PAMHYR\PAMHYR.lnk" "$INSTDIR\pamhyr.exe"
|
||||
|
||||
Section "Desktop Shortcuts"
|
||||
SetShellVarContext all
|
||||
CreateShortcut "$desktop\Pamhyr2.lnk" "$instdir\pamhyr\pamhyr.exe"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR"
|
||||
DeleteRegKey HKLM SOFTWARE\NSIS_PAMHYR
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2"
|
||||
DeleteRegKey HKLM SOFTWARE\NSIS_Pamhyr2
|
||||
|
||||
Delete "$SMPROGRAMS\PAMHYR\*.lnk"
|
||||
Delete "$SMPROGRAMS\Pamhyr2\*.lnk"
|
||||
Delete "$desktop\Pamhyr2.lnk"
|
||||
|
||||
RMDir "$SMPROGRAMS\PAMHYR"
|
||||
RMDir "$SMPROGRAMS\Pamhyr2"
|
||||
RMDir /R "$INSTDIR"
|
||||
SectionEnd
|
||||
@@ -0,0 +1,26 @@
|
||||
#! /bin/sh
|
||||
|
||||
# version.sh -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# ./version.sh BRANCH TAG COMMIT
|
||||
|
||||
if [ -z $2 ];
|
||||
then
|
||||
echo "$1-$3" > ../VERSION
|
||||
else
|
||||
echo "$2" > ../VERSION
|
||||
fi
|
||||
@@ -0,0 +1,54 @@
|
||||
rem windows.bat -- Pamhyr Windows batch for windows version building
|
||||
rem Copyright (C) 2023 INRAE
|
||||
rem
|
||||
rem This program is free software: you can redistribute it and/or modify
|
||||
rem it under the terms of the GNU General Public License as published by
|
||||
rem the Free Software Foundation, either version 3 of the License, or
|
||||
rem (at your option) any later version.
|
||||
rem
|
||||
rem This program is distributed in the hope that it will be useful,
|
||||
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
rem GNU General Public License for more details.
|
||||
rem
|
||||
rem You should have received a copy of the GNU General Public License
|
||||
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ECHO ON
|
||||
|
||||
rem Python environment
|
||||
python -m pip install -r ..\requirements.txt
|
||||
|
||||
rem Build windows version
|
||||
pyinstaller ..\src\pamhyr.py -y
|
||||
|
||||
rem Copy data
|
||||
mkdir dist\pamhyr\View\ui\ressources
|
||||
mkdir dist\pamhyr\View\ui\Widgets
|
||||
|
||||
rem UI
|
||||
copy /y ..\src\View\ui\ressources\ dist\pamhyr\View\ui\ressources
|
||||
copy /y ..\src\View\ui\Widgets\*.ui dist\pamhyr\View\ui\Widgets
|
||||
copy /y ..\src\View\ui\*.ui dist\pamhyr\View\ui\
|
||||
|
||||
rem Lang
|
||||
copy /y ..\src\lang\*.qm dist\pamhyr\lang\
|
||||
|
||||
rem Information
|
||||
copy /y ..\VERSION dist\pamhyr\
|
||||
copy /y ..\AUTHORS dist\pamhyr\
|
||||
copy /y ..\LICENSE dist\pamhyr\
|
||||
|
||||
rem MAGE
|
||||
mkdir dist\pamhyr\mage
|
||||
copy /y ..\mage\mage.exe dist\pamhyr\mage\
|
||||
copy /y ..\mage\mage_extraire.exe dist\pamhyr\mage\
|
||||
copy /y ..\mage\mailleurPF.exe dist\pamhyr\mage\
|
||||
|
||||
rem Copy tests_cases
|
||||
mkdir dist\pamhyr\tests_cases
|
||||
mkdir dist\pamhyr\tests_cases\Saar
|
||||
copy /y ..\tests_cases\Saar\Saar.pamhyr dist\pamhyr\tests_cases\Saar\
|
||||
|
||||
rem Make installer
|
||||
"C:\Program Files (x86)\NSIS\makensis.exe" pamhyr.nsi
|
||||
@@ -1,9 +1,35 @@
|
||||
#! /bin/sh
|
||||
|
||||
# wine.sh -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
echo " *** SETUP ENV"
|
||||
|
||||
export WINARCH=win64
|
||||
|
||||
if [ "$1" = "ci" ];
|
||||
then
|
||||
# HACK: Set wine prefix for gitlab-ci runner (DONT WORKS)
|
||||
cp -rfv /home/parouby/git/pamhyr/packages/wine-pamhyr-build $PWD/wine-pamhyr-build
|
||||
chown $USER -R $PWD/wine-pamhyr-build
|
||||
chmod 755 $PWD/wine-pamhyr-build
|
||||
export WINEPREFIX=$PWD/wine-pamhyr-build
|
||||
fi
|
||||
|
||||
|
||||
if [ -z $WINEPREFIX ];
|
||||
then
|
||||
# FIXME Not tested yet...
|
||||
@@ -30,7 +56,7 @@ then
|
||||
win10
|
||||
|
||||
echo " *** PYTHON INSTALL"
|
||||
if [ ! -f $WINEPREFIX/drive_c/Program Files/Python38/python.exe ];
|
||||
if [ ! -f "$WINEPREFIX/drive_c/Program Files/Python38/python.exe" ];
|
||||
then
|
||||
#DISPLAY=:42.0 wine cmd /c python-3.8.10-amd64.exe /quiet PrependPath=1
|
||||
wine cmd /c python-3.8.10-amd64.exe PrependPath=1
|
||||
@@ -55,10 +81,42 @@ wine pyinstaller ../src/pamhyr.py -y
|
||||
|
||||
echo " *** COPY DATA"
|
||||
|
||||
mkdir -p dist/pamhyr/View/ui
|
||||
cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/
|
||||
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/
|
||||
cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/
|
||||
mkdir -p pamhyr
|
||||
mkdir -p pamhyr/pamhyr
|
||||
|
||||
# Copy Pamhyr info
|
||||
cp -v ../VERSION pamhyr/pamhyr/
|
||||
cp -v ../AUTHORS pamhyr/pamhyr/
|
||||
cp -v ../LICENSE pamhyr/pamhyr/
|
||||
|
||||
# Copy mage
|
||||
mkdir -p pamhyr/mage
|
||||
cp -v ../mage/* pamhyr/mage/
|
||||
|
||||
# Copy Pamhyr
|
||||
cp -r dist/pamhyr pamhyr/
|
||||
|
||||
# Copy Pamhyr resources
|
||||
mkdir -p pamhyr/pamhyr/View
|
||||
mkdir -p pamhyr/pamhyr/View/ui
|
||||
mkdir -p pamhyr/pamhyr/View/ui/Widgets
|
||||
cp -r ../src/View/ui/*.ui pamhyr/pamhyr/View/ui/
|
||||
cp -r ../src/View/ui/ressources/ pamhyr/pamhyr/View/ui
|
||||
cp -r ../src/View/ui/Widgets/*.ui pamhyr/pamhyr/View/ui/Widgets
|
||||
|
||||
# Copy lang
|
||||
mkdir -p pamhyr/pamhyr/lang
|
||||
cp -r ../src/lang/*.qm pamhyr/pamhyr/lang
|
||||
|
||||
# Copy tests cases
|
||||
mkdir -p pamhyr/tests_cases/
|
||||
mkdir -p pamhyr/tests_cases/Saar
|
||||
cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/
|
||||
|
||||
# Copy documentations
|
||||
mkdir -p pamhyr/doc/
|
||||
cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf
|
||||
cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf
|
||||
|
||||
# Update TS and build QM files
|
||||
OLD_PWD=$PWD
|
||||
@@ -73,19 +131,19 @@ echo " *** MAKE PACKAGES"
|
||||
|
||||
echo " TAR pamhyr-win-amd64.tar.xz"
|
||||
|
||||
OLD_PWD=$PWD
|
||||
cd dist/
|
||||
tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100
|
||||
cd $OLD_PWD
|
||||
mv dist/pamhyr-win-amd64.tar.xz ./
|
||||
# OLD_PWD=$PWD
|
||||
# cd dist/
|
||||
# tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100
|
||||
# cd $OLD_PWD
|
||||
# mv dist/pamhyr-win-amd64.tar.xz ./
|
||||
|
||||
echo " ZIP pamhyr-win-amd64.zip"
|
||||
|
||||
OLD_PWD=$PWD
|
||||
cd dist/
|
||||
zip -r pamhyr-win-amd64.zip pamhyr/
|
||||
cd $OLD_PWD
|
||||
mv dist/pamhyr-win-amd64.zip ./
|
||||
# OLD_PWD=$PWD
|
||||
# cd dist/
|
||||
# zip -r pamhyr-win-amd64.zip pamhyr/
|
||||
# cd $OLD_PWD
|
||||
# mv dist/pamhyr-win-amd64.zip ./
|
||||
|
||||
echo " *** MAKE INSTALLER"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../AUTHORS
|
||||
@@ -1,3 +1,19 @@
|
||||
# Checker.py -- Pamhyr abstract checker class
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from enum import Enum
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Mage.py -- Pamhyr MAGE checkers
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Study.py -- Pamhyr study checkers
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class AbstractModelComponent(object):
|
||||
def __init__(self):
|
||||
super(AbstractModelComponent, self).__init__()
|
||||
|
||||
def new(self, filename):
|
||||
|
||||
|
||||
def export(self, filename):
|
||||
@@ -1,16 +1,45 @@
|
||||
# BoundaryCondition.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import trace, timer, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
class BoundaryCondition(object):
|
||||
def __init__(self, name:str = "",
|
||||
logger = logging.getLogger()
|
||||
|
||||
class BoundaryCondition(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int = -1, name:str = "",
|
||||
status = None):
|
||||
super(BoundaryCondition, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = BoundaryCondition._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
self._node = None
|
||||
@@ -18,6 +47,137 @@ class BoundaryCondition(object):
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
BoundaryCondition._id_cnt = max(BoundaryCondition._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
tab TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition_data(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
data0 TEXT NOT NULL,
|
||||
data1 TEXT NOT NULL,
|
||||
bc INTEGER,
|
||||
FOREIGN KEY(bc) REFERENCES boundary_condition(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _get_ctor_from_type(cls, t):
|
||||
from Model.BoundaryCondition.BoundaryConditionTypes import (
|
||||
NotDefined, PonctualContribution,
|
||||
TimeOverZ, TimeOverDischarge, ZOverDischarge,
|
||||
Solid,
|
||||
)
|
||||
|
||||
res = NotDefined
|
||||
if t == "PC":
|
||||
res = PonctualContribution
|
||||
elif t == "TZ":
|
||||
res = TimeOverZ
|
||||
elif t == "TD":
|
||||
res = TimeOverDischarge
|
||||
elif t == "ZD":
|
||||
res = ZOverDischarge
|
||||
elif t == "SL":
|
||||
res = Solid
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = []
|
||||
tab = data["tab"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, type, node " +
|
||||
"FROM boundary_condition " +
|
||||
f"WHERE tab = '{tab}'"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
t = row[2]
|
||||
ctor = cls._get_ctor_from_type(t)
|
||||
bc = ctor(
|
||||
id = row[0],
|
||||
name = row[1],
|
||||
status = data['status']
|
||||
)
|
||||
|
||||
bc.node = None
|
||||
if row[3] != -1:
|
||||
bc.node = next(filter(lambda n: n.id == row[3], data["nodes"]))
|
||||
|
||||
values = execute(
|
||||
"SELECT ind, data0, data1 FROM boundary_condition_data " +
|
||||
f"WHERE bc = '{bc.id}'"
|
||||
)
|
||||
# Create dummy data list
|
||||
for _ in values:
|
||||
bc.add(0)
|
||||
# Write data
|
||||
for v in values:
|
||||
ind = v[0]
|
||||
data0 = bc._types[0](v[1])
|
||||
data1 = bc._types[1](v[2])
|
||||
# Replace data at pos ind
|
||||
bc._data[ind] = (data0, data1)
|
||||
|
||||
new.append(bc)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
tab = data["tab"]
|
||||
|
||||
execute(f"DELETE FROM boundary_condition WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM boundary_condition_data WHERE bc = {self.id}")
|
||||
|
||||
node = -1
|
||||
if self._node is not None:
|
||||
node = self._node.id
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"boundary_condition(id, name, type, tab, node) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._sql_format(self._name)}', " +
|
||||
f"'{self._sql_format(self._type)}', '{tab}', {node}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._sql_format(str(d[0]))
|
||||
data1 = self._sql_format(str(d[1]))
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_data(ind, data0, data1, bc) "+
|
||||
f"VALUES ({ind}, '{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@@ -164,14 +324,17 @@ class BoundaryCondition(object):
|
||||
new = cls(name = self.name, status = self._status)
|
||||
new.node = self.node
|
||||
|
||||
for i, _ in self.data:
|
||||
for i, _ in enumerate(self.data):
|
||||
new.add(i)
|
||||
|
||||
for i in [0,1]:
|
||||
for j in [0,1]:
|
||||
if self._header[i] == new.header[j]:
|
||||
for ind, v in self.data:
|
||||
new._set_i_c_v(ind, j, v[i])
|
||||
try:
|
||||
new._set_i_c_v(ind, j, v[i])
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
|
||||
return new
|
||||
|
||||
|
||||
@@ -1,40 +1,68 @@
|
||||
# BoundaryConditionList.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelListWithTab
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
|
||||
from Model.BoundaryCondition.BoundaryConditionTypes import (
|
||||
NotDefined,
|
||||
PonctualContribution,
|
||||
TimeOverZ, TimeOverDischarge, ZOverDischarge
|
||||
TimeOverZ, TimeOverDischarge, ZOverDischarge,
|
||||
Solid,
|
||||
)
|
||||
|
||||
class BoundaryConditionList(object):
|
||||
def __init__(self, status = None):
|
||||
super(BoundaryConditionList, self).__init__()
|
||||
class BoundaryConditionList(PamhyrModelListWithTab):
|
||||
_tabs_list = ["liquid", "solid", "suspenssion"]
|
||||
_sub_classes = [
|
||||
BoundaryCondition,
|
||||
]
|
||||
|
||||
self._status = status
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data['status'])
|
||||
|
||||
self._tabs = {
|
||||
"liquid" : [],
|
||||
"solid" : [],
|
||||
"suspenssion" : []
|
||||
}
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
def len(self, lst):
|
||||
return len(self._tabs[lst])
|
||||
for tab in new._tabs:
|
||||
data["tab"] = tab
|
||||
new._tabs[tab] = BoundaryCondition._sql_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
def get_tab(self, lst):
|
||||
return self._tabs[lst].copy()
|
||||
return new
|
||||
|
||||
def get(self, lst, row):
|
||||
return self._tabs[lst][row]
|
||||
def _sql_save(self, execute, data = None):
|
||||
execute("DELETE FROM boundary_condition")
|
||||
|
||||
def set(self, lst, row, new):
|
||||
self._tabs[lst][row] = new
|
||||
self._status.modified()
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for tab in self._tabs:
|
||||
data["tab"] = tab
|
||||
for bc in self._tabs[tab]:
|
||||
bc._sql_save(execute, data = data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, lst, index):
|
||||
n = NotDefined(status=self._status)
|
||||
@@ -42,47 +70,6 @@ class BoundaryConditionList(object):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def insert(self, lst, index, new):
|
||||
self._tabs[lst].insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, lst, bcs):
|
||||
for bc in bcs:
|
||||
self._tabs[lst].remove(bc)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, lst, indexes):
|
||||
bcs = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._tabs[lst])
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(lst, bcs)
|
||||
|
||||
def sort(self, lst, reverse=False, key=None):
|
||||
self._tabs[lst].sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, lst, index):
|
||||
if index < len(self._tabs[lst]):
|
||||
next = index - 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, lst, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def __copy__(self):
|
||||
new = BoundaryConditionList()
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# BoundaryConditionTypes.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
@@ -6,8 +22,8 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
|
||||
|
||||
|
||||
class NotDefined(BoundaryCondition):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(NotDefined, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(NotDefined, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "ND"
|
||||
self._header = ["x", "y"]
|
||||
@@ -17,8 +33,8 @@ class NotDefined(BoundaryCondition):
|
||||
return 0.0
|
||||
|
||||
class PonctualContribution(BoundaryCondition):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(PonctualContribution, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(PonctualContribution, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "PC"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -29,8 +45,8 @@ class PonctualContribution(BoundaryCondition):
|
||||
return ["liquid"]
|
||||
|
||||
class TimeOverZ(BoundaryCondition):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(TimeOverZ, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(TimeOverZ, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "TZ"
|
||||
self._header = ["time", "z"]
|
||||
@@ -41,8 +57,8 @@ class TimeOverZ(BoundaryCondition):
|
||||
return ["liquid"]
|
||||
|
||||
class TimeOverDischarge(BoundaryCondition):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(TimeOverDischarge, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(TimeOverDischarge, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "TD"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -53,8 +69,8 @@ class TimeOverDischarge(BoundaryCondition):
|
||||
return ["liquid"]
|
||||
|
||||
class ZOverDischarge(BoundaryCondition):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(ZOverDischarge, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(ZOverDischarge, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "ZD"
|
||||
self._header = ["z", "discharge"]
|
||||
@@ -67,3 +83,19 @@ class ZOverDischarge(BoundaryCondition):
|
||||
@property
|
||||
def _default_0(self):
|
||||
return 0.0
|
||||
|
||||
|
||||
class Solid(BoundaryCondition):
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(Solid, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self.d50 = 0.002
|
||||
self.sigma = 1
|
||||
|
||||
self._type = "SL"
|
||||
self._header = ["time", "solid"]
|
||||
self._types = [TimeOverDischarge.time_convert, float]
|
||||
|
||||
@classmethod
|
||||
def compatibility(cls):
|
||||
return ["solid"]
|
||||
@@ -1,3 +1,19 @@
|
||||
# Except.py -- Pamhyr model exceptions
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
@@ -70,7 +86,7 @@ class NotImplementedMethodeError(ExeceptionWithMessageBox):
|
||||
f" '{self.func.__name__}' " +
|
||||
_translate("Exception", "not implemented") +
|
||||
_translate("Exception", "for class") +
|
||||
f" '{self.obj.__class__}'"
|
||||
f" '{self.obj.__class__ if self.obj.__class__ != type else self.obj}'"
|
||||
)
|
||||
|
||||
def header(self):
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# Friction.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
class Friction(SQLSubModel):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(Friction, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._name = name
|
||||
self._edge = None
|
||||
self._begin_kp = 0.0
|
||||
self._end_kp = 0.0
|
||||
self._begin_strickler = None
|
||||
self._end_strickler = None
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE friction(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTERGER NOT NULL,
|
||||
begin_kp REAL NOT NULL,
|
||||
end_kp REAL NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
begin_strickler INTERGER,
|
||||
end_strickler INTERGER,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id),
|
||||
FOREIGN KEY(begin_strickler) REFERENCES stricklers(id),
|
||||
FOREIGN KEY(end_strickler) REFERENCES stricklers(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = []
|
||||
reach = data["parent"] # Reach object
|
||||
status = data["status"]
|
||||
stricklers = data["stricklers"].stricklers
|
||||
|
||||
table = execute(
|
||||
"SELECT ind, begin_kp, end_kp, begin_strickler, end_strickler " +
|
||||
f"FROM friction WHERE reach = {reach.id}"
|
||||
)
|
||||
|
||||
for _ in table:
|
||||
new.append(None)
|
||||
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
# Get stricklers
|
||||
bs = next(filter(lambda s: s.id == row[3], stricklers))
|
||||
es = next(filter(lambda s: s.id == row[4], stricklers))
|
||||
|
||||
# Create friction
|
||||
sec = cls(status = status)
|
||||
sec.edge = reach
|
||||
sec.begin_kp = row[1]
|
||||
sec.end_kp = row[2]
|
||||
sec.begin_strickler = bs
|
||||
sec.end_strickler = es
|
||||
|
||||
new[ind] = sec
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ind = data["ind"]
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"friction(ind, begin_kp, end_kp, reach, begin_strickler, end_strickler) " +
|
||||
"VALUES (" +
|
||||
f"{ind}, {self._begin_kp}, {self._end_kp}, " +
|
||||
f"{self._edge.id}, " +
|
||||
f"{self._begin_strickler.id}, {self._end_strickler.id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def edge(self):
|
||||
return self._edge
|
||||
|
||||
@edge.setter
|
||||
def edge(self, edge):
|
||||
self._edge = edge
|
||||
if (edge is not None and
|
||||
self._begin_kp == 0.0 and
|
||||
self._end_kp == 0.0):
|
||||
self._begin_kp = self._edge.reach.get_kp_min()
|
||||
self._end_kp = self._edge.reach.get_kp_max()
|
||||
self._status.modified()
|
||||
|
||||
def has_edge(self):
|
||||
return self._edge is not None
|
||||
|
||||
@property
|
||||
def begin_kp(self):
|
||||
return self._begin_kp
|
||||
|
||||
@begin_kp.setter
|
||||
def begin_kp(self, begin_kp):
|
||||
if self._edge is None:
|
||||
self._begin_kp = begin_kp
|
||||
else:
|
||||
_min = self._edge.reach.get_kp_min()
|
||||
_max = self._edge.reach.get_kp_max()
|
||||
|
||||
if _min <= begin_kp <= _max:
|
||||
self._begin_kp = begin_kp
|
||||
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def end_kp(self):
|
||||
return self._end_kp
|
||||
|
||||
@end_kp.setter
|
||||
def end_kp(self, end_kp):
|
||||
if self._edge is None:
|
||||
self._end_kp = end_kp
|
||||
else:
|
||||
_min = self._edge.reach.get_kp_min()
|
||||
_max = self._edge.reach.get_kp_max()
|
||||
|
||||
if _min <= end_kp <= _max:
|
||||
self._end_kp = end_kp
|
||||
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def begin_strickler(self):
|
||||
return self._begin_strickler
|
||||
|
||||
@begin_strickler.setter
|
||||
def begin_strickler(self, strickler):
|
||||
self._begin_strickler = strickler
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def end_strickler(self):
|
||||
return self._end_strickler
|
||||
|
||||
@end_strickler.setter
|
||||
def end_strickler(self, strickler):
|
||||
self._end_strickler = strickler
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,79 @@
|
||||
# FrictionList.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
|
||||
from Model.Friction.Friction import Friction
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class FrictionList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
Friction
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _sql_update_0_0_1(cls, execute, version):
|
||||
execute("ALTER TABLE `section` RENAME TO `friction`")
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
if version == "0.0.0":
|
||||
logger.info(f"Update friction TABLE from {version}")
|
||||
cls._sql_update_0_0_1(execute, version)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data['status'])
|
||||
|
||||
new._lst = Friction._sql_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
reach = data["reach"]
|
||||
execute(f"DELETE FROM friction WHERE reach = {reach.id}")
|
||||
|
||||
ok = True
|
||||
ind = 0
|
||||
for friction in self._lst:
|
||||
data["ind"] = ind
|
||||
ok &= friction._sql_save(execute, data = data)
|
||||
ind += 1
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def frictions(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, index):
|
||||
n = Friction(status = self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
@@ -1,14 +1,47 @@
|
||||
# Point.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
class Point(object):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
def __init__(self, name:str = "", profile=None, status = None):
|
||||
super(Point, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._name = name
|
||||
self._profile = profile
|
||||
self._sl = None
|
||||
|
||||
|
||||
@property
|
||||
def sl(self):
|
||||
if self._sl is None:
|
||||
return self._profile.sl
|
||||
|
||||
return self._sl
|
||||
|
||||
@sl.setter
|
||||
def sl(self, sl):
|
||||
if sl == self._profile.sl:
|
||||
self._sl = None
|
||||
|
||||
self._sl = sl
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# PointXY.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from math import dist
|
||||
|
||||
@@ -1,19 +1,140 @@
|
||||
# PointXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from math import dist
|
||||
import numpy as np
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Geometry.Point import Point
|
||||
|
||||
class PointXYZ(Point):
|
||||
class PointXYZ(Point, SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0,
|
||||
name:str = "", status = None):
|
||||
super(PointXYZ, self).__init__(name=name, status=status)
|
||||
name:str = "", profile = None, status = None):
|
||||
super(PointXYZ, self).__init__(name=name, profile=profile, status=status)
|
||||
|
||||
self._x = float(x)
|
||||
self._y = float(y)
|
||||
self._z = float(z)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE geometry_pointXYZ(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL,
|
||||
profile INTEGER NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(profile) REFERENCES profileXYZ(id),
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
cls._update_submodel(execute, version)
|
||||
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"""
|
||||
ALTER TABLE geometry_pointXYZ
|
||||
ADD COLUMN sl INTEGER
|
||||
REFERENCES sedimentary_layer(id)
|
||||
"""
|
||||
)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
points = []
|
||||
|
||||
status = data["status"]
|
||||
profile = data["profile"]
|
||||
|
||||
table = execute(
|
||||
"SELECT ind, name, x, y, z, sl " +
|
||||
"FROM geometry_pointXYZ " +
|
||||
f"WHERE profile = {profile.id}"
|
||||
)
|
||||
|
||||
# Create points list
|
||||
for _ in table:
|
||||
points.append(None)
|
||||
|
||||
# Fill points list with new point
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
x = row[2]
|
||||
y = row[3]
|
||||
z = row[4]
|
||||
sl = row[5]
|
||||
|
||||
new = cls(
|
||||
name = name,
|
||||
x = x, y = y, z = z,
|
||||
profile = profile,
|
||||
status = status
|
||||
)
|
||||
|
||||
if sl == -1 or sl == None:
|
||||
new._sl = None
|
||||
else:
|
||||
new._sl = next(
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
)
|
||||
)
|
||||
|
||||
points[ind] = new
|
||||
|
||||
return points
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
profile = data["profile"]
|
||||
ind = data["ind"]
|
||||
|
||||
sl = self._sl.id if self._sl is not None else -1
|
||||
|
||||
sql = (
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"geometry_pointXYZ(ind, name, x, y, z, profile, sl) "+
|
||||
"VALUES (" +
|
||||
f"{ind}, '{self._sql_format(self._name)}', " +
|
||||
f"{self.x}, {self.y}, {self.z}, " +
|
||||
f"{profile.id}, {sl}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, header, data):
|
||||
point = None
|
||||
@@ -23,7 +144,7 @@ class PointXYZ(Point):
|
||||
*data
|
||||
)
|
||||
else:
|
||||
valid_header = {'name', 'x', 'y', 'z'}
|
||||
valid_header = {'name', 'x', 'y', 'z', 'profile'}
|
||||
d = {}
|
||||
for i, v in enumerate(data):
|
||||
h = header[i].strip().lower().split(' ')[0]
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
# Profile.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import timer
|
||||
|
||||
from Model.Geometry.Point import Point
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Profile(object):
|
||||
def __init__(self, num: int = 0,
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int = -1, num:int = 0,
|
||||
kp:float = 0.0, name:str = "",
|
||||
code1: int = 0, code2: int = 0,
|
||||
code1:int = 0, code2:int = 0,
|
||||
_type:str = "", reach = None,
|
||||
status = None):
|
||||
super(Profile, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = Profile._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
Profile._id_cnt = max(self.id, Profile._id_cnt+1)
|
||||
|
||||
self._num = int(num)
|
||||
self._code1 = int(code1)
|
||||
self._code2 = int(code2)
|
||||
self._kp = float(kp)
|
||||
self._name = str(name)
|
||||
self._reach = reach
|
||||
self._sl = None
|
||||
|
||||
self._points: List[Point] = []
|
||||
|
||||
@@ -34,6 +64,9 @@ class Profile(object):
|
||||
def points(self):
|
||||
return self._points.copy()
|
||||
|
||||
def point(self, index):
|
||||
return self._points[index]
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
@@ -107,6 +140,19 @@ class Profile(object):
|
||||
self._name = value.strip()
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def sl(self):
|
||||
"""
|
||||
Returns:
|
||||
Profile sediment layers.
|
||||
"""
|
||||
return self._sl
|
||||
|
||||
@sl.setter
|
||||
def sl(self, value):
|
||||
self._sl = value
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def profile_type(self):
|
||||
"""
|
||||
@@ -245,7 +291,7 @@ class Profile(object):
|
||||
@timer
|
||||
def sort_with_indexes(self, indexes: list):
|
||||
if len(self._points) != len(indexes):
|
||||
print("TODO: CRITICAL ERROR!")
|
||||
logger.critical("Indexes list do not correspond to point list")
|
||||
|
||||
self._points = list(
|
||||
map(
|
||||
@@ -258,6 +304,39 @@ class Profile(object):
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
def get_sl(self):
|
||||
"""Get sediment layer height of points
|
||||
|
||||
Get sediment layer of points (without spesific point sl)
|
||||
|
||||
Returns:
|
||||
List of sediment layers height
|
||||
"""
|
||||
res = []
|
||||
psl = [point.sl for point in self.points]
|
||||
|
||||
# Compute max number of layers
|
||||
sl_max = 0
|
||||
for sl in psl:
|
||||
n = 0 if sl is None else len(sl)
|
||||
sl_max = max(n, sl_max)
|
||||
|
||||
# Create list of height for each sl and each layer
|
||||
for i in range(0, sl_max):
|
||||
cur = []
|
||||
# Compute new layer line for each sl
|
||||
for sl in psl:
|
||||
if sl is not None and i < len(sl):
|
||||
cur.append(sl.get(i).height)
|
||||
else:
|
||||
cur.append(0)
|
||||
# Add layer line to result
|
||||
res.append(cur)
|
||||
|
||||
return res
|
||||
|
||||
# Abstract method, must be implemented for in non abstract class
|
||||
def get_station(self):
|
||||
raise NotImplementedMethodeError(self, self.get_station)
|
||||
@@ -1,3 +1,19 @@
|
||||
# ProfileXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import numpy as np
|
||||
@@ -5,13 +21,19 @@ from typing import List
|
||||
|
||||
from tools import timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import ClipboardFormatError
|
||||
from Model.Geometry.Profile import Profile
|
||||
from Model.Geometry.PointXYZ import PointXYZ
|
||||
from Model.Geometry.Vector_1d import Vector1d
|
||||
|
||||
class ProfileXYZ(Profile):
|
||||
class ProfileXYZ(Profile, SQLSubModel):
|
||||
_sub_classes = [
|
||||
PointXYZ,
|
||||
]
|
||||
|
||||
def __init__(self,
|
||||
id: int = -1,
|
||||
name: str = "",
|
||||
kp: float = 0.,
|
||||
reach = None,
|
||||
@@ -32,6 +54,7 @@ class ProfileXYZ(Profile):
|
||||
Nothing.
|
||||
"""
|
||||
super(ProfileXYZ, self).__init__(
|
||||
id = id,
|
||||
num = num,
|
||||
name = name,
|
||||
kp = kp,
|
||||
@@ -41,6 +64,119 @@ class ProfileXYZ(Profile):
|
||||
status = status,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE geometry_profileXYZ(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
reach INTEGER NOT NULL,
|
||||
kp REAL NOT NULL,
|
||||
num INTEGER NOT NULL,
|
||||
code1 INTEGER NOT NULL,
|
||||
code2 INTEGER NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id),
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"""
|
||||
ALTER TABLE geometry_profileXYZ
|
||||
ADD COLUMN sl INTEGER
|
||||
REFERENCES sedimentary_layer(id)
|
||||
"""
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
profiles = []
|
||||
status = data["status"]
|
||||
reach = data["reach"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, ind, name, kp, num, code1, code2, sl " +
|
||||
"FROM geometry_profileXYZ " +
|
||||
f"WHERE reach = {reach}"
|
||||
)
|
||||
|
||||
for _ in table:
|
||||
profiles.append(None)
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
ind = row[1]
|
||||
name = row[2]
|
||||
kp = row[3]
|
||||
num = row[5]
|
||||
code1 = row[5]
|
||||
code2 = row[6]
|
||||
sl = row[7]
|
||||
|
||||
new = cls(
|
||||
id=id, num = num,
|
||||
name = name, kp = kp,
|
||||
code1 = code1, code2 = code2,
|
||||
reach = data["parent"],
|
||||
status = status
|
||||
)
|
||||
|
||||
if sl == -1 or sl == None:
|
||||
new._sl = None
|
||||
else:
|
||||
new._sl = next(
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
)
|
||||
)
|
||||
|
||||
data["profile"] = new
|
||||
new._points = PointXYZ._sql_load(execute, data)
|
||||
|
||||
profiles[ind] = new
|
||||
|
||||
return profiles
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ok = True
|
||||
ind = data["ind"]
|
||||
|
||||
sl = self._sl.id if self._sl is not None else -1
|
||||
|
||||
sql = (
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"geometry_profileXYZ(id, ind, name, reach, kp, num, code1, code2, sl) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, {ind}, '{self._sql_format(self._name)}', " +
|
||||
f"{self.reach.id}, {self.kp}, {self.num}, " +
|
||||
f"{self.code1}, {self.code1}, {sl}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["profile"] = self
|
||||
execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}")
|
||||
|
||||
ind = 0
|
||||
for point in self._points:
|
||||
data["ind"] = ind
|
||||
ok &= point._sql_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return ok
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, header, data):
|
||||
profile = None
|
||||
@@ -68,11 +204,11 @@ class ProfileXYZ(Profile):
|
||||
try:
|
||||
if len(header) == 0:
|
||||
point = PointXYZ(
|
||||
*data, status=self._status
|
||||
*data, profile=self, status=self._status
|
||||
)
|
||||
else:
|
||||
valid_header = {'name', 'x', 'y', 'z'}
|
||||
d = {"status": self._status}
|
||||
d = {"status": self._status, "profile": self}
|
||||
for i, v in enumerate(data):
|
||||
h = header[i].strip().lower().split(' ')[0]
|
||||
if h in valid_header:
|
||||
@@ -125,7 +261,7 @@ class ProfileXYZ(Profile):
|
||||
Nothing.
|
||||
"""
|
||||
for point in list_points:
|
||||
pt = PointXYZ(*point, status=self._status)
|
||||
pt = PointXYZ(*point, profile=self, status=self._status)
|
||||
self._points.append(pt)
|
||||
self._status.modified()
|
||||
|
||||
@@ -149,7 +285,7 @@ class ProfileXYZ(Profile):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
point_xyz = PointXYZ(0., 0., 0., status=self._status)
|
||||
point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status)
|
||||
self._points.append(point_xyz)
|
||||
self._status.modified()
|
||||
|
||||
@@ -162,7 +298,7 @@ class ProfileXYZ(Profile):
|
||||
Returns:
|
||||
The new point.
|
||||
"""
|
||||
point = PointXYZ(0., 0., 0., status=self._status)
|
||||
point = PointXYZ(0., 0., 0., profile=self, status=self._status)
|
||||
self._points.insert(index, point)
|
||||
self._status.modified()
|
||||
return point
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# Reach.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from time import time
|
||||
@@ -10,13 +27,22 @@ from functools import reduce
|
||||
|
||||
from tools import flatten, timer, trace
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
from Model.Geometry.Profile import Profile
|
||||
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||
|
||||
from Model.Except import FileFormatError, exception_message_box
|
||||
|
||||
class Reach:
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Reach(SQLSubModel):
|
||||
_sub_classes = [
|
||||
ProfileXYZ,
|
||||
]
|
||||
|
||||
def __init__(self, status=None, parent=None):
|
||||
self.id = parent.id
|
||||
self._status = status
|
||||
self._parent = parent
|
||||
self._profiles: List[Profile] = []
|
||||
@@ -24,6 +50,41 @@ class Reach:
|
||||
self._guidelines_is_valid = False
|
||||
self._guidelines = {}
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data["status"], parent = data["parent"])
|
||||
|
||||
new._profiles = ProfileXYZ._sql_load(
|
||||
execute,
|
||||
data = data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
# Delete old data
|
||||
execute(f"DELETE FROM geometry_profileXYZ WHERE reach = {self.id}")
|
||||
# execute(f"DELETE FROM geometry_pointXYZ")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
ind = 0
|
||||
for profile in self._profiles:
|
||||
data["ind"] = ind
|
||||
profile._sql_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def profile(self, i):
|
||||
"""Returns profile at index i
|
||||
|
||||
@@ -215,6 +276,39 @@ class Reach:
|
||||
return max(self.get_kp())
|
||||
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
def get_sl(self):
|
||||
"""Get sediment layer height of profile
|
||||
|
||||
Get sediment layer of profile (without spesific point sl)
|
||||
|
||||
Returns:
|
||||
List of sediment layers height
|
||||
"""
|
||||
res = []
|
||||
psl = [profile.sl for profile in self.profiles]
|
||||
|
||||
# Compute max number of layers
|
||||
sl_max = 0
|
||||
for sl in psl:
|
||||
n = 0 if sl is None else len(sl)
|
||||
sl_max = max(n, sl_max)
|
||||
|
||||
# Create list of height for each sl and each layer
|
||||
for i in range(0, sl_max):
|
||||
cur = []
|
||||
# Compute new layer line for each sl
|
||||
for sl in psl:
|
||||
if sl is not None and i < len(sl):
|
||||
cur.append(sl.get(i).height)
|
||||
else:
|
||||
cur.append(0)
|
||||
# Add layer line to result
|
||||
res.append(cur)
|
||||
|
||||
return res
|
||||
|
||||
# Guidelines
|
||||
|
||||
@timer
|
||||
@@ -335,7 +429,7 @@ class Reach:
|
||||
@timer
|
||||
def sort_with_indexes(self, indexes: list):
|
||||
if len(self._profiles) != len(indexes):
|
||||
print("TODO: CRITICAL ERROR!")
|
||||
logger.critical("Indexes list do not correspond to profile list")
|
||||
|
||||
self._profiles = list(
|
||||
map(
|
||||
@@ -382,10 +476,10 @@ class Reach:
|
||||
|
||||
self._status.modified()
|
||||
except FileNotFoundError as e:
|
||||
print(e)
|
||||
logger.error(e)
|
||||
exception_message_box(e)
|
||||
except FileFormatError as e:
|
||||
print(e)
|
||||
logger.error(e)
|
||||
e.alert()
|
||||
|
||||
@timer
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Vector_1d.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -1,36 +1,140 @@
|
||||
# InitialConditions.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy, deepcopy
|
||||
from tools import trace, timer
|
||||
from functools import reduce
|
||||
|
||||
class Data(object):
|
||||
def __init__(self, reach = None, status = None):
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
class Data(SQLSubModel):
|
||||
def __init__(self, name:str = "",
|
||||
comment:str = "", reach = None,
|
||||
kp:float = 0.0, discharge:float = 0.0,
|
||||
height:float = 0.0,
|
||||
status = None):
|
||||
super(Data, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._reach = reach
|
||||
|
||||
self._name = ""
|
||||
self._comment = ""
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
|
||||
self._kp = 0.0
|
||||
self._discharge = 0.0
|
||||
self._kp = kp
|
||||
self._discharge = discharge
|
||||
self._speed = 0.0
|
||||
self._elevation = 0.0
|
||||
self._height = 0.0
|
||||
self._height = height
|
||||
|
||||
if self._kp != 0.0:
|
||||
self._update_from_kp()
|
||||
if self._height != 0.0:
|
||||
self._update_from_height()
|
||||
if self._discharge != 0.0:
|
||||
self._update_from_discharge()
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE initial_conditions(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
reach INTEGER,
|
||||
kp REAL NOT NULL,
|
||||
discharge REAL NOT NULL,
|
||||
height REAL NOT NULL,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
id = data["reach"].id
|
||||
table = execute(
|
||||
"SELECT ind, name, comment, kp, discharge, height " +
|
||||
"FROM initial_conditions " +
|
||||
f"WHERE reach = {id}"
|
||||
)
|
||||
|
||||
new = []
|
||||
|
||||
for _ in table:
|
||||
new.append(None)
|
||||
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
comment = row[2]
|
||||
kp = row[3]
|
||||
discharge = row[4]
|
||||
height = row[5]
|
||||
|
||||
d = cls(
|
||||
reach = data["reach"],
|
||||
status = data["status"],
|
||||
name = name,
|
||||
comment = comment,
|
||||
kp = kp,
|
||||
discharge = discharge,
|
||||
height = height,
|
||||
)
|
||||
|
||||
new[ind] = d
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ind = data["ind"]
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"initial_conditions(ind, name, comment, kp, discharge, height, reach) " +
|
||||
"VALUES (" +
|
||||
f"{ind}, '{self._sql_format(self.name)}', " +
|
||||
f"'{self._sql_format(self._comment)}', " +
|
||||
f"{self._kp}, {self._discharge}, {self._height}, " +
|
||||
f"{self._reach.id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def copy(self):
|
||||
new = Data(self._reach, self._status)
|
||||
|
||||
new._name = self._name
|
||||
new._comment = self._comment
|
||||
new._kp = self._kp
|
||||
new._discharge = self._discharge
|
||||
new._speed = self._speed
|
||||
new._elevation = self._elevation
|
||||
new._height = self._height
|
||||
new = Data(
|
||||
name = self.name,
|
||||
comment = self._comment,
|
||||
kp = self._kp,
|
||||
discharge = self._discharge,
|
||||
height = self._height,
|
||||
reach = self._reach,
|
||||
status = self._status,
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
@@ -81,7 +185,7 @@ class Data(object):
|
||||
|
||||
def _update_from_discharge(self):
|
||||
min = self._update_get_min()
|
||||
print("TODO")
|
||||
# print("TODO")
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == "name":
|
||||
@@ -92,8 +196,8 @@ class Data(object):
|
||||
self._kp = float(value)
|
||||
self._update_from_kp()
|
||||
elif key == "speed":
|
||||
self._speed = float(value)
|
||||
# Not supposed to be modified
|
||||
self._speed = float(value)
|
||||
elif key == "discharge":
|
||||
self._discharge = float(value)
|
||||
self._update_from_discharge()
|
||||
@@ -106,7 +210,12 @@ class Data(object):
|
||||
|
||||
self._status.modified()
|
||||
|
||||
class InitialConditions(object):
|
||||
|
||||
class InitialConditions(SQLSubModel):
|
||||
_sub_classes = [
|
||||
Data
|
||||
]
|
||||
|
||||
def __init__(self, reach = None, status = None):
|
||||
super(InitialConditions, self).__init__()
|
||||
|
||||
@@ -115,6 +224,42 @@ class InitialConditions(object):
|
||||
self._reach = reach
|
||||
self._data = []
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(
|
||||
reach = data["reach"],
|
||||
status = data["status"]
|
||||
)
|
||||
|
||||
new._data = Data._sql_load(
|
||||
execute,
|
||||
data = data
|
||||
)
|
||||
|
||||
if new._data is None:
|
||||
return None
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ok = True
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data["ind"] = ind
|
||||
ok &= d._sql_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return ok
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
|
||||
@@ -1,35 +1,61 @@
|
||||
# InitialConditionsDict.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDict import PamhyrModelDict
|
||||
from Model.InitialConditions.InitialConditions import InitialConditions
|
||||
|
||||
class InitialConditionsDict(object):
|
||||
def __init__(self, status = None):
|
||||
super(InitialConditionsDict, self).__init__()
|
||||
class InitialConditionsDict(PamhyrModelDict):
|
||||
_sub_classes = [
|
||||
InitialConditions,
|
||||
]
|
||||
|
||||
self._status = status
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data["status"])
|
||||
|
||||
self._reach = {}
|
||||
for reach in data["edges"]:
|
||||
data["reach"] = reach
|
||||
|
||||
def __len__(self):
|
||||
return len(self._reach)
|
||||
ic = InitialConditions._sql_load(
|
||||
execute,
|
||||
data = data
|
||||
)
|
||||
|
||||
def is_defined(self, reach):
|
||||
return reach in self._reach
|
||||
if ic is not None:
|
||||
new._dict[reach] = ic
|
||||
|
||||
def get(self, reach):
|
||||
if reach in self._reach:
|
||||
return self._reach[reach]
|
||||
|
||||
new = self.new(reach)
|
||||
self.set(reach, new)
|
||||
return new
|
||||
|
||||
def set(self, reach, new):
|
||||
self._reach[reach] = new
|
||||
self._status.modified()
|
||||
def _sql_save(self, execute, data = None):
|
||||
ok = True
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
execute("DELETE FROM initial_conditions")
|
||||
|
||||
for reach in self._dict:
|
||||
data["reach"] = reach
|
||||
ok &= self._dict[reach]._sql_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
def new(self, reach):
|
||||
new = InitialConditions(reach = reach, status = self._status)
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
# LateralContribution.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import trace, timer, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
class LateralContribution(object):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
logger = logging.getLogger()
|
||||
|
||||
class LateralContribution(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int = -1, name:str = "", status = None):
|
||||
super(LateralContribution, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = LateralContribution._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
self._edge = None
|
||||
@@ -19,6 +49,134 @@ class LateralContribution(object):
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
LateralContribution._id_cnt = max(LateralContribution._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
tab TEXT NOT NULL,
|
||||
edge INTEGER,
|
||||
begin_kp REAL NOT NULL,
|
||||
end_kp REAL NOT NULL,
|
||||
FOREIGN KEY(edge) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution_data(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
data0 TEXT NOT NULL,
|
||||
data1 TEXT NOT NULL,
|
||||
lc INTEGER,
|
||||
FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _get_ctor_from_type(cls, t):
|
||||
from Model.LateralContribution.LateralContributionTypes import (
|
||||
NotDefined, LateralContrib, Rain, Evaporation
|
||||
)
|
||||
|
||||
res = NotDefined
|
||||
if t == "LC":
|
||||
res = LateralContrib
|
||||
elif t == "RA":
|
||||
res = Rain
|
||||
elif t == "EV":
|
||||
res = Evaporation
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = []
|
||||
tab = data["tab"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, type, edge, begin_kp, end_kp " +
|
||||
f"FROM lateral_contribution WHERE tab = '{tab}'"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
t = row[2]
|
||||
ctor = cls._get_ctor_from_type(t)
|
||||
lc = ctor(
|
||||
id = row[0],
|
||||
name = row[1],
|
||||
status = data['status']
|
||||
)
|
||||
lc.edge = None
|
||||
if row[3] != -1:
|
||||
lc.edge = next(filter(lambda e: e.id == row[3], data["edges"]))
|
||||
lc._begin_kp = row[4]
|
||||
lc._end_kp = row[5]
|
||||
|
||||
values = execute(
|
||||
"SELECT ind, data0, data1 FROM lateral_contribution_data " +
|
||||
f"WHERE lc = '{lc.id}'"
|
||||
)
|
||||
# Create dummy data list
|
||||
for _ in values:
|
||||
lc.add(0)
|
||||
# Write data
|
||||
for v in values:
|
||||
ind = v[0]
|
||||
data0 = lc._types[0](v[1])
|
||||
data1 = lc._types[1](v[2])
|
||||
# Replace data at pos ind
|
||||
lc._data[ind] = (data0, data1)
|
||||
|
||||
new.append(lc)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
tab = data["tab"]
|
||||
|
||||
execute(f"DELETE FROM lateral_contribution WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM lateral_contribution_data WHERE lc = {self.id}")
|
||||
|
||||
edge = -1
|
||||
if self._edge is not None:
|
||||
edge = self._edge.id
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution(id, name, type, tab, edge, begin_kp, end_kp) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._sql_format(self._name)}', " +
|
||||
f"'{self._sql_format(self._type)}', '{tab}', {edge}, " +
|
||||
f"{self._begin_kp}, {self._end_kp}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._sql_format(str(d[0]))
|
||||
data1 = self._sql_format(str(d[1]))
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_data(ind, data0, data1, lc) "+
|
||||
f"VALUES ({ind}, '{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@@ -204,14 +362,17 @@ class LateralContribution(object):
|
||||
new.begin_kp = self.begin_kp
|
||||
new.end_kp = self.end_kp
|
||||
|
||||
for i, _ in self.data:
|
||||
for i, _ in enumerate(self.data):
|
||||
new.add(i)
|
||||
|
||||
for i in [0,1]:
|
||||
for j in [0,1]:
|
||||
if self._header[i] == new.header[j]:
|
||||
for ind, v in self.data:
|
||||
new._set_i_c_v(ind, j, v[i])
|
||||
try:
|
||||
new._set_i_c_v(ind, j, v[i])
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
|
||||
self._status.modified()
|
||||
return new
|
||||
|
||||
@@ -1,38 +1,65 @@
|
||||
# LateralContributionList.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelListWithTab
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.LateralContribution.LateralContribution import LateralContribution
|
||||
from Model.LateralContribution.LateralContributionTypes import (
|
||||
NotDefined, LateralContrib, Rain, Evaporation,
|
||||
)
|
||||
|
||||
class LateralContributionList(object):
|
||||
def __init__(self, status = None):
|
||||
super(LateralContributionList, self).__init__()
|
||||
class LateralContributionList(PamhyrModelListWithTab):
|
||||
_tabs_list = ["liquid", "solid", "suspenssion"]
|
||||
_sub_classes = [
|
||||
LateralContribution,
|
||||
]
|
||||
|
||||
self._status = status
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data['status'])
|
||||
|
||||
self._tabs = {
|
||||
"liquid" : [],
|
||||
"solid" : [],
|
||||
"suspenssion" : []
|
||||
}
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
def len(self, lst):
|
||||
return len(self._tabs[lst])
|
||||
for tab in new._tabs:
|
||||
data["tab"] = tab
|
||||
new._tabs[tab] = LateralContribution._sql_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
def get_tab(self, lst):
|
||||
return self._tabs[lst].copy()
|
||||
return new
|
||||
|
||||
def get(self, lst, row):
|
||||
return self._tabs[lst][row]
|
||||
def _sql_save(self, execute, data = None):
|
||||
execute("DELETE FROM lateral_contribution")
|
||||
|
||||
def set(self, lst, row, new):
|
||||
self._tabs[lst][row] = new
|
||||
self._status.modified()
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for tab in self._tabs:
|
||||
data["tab"] = tab
|
||||
for lc in self._tabs[tab]:
|
||||
lc._sql_save(execute, data = data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, lst, index):
|
||||
n = NotDefined(status=self._status)
|
||||
@@ -40,47 +67,6 @@ class LateralContributionList(object):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def insert(self, lst, index, new):
|
||||
self._tabs[lst].insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, lst, bcs):
|
||||
for bc in bcs:
|
||||
self._tabs[lst].remove(bc)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, lst, indexes):
|
||||
bcs = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._tabs[lst])
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(lst, bcs)
|
||||
|
||||
def sort(self, lst, reverse=False, key=None):
|
||||
self._tabs[lst].sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, lst, index):
|
||||
if index < len(self._tabs[lst]):
|
||||
next = index - 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, lst, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def __copy__(self):
|
||||
new = LateralContributionList()
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# LateralContributionTypes.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
@@ -6,8 +22,8 @@ from Model.LateralContribution.LateralContribution import LateralContribution
|
||||
|
||||
|
||||
class NotDefined(LateralContribution):
|
||||
def __init__(self, name:str = "", status=None):
|
||||
super(NotDefined, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status=None):
|
||||
super(NotDefined, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "ND"
|
||||
self._header = ["x", "y"]
|
||||
@@ -17,8 +33,8 @@ class NotDefined(LateralContribution):
|
||||
return 0.0
|
||||
|
||||
class LateralContrib(LateralContribution):
|
||||
def __init__(self, name:str = "", status=None):
|
||||
super(LateralContrib, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status=None):
|
||||
super(LateralContrib, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "LC"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -29,8 +45,8 @@ class LateralContrib(LateralContribution):
|
||||
return ["liquid"]
|
||||
|
||||
class Rain(LateralContribution):
|
||||
def __init__(self, name:str = "", status=None):
|
||||
super(Rain, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status=None):
|
||||
super(Rain, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "RA"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -41,8 +57,8 @@ class Rain(LateralContribution):
|
||||
return ["liquid"]
|
||||
|
||||
class Evaporation(LateralContribution):
|
||||
def __init__(self, name:str = "", status=None):
|
||||
super(Evaporation, self).__init__(name=name, status=status)
|
||||
def __init__(self, id:int = -1, name:str = "", status=None):
|
||||
super(Evaporation, self).__init__(id=id, name=name, status=status)
|
||||
|
||||
self._type = "EV"
|
||||
self._header = ["time", "discharge"]
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
# Edge.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Network.Node import Node
|
||||
|
||||
class Edge(object):
|
||||
def __init__(self, id:str, name:str,
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int, name:str,
|
||||
node1:Node = None,
|
||||
node2:Node = None,
|
||||
status = None):
|
||||
@@ -11,7 +29,12 @@ class Edge(object):
|
||||
|
||||
self._status = status
|
||||
|
||||
self.id = id
|
||||
if id == -1:
|
||||
type(self)._id_cnt += 1
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
|
||||
self.node1 = node1
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Graph.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from functools import reduce
|
||||
@@ -14,9 +30,6 @@ class Graph(object):
|
||||
self._node_ctor = Node
|
||||
self._edge_ctor = Edge
|
||||
|
||||
self._nodes_ids = 0
|
||||
self._edges_ids = 0
|
||||
|
||||
self._nodes = []
|
||||
self._edges = []
|
||||
|
||||
@@ -32,6 +45,14 @@ class Graph(object):
|
||||
def edges(self):
|
||||
return self._edges
|
||||
|
||||
def enable_edges(self):
|
||||
return list(
|
||||
filter(
|
||||
lambda e: e.is_enable(),
|
||||
self._edges
|
||||
)
|
||||
)
|
||||
|
||||
def edges_names(self):
|
||||
return list(map(lambda e: e.name, self._edges))
|
||||
|
||||
@@ -41,6 +62,13 @@ class Graph(object):
|
||||
def edges_counts(self):
|
||||
return len(self._edges)
|
||||
|
||||
def enable_edges_counts(self):
|
||||
return reduce(
|
||||
lambda acc, e: acc + 1 if e.is_enable() else acc,
|
||||
self._edges,
|
||||
0
|
||||
)
|
||||
|
||||
def is_node_exists(self, node_name):
|
||||
return reduce(
|
||||
lambda acc, n: (acc or (n.name == node_name)),
|
||||
@@ -83,12 +111,11 @@ class Graph(object):
|
||||
|
||||
def _create_node(self, x:float, y:float):
|
||||
node = self._node_ctor(
|
||||
self._nodes_ids,
|
||||
f"Node {self._nodes_ids}",
|
||||
-1,
|
||||
"",
|
||||
x = x, y = y,
|
||||
status = self._status
|
||||
)
|
||||
self._nodes_ids += 1
|
||||
return node
|
||||
|
||||
def _add_node(self, node):
|
||||
@@ -118,11 +145,10 @@ class Graph(object):
|
||||
|
||||
def _create_edge(self, n1:Node, n2:Node):
|
||||
edge = self._edge_ctor(
|
||||
self._edges_ids,
|
||||
-1,
|
||||
"", n1, n2,
|
||||
status = self._status
|
||||
)
|
||||
self._edges_ids += 1
|
||||
return edge
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +1,62 @@
|
||||
# Node.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Network.Point import Point
|
||||
|
||||
class Node(object):
|
||||
def __init__(self, id:str, name:str,
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int, name:str,
|
||||
x:float = 0.0, y:float = 0.0,
|
||||
status = None):
|
||||
super(Node, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self.id = id
|
||||
self._name = name
|
||||
if id == -1:
|
||||
type(self)._id_cnt += 1
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
if name == "":
|
||||
self._name = f"Node {self.id}"
|
||||
else:
|
||||
self._name = name
|
||||
|
||||
self.pos = Point(x, y)
|
||||
|
||||
def __getitem__(self, name):
|
||||
def __getitem__(self, key):
|
||||
ret = None
|
||||
|
||||
if name == "name":
|
||||
if key == "name":
|
||||
ret = self._name
|
||||
elif name == "id":
|
||||
elif key == "id":
|
||||
ret = self.id
|
||||
elif name == "pos":
|
||||
elif key == "pos":
|
||||
ret = f"({self.pos.x},{self.pos.y})"
|
||||
|
||||
return ret
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
if name == "name":
|
||||
def __setitem__(self, key, value):
|
||||
if key == "name":
|
||||
self._name = value
|
||||
elif name == "id":
|
||||
elif key == "id":
|
||||
self.id = value
|
||||
|
||||
self._status.modified()
|
||||
@@ -38,6 +65,15 @@ class Node(object):
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.pos.x
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self.pos.y
|
||||
|
||||
|
||||
def setPos(self, x, y):
|
||||
self.pos.x = x
|
||||
self.pos.y = y
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Point.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class Point(object):
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Results.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
|
||||
from Model.Results.River.River import River
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Results(object):
|
||||
def __init__(self, study = None):
|
||||
self._study = study
|
||||
self._river = River(self._study)
|
||||
|
||||
self._meta_data = {
|
||||
# Keep results creation date
|
||||
"creation_date": datetime.now(),
|
||||
}
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
date = self._meta_data["creation_date"]
|
||||
return f"{date.isoformat(sep=' ')}"
|
||||
|
||||
@property
|
||||
def river(self):
|
||||
return self._river
|
||||
|
||||
def set(self, key, value):
|
||||
self._meta_data[key] = value
|
||||
|
||||
def get(self, key):
|
||||
return self._meta_data[key]
|
||||
@@ -0,0 +1,110 @@
|
||||
# River.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Profile(object):
|
||||
def __init__(self, profile, study):
|
||||
self._study = study
|
||||
self._profile = profile # Source profile in the study
|
||||
self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._profile.name
|
||||
|
||||
@property
|
||||
def kp(self):
|
||||
return self._profile.kp
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self._profile
|
||||
|
||||
def set(self, timestamp, key, data):
|
||||
if timestamp not in self._data:
|
||||
self._data[timestamp] = {}
|
||||
|
||||
self._data[timestamp][key] = data
|
||||
|
||||
def get_ts(self, timestamp):
|
||||
return self._data[timestamp]
|
||||
|
||||
def get_key(self, key):
|
||||
return list(
|
||||
map(lambda ts: self._data[ts][key], self._data)
|
||||
)
|
||||
|
||||
def get_ts_key(self, timestamp, key):
|
||||
return self._data[timestamp][key]
|
||||
|
||||
class Reach(object):
|
||||
def __init__(self, reach, study):
|
||||
self._study = study
|
||||
self._reach = reach # Source reach in the study
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._reach.name
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self._reach
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
return self._profiles.copy()
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
def set(self, profile_id, timestamp, key, data):
|
||||
self._profiles[profile_id].set(timestamp, key, data)
|
||||
|
||||
class River(object):
|
||||
def __init__(self, study):
|
||||
self._study = study
|
||||
|
||||
# Dict with timestamps as key
|
||||
self._reachs = []
|
||||
|
||||
@property
|
||||
def reachs(self):
|
||||
return self._reachs.copy()
|
||||
|
||||
def reach(self, id):
|
||||
return self._reachs[id]
|
||||
|
||||
def has_reach(self, id):
|
||||
return 0 <= id < len(self._reachs)
|
||||
|
||||
def add(self, reach_id):
|
||||
reachs = self._study.river.enable_edges()
|
||||
|
||||
new = Reach(reachs[reach_id].reach, self._study)
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
@@ -1,5 +1,25 @@
|
||||
# River.py -- Pamhyr river model
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import flatten
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
from Model.Network.Node import Node
|
||||
from Model.Network.Edge import Edge
|
||||
from Model.Network.Graph import Graph
|
||||
@@ -11,12 +31,15 @@ from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
|
||||
from Model.LateralContribution.LateralContributionList import LateralContributionList
|
||||
from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
|
||||
from Model.Stricklers.StricklersList import StricklersList
|
||||
from Model.Section.SectionList import SectionList
|
||||
from Model.Friction.FrictionList import FrictionList
|
||||
from Model.SolverParameters.SolverParametersList import SolverParametersList
|
||||
from Model.SedimentLayer.SedimentLayerList import SedimentLayerList
|
||||
|
||||
from Solver.Solvers import solver_type_list
|
||||
|
||||
class RiverNode(Node):
|
||||
class RiverNode(Node, SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id:str, name:str,
|
||||
x:float, y:float,
|
||||
status = None):
|
||||
@@ -28,6 +51,48 @@ class RiverNode(Node):
|
||||
|
||||
self._locker = None
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_node(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
nodes = []
|
||||
|
||||
table = execute("SELECT id, name, x, y FROM river_node")
|
||||
for row in table:
|
||||
# Update id counter
|
||||
cls._id_cnt = max(cls._id_cnt, row[0])
|
||||
# Create new node
|
||||
nodes.append(cls(*row, status=data["status"]))
|
||||
|
||||
return nodes
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
sql = (
|
||||
"INSERT OR REPLACE INTO river_node(id, name, x, y) VALUES (" +
|
||||
f"{self.id}, '{self._sql_format(self.name)}', " +
|
||||
f"{self.x}, {self.y}"+
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def locker(self):
|
||||
return self._locker
|
||||
@@ -37,7 +102,12 @@ class RiverNode(Node):
|
||||
self._locker = locker
|
||||
|
||||
|
||||
class RiverReach(Edge):
|
||||
class RiverReach(Edge, SQLSubModel):
|
||||
_sub_classes = [
|
||||
Reach,
|
||||
FrictionList,
|
||||
]
|
||||
|
||||
def __init__(self, id:str, name:str,
|
||||
node1:RiverNode = None,
|
||||
node2:RiverNode = None,
|
||||
@@ -49,17 +119,101 @@ class RiverReach(Edge):
|
||||
)
|
||||
|
||||
self._reach = Reach(status=self._status, parent=self)
|
||||
self._sections = SectionList(status=self._status)
|
||||
self._frictions = FrictionList(status = self._status)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_reach(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
enable BOOLEAN NOT NULL,
|
||||
node1 INTEGER,
|
||||
node2 INTEGER,
|
||||
FOREIGN KEY(node1) REFERENCES river_node(id),
|
||||
FOREIGN KEY(node2) REFERENCES river_node(id)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
reachs = []
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
table = execute("SELECT id, name, enable, node1, node2 FROM river_reach")
|
||||
for row in table:
|
||||
# Update id counter
|
||||
cls._id_cnt = max(cls._id_cnt, row[0])
|
||||
# Create new reach
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
enable = (row[2] == 1)
|
||||
# Get nodes corresponding to db foreign key id
|
||||
node1 = next(filter(lambda n: n.id == row[3], data["nodes"]))
|
||||
node2 = next(filter(lambda n: n.id == row[4], data["nodes"]))
|
||||
|
||||
new = cls(id, name, node1, node2, status = data["status"])
|
||||
new.enable(enable = enable)
|
||||
|
||||
data["reach"] = id
|
||||
data["parent"] = new
|
||||
new._reach = Reach._sql_load(execute, data)
|
||||
|
||||
new._frictions = FrictionList._sql_load(execute, data)
|
||||
|
||||
reachs.append(new)
|
||||
|
||||
return reachs
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
sql = (
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"river_reach(id, name, enable, node1, node2) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._sql_format(self._name)}', " +
|
||||
f"{self._sql_format(self.is_enable())},"
|
||||
f"{self.node1.id}, {self.node2.id}"+
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
data["reach"] = self
|
||||
|
||||
objs = [self._reach, self._frictions]
|
||||
return self._save_submodel(execute, objs, data)
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@property
|
||||
def sections(self):
|
||||
return self._sections
|
||||
def frictions(self):
|
||||
return self._frictions
|
||||
|
||||
class River(Graph, SQLSubModel):
|
||||
_sub_classes = [
|
||||
RiverNode,
|
||||
RiverReach,
|
||||
BoundaryConditionList,
|
||||
LateralContributionList,
|
||||
InitialConditionsDict,
|
||||
StricklersList,
|
||||
SolverParametersList,
|
||||
SedimentLayerList,
|
||||
]
|
||||
|
||||
class River(Graph):
|
||||
def __init__(self, status=None):
|
||||
super(River, self).__init__(status=status)
|
||||
|
||||
@@ -73,6 +227,89 @@ class River(Graph):
|
||||
self._initial_conditions = InitialConditionsDict(status=self._status)
|
||||
self._stricklers = StricklersList(status=self._status)
|
||||
self._parameters = {}
|
||||
self._sediment_layers = SedimentLayerList(status=self._status)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
cls._update_submodel(execute, version)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(data["status"])
|
||||
|
||||
# Stricklers (Stricklers is load in first because it's needed
|
||||
# for reachs)
|
||||
new._stricklers = StricklersList._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
data["stricklers"] = new._stricklers
|
||||
|
||||
|
||||
# Initial conditions
|
||||
new._sediment_layers = SedimentLayerList._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
data["sediment_layers_list"] = new._sediment_layers
|
||||
|
||||
# Network
|
||||
new._nodes = RiverNode._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
data["nodes"] = new.nodes()
|
||||
|
||||
new._edges = RiverReach._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
data["edges"] = new.edges()
|
||||
|
||||
# Boundary Condition
|
||||
new._boundary_condition = BoundaryConditionList._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
# Lateral Contribution
|
||||
new._lateral_contribution = LateralContributionList._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
|
||||
# Initial conditions
|
||||
new._initial_conditions = InitialConditionsDict._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
|
||||
# Parameters
|
||||
new._parameters = SolverParametersList._sql_load(
|
||||
execute,
|
||||
data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
objs = (self._nodes + self._edges)
|
||||
objs.append(self._boundary_condition)
|
||||
objs.append(self._initial_conditions)
|
||||
objs.append(self._lateral_contribution)
|
||||
objs.append(self._sediment_layers)
|
||||
objs.append(self._stricklers)
|
||||
|
||||
for solver in self._parameters:
|
||||
objs.append(self._parameters[solver])
|
||||
|
||||
self._save_submodel(execute, objs, data)
|
||||
return True
|
||||
|
||||
@property
|
||||
def boundary_condition(self):
|
||||
@@ -86,6 +323,10 @@ class River(Graph):
|
||||
def initial_conditions(self):
|
||||
return self._initial_conditions
|
||||
|
||||
@property
|
||||
def sediment_layers(self):
|
||||
return self._sediment_layers
|
||||
|
||||
@property
|
||||
def stricklers(self):
|
||||
return self._stricklers
|
||||
@@ -121,10 +362,30 @@ class River(Graph):
|
||||
return self._parameters[solver]
|
||||
|
||||
def has_current_reach(self):
|
||||
if self.enable_edges_counts() == 1:
|
||||
return True
|
||||
|
||||
return self._current_reach is not None
|
||||
|
||||
def current_reach(self):
|
||||
ee = self.enable_edges()
|
||||
if len(ee) == 1:
|
||||
return ee[0]
|
||||
|
||||
return self._current_reach
|
||||
|
||||
def set_current_reach(self, reach):
|
||||
self._current_reach = reach
|
||||
|
||||
def has_sediment(self):
|
||||
has = len(self._sediment_layers) != 0
|
||||
has &= any(
|
||||
filter(
|
||||
lambda p: p.sl != None,
|
||||
flatten(
|
||||
map(lambda e: e.reach.profiles, self.edges())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return has
|
||||
@@ -1,5 +1,25 @@
|
||||
# Saved.py -- Pamhyr model status class
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class SavedStatus(object):
|
||||
def __init__(self):
|
||||
super(SavedStatus, self).__init__()
|
||||
@@ -9,9 +29,9 @@ class SavedStatus(object):
|
||||
return self._saved
|
||||
|
||||
def save(self):
|
||||
# print(" * save")
|
||||
logger.debug("model status set as saved")
|
||||
self._saved = True
|
||||
|
||||
def modified(self):
|
||||
# print(" * modified ...")
|
||||
# logger.debug("model status set as modified")
|
||||
self._saved = False
|
||||
@@ -1,95 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
|
||||
class Section(object):
|
||||
def __init__(self, name:str = "", status = None):
|
||||
super(Section, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._name = name
|
||||
self._edge = None
|
||||
self._begin_kp = 0.0
|
||||
self._end_kp = 0.0
|
||||
self._begin_strickler = None
|
||||
self._end_strickler = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def edge(self):
|
||||
return self._edge
|
||||
|
||||
@edge.setter
|
||||
def edge(self, edge):
|
||||
self._edge = edge
|
||||
if (edge is not None and
|
||||
self._begin_kp == 0.0 and
|
||||
self._end_kp == 0.0):
|
||||
self._begin_kp = self._edge.reach.get_kp_min()
|
||||
self._end_kp = self._edge.reach.get_kp_max()
|
||||
self._status.modified()
|
||||
|
||||
def has_edge(self):
|
||||
return self._edge is not None
|
||||
|
||||
@property
|
||||
def begin_kp(self):
|
||||
return self._begin_kp
|
||||
|
||||
@begin_kp.setter
|
||||
def begin_kp(self, begin_kp):
|
||||
if self._edge is None:
|
||||
self._begin_kp = begin_kp
|
||||
else:
|
||||
_min = self._edge.reach.get_kp_min()
|
||||
_max = self._edge.reach.get_kp_max()
|
||||
|
||||
if _min <= begin_kp <= _max:
|
||||
self._begin_kp = begin_kp
|
||||
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def end_kp(self):
|
||||
return self._end_kp
|
||||
|
||||
@end_kp.setter
|
||||
def end_kp(self, end_kp):
|
||||
if self._edge is None:
|
||||
self._end_kp = end_kp
|
||||
else:
|
||||
_min = self._edge.reach.get_kp_min()
|
||||
_max = self._edge.reach.get_kp_max()
|
||||
|
||||
if _min <= end_kp <= _max:
|
||||
self._end_kp = end_kp
|
||||
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def begin_strickler(self):
|
||||
return self._begin_strickler
|
||||
|
||||
@begin_strickler.setter
|
||||
def begin_strickler(self, strickler):
|
||||
self._begin_strickler = strickler
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def end_strickler(self):
|
||||
return self._end_strickler
|
||||
|
||||
@end_strickler.setter
|
||||
def end_strickler(self, strickler):
|
||||
self._end_strickler = strickler
|
||||
self._status.modified()
|
||||
@@ -1,74 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Section.Section import Section
|
||||
|
||||
class SectionList(object):
|
||||
def __init__(self, status = None):
|
||||
super(SectionList, self).__init__()
|
||||
|
||||
self._status = status
|
||||
self._sections = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self._sections)
|
||||
|
||||
@property
|
||||
def sections(self):
|
||||
return self._sections.copy()
|
||||
|
||||
def get(self, row):
|
||||
return self._sections[row]
|
||||
|
||||
def set(self, row, new):
|
||||
self._sections[row] = new
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = Section(status = self._status)
|
||||
self._sections.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def insert(self, index, new):
|
||||
self._sections.insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, sections):
|
||||
for section in sections:
|
||||
self._sections.remove(section)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
sections = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._sections)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(sections)
|
||||
|
||||
def sort(self, reverse=False, key=None):
|
||||
self._sections.sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, index):
|
||||
if index < len(self._sections):
|
||||
next = index - 1
|
||||
|
||||
l = self._sections
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._sections
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,339 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
class Layer(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self,
|
||||
id:int = -1, name:str = "",
|
||||
type = "",
|
||||
height = 0.0, d50 = 0.0, sigma = 0.0,
|
||||
critical_constraint = 0.0,
|
||||
sl = None, status = None):
|
||||
super(Layer, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._name = name
|
||||
self._type = type
|
||||
|
||||
self._height = height
|
||||
self._d50 = d50
|
||||
self._sigma = sigma
|
||||
self._critical_constraint = critical_constraint
|
||||
|
||||
if id == -1:
|
||||
self.id = Layer._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
Layer._id_cnt = max(id, Layer._id_cnt+1)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
@height.setter
|
||||
def height(self, height):
|
||||
self._height = float(height)
|
||||
|
||||
@property
|
||||
def d50(self):
|
||||
return self._d50
|
||||
|
||||
@d50.setter
|
||||
def d50(self, d50):
|
||||
self._d50 = float(d50)
|
||||
|
||||
@property
|
||||
def sigma(self):
|
||||
return self._sigma
|
||||
|
||||
@sigma.setter
|
||||
def sigma(self, sigma):
|
||||
self._sigma = float(sigma)
|
||||
|
||||
@property
|
||||
def critical_constraint(self):
|
||||
return self._critical_constraint
|
||||
|
||||
@critical_constraint.setter
|
||||
def critical_constraint(self, critical_constraint):
|
||||
self._critical_constraint = float(critical_constraint)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE sedimentary_layer_layer(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
height REAL NOT NULL,
|
||||
d50 REAL NOT NULL,
|
||||
sigma REAL NOT NULL,
|
||||
critical_constraint REAL NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 2:
|
||||
cls._sql_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = []
|
||||
sl = data["sl"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, ind, name, type, height, d50, sigma, critical_constraint " +
|
||||
"FROM sedimentary_layer_layer " +
|
||||
f"WHERE sl = {sl}"
|
||||
)
|
||||
|
||||
for _ in table:
|
||||
new.append(None)
|
||||
|
||||
for row in table:
|
||||
ind = row[1]
|
||||
|
||||
layer = cls(
|
||||
id = row[0], name = row[2],
|
||||
type = row[3], height = row[4],
|
||||
d50 = row[5], sigma = row[6],
|
||||
critical_constraint = row[7],
|
||||
sl = sl, status = data['status']
|
||||
)
|
||||
|
||||
new[ind] = layer
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ind = data["ind"]
|
||||
sl = data["sl"]
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"sedimentary_layer_layer(id, ind, name, type, height, d50, sigma, critical_constraint, sl) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, {ind}, '{self._sql_format(self._name)}', " +
|
||||
f"'{self._sql_format(self._type)}', {self._height}, " +
|
||||
f"{self._d50}, {self._sigma}, {self._critical_constraint}, " +
|
||||
f"{sl.id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class SedimentLayer(SQLSubModel):
|
||||
_sub_classes = [Layer]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id:int = -1,
|
||||
name:str = "", comment:str = "",
|
||||
status = None):
|
||||
super(SedimentLayer, self).__init__()
|
||||
|
||||
self._status = status
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
self._layers = []
|
||||
|
||||
if id == -1:
|
||||
self.id = SedimentLayer._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
SedimentLayer._id_cnt = max(id, SedimentLayer._id_cnt+1)
|
||||
|
||||
def __str__(self):
|
||||
s = f"{self.name} ({len(self)})"
|
||||
|
||||
if self.comment != "":
|
||||
s += f" - {self.comment}"
|
||||
|
||||
return s
|
||||
|
||||
def __len__(self):
|
||||
return len(self._layers)
|
||||
|
||||
@property
|
||||
def layers(self):
|
||||
return self._layers.copy()
|
||||
|
||||
def height(self):
|
||||
return list(
|
||||
map(lambda l: l.height, self._layers)
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
|
||||
def names(self):
|
||||
return list(
|
||||
map(lambda l: l.name, self._layers)
|
||||
)
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
return self._comment
|
||||
|
||||
@comment.setter
|
||||
def comment(self, comment):
|
||||
self._comment = comment
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE sedimentary_layer(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
comment TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 2:
|
||||
cls._sql_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, comment " +
|
||||
"FROM sedimentary_layer "
|
||||
)
|
||||
|
||||
for row in table:
|
||||
sl = cls(
|
||||
id = row[0],
|
||||
name = row[1],
|
||||
comment = row[2],
|
||||
status = data['status']
|
||||
)
|
||||
|
||||
data["sl"] = sl.id
|
||||
sl._layers = Layer._sql_load(execute, data)
|
||||
|
||||
new.append(sl)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
sql = (
|
||||
"INSERT INTO sedimentary_layer (id, name, comment) " +
|
||||
f"VALUES ({self.id}, '{self._sql_format(self._name)}', " +
|
||||
f"'{self._sql_format(self._comment)}')"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["sl"] = self
|
||||
|
||||
ind = 0
|
||||
for l in self._layers:
|
||||
data["ind"] = ind
|
||||
l._sql_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def get(self, index):
|
||||
return self._layers[index]
|
||||
|
||||
def set(self, index, new):
|
||||
self._layers[index] = new
|
||||
self._status.modified()
|
||||
|
||||
def insert(self, index, new):
|
||||
self._layers.insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = Layer(sl=self, status = self._status)
|
||||
self.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def delete(self, els):
|
||||
for el in els:
|
||||
self._layers.remove(el)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
els = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._layers)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(els)
|
||||
|
||||
def move_up(self, index):
|
||||
if index >= 0:
|
||||
next = index - 1
|
||||
|
||||
l = self._layers
|
||||
l[index], l[next] = l[next], l[index]
|
||||
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index + 1 < len(self._layers):
|
||||
prev = index + 1
|
||||
|
||||
l = self._layers
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.SedimentLayer.SedimentLayer import SedimentLayer
|
||||
|
||||
class SedimentLayerList(PamhyrModelList):
|
||||
_sub_classes = [SedimentLayer]
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data["status"])
|
||||
|
||||
new._lst = SedimentLayer._sql_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM sedimentary_layer")
|
||||
execute("DELETE FROM sedimentary_layer_layer")
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._sql_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def sediment_layers(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, index):
|
||||
n = SedimentLayer(status = self._status)
|
||||
self.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
@@ -1,10 +1,26 @@
|
||||
# Serializable.py -- Pamhyr pickle abstract class
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pickle
|
||||
|
||||
class Serializable():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def open(cls, filename):
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
# SolverParametersList.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
from Solver.Solvers import solver_type_list
|
||||
|
||||
class Parameter(object):
|
||||
def __init__(self, status = None):
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
|
||||
class Parameter():
|
||||
def __init__(self,
|
||||
name = "", value = "",
|
||||
status = None):
|
||||
self._status = status
|
||||
|
||||
self._name = ""
|
||||
self._value = ""
|
||||
self._name = name
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -42,75 +63,193 @@ class Parameter(object):
|
||||
|
||||
return new
|
||||
|
||||
class SolverParametersList(object):
|
||||
class SolverParametersList(PamhyrModelList):
|
||||
def __init__(self, solver_type = None, status = None):
|
||||
super(SolverParametersList, self).__init__()
|
||||
|
||||
self._status = status
|
||||
self._parameters = list(
|
||||
self._solver = solver_type
|
||||
self._lst = list(
|
||||
map(
|
||||
lambda t: Parameter.from_tuple(t, self._status),
|
||||
solver_type.default_parameters()
|
||||
)
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._parameters)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE solver_parameter(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
solver TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == minor == "0":
|
||||
if int(release) < 3:
|
||||
execute(f"UPDATE solver_parameter SET name='mage_implicitation' WHERE name='mage_implication'")
|
||||
execute(f"UPDATE solver_parameter SET name='mage_iteration_type' WHERE name='mage_iter_type'")
|
||||
|
||||
if int(release) < 4:
|
||||
solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
|
||||
|
||||
for solver in solvers:
|
||||
if solver != "mage8":
|
||||
continue
|
||||
|
||||
data = execute("SELECT ind, name, value " +
|
||||
"FROM solver_parameter "+
|
||||
f"WHERE solver = '{solver}' " +
|
||||
"ORDER BY ind ACS")
|
||||
|
||||
execute(
|
||||
"DELETE FROM solver_parameter " +
|
||||
f"WHERE solver = '{solver}'"
|
||||
)
|
||||
|
||||
insert = lambda name, value, ind: execute(
|
||||
"INSERT INTO " +
|
||||
"solver_parameter(ind, name, value, solver) "+
|
||||
"VALUES (" +
|
||||
f"{ind}, " +
|
||||
f"'{cls._sql_format(name)}', " +
|
||||
f"'{cls._sql_format(value)}', " +
|
||||
f"'{cls._sql_format(solver)}'" +
|
||||
")"
|
||||
)
|
||||
|
||||
ind = 0
|
||||
lst = []
|
||||
for v in data:
|
||||
insert(v[0], v[1], ind)
|
||||
ind += 1
|
||||
|
||||
new = [
|
||||
("mage_sediment_masse_volumique", "2650.0"),
|
||||
("mage_sediment_angle_repos", "40.0"),
|
||||
("mage_sediment_porosity", "0.40"),
|
||||
("mage_distance_han", "0.0"),
|
||||
("mage_distance_chargement_d50", "100.0"),
|
||||
("mage_distance_chargement_sigma", "100.0"),
|
||||
("mage_methode_modification_geometrie", "1"),
|
||||
("mage_shields_critique", "1"),
|
||||
("mage_shields_correction", "1"),
|
||||
("mage_capacite_solide", "1"),
|
||||
("mage_pas_de_temps_charriage", "1"),
|
||||
("mage_facteur_multiplicateur", "1.0"),
|
||||
]
|
||||
|
||||
for v in new:
|
||||
insert(v[0], v[1], ind)
|
||||
ind += 1
|
||||
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
status = data["status"]
|
||||
solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
|
||||
new = {}
|
||||
|
||||
for solver in solvers:
|
||||
solver = solver[0]
|
||||
if solver not in solver_type_list:
|
||||
continue
|
||||
|
||||
st = solver_type_list[solver]
|
||||
n = cls(solver_type = st, status = status)
|
||||
|
||||
table = execute(
|
||||
"SELECT ind, name, value " +
|
||||
"FROM solver_parameter " +
|
||||
f"WHERE solver = '{solver}'"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
value = row[2]
|
||||
|
||||
n.set_value(name, value)
|
||||
|
||||
new[solver] = n
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
t = self._solver._type
|
||||
execute(
|
||||
"DELETE FROM solver_parameter " +
|
||||
f"WHERE solver = '{t}'"
|
||||
)
|
||||
|
||||
ind = 0
|
||||
for param in self._lst:
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"solver_parameter(ind, name, value, solver) "+
|
||||
"VALUES (" +
|
||||
f"{ind}, " +
|
||||
f"'{self._sql_format(param.name)}', " +
|
||||
f"'{self._sql_format(param.value)}', " +
|
||||
f"'{self._sql_format(t)}'" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
return self._parameters.copy()
|
||||
return self.lst
|
||||
|
||||
def get(self, index):
|
||||
return self._parameters[index]
|
||||
return self._lst[index]
|
||||
|
||||
def get_by_key(self, key):
|
||||
try:
|
||||
return next(
|
||||
filter(
|
||||
lambda p: p["name"] == key,
|
||||
self._lst
|
||||
)
|
||||
)["value"]
|
||||
except:
|
||||
return None
|
||||
|
||||
def set(self, index, new):
|
||||
self._parameters[index] = new
|
||||
self._lst[index] = new
|
||||
self._status.modified()
|
||||
|
||||
def set_value(self, key, value):
|
||||
for p in self._lst:
|
||||
if p["name"] == key:
|
||||
p["value"] = value
|
||||
self._status.modified()
|
||||
return
|
||||
|
||||
self._lst.append(
|
||||
Parameter(
|
||||
name = key,
|
||||
value = value,
|
||||
status = self._status
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = Parameter(status = self._status)
|
||||
self._parameters.insert(index, n)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def insert(self, index, new):
|
||||
self._parameters.insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, parameters):
|
||||
for parameter in parameters:
|
||||
self._parameters.remove(parameter)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
parameters = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._parameters)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(parameters)
|
||||
|
||||
def sort(self, reverse=False, key=None):
|
||||
self._parameters.sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, index):
|
||||
if index < len(self._parameters):
|
||||
next = index - 1
|
||||
|
||||
l = self._parameters
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._parameters
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
@@ -1,18 +1,116 @@
|
||||
# Stricklers.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
class Stricklers(object):
|
||||
def __init__(self, status = None):
|
||||
super(Stricklers, self).__init__()
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
class Stricklers(SQLSubModel):
|
||||
_id_cnt = 0
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id:int = -1,
|
||||
name:str = "",
|
||||
comment:str = "",
|
||||
minor:float = 35.0,
|
||||
medium:float = 15.0,
|
||||
status = None):
|
||||
super(Stricklers, self).__init__()
|
||||
self._status = status
|
||||
|
||||
self._name = ""
|
||||
self._comment = ""
|
||||
if id == -1:
|
||||
self.id = Stricklers._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._minor = 35
|
||||
self._medium = 15
|
||||
Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
|
||||
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
|
||||
self._minor = minor
|
||||
self._medium = medium
|
||||
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE stricklers(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT,
|
||||
comment TEXT,
|
||||
minor REAL NOT NULL,
|
||||
medium REAL NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
stricklers = []
|
||||
status = data["status"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, comment, minor, medium " +
|
||||
"FROM stricklers"
|
||||
)
|
||||
|
||||
if table is None:
|
||||
return None
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
comment = row[2]
|
||||
minor = row[3]
|
||||
medium = row[4]
|
||||
|
||||
new = cls(
|
||||
id = id,
|
||||
name = name,
|
||||
comment = comment,
|
||||
minor = minor, medium = medium,
|
||||
status = status
|
||||
)
|
||||
|
||||
stricklers.append(new)
|
||||
|
||||
return stricklers
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"stricklers(id, name, comment, minor, medium) "+
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"'{self._sql_format(self.name)}', " +
|
||||
f"'{self._sql_format(self.comment)}', " +
|
||||
f"{float(self.minor)}, {float(self.medium)}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
if self._name != "":
|
||||
@@ -42,7 +140,7 @@ class Stricklers(object):
|
||||
|
||||
@minor.setter
|
||||
def minor(self, minor):
|
||||
self._minor = int(minor)
|
||||
self._minor = float(minor)
|
||||
|
||||
@property
|
||||
def medium(self):
|
||||
@@ -50,4 +148,4 @@ class Stricklers(object):
|
||||
|
||||
@medium.setter
|
||||
def medium(self, medium):
|
||||
self._medium = int(medium)
|
||||
self._medium = float(medium)
|
||||
@@ -1,69 +1,74 @@
|
||||
# StricklersList.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Stricklers.Stricklers import Stricklers
|
||||
|
||||
class StricklersList(object):
|
||||
def __init__(self, status = None):
|
||||
if status is None:
|
||||
status = SavedStatus()
|
||||
self._status = status
|
||||
class StricklersList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
Stricklers,
|
||||
]
|
||||
|
||||
self._stricks = []
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._stricks)
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
new = cls(status = data["status"])
|
||||
|
||||
new._lst = Stricklers._sql_load(
|
||||
execute,
|
||||
data = data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
execute("DELETE FROM stricklers")
|
||||
|
||||
objs = self._lst
|
||||
return self._save_submodel(execute, objs, data)
|
||||
|
||||
@property
|
||||
def stricklers(self):
|
||||
return self._stricks.copy()
|
||||
return self.lst
|
||||
|
||||
def get(self, index):
|
||||
if 0 <= index < len(self._stricks):
|
||||
return self._stricks[index]
|
||||
|
||||
return None
|
||||
|
||||
def insert(self, index, strick):
|
||||
self._stricks.insert(index, strick)
|
||||
self._status.modified()
|
||||
|
||||
def add(self, index):
|
||||
def new(self, index):
|
||||
s = Stricklers(status = self._status)
|
||||
self.insert(index, s)
|
||||
return s
|
||||
|
||||
def delete(self, stricks):
|
||||
self._stricks = list(
|
||||
filter(
|
||||
lambda s: s not in stricks,
|
||||
self._stricks
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
stricks = set(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self._stricks)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(stricks)
|
||||
|
||||
@timer
|
||||
def sort(self, reverse:bool = False, key=None):
|
||||
f = lambda st: st.name
|
||||
if key is not None:
|
||||
f = key
|
||||
|
||||
self._stricks = sorted(
|
||||
self._stricks,
|
||||
self._lst = sorted(
|
||||
self._lst,
|
||||
key = f,
|
||||
reverse = reverse,
|
||||
)
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
# Study.py -- Pamhyr Study class
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from tools import timer, timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLModel
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Serializable import Serializable
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.River import River
|
||||
|
||||
from Checker.Study import *
|
||||
|
||||
class Study(Serializable):
|
||||
def __init__(self):
|
||||
# Serialization information
|
||||
super(Study, self).__init__("")
|
||||
self.filename = ""
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Study(SQLModel):
|
||||
_sub_classes = [
|
||||
River,
|
||||
]
|
||||
|
||||
def __init__(self, filename = None, init_new = True):
|
||||
# Metadata
|
||||
self._version = "0.0.4"
|
||||
self.creation_date = datetime.now()
|
||||
self.last_modification_date = datetime.now()
|
||||
self.last_save_date = datetime.now()
|
||||
|
||||
self._filename = filename
|
||||
super(Study, self).__init__(filename = filename)
|
||||
|
||||
self.status = SavedStatus()
|
||||
|
||||
@@ -25,12 +57,13 @@ class Study(Serializable):
|
||||
self._time_system = "time"
|
||||
self._date = datetime.fromtimestamp(0)
|
||||
|
||||
self.creation_date = datetime.now()
|
||||
self.last_modification_date = datetime.now()
|
||||
self.last_save_date = datetime.now()
|
||||
if init_new:
|
||||
# Study data
|
||||
self._river = River(status = self.status)
|
||||
else:
|
||||
self._init_db_file(filename, is_new = False)
|
||||
|
||||
# Study data
|
||||
self._river = River(status = self.status)
|
||||
self._old_save_id = 0
|
||||
|
||||
@classmethod
|
||||
def checkers(cls):
|
||||
@@ -53,9 +86,31 @@ class Study(Serializable):
|
||||
return self.status.is_saved()
|
||||
|
||||
def save(self):
|
||||
# Save a copy of database
|
||||
fdir, fname = os.path.split(self.filename)
|
||||
|
||||
if self._old_save_id == 0:
|
||||
old_dir = os.path.join(fdir, "_PAMHYR_", "__old__")
|
||||
if os.name == "nt":
|
||||
old_dir = old_dir.replace("/", "\\")
|
||||
|
||||
try:
|
||||
os.makedirs(old_dir)
|
||||
except FileExistsError as e:
|
||||
shutil.rmtree(old_dir)
|
||||
os.makedirs(old_dir)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
fname = fname + "." + str(self._old_save_id)
|
||||
filename = os.path.join(fdir, "_PAMHYR_", "__old__", fname)
|
||||
shutil.copy(self.filename, filename)
|
||||
self._old_save_id += 1
|
||||
|
||||
# Save
|
||||
self.last_save_date = datetime.now()
|
||||
self.status.save()
|
||||
self._save()
|
||||
self.status.save()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -66,6 +121,16 @@ class Study(Serializable):
|
||||
self._name = str(name)
|
||||
self.status.modified()
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._filename
|
||||
|
||||
@filename.setter
|
||||
def filename(self, filename):
|
||||
self._filename = str(filename)
|
||||
self._init_db_file(filename, is_new = True)
|
||||
self.status.modified()
|
||||
|
||||
@property
|
||||
def time_system(self):
|
||||
return self._time_system
|
||||
@@ -88,9 +153,9 @@ class Study(Serializable):
|
||||
self._date = timestamp
|
||||
self.status.modified()
|
||||
|
||||
@classmethod
|
||||
def new(cls):
|
||||
return cls()
|
||||
# @classmethod
|
||||
# def new(cls):
|
||||
# return cls()
|
||||
|
||||
@classmethod
|
||||
def new(cls, name, description, date = None):
|
||||
@@ -101,3 +166,97 @@ class Study(Serializable):
|
||||
me.use_date()
|
||||
me.date = date
|
||||
return me
|
||||
|
||||
@classmethod
|
||||
def open(cls, filename):
|
||||
me = cls._load(filename)
|
||||
return me
|
||||
|
||||
#######
|
||||
# SQL #
|
||||
#######
|
||||
|
||||
def _create(self):
|
||||
# Info (metadata)
|
||||
self.execute("CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)")
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('version', '{self._sql_format(self._version)}')",
|
||||
commit = True
|
||||
)
|
||||
self.execute("INSERT INTO info VALUES ('name', '')")
|
||||
self.execute("INSERT INTO info VALUES ('description', '')")
|
||||
self.execute(f"INSERT INTO info VALUES ('time_system', '{self._time_system}')")
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('date', " +
|
||||
"'{self._date.timestamp()}')"
|
||||
)
|
||||
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('creation_date', " +
|
||||
"'{self.creation_time.timestamp()}')"
|
||||
)
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('last_save_date', " +
|
||||
"'{self.last_save_time.timestamp()}')"
|
||||
)
|
||||
|
||||
self._create_submodel()
|
||||
self.commit()
|
||||
|
||||
def _update(self):
|
||||
version = self.execute(f"SELECT value FROM info WHERE key='version'")
|
||||
|
||||
logger.debug(f"{version[0]} == {self._version}")
|
||||
if version[0] == self._version:
|
||||
return True
|
||||
|
||||
logger.info("Update database")
|
||||
|
||||
if self._update_submodel(version[0]):
|
||||
self.execute(f"UPDATE info SET value='{self._version}' WHERE key='version'")
|
||||
return True
|
||||
|
||||
logger.info("TODO: update failed")
|
||||
raise NotImplementedMethodeError(self, self._update)
|
||||
|
||||
@classmethod
|
||||
def _load(cls, filename):
|
||||
new = cls(init_new = False, filename = filename)
|
||||
|
||||
# TODO: Load metadata
|
||||
new.name = new.execute("SELECT value FROM info WHERE key='name'")[0]
|
||||
new.description = new.execute("SELECT value FROM info WHERE key='description'")[0]
|
||||
new._time_system = new.execute("SELECT value FROM info WHERE key='time_system'")[0]
|
||||
new._date = datetime.fromtimestamp(
|
||||
float(new.execute("SELECT value FROM info WHERE key='date'")[0])
|
||||
)
|
||||
|
||||
new.creation_date = datetime.fromtimestamp(
|
||||
float(new.execute("SELECT value FROM info WHERE key='creation_date'")[0])
|
||||
)
|
||||
new.last_save_date = datetime.fromtimestamp(
|
||||
float(new.execute("SELECT value FROM info WHERE key='last_save_date'")[0])
|
||||
)
|
||||
|
||||
# Load river data
|
||||
new._river = River._sql_load(
|
||||
lambda sql: new.execute(
|
||||
sql,
|
||||
fetch_one = False,
|
||||
commit = True
|
||||
),
|
||||
data = {"status": new.status}
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _save(self):
|
||||
self.execute(f"UPDATE info SET value='{self._sql_format(self.name)}' WHERE key='name'")
|
||||
self.execute(f"UPDATE info SET value='{self._sql_format(self.description)}' WHERE key='description'")
|
||||
self.execute(f"UPDATE info SET value='{self._time_system}' WHERE key='time_system'")
|
||||
self.execute(f"UPDATE info SET value='{timestamp(self._date)}' WHERE key='date'")
|
||||
self.execute(f"UPDATE info SET value='{timestamp(self.creation_date)}' WHERE key='creation_date'")
|
||||
self.execute(f"UPDATE info SET value='{timestamp(self.last_save_date)}' WHERE key='last_save_date'")
|
||||
|
||||
self._save_submodel([self._river])
|
||||
self.commit()
|
||||
@@ -0,0 +1,184 @@
|
||||
# PamhyrDB.py -- Pamhyr abstract model database classes
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tools import SQL
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
# Top level model class
|
||||
class SQLModel(SQL):
|
||||
_sub_classes = []
|
||||
|
||||
def _init_db_file(self, db, is_new = True):
|
||||
exists = Path(db).exists()
|
||||
|
||||
if exists and is_new:
|
||||
os.remove(db)
|
||||
|
||||
self._db = sqlite3.connect(db)
|
||||
self._cur = self._db.cursor()
|
||||
|
||||
if is_new:
|
||||
logger.info("Create database")
|
||||
self._create() # Create db
|
||||
# self._save() # Save
|
||||
else:
|
||||
self._update() # Update db scheme if necessary
|
||||
# self._load() # Load data
|
||||
|
||||
|
||||
def __init__(self, filename = None):
|
||||
self._db = None
|
||||
|
||||
def _create_submodel(self):
|
||||
fn = lambda sql: self.execute(
|
||||
sql,
|
||||
fetch_one = False,
|
||||
commit = False
|
||||
)
|
||||
|
||||
for cls in self._sub_classes:
|
||||
requests = cls._sql_create(fn)
|
||||
|
||||
self.commit()
|
||||
return True
|
||||
|
||||
def _create(self):
|
||||
raise NotImplementedMethodeError(self, self._create)
|
||||
|
||||
def _update_submodel(self, version):
|
||||
fn = lambda sql: self.execute(
|
||||
sql,
|
||||
fetch_one = False,
|
||||
commit = False
|
||||
)
|
||||
|
||||
ok = True
|
||||
for cls in self._sub_classes:
|
||||
ok &= cls._sql_update(fn, version)
|
||||
|
||||
self.commit()
|
||||
return ok
|
||||
|
||||
def _update(self):
|
||||
raise NotImplementedMethodeError(self, self._update)
|
||||
|
||||
def _save_submodel(self, objs, data = None):
|
||||
fn = lambda sql: self.execute(
|
||||
sql,
|
||||
fetch_one = False,
|
||||
commit = False
|
||||
)
|
||||
|
||||
ok = True
|
||||
for obj in objs:
|
||||
ok &= obj._sql_save(fn)
|
||||
|
||||
self.commit()
|
||||
return ok
|
||||
|
||||
def _save(self):
|
||||
raise NotImplementedMethodeError(self, self._save)
|
||||
|
||||
@classmethod
|
||||
def _load(cls, filename = None):
|
||||
raise NotImplementedMethodeError(cls, cls._load)
|
||||
|
||||
# Sub model class
|
||||
class SQLSubModel(object):
|
||||
_sub_classes = []
|
||||
|
||||
def _sql_format(self, value):
|
||||
# Replace ''' by ''' to preserve SQL injection
|
||||
if type(value) == str:
|
||||
value = value.replace("'", "'")
|
||||
elif type(value) == bool:
|
||||
value = 'TRUE' if value else 'FALSE'
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _create_submodel(cls, execute):
|
||||
for sc in cls._sub_classes:
|
||||
sc._sql_create(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
"""Create data base scheme
|
||||
|
||||
Args:
|
||||
execute: Function to exec SQL resquest
|
||||
|
||||
Returns:
|
||||
Return true, otherelse false if an issue appear
|
||||
"""
|
||||
raise NotImplementedMethodeError(cls, cls._sql_create)
|
||||
|
||||
@classmethod
|
||||
def _update_submodel(cls, execute, version):
|
||||
for sc in cls._sub_classes:
|
||||
sc._sql_update(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
"""Update data base scheme
|
||||
|
||||
Args:
|
||||
execute: Function to exec SQL resquest
|
||||
version: Current database version
|
||||
|
||||
Returns:
|
||||
Return true, otherelse false if an issue appear
|
||||
"""
|
||||
raise NotImplementedMethodeError(cls, cls._sql_update)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
"""Load instance of this class from SQL data base
|
||||
|
||||
Args:
|
||||
execute: Function to exec SQL request
|
||||
data: Optional data for the class constructor
|
||||
|
||||
Returns:
|
||||
Return new instance of class
|
||||
"""
|
||||
raise NotImplementedMethodeError(cls, cls._sql_load)
|
||||
|
||||
def _save_submodel(self, execute, objs, data = None):
|
||||
for o in objs:
|
||||
o._sql_save(execute, data = data)
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
"""Save class data to data base
|
||||
|
||||
Args:
|
||||
execute: Function to exec SQL resquest
|
||||
data: Optional additional information for save
|
||||
|
||||
Returns:
|
||||
Return true, otherelse false if an issue appear during
|
||||
save
|
||||
"""
|
||||
raise NotImplementedMethodeError(self, self._sql_save)
|
||||
@@ -0,0 +1,74 @@
|
||||
# PamhyrDict.py -- Pamhyr abstract dict for model classes
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class PamhyrModelDict(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, status = None):
|
||||
super(PamhyrModelDict, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
self._dict = {}
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
raise NotImplementedMethodeError(cls, cls._sql_load)
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
raise NotImplementedMethodeError(self, self._sql_save)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
|
||||
def is_defined(self, key):
|
||||
return key in self._dict
|
||||
|
||||
def set(self, key, new):
|
||||
self._dict[key] = new
|
||||
self._status.modified()
|
||||
|
||||
def get(self, key):
|
||||
if key in self._dict:
|
||||
return self._dict[key]
|
||||
|
||||
new = self.new(key)
|
||||
self.set(key, new)
|
||||
return new
|
||||
|
||||
def new(self, key):
|
||||
raise NotImplementedMethodeError(self, self.new)
|
||||
@@ -0,0 +1,229 @@
|
||||
# PamhyrList.py -- Pamhyr Abstract List object for the Model
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class PamhyrModelList(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
|
||||
def __init__(self, status = None):
|
||||
super(PamhyrModelList, self).__init__()
|
||||
|
||||
self._status = status
|
||||
self._lst = []
|
||||
|
||||
#######
|
||||
# SQL #
|
||||
#######
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
raise NotImplementedMethodeError(cls, cls._sql_load)
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
raise NotImplementedMethodeError(self, self._sql_save)
|
||||
|
||||
################
|
||||
# MODEL METHOD #
|
||||
################
|
||||
|
||||
def __len__(self):
|
||||
return len(self._lst)
|
||||
|
||||
@property
|
||||
def lst(self):
|
||||
return self._lst.copy()
|
||||
|
||||
def get(self, row):
|
||||
return self._lst[row]
|
||||
|
||||
def set(self, row, new):
|
||||
self._lst[row] = new
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
"""Create new elements and add it to list
|
||||
|
||||
Args:
|
||||
index: The index of new elements
|
||||
|
||||
Returns:
|
||||
The new elements
|
||||
"""
|
||||
raise NotImplementedMethodeError(self, self.new)
|
||||
|
||||
def insert(self, index, new):
|
||||
self._lst.insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, lst):
|
||||
for el in lst:
|
||||
self._lst.remove(el)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
lst = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._lst)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(lst)
|
||||
|
||||
def sort(self, reverse=False, key=None):
|
||||
self._lst.sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, index):
|
||||
if index < len(self._lst):
|
||||
next = index - 1
|
||||
|
||||
l = self._lst
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._lst
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
|
||||
|
||||
class PamhyrModelListWithTab(SQLSubModel):
|
||||
_tabs_list = []
|
||||
_sub_classes = [
|
||||
]
|
||||
|
||||
def __init__(self, status = None):
|
||||
super(PamhyrModelListWithTab, self).__init__()
|
||||
|
||||
self._status = status
|
||||
self._tabs = {}
|
||||
for tab in self._tabs_list:
|
||||
self._tabs[tab] = []
|
||||
|
||||
#######
|
||||
# SQL #
|
||||
#######
|
||||
|
||||
@classmethod
|
||||
def _sql_create(cls, execute):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _sql_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
|
||||
@classmethod
|
||||
def _sql_load(cls, execute, data = None):
|
||||
raise NotImplementedMethodeError(cls, cls._sql_load)
|
||||
|
||||
def _sql_save(self, execute, data = None):
|
||||
raise NotImplementedMethodeError(self, self._sql_save)
|
||||
|
||||
################
|
||||
# MODEL METHOD #
|
||||
################
|
||||
|
||||
def len(self, lst):
|
||||
return len(self._tabs[lst])
|
||||
|
||||
def get_tab(self, lst):
|
||||
return self._tabs[lst].copy()
|
||||
|
||||
def get(self, lst, row):
|
||||
return self._tabs[lst][row]
|
||||
|
||||
def set(self, lst, row, new):
|
||||
self._tabs[lst][row] = new
|
||||
self._status.modified()
|
||||
|
||||
def new(self, lst, index):
|
||||
"""Create new elements and add it to list
|
||||
|
||||
Args:
|
||||
lst: The tab name
|
||||
index: The index of new elements
|
||||
|
||||
Returns:
|
||||
The new elements
|
||||
"""
|
||||
raise NotImplementedMethodeError(self, self.new)
|
||||
|
||||
def insert(self, lst, index, new):
|
||||
self._tabs[lst].insert(index, new)
|
||||
self._status.modified()
|
||||
|
||||
def delete(self, lst, els):
|
||||
for el in els:
|
||||
self._tabs[lst].remove(el)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, lst, indexes):
|
||||
els = list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(self._tabs[lst])
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(lst, els)
|
||||
|
||||
def sort(self, lst, reverse=False, key=None):
|
||||
self._tabs[lst].sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def move_up(self, lst, index):
|
||||
if index < len(self._tabs[lst]):
|
||||
next = index - 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[next] = l[next], l[index]
|
||||
self._status.modified()
|
||||
|
||||
def move_down(self, lst, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
|
||||
l = self._tabs[lst]
|
||||
l[index], l[prev] = l[prev], l[index]
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,36 @@
|
||||
# AScript.py -- Pamhyr abstract script class
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
class AScript(object):
|
||||
name = ""
|
||||
description = ""
|
||||
|
||||
def __init__(self, app, conf, args):
|
||||
super(AScript, self).__init__()
|
||||
|
||||
self._app = app
|
||||
self._conf = conf
|
||||
self._args = args.copy()
|
||||
|
||||
def usage(self):
|
||||
raise NotImplementedMethodeError(self, self.usage)
|
||||
|
||||
def run(self):
|
||||
raise NotImplementedMethodeError(self, self.run)
|
||||
@@ -0,0 +1,34 @@
|
||||
# help.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from Scripts.AScript import AScript
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class ScriptHello(AScript):
|
||||
name = "Hello"
|
||||
description = "Display hello"
|
||||
|
||||
def usage(self):
|
||||
logger.info(f"Usage : {self._args[0]} hello")
|
||||
|
||||
def run(self):
|
||||
logger.info("Hello from Pamhyr2 script !")
|
||||
return 0
|
||||
@@ -1,3 +1,19 @@
|
||||
# plot_3DST.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# a lancer depuis src
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
# ASolver.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from tools import timer
|
||||
|
||||
try:
|
||||
# Installation allow Unix-like signal
|
||||
from signal import SIGTERM, SIGSTOP, SIGCONT
|
||||
_signal = True
|
||||
except:
|
||||
@@ -12,6 +32,11 @@ from enum import Enum
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.Results.Results import Results
|
||||
from Model.Results.River.River import River, Reach, Profile
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class STATUS(Enum):
|
||||
NOT_LAUNCHED = -1
|
||||
STOPED = 0
|
||||
@@ -19,6 +44,8 @@ class STATUS(Enum):
|
||||
PAUSED = 5
|
||||
|
||||
class AbstractSolver(object):
|
||||
_type = ""
|
||||
|
||||
def __init__(self, name):
|
||||
super(AbstractSolver, self).__init__()
|
||||
|
||||
@@ -41,6 +68,9 @@ class AbstractSolver(object):
|
||||
self._process = None
|
||||
self._output = None
|
||||
|
||||
# Last study running
|
||||
self._study = None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self._name} : {self._type} : {self._description}"
|
||||
|
||||
@@ -60,6 +90,7 @@ class AbstractSolver(object):
|
||||
("all_init_time", "000:00:00:00"),
|
||||
("all_final_time", "999:99:00:00"),
|
||||
("all_timestep", "300.0"),
|
||||
("all_command_line_arguments", ""),
|
||||
]
|
||||
|
||||
return lst
|
||||
@@ -127,6 +158,17 @@ class AbstractSolver(object):
|
||||
def export(self, study, repertory, qlog = None):
|
||||
raise NotImplementedMethodeError(self, self.export)
|
||||
|
||||
def cmd_args(self, study):
|
||||
"""Return solver command line arguments list
|
||||
|
||||
Returns:
|
||||
Command line arguments list
|
||||
"""
|
||||
params = study.river.get_params(self.type)
|
||||
args = params.get_by_key("all_command_line_arguments")
|
||||
|
||||
return args.split(" ")
|
||||
|
||||
def input_param(self):
|
||||
"""Return input command line parameter(s)
|
||||
|
||||
@@ -143,25 +185,82 @@ class AbstractSolver(object):
|
||||
"""
|
||||
raise NotImplementedMethodeError(self, self.log_file)
|
||||
|
||||
###########
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
@timer
|
||||
def results(self, study, repertory, qlog = None):
|
||||
results = Results(study = study)
|
||||
return results
|
||||
|
||||
#######
|
||||
# Run #
|
||||
#######
|
||||
|
||||
def run_input_data_fomater(self):
|
||||
def _install_dir(self):
|
||||
return os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..", ".."
|
||||
)
|
||||
)
|
||||
|
||||
def _format_command(self, study, cmd, path = ""):
|
||||
"""Format command line
|
||||
|
||||
Args:
|
||||
cmd: The command line
|
||||
path: Optional path string (replace @path in cmd)
|
||||
|
||||
Returns:
|
||||
The executable and list of arguments
|
||||
"""
|
||||
# HACK: Works in most case... Trust me i'm an engineer
|
||||
cmd = cmd.replace("@install_dir", self._install_dir())
|
||||
cmd = cmd.replace("@path", path.replace(" ", "\ "))
|
||||
cmd = cmd.replace("@input", self.input_param())
|
||||
cmd = cmd.replace("@dir", self._process.workingDirectory())
|
||||
cmd = cmd.replace("@args", " ".join(self.cmd_args(study)))
|
||||
|
||||
logger.debug(f"! {cmd}")
|
||||
|
||||
if cmd[0] == "\"":
|
||||
# Command line executable path is between " char
|
||||
cmd = cmd.split("\"")
|
||||
exe = cmd[1].replace("\ ", " ")
|
||||
args = list(
|
||||
filter(
|
||||
lambda s: s != "",
|
||||
"\"".join(cmd[2:]).split(" ")[1:]
|
||||
)
|
||||
)
|
||||
else:
|
||||
# We suppose the command line executable path as no space char
|
||||
cmd = cmd.replace("\ ", "&_&").split(" ")
|
||||
exe = cmd[0].replace("&_&", " ")
|
||||
args = list(
|
||||
filter(
|
||||
lambda s: s != "",
|
||||
map(lambda s: s.replace("&_&", "\ "), cmd[1:])
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(f"! {exe} {args}")
|
||||
return exe, args
|
||||
|
||||
def run_input_data_fomater(self, study):
|
||||
if self._cmd_input == "":
|
||||
self._run_next()
|
||||
self._run_next(study)
|
||||
return True
|
||||
|
||||
cmd = self._cmd_input
|
||||
cmd = cmd.replace("@path", self._path_input)
|
||||
cmd = cmd.replace("@input", self.input_param())
|
||||
cmd = cmd.replace("@dir", self._process.workingDirectory())
|
||||
exe, args = self._format_command(study, cmd, self._path_input)
|
||||
|
||||
print(f"! {cmd}")
|
||||
|
||||
cmd = cmd.split()
|
||||
exe = cmd[0]
|
||||
args = cmd[1:]
|
||||
if not os.path.exists(exe):
|
||||
error = f"[ERROR] Path {exe} do not exists"
|
||||
logger.info(error)
|
||||
return error
|
||||
|
||||
self._process.start(
|
||||
exe, args,
|
||||
@@ -169,22 +268,18 @@ class AbstractSolver(object):
|
||||
|
||||
return True
|
||||
|
||||
def run_solver(self):
|
||||
print("run solver")
|
||||
def run_solver(self, study):
|
||||
if self._cmd_solver == "":
|
||||
self._run_next()
|
||||
self._run_next(study)
|
||||
return True
|
||||
|
||||
cmd = self._cmd_solver
|
||||
cmd = cmd.replace("@path", self._path_solver)
|
||||
cmd = cmd.replace("@input", self.input_param())
|
||||
cmd = cmd.replace("@dir", self._process.workingDirectory())
|
||||
exe, args = self._format_command(study, cmd, self._path_solver)
|
||||
|
||||
print(f"! {cmd}")
|
||||
|
||||
cmd = cmd.split()
|
||||
exe = cmd[0]
|
||||
args = cmd[1:]
|
||||
if not os.path.exists(exe):
|
||||
error = f"[ERROR] Path {exe} do not exists"
|
||||
logger.info(error)
|
||||
return error
|
||||
|
||||
self._process.start(
|
||||
exe, args,
|
||||
@@ -193,21 +288,18 @@ class AbstractSolver(object):
|
||||
self._status = STATUS.RUNNING
|
||||
return True
|
||||
|
||||
def run_output_data_fomater(self):
|
||||
def run_output_data_fomater(self, study):
|
||||
if self._cmd_output == "":
|
||||
self._run_next()
|
||||
self._run_next(study)
|
||||
return True
|
||||
|
||||
cmd = self._cmd_output
|
||||
cmd = cmd.replace("@path", self._path_output)
|
||||
cmd = cmd.replace("@input", self.input_param())
|
||||
cmd = cmd.replace("@dir", self._process.workingDirectory())
|
||||
exe, args = self._format_command(study, cmd, self._path_output)
|
||||
|
||||
print(f"! {cmd}")
|
||||
|
||||
cmd = cmd.split()
|
||||
exe = cmd[0]
|
||||
args = cmd[1:]
|
||||
if not os.path.exists(exe):
|
||||
error = f"[ERROR] Path {exe} do not exists"
|
||||
logger.info(error)
|
||||
return error
|
||||
|
||||
self._process.start(
|
||||
exe, args,
|
||||
@@ -221,27 +313,29 @@ class AbstractSolver(object):
|
||||
for x in s.split('\n'):
|
||||
self._output.put(x)
|
||||
|
||||
def _run_next(self):
|
||||
def _run_next(self, study):
|
||||
self._step += 1
|
||||
if self._step < len(self._runs):
|
||||
self._runs[self._step]()
|
||||
res = self._runs[self._step](study)
|
||||
if res is not True:
|
||||
self._output.put(res)
|
||||
else:
|
||||
self._status = STATUS.STOPED
|
||||
|
||||
def _finished(self, exit_code, exit_status):
|
||||
def _finished(self, study, exit_code, exit_status):
|
||||
if self._output is not None:
|
||||
self._output.put(exit_code)
|
||||
|
||||
self._run_next()
|
||||
self._run_next(study)
|
||||
|
||||
def run(self, process = None, output_queue = None):
|
||||
def run(self, study, process = None, output_queue = None):
|
||||
if process is not None:
|
||||
self._process = process
|
||||
if output_queue is not None:
|
||||
self._output = output_queue
|
||||
|
||||
self._process.readyRead.connect(self._data_ready)
|
||||
self._process.finished.connect(self._finished)
|
||||
self._process.finished.connect(lambda c, s: self._finished(study, c, s))
|
||||
|
||||
self._runs = [
|
||||
self.run_input_data_fomater,
|
||||
@@ -250,7 +344,9 @@ class AbstractSolver(object):
|
||||
]
|
||||
self._step = 0
|
||||
# Run first step
|
||||
self._runs[0]()
|
||||
res = self._runs[0](study)
|
||||
if res is not True:
|
||||
self._output.put(res)
|
||||
|
||||
def kill(self):
|
||||
if self._process is None:
|
||||
@@ -260,14 +356,15 @@ class AbstractSolver(object):
|
||||
self._status = STATUS.STOPED
|
||||
return True
|
||||
|
||||
def start(self, process = None):
|
||||
def start(self, study, process = None):
|
||||
if _signal:
|
||||
# Solver is PAUSED, so continue execution
|
||||
if self._status == STATUS.PAUSED:
|
||||
os.kill(self._process.pid(), SIGCONT)
|
||||
self._status = STATUS.RUNNING
|
||||
return True
|
||||
|
||||
self.run(process)
|
||||
self.run(study, process)
|
||||
return True
|
||||
|
||||
def pause(self):
|
||||
@@ -275,6 +372,7 @@ class AbstractSolver(object):
|
||||
if self._process is None:
|
||||
return False
|
||||
|
||||
# Send SIGSTOP to PAUSED solver
|
||||
os.kill(self._process.pid(), SIGSTOP)
|
||||
self._status = STATUS.PAUSED
|
||||
return True
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# GenericSolver.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Solver.ASolver import (
|
||||
@@ -5,6 +21,8 @@ from Solver.ASolver import (
|
||||
)
|
||||
|
||||
class GenericSolver(AbstractSolver):
|
||||
_type = "generic"
|
||||
|
||||
def __init__(self, name):
|
||||
super(GenericSolver, self).__init__(name)
|
||||
|
||||
|
||||
@@ -1,13 +1,47 @@
|
||||
# Mage.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from tools import timer
|
||||
|
||||
from Solver.ASolver import AbstractSolver
|
||||
from Checker.Mage import MageNetworkGraphChecker
|
||||
|
||||
from Model.Results.Results import Results
|
||||
from Model.Results.River.River import River, Reach, Profile
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
def mage_file_open(filepath, mode):
|
||||
f = open(filepath, mode)
|
||||
|
||||
if "w" in mode:
|
||||
# Write header
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
|
||||
return f
|
||||
|
||||
class Mage(AbstractSolver):
|
||||
_type = "mage"
|
||||
|
||||
def __init__(self, name):
|
||||
super(Mage, self).__init__(name)
|
||||
|
||||
@@ -27,11 +61,11 @@ class Mage(AbstractSolver):
|
||||
("mage_timestep_tra", "3600"),
|
||||
("mage_timestep_bin", "0"),
|
||||
# ("mage_timestep_melissa", "0"),
|
||||
("mage_implication", "0.70"),
|
||||
("mage_implicitation", "0.70"),
|
||||
("mage_continuity_discretization", "S"),
|
||||
("mage_qsj_discretization", "B"),
|
||||
("mage_stop_criterion_iterations", "R"),
|
||||
("mage_iter_type", "0"),
|
||||
("mage_iteration_type", "0"),
|
||||
("mage_smooth_coef", "0"),
|
||||
("mage_cfl_max", "-1."),
|
||||
("mage_min_height", "0.1"),
|
||||
@@ -63,19 +97,31 @@ class Mage(AbstractSolver):
|
||||
# Export #
|
||||
##########
|
||||
|
||||
def cmd_args(self, study):
|
||||
l = super(Mage, self).cmd_args(study)
|
||||
|
||||
l.append("-r")
|
||||
|
||||
return l
|
||||
|
||||
def input_param(self):
|
||||
return "0.REP"
|
||||
name = self._study.name
|
||||
return f"{name}.REP"
|
||||
|
||||
def log_file(self):
|
||||
return "0.TRA"
|
||||
name = self._study.name
|
||||
return f"{name}.TRA"
|
||||
|
||||
@timer
|
||||
def _export_ST(self, study, repertory, qlog):
|
||||
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)
|
||||
gra_file = f"{name}.GRA"
|
||||
|
||||
# Write header
|
||||
edges = study.river.edges()
|
||||
edges = list(
|
||||
@@ -90,87 +136,123 @@ class Mage(AbstractSolver):
|
||||
if edge._name == "":
|
||||
name = f"Reach_{edge.id}"
|
||||
|
||||
with open(os.path.join(repertory, f"{name}.ST"), "w+") as f:
|
||||
files.append(f"{name}.ST")
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
with mage_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:
|
||||
num = f"{profile.num:>6}"
|
||||
num = f"{cnt_num:>6}"
|
||||
c1 = f"{profile.code1:>6}"
|
||||
c2 = f"{profile.code2:>6}"
|
||||
t = f"{len(profile.points):>6}"
|
||||
kp = f"{profile.kp:>13.4f}"
|
||||
name = profile.name
|
||||
pname = profile.name
|
||||
if profile.name == "":
|
||||
pname = f"p{profile.id:>3}".replace(" ", "p")
|
||||
name = f"{pname}"
|
||||
|
||||
f.write(f"{num}{c1}{c2}{t}{kp} {name}\n")
|
||||
sediment = ""
|
||||
if profile.sl is not None:
|
||||
if not any(filter(lambda f: ".GRA" in f, files)):
|
||||
files.append(gra_file)
|
||||
|
||||
nl = len(profile.sl)
|
||||
sediment = f" {nl:>3}"
|
||||
for l in profile.sl.layers:
|
||||
sediment += f" {l.height:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}"
|
||||
|
||||
f.write(f"{num}{c1}{c2}{t}{kp} {name} {sediment}\n")
|
||||
cnt_num += 1
|
||||
|
||||
for point in profile.points:
|
||||
x = f"{point.x:>13.4f}"
|
||||
y = f"{point.y:>13.4f}"
|
||||
z = f"{point.z:>13.4f}"
|
||||
n = point.name
|
||||
n = f"{point.name:<3}"
|
||||
|
||||
f.write(f"{x}{y}{z} {n}\n")
|
||||
sediment = ""
|
||||
prev = point.z
|
||||
if point.sl is not None:
|
||||
nl = len(point.sl)
|
||||
sediment = f"{nl:>3}"
|
||||
for l in point.sl.layers:
|
||||
prev = round(prev - l.height, 5)
|
||||
sediment += f" {prev:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}"
|
||||
|
||||
f.write(f"{x}{y}{z} {n} {sediment}\n")
|
||||
|
||||
f.write(f" 999.9990 999.9990 999.9990\n")
|
||||
|
||||
return files
|
||||
|
||||
@timer
|
||||
def _export_BC(self, bound, repertory, qlog):
|
||||
def _export_BC(self, t, bounds, repertory, qlog, name="0"):
|
||||
files = []
|
||||
|
||||
t = "XX"
|
||||
if bound.bctype == "ZD":
|
||||
t = "AVA"
|
||||
elif bound.bctype == "TD":
|
||||
t = "HYD"
|
||||
elif bound.bctype == "TZ":
|
||||
t = "LIM"
|
||||
else:
|
||||
return None
|
||||
if len(bounds) == 0:
|
||||
return files
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put(f"Export {t} file")
|
||||
|
||||
with open(os.path.join(repertory, f"0.{t}"), "w+") as f:
|
||||
files.append(f"0.{t}")
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.{t}"), "w+") as f:
|
||||
files.append(f"{name}.{t}")
|
||||
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
for bound in bounds:
|
||||
name = f"{bound.node.id:3}".replace(" ", "x")
|
||||
f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n")
|
||||
f.write(f"${name}\n")
|
||||
header = bound.header
|
||||
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
|
||||
|
||||
name = f"{bound.node.id:3}".replace(" ", "x")
|
||||
f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n")
|
||||
f.write(f"${name}\n")
|
||||
header = bound.header
|
||||
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
|
||||
for d in bound.data:
|
||||
v0 = d[0]
|
||||
v1 = d[1]
|
||||
|
||||
for d in bound.data:
|
||||
f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
|
||||
if t in ["HYD", "QSO", "LIM"]:
|
||||
v0 /= 60 # Convert first column to minute
|
||||
|
||||
f.write(f"{v0:10}{v1:10}\n")
|
||||
|
||||
return files
|
||||
|
||||
@timer
|
||||
def _export_bound_cond(self, study, repertory, qlog):
|
||||
def _export_bound_cond(self, study, repertory, qlog, name="0"):
|
||||
files = []
|
||||
lst = study.river.boundary_condition
|
||||
|
||||
AVA = []
|
||||
HYD = []
|
||||
LIM = []
|
||||
QSO = []
|
||||
|
||||
for tab in ["liquid", "solid", "suspenssion"]:
|
||||
for bound in lst.get_tab(tab):
|
||||
files = files + self._export_BC(bound, repertory, qlog)
|
||||
if bound.bctype == "ZD":
|
||||
AVA.append(bound)
|
||||
elif bound.bctype == "TD" or bound.bctype == "PC":
|
||||
HYD.append(bound)
|
||||
elif bound.bctype == "TZ":
|
||||
LIM.append(bound)
|
||||
elif bound.bctype == "SL":
|
||||
QSO.append(bound)
|
||||
|
||||
files = files + self._export_BC("AVA", AVA, repertory, qlog, name=name)
|
||||
files = files + self._export_BC("HYD", HYD, repertory, qlog, name=name)
|
||||
files = files + self._export_BC("LIM", LIM, repertory, qlog, name=name)
|
||||
files = files + self._export_QSO(QSO, repertory, qlog, name=name)
|
||||
|
||||
return files
|
||||
|
||||
# @timer
|
||||
# def _export_LC(self, lateral, repertory, qlog):
|
||||
# def _export_LC(self, lateral, repertory, qlog, name="0"):
|
||||
# files = []
|
||||
|
||||
# if qlog is not None:
|
||||
# qlog.put(f"Export LAT file")
|
||||
|
||||
# with open(os.path.join(repertory, f"0.LAT"), "w+") as f:
|
||||
# files.append(f"0.LAT")
|
||||
|
||||
# f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
# with mage_file_open(os.path.join(repertory, f"{name}.LAT"), "w+") as f:
|
||||
# files.append(f"{name}.LAT")
|
||||
|
||||
# name = f"{lateral.node.id:3}".replace(" ", "x")
|
||||
# f.write(f"* {lateral.node.name} ({name}) {lateral.bctype}\n")
|
||||
@@ -179,12 +261,12 @@ class Mage(AbstractSolver):
|
||||
# f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
|
||||
|
||||
# for d in lateral.data:
|
||||
# f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
|
||||
# f.write(f"{d[0]:1{name}.3f}{d[1]:10.3f}\n")
|
||||
|
||||
# return files
|
||||
|
||||
# @timer
|
||||
# def _export_lateral_contrib(self, study, repertory, qlog):
|
||||
# def _export_lateral_contrib(self, study, repertory, qlog, name="0"):
|
||||
# files = []
|
||||
# lst = study.river.lateral_contribution
|
||||
|
||||
@@ -195,16 +277,15 @@ class Mage(AbstractSolver):
|
||||
# return files
|
||||
|
||||
@timer
|
||||
def _export_RUG(self, study, repertory, qlog):
|
||||
def _export_RUG(self, study, repertory, qlog, name="0"):
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export RUG file")
|
||||
|
||||
# Write header
|
||||
with open(os.path.join(repertory, "0.RUG"), "w+") as f:
|
||||
files.append("0.RUG")
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.RUG"), "w+") as f:
|
||||
files.append(f"{name}.RUG")
|
||||
|
||||
edges = study.river.edges()
|
||||
edges = list(
|
||||
@@ -216,17 +297,17 @@ class Mage(AbstractSolver):
|
||||
|
||||
id = 1
|
||||
for edge in edges:
|
||||
sections = edge.sections
|
||||
frictions = edge.frictions
|
||||
|
||||
for section in sections.sections:
|
||||
for friction in frictions.frictions:
|
||||
num = f"{id:>3}"
|
||||
bkp = f"{section.begin_kp:>10.3f}"
|
||||
ekp = f"{section.end_kp:>10.3f}"
|
||||
bkp = f"{friction.begin_kp:>10.3f}"
|
||||
ekp = f"{friction.end_kp:>10.3f}"
|
||||
|
||||
# if section.begin_kp != section.end_kp:
|
||||
# if friction.begin_kp != friction.end_kp:
|
||||
# print("TODO")
|
||||
|
||||
strickler = section.begin_strickler
|
||||
strickler = friction.begin_strickler
|
||||
coef_1 = f"{strickler.minor:>10.3f}"
|
||||
coef_2 = f"{strickler.medium:>10.3f}"
|
||||
|
||||
@@ -237,20 +318,15 @@ class Mage(AbstractSolver):
|
||||
return files
|
||||
|
||||
@timer
|
||||
def _export_INI(self, study, repertory, qlog):
|
||||
def _export_INI(self, study, repertory, qlog, name="0"):
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export INI file")
|
||||
|
||||
# Write header
|
||||
with open(os.path.join(repertory, "0.INI"), "w+") as f:
|
||||
files.append("0.INI")
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
# TODO put real date...
|
||||
f.write(f"$ date en minutes : 0.00\n")
|
||||
f.write(f"* IB IS discharge elevation kp\n")
|
||||
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.INI"), "w+") as f:
|
||||
has_ini = False
|
||||
id = 1
|
||||
reachs = study.river.edges()
|
||||
reachs = list(
|
||||
@@ -260,10 +336,18 @@ class Mage(AbstractSolver):
|
||||
)
|
||||
)
|
||||
|
||||
# TODO put real date...
|
||||
f.write(f"$ date en minutes : 0.00\n")
|
||||
f.write(f"* IB IS discharge elevation kp\n")
|
||||
|
||||
id = 1
|
||||
for reach in reachs:
|
||||
cond = study.river.initial_conditions.get(reach)
|
||||
data = cond.data
|
||||
if len(data) == 0:
|
||||
continue
|
||||
|
||||
has_ini = True
|
||||
|
||||
id_sec = 1
|
||||
for d in data:
|
||||
@@ -278,38 +362,67 @@ class Mage(AbstractSolver):
|
||||
|
||||
id += 1
|
||||
|
||||
if has_ini:
|
||||
files.append(f"{name}.INI")
|
||||
return files
|
||||
|
||||
@timer
|
||||
def _export_REP(self, study, repertory, files, qlog):
|
||||
def _export_REP(self, study, repertory, files, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export REP file")
|
||||
|
||||
# Write header
|
||||
with open(os.path.join(repertory, f"0.REP"), "w+") as f:
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.REP"), "w+") as f:
|
||||
f.write("confirmation=non\n")
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
|
||||
for file in files:
|
||||
EXT = file.split('.')[1]
|
||||
|
||||
f.write(f"{EXT} {file}\n")
|
||||
if EXT not in ["ST", "GRA"]:
|
||||
f.write(f"{EXT} {file}\n")
|
||||
|
||||
f.write("* OUTPUT\n")
|
||||
f.write(f"TRA 0.TRA\n")
|
||||
f.write(f"BIN 0.BIN\n")
|
||||
f.write(f"TRA {name}.TRA\n")
|
||||
f.write(f"BIN {name}.BIN\n")
|
||||
|
||||
for file in files:
|
||||
EXT = file.split('.')[1]
|
||||
|
||||
if EXT in ["GRA"]:
|
||||
f.write(f"{EXT} {file}\n")
|
||||
|
||||
|
||||
@timer
|
||||
def export(self, study, repertory, qlog = None):
|
||||
self._export_ST(study, repertory, qlog)
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
self._export_ST(study, repertory, qlog, name = name)
|
||||
|
||||
return True
|
||||
|
||||
###########
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
def read_bin(self, study, repertory, results, qlog = None, name="0"):
|
||||
return
|
||||
|
||||
@timer
|
||||
def results(self, study, repertory, qlog = None, name="0"):
|
||||
results = Results(study = study)
|
||||
|
||||
self.read_bin(study, repertory, results, qlog, name=name)
|
||||
|
||||
return results
|
||||
|
||||
##########
|
||||
# MAGE 7 #
|
||||
##########
|
||||
|
||||
class Mage7(Mage):
|
||||
_type = "mage7"
|
||||
|
||||
def __init__(self, name):
|
||||
super(Mage7, self).__init__(name)
|
||||
|
||||
@@ -326,6 +439,8 @@ class Mage7(Mage):
|
||||
##########
|
||||
|
||||
class Mage8(Mage):
|
||||
_type = "mage8"
|
||||
|
||||
def __init__(self, name):
|
||||
super(Mage8, self).__init__(name)
|
||||
|
||||
@@ -340,23 +455,43 @@ class Mage8(Mage):
|
||||
i = names.index("mage_precision_reduction_factor_Q")
|
||||
lst.insert(i+1, ("mage_precision_reduction_factor_r", "1"))
|
||||
|
||||
# Mage parameter for sediment module (added in DB 0.0.4)
|
||||
lst.append(("mage_sediment_masse_volumique", "2650.0"))
|
||||
lst.append(("mage_sediment_angle_repos", "40.0"))
|
||||
lst.append(("mage_sediment_porosity", "0.40"))
|
||||
lst.append(("mage_distance_Han", "0.0"))
|
||||
lst.append(("mage_distance_chargement_d50", "100.0"))
|
||||
lst.append(("mage_distance_chargement_sigma", "100.0"))
|
||||
lst.append(("mage_methode_modification_geometrie", "1"))
|
||||
lst.append(("mage_shields_critique", "1"))
|
||||
lst.append(("mage_shields_correction", "1"))
|
||||
lst.append(("mage_capacite_solide", "1"))
|
||||
lst.append(("mage_pas_de_temps_charriage", "1"))
|
||||
lst.append(("mage_facteur_multiplicateur", "1.0"))
|
||||
|
||||
return lst
|
||||
|
||||
##########
|
||||
# Export #
|
||||
##########
|
||||
|
||||
def cmd_args(self, study):
|
||||
l = super(Mage8, self).cmd_args(study)
|
||||
|
||||
if study.river.has_sediment():
|
||||
l.append("-c=3")
|
||||
|
||||
return l
|
||||
|
||||
@timer
|
||||
def _export_PAR(self, study, repertory, qlog = None):
|
||||
def _export_PAR(self, study, repertory, qlog = None, name = "0"):
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export PAR file")
|
||||
|
||||
with open(os.path.join(repertory, "0.PAR"), "w+") as f:
|
||||
files.append("0.PAR")
|
||||
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.PAR"), "w+") as f:
|
||||
files.append(f"{name}.PAR")
|
||||
|
||||
params = study.river.get_params(self.type).parameters
|
||||
for p in params:
|
||||
@@ -365,22 +500,25 @@ class Mage8(Mage):
|
||||
.replace("mage_", "")
|
||||
value = p.value
|
||||
|
||||
f.write(f"{name} {value}\n")
|
||||
if name in ["command_line_arguments"]:
|
||||
continue
|
||||
|
||||
if name is "mage_compute_reach_volume_balance":
|
||||
value = "O" if value == "y" else "N"
|
||||
|
||||
f.write(f"{name} {value}\n")
|
||||
|
||||
return files
|
||||
|
||||
@timer
|
||||
def _export_NET(self, study, repertory, qlog = None):
|
||||
def _export_NET(self, study, repertory, qlog = None, name="0"):
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export NET file")
|
||||
|
||||
with open(os.path.join(repertory, "0.NET"), "w+") as f:
|
||||
files.append("0.NET")
|
||||
|
||||
f.write("* This file is generate by PAMHYR, please don't modify\n")
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.NET"), "w+") as f:
|
||||
files.append(f"{name}.NET")
|
||||
|
||||
edges = study.river.edges()
|
||||
edges = list(
|
||||
@@ -399,22 +537,352 @@ class Mage8(Mage):
|
||||
|
||||
n1 = f"{e.node1.id:3}".replace(" ", "x")
|
||||
n2 = f"{e.node2.id:3}".replace(" ", "x")
|
||||
file = name + ".ST"
|
||||
file = os.path.join("net", name + ".ST")
|
||||
|
||||
f.write(f"{id} {n1} {n2} {file}\n")
|
||||
|
||||
return files
|
||||
|
||||
@timer
|
||||
def export(self, study, repertory, qlog = None):
|
||||
def _export_QSO(self, bounds, repertory, qlog, name="0"):
|
||||
files = []
|
||||
|
||||
self._export_ST(study, repertory, qlog)
|
||||
files = files + self._export_PAR(study, repertory, qlog)
|
||||
files = files + self._export_NET(study, repertory, qlog)
|
||||
files = files + self._export_bound_cond(study, repertory, qlog)
|
||||
files = files + self._export_RUG(study, repertory, qlog)
|
||||
files = files + self._export_INI(study, repertory, qlog)
|
||||
self._export_REP(study, repertory, files, qlog)
|
||||
if len(bounds) == 0:
|
||||
return files
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put(f"Export QSO file")
|
||||
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.QSO"), "w+") as f:
|
||||
files.append(f"{name}.QSO")
|
||||
|
||||
for bound in bounds:
|
||||
# File header
|
||||
name = f"{bound.node.id:3}".replace(" ", "x")
|
||||
f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n")
|
||||
|
||||
d50 = bound.d50
|
||||
sigma = bound.sigma
|
||||
|
||||
if len(bound.data) == 0:
|
||||
f.write(f"${name} {d50} {sigma} default\n")
|
||||
else:
|
||||
f.write(f"${name} {d50} {sigma}\n")
|
||||
|
||||
# Table header
|
||||
header = bound.header
|
||||
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
|
||||
|
||||
# Data
|
||||
for d in bound.data:
|
||||
f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
|
||||
|
||||
return files
|
||||
|
||||
|
||||
@timer
|
||||
def export(self, study, repertory, qlog = None, name="0"):
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
files = []
|
||||
|
||||
files = self._export_ST(study, repertory, qlog, name=name)
|
||||
files = files + self._export_PAR(study, repertory, qlog, name=name)
|
||||
files = files + self._export_NET(study, repertory, qlog, name=name)
|
||||
files = files + self._export_bound_cond(study, repertory, qlog, name=name)
|
||||
files = files + self._export_RUG(study, repertory, qlog, name=name)
|
||||
files = files + self._export_INI(study, repertory, qlog, name=name)
|
||||
self._export_REP(study, repertory, files, qlog, name=name)
|
||||
|
||||
return True
|
||||
|
||||
###########
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
@timer
|
||||
def read_bin(self, study, repertory, results, qlog = None, name="0"):
|
||||
fname = os.path.join(repertory, f"{name}.BIN")
|
||||
logger.info(f"read_bin: Start reading '{fname}' ...")
|
||||
|
||||
with mage_file_open(fname, "r") as f:
|
||||
newline = lambda: np.fromfile(f, dtype=np.int32, count=1)
|
||||
endline = lambda: np.fromfile(f, dtype=np.int32, count=1)
|
||||
|
||||
read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size)
|
||||
read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size)
|
||||
read_float64 = lambda size: np.fromfile(f, dtype=np.float64, count=size)
|
||||
|
||||
# Meta data (1st line)
|
||||
newline()
|
||||
|
||||
data = read_int(3)
|
||||
|
||||
nb_reach = data[0]
|
||||
nb_profile = data[1]
|
||||
mage_version = data[2]
|
||||
|
||||
logger.debug(f"read_bin: nb_reach = {nb_reach}")
|
||||
logger.debug(f"read_bin: nb_profile = {nb_profile}")
|
||||
logger.debug(f"read_bin: mage_version = {mage_version}")
|
||||
|
||||
if mage_version <= 80:
|
||||
msg = (
|
||||
"Read BIN files: " +
|
||||
f"Possible incompatible mage version '{mage_version}', " +
|
||||
"please check your solver configuration..."
|
||||
)
|
||||
logger.warning(msg)
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("[WARNING] " + msg)
|
||||
|
||||
results.set("solver_version", f"Mage8 ({mage_version})")
|
||||
results.set("nb_reach", f"{nb_reach}")
|
||||
results.set("nb_profile", f"{nb_profile}")
|
||||
|
||||
endline()
|
||||
|
||||
# Reach information (2nd line)
|
||||
newline()
|
||||
|
||||
reachs = []
|
||||
iprofiles = {}
|
||||
reach_offset = {}
|
||||
|
||||
data = read_int(2*nb_reach)
|
||||
|
||||
for i in range(nb_reach):
|
||||
# Add results reach to reach list
|
||||
r = results.river.add(i)
|
||||
reachs.append(r)
|
||||
|
||||
# ID of first and last reach profiles
|
||||
i1 = data[2*i] - 1
|
||||
i2 = data[2*i+1] - 1
|
||||
|
||||
# Add profile id correspondance to reach
|
||||
key = (i1, i2)
|
||||
iprofiles[key] = r
|
||||
|
||||
# Profile ID offset
|
||||
reach_offset[r] = i1
|
||||
|
||||
logger.debug(f"read_bin: iprofiles = {iprofiles}")
|
||||
|
||||
endline()
|
||||
|
||||
# X (3rd line)
|
||||
newline()
|
||||
_ = read_float(nb_profile)
|
||||
endline()
|
||||
|
||||
# Z and Y (4th line)
|
||||
newline()
|
||||
_ = read_float(3*nb_profile)
|
||||
endline()
|
||||
|
||||
# Data
|
||||
newline()
|
||||
|
||||
ip_to_r = lambda i: iprofiles[
|
||||
next(
|
||||
filter(
|
||||
lambda k: k[0] <= i <= k[1],
|
||||
iprofiles
|
||||
)
|
||||
)
|
||||
]
|
||||
ip_to_ri = lambda r, i: i - reach_offset[r]
|
||||
|
||||
ts = set()
|
||||
end = False
|
||||
while not end:
|
||||
n = read_int(1)[0]
|
||||
timestamp = read_float64(1)[0]
|
||||
key = bytearray(np.fromfile(f, dtype=np.byte, count=1)).decode()
|
||||
data = read_float(n)
|
||||
|
||||
logger.debug(f"read_bin: timestamp = {timestamp} sec")
|
||||
ts.add(timestamp)
|
||||
|
||||
if key in ["Z", "Q"]:
|
||||
for i, d in enumerate(data):
|
||||
# Get reach corresponding to profile ID
|
||||
reach = ip_to_r(i)
|
||||
# Get profile id in reach
|
||||
ri = ip_to_ri(reach, i)
|
||||
|
||||
# Set data for profile RI
|
||||
reach.set(ri, timestamp, key, d)
|
||||
|
||||
endline()
|
||||
end = newline().size <= 0
|
||||
|
||||
logger.debug(reachs[0].profiles[0]._data)
|
||||
results.set("timestamps", ts)
|
||||
logger.info(f"read_bin: ... end with {len(ts)} timestamp read")
|
||||
|
||||
@timer
|
||||
def read_gra(self, study, repertory, results, qlog = None, name="0"):
|
||||
if not study.river.has_sediment():
|
||||
return
|
||||
|
||||
fname = os.path.join(repertory, f"{name}.GRA")
|
||||
logger.info(f"read_gra: Start reading '{fname}' ...")
|
||||
|
||||
with mage_file_open(fname, "r") as f:
|
||||
newline = lambda: np.fromfile(f, dtype=np.int32, count=1)
|
||||
endline = lambda: np.fromfile(f, dtype=np.int32, count=1)
|
||||
|
||||
read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size)
|
||||
read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size)
|
||||
read_float64 = lambda size: np.fromfile(f, dtype=np.float64, count=size)
|
||||
|
||||
# Meta data (1st line)
|
||||
newline()
|
||||
|
||||
data = read_int(3)
|
||||
|
||||
nb_reach = data[0]
|
||||
nb_profile = data[1]
|
||||
mage_version = data[2]
|
||||
|
||||
logger.debug(f"read_gra: nb_reach = {nb_reach}")
|
||||
logger.debug(f"read_gra: nb_profile = {nb_profile}")
|
||||
logger.debug(f"read_gra: mage_version = {mage_version}")
|
||||
|
||||
if mage_version <= 80:
|
||||
msg = (
|
||||
"Read GRA files: " +
|
||||
f"Possible incompatible mage version '{mage_version}', " +
|
||||
"please check your solver configuration..."
|
||||
)
|
||||
logger.warning(msg)
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("[WARNING] " + msg)
|
||||
|
||||
results.set("gra_solver_version", f"Mage8 ({mage_version})")
|
||||
results.set("gra_nb_reach", f"{nb_reach}")
|
||||
results.set("gra_nb_profile", f"{nb_profile}")
|
||||
|
||||
endline()
|
||||
|
||||
# Reach information (2nd line)
|
||||
newline()
|
||||
|
||||
reachs = []
|
||||
iprofiles = {}
|
||||
reach_offset = {}
|
||||
|
||||
data = read_int(2*nb_reach)
|
||||
|
||||
for i in range(nb_reach):
|
||||
# Get results reach to reach list
|
||||
r = results.river.reach(i)
|
||||
reachs.append(r)
|
||||
|
||||
# ID of first and last reach profiles
|
||||
i1 = data[2*i] - 1
|
||||
i2 = data[2*i+1] - 1
|
||||
|
||||
# Add profile id correspondance to reach
|
||||
key = (i1, i2)
|
||||
iprofiles[key] = r
|
||||
|
||||
# Profile ID offset
|
||||
reach_offset[r] = i1
|
||||
|
||||
logger.debug(f"read_gra: iprofiles = {iprofiles}")
|
||||
|
||||
endline()
|
||||
|
||||
# X (3rd line)
|
||||
newline()
|
||||
_ = read_float(nb_profile)
|
||||
endline()
|
||||
|
||||
# npts (4th line)
|
||||
newline()
|
||||
_ = read_int(nb_profile)
|
||||
endline()
|
||||
|
||||
# Data
|
||||
ip_to_r = lambda i: iprofiles[
|
||||
next(
|
||||
filter(
|
||||
lambda k: k[0] <= i <= k[1],
|
||||
iprofiles
|
||||
)
|
||||
)
|
||||
]
|
||||
ip_to_ri = lambda r, i: i - reach_offset[r]
|
||||
|
||||
ts = set()
|
||||
ind = 0
|
||||
end = False
|
||||
|
||||
newline()
|
||||
while not end:
|
||||
n = read_int(1)[0]
|
||||
timestamp = read_float64(1)[0]
|
||||
|
||||
logger.debug(f"read_gra: timestamp = {timestamp} sec")
|
||||
ts.add(timestamp)
|
||||
|
||||
endline()
|
||||
|
||||
for i in range(n):
|
||||
newline()
|
||||
nsl = read_int(1)[0]
|
||||
endline()
|
||||
|
||||
# Get current profile id
|
||||
reach = ip_to_r(i)
|
||||
ri = ip_to_ri(reach, i)
|
||||
|
||||
if nsl > 1:
|
||||
logger.warning(
|
||||
"read_gra: " +
|
||||
"Multiple sediment layers for one profile " +
|
||||
"is not implemented yet..."
|
||||
)
|
||||
|
||||
for j in range(nsl):
|
||||
newline()
|
||||
nl = read_int(1)[0]
|
||||
endline()
|
||||
|
||||
sl = []
|
||||
|
||||
for k in range(nl):
|
||||
newline()
|
||||
data = read_float64(3)
|
||||
endline()
|
||||
|
||||
h = data[0]
|
||||
d50 = data[1]
|
||||
sigma = data[2]
|
||||
|
||||
sl.append((h, d50, sigma))
|
||||
|
||||
reach.set(ri, timestamp, "sl", sl)
|
||||
|
||||
ind += 1
|
||||
end = newline().size <= 0
|
||||
|
||||
logger.debug(reachs[0].profiles[0]._data)
|
||||
results.set("timestamps", ts)
|
||||
logger.info(f"read_gra: ... end with {len(ts)} timestamp read")
|
||||
|
||||
@timer
|
||||
def results(self, study, repertory, qlog = None):
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
results = super(Mage8, self).results(study, repertory, qlog, name = name)
|
||||
self.read_gra(study, repertory, results, qlog, name = name)
|
||||
|
||||
return results
|
||||
@@ -1,3 +1,19 @@
|
||||
# Solvers.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../VERSION
|
||||
@@ -1,7 +1,24 @@
|
||||
# ASubWindow.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import csv
|
||||
import logging
|
||||
|
||||
from io import StringIO
|
||||
from datetime import datetime
|
||||
@@ -17,7 +34,8 @@ from PyQt5.QtWidgets import (
|
||||
QTimeEdit, QSpinBox, QTextEdit,
|
||||
QRadioButton, QComboBox, QFileDialog,
|
||||
QMessageBox, QTableView, QAction,
|
||||
QDateTimeEdit, QWidget,
|
||||
QDateTimeEdit, QWidget, QPlainTextEdit,
|
||||
QLabel,
|
||||
)
|
||||
from PyQt5.QtCore import (
|
||||
QTime, QDateTime,
|
||||
@@ -26,6 +44,8 @@ from PyQt5.uic import loadUi
|
||||
|
||||
from Model.Except import ClipboardFormatError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class WindowToolKit(object):
|
||||
def __init__(self, parent=None):
|
||||
super(WindowToolKit, self).__init__()
|
||||
@@ -58,13 +78,14 @@ class WindowToolKit(object):
|
||||
|
||||
return header, values
|
||||
|
||||
def file_dialog(self, select_file=True, callback=lambda x: None):
|
||||
def file_dialog(self, select_file=True, callback=lambda x: None, directory=None):
|
||||
"""Open a new file dialog and send result to callback function
|
||||
|
||||
Args:
|
||||
select_file: Select a file if True, else select a dir
|
||||
callback: The callback function with one arguments, files
|
||||
selection list
|
||||
directory: Defaut directory
|
||||
|
||||
Returns:
|
||||
The returns of callback
|
||||
@@ -77,6 +98,8 @@ class WindowToolKit(object):
|
||||
mode = QFileDialog.FileMode.Directory
|
||||
|
||||
dialog.setFileMode(mode)
|
||||
if directory is not None:
|
||||
dialog.setDirectory(directory)
|
||||
|
||||
if dialog.exec_():
|
||||
file_names = dialog.selectedFiles()
|
||||
@@ -127,6 +150,30 @@ class ASubWindowFeatures(object):
|
||||
|
||||
return qtype
|
||||
|
||||
def get_label_text(self, name:str):
|
||||
"""Get text of label component
|
||||
|
||||
Args:
|
||||
label: The label component name
|
||||
|
||||
Returns:
|
||||
Text
|
||||
"""
|
||||
return self.find(QLabel, name).text()
|
||||
|
||||
def set_label_text(self, name:str, text:str):
|
||||
"""Set text of label component
|
||||
|
||||
Args:
|
||||
text_edit: The label component name
|
||||
text: The text
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
self.find(QLabel, name).setText(text)
|
||||
|
||||
|
||||
def set_line_edit_text(self, name:str, text:str):
|
||||
"""Set text of line edit component
|
||||
|
||||
@@ -140,7 +187,7 @@ class ASubWindowFeatures(object):
|
||||
try:
|
||||
self.find(QLineEdit, name).setText(text)
|
||||
except AttributeError as e:
|
||||
print(e)
|
||||
logger.error(e)
|
||||
|
||||
def get_line_edit_text(self, name:str):
|
||||
"""Get text of line edit component
|
||||
@@ -176,6 +223,29 @@ class ASubWindowFeatures(object):
|
||||
"""
|
||||
return self.find(QTextEdit, name).toHtml()
|
||||
|
||||
def set_plaintext_edit_text(self, name:str, text:str):
|
||||
"""Set text of text edit component
|
||||
|
||||
Args:
|
||||
text_edit: The text edit component name
|
||||
text: The text
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
self.find(QPlainTextEdit, name).setPlainText(text)
|
||||
|
||||
def get_plaintext_edit_text(self, name:str):
|
||||
"""Get text of text edit component
|
||||
|
||||
Args:
|
||||
text_edit: The text edit component name
|
||||
|
||||
Returns:
|
||||
Text
|
||||
"""
|
||||
return self.find(QPlainTextEdit, name).toPlainText()
|
||||
|
||||
def set_check_box(self, name:str, checked:bool):
|
||||
"""Set status of checkbox component
|
||||
|
||||
@@ -395,10 +465,12 @@ class ASubWindowFeatures(object):
|
||||
class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit):
|
||||
def __init__(self, name="", ui="dummy", parent=None):
|
||||
super(ASubMainWindow, self).__init__(parent=parent)
|
||||
self.ui = loadUi(
|
||||
os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"),
|
||||
self
|
||||
)
|
||||
if ui is not None:
|
||||
self.ui = loadUi(
|
||||
os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"),
|
||||
self
|
||||
)
|
||||
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
if self.parent is not None:
|
||||
|
||||
@@ -1,8 +1,63 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from View.ASubWindow import ASubWindow
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
logger = logging.getLogger()
|
||||
|
||||
class AboutWindow(ASubWindow):
|
||||
def _path_file(self, filename):
|
||||
return os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..", "..", filename
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def __init__(self, title="About", parent=None):
|
||||
super(AboutWindow, self).__init__(name=title, ui="about", parent=parent)
|
||||
self.ui.setWindowTitle(title)
|
||||
|
||||
# Version
|
||||
with open(self._path_file("VERSION"), "r") as f:
|
||||
version = f.readline().strip()
|
||||
logger.info(f"version: {version}")
|
||||
|
||||
label = self.get_label_text("label_version")
|
||||
label = label.replace("@version", version)
|
||||
self.set_label_text("label_version", label)
|
||||
|
||||
# Authors
|
||||
with open(self._path_file("AUTHORS"), "r") as f:
|
||||
label = ""
|
||||
try:
|
||||
while True:
|
||||
author = next(f).strip()
|
||||
logger.info(f"author: {author}")
|
||||
label = f"\n - {author}" + label
|
||||
except StopIteration:
|
||||
label = _translate("About", "Contributors: ") + label
|
||||
label = "Copyright © 2023 INRAE\n" + label
|
||||
self.set_label_text("label_copyright", label)
|
||||
@@ -1,5 +1,23 @@
|
||||
# Plot.py -- Pamhyr
|
||||
# Copyright (C) 2023 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from tools import timer, trace
|
||||
@@ -13,6 +31,8 @@ from View.BoundaryCondition.Edit.translate import *
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Plot(APlot):
|
||||
def __init__(self, canvas=None, data=None,
|
||||
mode = "time", toolbar=None):
|
||||
@@ -25,6 +45,9 @@ class Plot(APlot):
|
||||
self._mode = mode
|
||||
|
||||
def custom_ticks(self):
|
||||
if self.data.header[0] != "time":
|
||||
return
|
||||
|
||||
t0 = datetime.fromtimestamp(0)
|
||||
nb = len(self.data.data)
|
||||
mod = int(nb / 5)
|
||||
|
||||