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.
@@ -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
+ ![](https://github.com/FlorisCalkoen/CoastalCodebook/blob/main/coastalcodebook/imgs/waves_angola.jpeg)
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
+ ![](https://github.com/FlorisCalkoen/CoastalCodebook/blob/main/coastalcodebook/imgs/waves_angola.jpeg)
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
@@ -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)