coastal-dynamics 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coastal_dynamics-0.0.2/LICENSE +29 -0
- coastal_dynamics-0.0.2/PKG-INFO +214 -0
- coastal_dynamics-0.0.2/README.md +200 -0
- coastal_dynamics-0.0.2/pyproject.toml +61 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/__init__.py +1 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/geometries.py +121 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/hidden.py +204 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/maps.py +31 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/questions/__init__.py +0 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/questions/factory.py +87 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/questions/multiple_choice.py +130 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/questions/multiple_selection.py +109 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/questions/numeric.py +86 -0
- coastal_dynamics-0.0.2/src/coastal_dynamics/visualization.py +58 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, Floris Calkoen
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
7
|
+
are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
13
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
14
|
+
other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from this
|
|
18
|
+
software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
23
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
24
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
25
|
+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
26
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
27
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
28
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
29
|
+
OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: coastal-dynamics
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Jupyterbook for Delft Technical University Coastal Systems
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Floris Calkoen
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Requires-Dist: pytest (>=7.4.0,<8.0.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
# Coastal Systems Open Codebook
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Tutorial notebooks for Delft University of Technology Coastal Systems course. The book is
|
|
20
|
+
available [online](https://floriscalkoen.github.io/CoastalCodebook/intro.html) in both
|
|
21
|
+
PDF and markdown.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
For the tuturial sessions we will use an interactive computing environment, that is built
|
|
26
|
+
on the [Jupyter]() ecosystem and mostly rely on software that is supported by numfocus. We will communicate the tutorial content
|
|
27
|
+
using `git` version control and provide instructions on how to do so using the GitHub client. In the subsections
|
|
28
|
+
that follow we talk you through the three configurations steps.
|
|
29
|
+
|
|
30
|
+
### 1. Git
|
|
31
|
+
|
|
32
|
+
If you are not familiar with using Git, please have a look this short but excellent
|
|
33
|
+
[introduction](https://earth-env-data-science.github.io/lectures/environment/intro_to_git.html)
|
|
34
|
+
first.
|
|
35
|
+
|
|
36
|
+
1. Please refer to the [GitHub Client documentation](https://desktop.github.com/) to
|
|
37
|
+
install the GitHub client, or see [these
|
|
38
|
+
instructions](https://github.com/git-guides/install-git) to install git using the
|
|
39
|
+
command line.
|
|
40
|
+
2. Clone this repository to your local
|
|
41
|
+
computer using either of the following options.
|
|
42
|
+
|
|
43
|
+
1. **GitHub client**: Browse to the
|
|
44
|
+
[webpage](https://github.com/FlorisCalkoen/CoastalCodebook), click on the green "Code"
|
|
45
|
+
button and select "Open with GitHub Desktop"; or simply paste the URL into the GitHub
|
|
46
|
+
client "clone repository" menu.
|
|
47
|
+
|
|
48
|
+
2. **Bash shell**: If you have a bash terminal available, assuming that git [is
|
|
49
|
+
configured](https://docs.github.com/en/get-started/getting-started-with-git), you
|
|
50
|
+
can simply run: ` git clone https://github.com/FlorisCalkoen/CoastalCodebook.git`.
|
|
51
|
+
|
|
52
|
+
3. GitHub client does not install the underlying git software on your machine. Follow [these
|
|
53
|
+
instructions](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git)
|
|
54
|
+
to install git on your machine.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
By these steps, the files that are hosted at GitHub are "pulled" to your machine. You can
|
|
58
|
+
check that by opening a file explorer and going to the path where you cloned the
|
|
59
|
+
directory. The files that you find there should reflect what's on the GitHub page.
|
|
60
|
+
But we can't we do anything with the files yet, as we don't have the software that can
|
|
61
|
+
understand the code, so we will continue with installing a package manager.
|
|
62
|
+
|
|
63
|
+
### 2. Mamba package manager
|
|
64
|
+
|
|
65
|
+
If you're not familiar with managing Python environments, please have a look at this
|
|
66
|
+
[introduction](https://earth-env-data-science.github.io/lectures/environment/python_environments.html?highlight=conda)
|
|
67
|
+
first. The bottom line is that it is good practice to manage your software environments
|
|
68
|
+
to avoid dependency conflicts. For the tutorial notebooks, we recommend to use the
|
|
69
|
+
lightweight package manager `mambaforge`. The instructions to install this package
|
|
70
|
+
manager can be found in [their
|
|
71
|
+
documentation](https://mamba.readthedocs.io/en/latest/installation.html), in which they
|
|
72
|
+
refer to the [Conda Forge GitHub](https://github.com/conda-forge/miniforge#mambaforge)
|
|
73
|
+
page to download the software.
|
|
74
|
+
|
|
75
|
+
#### Windows
|
|
76
|
+
|
|
77
|
+
1. Download the mambaforge executable file for Windows from [Miniforge GitHub
|
|
78
|
+
page](https://github.com/conda-forge/miniforge#mambaforge). On that page there are also
|
|
79
|
+
binaries for Mac and Linux; and for `conda` package managers, so make sure you download
|
|
80
|
+
the `mambaforge` executable file for Windows. Install the executable by clicking on it;
|
|
81
|
+
you can stay with the default settings by just clicking next through the installation
|
|
82
|
+
client.
|
|
83
|
+
2. Now that mambaforge is installed, you can open a `Miniforge Prompt`. You can open this
|
|
84
|
+
shell by opening the start window and search for "Miniforge".
|
|
85
|
+
|
|
86
|
+
**Known issues**: Some users have their firewalls configured in such way that the
|
|
87
|
+
mambaforge installation is blocked. If you have trouble installing mambaforge, please make
|
|
88
|
+
sure to temporarily disable your firewall.
|
|
89
|
+
|
|
90
|
+
#### Unix like - Mac and Linux
|
|
91
|
+
1. We recommend to install Mambaforge on Linux and Mac using a terminal. On Mac, you can
|
|
92
|
+
open a terminal by searching for "terminal" or "iterm". On Linux the hotkey to open a
|
|
93
|
+
terminal is "cntrl + shift + t". The commands to
|
|
94
|
+
install the package manager are copied from their documentation and can be run by
|
|
95
|
+
copying the commands below over to your terminal and pressing enter:
|
|
96
|
+
```bash
|
|
97
|
+
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh"
|
|
98
|
+
bash Mambaforge-$(uname)-$(uname -m).sh
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
2. Accept the user agreements, and allow the installation script to edit your profile
|
|
102
|
+
file. The profile file (`~/.bashrc` on Linux or possibly `~/.zshrc` on Mac) is the
|
|
103
|
+
first script which is being executed when you open a new terminal. The installation
|
|
104
|
+
script will add a few lines to that file to make the `mamba` command available every
|
|
105
|
+
time open a new terminal.
|
|
106
|
+
3. Close the terminal.
|
|
107
|
+
|
|
108
|
+
### 3. Software environments
|
|
109
|
+
To run the tutorial notebooks we need several packages. To avoid dependency conflicts it
|
|
110
|
+
is good practice to seperate your environments; that was the reason for installing a
|
|
111
|
+
package manager. Now that we have our package manager we will create the software
|
|
112
|
+
environments. We will create one environment that runs the JupyterLab IDE, including
|
|
113
|
+
several extensions; and another one that contains the packages that we need for the
|
|
114
|
+
tutorials.
|
|
115
|
+
|
|
116
|
+
1. Now that mambaforge is available on your machine, open a terminal. On Windows you
|
|
117
|
+
should open the Miniforge prompt, which you can find by searching for it in the Start
|
|
118
|
+
window. On Mac you can open a terminal by searching for "terminal" or "iterm". For
|
|
119
|
+
Linux it's "cntrl + shift + t".
|
|
120
|
+
2. You can check if mamba was installed by running the following command in the terminal:
|
|
121
|
+
```bash
|
|
122
|
+
mamba --version
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
It should output something like:
|
|
126
|
+
|
|
127
|
+
```console
|
|
128
|
+
~ (base) mamba --version
|
|
129
|
+
mamba 1.1.0
|
|
130
|
+
conda 22.9.0
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. Now that mambaforge is installed, navigate in the terminal to the directory
|
|
134
|
+
where you cloned the GitHub CoastalCodeBook repository. You can navigate the terminal
|
|
135
|
+
using `cd`, which stands for "change directory".
|
|
136
|
+
- **Windows**: if you are on Windows and you installed the GitHub client using their default settings you can
|
|
137
|
+
simply run `cd %userprofile%\Documents\GitHub\CoastalCodeBook`.
|
|
138
|
+
- **Linux/Mac**: change to the directory where you cloned the GitHub repository. This
|
|
139
|
+
will be something like `cd ~/path/to/github/repository`.
|
|
140
|
+
4. The CoastalCodeBook root directory contains an [environment.yml](environment.yml) file that describes the software
|
|
141
|
+
dependencies. This environment contains several packages and extension to build an
|
|
142
|
+
interactive Jupyter lab environment that you can use to run the tutorial notebooks.
|
|
143
|
+
|
|
144
|
+
You can create the software environment using this command:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mamba env create -f environment.yml
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
### Running the tutorial notebooks
|
|
152
|
+
Now that you have access to the code (cloning this Github repository), installed a
|
|
153
|
+
package manager and created your environments we can start running the notebooks in
|
|
154
|
+
Jupyterlab. If you are new to JupyterLab we encourage you to have a look at [this
|
|
155
|
+
introduction](https://earth-env-data-science.github.io/lectures/environment/intro_to_jupyterlab.html).
|
|
156
|
+
|
|
157
|
+
1. Open a terminal or Miniforge prompt.
|
|
158
|
+
2. Change to the directory where you cloned the repository `cd </path/to/local/repo>`.
|
|
159
|
+
Note, on Windows you should use backslashes (see sec 2).
|
|
160
|
+
3. Activate your environment by running:
|
|
161
|
+
```bash
|
|
162
|
+
mamba activate coastal
|
|
163
|
+
```
|
|
164
|
+
4. Open Jupyterlab by running the following command:
|
|
165
|
+
```bash
|
|
166
|
+
jupyter lab
|
|
167
|
+
```
|
|
168
|
+
This will open a Jupyterlab client in your browser.
|
|
169
|
+
5. In the JupyterLab IDE you can browse to the `notebooks` directory and open
|
|
170
|
+
one of the notebooks, for instance,
|
|
171
|
+
[01_coastal_classification.ipynb](notebooks/01_coastal_classification.ipynb).
|
|
172
|
+
6. Once the notebook is open you can activate the `coastal` environment in the
|
|
173
|
+
upper-right corner; change `Python 3 (ipykernel)` to `Python [conda env:coastal]`.
|
|
174
|
+
7. Now you can run the cells and do some interactive coastal analysis!
|
|
175
|
+
|
|
176
|
+
## Questions
|
|
177
|
+
|
|
178
|
+
If you have a question about the installation process or notebooks, feel free to open an
|
|
179
|
+
issue in the [GitHub repository](https://github.com/FlorisCalkoen/CoastalCodebook). If
|
|
180
|
+
that's your first time, have a look at [these
|
|
181
|
+
instructions](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue).
|
|
182
|
+
We choose to use the GitHub issue-tracker because your fellow students probably have
|
|
183
|
+
similar problems. We will not troubleshoot the tutorial notebooks by email.
|
|
184
|
+
|
|
185
|
+
### Building the book
|
|
186
|
+
|
|
187
|
+
If you'd like to develop and/or build the CoastalCodeBook book, you should:
|
|
188
|
+
|
|
189
|
+
1. Clone this repository
|
|
190
|
+
2. Run `mamba env create -f environment-coastal.yml`
|
|
191
|
+
3. Run `mamba activate coastal`
|
|
192
|
+
5. Run `jupyter-book build book`
|
|
193
|
+
|
|
194
|
+
A fully-rendered HTML version of the book will be built in
|
|
195
|
+
`book/_build/html/`.
|
|
196
|
+
|
|
197
|
+
**Known issues**: If you use `nb_conda_kernels` to expose your environments, you might run
|
|
198
|
+
into kernelspec errors when building the book. Until
|
|
199
|
+
https://github.com/executablebooks/jupyter-book/issues/1348 is fixed, a workaround is to
|
|
200
|
+
add the environments manually to the kernselspec:
|
|
201
|
+
1. Run `mamba activate coastal`
|
|
202
|
+
2. Run `python -m ipykernel install --user --name conda-env-coastal-py --display-name "conda-env-coastal-py"`
|
|
203
|
+
|
|
204
|
+
## Contributors
|
|
205
|
+
|
|
206
|
+
We welcome and recognize all contributions. You can see a list of current contributors in
|
|
207
|
+
the [contributors
|
|
208
|
+
tab](https://github.com/floriscalkoen/coastalcodebook/graphs/contributors).
|
|
209
|
+
|
|
210
|
+
## Credits
|
|
211
|
+
|
|
212
|
+
This project is created using the excellent open source [Jupyter Book
|
|
213
|
+
project](https://jupyterbook.org/) and the [executablebooks/cookiecutter-jupyter-book
|
|
214
|
+
template](https://github.com/executablebooks/cookiecutter-jupyter-book).
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+

|
|
2
|
+
# Coastal Systems Open Codebook
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Tutorial notebooks for Delft University of Technology Coastal Systems course. The book is
|
|
6
|
+
available [online](https://floriscalkoen.github.io/CoastalCodebook/intro.html) in both
|
|
7
|
+
PDF and markdown.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
For the tuturial sessions we will use an interactive computing environment, that is built
|
|
12
|
+
on the [Jupyter]() ecosystem and mostly rely on software that is supported by numfocus. We will communicate the tutorial content
|
|
13
|
+
using `git` version control and provide instructions on how to do so using the GitHub client. In the subsections
|
|
14
|
+
that follow we talk you through the three configurations steps.
|
|
15
|
+
|
|
16
|
+
### 1. Git
|
|
17
|
+
|
|
18
|
+
If you are not familiar with using Git, please have a look this short but excellent
|
|
19
|
+
[introduction](https://earth-env-data-science.github.io/lectures/environment/intro_to_git.html)
|
|
20
|
+
first.
|
|
21
|
+
|
|
22
|
+
1. Please refer to the [GitHub Client documentation](https://desktop.github.com/) to
|
|
23
|
+
install the GitHub client, or see [these
|
|
24
|
+
instructions](https://github.com/git-guides/install-git) to install git using the
|
|
25
|
+
command line.
|
|
26
|
+
2. Clone this repository to your local
|
|
27
|
+
computer using either of the following options.
|
|
28
|
+
|
|
29
|
+
1. **GitHub client**: Browse to the
|
|
30
|
+
[webpage](https://github.com/FlorisCalkoen/CoastalCodebook), click on the green "Code"
|
|
31
|
+
button and select "Open with GitHub Desktop"; or simply paste the URL into the GitHub
|
|
32
|
+
client "clone repository" menu.
|
|
33
|
+
|
|
34
|
+
2. **Bash shell**: If you have a bash terminal available, assuming that git [is
|
|
35
|
+
configured](https://docs.github.com/en/get-started/getting-started-with-git), you
|
|
36
|
+
can simply run: ` git clone https://github.com/FlorisCalkoen/CoastalCodebook.git`.
|
|
37
|
+
|
|
38
|
+
3. GitHub client does not install the underlying git software on your machine. Follow [these
|
|
39
|
+
instructions](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git)
|
|
40
|
+
to install git on your machine.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
By these steps, the files that are hosted at GitHub are "pulled" to your machine. You can
|
|
44
|
+
check that by opening a file explorer and going to the path where you cloned the
|
|
45
|
+
directory. The files that you find there should reflect what's on the GitHub page.
|
|
46
|
+
But we can't we do anything with the files yet, as we don't have the software that can
|
|
47
|
+
understand the code, so we will continue with installing a package manager.
|
|
48
|
+
|
|
49
|
+
### 2. Mamba package manager
|
|
50
|
+
|
|
51
|
+
If you're not familiar with managing Python environments, please have a look at this
|
|
52
|
+
[introduction](https://earth-env-data-science.github.io/lectures/environment/python_environments.html?highlight=conda)
|
|
53
|
+
first. The bottom line is that it is good practice to manage your software environments
|
|
54
|
+
to avoid dependency conflicts. For the tutorial notebooks, we recommend to use the
|
|
55
|
+
lightweight package manager `mambaforge`. The instructions to install this package
|
|
56
|
+
manager can be found in [their
|
|
57
|
+
documentation](https://mamba.readthedocs.io/en/latest/installation.html), in which they
|
|
58
|
+
refer to the [Conda Forge GitHub](https://github.com/conda-forge/miniforge#mambaforge)
|
|
59
|
+
page to download the software.
|
|
60
|
+
|
|
61
|
+
#### Windows
|
|
62
|
+
|
|
63
|
+
1. Download the mambaforge executable file for Windows from [Miniforge GitHub
|
|
64
|
+
page](https://github.com/conda-forge/miniforge#mambaforge). On that page there are also
|
|
65
|
+
binaries for Mac and Linux; and for `conda` package managers, so make sure you download
|
|
66
|
+
the `mambaforge` executable file for Windows. Install the executable by clicking on it;
|
|
67
|
+
you can stay with the default settings by just clicking next through the installation
|
|
68
|
+
client.
|
|
69
|
+
2. Now that mambaforge is installed, you can open a `Miniforge Prompt`. You can open this
|
|
70
|
+
shell by opening the start window and search for "Miniforge".
|
|
71
|
+
|
|
72
|
+
**Known issues**: Some users have their firewalls configured in such way that the
|
|
73
|
+
mambaforge installation is blocked. If you have trouble installing mambaforge, please make
|
|
74
|
+
sure to temporarily disable your firewall.
|
|
75
|
+
|
|
76
|
+
#### Unix like - Mac and Linux
|
|
77
|
+
1. We recommend to install Mambaforge on Linux and Mac using a terminal. On Mac, you can
|
|
78
|
+
open a terminal by searching for "terminal" or "iterm". On Linux the hotkey to open a
|
|
79
|
+
terminal is "cntrl + shift + t". The commands to
|
|
80
|
+
install the package manager are copied from their documentation and can be run by
|
|
81
|
+
copying the commands below over to your terminal and pressing enter:
|
|
82
|
+
```bash
|
|
83
|
+
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh"
|
|
84
|
+
bash Mambaforge-$(uname)-$(uname -m).sh
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
2. Accept the user agreements, and allow the installation script to edit your profile
|
|
88
|
+
file. The profile file (`~/.bashrc` on Linux or possibly `~/.zshrc` on Mac) is the
|
|
89
|
+
first script which is being executed when you open a new terminal. The installation
|
|
90
|
+
script will add a few lines to that file to make the `mamba` command available every
|
|
91
|
+
time open a new terminal.
|
|
92
|
+
3. Close the terminal.
|
|
93
|
+
|
|
94
|
+
### 3. Software environments
|
|
95
|
+
To run the tutorial notebooks we need several packages. To avoid dependency conflicts it
|
|
96
|
+
is good practice to seperate your environments; that was the reason for installing a
|
|
97
|
+
package manager. Now that we have our package manager we will create the software
|
|
98
|
+
environments. We will create one environment that runs the JupyterLab IDE, including
|
|
99
|
+
several extensions; and another one that contains the packages that we need for the
|
|
100
|
+
tutorials.
|
|
101
|
+
|
|
102
|
+
1. Now that mambaforge is available on your machine, open a terminal. On Windows you
|
|
103
|
+
should open the Miniforge prompt, which you can find by searching for it in the Start
|
|
104
|
+
window. On Mac you can open a terminal by searching for "terminal" or "iterm". For
|
|
105
|
+
Linux it's "cntrl + shift + t".
|
|
106
|
+
2. You can check if mamba was installed by running the following command in the terminal:
|
|
107
|
+
```bash
|
|
108
|
+
mamba --version
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
It should output something like:
|
|
112
|
+
|
|
113
|
+
```console
|
|
114
|
+
~ (base) mamba --version
|
|
115
|
+
mamba 1.1.0
|
|
116
|
+
conda 22.9.0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
3. Now that mambaforge is installed, navigate in the terminal to the directory
|
|
120
|
+
where you cloned the GitHub CoastalCodeBook repository. You can navigate the terminal
|
|
121
|
+
using `cd`, which stands for "change directory".
|
|
122
|
+
- **Windows**: if you are on Windows and you installed the GitHub client using their default settings you can
|
|
123
|
+
simply run `cd %userprofile%\Documents\GitHub\CoastalCodeBook`.
|
|
124
|
+
- **Linux/Mac**: change to the directory where you cloned the GitHub repository. This
|
|
125
|
+
will be something like `cd ~/path/to/github/repository`.
|
|
126
|
+
4. The CoastalCodeBook root directory contains an [environment.yml](environment.yml) file that describes the software
|
|
127
|
+
dependencies. This environment contains several packages and extension to build an
|
|
128
|
+
interactive Jupyter lab environment that you can use to run the tutorial notebooks.
|
|
129
|
+
|
|
130
|
+
You can create the software environment using this command:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
mamba env create -f environment.yml
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Running the tutorial notebooks
|
|
138
|
+
Now that you have access to the code (cloning this Github repository), installed a
|
|
139
|
+
package manager and created your environments we can start running the notebooks in
|
|
140
|
+
Jupyterlab. If you are new to JupyterLab we encourage you to have a look at [this
|
|
141
|
+
introduction](https://earth-env-data-science.github.io/lectures/environment/intro_to_jupyterlab.html).
|
|
142
|
+
|
|
143
|
+
1. Open a terminal or Miniforge prompt.
|
|
144
|
+
2. Change to the directory where you cloned the repository `cd </path/to/local/repo>`.
|
|
145
|
+
Note, on Windows you should use backslashes (see sec 2).
|
|
146
|
+
3. Activate your environment by running:
|
|
147
|
+
```bash
|
|
148
|
+
mamba activate coastal
|
|
149
|
+
```
|
|
150
|
+
4. Open Jupyterlab by running the following command:
|
|
151
|
+
```bash
|
|
152
|
+
jupyter lab
|
|
153
|
+
```
|
|
154
|
+
This will open a Jupyterlab client in your browser.
|
|
155
|
+
5. In the JupyterLab IDE you can browse to the `notebooks` directory and open
|
|
156
|
+
one of the notebooks, for instance,
|
|
157
|
+
[01_coastal_classification.ipynb](notebooks/01_coastal_classification.ipynb).
|
|
158
|
+
6. Once the notebook is open you can activate the `coastal` environment in the
|
|
159
|
+
upper-right corner; change `Python 3 (ipykernel)` to `Python [conda env:coastal]`.
|
|
160
|
+
7. Now you can run the cells and do some interactive coastal analysis!
|
|
161
|
+
|
|
162
|
+
## Questions
|
|
163
|
+
|
|
164
|
+
If you have a question about the installation process or notebooks, feel free to open an
|
|
165
|
+
issue in the [GitHub repository](https://github.com/FlorisCalkoen/CoastalCodebook). If
|
|
166
|
+
that's your first time, have a look at [these
|
|
167
|
+
instructions](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue).
|
|
168
|
+
We choose to use the GitHub issue-tracker because your fellow students probably have
|
|
169
|
+
similar problems. We will not troubleshoot the tutorial notebooks by email.
|
|
170
|
+
|
|
171
|
+
### Building the book
|
|
172
|
+
|
|
173
|
+
If you'd like to develop and/or build the CoastalCodeBook book, you should:
|
|
174
|
+
|
|
175
|
+
1. Clone this repository
|
|
176
|
+
2. Run `mamba env create -f environment-coastal.yml`
|
|
177
|
+
3. Run `mamba activate coastal`
|
|
178
|
+
5. Run `jupyter-book build book`
|
|
179
|
+
|
|
180
|
+
A fully-rendered HTML version of the book will be built in
|
|
181
|
+
`book/_build/html/`.
|
|
182
|
+
|
|
183
|
+
**Known issues**: If you use `nb_conda_kernels` to expose your environments, you might run
|
|
184
|
+
into kernelspec errors when building the book. Until
|
|
185
|
+
https://github.com/executablebooks/jupyter-book/issues/1348 is fixed, a workaround is to
|
|
186
|
+
add the environments manually to the kernselspec:
|
|
187
|
+
1. Run `mamba activate coastal`
|
|
188
|
+
2. Run `python -m ipykernel install --user --name conda-env-coastal-py --display-name "conda-env-coastal-py"`
|
|
189
|
+
|
|
190
|
+
## Contributors
|
|
191
|
+
|
|
192
|
+
We welcome and recognize all contributions. You can see a list of current contributors in
|
|
193
|
+
the [contributors
|
|
194
|
+
tab](https://github.com/floriscalkoen/coastalcodebook/graphs/contributors).
|
|
195
|
+
|
|
196
|
+
## Credits
|
|
197
|
+
|
|
198
|
+
This project is created using the excellent open source [Jupyter Book
|
|
199
|
+
project](https://jupyterbook.org/) and the [executablebooks/cookiecutter-jupyter-book
|
|
200
|
+
template](https://github.com/executablebooks/cookiecutter-jupyter-book).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "coastal-dynamics"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
description = "Jupyterbook for Delft Technical University Coastal Systems"
|
|
5
|
+
authors = ["Floris Calkoen"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.10"
|
|
11
|
+
pytest = "^7.4.0"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dev-dependencies]
|
|
14
|
+
black = "^23.7.0"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["poetry-core>=1.0.0"]
|
|
18
|
+
build-backend = "poetry.core.masonry.api"
|
|
19
|
+
|
|
20
|
+
[tool.black]
|
|
21
|
+
line-length = 88
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
select = [
|
|
25
|
+
"E", "F", "W", # flake8
|
|
26
|
+
"B", # flake8-bugbear
|
|
27
|
+
"I", # isort
|
|
28
|
+
"ARG", # flake8-unused-arguments
|
|
29
|
+
"C4", # flake8-comprehensions
|
|
30
|
+
"EM", # flake8-errmsg
|
|
31
|
+
"ICN", # flake8-import-conventions
|
|
32
|
+
"ISC", # flake8-implicit-str-concat
|
|
33
|
+
"PGH", # pygrep-hooks
|
|
34
|
+
"PIE", # flake8-pie
|
|
35
|
+
"PL", # pylint
|
|
36
|
+
"PT", # flake8-pytest-style
|
|
37
|
+
"PTH", # flake8-use-pathlib
|
|
38
|
+
"RUF", # Ruff-specific
|
|
39
|
+
"SIM", # flake8-simplify
|
|
40
|
+
"TID251", # flake8-tidy-imports.banned-api
|
|
41
|
+
"T20", # flake8-print
|
|
42
|
+
"UP", # pyupgrade
|
|
43
|
+
"YTT", # flake8-2020
|
|
44
|
+
]
|
|
45
|
+
extend-ignore = [
|
|
46
|
+
"PLR", # Design related pylint codes
|
|
47
|
+
"E501", # Line too long
|
|
48
|
+
"PT004", # Incorrect check, usefixtures is the correct way to do this
|
|
49
|
+
"RUF012", # Would require a lot of ClassVar's
|
|
50
|
+
]
|
|
51
|
+
src = ["src"]
|
|
52
|
+
unfixable = [
|
|
53
|
+
"T20", # Removes print statements
|
|
54
|
+
"F841", # Removes unused variables
|
|
55
|
+
]
|
|
56
|
+
exclude = []
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
flake8-unused-arguments.ignore-variadic-names = true
|
|
59
|
+
|
|
60
|
+
[tool.ruff.pydocstyle]
|
|
61
|
+
convention = "google"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.2"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Union
|
|
2
|
+
|
|
3
|
+
import geopandas as gpd
|
|
4
|
+
import shapely
|
|
5
|
+
from pyproj import Transformer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_bbox(min_lon, min_lat, max_lon, max_lat, src_crs, crs):
|
|
9
|
+
"""
|
|
10
|
+
Build a latitude / longitude bounding box from the coordinates.
|
|
11
|
+
"""
|
|
12
|
+
# minimum / maximum points from the dataset
|
|
13
|
+
# left, bottom, right, top = (-5802250.0, -622000.0, -5519250.0, -39000.0)
|
|
14
|
+
points = [
|
|
15
|
+
[min_lon, max_lon, max_lon, min_lon],
|
|
16
|
+
[min_lat, min_lat, max_lat, max_lat],
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
transformer = Transformer.from_crs(src_crs, crs, always_xy=True)
|
|
20
|
+
lons, lats = transformer.transform(*points)
|
|
21
|
+
|
|
22
|
+
west = min(lons)
|
|
23
|
+
east = max(lons)
|
|
24
|
+
north = max(lats)
|
|
25
|
+
south = min(lats)
|
|
26
|
+
return [west, south, east, north]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]:
|
|
30
|
+
"""Extract the bounding box from a geojson geometry
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
geometry : GeoJSON geometry dict
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list: Bounding box of geojson geometry, formatted according to:
|
|
37
|
+
https://tools.ietf.org/html/rfc7946#section-5
|
|
38
|
+
"""
|
|
39
|
+
coords = geometry["coordinates"]
|
|
40
|
+
|
|
41
|
+
lats: List[float] = []
|
|
42
|
+
lons: List[float] = []
|
|
43
|
+
|
|
44
|
+
def extract_coords(coords: List[Union[List[float], List[List[Any]]]]) -> None:
|
|
45
|
+
for x in coords:
|
|
46
|
+
# This handles points
|
|
47
|
+
if isinstance(x, float):
|
|
48
|
+
assert isinstance(
|
|
49
|
+
coords[0], float
|
|
50
|
+
), f"Type mismatch: {coords[0]} is not a float"
|
|
51
|
+
assert isinstance(
|
|
52
|
+
coords[1], float
|
|
53
|
+
), f"Type mismatch: {coords[1]} is not a float"
|
|
54
|
+
lats.append(coords[0])
|
|
55
|
+
lons.append(coords[1])
|
|
56
|
+
return
|
|
57
|
+
if isinstance(x[0], list):
|
|
58
|
+
extract_coords(x) # type:ignore
|
|
59
|
+
else:
|
|
60
|
+
lat, lon = x
|
|
61
|
+
lats.append(lat) # type:ignore
|
|
62
|
+
lons.append(lon) # type:ignore
|
|
63
|
+
|
|
64
|
+
extract_coords(coords)
|
|
65
|
+
|
|
66
|
+
lons.sort()
|
|
67
|
+
lats.sort()
|
|
68
|
+
|
|
69
|
+
bbox = [lats[0], lons[0], lats[-1], lons[-1]]
|
|
70
|
+
|
|
71
|
+
return bbox
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def bbox_to_geometry(bbox: List[float]) -> Dict:
|
|
75
|
+
"""Use shapely.geometry.shape to cast to shapeley geom"""
|
|
76
|
+
return {
|
|
77
|
+
"type": "Polygon",
|
|
78
|
+
"coordinates": [
|
|
79
|
+
[
|
|
80
|
+
[bbox[2], bbox[1]],
|
|
81
|
+
[bbox[2], bbox[3]],
|
|
82
|
+
[bbox[0], bbox[3]],
|
|
83
|
+
[bbox[0], bbox[1]],
|
|
84
|
+
[bbox[2], bbox[1]],
|
|
85
|
+
]
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def geo_bbox(
|
|
91
|
+
min_lon: float,
|
|
92
|
+
min_lat: float,
|
|
93
|
+
max_lon: float,
|
|
94
|
+
max_lat: float,
|
|
95
|
+
src_crs="EPSG:4326",
|
|
96
|
+
dst_crs="EPSG:4326",
|
|
97
|
+
) -> gpd.GeoDataFrame:
|
|
98
|
+
"""GeoDataFrame with a bounding box that can be used for various gis operations.
|
|
99
|
+
Args:
|
|
100
|
+
min_lon (float): most eastern longitude
|
|
101
|
+
min_lat (float): most northern latitude
|
|
102
|
+
max_lon (float): most wester longitude
|
|
103
|
+
max_lat (float): most soutern latitude
|
|
104
|
+
src_crs (str, optional): Valid EPSG string or number (int). Defaults to "EPSG:4326".
|
|
105
|
+
dst_crs (str, optional): Valid EPSG string or number (int). Defaults to "EPSG:4326".
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
gpd.GeoDataFrame: GeoDataFrame with bounding box as geometry.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
bbox = [min_lon, min_lat, max_lon, max_lat]
|
|
112
|
+
bbox = bbox_to_geometry(bbox)
|
|
113
|
+
bbox = shapely.geometry.shape(bbox)
|
|
114
|
+
return gpd.GeoDataFrame(geometry=[bbox], crs=src_crs).to_crs(dst_crs)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_xy_range(gdf):
|
|
118
|
+
"""min, max lon/lat formatted as xy range for geoviews."""
|
|
119
|
+
x_range = tuple(gdf.total_bounds[[0, 2]])
|
|
120
|
+
y_range = tuple(gdf.total_bounds[[1, 3]])
|
|
121
|
+
return x_range, y_range
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# TODO: in future use https://github.com/jupyter/nbgrader instead
|
|
2
|
+
# Function for cheking the first exercise:
|
|
3
|
+
def Check1(ans1, ans2, ans3, ans4, ans5):
|
|
4
|
+
c = 0
|
|
5
|
+
# Checking answer 1:
|
|
6
|
+
if ans1 == "A":
|
|
7
|
+
c = c + 1
|
|
8
|
+
print(
|
|
9
|
+
"Answer 1: \t Correct! The channel velocity is too small to keep the inlet open. Sedimentation leads to closure of the inlet."
|
|
10
|
+
)
|
|
11
|
+
if ans1 == "B":
|
|
12
|
+
print("Answer 1: \t Are you sure that point B is a stable point?")
|
|
13
|
+
if (ans1 == "C") & (ans1 == "D"):
|
|
14
|
+
print(
|
|
15
|
+
"Answer 1: \t Is it possible for a tidal inlet to naturally pass point B from location 1?"
|
|
16
|
+
)
|
|
17
|
+
if ans1 == "E":
|
|
18
|
+
print(
|
|
19
|
+
"Answer1: \t Wow! Talking about over compensation... I suggest you rethink your answer"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Checking answer 2:
|
|
23
|
+
if ans2 == "D":
|
|
24
|
+
c = c + 1
|
|
25
|
+
print(
|
|
26
|
+
"Answer 2: \t Correct! Although we are close to point B, an unstable equilibrium, the channel velocity is large enough to increase the channel cross-section. It grows towards point C, passes it, and eventually reach point D, a stable equilibrium."
|
|
27
|
+
)
|
|
28
|
+
if ans2 == "C":
|
|
29
|
+
print(
|
|
30
|
+
"Answer 2: \t Almost! The cross-section grows towards point C, but does it stop there?"
|
|
31
|
+
)
|
|
32
|
+
if (ans2 != "D") & (ans2 != "C"):
|
|
33
|
+
print(
|
|
34
|
+
'Answer 2: \t Location 2 lies between an unstable equilibrium (point B) and stable equilbrium (point D). The unstable equilibrium "pushes" away and the stable equilibrium "attracts". It can\'t really go anywhere else but...?'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Checking answer 3:
|
|
38
|
+
if ans3 == "D":
|
|
39
|
+
c = c + 1
|
|
40
|
+
print(
|
|
41
|
+
"Answer 3: \t Correct! The channel velocity is still too large. Through erosion the channel moves to point D."
|
|
42
|
+
)
|
|
43
|
+
if ans3 == "C":
|
|
44
|
+
print(
|
|
45
|
+
"Answer 3: \t Almost! The channel velocity is larger than the equilibrium velocity. The channel is eroding, thus moving towards?"
|
|
46
|
+
)
|
|
47
|
+
if (ans3 != "D") & (ans3 != "C"):
|
|
48
|
+
print(
|
|
49
|
+
'Answer 3: \t Location 3 lies between an unstable equilibrium (point B) and stable equilbrium (point D). The unstable equilibrium "pushes" away and the stable equilibrium "attracts". It can\'t really go anywhere else but...?'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Checking answer 4:
|
|
53
|
+
if ans4 == "D":
|
|
54
|
+
c = c + 1
|
|
55
|
+
print(
|
|
56
|
+
"Answer 4: \t Correct! The channel velocity is too small and the cross-section too large. Sedimentation occurs and we move towards point D"
|
|
57
|
+
)
|
|
58
|
+
if (ans4 == "C") | (ans4 == "B"):
|
|
59
|
+
print("Answer 4: \t Hmmm, are you sure we can move past point D?")
|
|
60
|
+
if ans4 == "A":
|
|
61
|
+
print("Answer 4: \t Oh no! Where are you going, come back!")
|
|
62
|
+
if ans4 == "E":
|
|
63
|
+
print(
|
|
64
|
+
"Answer 4: \t A channel cross-section that is actually too large will grow even more. Are you sure about that?"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Checking answer 5:
|
|
68
|
+
if ans5 == "D":
|
|
69
|
+
c = c + 1
|
|
70
|
+
print(
|
|
71
|
+
"Answer 5: \t Correct! The cross-section will grow until an equilibrium is found."
|
|
72
|
+
)
|
|
73
|
+
if ans5 == "E":
|
|
74
|
+
print(
|
|
75
|
+
"Answer 5: \t A channel cross-section that is actually too large will grow even more. Are you sure about that?"
|
|
76
|
+
)
|
|
77
|
+
if (ans5 != "D") & (ans5 != "E"):
|
|
78
|
+
print("Answer 5: \t Hmmm, are you sure we can move past point D?")
|
|
79
|
+
if ans5 == "A":
|
|
80
|
+
print("Answer 4: \t Oh no! Where are you going, come back!")
|
|
81
|
+
|
|
82
|
+
if c == 5:
|
|
83
|
+
print(
|
|
84
|
+
"Well done! You are a master of understanding the Escoffier curve. But what about other scenarios? Go to Part 2 and find out."
|
|
85
|
+
)
|
|
86
|
+
if c == 0:
|
|
87
|
+
print(
|
|
88
|
+
"Mmm... I think you should read the section about this topic again. If you can't figure it out, discuss with your peers or ask us for help."
|
|
89
|
+
)
|
|
90
|
+
if (c > 0) & (c < 5):
|
|
91
|
+
print(
|
|
92
|
+
"Some of your answers are incorrect. Retrhink your answers or dicuss with your peers what the correct answers should be."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Function for cheking the second exercise:
|
|
97
|
+
def Check2(ansII_1, ansII_2, ansII_3, ansIII_1, ansIII_2, ansIII_3):
|
|
98
|
+
c = 0
|
|
99
|
+
# Checking scenario II answer 1:
|
|
100
|
+
if ansII_1 == "A":
|
|
101
|
+
c = c + 1
|
|
102
|
+
print("Answer II_1: \t Correct!")
|
|
103
|
+
if ansII_1 == "C":
|
|
104
|
+
print(
|
|
105
|
+
"Answer II_1: \t Point C does coincide with the equilibrium velocity, but is this a stable situtaion?"
|
|
106
|
+
)
|
|
107
|
+
if ansII_1 == "E":
|
|
108
|
+
print(
|
|
109
|
+
"Answer II_1: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Checking scenario II answer 2:
|
|
113
|
+
if ansII_2 == "A":
|
|
114
|
+
c = c + 1
|
|
115
|
+
print("Answer II_2: \t Correct!")
|
|
116
|
+
if ansII_1 == "C":
|
|
117
|
+
print(
|
|
118
|
+
'Answer II_2: \t Definitely possible, as long as the deviation is "to the right". As soon as a deviation is "to the left", what happens?'
|
|
119
|
+
)
|
|
120
|
+
if ansII_1 == "E":
|
|
121
|
+
print(
|
|
122
|
+
"Answer II_2: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Checking scenario II answer 3:
|
|
126
|
+
if ansII_3 == "A":
|
|
127
|
+
c = c + 1
|
|
128
|
+
print("Answer II_3: \t Correct!")
|
|
129
|
+
if ansII_1 == "C":
|
|
130
|
+
print(
|
|
131
|
+
"Answer II_3: \t The situation will approach point C, but how likely is it that no overshoot occurs?"
|
|
132
|
+
)
|
|
133
|
+
if ansII_1 == "E":
|
|
134
|
+
print(
|
|
135
|
+
"Answer II_3: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Checking scenario III answer 1:
|
|
139
|
+
if ansIII_1 == "A":
|
|
140
|
+
c = c + 1
|
|
141
|
+
print("Answer III_1: \t Correct!")
|
|
142
|
+
if ansIII_1 == "C":
|
|
143
|
+
print(
|
|
144
|
+
"Answer III_1: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
145
|
+
)
|
|
146
|
+
if ansIII_1 == "E":
|
|
147
|
+
print(
|
|
148
|
+
"Answer III_1: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Checking scenario III answer 2:
|
|
152
|
+
if ansIII_2 == "A":
|
|
153
|
+
c = c + 1
|
|
154
|
+
print("Answer III_2: \t Correct!")
|
|
155
|
+
if ansIII_2 == "C":
|
|
156
|
+
print(
|
|
157
|
+
"Answer III_2: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
158
|
+
)
|
|
159
|
+
if ansIII_2 == "E":
|
|
160
|
+
print(
|
|
161
|
+
"Answer III_2: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Checking scenario III answer 3:
|
|
165
|
+
if ansIII_3 == "A":
|
|
166
|
+
c = c + 1
|
|
167
|
+
print("Answer III_3: \t Correct!")
|
|
168
|
+
if ansIII_3 == "C":
|
|
169
|
+
print(
|
|
170
|
+
"Answer III_3: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
171
|
+
)
|
|
172
|
+
if ansIII_3 == "E":
|
|
173
|
+
print(
|
|
174
|
+
"Answer III_3: \t Are you sure? Look back at the previous exercise and reconsider what happens."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if c == 6:
|
|
178
|
+
print(
|
|
179
|
+
"Well done! Your understanding of the possible scenarios is very good. Now we are going to test you some more, go to the next exercise."
|
|
180
|
+
)
|
|
181
|
+
if c == 0:
|
|
182
|
+
print(
|
|
183
|
+
"Mmm... I think you should go through the theory and this notebook again."
|
|
184
|
+
)
|
|
185
|
+
if (c > 0) & (c < 6):
|
|
186
|
+
print(
|
|
187
|
+
"Some of your answers are incorrect. Retrhink your answers or dicuss with your peers what the correct answers should be."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Function for cheking the third exercise:
|
|
192
|
+
def Check3():
|
|
193
|
+
print(
|
|
194
|
+
"Question 1: \t If the cross-section gets really large, there is a point where the estuary/basin starts to act more as a normal coastline."
|
|
195
|
+
)
|
|
196
|
+
print(
|
|
197
|
+
"Question 2: \t A cross-sectional (area) going to inifity does not really have a physical meaning."
|
|
198
|
+
)
|
|
199
|
+
print(
|
|
200
|
+
"Question 3: \t Modifying the Escoffier curve means modifying the tidal prism, see the equation in this notebook. You can change the tidal prism in many ways. A possbility is damming the estuary, as is done with the Zuiderzee (now IJsselmeer) in The Netherlands. Another option is to dredge and/or reclaim land, altering the storage capacity of the estuary. "
|
|
201
|
+
)
|
|
202
|
+
print(
|
|
203
|
+
"Question 4: \t Ofcourse! Estuaries constantly change due to changes in sediment import/export and fluvial discharges. As long as the estuary is not in a steady equilibrium, the tidal prism can vary in time, and thus the closure curve."
|
|
204
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import ipyleaflet
|
|
2
|
+
from ipyleaflet import Map, Marker, ScaleControl, basemaps
|
|
3
|
+
from ipywidgets import HTML
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plot_esri_basemap(
|
|
7
|
+
lon: float, lat: float, zoom: int, name: str
|
|
8
|
+
) -> ipyleaflet.leaflet.Map:
|
|
9
|
+
"""Plot IPyleaflet map with ESRI basemap tiles.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
lon (float): Longitude in degrees.
|
|
13
|
+
lat (float): Latitude in degrees.
|
|
14
|
+
zoom (int): Zoom level following OSM slippy map tiles.
|
|
15
|
+
name (str): Name for the marker.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
ipyleaflet.leaflet.Map: Basemap with ESRI World Imagery.
|
|
19
|
+
"""
|
|
20
|
+
m = Map(basemap=basemaps.Esri.WorldImagery, scroll_wheel_zoom=True)
|
|
21
|
+
center = (lat, lon)
|
|
22
|
+
marker = Marker(location=center)
|
|
23
|
+
m.add_layer(marker)
|
|
24
|
+
m.center = center
|
|
25
|
+
m.zoom = zoom
|
|
26
|
+
m.layout.height = "800px"
|
|
27
|
+
m.add_control(ScaleControl(position="bottomleft"))
|
|
28
|
+
title = HTML()
|
|
29
|
+
title.value = name
|
|
30
|
+
marker.popup = title
|
|
31
|
+
return m
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from coastal_dynamics.questions.multiple_choice import MultipleChoiceQuestion
|
|
4
|
+
from coastal_dynamics.questions.multiple_selection import MultipleSelectionQuestion
|
|
5
|
+
from coastal_dynamics.questions.numeric import NumericQuestion
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QuestionWidgetFactory:
|
|
9
|
+
"""
|
|
10
|
+
A factory class for creating question widgets based on question data.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
question_data (Dict[str, Any]): Dictionary containing data for the question.
|
|
14
|
+
|
|
15
|
+
Methods:
|
|
16
|
+
create_question_widget(): Creates a question widget based on the question data.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, question_data: dict[str, Any]):
|
|
20
|
+
self.question_data = question_data
|
|
21
|
+
|
|
22
|
+
def create_question_widget(self):
|
|
23
|
+
"""Creates a question widget based on the question data."""
|
|
24
|
+
question_type = self.question_data.get("type")
|
|
25
|
+
valid_types = ["multiple_choice", "multiple_selection", "numeric"]
|
|
26
|
+
|
|
27
|
+
if question_type not in valid_types:
|
|
28
|
+
raise ValueError(f"Unknown question type: {question_type}")
|
|
29
|
+
|
|
30
|
+
if question_type == "multiple_choice":
|
|
31
|
+
return self._create_multiple_choice_question()
|
|
32
|
+
elif question_type == "multiple_selection":
|
|
33
|
+
return self._create_multiple_selection_question()
|
|
34
|
+
elif question_type == "numeric":
|
|
35
|
+
return self._create_numeric_question()
|
|
36
|
+
|
|
37
|
+
def _create_multiple_choice_question(self):
|
|
38
|
+
return MultipleChoiceQuestion(
|
|
39
|
+
question_name=self.question_data.get("name", ""),
|
|
40
|
+
question_text=self.question_data.get("question", ""),
|
|
41
|
+
question_options=self.question_data.get("options", {}),
|
|
42
|
+
question_answer=self.question_data.get("answer", ""),
|
|
43
|
+
**self.question_data.get("kwargs", {}),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def _create_multiple_selection_question(self):
|
|
47
|
+
return MultipleSelectionQuestion(
|
|
48
|
+
question_name=self.question_data.get("name", ""),
|
|
49
|
+
question_text=self.question_data.get("question", ""),
|
|
50
|
+
question_options=self.question_data.get("options", {}),
|
|
51
|
+
question_answers=self.question_data.get("answers", []),
|
|
52
|
+
**self.question_data.get("kwargs", {}),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def _create_numeric_question(self):
|
|
56
|
+
return NumericQuestion(
|
|
57
|
+
question_name=self.question_data.get("name", ""),
|
|
58
|
+
question_text=self.question_data.get("question", ""),
|
|
59
|
+
question_answer=self.question_data.get("answer", 0),
|
|
60
|
+
**self.question_data.get("kwargs", {}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
|
|
66
|
+
def example():
|
|
67
|
+
question_data = {
|
|
68
|
+
"type": "multiple_choice",
|
|
69
|
+
"name": "q1",
|
|
70
|
+
"question": (
|
|
71
|
+
"Which of the following is a common along the coast in the middle"
|
|
72
|
+
" latitudes?"
|
|
73
|
+
),
|
|
74
|
+
"options": {
|
|
75
|
+
"a": "Salt marshes",
|
|
76
|
+
"b": "Coral reefs",
|
|
77
|
+
"c": "Tropical rainforests",
|
|
78
|
+
"d": "Mangrove swamps",
|
|
79
|
+
},
|
|
80
|
+
"answer": "a",
|
|
81
|
+
"feedback": "...",
|
|
82
|
+
"hint": "...",
|
|
83
|
+
}
|
|
84
|
+
factory = QuestionWidgetFactory(question_data)
|
|
85
|
+
return factory.create_question_widget()
|
|
86
|
+
|
|
87
|
+
widget = example()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
import panel as pn
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MultipleChoiceQuestion:
|
|
7
|
+
"""A class to create and manage a multiple choice question widget.
|
|
8
|
+
|
|
9
|
+
This class creates a multiple choice question using Panel widgets.
|
|
10
|
+
It supports question text, multiple options, and a single correct answer.
|
|
11
|
+
The correct answer is stored in an encrypted format for basic obfuscation.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
question_text (str): The text of the question.
|
|
15
|
+
options (Dict[str, str]): A dictionary of option keys and their text.
|
|
16
|
+
correct_answer (str): The encrypted correct answer key.
|
|
17
|
+
name (str): The name of the question widget.
|
|
18
|
+
question_widget (pn.widgets.StaticText): The widget for displaying the question.
|
|
19
|
+
options_widget (pn.widgets.RadioBoxGroup): The widget for displaying the options.
|
|
20
|
+
submit_button (pn.widgets.Button): The button to submit the answer.
|
|
21
|
+
feedback_widget (pn.widgets.StaticText): The widget to display feedback.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
question_name: (str): The name for the question widget.
|
|
25
|
+
question_text: (str): The question.
|
|
26
|
+
question_options: (str): The options for the question,
|
|
27
|
+
question_answerL: (str): The answer for the question,
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
question_name: str,
|
|
33
|
+
question_text: str,
|
|
34
|
+
question_options: dict[str, str],
|
|
35
|
+
question_answer: str,
|
|
36
|
+
**kwargs
|
|
37
|
+
):
|
|
38
|
+
self.question_text: str = question_text
|
|
39
|
+
self.options: dict[str, str] = question_options
|
|
40
|
+
self.correct_answer: str = self._encode_answer(question_answer)
|
|
41
|
+
self.name: str = question_name
|
|
42
|
+
self.question_widget: pn.widgets.StaticText
|
|
43
|
+
self.options_widget: pn.widgets.RadioBoxGroup
|
|
44
|
+
self.submit_button: pn.widgets.Button
|
|
45
|
+
self.feedback_widget: pn.widgets.StaticText
|
|
46
|
+
self.options_inverse: dict[str, str] = {v: k for k, v in self.options.items()}
|
|
47
|
+
self.create_widgets()
|
|
48
|
+
|
|
49
|
+
def create_widgets(self) -> None:
|
|
50
|
+
"""Create and initialize the Panel widgets for the question."""
|
|
51
|
+
self.question_widget = pn.widgets.StaticText(
|
|
52
|
+
name=self.name, value=self.question_text
|
|
53
|
+
)
|
|
54
|
+
self.options_widget = pn.widgets.RadioBoxGroup(
|
|
55
|
+
name="Options", options=list(self.options.values())
|
|
56
|
+
)
|
|
57
|
+
self.submit_button = pn.widgets.Button(name="Submit")
|
|
58
|
+
self.feedback_widget = pn.widgets.StaticText()
|
|
59
|
+
self.submit_button.on_click(self._check_answer)
|
|
60
|
+
|
|
61
|
+
def _check_answer(self, event: pn.widgets.Button) -> None:
|
|
62
|
+
"""Check the selected answer against the correct answer.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
event (pn.widgets.Button): The event triggered by the submit button.
|
|
66
|
+
"""
|
|
67
|
+
selected_option = self.options_inverse[self.options_widget.value]
|
|
68
|
+
decoded_answer = self._decode_answer(self.correct_answer)
|
|
69
|
+
if selected_option == decoded_answer:
|
|
70
|
+
self.feedback_widget.value = "Correct!"
|
|
71
|
+
else:
|
|
72
|
+
self.feedback_widget.value = "Incorrect, try again."
|
|
73
|
+
|
|
74
|
+
def _encode_answer(self, plain_answer: str) -> str:
|
|
75
|
+
"""Encode the answer using base64.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
plain_answer (str): The plain text answer to encode.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
str: The encoded answer.
|
|
82
|
+
"""
|
|
83
|
+
return base64.b64encode(plain_answer.encode()).decode()
|
|
84
|
+
|
|
85
|
+
def _decode_answer(self, encoded_answer: str) -> str:
|
|
86
|
+
"""Decode the encoded answer.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
encoded_answer (str): The encoded answer to decode.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: The decoded answer.
|
|
93
|
+
"""
|
|
94
|
+
return base64.b64decode(encoded_answer.encode()).decode()
|
|
95
|
+
|
|
96
|
+
def serve(self) -> pn.Column:
|
|
97
|
+
"""Serve the question as a Panel column.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
pn.Column: The column containing the question and widgets.
|
|
101
|
+
"""
|
|
102
|
+
return pn.Column(
|
|
103
|
+
self.question_widget,
|
|
104
|
+
self.options_widget,
|
|
105
|
+
self.submit_button,
|
|
106
|
+
self.feedback_widget,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
question_data = {
|
|
112
|
+
"question": (
|
|
113
|
+
"Which coastal system do you typically find in tide-dominated coasts?"
|
|
114
|
+
),
|
|
115
|
+
"options": {
|
|
116
|
+
"a": "Mudflats",
|
|
117
|
+
"b": "Open coasts",
|
|
118
|
+
"c": "Cliffed coasts",
|
|
119
|
+
"d": "Mixed sand and gravel beaches",
|
|
120
|
+
},
|
|
121
|
+
"answer": "a", # Multiple correct answers
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mcq = MultipleChoiceQuestion(
|
|
125
|
+
question_name="Q1: Coastline Features Quiz",
|
|
126
|
+
question_text=question_data["question"],
|
|
127
|
+
question_options=question_data["options"],
|
|
128
|
+
question_answer=question_data["answer"],
|
|
129
|
+
)
|
|
130
|
+
print("done")
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
import panel as pn
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MultipleSelectionQuestion:
|
|
7
|
+
"""A class to create and manage multiple selection questions using Panel widgets.
|
|
8
|
+
|
|
9
|
+
This class creates a multiple selection using Panel widgets.
|
|
10
|
+
It supports question text, multiple options, and a multiple correct answers.
|
|
11
|
+
The correct answers are stored in an encrypted format for basic obfuscation.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
question_text (str): The text of the question.
|
|
15
|
+
options (Dict[str, str]): A dictionary of option keys and their text.
|
|
16
|
+
correct_answers (List[str]): A list of base64-encoded correct answer keys.
|
|
17
|
+
name (str): The name of the question widget.
|
|
18
|
+
question_widget (pn.widgets.StaticText): Panel widget for displaying the question.
|
|
19
|
+
options_widget (pn.widgets.CheckBoxGroup): Panel widget for displaying options.
|
|
20
|
+
submit_button (pn.widgets.Button): Button widget for submitting the answer.
|
|
21
|
+
feedback_widget (pn.widgets.StaticText): Widget for showing feedback.
|
|
22
|
+
options_inverse (Dict[str, str]): Inverse mapping of options for easy lookup.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
question_name: (str): The name for the question widget.
|
|
26
|
+
question_text: (str): The question.
|
|
27
|
+
question_options: (str): The options for the question,
|
|
28
|
+
question_answers: (str): The answer for the question,
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
question_name: str,
|
|
34
|
+
question_text: str,
|
|
35
|
+
question_options: dict[str, str],
|
|
36
|
+
question_answers: list[str],
|
|
37
|
+
**kwargs,
|
|
38
|
+
):
|
|
39
|
+
self.name: str = question_name
|
|
40
|
+
self.question_text: str = question_text
|
|
41
|
+
self.options: dict[str, str] = question_options
|
|
42
|
+
self.correct_answers: list[str] = [
|
|
43
|
+
self._encode_answer(ans) for ans in question_answers
|
|
44
|
+
]
|
|
45
|
+
self.create_widgets()
|
|
46
|
+
self.options_inverse: dict[str, str] = {v: k for k, v in self.options.items()}
|
|
47
|
+
|
|
48
|
+
def create_widgets(self) -> None:
|
|
49
|
+
"""Creates the Panel widgets for the question."""
|
|
50
|
+
self.question_widget = pn.widgets.StaticText(
|
|
51
|
+
name=self.name, value=self.question_text
|
|
52
|
+
)
|
|
53
|
+
self.options_widget = pn.widgets.CheckBoxGroup(
|
|
54
|
+
name="Options", options=list(self.options.values())
|
|
55
|
+
)
|
|
56
|
+
self.submit_button = pn.widgets.Button(name="Submit")
|
|
57
|
+
self.feedback_widget = pn.widgets.StaticText()
|
|
58
|
+
self.submit_button.on_click(self._check_answers)
|
|
59
|
+
|
|
60
|
+
def _check_answers(self, event: pn.widgets.Button) -> None:
|
|
61
|
+
"""Checks the selected answers against the correct ones."""
|
|
62
|
+
selected_options = [
|
|
63
|
+
self.options_inverse[opt] for opt in self.options_widget.value
|
|
64
|
+
]
|
|
65
|
+
decoded_answers = [
|
|
66
|
+
self._decode_answer(enc_ans) for enc_ans in self.correct_answers
|
|
67
|
+
]
|
|
68
|
+
if set(selected_options) == set(decoded_answers):
|
|
69
|
+
self.feedback_widget.value = "Correct!"
|
|
70
|
+
else:
|
|
71
|
+
self.feedback_widget.value = "Incorrect, try again."
|
|
72
|
+
|
|
73
|
+
def _encode_answer(self, plain_answer: str) -> str:
|
|
74
|
+
"""Encodes the answer using base64."""
|
|
75
|
+
return base64.b64encode(plain_answer.encode()).decode()
|
|
76
|
+
|
|
77
|
+
def _decode_answer(self, encoded_answer: str) -> str:
|
|
78
|
+
"""Decodes the answer from base64."""
|
|
79
|
+
return base64.b64decode(encoded_answer.encode()).decode()
|
|
80
|
+
|
|
81
|
+
def serve(self) -> pn.Column:
|
|
82
|
+
"""Serves the complete question widget."""
|
|
83
|
+
return pn.Column(
|
|
84
|
+
self.question_widget,
|
|
85
|
+
self.options_widget,
|
|
86
|
+
self.submit_button,
|
|
87
|
+
self.feedback_widget,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
question_data = {
|
|
93
|
+
"question": "Select all features commonly found along a coastline",
|
|
94
|
+
"options": {
|
|
95
|
+
"a": "Beaches",
|
|
96
|
+
"b": "Glaciers",
|
|
97
|
+
"c": "Estuaries",
|
|
98
|
+
"d": "Mountains",
|
|
99
|
+
},
|
|
100
|
+
"answers": ["a", "c"], # Multiple correct answers
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
mcq = MultipleSelectionQuestion(
|
|
104
|
+
question_name="Q1: Coastline Features Quiz",
|
|
105
|
+
question_text=question_data["question"],
|
|
106
|
+
question_options=question_data["options"],
|
|
107
|
+
question_answers=question_data["answers"],
|
|
108
|
+
)
|
|
109
|
+
print("done")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import panel as pn
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NumericQuestion:
|
|
5
|
+
"""
|
|
6
|
+
A class to create and manage a numeric answer question widget.
|
|
7
|
+
|
|
8
|
+
This class creates a numeric question using Panel widgets.
|
|
9
|
+
It supports a question text, numeric answer, and precision for the answer.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
question_text (str): The text of the question.
|
|
13
|
+
correct_answer (float): The correct numeric answer.
|
|
14
|
+
precision (int): The precision of the numeric answer.
|
|
15
|
+
name (str): The name of the question widget.
|
|
16
|
+
question_widget (pn.widgets.StaticText): The widget for displaying the question.
|
|
17
|
+
answer_input (pn.widgets.FloatInput): The widget for inputting the answer.
|
|
18
|
+
submit_button (pn.widgets.Button): The button to submit the answer.
|
|
19
|
+
feedback_widget (pn.widgets.StaticText): The widget to display feedback.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
question_data (Dict[str, any]): The data for the question, including text and answer.
|
|
23
|
+
name (str): The name for the question widget.
|
|
24
|
+
precision (int): The precision for rounding the numeric answer.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
question_name: str,
|
|
30
|
+
question_text: str,
|
|
31
|
+
question_answer: float,
|
|
32
|
+
precision: int = 0,
|
|
33
|
+
**kwargs,
|
|
34
|
+
):
|
|
35
|
+
self.name: str = question_name
|
|
36
|
+
self.question_text: str = question_text
|
|
37
|
+
self.precision: int = precision
|
|
38
|
+
self.correct_answer: float = round(float(question_answer), self.precision)
|
|
39
|
+
self.create_widgets()
|
|
40
|
+
|
|
41
|
+
def create_widgets(self) -> None:
|
|
42
|
+
"""Create and initialize the Panel widgets for the question."""
|
|
43
|
+
self.question_widget = pn.widgets.StaticText(
|
|
44
|
+
name=self.name, value=self.question_text
|
|
45
|
+
)
|
|
46
|
+
self.answer_input = pn.widgets.FloatInput(name="Your Answer")
|
|
47
|
+
self.submit_button = pn.widgets.Button(name="Submit")
|
|
48
|
+
self.feedback_widget = pn.widgets.StaticText()
|
|
49
|
+
self.submit_button.on_click(self.check_answer)
|
|
50
|
+
|
|
51
|
+
def check_answer(self, event) -> None:
|
|
52
|
+
"""Check the submitted answer against the correct answer."""
|
|
53
|
+
try:
|
|
54
|
+
user_answer = round(float(self.answer_input.value), self.precision)
|
|
55
|
+
if user_answer == self.correct_answer:
|
|
56
|
+
self.feedback_widget.value = "Correct!"
|
|
57
|
+
else:
|
|
58
|
+
self.feedback_widget.value = "Incorrect, try again."
|
|
59
|
+
except ValueError:
|
|
60
|
+
self.feedback_widget.value = "Please enter a valid number."
|
|
61
|
+
|
|
62
|
+
def serve(self) -> pn.Column:
|
|
63
|
+
"""Serve the question as a Panel column."""
|
|
64
|
+
return pn.Column(
|
|
65
|
+
self.question_widget,
|
|
66
|
+
self.answer_input,
|
|
67
|
+
self.submit_button,
|
|
68
|
+
self.feedback_widget,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
# Example Usage
|
|
74
|
+
question_data = {
|
|
75
|
+
"question": "What is the relative importance of S2 vs M2?",
|
|
76
|
+
"answer": 0.33,
|
|
77
|
+
"kwargs": {"precision": 2},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
nq = NumericQuestion(
|
|
81
|
+
question_name="Q3: Simple numeric question",
|
|
82
|
+
question_text=question_data["question"],
|
|
83
|
+
question_answer=question_data["answer"],
|
|
84
|
+
**question_data["kwargs"],
|
|
85
|
+
)
|
|
86
|
+
print("done")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import panel as pn
|
|
6
|
+
|
|
7
|
+
pn.extension()
|
|
8
|
+
|
|
9
|
+
warnings.filterwarnings("ignore", category=FutureWarning, module="holoviews.core.data")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DynamicWavePlot(pn.viewable.Viewer):
|
|
13
|
+
"""
|
|
14
|
+
A class for creating a dynamic wave plot with adjustable parameters.
|
|
15
|
+
|
|
16
|
+
This class uses Panel widgets to create an interactive visualization
|
|
17
|
+
of a sine wave, where the amplitude 'a' and wavelength 'L' can be adjusted.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
amplitude_slider (pn.widgets.FloatSlider): Slider to adjust the amplitude.
|
|
21
|
+
wavelength_slider (pn.widgets.FloatSlider): Slider to adjust the wavelength.
|
|
22
|
+
plot (pn.Column): A Panel Column containing the dynamic plot.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
amplitude_range: tuple[float, float, float],
|
|
28
|
+
wavelength_range: tuple[float, float, float],
|
|
29
|
+
):
|
|
30
|
+
"""Inits DynamicWavePlot with ranges for amplitude and wavelength sliders."""
|
|
31
|
+
self.amplitude_slider = pn.widgets.FloatSlider(
|
|
32
|
+
name="Amplitude [a]",
|
|
33
|
+
start=amplitude_range[0],
|
|
34
|
+
end=amplitude_range[1],
|
|
35
|
+
step=amplitude_range[2],
|
|
36
|
+
value=amplitude_range[0],
|
|
37
|
+
)
|
|
38
|
+
self.wavelength_slider = pn.widgets.FloatSlider(
|
|
39
|
+
name="Wavelength [L]",
|
|
40
|
+
start=wavelength_range[0],
|
|
41
|
+
end=wavelength_range[1],
|
|
42
|
+
step=wavelength_range[2],
|
|
43
|
+
value=wavelength_range[0],
|
|
44
|
+
)
|
|
45
|
+
self.plot = pn.bind(
|
|
46
|
+
self.generate_wave_plot, a=self.amplitude_slider, L=self.wavelength_slider
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def generate_wave_plot(self, a: float, L: float):
|
|
50
|
+
"""Generates a sine wave plot based on the given amplitude and wavelength."""
|
|
51
|
+
x = np.linspace(0, 12, 100)
|
|
52
|
+
eta = a * np.sin(2 * np.pi / L * x)
|
|
53
|
+
df = pd.DataFrame({"x": x, "eta": eta})
|
|
54
|
+
return df.hvplot.line(x="x", y="eta", grid=True, width=900, height=600)
|
|
55
|
+
|
|
56
|
+
def __panel__(self) -> pn.Column:
|
|
57
|
+
"""Creates a Panel layout with the sliders and the plot."""
|
|
58
|
+
return pn.Column(self.amplitude_slider, self.wavelength_slider, self.plot)
|