Merge branch 'master' into mesh

This commit is contained in:
Theophile Terraz committed 2023-09-27 10:16:02 +02:00
commit 9f0caaaa69
239 files changed
+18304 -2959

No files matched your search

+12
View File
@@ -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
+11
View File
@@ -4,8 +4,19 @@
*.tar.xz *.tar.xz
*.zip *.zip
*.spec *.spec
*.pkl
*.class
tmp
*.tar.*
*.zip
pamhyrBuild pamhyrBuild
wine-pamhyr-build*
_PAMHYR_
__old__
### END CUSTOM ### ### END CUSTOM ###
+360
View File
@@ -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'
View File
Whitespace-only changes.
+3
View File
@@ -0,0 +1,3 @@
Sylvain COULIBALY, INRAE, 2020 - 2022
Théophile TERRAZ, INRAE, 2022 - 2023
Pierre-Antoine ROUBY, INRAE, 2023
+674
View File
@@ -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>.
+50 -1
View File
@@ -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
```
+1
View File
@@ -0,0 +1 @@
git
+10
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
+606
View File
@@ -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}}}
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

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 --&gt; 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&apos;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&apos;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&apos;é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"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.
+131
View File
@@ -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
+163
View File
@@ -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}
+19
View File
@@ -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
+51
View File
@@ -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
View File
Whitespace-only changes.
+66
View File
@@ -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) "/")))
+10
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
+20
View File
@@ -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}}}
+30 -1
View File
@@ -1,5 +1,21 @@
#! /bin/sh #! /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" echo " *** RM OLD ENV"
rm dist/ -rf rm dist/ -rf
@@ -28,13 +44,26 @@ cd $OLD_PWD
echo " *** COPY DATA" echo " *** COPY DATA"
mkdir -p dist/pamhyr/View/ui 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/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/ cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/
mkdir -p dist/pamhyr/lang mkdir -p dist/pamhyr/lang
cp -r ../src/lang/*.qm 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" echo " *** MAKE SRC PACKAGE"
+54
View File
@@ -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
+20
View File
@@ -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
+27 -18
View File
@@ -1,16 +1,21 @@
!include "x64.nsh" !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 RequestExecutionLevel admin
Unicode True 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 components
Page directory Page directory
Page instfiles Page instfiles
@@ -18,35 +23,39 @@ Page instfiles
UninstPage uninstConfirm UninstPage uninstConfirm
UninstPage instfiles UninstPage instfiles
Section "PAMHYR" Section "Pamhyr2"
SectionIn RO SectionIn RO
SetOutPath $INSTDIR 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\Pamhyr2" "DisplayName" "NSIS Pamhyr2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "UninstallString" "$INSTDIR\uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoRepair" 1
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd SectionEnd
Section "Start Menu Shortcuts" 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" Section "Desktop Shortcuts"
CreateShortcut "$SMPROGRAMS\PAMHYR\Uninstall.lnk" "$INSTDIR\uninstall.exe" SetShellVarContext all
CreateShortcut "$SMPROGRAMS\PAMHYR\PAMHYR.lnk" "$INSTDIR\pamhyr.exe" CreateShortcut "$desktop\Pamhyr2.lnk" "$instdir\pamhyr\pamhyr.exe"
SectionEnd SectionEnd
Section "Uninstall" Section "Uninstall"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2"
DeleteRegKey HKLM SOFTWARE\NSIS_PAMHYR 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" RMDir /R "$INSTDIR"
SectionEnd SectionEnd
+26
View File
@@ -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
+54
View File
@@ -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
+73 -15
View File
@@ -1,9 +1,35 @@
#! /bin/sh #! /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" echo " *** SETUP ENV"
export WINARCH=win64 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 ]; if [ -z $WINEPREFIX ];
then then
# FIXME Not tested yet... # FIXME Not tested yet...
@@ -30,7 +56,7 @@ then
win10 win10
echo " *** PYTHON INSTALL" echo " *** PYTHON INSTALL"
if [ ! -f $WINEPREFIX/drive_c/Program Files/Python38/python.exe ]; if [ ! -f "$WINEPREFIX/drive_c/Program Files/Python38/python.exe" ];
then then
#DISPLAY=:42.0 wine cmd /c python-3.8.10-amd64.exe /quiet PrependPath=1 #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 wine cmd /c python-3.8.10-amd64.exe PrependPath=1
@@ -55,10 +81,42 @@ wine pyinstaller ../src/pamhyr.py -y
echo " *** COPY DATA" echo " *** COPY DATA"
mkdir -p dist/pamhyr/View/ui mkdir -p pamhyr
cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/ mkdir -p pamhyr/pamhyr
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/
cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/ # 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 # Update TS and build QM files
OLD_PWD=$PWD OLD_PWD=$PWD
@@ -73,19 +131,19 @@ echo " *** MAKE PACKAGES"
echo " TAR pamhyr-win-amd64.tar.xz" echo " TAR pamhyr-win-amd64.tar.xz"
OLD_PWD=$PWD # OLD_PWD=$PWD
cd dist/ # cd dist/
tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100 # tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100
cd $OLD_PWD # cd $OLD_PWD
mv dist/pamhyr-win-amd64.tar.xz ./ # mv dist/pamhyr-win-amd64.tar.xz ./
echo " ZIP pamhyr-win-amd64.zip" echo " ZIP pamhyr-win-amd64.zip"
OLD_PWD=$PWD # OLD_PWD=$PWD
cd dist/ # cd dist/
zip -r pamhyr-win-amd64.zip pamhyr/ # zip -r pamhyr-win-amd64.zip pamhyr/
cd $OLD_PWD # cd $OLD_PWD
mv dist/pamhyr-win-amd64.zip ./ # mv dist/pamhyr-win-amd64.zip ./
echo " *** MAKE INSTALLER" echo " *** MAKE INSTALLER"
+1
View File
@@ -0,0 +1 @@
../AUTHORS
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from enum import Enum from enum import Enum
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import time import time
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import time import time
-10
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
class BoundaryCondition(object): logger = logging.getLogger()
def __init__(self, name:str = "",
class BoundaryCondition(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id:int = -1, name:str = "",
status = None): status = None):
super(BoundaryCondition, self).__init__() super(BoundaryCondition, self).__init__()
self._status = status self._status = status
if id == -1:
self.id = BoundaryCondition._id_cnt
else:
self.id = id
self._name = name self._name = name
self._type = "" self._type = ""
self._node = None self._node = None
@@ -18,6 +47,137 @@ class BoundaryCondition(object):
self._header = [] self._header = []
self._types = [float, float] 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): def __len__(self):
return len(self._data) return len(self._data)
@@ -164,14 +324,17 @@ class BoundaryCondition(object):
new = cls(name = self.name, status = self._status) new = cls(name = self.name, status = self._status)
new.node = self.node new.node = self.node
for i, _ in self.data: for i, _ in enumerate(self.data):
new.add(i) new.add(i)
for i in [0,1]: for i in [0,1]:
for j in [0,1]: for j in [0,1]:
if self._header[i] == new.header[j]: if self._header[i] == new.header[j]:
for ind, v in self.data: 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 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 -*- # -*- coding: utf-8 -*-
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
from Model.BoundaryCondition.BoundaryConditionTypes import ( from Model.BoundaryCondition.BoundaryConditionTypes import (
NotDefined, NotDefined,
PonctualContribution, PonctualContribution,
TimeOverZ, TimeOverDischarge, ZOverDischarge TimeOverZ, TimeOverDischarge, ZOverDischarge,
Solid,
) )
class BoundaryConditionList(object): class BoundaryConditionList(PamhyrModelListWithTab):
def __init__(self, status = None): _tabs_list = ["liquid", "solid", "suspenssion"]
super(BoundaryConditionList, self).__init__() _sub_classes = [
BoundaryCondition,
]
self._status = status @classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
self._tabs = { if data is None:
"liquid" : [], data = {}
"solid" : [],
"suspenssion" : []
}
def len(self, lst): for tab in new._tabs:
return len(self._tabs[lst]) data["tab"] = tab
new._tabs[tab] = BoundaryCondition._sql_load(
execute, data
)
def get_tab(self, lst): return new
return self._tabs[lst].copy()
def get(self, lst, row): def _sql_save(self, execute, data = None):
return self._tabs[lst][row] execute("DELETE FROM boundary_condition")
def set(self, lst, row, new): if data is None:
self._tabs[lst][row] = new data = {}
self._status.modified()
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): def new(self, lst, index):
n = NotDefined(status=self._status) n = NotDefined(status=self._status)
@@ -42,47 +70,6 @@ class BoundaryConditionList(object):
self._status.modified() self._status.modified()
return n 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): def __copy__(self):
new = BoundaryConditionList() 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 -*- # -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
@@ -6,8 +22,8 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
class NotDefined(BoundaryCondition): class NotDefined(BoundaryCondition):
def __init__(self, name:str = "", status = None): def __init__(self, id:int = -1, name:str = "", status = None):
super(NotDefined, self).__init__(name=name, status=status) super(NotDefined, self).__init__(id=id, name=name, status=status)
self._type = "ND" self._type = "ND"
self._header = ["x", "y"] self._header = ["x", "y"]
@@ -17,8 +33,8 @@ class NotDefined(BoundaryCondition):
return 0.0 return 0.0
class PonctualContribution(BoundaryCondition): class PonctualContribution(BoundaryCondition):
def __init__(self, name:str = "", status = None): def __init__(self, id:int = -1, name:str = "", status = None):
super(PonctualContribution, self).__init__(name=name, status=status) super(PonctualContribution, self).__init__(id=id, name=name, status=status)
self._type = "PC" self._type = "PC"
self._header = ["time", "discharge"] self._header = ["time", "discharge"]
@@ -29,8 +45,8 @@ class PonctualContribution(BoundaryCondition):
return ["liquid"] return ["liquid"]
class TimeOverZ(BoundaryCondition): class TimeOverZ(BoundaryCondition):
def __init__(self, name:str = "", status = None): def __init__(self, id:int = -1, name:str = "", status = None):
super(TimeOverZ, self).__init__(name=name, status=status) super(TimeOverZ, self).__init__(id=id, name=name, status=status)
self._type = "TZ" self._type = "TZ"
self._header = ["time", "z"] self._header = ["time", "z"]
@@ -41,8 +57,8 @@ class TimeOverZ(BoundaryCondition):
return ["liquid"] return ["liquid"]
class TimeOverDischarge(BoundaryCondition): class TimeOverDischarge(BoundaryCondition):
def __init__(self, name:str = "", status = None): def __init__(self, id:int = -1, name:str = "", status = None):
super(TimeOverDischarge, self).__init__(name=name, status=status) super(TimeOverDischarge, self).__init__(id=id, name=name, status=status)
self._type = "TD" self._type = "TD"
self._header = ["time", "discharge"] self._header = ["time", "discharge"]
@@ -53,8 +69,8 @@ class TimeOverDischarge(BoundaryCondition):
return ["liquid"] return ["liquid"]
class ZOverDischarge(BoundaryCondition): class ZOverDischarge(BoundaryCondition):
def __init__(self, name:str = "", status = None): def __init__(self, id:int = -1, name:str = "", status = None):
super(ZOverDischarge, self).__init__(name=name, status=status) super(ZOverDischarge, self).__init__(id=id, name=name, status=status)
self._type = "ZD" self._type = "ZD"
self._header = ["z", "discharge"] self._header = ["z", "discharge"]
@@ -67,3 +83,19 @@ class ZOverDischarge(BoundaryCondition):
@property @property
def _default_0(self): def _default_0(self):
return 0.0 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"]
+17 -1
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from PyQt5.QtCore import ( from PyQt5.QtCore import (
@@ -70,7 +86,7 @@ class NotImplementedMethodeError(ExeceptionWithMessageBox):
f" '{self.func.__name__}' " + f" '{self.func.__name__}' " +
_translate("Exception", "not implemented") + _translate("Exception", "not implemented") +
_translate("Exception", "for class") + _translate("Exception", "for class") +
f" '{self.obj.__class__}'" f" '{self.obj.__class__ if self.obj.__class__ != type else self.obj}'"
) )
def header(self): def header(self):
+182
View File
@@ -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()
+79
View File
@@ -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
+34 -1
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
class Point(object): class Point(object):
def __init__(self, name:str = "", status = None): def __init__(self, name:str = "", profile=None, status = None):
super(Point, self).__init__() super(Point, self).__init__()
self._status = status self._status = status
self._name = name 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 @property
def name(self): def name(self):
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from math import dist from math import dist
+125 -4
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from math import dist from math import dist
import numpy as np import numpy as np
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Point import Point 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, def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0,
name:str = "", status = None): name:str = "", profile = None, status = None):
super(PointXYZ, self).__init__(name=name, status=status) super(PointXYZ, self).__init__(name=name, profile=profile, status=status)
self._x = float(x) self._x = float(x)
self._y = float(y) self._y = float(y)
self._z = float(z) 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 @classmethod
def from_data(cls, header, data): def from_data(cls, header, data):
point = None point = None
@@ -23,7 +144,7 @@ class PointXYZ(Point):
*data *data
) )
else: else:
valid_header = {'name', 'x', 'y', 'z'} valid_header = {'name', 'x', 'y', 'z', 'profile'}
d = {} d = {}
for i, v in enumerate(data): for i, v in enumerate(data):
h = header[i].strip().lower().split(' ')[0] h = header[i].strip().lower().split(' ')[0]
+82 -3
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import logging
from tools import timer from tools import timer
from Model.Geometry.Point import Point from Model.Geometry.Point import Point
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class Profile(object): 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 = "", kp:float = 0.0, name:str = "",
code1: int = 0, code2: int = 0, code1:int = 0, code2:int = 0,
_type:str = "", reach = None, _type:str = "", reach = None,
status = None): status = None):
super(Profile, self).__init__() super(Profile, self).__init__()
self._status = status 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._num = int(num)
self._code1 = int(code1) self._code1 = int(code1)
self._code2 = int(code2) self._code2 = int(code2)
self._kp = float(kp) self._kp = float(kp)
self._name = str(name) self._name = str(name)
self._reach = reach self._reach = reach
self._sl = None
self._points: List[Point] = [] self._points: List[Point] = []
@@ -34,6 +64,9 @@ class Profile(object):
def points(self): def points(self):
return self._points.copy() return self._points.copy()
def point(self, index):
return self._points[index]
@property @property
def reach(self): def reach(self):
return self._reach return self._reach
@@ -107,6 +140,19 @@ class Profile(object):
self._name = value.strip() self._name = value.strip()
self._status.modified() 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 @property
def profile_type(self): def profile_type(self):
""" """
@@ -245,7 +291,7 @@ class Profile(object):
@timer @timer
def sort_with_indexes(self, indexes: list): def sort_with_indexes(self, indexes: list):
if len(self._points) != len(indexes): if len(self._points) != len(indexes):
print("TODO: CRITICAL ERROR!") logger.critical("Indexes list do not correspond to point list")
self._points = list( self._points = list(
map( map(
@@ -258,6 +304,39 @@ class Profile(object):
) )
self._status.modified() 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 # Abstract method, must be implemented for in non abstract class
def get_station(self): def get_station(self):
raise NotImplementedMethodeError(self, self.get_station) raise NotImplementedMethodeError(self, self.get_station)
+142 -6
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import numpy as np import numpy as np
@@ -5,13 +21,19 @@ from typing import List
from tools import timer from tools import timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import ClipboardFormatError from Model.Except import ClipboardFormatError
from Model.Geometry.Profile import Profile from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ from Model.Geometry.PointXYZ import PointXYZ
from Model.Geometry.Vector_1d import Vector1d from Model.Geometry.Vector_1d import Vector1d
class ProfileXYZ(Profile): class ProfileXYZ(Profile, SQLSubModel):
_sub_classes = [
PointXYZ,
]
def __init__(self, def __init__(self,
id: int = -1,
name: str = "", name: str = "",
kp: float = 0., kp: float = 0.,
reach = None, reach = None,
@@ -32,6 +54,7 @@ class ProfileXYZ(Profile):
Nothing. Nothing.
""" """
super(ProfileXYZ, self).__init__( super(ProfileXYZ, self).__init__(
id = id,
num = num, num = num,
name = name, name = name,
kp = kp, kp = kp,
@@ -41,6 +64,119 @@ class ProfileXYZ(Profile):
status = status, 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 @classmethod
def from_data(cls, header, data): def from_data(cls, header, data):
profile = None profile = None
@@ -68,11 +204,11 @@ class ProfileXYZ(Profile):
try: try:
if len(header) == 0: if len(header) == 0:
point = PointXYZ( point = PointXYZ(
*data, status=self._status *data, profile=self, status=self._status
) )
else: else:
valid_header = {'name', 'x', 'y', 'z'} valid_header = {'name', 'x', 'y', 'z'}
d = {"status": self._status} d = {"status": self._status, "profile": self}
for i, v in enumerate(data): for i, v in enumerate(data):
h = header[i].strip().lower().split(' ')[0] h = header[i].strip().lower().split(' ')[0]
if h in valid_header: if h in valid_header:
@@ -125,7 +261,7 @@ class ProfileXYZ(Profile):
Nothing. Nothing.
""" """
for point in list_points: for point in list_points:
pt = PointXYZ(*point, status=self._status) pt = PointXYZ(*point, profile=self, status=self._status)
self._points.append(pt) self._points.append(pt)
self._status.modified() self._status.modified()
@@ -149,7 +285,7 @@ class ProfileXYZ(Profile):
Returns: Returns:
Nothing. 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._points.append(point_xyz)
self._status.modified() self._status.modified()
@@ -162,7 +298,7 @@ class ProfileXYZ(Profile):
Returns: Returns:
The new point. 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._points.insert(index, point)
self._status.modified() self._status.modified()
return point return point
+98 -4
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import logging
import numpy as np import numpy as np
from time import time from time import time
@@ -10,13 +27,22 @@ from functools import reduce
from tools import flatten, timer, trace from tools import flatten, timer, trace
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Profile import Profile from Model.Geometry.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ from Model.Geometry.ProfileXYZ import ProfileXYZ
from Model.Except import FileFormatError, exception_message_box 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): def __init__(self, status=None, parent=None):
self.id = parent.id
self._status = status self._status = status
self._parent = parent self._parent = parent
self._profiles: List[Profile] = [] self._profiles: List[Profile] = []
@@ -24,6 +50,41 @@ class Reach:
self._guidelines_is_valid = False self._guidelines_is_valid = False
self._guidelines = {} 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): def profile(self, i):
"""Returns profile at index i """Returns profile at index i
@@ -215,6 +276,39 @@ class Reach:
return max(self.get_kp()) 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 # Guidelines
@timer @timer
@@ -335,7 +429,7 @@ class Reach:
@timer @timer
def sort_with_indexes(self, indexes: list): def sort_with_indexes(self, indexes: list):
if len(self._profiles) != len(indexes): if len(self._profiles) != len(indexes):
print("TODO: CRITICAL ERROR!") logger.critical("Indexes list do not correspond to profile list")
self._profiles = list( self._profiles = list(
map( map(
@@ -382,10 +476,10 @@ class Reach:
self._status.modified() self._status.modified()
except FileNotFoundError as e: except FileNotFoundError as e:
print(e) logger.error(e)
exception_message_box(e) exception_message_box(e)
except FileFormatError as e: except FileFormatError as e:
print(e) logger.error(e)
e.alert() e.alert()
@timer @timer
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import numpy as np import numpy as np
+164 -19
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from copy import copy, deepcopy from copy import copy, deepcopy
from tools import trace, timer from tools import trace, timer
from functools import reduce from functools import reduce
class Data(object): from Model.Tools.PamhyrDB import SQLSubModel
def __init__(self, reach = None, status = None):
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__() super(Data, self).__init__()
self._status = status self._status = status
self._reach = reach self._reach = reach
self._name = "" self._name = name
self._comment = "" self._comment = comment
self._kp = 0.0 self._kp = kp
self._discharge = 0.0 self._discharge = discharge
self._speed = 0.0 self._speed = 0.0
self._elevation = 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): def copy(self):
new = Data(self._reach, self._status) new = Data(
name = self.name,
new._name = self._name comment = self._comment,
new._comment = self._comment kp = self._kp,
new._kp = self._kp discharge = self._discharge,
new._discharge = self._discharge height = self._height,
new._speed = self._speed reach = self._reach,
new._elevation = self._elevation status = self._status,
new._height = self._height )
return new return new
@@ -81,7 +185,7 @@ class Data(object):
def _update_from_discharge(self): def _update_from_discharge(self):
min = self._update_get_min() min = self._update_get_min()
print("TODO") # print("TODO")
def __setitem__(self, key, value): def __setitem__(self, key, value):
if key == "name": if key == "name":
@@ -92,8 +196,8 @@ class Data(object):
self._kp = float(value) self._kp = float(value)
self._update_from_kp() self._update_from_kp()
elif key == "speed": elif key == "speed":
self._speed = float(value)
# Not supposed to be modified # Not supposed to be modified
self._speed = float(value)
elif key == "discharge": elif key == "discharge":
self._discharge = float(value) self._discharge = float(value)
self._update_from_discharge() self._update_from_discharge()
@@ -106,7 +210,12 @@ class Data(object):
self._status.modified() self._status.modified()
class InitialConditions(object):
class InitialConditions(SQLSubModel):
_sub_classes = [
Data
]
def __init__(self, reach = None, status = None): def __init__(self, reach = None, status = None):
super(InitialConditions, self).__init__() super(InitialConditions, self).__init__()
@@ -115,6 +224,42 @@ class InitialConditions(object):
self._reach = reach self._reach = reach
self._data = [] 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): def __len__(self):
return len(self._data) 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 -*- # -*- coding: utf-8 -*-
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.InitialConditions.InitialConditions import InitialConditions from Model.InitialConditions.InitialConditions import InitialConditions
class InitialConditionsDict(object): class InitialConditionsDict(PamhyrModelDict):
def __init__(self, status = None): _sub_classes = [
super(InitialConditionsDict, self).__init__() 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): ic = InitialConditions._sql_load(
return len(self._reach) execute,
data = data
)
def is_defined(self, reach): if ic is not None:
return reach in self._reach 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 return new
def set(self, reach, new): def _sql_save(self, execute, data = None):
self._reach[reach] = new ok = True
self._status.modified() 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): def new(self, reach):
new = InitialConditions(reach = reach, status = self._status) 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 -*- # -*- coding: utf-8 -*-
import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
class LateralContribution(object): logger = logging.getLogger()
def __init__(self, name:str = "", status = None):
class LateralContribution(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id:int = -1, name:str = "", status = None):
super(LateralContribution, self).__init__() super(LateralContribution, self).__init__()
self._status = status self._status = status
if id == -1:
self.id = LateralContribution._id_cnt
else:
self.id = id
self._name = name self._name = name
self._type = "" self._type = ""
self._edge = None self._edge = None
@@ -19,6 +49,134 @@ class LateralContribution(object):
self._header = [] self._header = []
self._types = [float, float] 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): def __len__(self):
return len(self._data) return len(self._data)
@@ -204,14 +362,17 @@ class LateralContribution(object):
new.begin_kp = self.begin_kp new.begin_kp = self.begin_kp
new.end_kp = self.end_kp new.end_kp = self.end_kp
for i, _ in self.data: for i, _ in enumerate(self.data):
new.add(i) new.add(i)
for i in [0,1]: for i in [0,1]:
for j in [0,1]: for j in [0,1]:
if self._header[i] == new.header[j]: if self._header[i] == new.header[j]:
for ind, v in self.data: 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() self._status.modified()
return new 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 -*- # -*- coding: utf-8 -*-
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.LateralContribution.LateralContribution import LateralContribution
from Model.LateralContribution.LateralContributionTypes import ( from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation, NotDefined, LateralContrib, Rain, Evaporation,
) )
class LateralContributionList(object): class LateralContributionList(PamhyrModelListWithTab):
def __init__(self, status = None): _tabs_list = ["liquid", "solid", "suspenssion"]
super(LateralContributionList, self).__init__() _sub_classes = [
LateralContribution,
]
self._status = status @classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
self._tabs = { if data is None:
"liquid" : [], data = {}
"solid" : [],
"suspenssion" : []
}
def len(self, lst): for tab in new._tabs:
return len(self._tabs[lst]) data["tab"] = tab
new._tabs[tab] = LateralContribution._sql_load(
execute, data
)
def get_tab(self, lst): return new
return self._tabs[lst].copy()
def get(self, lst, row): def _sql_save(self, execute, data = None):
return self._tabs[lst][row] execute("DELETE FROM lateral_contribution")
def set(self, lst, row, new): if data is None:
self._tabs[lst][row] = new data = {}
self._status.modified()
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): def new(self, lst, index):
n = NotDefined(status=self._status) n = NotDefined(status=self._status)
@@ -40,47 +67,6 @@ class LateralContributionList(object):
self._status.modified() self._status.modified()
return n 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): def __copy__(self):
new = LateralContributionList() 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 -*- # -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
@@ -6,8 +22,8 @@ from Model.LateralContribution.LateralContribution import LateralContribution
class NotDefined(LateralContribution): class NotDefined(LateralContribution):
def __init__(self, name:str = "", status=None): def __init__(self, id:int = -1, name:str = "", status=None):
super(NotDefined, self).__init__(name=name, status=status) super(NotDefined, self).__init__(id=id, name=name, status=status)
self._type = "ND" self._type = "ND"
self._header = ["x", "y"] self._header = ["x", "y"]
@@ -17,8 +33,8 @@ class NotDefined(LateralContribution):
return 0.0 return 0.0
class LateralContrib(LateralContribution): class LateralContrib(LateralContribution):
def __init__(self, name:str = "", status=None): def __init__(self, id:int = -1, name:str = "", status=None):
super(LateralContrib, self).__init__(name=name, status=status) super(LateralContrib, self).__init__(id=id, name=name, status=status)
self._type = "LC" self._type = "LC"
self._header = ["time", "discharge"] self._header = ["time", "discharge"]
@@ -29,8 +45,8 @@ class LateralContrib(LateralContribution):
return ["liquid"] return ["liquid"]
class Rain(LateralContribution): class Rain(LateralContribution):
def __init__(self, name:str = "", status=None): def __init__(self, id:int = -1, name:str = "", status=None):
super(Rain, self).__init__(name=name, status=status) super(Rain, self).__init__(id=id, name=name, status=status)
self._type = "RA" self._type = "RA"
self._header = ["time", "discharge"] self._header = ["time", "discharge"]
@@ -41,8 +57,8 @@ class Rain(LateralContribution):
return ["liquid"] return ["liquid"]
class Evaporation(LateralContribution): class Evaporation(LateralContribution):
def __init__(self, name:str = "", status=None): def __init__(self, id:int = -1, name:str = "", status=None):
super(Evaporation, self).__init__(name=name, status=status) super(Evaporation, self).__init__(id=id, name=name, status=status)
self._type = "EV" self._type = "EV"
self._header = ["time", "discharge"] self._header = ["time", "discharge"]
+25 -2
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from Model.Network.Node import Node from Model.Network.Node import Node
class Edge(object): class Edge(object):
def __init__(self, id:str, name:str, _id_cnt = 0
def __init__(self, id:int, name:str,
node1:Node = None, node1:Node = None,
node2:Node = None, node2:Node = None,
status = None): status = None):
@@ -11,7 +29,12 @@ class Edge(object):
self._status = status 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._name = name
self.node1 = node1 self.node1 = node1
+34 -8
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from functools import reduce from functools import reduce
@@ -14,9 +30,6 @@ class Graph(object):
self._node_ctor = Node self._node_ctor = Node
self._edge_ctor = Edge self._edge_ctor = Edge
self._nodes_ids = 0
self._edges_ids = 0
self._nodes = [] self._nodes = []
self._edges = [] self._edges = []
@@ -32,6 +45,14 @@ class Graph(object):
def edges(self): def edges(self):
return self._edges return self._edges
def enable_edges(self):
return list(
filter(
lambda e: e.is_enable(),
self._edges
)
)
def edges_names(self): def edges_names(self):
return list(map(lambda e: e.name, self._edges)) return list(map(lambda e: e.name, self._edges))
@@ -41,6 +62,13 @@ class Graph(object):
def edges_counts(self): def edges_counts(self):
return len(self._edges) 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): def is_node_exists(self, node_name):
return reduce( return reduce(
lambda acc, n: (acc or (n.name == node_name)), lambda acc, n: (acc or (n.name == node_name)),
@@ -83,12 +111,11 @@ class Graph(object):
def _create_node(self, x:float, y:float): def _create_node(self, x:float, y:float):
node = self._node_ctor( node = self._node_ctor(
self._nodes_ids, -1,
f"Node {self._nodes_ids}", "",
x = x, y = y, x = x, y = y,
status = self._status status = self._status
) )
self._nodes_ids += 1
return node return node
def _add_node(self, node): def _add_node(self, node):
@@ -118,11 +145,10 @@ class Graph(object):
def _create_edge(self, n1:Node, n2:Node): def _create_edge(self, n1:Node, n2:Node):
edge = self._edge_ctor( edge = self._edge_ctor(
self._edges_ids, -1,
"", n1, n2, "", n1, n2,
status = self._status status = self._status
) )
self._edges_ids += 1
return edge return edge
+46 -10
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from Model.Network.Point import Point from Model.Network.Point import Point
class Node(object): 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, x:float = 0.0, y:float = 0.0,
status = None): status = None):
super(Node, self).__init__() super(Node, self).__init__()
self._status = status self._status = status
self.id = id if id == -1:
self._name = name 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) self.pos = Point(x, y)
def __getitem__(self, name): def __getitem__(self, key):
ret = None ret = None
if name == "name": if key == "name":
ret = self._name ret = self._name
elif name == "id": elif key == "id":
ret = self.id ret = self.id
elif name == "pos": elif key == "pos":
ret = f"({self.pos.x},{self.pos.y})" ret = f"({self.pos.x},{self.pos.y})"
return ret return ret
def __setitem__(self, name, value): def __setitem__(self, key, value):
if name == "name": if key == "name":
self._name = value self._name = value
elif name == "id": elif key == "id":
self.id = value self.id = value
self._status.modified() self._status.modified()
@@ -38,6 +65,15 @@ class Node(object):
def name(self): def name(self):
return self._name return self._name
@property
def x(self):
return self.pos.x
@property
def y(self):
return self.pos.y
def setPos(self, x, y): def setPos(self, x, y):
self.pos.x = x self.pos.x = x
self.pos.y = y self.pos.y = y
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
class Point(object): class Point(object):
+50
View File
@@ -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]
+110
View File
@@ -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
+268 -7
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from tools import flatten
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node from Model.Network.Node import Node
from Model.Network.Edge import Edge from Model.Network.Edge import Edge
from Model.Network.Graph import Graph from Model.Network.Graph import Graph
@@ -11,12 +31,15 @@ from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
from Model.LateralContribution.LateralContributionList import LateralContributionList from Model.LateralContribution.LateralContributionList import LateralContributionList
from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
from Model.Stricklers.StricklersList import StricklersList 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.SolverParameters.SolverParametersList import SolverParametersList
from Model.SedimentLayer.SedimentLayerList import SedimentLayerList
from Solver.Solvers import solver_type_list from Solver.Solvers import solver_type_list
class RiverNode(Node): class RiverNode(Node, SQLSubModel):
_sub_classes = []
def __init__(self, id:str, name:str, def __init__(self, id:str, name:str,
x:float, y:float, x:float, y:float,
status = None): status = None):
@@ -28,6 +51,48 @@ class RiverNode(Node):
self._locker = None 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 @property
def locker(self): def locker(self):
return self._locker return self._locker
@@ -37,7 +102,12 @@ class RiverNode(Node):
self._locker = locker self._locker = locker
class RiverReach(Edge): class RiverReach(Edge, SQLSubModel):
_sub_classes = [
Reach,
FrictionList,
]
def __init__(self, id:str, name:str, def __init__(self, id:str, name:str,
node1:RiverNode = None, node1:RiverNode = None,
node2:RiverNode = None, node2:RiverNode = None,
@@ -49,17 +119,101 @@ class RiverReach(Edge):
) )
self._reach = Reach(status=self._status, parent=self) 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 @property
def reach(self): def reach(self):
return self._reach return self._reach
@property @property
def sections(self): def frictions(self):
return self._sections 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): def __init__(self, status=None):
super(River, self).__init__(status=status) super(River, self).__init__(status=status)
@@ -73,6 +227,89 @@ class River(Graph):
self._initial_conditions = InitialConditionsDict(status=self._status) self._initial_conditions = InitialConditionsDict(status=self._status)
self._stricklers = StricklersList(status=self._status) self._stricklers = StricklersList(status=self._status)
self._parameters = {} 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 @property
def boundary_condition(self): def boundary_condition(self):
@@ -86,6 +323,10 @@ class River(Graph):
def initial_conditions(self): def initial_conditions(self):
return self._initial_conditions return self._initial_conditions
@property
def sediment_layers(self):
return self._sediment_layers
@property @property
def stricklers(self): def stricklers(self):
return self._stricklers return self._stricklers
@@ -121,10 +362,30 @@ class River(Graph):
return self._parameters[solver] return self._parameters[solver]
def has_current_reach(self): def has_current_reach(self):
if self.enable_edges_counts() == 1:
return True
return self._current_reach is not None return self._current_reach is not None
def current_reach(self): def current_reach(self):
ee = self.enable_edges()
if len(ee) == 1:
return ee[0]
return self._current_reach return self._current_reach
def set_current_reach(self, reach): def set_current_reach(self, reach):
self._current_reach = 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
+22 -2
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import logging
logger = logging.getLogger()
class SavedStatus(object): class SavedStatus(object):
def __init__(self): def __init__(self):
super(SavedStatus, self).__init__() super(SavedStatus, self).__init__()
@@ -9,9 +29,9 @@ class SavedStatus(object):
return self._saved return self._saved
def save(self): def save(self):
# print(" * save") logger.debug("model status set as saved")
self._saved = True self._saved = True
def modified(self): def modified(self):
# print(" * modified ...") # logger.debug("model status set as modified")
self._saved = False self._saved = False
-95
View File
@@ -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()
-74
View File
@@ -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()
+339
View File
@@ -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
+18 -2
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import pickle import pickle
class Serializable(): class Serializable():
def __init__(self, filename): def __init__(self):
self.filename = filename return
@classmethod @classmethod
def open(cls, filename): 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 -*- # -*- coding: utf-8 -*-
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Solver.Solvers import solver_type_list
class Parameter(object): from Model.Tools.PamhyrList import PamhyrModelList
def __init__(self, status = None):
class Parameter():
def __init__(self,
name = "", value = "",
status = None):
self._status = status self._status = status
self._name = "" self._name = name
self._value = "" self._value = value
@property @property
def name(self): def name(self):
@@ -42,75 +63,193 @@ class Parameter(object):
return new return new
class SolverParametersList(object): class SolverParametersList(PamhyrModelList):
def __init__(self, solver_type = None, status = None): def __init__(self, solver_type = None, status = None):
super(SolverParametersList, self).__init__() super(SolverParametersList, self).__init__()
self._status = status self._status = status
self._parameters = list( self._solver = solver_type
self._lst = list(
map( map(
lambda t: Parameter.from_tuple(t, self._status), lambda t: Parameter.from_tuple(t, self._status),
solver_type.default_parameters() 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 @property
def parameters(self): def parameters(self):
return self._parameters.copy() return self.lst
def get(self, index): 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): 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() self._status.modified()
def new(self, index): def new(self, index):
n = Parameter(status = self._status) n = Parameter(status = self._status)
self._parameters.insert(index, n) self._lst.insert(index, n)
self._status.modified() self._status.modified()
return n 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()
+107 -9
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from tools import trace, timer from tools import trace, timer
class Stricklers(object): from Model.Tools.PamhyrDB import SQLSubModel
def __init__(self, status = None):
super(Stricklers, self).__init__()
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._status = status
self._name = "" if id == -1:
self._comment = "" self.id = Stricklers._id_cnt
else:
self.id = id
self._minor = 35 Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
self._medium = 15
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): def __str__(self):
if self._name != "": if self._name != "":
@@ -42,7 +140,7 @@ class Stricklers(object):
@minor.setter @minor.setter
def minor(self, minor): def minor(self, minor):
self._minor = int(minor) self._minor = float(minor)
@property @property
def medium(self): def medium(self):
@@ -50,4 +148,4 @@ class Stricklers(object):
@medium.setter @medium.setter
def medium(self, medium): def medium(self, medium):
self._medium = int(medium) self._medium = float(medium)
+48 -43
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from tools import trace, timer from tools import trace, timer
from Model.Saved import SavedStatus from Model.Saved import SavedStatus
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Stricklers.Stricklers import Stricklers from Model.Stricklers.Stricklers import Stricklers
class StricklersList(object): class StricklersList(PamhyrModelList):
def __init__(self, status = None): _sub_classes = [
if status is None: Stricklers,
status = SavedStatus() ]
self._status = status
self._stricks = [] @classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
def __len__(self): @classmethod
return len(self._stricks) 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 @property
def stricklers(self): def stricklers(self):
return self._stricks.copy() return self.lst
def get(self, index): def new(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):
s = Stricklers(status = self._status) s = Stricklers(status = self._status)
self.insert(index, s) self.insert(index, s)
return 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 @timer
def sort(self, reverse:bool = False, key=None): def sort(self, reverse:bool = False, key=None):
f = lambda st: st.name f = lambda st: st.name
if key is not None: if key is not None:
f = key f = key
self._stricks = sorted( self._lst = sorted(
self._stricks, self._lst,
key = f, key = f,
reverse = reverse, reverse = reverse,
) )
+174 -15
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import os import os
import shutil
import logging
from datetime import datetime from datetime import datetime
from tools import timer, timestamp
from Model.Tools.PamhyrDB import SQLModel
from Model.Saved import SavedStatus from Model.Saved import SavedStatus
from Model.Serializable import Serializable from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError
from Model.River import River from Model.River import River
from Checker.Study import * from Checker.Study import *
class Study(Serializable): logger = logging.getLogger()
def __init__(self):
# Serialization information class Study(SQLModel):
super(Study, self).__init__("") _sub_classes = [
self.filename = "" 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() self.status = SavedStatus()
@@ -25,12 +57,13 @@ class Study(Serializable):
self._time_system = "time" self._time_system = "time"
self._date = datetime.fromtimestamp(0) self._date = datetime.fromtimestamp(0)
self.creation_date = datetime.now() if init_new:
self.last_modification_date = datetime.now() # Study data
self.last_save_date = datetime.now() self._river = River(status = self.status)
else:
self._init_db_file(filename, is_new = False)
# Study data self._old_save_id = 0
self._river = River(status = self.status)
@classmethod @classmethod
def checkers(cls): def checkers(cls):
@@ -53,9 +86,31 @@ class Study(Serializable):
return self.status.is_saved() return self.status.is_saved()
def save(self): 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.last_save_date = datetime.now()
self.status.save()
self._save() self._save()
self.status.save()
@property @property
def name(self): def name(self):
@@ -66,6 +121,16 @@ class Study(Serializable):
self._name = str(name) self._name = str(name)
self.status.modified() 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 @property
def time_system(self): def time_system(self):
return self._time_system return self._time_system
@@ -88,9 +153,9 @@ class Study(Serializable):
self._date = timestamp self._date = timestamp
self.status.modified() self.status.modified()
@classmethod # @classmethod
def new(cls): # def new(cls):
return cls() # return cls()
@classmethod @classmethod
def new(cls, name, description, date = None): def new(cls, name, description, date = None):
@@ -101,3 +166,97 @@ class Study(Serializable):
me.use_date() me.use_date()
me.date = date me.date = date
return me 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()
+184
View File
@@ -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 '&#39;' to preserve SQL injection
if type(value) == str:
value = value.replace("'", "&#39;")
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)
+74
View File
@@ -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)
+229
View File
@@ -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()
+36
View File
@@ -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)
+34
View File
@@ -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
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
# a lancer depuis src # a lancer depuis src
+138 -40
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import os import os
import logging
from tools import timer
try: try:
# Installation allow Unix-like signal
from signal import SIGTERM, SIGSTOP, SIGCONT from signal import SIGTERM, SIGSTOP, SIGCONT
_signal = True _signal = True
except: except:
@@ -12,6 +32,11 @@ from enum import Enum
from Model.Except import NotImplementedMethodeError 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): class STATUS(Enum):
NOT_LAUNCHED = -1 NOT_LAUNCHED = -1
STOPED = 0 STOPED = 0
@@ -19,6 +44,8 @@ class STATUS(Enum):
PAUSED = 5 PAUSED = 5
class AbstractSolver(object): class AbstractSolver(object):
_type = ""
def __init__(self, name): def __init__(self, name):
super(AbstractSolver, self).__init__() super(AbstractSolver, self).__init__()
@@ -41,6 +68,9 @@ class AbstractSolver(object):
self._process = None self._process = None
self._output = None self._output = None
# Last study running
self._study = None
def __str__(self): def __str__(self):
return f"{self._name} : {self._type} : {self._description}" return f"{self._name} : {self._type} : {self._description}"
@@ -60,6 +90,7 @@ class AbstractSolver(object):
("all_init_time", "000:00:00:00"), ("all_init_time", "000:00:00:00"),
("all_final_time", "999:99:00:00"), ("all_final_time", "999:99:00:00"),
("all_timestep", "300.0"), ("all_timestep", "300.0"),
("all_command_line_arguments", ""),
] ]
return lst return lst
@@ -127,6 +158,17 @@ class AbstractSolver(object):
def export(self, study, repertory, qlog = None): def export(self, study, repertory, qlog = None):
raise NotImplementedMethodeError(self, self.export) 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): def input_param(self):
"""Return input command line parameter(s) """Return input command line parameter(s)
@@ -143,25 +185,82 @@ class AbstractSolver(object):
""" """
raise NotImplementedMethodeError(self, self.log_file) raise NotImplementedMethodeError(self, self.log_file)
###########
# RESULTS #
###########
@timer
def results(self, study, repertory, qlog = None):
results = Results(study = study)
return results
####### #######
# Run # # 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 == "": if self._cmd_input == "":
self._run_next() self._run_next(study)
return True return True
cmd = self._cmd_input cmd = self._cmd_input
cmd = cmd.replace("@path", self._path_input) exe, args = self._format_command(study, cmd, self._path_input)
cmd = cmd.replace("@input", self.input_param())
cmd = cmd.replace("@dir", self._process.workingDirectory())
print(f"! {cmd}") if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
cmd = cmd.split() logger.info(error)
exe = cmd[0] return error
args = cmd[1:]
self._process.start( self._process.start(
exe, args, exe, args,
@@ -169,22 +268,18 @@ class AbstractSolver(object):
return True return True
def run_solver(self): def run_solver(self, study):
print("run solver")
if self._cmd_solver == "": if self._cmd_solver == "":
self._run_next() self._run_next(study)
return True return True
cmd = self._cmd_solver cmd = self._cmd_solver
cmd = cmd.replace("@path", self._path_solver) exe, args = self._format_command(study, cmd, self._path_solver)
cmd = cmd.replace("@input", self.input_param())
cmd = cmd.replace("@dir", self._process.workingDirectory())
print(f"! {cmd}") if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
cmd = cmd.split() logger.info(error)
exe = cmd[0] return error
args = cmd[1:]
self._process.start( self._process.start(
exe, args, exe, args,
@@ -193,21 +288,18 @@ class AbstractSolver(object):
self._status = STATUS.RUNNING self._status = STATUS.RUNNING
return True return True
def run_output_data_fomater(self): def run_output_data_fomater(self, study):
if self._cmd_output == "": if self._cmd_output == "":
self._run_next() self._run_next(study)
return True return True
cmd = self._cmd_output cmd = self._cmd_output
cmd = cmd.replace("@path", self._path_output) exe, args = self._format_command(study, cmd, self._path_output)
cmd = cmd.replace("@input", self.input_param())
cmd = cmd.replace("@dir", self._process.workingDirectory())
print(f"! {cmd}") if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
cmd = cmd.split() logger.info(error)
exe = cmd[0] return error
args = cmd[1:]
self._process.start( self._process.start(
exe, args, exe, args,
@@ -221,27 +313,29 @@ class AbstractSolver(object):
for x in s.split('\n'): for x in s.split('\n'):
self._output.put(x) self._output.put(x)
def _run_next(self): def _run_next(self, study):
self._step += 1 self._step += 1
if self._step < len(self._runs): 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: else:
self._status = STATUS.STOPED 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: if self._output is not None:
self._output.put(exit_code) 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: if process is not None:
self._process = process self._process = process
if output_queue is not None: if output_queue is not None:
self._output = output_queue self._output = output_queue
self._process.readyRead.connect(self._data_ready) 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._runs = [
self.run_input_data_fomater, self.run_input_data_fomater,
@@ -250,7 +344,9 @@ class AbstractSolver(object):
] ]
self._step = 0 self._step = 0
# Run first step # Run first step
self._runs[0]() res = self._runs[0](study)
if res is not True:
self._output.put(res)
def kill(self): def kill(self):
if self._process is None: if self._process is None:
@@ -260,14 +356,15 @@ class AbstractSolver(object):
self._status = STATUS.STOPED self._status = STATUS.STOPED
return True return True
def start(self, process = None): def start(self, study, process = None):
if _signal: if _signal:
# Solver is PAUSED, so continue execution
if self._status == STATUS.PAUSED: if self._status == STATUS.PAUSED:
os.kill(self._process.pid(), SIGCONT) os.kill(self._process.pid(), SIGCONT)
self._status = STATUS.RUNNING self._status = STATUS.RUNNING
return True return True
self.run(process) self.run(study, process)
return True return True
def pause(self): def pause(self):
@@ -275,6 +372,7 @@ class AbstractSolver(object):
if self._process is None: if self._process is None:
return False return False
# Send SIGSTOP to PAUSED solver
os.kill(self._process.pid(), SIGSTOP) os.kill(self._process.pid(), SIGSTOP)
self._status = STATUS.PAUSED self._status = STATUS.PAUSED
return True return True
+18
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from Solver.ASolver import ( from Solver.ASolver import (
@@ -5,6 +21,8 @@ from Solver.ASolver import (
) )
class GenericSolver(AbstractSolver): class GenericSolver(AbstractSolver):
_type = "generic"
def __init__(self, name): def __init__(self, name):
super(GenericSolver, self).__init__(name) super(GenericSolver, self).__init__(name)
+555 -87
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import os import os
import logging
import numpy as np
from tools import timer from tools import timer
from Solver.ASolver import AbstractSolver from Solver.ASolver import AbstractSolver
from Checker.Mage import MageNetworkGraphChecker 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): class Mage(AbstractSolver):
_type = "mage"
def __init__(self, name): def __init__(self, name):
super(Mage, self).__init__(name) super(Mage, self).__init__(name)
@@ -27,11 +61,11 @@ class Mage(AbstractSolver):
("mage_timestep_tra", "3600"), ("mage_timestep_tra", "3600"),
("mage_timestep_bin", "0"), ("mage_timestep_bin", "0"),
# ("mage_timestep_melissa", "0"), # ("mage_timestep_melissa", "0"),
("mage_implication", "0.70"), ("mage_implicitation", "0.70"),
("mage_continuity_discretization", "S"), ("mage_continuity_discretization", "S"),
("mage_qsj_discretization", "B"), ("mage_qsj_discretization", "B"),
("mage_stop_criterion_iterations", "R"), ("mage_stop_criterion_iterations", "R"),
("mage_iter_type", "0"), ("mage_iteration_type", "0"),
("mage_smooth_coef", "0"), ("mage_smooth_coef", "0"),
("mage_cfl_max", "-1."), ("mage_cfl_max", "-1."),
("mage_min_height", "0.1"), ("mage_min_height", "0.1"),
@@ -63,19 +97,31 @@ class Mage(AbstractSolver):
# Export # # Export #
########## ##########
def cmd_args(self, study):
l = super(Mage, self).cmd_args(study)
l.append("-r")
return l
def input_param(self): def input_param(self):
return "0.REP" name = self._study.name
return f"{name}.REP"
def log_file(self): def log_file(self):
return "0.TRA" name = self._study.name
return f"{name}.TRA"
@timer @timer
def _export_ST(self, study, repertory, qlog): def _export_ST(self, study, repertory, qlog, name="0"):
files = [] files = []
if qlog is not None: if qlog is not None:
qlog.put("Export ST file") qlog.put("Export ST file")
os.makedirs(os.path.join(repertory, "net"), exist_ok=True)
gra_file = f"{name}.GRA"
# Write header # Write header
edges = study.river.edges() edges = study.river.edges()
edges = list( edges = list(
@@ -90,87 +136,123 @@ class Mage(AbstractSolver):
if edge._name == "": if edge._name == "":
name = f"Reach_{edge.id}" name = f"Reach_{edge.id}"
with open(os.path.join(repertory, f"{name}.ST"), "w+") as f: with mage_file_open(os.path.join(repertory, "net", f"{name}.ST"), "w+") as f:
files.append(f"{name}.ST") files.append(str(os.path.join("net", f"{name}.ST")))
f.write("* This file is generate by PAMHYR, please don't modify\n")
cnt_num = 1
for profile in edge.reach.profiles: for profile in edge.reach.profiles:
num = f"{profile.num:>6}" num = f"{cnt_num:>6}"
c1 = f"{profile.code1:>6}" c1 = f"{profile.code1:>6}"
c2 = f"{profile.code2:>6}" c2 = f"{profile.code2:>6}"
t = f"{len(profile.points):>6}" t = f"{len(profile.points):>6}"
kp = f"{profile.kp:>13.4f}" 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: for point in profile.points:
x = f"{point.x:>13.4f}" x = f"{point.x:>13.4f}"
y = f"{point.y:>13.4f}" y = f"{point.y:>13.4f}"
z = f"{point.z:>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") f.write(f" 999.9990 999.9990 999.9990\n")
return files return files
@timer @timer
def _export_BC(self, bound, repertory, qlog): def _export_BC(self, t, bounds, repertory, qlog, name="0"):
files = [] files = []
t = "XX" if len(bounds) == 0:
if bound.bctype == "ZD": return files
t = "AVA"
elif bound.bctype == "TD":
t = "HYD"
elif bound.bctype == "TZ":
t = "LIM"
else:
return None
if qlog is not None: if qlog is not None:
qlog.put(f"Export {t} file") qlog.put(f"Export {t} file")
with open(os.path.join(repertory, f"0.{t}"), "w+") as f: with mage_file_open(os.path.join(repertory, f"{name}.{t}"), "w+") as f:
files.append(f"0.{t}") 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") for d in bound.data:
f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n") v0 = d[0]
f.write(f"${name}\n") v1 = d[1]
header = bound.header
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
for d in bound.data: if t in ["HYD", "QSO", "LIM"]:
f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n") v0 /= 60 # Convert first column to minute
f.write(f"{v0:10}{v1:10}\n")
return files return files
@timer @timer
def _export_bound_cond(self, study, repertory, qlog): def _export_bound_cond(self, study, repertory, qlog, name="0"):
files = [] files = []
lst = study.river.boundary_condition lst = study.river.boundary_condition
AVA = []
HYD = []
LIM = []
QSO = []
for tab in ["liquid", "solid", "suspenssion"]: for tab in ["liquid", "solid", "suspenssion"]:
for bound in lst.get_tab(tab): 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 return files
# @timer # @timer
# def _export_LC(self, lateral, repertory, qlog): # def _export_LC(self, lateral, repertory, qlog, name="0"):
# files = [] # files = []
# if qlog is not None: # if qlog is not None:
# qlog.put(f"Export LAT file") # qlog.put(f"Export LAT file")
# with open(os.path.join(repertory, f"0.LAT"), "w+") as f: # with mage_file_open(os.path.join(repertory, f"{name}.LAT"), "w+") as f:
# files.append(f"0.LAT") # files.append(f"{name}.LAT")
# f.write("* This file is generate by PAMHYR, please don't modify\n")
# name = f"{lateral.node.id:3}".replace(" ", "x") # name = f"{lateral.node.id:3}".replace(" ", "x")
# f.write(f"* {lateral.node.name} ({name}) {lateral.bctype}\n") # 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") # f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
# for d in lateral.data: # 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 # return files
# @timer # @timer
# def _export_lateral_contrib(self, study, repertory, qlog): # def _export_lateral_contrib(self, study, repertory, qlog, name="0"):
# files = [] # files = []
# lst = study.river.lateral_contribution # lst = study.river.lateral_contribution
@@ -195,16 +277,15 @@ class Mage(AbstractSolver):
# return files # return files
@timer @timer
def _export_RUG(self, study, repertory, qlog): def _export_RUG(self, study, repertory, qlog, name="0"):
files = [] files = []
if qlog is not None: if qlog is not None:
qlog.put("Export RUG file") qlog.put("Export RUG file")
# Write header # Write header
with open(os.path.join(repertory, "0.RUG"), "w+") as f: with mage_file_open(os.path.join(repertory, f"{name}.RUG"), "w+") as f:
files.append("0.RUG") files.append(f"{name}.RUG")
f.write("* This file is generate by PAMHYR, please don't modify\n")
edges = study.river.edges() edges = study.river.edges()
edges = list( edges = list(
@@ -216,17 +297,17 @@ class Mage(AbstractSolver):
id = 1 id = 1
for edge in edges: for edge in edges:
sections = edge.sections frictions = edge.frictions
for section in sections.sections: for friction in frictions.frictions:
num = f"{id:>3}" num = f"{id:>3}"
bkp = f"{section.begin_kp:>10.3f}" bkp = f"{friction.begin_kp:>10.3f}"
ekp = f"{section.end_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") # print("TODO")
strickler = section.begin_strickler strickler = friction.begin_strickler
coef_1 = f"{strickler.minor:>10.3f}" coef_1 = f"{strickler.minor:>10.3f}"
coef_2 = f"{strickler.medium:>10.3f}" coef_2 = f"{strickler.medium:>10.3f}"
@@ -237,20 +318,15 @@ class Mage(AbstractSolver):
return files return files
@timer @timer
def _export_INI(self, study, repertory, qlog): def _export_INI(self, study, repertory, qlog, name="0"):
files = [] files = []
if qlog is not None: if qlog is not None:
qlog.put("Export INI file") qlog.put("Export INI file")
# Write header # Write header
with open(os.path.join(repertory, "0.INI"), "w+") as f: with mage_file_open(os.path.join(repertory, f"{name}.INI"), "w+") as f:
files.append("0.INI") has_ini = False
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")
id = 1 id = 1
reachs = study.river.edges() reachs = study.river.edges()
reachs = list( 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 id = 1
for reach in reachs: for reach in reachs:
cond = study.river.initial_conditions.get(reach) cond = study.river.initial_conditions.get(reach)
data = cond.data data = cond.data
if len(data) == 0:
continue
has_ini = True
id_sec = 1 id_sec = 1
for d in data: for d in data:
@@ -278,38 +362,67 @@ class Mage(AbstractSolver):
id += 1 id += 1
if has_ini:
files.append(f"{name}.INI")
return files return files
@timer @timer
def _export_REP(self, study, repertory, files, qlog): def _export_REP(self, study, repertory, files, qlog, name="0"):
if qlog is not None: if qlog is not None:
qlog.put("Export REP file") qlog.put("Export REP file")
# Write header # 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("confirmation=non\n")
f.write("* This file is generate by PAMHYR, please don't modify\n")
for file in files: for file in files:
EXT = file.split('.')[1] 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("* OUTPUT\n")
f.write(f"TRA 0.TRA\n") f.write(f"TRA {name}.TRA\n")
f.write(f"BIN 0.BIN\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 @timer
def export(self, study, repertory, qlog = None): 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 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 # # MAGE 7 #
########## ##########
class Mage7(Mage): class Mage7(Mage):
_type = "mage7"
def __init__(self, name): def __init__(self, name):
super(Mage7, self).__init__(name) super(Mage7, self).__init__(name)
@@ -326,6 +439,8 @@ class Mage7(Mage):
########## ##########
class Mage8(Mage): class Mage8(Mage):
_type = "mage8"
def __init__(self, name): def __init__(self, name):
super(Mage8, self).__init__(name) super(Mage8, self).__init__(name)
@@ -340,23 +455,43 @@ class Mage8(Mage):
i = names.index("mage_precision_reduction_factor_Q") i = names.index("mage_precision_reduction_factor_Q")
lst.insert(i+1, ("mage_precision_reduction_factor_r", "1")) 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 return lst
########## ##########
# Export # # 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 @timer
def _export_PAR(self, study, repertory, qlog = None): def _export_PAR(self, study, repertory, qlog = None, name = "0"):
files = [] files = []
if qlog is not None: if qlog is not None:
qlog.put("Export PAR file") qlog.put("Export PAR file")
with open(os.path.join(repertory, "0.PAR"), "w+") as f: with mage_file_open(os.path.join(repertory, f"{name}.PAR"), "w+") as f:
files.append("0.PAR") files.append(f"{name}.PAR")
f.write("* This file is generate by PAMHYR, please don't modify\n")
params = study.river.get_params(self.type).parameters params = study.river.get_params(self.type).parameters
for p in params: for p in params:
@@ -365,22 +500,25 @@ class Mage8(Mage):
.replace("mage_", "") .replace("mage_", "")
value = p.value 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 return files
@timer @timer
def _export_NET(self, study, repertory, qlog = None): def _export_NET(self, study, repertory, qlog = None, name="0"):
files = [] files = []
if qlog is not None: if qlog is not None:
qlog.put("Export NET file") qlog.put("Export NET file")
with open(os.path.join(repertory, "0.NET"), "w+") as f: with mage_file_open(os.path.join(repertory, f"{name}.NET"), "w+") as f:
files.append("0.NET") files.append(f"{name}.NET")
f.write("* This file is generate by PAMHYR, please don't modify\n")
edges = study.river.edges() edges = study.river.edges()
edges = list( edges = list(
@@ -399,22 +537,352 @@ class Mage8(Mage):
n1 = f"{e.node1.id:3}".replace(" ", "x") n1 = f"{e.node1.id:3}".replace(" ", "x")
n2 = f"{e.node2.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") f.write(f"{id} {n1} {n2} {file}\n")
return files return files
@timer @timer
def export(self, study, repertory, qlog = None): def _export_QSO(self, bounds, repertory, qlog, name="0"):
files = [] files = []
self._export_ST(study, repertory, qlog) if len(bounds) == 0:
files = files + self._export_PAR(study, repertory, qlog) return files
files = files + self._export_NET(study, repertory, qlog)
files = files + self._export_bound_cond(study, repertory, qlog) if qlog is not None:
files = files + self._export_RUG(study, repertory, qlog) qlog.put(f"Export QSO file")
files = files + self._export_INI(study, repertory, qlog)
self._export_REP(study, repertory, files, qlog) 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 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
+16
View File
@@ -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 -*- # -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication from PyQt5.QtCore import QCoreApplication
+1
View File
@@ -0,0 +1 @@
../VERSION
+79 -7
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import os import os
import csv import csv
import logging
from io import StringIO from io import StringIO
from datetime import datetime from datetime import datetime
@@ -17,7 +34,8 @@ from PyQt5.QtWidgets import (
QTimeEdit, QSpinBox, QTextEdit, QTimeEdit, QSpinBox, QTextEdit,
QRadioButton, QComboBox, QFileDialog, QRadioButton, QComboBox, QFileDialog,
QMessageBox, QTableView, QAction, QMessageBox, QTableView, QAction,
QDateTimeEdit, QWidget, QDateTimeEdit, QWidget, QPlainTextEdit,
QLabel,
) )
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QTime, QDateTime, QTime, QDateTime,
@@ -26,6 +44,8 @@ from PyQt5.uic import loadUi
from Model.Except import ClipboardFormatError from Model.Except import ClipboardFormatError
logger = logging.getLogger()
class WindowToolKit(object): class WindowToolKit(object):
def __init__(self, parent=None): def __init__(self, parent=None):
super(WindowToolKit, self).__init__() super(WindowToolKit, self).__init__()
@@ -58,13 +78,14 @@ class WindowToolKit(object):
return header, values 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 """Open a new file dialog and send result to callback function
Args: Args:
select_file: Select a file if True, else select a dir select_file: Select a file if True, else select a dir
callback: The callback function with one arguments, files callback: The callback function with one arguments, files
selection list selection list
directory: Defaut directory
Returns: Returns:
The returns of callback The returns of callback
@@ -77,6 +98,8 @@ class WindowToolKit(object):
mode = QFileDialog.FileMode.Directory mode = QFileDialog.FileMode.Directory
dialog.setFileMode(mode) dialog.setFileMode(mode)
if directory is not None:
dialog.setDirectory(directory)
if dialog.exec_(): if dialog.exec_():
file_names = dialog.selectedFiles() file_names = dialog.selectedFiles()
@@ -127,6 +150,30 @@ class ASubWindowFeatures(object):
return qtype 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): def set_line_edit_text(self, name:str, text:str):
"""Set text of line edit component """Set text of line edit component
@@ -140,7 +187,7 @@ class ASubWindowFeatures(object):
try: try:
self.find(QLineEdit, name).setText(text) self.find(QLineEdit, name).setText(text)
except AttributeError as e: except AttributeError as e:
print(e) logger.error(e)
def get_line_edit_text(self, name:str): def get_line_edit_text(self, name:str):
"""Get text of line edit component """Get text of line edit component
@@ -176,6 +223,29 @@ class ASubWindowFeatures(object):
""" """
return self.find(QTextEdit, name).toHtml() 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): def set_check_box(self, name:str, checked:bool):
"""Set status of checkbox component """Set status of checkbox component
@@ -395,10 +465,12 @@ class ASubWindowFeatures(object):
class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit): class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit):
def __init__(self, name="", ui="dummy", parent=None): def __init__(self, name="", ui="dummy", parent=None):
super(ASubMainWindow, self).__init__(parent=parent) super(ASubMainWindow, self).__init__(parent=parent)
self.ui = loadUi( if ui is not None:
os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"), self.ui = loadUi(
self os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"),
) self
)
self.name = name self.name = name
self.parent = parent self.parent = parent
if self.parent is not None: if self.parent is not None:
+55
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import os
import logging
from View.ASubWindow import ASubWindow from View.ASubWindow import ASubWindow
from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
logger = logging.getLogger()
class AboutWindow(ASubWindow): 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): def __init__(self, title="About", parent=None):
super(AboutWindow, self).__init__(name=title, ui="about", parent=parent) super(AboutWindow, self).__init__(name=title, ui="about", parent=parent)
self.ui.setWindowTitle(title) 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)
+23
View File
@@ -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 -*- # -*- coding: utf-8 -*-
import logging
from datetime import datetime from datetime import datetime
from tools import timer, trace from tools import timer, trace
@@ -13,6 +31,8 @@ from View.BoundaryCondition.Edit.translate import *
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
logger = logging.getLogger()
class Plot(APlot): class Plot(APlot):
def __init__(self, canvas=None, data=None, def __init__(self, canvas=None, data=None,
mode = "time", toolbar=None): mode = "time", toolbar=None):
@@ -25,6 +45,9 @@ class Plot(APlot):
self._mode = mode self._mode = mode
def custom_ticks(self): def custom_ticks(self):
if self.data.header[0] != "time":
return
t0 = datetime.fromtimestamp(0) t0 = datetime.fromtimestamp(0)
nb = len(self.data.data) nb = len(self.data.data)
mod = int(nb / 5) mod = int(nb / 5)
Loaded 100 of 239 files, more files were not shown because too many files have changed in this diff. Show more