diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..fa6720f Binary files /dev/null and b/.DS_Store differ diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7da1f96 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 100 diff --git a/.github/.DS_Store b/.github/.DS_Store new file mode 100644 index 0000000..6eb0d3f Binary files /dev/null and b/.github/.DS_Store differ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..b21b4a5 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,64 @@ +name: Docs + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + name: Docs + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Autogenerate new documentation + continue-on-error: true + run: | + pip install -e . + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install pyyaml + python automate_mkdocs.py + git add . + - name: Update and Build GH Pages + run: | + python -m pip install --upgrade pip + pip install mkdocs==1.2.3 + pip install mkgendocs==0.9.0 + pip install markupsafe==2.0.1 + pip install jinja2==2.11 + gendocs --config mkgendocs.yml + - name: deploy + run: | + mkdocs gh-deploy --force --clean --verbose + + - name: Commit any changes to docs + continue-on-error: true + run: | + git config --local user.name "github-actions[bot]" + git add ./docs + git commit -m "Auto-updating the docs" + + + - name: Push the changes to master + continue-on-error: true + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: main + force: true + + + diff --git a/.github/workflows/lint_action.yml b/.github/workflows/lint_action.yml new file mode 100644 index 0000000..5169118 --- /dev/null +++ b/.github/workflows/lint_action.yml @@ -0,0 +1,37 @@ +name: Lint + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + + - name: Install Python dependencies + run: pip install black flake8 + + - name: Run linters + uses: wearerequired/lint-action@v2 + with: + auto_fix: true + black: true + black_auto_fix: true + flake8: true + flake8_auto_fix: false diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..db8f299 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,35 @@ +name: Tests + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + name: Running Tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install -e . + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Test with pytest + run: | + pytest + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index a0cbad3..d6ccdb4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Please read/watch all those materials before you start the exercise. 7. Write a function in plotting.py that creates a scatter plot and uses the [ALLFED Style Sheet](https://github.com/allfed/ALLFED-matplotlib-style-sheet) 8. Make your repository an installable package as described in [Good Research Code Handbook](https://goodresearch.dev/setup.html) 9. Add a Jupyter Notebook in your scripts folder and import numerical.py and call it -11. Write two test for numerical.py +11. Write two tests for numerical.py 12. Make sure that the documenation of all code follows the [ALLFED Guidelines](https://github.com/allfed/ALLFED-Repository-Template#allfed-python-style-guide) 13. Automate the tests, so they run on every commit (you can just copy the files needed for that from [the template](https://github.com/allfed/ALLFED-Repository-Template) 14. Create an environment.yml that specifies how your virtual environment can be recreated and save it in the repository diff --git a/automate_mkdocs.py b/automate_mkdocs.py new file mode 100644 index 0000000..95b8bcc --- /dev/null +++ b/automate_mkdocs.py @@ -0,0 +1,222 @@ +"""Automates Python scripts formatting, linting and Mkdocs documentation.""" + +import ast +import importlib +import json +import yaml +from collections import defaultdict +from pathlib import Path +from typing import Union + + +def add_val(indices, value, data): + if not len(indices): + return + element = data + for index in indices[:-1]: + element = element[index] + element[indices[-1]] = value + + +def automate_mkdocs_from_docstring( + mkdocs_dir: Union[str, Path], mkgendocs_f: str, repo_dir: Path, match_string: str +) -> dict: + """Automates the -pages for mkgendocs package by adding all Python functions in a + directory to the mkgendocs config. + Args: + mkdocs_dir (typing.Union[str, pathlib.Path]): textual directory for + the hierarchical directory & navigation in Mkdocs + mkgendocs_f (str): The configurations file for the mkgendocs package + repo_dir (pathlib.Path): textual directory to search for Python functions in + match_string (str): the text to be matches, after which the functions will be + added in mkgendocs format + Example: + >>> + >>> automate_mkdocs_from_docstring('scripts', repo_dir=Path.cwd(), match_string='pages:') + Returns: + list: list of created markdown files and their relative paths + + """ + p = repo_dir.glob("**/*.py") + scripts = [x for x in p if x.is_file()] + + if ( + Path.cwd() != repo_dir + ): # look for mkgendocs.yml in the parent file if a subdirectory is used + repo_dir = repo_dir.parent + + functions = defaultdict(dict) + structure = fix(defaultdict)() + full_repo_dir = str(repo_dir) + "/" + for script in scripts: + + with open(script, "r") as source: + tree = ast.parse(source.read()) + funcs = {"classes": [], "functions": []} + for child in ast.iter_child_nodes(tree): + try: + if isinstance( + child, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef) + ): + if child.name not in ["main"]: + + relative_path = ( + str(script) + .replace(full_repo_dir, "") + .replace("/", ".") + .replace(".py", "") + ) + module = importlib.import_module(relative_path) + f_ = getattr(module, child.name) + function = f_.__name__ + if isinstance(child, (ast.ClassDef)): + funcs["classes"].append(function) + if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)): + funcs["functions"].append(function) + + except Exception as e: + print("trouble on importing " + script.stem) + print("did not document " + child.name) + print(str(e)) + if not funcs["classes"]: + funcs.pop("classes") + if not funcs["functions"]: + funcs.pop("functions") + if funcs: + functions[script] = funcs + with open(f"{repo_dir}/{mkgendocs_f}", "r+") as mkgen_config: + insert_string = "" + for path, function_names in functions.items(): + relative_path = str(path).replace(full_repo_dir, "").replace(".py", "") + insert_string += ( + f' - page: "{mkdocs_dir}/{relative_path}.md"\n ' + f'source: "{relative_path}.py"\n' # functions:\n' + ) + page = f"{mkdocs_dir}/{relative_path}" + split_page = page.split("/") + split_page = [" - " + s for s in split_page] + page += f".md" + + add_val(split_page, page, structure) + for class_name, class_list in function_names.items(): + insert_string += f" {class_name}:\n" + f_string = "" + for f in class_list: + insert_f_string = f" - {f}\n" + f_string += insert_f_string + + insert_string += f_string + insert_string += "\n" + + contents = mkgen_config.readlines() + if match_string in contents[-1]: + contents.append(insert_string) + else: + + for index, line in enumerate(contents): + if match_string in line and insert_string not in contents[index + 1]: + + contents = contents[: index + 1] + contents.append(insert_string) + break + + with open(f"{repo_dir}/{mkgendocs_f}", "w") as mkgen_config: + mkgen_config.writelines(contents) + + return structure + + +def automate_nav_structure( + mkdocs_dir: Union[str, Path], + mkdocs_f: str, + repo_dir: Path, + match_string: str, + structure: dict, +) -> str: + """Automates the -pages for mkgendocs package by adding all Python + functions in a directory to the mkgendocs config. + Args: + mkdocs_dir (typing.Union[str, pathlib.Path]): textual directory for + the hierarchical directory & navigation in Mkdocs + mkgendocs_f (str): The configurations file for the mkgendocs package + repo_dir (pathlib.Path): textual directory to search for Python functions in + match_string (str): the text to be matches, after which the functions + will be added in mkgendocs format + Example: + >>> + >>> automate_mkdocs_from_docstring('scripts', repo_dir=Path.cwd(), match_string='pages:') + Returns: + str: feedback message + + """ + insert_string = yaml.safe_dump(json.loads(json.dumps(structure, indent=4))).replace( + "'", "" + ) + # print(structure) + with open(f"{repo_dir}/{mkdocs_f}", "r+") as mkgen_config: + assert mkgen_config is not None + contents = mkgen_config.readlines() + if match_string in contents[-1]: + contents.append(insert_string) + else: + + for index, line in enumerate(contents): + if match_string in line and insert_string not in contents[index + 1]: + + contents = contents[: index + 1] + contents.append(insert_string) + break + + with open(f"{repo_dir}/{mkdocs_f}", "w") as mkgen_config: + mkgen_config.writelines(contents) + + +def fix(f): + """Allows creation of arbitrary length dict item + + Args: + f (type): Description of parameter `f`. + + Returns: + type: Description of returned object. + + """ + return lambda *args, **kwargs: f(fix(f), *args, **kwargs) + + +def indent(string: str) -> int: + """Count the indentation in whitespace characters. + Args: + string (str): text with indents + Returns: + int: Number of whitespace indentations + + """ + return sum(4 if char == "\t" else 1 for char in string[: -len(string.lstrip())]) + + +def main(): + """Execute when running this script.""" + # This is the path the script will look for code to document + python_tips_dir = Path.cwd().joinpath("src") + # This is the path the script will look for mkgendocs.yml + root_dir = Path.cwd() + + structure = automate_mkdocs_from_docstring( + mkdocs_dir="modules", + mkgendocs_f="mkgendocs.yml", + repo_dir=python_tips_dir, + match_string="pages:\n", + ) + + automate_nav_structure( + mkdocs_dir="modules", + mkdocs_f="mkdocs.yml", + repo_dir=root_dir, + match_string="- Home: index.md\n", + structure=structure, + ) + + +if __name__ == "__main__": + main() diff --git a/data/readme.txt b/data/readme.txt new file mode 100644 index 0000000..1b7b100 --- /dev/null +++ b/data/readme.txt @@ -0,0 +1 @@ +data: Where you put raw data for your project. You usually won’t sync this to source control, unless you use very small, text-based datasets (< 10 MBs). diff --git a/docs/readme.txt b/docs/readme.txt new file mode 100644 index 0000000..8128cbf --- /dev/null +++ b/docs/readme.txt @@ -0,0 +1 @@ +docs: Where you put documentation, including Markdown and reStructuredText (reST). Calling it docs makes it easy to publish documentation online through Github pages. diff --git a/docs/sources/index.md b/docs/sources/index.md new file mode 100644 index 0000000..d6ccdb4 --- /dev/null +++ b/docs/sources/index.md @@ -0,0 +1,32 @@ +# Github-Training-Repository +This repository is meant to be used to train new hires/volunteers on how to use Github. + +# Preparation Material +Please read/watch all those materials before you start the exercise. + +* [Getting started with anaconda and conda](https://youtu.be/YJC6ldI3hWk) +* [Good Research Code Handbook](https://goodresearch.dev/) +* [How to use Github with Github Desktop](https://www.youtube.com/watch?v=8Dd7KRpKeaE) +* [ALLFED Repository Template Readme](https://github.com/allfed/ALLFED-Repository-Template) +* [Overview of Python](https://www.youtube.com/watch?v=kqtD5dpn9C8) +* [Automated Testing](https://blog.deepjyoti30.dev/tests-github-python) +* The [OpenAI Chat Bot](https://chat.openai.com/chat) is pretty good at answering programming questions + +# Exercise +1. Fork this repository +2. Clone it to your local computer +3. Recreate the folder structure as described in the [ALLFED Guidelines](https://github.com/allfed/ALLFED-Repository-Template) +4. Create a local virtual environment for the repository +5. Create two files in the src folder: numerical.py and plotting.py +6. Write a function in numerical.py that takes at least one argument and returns a numerical value +7. Write a function in plotting.py that creates a scatter plot and uses the [ALLFED Style Sheet](https://github.com/allfed/ALLFED-matplotlib-style-sheet) +8. Make your repository an installable package as described in [Good Research Code Handbook](https://goodresearch.dev/setup.html) +9. Add a Jupyter Notebook in your scripts folder and import numerical.py and call it +11. Write two tests for numerical.py +12. Make sure that the documenation of all code follows the [ALLFED Guidelines](https://github.com/allfed/ALLFED-Repository-Template#allfed-python-style-guide) +13. Automate the tests, so they run on every commit (you can just copy the files needed for that from [the template](https://github.com/allfed/ALLFED-Repository-Template) +14. Create an environment.yml that specifies how your virtual environment can be recreated and save it in the repository +15. Send back a pull request +16. Invite one of the data scientist (either florian@allfed.info or morgan@allfed.info) to review your pull request + +If you get stuck at any point please reach out to one of the data scientists (either florian@allfed.info or morgan@allfed.info). diff --git a/docs/sources/modules/src/numerical.md b/docs/sources/modules/src/numerical.md new file mode 100644 index 0000000..3ef60ff --- /dev/null +++ b/docs/sources/modules/src/numerical.md @@ -0,0 +1,24 @@ +# + + +### bumpy_reduction +[source](https://github.com/allfed/My-Super-Cool-Respository/blob/master/src/numerical.py/#L1) +```python +.bumpy_reduction( + x, recursions = 5 +) +``` + +--- +This function is a recursive function that does some whacky things that I can't explain + +**Arguments** + +* **x** : input number +* **recursions** : must be a positive int, default is 5 if noit specified, is the number of times the function will recurse + + +**Returns** + +* **function** : if recursions is 0, returns x, otherwise returns bumpy_reduction(x/2 + recursions**2, recursions - 1) + diff --git a/docs/sources/modules/src/plotting.md b/docs/sources/modules/src/plotting.md new file mode 100644 index 0000000..bf69bfa --- /dev/null +++ b/docs/sources/modules/src/plotting.md @@ -0,0 +1,26 @@ +# + + +### plot_data +[source](https://github.com/allfed/My-Super-Cool-Respository/blob/master/src/plotting.py/#L4) +```python +.plot_data( + x, y, title, x_label, y_label +) +``` + +--- +Plots the results of the model + +**Arguments** + +* **x** : x values to plot +* **y** : y values to plot +* **title** : title of the plot +* **x_label** : label for the x axis +* **y_label** : label for the y axis + + +**Returns** + +None, but plots the results diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..3a9cb13 --- /dev/null +++ b/environment.yml @@ -0,0 +1,109 @@ +name: code_training +channels: + - defaults +dependencies: + - appnope=0.1.2=py310hecd8cb5_1001 + - asttokens=2.0.5=pyhd3eb1b0_0 + - backcall=0.2.0=pyhd3eb1b0_0 + - blas=1.0=mkl + - brotli=1.0.9=hca72f7f_7 + - brotli-bin=1.0.9=hca72f7f_7 + - bzip2=1.0.8=h1de35cc_0 + - ca-certificates=2022.10.11=hecd8cb5_0 + - certifi=2022.9.24=py310hecd8cb5_0 + - contourpy=1.0.5=py310haf03e11_0 + - cycler=0.11.0=pyhd3eb1b0_0 + - debugpy=1.5.1=py310he9d5cce_0 + - decorator=5.1.1=pyhd3eb1b0_0 + - entrypoints=0.4=py310hecd8cb5_0 + - executing=0.8.3=pyhd3eb1b0_0 + - fonttools=4.25.0=pyhd3eb1b0_0 + - freetype=2.12.1=hd8bbffd_0 + - giflib=5.2.1=haf1e3a3_0 + - intel-openmp=2021.4.0=hecd8cb5_3538 + - ipykernel=6.15.2=py310hecd8cb5_0 + - ipython=8.6.0=py310hecd8cb5_0 + - jedi=0.18.1=py310hecd8cb5_1 + - jpeg=9e=hca72f7f_0 + - jupyter_client=7.4.7=py310hecd8cb5_0 + - jupyter_core=4.11.2=py310hecd8cb5_0 + - kiwisolver=1.4.2=py310he9d5cce_0 + - lcms2=2.12=hf1fd2bf_0 + - lerc=3.0=he9d5cce_0 + - libbrotlicommon=1.0.9=hca72f7f_7 + - libbrotlidec=1.0.9=hca72f7f_7 + - libbrotlienc=1.0.9=hca72f7f_7 + - libcxx=14.0.6=h9765a3e_0 + - libdeflate=1.8=h9ed2024_5 + - libffi=3.4.2=hecd8cb5_6 + - libpng=1.6.37=ha441bb4_0 + - libsodium=1.0.18=h1de35cc_0 + - libtiff=4.4.0=h2cd0358_2 + - libwebp=1.2.4=h56c3ce4_0 + - libwebp-base=1.2.4=hca72f7f_0 + - lz4-c=1.9.3=h23ab428_1 + - matplotlib=3.6.2=py310hecd8cb5_0 + - matplotlib-base=3.6.2=py310h220de94_0 + - matplotlib-inline=0.1.6=py310hecd8cb5_0 + - mkl=2021.4.0=hecd8cb5_637 + - mkl-service=2.4.0=py310hca72f7f_0 + - mkl_fft=1.3.1=py310hf879493_0 + - mkl_random=1.2.2=py310hc081a56_0 + - munkres=1.1.4=py_0 + - ncurses=6.3=hca72f7f_3 + - nest-asyncio=1.5.5=py310hecd8cb5_0 + - numpy=1.23.4=py310h9638375_0 + - numpy-base=1.23.4=py310ha98c3c9_0 + - openssl=1.1.1s=hca72f7f_0 + - packaging=21.3=pyhd3eb1b0_0 + - parso=0.8.3=pyhd3eb1b0_0 + - pexpect=4.8.0=pyhd3eb1b0_3 + - pickleshare=0.7.5=pyhd3eb1b0_1003 + - pillow=9.2.0=py310hde71d04_1 + - pip=22.3.1=py310hecd8cb5_0 + - prompt-toolkit=3.0.20=pyhd3eb1b0_0 + - psutil=5.9.0=py310hca72f7f_0 + - ptyprocess=0.7.0=pyhd3eb1b0_2 + - pure_eval=0.2.2=pyhd3eb1b0_0 + - pygments=2.11.2=pyhd3eb1b0_0 + - pyparsing=3.0.9=py310hecd8cb5_0 + - python=3.10.8=h218abb5_1 + - python-dateutil=2.8.2=pyhd3eb1b0_0 + - pyzmq=23.2.0=py310he9d5cce_0 + - readline=8.2=hca72f7f_0 + - setuptools=65.5.0=py310hecd8cb5_0 + - six=1.16.0=pyhd3eb1b0_1 + - sqlite=3.40.0=h880c91c_0 + - stack_data=0.2.0=pyhd3eb1b0_0 + - tk=8.6.12=h5d9f67b_0 + - tornado=6.2=py310hca72f7f_0 + - traitlets=5.1.1=pyhd3eb1b0_0 + - tzdata=2022g=h04d1e81_0 + - wcwidth=0.2.5=pyhd3eb1b0_0 + - wheel=0.37.1=pyhd3eb1b0_0 + - xz=5.2.8=h6c40b1e_0 + - zeromq=4.3.4=h23ab428_0 + - zlib=1.2.13=h4dc903c_0 + - zstd=1.5.2=hcb37349_0 + - pip: + - attrs==22.1.0 + - babel==2.11.0 + - click==8.1.3 + - exceptiongroup==1.0.4 + - flask==2.2.2 + - flask-moment==1.0.5 + - flask-sqlalchemy==3.0.2 + - flask-wtf==1.0.1 + - greenlet==2.0.1 + - iniconfig==1.1.1 + - itsdangerous==2.1.2 + - jinja2==3.1.2 + - markupsafe==2.1.1 + - pluggy==1.0.0 + - pytest==7.2.0 + - pytz==2022.6 + - sqlalchemy==1.4.45 + - tomli==2.0.1 + - werkzeug==2.2.2 + - wtforms==3.0.1 +prefix: /Users/kevin/opt/miniconda3/envs/code_training diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..0b58c4a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,28 @@ +site_name: My Super Cool Respository + + +# Repository +repo_name: My Super Cool Respository +repo_url: https://github.com/allfed/My Super-Cool-Respository +edit_uri: "" +# Copyright +copyright: Me + +docs_dir: "docs/sources" + +plugins: + - search + +theme: + name: 'readthedocs' + custom_dir: 'docs' + palette: + primary: 'white' + logo: 'assets/images/logo_full.svg' + favicon: 'assets/images/favicon.svg' + features: + - navigation.tabs + - navigation.tabs.sticky + - instant +nav: + diff --git a/mkgendocs.yml b/mkgendocs.yml new file mode 100644 index 0000000..aee3565 --- /dev/null +++ b/mkgendocs.yml @@ -0,0 +1,21 @@ +site_name: My Super Cool Respository + + +# Repository +repo_name: My Super Cool Respository +repo: https://github.com/allfed/My-Super-Cool-Respository +# Copyright +copyright: ALLFED + +#This specifies the autogeneration portion +pages: + - page: "modules/src/numerical.md" + source: "src/numerical.py" + functions: + - bumpy_reduction + + - page: "modules/src/plotting.md" + source: "src/plotting.py" + functions: + - plot_data + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4355f56 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +pandas>=1.4.3 +setuptools>=61.2.0 +pytest>=7.1.2 +numpy>=1.23.1 +matplotlib>=3.5.1 +mkdocs diff --git a/results/readme.txt b/results/readme.txt new file mode 100644 index 0000000..aede27e --- /dev/null +++ b/results/readme.txt @@ -0,0 +1 @@ +results: Where you put results, including checkpoints, hdf5 files, pickle files, as well as figures and tables. If these files are heavy, you won’t put these under source control. diff --git a/scripts/readme.txt b/scripts/readme.txt new file mode 100644 index 0000000..e7c528c --- /dev/null +++ b/scripts/readme.txt @@ -0,0 +1 @@ +scripts: Where you put scripts - Python and bash alike - as well as .ipynb notebooks. diff --git a/scripts/training_notebook.ipynb b/scripts/training_notebook.ipynb new file mode 100644 index 0000000..836595e --- /dev/null +++ b/scripts/training_notebook.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import src.numerical\n", + "import numpy as np\n", + "import src.plotting\n", + "\n", + "# Create a list of starting points and a list of recursions\n", + "# recursions = np.linspace(0,1,2)\n", + "# starting_points = np.linspace(0,1,)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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FStWKCMjQyEhIbLb7dq3b5/+8pe/6ODBg3r//fd1++23t+v/3//93zpy5IgsFouWL1+uRYsWKTw8XHV1dVq3bp0+/vhjhYeHG/6e2rQFcxaLRUuXLtX111+v2NhYud1u1dTU6Pjx4yoqKvLs/5vf/MZz51psbGyHuzB7Kj8/X2vXrpUkTZs2TbfffrtGjRolh8OhPXv26K233tLp06f14osv6tFHH/V8Hi5XX1+vNWvWKCsrS8uWLdOIESPU3NysXbt2ae3atTp79qz+9re/acWKFX27OAAAwCcI5wAAgN96/PHHPb9vW63V5XIpLCxMc+bM0de//nWfPqbX2Nio73//+7r22ms92xISEnT//ffrySefVGVlpXJzc/Xoo48qLS3Ns8/48eP1ne98R7/97W914sQJVVdXe12hMzo6Wg8//LBCQkIkSVarVbNnz1ZkZKRWr16t0tJS7d+/v93jnAsWLNDatWu1c+dO3XTTTR2CmoMHD6q2tlY2m01ZWVmGXY+6ujrl5OR4HmlNTEzscFfgjh07VFpaqjFjxuiRRx5pd5dWaGiosrKyNHbsWP3Hf/yHtm3bphtvvNFzt9nFixeVk5MjScrOztayZcvaXae77rpLjY2N2r17t2Hv6XJHjx713DH37W9/W1/5ylc8bRaLRcOHD9ecOXM0Z84cw8/9/vvvS2p9HPrBBx/0/JnabDZdd911Cg8P1+9//3sVFxd3+Dy0aW5uVlZWlu6++27PtpCQEF1//fW6cOGCNm/erN27dxPOAQDgZwjnAACA36qtre10e3NzsxobG1VXV+d1XjEjjBgxotMgZtSoURo1apQqKiqUlpbWLphrk56eLpvNJofDodOnT3sN55YuXeoJ5i6XmZmpiRMnqri4WLt3724XxmRlZemDDz7QxYsXdejQIU2bNq1d3x07dkiSZsyYoaioqF6958v9x3/8hyckant0s82wYcP0/e9/v0OftnN/5Stf8fr45Pjx4zVmzBidOXNGhYWFuuaaayRJ+/btk8vlUnBwsL761a922vfmm2/2WTjXFjqOGTOmXTDna1988YXOnj0rSVq2bFmnd8VNmzZNKSkpKikp6fB5uFx2dnan26dNm6bNmzeroqJCzc3NnX7mAACAOQjnAACA33rhhRfavW5paVF5eblyc3O1c+dOHT16VCtXrtSMGTN8cv7x48d7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27W1BRRt4cLMmTO1aNEiSa3zKJSVlXmdA+vixYue/YBA0p/xdKXc3FytW7dO4eHhevjhh73O58h4AjoaMWKEpL9//q/UNl7a9gPQXn19vVavXq2qqirNnTtX3/zmNzvs0/a94u37x263q7GxUeHh4Z7QARgqDh06pPDwcL355pvttre0tEhqvbOu7f+f/uEf/kFhYWGMKQwKhHOAibpaBbK8vFzl5eXtHoNITEzUwYMHVVZW1mmftu3jxo0ztlAgQHz22Wd6++23FRISooceesjripMS4wnoTNvnnXEB9F5TU5N+97vf6dy5c5oxY4buvvvuTh8RT0hIkM1mU319vaqrqzv8JRDjDENdY2Ojjh8/3mlbS0uLp83lckliTGFwIJwDTLB8+XItX76807ZXXnlFeXl5Wrlypa655pp2bVdffbXWr1+v/Px8tbS0tJurrra2VkVFRQoPD1daWppP6wf80aFDh/TKK6/IarXqwQcfVGpqapf7M56AjlJTUxUeHq6KigqVlZV1CLj37dsnqXX8APi7lpYW/f73v9epU6c0ZcoUrVy5UkFBna+9FxISokmTJunw4cPat29fh8de9+7dK0maOnWqz+sG/M0LL7zQ6fbKyko98cQTSkhI0JNPPtmujTGFwYDVWoEAkpKSotTUVNXV1en999/3bHc6nZ75GRYtWiSr1WpekYAJTpw4oRdffFGStHLlSk2ZMqXbPownoCObzabrr79ekvTWW2+1m3tu8+bNOn36tFJTU7tcjAUYalwul/785z/r2LFjSktL04MPPiibret7IJYsWSJJ2rBhg86fP+/ZXlxcrO3btyssLEzz5s3zad3AYMKYQqDjzjkgwNxzzz165plntHXrVhUWFmrMmDEqLS3VhQsXlJKSouzsbLNLBAbcCy+8oJaWFo0cOVIHDx7UwYMHO+yTmpqq+fPnt9vGeAI6WrZsmY4ePari4mL9+7//u1JTU1VVVaWSkhJFRkbqnnvuMbtEwK/k5OTowIEDkqTIyMgOc2W1+eY3v6moqChJUmZmpm644QZt3bpVTz/9tDIzM+V0OlVQUCC326377ruv08WMAHSOMYVARzgHBJj4+Hj98z//s9atW6cjR47owIEDio2N1bJly3TjjTe2ezQPGCoaGxsltT7yUFlZ6XW/K8M5xhPQUXBwsH7yk5/oo48+0u7du3Xw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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "recursions_start = 1\n", + "recursion_end = 50\n", + "starting_points = -100\n", + "end_points = 100\n", + "step = 1\n", + "\n", + "x_vals = []\n", + "y_vals = []\n", + "\n", + "for starting_point in range(starting_points,end_points, step):\n", + " for recursion in range(recursions_start,recursion_end, step):\n", + " x = src.numerical.bumpy_reduction(starting_point, recursion)\n", + " y = recursion\n", + " x_vals.append(x)\n", + " y_vals.append(y)\n", + "\n", + "\n", + "title = \"Bumpy Reduction\"\n", + "x_label = \"Starting Point\"\n", + "y_label = \"Recursions\"\n", + "\n", + "src.plotting.plot_data(x_vals,y_vals, title, x_label, y_label)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.8 ('code_training')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "c1776c3a062e736b6ae916e99897399ef44bbcb41164826faa770db8a71877b3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..161f66e --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +from setuptools import find_packages, setup + +setup( + name="src", + packages=find_packages(), +) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/environment.yml b/src/environment.yml new file mode 100644 index 0000000..c4d8320 --- /dev/null +++ b/src/environment.yml @@ -0,0 +1,16 @@ +name: code_training +channels: + - defaults +dependencies: + - ipykernel=6.15.2=py310hecd8cb5_0 + - ipython=8.6.0=py310hecd8cb5_0 + - jupyter_client=7.4.7=py310hecd8cb5_0 + - jupyter_core=4.11.2=py310hecd8cb5_0 + - matplotlib-inline=0.1.6=py310hecd8cb5_0 + - matplotlib=3.5.2 + - numpy=1.23.4=py310h9638375_0 + - numpy-base=1.23.4=py310ha98c3c9_0 + - pip=22.3.1=py310hecd8cb5_0 + - python=3.10.8=h218abb5_1 + - python-dateutil=2.8.2=pyhd3eb1b0_0 +prefix: /Users/kevin/opt/miniconda3/envs/code_training diff --git a/src/numerical.py b/src/numerical.py new file mode 100644 index 0000000..fcf43b9 --- /dev/null +++ b/src/numerical.py @@ -0,0 +1,16 @@ +def bumpy_reduction(x,recursions = 5): + """ + This function is a recursive function that does some whacky things that I can't explain + Arguments: + x: input number + recursions: must be a positive int, default is 5 if noit specified, is the number of times the function will recurse + Returns: + recursive function: if recursions is 0, returns x, otherwise returns bumpy_reduction(x/2 + recursions**2, recursions - 1) + """ + if type(recursions) != int or recursions < 0: + raise NotImplementedError('fib only defined on non-negative integers.') + elif recursions == 0: + return x + else: + return bumpy_reduction(x/2 + recursions**2, recursions - 1) + diff --git a/src/plotting.py b/src/plotting.py new file mode 100644 index 0000000..9edc104 --- /dev/null +++ b/src/plotting.py @@ -0,0 +1,24 @@ +import matplotlib.pyplot as plt +plt.style.use("https://raw.githubusercontent.com/allfed/ALLFED-matplotlib-style-sheet/main/ALLFED.mplstyle") + +def plot_data(x,y, title, x_label, y_label): + + + """ + Plots the results of the model + Arguments: + x: x values to plot + y: y values to plot + title: title of the plot + x_label: label for the x axis + y_label: label for the y axis + Returns: + None, but plots the results + """ + plt.scatter(x, y, alpha=0.5) + plt.title(title) + plt.xlabel(x_label) + plt.ylabel(y_label) + plt.show() + + diff --git a/src/readme.txt b/src/readme.txt new file mode 100644 index 0000000..503277b --- /dev/null +++ b/src/readme.txt @@ -0,0 +1 @@ +src: Where you put reusable Python modules for your project. This is the kind of python code that you import. diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/tests/.DS_Store differ diff --git a/tests/readme.txt b/tests/readme.txt new file mode 100644 index 0000000..2cbb868 --- /dev/null +++ b/tests/readme.txt @@ -0,0 +1 @@ +tests: Where you put tests for your code. diff --git a/tests/test_numerical.py b/tests/test_numerical.py new file mode 100644 index 0000000..7c6b646 --- /dev/null +++ b/tests/test_numerical.py @@ -0,0 +1,26 @@ +''' +write docstrings for your tests + + +''' +import src.numerical +import numpy as np +import pytest + +def test_bumpy_reduction(): + """ + Tests if the function returns the correct value (from a known result) + """ + expected_result = pytest.approx(11.99, abs=0.1) + assert src.numerical.bumpy_reduction(10, 20) == expected_result + + +def test_raises(): + """ + Tests if the function raises an error when the input is not a positive integer + """ + with pytest.raises(NotImplementedError): + src.numerical.bumpy_reduction(0.5, -1) + src.numerical.bumpy_reduction(0.5, 'dogs') + +