├── .all-contributorsrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── inject_solutions.bash ├── problems ├── __init__.py ├── ada_lovelaces_note_g.py ├── almost_pi.py ├── babylonian_spiral.py ├── babylonian_square_roots.py ├── blood_types.py ├── caesar_cipher.py ├── chaos.py ├── colorful_resistors.py ├── compound_interest.py ├── correlation_does_not_imply_causation.py ├── definite_integrals.py ├── dna_transcription.py ├── earthquake_epicenters.py ├── el_nino_intensities.py ├── exponential_growth.py ├── flight_paths.py ├── game_of_life.py ├── habitable_exoplanets.py ├── molecular_mass_calculator.py ├── nand_gate.py ├── numerical_diff.py ├── plump_moose.py ├── rna_translation.py ├── rock_star_climate.py ├── rocket_science.py ├── scientific_temperatures.py ├── sha_256.py ├── speed_of_light.py ├── temperature_variations.py ├── test_case.py └── wind_chill.py ├── pytest.ini ├── requirements.txt ├── resources ├── correlation_does_not_imply_causation │ └── spurious_xy.csv ├── el_nino_intensities │ └── mei.ext_index.txt ├── game_of_life │ ├── oscillators.txt │ ├── spaceships.txt │ └── still_life.txt └── molecular_mass_calculator │ └── periodic_table.csv ├── setup.py └── tests └── test_problems.py /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "ali-ramadhan", 10 | "name": "Ali Ramadhan", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/20099589?v=4", 12 | "profile": "http://aliramadhan.me", 13 | "contributions": [ 14 | "content", 15 | "test", 16 | "code" 17 | ] 18 | }, 19 | { 20 | "login": "basimr", 21 | "name": "br", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/9298270?v=4", 23 | "profile": "https://github.com/basimr", 24 | "contributions": [ 25 | "content", 26 | "code" 27 | ] 28 | } 29 | ], 30 | "contributorsPerLine": 7, 31 | "projectName": "lovelace-problems", 32 | "projectOwner": "project-lovelace", 33 | "repoType": "github", 34 | "repoHost": "https://github.com", 35 | "skipCi": true 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: [3.7, 3.8, 3.9] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | 22 | - name: Install Python dependencies 23 | run: | 24 | pip install --upgrade pip 25 | pip install -r requirements.txt 26 | 27 | - name: Set up package 28 | run: python setup.py develop 29 | 30 | - name: Clone lovelace-solutions 31 | run: | 32 | git clone https://${{ secrets.LOVELACE_GITHUB_TOKEN }}@github.com/project-lovelace/lovelace-solutions.git 33 | ln -s ../lovelace-solutions/python problems/solutions 34 | 35 | - name: Run tests 36 | run: pytest --capture=no --verbose tests/ 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # IDE files 92 | .idea 93 | 94 | # Symbolic links to secret stuff 95 | lovelace-solutions 96 | problems/solutions 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v3.2.0](https://github.com/project-lovelace/lovelace-problems/tree/v3.2.0) (2021-04-11) 4 | 5 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/v3.1.0...v3.2.0) 6 | 7 | **Closed issues:** 8 | 9 | - Record version of problems repo [\#46](https://github.com/project-lovelace/lovelace-problems/issues/46) 10 | 11 | **Merged pull requests:** 12 | 13 | - New problem: Exponential growth [\#67](https://github.com/project-lovelace/lovelace-problems/pull/67) ([ali-ramadhan](https://github.com/ali-ramadhan)) 14 | - Bump urllib3 from 1.26.3 to 1.26.4 [\#66](https://github.com/project-lovelace/lovelace-problems/pull/66) ([dependabot[bot]](https://github.com/apps/dependabot)) 15 | - Flesh out RNA translation tests [\#65](https://github.com/project-lovelace/lovelace-problems/pull/65) ([ali-ramadhan](https://github.com/ali-ramadhan)) 16 | - Parameterize test cases too [\#64](https://github.com/project-lovelace/lovelace-problems/pull/64) ([ali-ramadhan](https://github.com/ali-ramadhan)) 17 | - Test cases for RNA translation [\#63](https://github.com/project-lovelace/lovelace-problems/pull/63) ([ali-ramadhan](https://github.com/ali-ramadhan)) 18 | - Cleanup problem module logic [\#61](https://github.com/project-lovelace/lovelace-problems/pull/61) ([ali-ramadhan](https://github.com/ali-ramadhan)) 19 | 20 | ## [v3.1.0](https://github.com/project-lovelace/lovelace-problems/tree/v3.1.0) (2021-03-27) 21 | 22 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/v3.0.0...v3.1.0) 23 | 24 | **Closed issues:** 25 | 26 | - Test suite that just makes sure we can generate every test case? [\#54](https://github.com/project-lovelace/lovelace-problems/issues/54) 27 | - Change output for El Niño intensities when no El Nino or La Nina is present [\#32](https://github.com/project-lovelace/lovelace-problems/issues/32) 28 | - Rewrite README.me [\#11](https://github.com/project-lovelace/lovelace-problems/issues/11) 29 | 30 | **Merged pull requests:** 31 | 32 | - Run CI tests with Python 3.9 [\#58](https://github.com/project-lovelace/lovelace-problems/pull/58) ([ali-ramadhan](https://github.com/ali-ramadhan)) 33 | - Add GitHub Actions CI pipeline [\#57](https://github.com/project-lovelace/lovelace-problems/pull/57) ([ali-ramadhan](https://github.com/ali-ramadhan)) 34 | - Add a test suite [\#56](https://github.com/project-lovelace/lovelace-problems/pull/56) ([ali-ramadhan](https://github.com/ali-ramadhan)) 35 | - Revise El Nino intensities problem [\#55](https://github.com/project-lovelace/lovelace-problems/pull/55) ([ali-ramadhan](https://github.com/ali-ramadhan)) 36 | 37 | ## [v3.0.0](https://github.com/project-lovelace/lovelace-problems/tree/v3.0.0) (2021-03-25) 38 | 39 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/v2.0...v3.0.0) 40 | 41 | **Fixed bugs:** 42 | 43 | - Game of Life: Oscillator test case expected result is wrong. [\#41](https://github.com/project-lovelace/lovelace-problems/issues/41) 44 | 45 | **Closed issues:** 46 | 47 | - Blood types: Add more test cases that return False [\#42](https://github.com/project-lovelace/lovelace-problems/issues/42) 48 | - Bug in verifying flight paths solution [\#37](https://github.com/project-lovelace/lovelace-problems/issues/37) 49 | - RNA string needs to be reversed! [\#36](https://github.com/project-lovelace/lovelace-problems/issues/36) 50 | - Share verify\_user\_solution function [\#35](https://github.com/project-lovelace/lovelace-problems/issues/35) 51 | - Arrays should not be verified recursively [\#26](https://github.com/project-lovelace/lovelace-problems/issues/26) 52 | - Remove jargon from problem descriptions [\#10](https://github.com/project-lovelace/lovelace-problems/issues/10) 53 | 54 | **Merged pull requests:** 55 | 56 | - Plump moose and compound interest [\#52](https://github.com/project-lovelace/lovelace-problems/pull/52) ([ali-ramadhan](https://github.com/ali-ramadhan)) 57 | - Clean up README + sandbox and remove `output_str` [\#51](https://github.com/project-lovelace/lovelace-problems/pull/51) ([ali-ramadhan](https://github.com/ali-ramadhan)) 58 | - Fix typo in `almost_pi.py` [\#50](https://github.com/project-lovelace/lovelace-problems/pull/50) ([ali-ramadhan](https://github.com/ali-ramadhan)) 59 | - Fix: constant should be float [\#49](https://github.com/project-lovelace/lovelace-problems/pull/49) ([ali-ramadhan](https://github.com/ali-ramadhan)) 60 | - Remove `verify_user_solution` boilerplate [\#48](https://github.com/project-lovelace/lovelace-problems/pull/48) ([ali-ramadhan](https://github.com/ali-ramadhan)) 61 | - Deal in Python lists, not numpy arrays [\#47](https://github.com/project-lovelace/lovelace-problems/pull/47) ([ali-ramadhan](https://github.com/ali-ramadhan)) 62 | - blood\_types: Add more test cases where no donors are available [\#44](https://github.com/project-lovelace/lovelace-problems/pull/44) ([benallan](https://github.com/benallan)) 63 | - More robust test case and value checking [\#43](https://github.com/project-lovelace/lovelace-problems/pull/43) ([ali-ramadhan](https://github.com/ali-ramadhan)) 64 | - Solve test cases when they're created [\#39](https://github.com/project-lovelace/lovelace-problems/pull/39) ([ali-ramadhan](https://github.com/ali-ramadhan)) 65 | 66 | ## [v2.0](https://github.com/project-lovelace/lovelace-problems/tree/v2.0) (2019-07-22) 67 | 68 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/v1.1...v2.0) 69 | 70 | **Fixed bugs:** 71 | 72 | - Bug in Note G solution [\#30](https://github.com/project-lovelace/lovelace-problems/issues/30) 73 | - Caesar cipher should not use a shift of zero [\#12](https://github.com/project-lovelace/lovelace-problems/issues/12) 74 | 75 | **Closed issues:** 76 | 77 | - Shave down the extreme test cases in game of life and the Note G problems [\#29](https://github.com/project-lovelace/lovelace-problems/issues/29) 78 | - Rename test case class names for each problem module [\#20](https://github.com/project-lovelace/lovelace-problems/issues/20) 79 | - Generic verify\_user\_solution function [\#17](https://github.com/project-lovelace/lovelace-problems/issues/17) 80 | - Store more physical constants in the PHYSICAL\_CONSTANTS dictionary [\#16](https://github.com/project-lovelace/lovelace-problems/issues/16) 81 | - Engine should use the solutions repo [\#21](https://github.com/project-lovelace/lovelace-problems/issues/21) 82 | - Recalibrate difficulty scale on the problems [\#9](https://github.com/project-lovelace/lovelace-problems/issues/9) 83 | - Move solutions to a private lovelace-solutions repo [\#8](https://github.com/project-lovelace/lovelace-problems/issues/8) 84 | - Warmup problem modules [\#6](https://github.com/project-lovelace/lovelace-problems/issues/6) 85 | 86 | **Merged pull requests:** 87 | 88 | - More test cases for warmup problems [\#34](https://github.com/project-lovelace/lovelace-problems/pull/34) ([ali-ramadhan](https://github.com/ali-ramadhan)) 89 | - No huge cases [\#33](https://github.com/project-lovelace/lovelace-problems/pull/33) ([ali-ramadhan](https://github.com/ali-ramadhan)) 90 | - Revise test cases [\#31](https://github.com/project-lovelace/lovelace-problems/pull/31) ([ali-ramadhan](https://github.com/ali-ramadhan)) 91 | - More cleanup and better documentation/references for physical constants [\#28](https://github.com/project-lovelace/lovelace-problems/pull/28) ([ali-ramadhan](https://github.com/ali-ramadhan)) 92 | - Remove template as there are plenty of examples [\#27](https://github.com/project-lovelace/lovelace-problems/pull/27) ([ali-ramadhan](https://github.com/ali-ramadhan)) 93 | - Generic and more sophisticated test case verification [\#25](https://github.com/project-lovelace/lovelace-problems/pull/25) ([ali-ramadhan](https://github.com/ali-ramadhan)) 94 | - Cleanup [\#24](https://github.com/project-lovelace/lovelace-problems/pull/24) ([ali-ramadhan](https://github.com/ali-ramadhan)) 95 | - Less dumb class names [\#23](https://github.com/project-lovelace/lovelace-problems/pull/23) ([ali-ramadhan](https://github.com/ali-ramadhan)) 96 | - No more publicly visible solutions [\#22](https://github.com/project-lovelace/lovelace-problems/pull/22) ([ali-ramadhan](https://github.com/ali-ramadhan)) 97 | - Nuke solutions [\#19](https://github.com/project-lovelace/lovelace-problems/pull/19) ([ali-ramadhan](https://github.com/ali-ramadhan)) 98 | - Small improvements to warmup problem modules [\#15](https://github.com/project-lovelace/lovelace-problems/pull/15) ([ali-ramadhan](https://github.com/ali-ramadhan)) 99 | - Fix warmup problems [\#14](https://github.com/project-lovelace/lovelace-problems/pull/14) ([ali-ramadhan](https://github.com/ali-ramadhan)) 100 | - Warmup problems [\#5](https://github.com/project-lovelace/lovelace-problems/pull/5) ([ali-ramadhan](https://github.com/ali-ramadhan)) 101 | 102 | ## [v1.1](https://github.com/project-lovelace/lovelace-problems/tree/v1.1) (2019-01-05) 103 | 104 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/v1.0...v1.1) 105 | 106 | ## [v1.0](https://github.com/project-lovelace/lovelace-problems/tree/v1.0) (2018-11-11) 107 | 108 | [Full Changelog](https://github.com/project-lovelace/lovelace-problems/compare/4ba7fefc64d9cbec91878a41ae5739bff8ba56ab...v1.0) 109 | 110 | **Closed issues:** 111 | 112 | - As a developer, I want a consistent and scalable way of describing the expected input/outputs for a TestCase object in each problem module. [\#1](https://github.com/project-lovelace/lovelace-problems/issues/1) 113 | 114 | **Merged pull requests:** 115 | 116 | - Alpha2018 [\#4](https://github.com/project-lovelace/lovelace-problems/pull/4) ([ali-ramadhan](https://github.com/ali-ramadhan)) 117 | - Updating master/production with Basim's work. [\#3](https://github.com/project-lovelace/lovelace-problems/pull/3) ([ali-ramadhan](https://github.com/ali-ramadhan)) 118 | - First d3 test works. [\#2](https://github.com/project-lovelace/lovelace-problems/pull/2) ([ali-ramadhan](https://github.com/ali-ramadhan)) 119 | 120 | 121 | 122 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 123 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Basim Ramadhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Lovelace problem modules 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | [![Tests](https://github.com/project-lovelace/lovelace-problems/actions/workflows/ci.yml/badge.svg)](https://github.com/project-lovelace/lovelace-problems/actions/workflows/ci.yml) 7 | 8 | This repository contains modules for generating test cases (inputs and outputs) for 9 | [Project Lovelace](http://projectlovelace.net) problems. 10 | 11 | Note that it relies on a private solutions repository to generate solutions. 12 | The solutions are kept private to avoid providing copy-pasteable solutions to every problem. 13 | 14 | ## New problem submission guide 15 | 16 | If you have an idea for a new problem please consider submitting a new problem, we love receiving new contributions! 17 | We discuss new problems on Discord mostly but you can also open a GitHub issue or post on Discourse to discuss. Also feel free to ask us any questions at all! 18 | Let us know if you're interested in submitting new problems so we can invite you to the [Project Lovelace GitHub organization](https://github.com/project-lovelace). 19 | 20 | There are three steps to submitting a new problem: 21 | 22 | 1. Open a pull request to [lovelace-solutions](https://github.com/project-lovelace/lovelace-solutions#new-problem-submission-guide) with the solution to the problem. 23 | 2. Open a pull request to lovelace-problems with code to generate test cases for the problem. (You are here!) 24 | 3. Open a pull request to [lovelace-website](https://github.com/project-lovelace/lovelace-website#new-problem-submission-guide) with the problem description, code stubs, and any visualization code. 25 | 26 | ### How to submit new problem test cases 27 | 28 | 1. Add a new problem module under the [`problems/`](https://github.com/project-lovelace/lovelace-problems/tree/main/problems) directory. Structure it similarly to the other problem modules. You can generate as many test cases as you want although it's good to cover all the interesting cases. 29 | 2. Open a pull request! Once all the tests pass it can be merged. 30 | 31 | ## Contributors ✨ 32 | 33 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |

Ali Ramadhan

🖋 ⚠️ 💻

br

🖋 💻
44 | 45 | 46 | 47 | 48 | 49 | 50 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /inject_solutions.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gh repo clone project-lovelace/lovelace-solutions 4 | ln -s ../lovelace-solutions/python problems/solutions 5 | -------------------------------------------------------------------------------- /problems/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /problems/ada_lovelaces_note_g.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import randint 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.ada_lovelaces_note_g import bernoulli 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "bernoulli" 12 | INPUT_VARS = ['n'] 13 | OUTPUT_VARS = ['numerator', 'denominator'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = {} 18 | ATOL = {} 19 | RTOL = {} 20 | 21 | 22 | class TestCaseType(TestCaseTypeEnum): 23 | ZEROTH = ("Zeroth Bernoulli number", 1) 24 | FIRST = ("First Bernoulli number", 1) 25 | SECOND = ("Second Bernoulli number", 1) 26 | THIRD = ("Third Bernoulli number", 1) 27 | RANDOM_EVEN = ("Even 2 < n < 50", 2) 28 | RANDOM_ODD = ("Odd 3 < n < 51", 2) 29 | LARGE_EVEN = ("Even 50 < n < 125", 1) 30 | LARGE_ODD = ("Odd 51 < n < 126", 1) 31 | 32 | 33 | class ProblemTestCase(TestCase): 34 | def input_tuple(self) -> tuple: 35 | return self.input['n'], 36 | 37 | def output_tuple(self) -> tuple: 38 | numerator = self.output['numerator'] 39 | denominator = self.output['denominator'] 40 | return numerator, denominator 41 | 42 | 43 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 44 | test_case = ProblemTestCase(test_type) 45 | 46 | if test_type is TestCaseType.ZEROTH: 47 | n = 0 48 | 49 | elif test_type is TestCaseType.FIRST: 50 | n = 1 51 | 52 | elif test_type is TestCaseType.SECOND: 53 | n = 2 54 | 55 | elif test_type is TestCaseType.THIRD: 56 | n = 3 57 | 58 | elif test_type is TestCaseType.RANDOM_EVEN: 59 | n = 2 * randint(2, 50) 60 | 61 | elif test_type is TestCaseType.RANDOM_ODD: 62 | n = 2 * randint(2, 50) + 1 63 | 64 | elif test_type is TestCaseType.LARGE_EVEN: 65 | n = 2 * randint(50, 125) 66 | 67 | elif test_type is TestCaseType.LARGE_ODD: 68 | n = 2 * randint(50, 125) + 1 69 | 70 | else: 71 | raise ValueError(f"Unrecognized test case: {test_type}") 72 | 73 | test_case.input['n'] = n 74 | test_case.output['numerator'], test_case.output['denominator'] = bernoulli(n) 75 | 76 | return test_case 77 | -------------------------------------------------------------------------------- /problems/almost_pi.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import randint 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.almost_pi import almost_pi 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "almost_pi" 12 | INPUT_VARS = ['N'] 13 | OUTPUT_VARS = ['pi'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = {} 18 | ATOL = {} 19 | RTOL = { 20 | 'pi': 1e-5 21 | } 22 | 23 | 24 | class TestCaseType(TestCaseTypeEnum): 25 | SMALL_N = ("1 < n < 10", 2) 26 | MEDIUM_N = ("10 < n < 100", 2) 27 | LARGE_N = ("100 < n < 1,000", 1) 28 | HUGE_N = ("10,000 < n < 100,000", 1) 29 | 30 | 31 | class ProblemTestCase(TestCase): 32 | def input_tuple(self) -> tuple: 33 | return self.input['N'], 34 | 35 | def output_tuple(self) -> tuple: 36 | return self.output['pi'], 37 | 38 | 39 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 40 | test_case = ProblemTestCase(test_type) 41 | 42 | if test_type is TestCaseType.SMALL_N: 43 | N = randint(2, 10) 44 | 45 | elif test_type is TestCaseType.MEDIUM_N: 46 | N = randint(11, 100) 47 | 48 | elif test_type is TestCaseType.LARGE_N: 49 | N = randint(101, 1000) 50 | 51 | elif test_type is TestCaseType.HUGE_N: 52 | N = randint(10000, 100000) 53 | 54 | else: 55 | raise ValueError(f"Unrecognized test case: {test_type}") 56 | 57 | test_case.input['N'] = N 58 | test_case.output['pi'] = almost_pi(N) 59 | 60 | return test_case 61 | -------------------------------------------------------------------------------- /problems/babylonian_spiral.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from numpy.random import randint 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.babylonian_spiral import babylonian_spiral 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "babylonian_spiral" 11 | INPUT_VARS = ["n_steps"] 12 | OUTPUT_VARS = ["x", "y"] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = {} 19 | 20 | 21 | class TestCaseType(TestCaseTypeEnum): 22 | FEW_STEPS = ("few steps", 3) 23 | MANY_STEPS = ("many steps", 2) 24 | 25 | 26 | class ProblemTestCase(TestCase): 27 | def input_tuple(self): 28 | return self.input["n_steps"], 29 | 30 | def output_tuple(self) -> tuple: 31 | return (self.output["x"], self.output["y"]) 32 | 33 | 34 | def generate_test_case(test_type): 35 | test_case = ProblemTestCase(test_type) 36 | 37 | if test_type is TestCaseType.FEW_STEPS: 38 | n = randint(0, 20) 39 | 40 | elif test_type is TestCaseType.MANY_STEPS: 41 | n = randint(20, 1000) 42 | 43 | else: 44 | raise ValueError(f"Unrecognized test case: {test_type}") 45 | 46 | x, y = babylonian_spiral(n) 47 | 48 | test_case.input["n_steps"] = n 49 | test_case.output = {"x": x, "y": y} 50 | 51 | return test_case 52 | -------------------------------------------------------------------------------- /problems/babylonian_square_roots.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from math import isclose 3 | from typing import Tuple 4 | 5 | from numpy.random import uniform, randint 6 | 7 | from problems.test_case import TestCase, TestCaseTypeEnum 8 | from problems.solutions.babylonian_square_roots import babylonian_sqrt 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | FUNCTION_NAME = "babylonian_sqrt" 13 | INPUT_VARS = ['n'] 14 | OUTPUT_VARS = ['sqrt_n'] 15 | 16 | STATIC_RESOURCES = [] 17 | 18 | PHYSICAL_CONSTANTS = {} 19 | ATOL = {} 20 | RTOL = { 21 | 'sqrt_n': 1e-10 22 | } 23 | 24 | 25 | class TestCaseType(TestCaseTypeEnum): 26 | ZERO = ("zero", 1) 27 | SMALL = ("1 < n < 10", 2) 28 | LARGE = ("10 < n < 1,000,000", 2) 29 | SQUARE = ("square number", 1) 30 | 31 | 32 | class ProblemTestCase(TestCase): 33 | def input_tuple(self) -> tuple: 34 | return self.input['n'], 35 | 36 | def output_tuple(self) -> tuple: 37 | return self.output['sqrt_n'], 38 | 39 | 40 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 41 | test_case = ProblemTestCase(test_type) 42 | 43 | if test_type is TestCaseType.ZERO: 44 | n = 0 45 | 46 | elif test_type is TestCaseType.SMALL: 47 | n = uniform(1, 10) 48 | 49 | elif test_type is TestCaseType.LARGE: 50 | n = uniform(10, 1000000) 51 | 52 | elif test_type is TestCaseType.SQUARE: 53 | n = randint(5, 100)**2 54 | 55 | else: 56 | raise ValueError(f"Unrecognized test case: {test_type}") 57 | 58 | test_case.input['n'] = float(n) 59 | test_case.output['sqrt_n'] = float(babylonian_sqrt(n)) 60 | 61 | return test_case 62 | -------------------------------------------------------------------------------- /problems/blood_types.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from random import choice, choices, randint 3 | from typing import Tuple 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.blood_types import survive 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "survive" 11 | INPUT_VARS = ["patient_blood_type", "donated_blood"] 12 | OUTPUT_VARS = ["survive"] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {"blood_types": ["A-", "B-", "AB-", "O-", "A+", "B+", "AB+", "O+"]} 17 | 18 | ATOL = {} 19 | RTOL = {} 20 | 21 | 22 | class TestCaseType(TestCaseTypeEnum): 23 | NO_DONATIONS = ("No donations have been made.", 0) 24 | LUCKY_PATIENT = ("Lucky patient (O- is available)", 1) 25 | LUCKY_ABP_PATIENT = ("Lucky AB+ patient", 1) 26 | SLIM_PICKINGS = ("Slim pickings (few donations)", 2) 27 | WELL_STOCKED = ("Well stocked hospital (many donations)", 2) 28 | UNLUCKY_ONEG_PATIENT = ("Unlucky O- patient (No suitable donors available)", 1) 29 | UNLUCKY_OPOS_PATIENT = ("Unlucky O+ patient (No suitable donors available)", 1) 30 | UNLUCKY_ANEG_PATIENT = ("Unlucky A- patient (No suitable donors available)", 1) 31 | UNLUCKY_APOS_PATIENT = ("Unlucky A+ patient (No suitable donors available)", 1) 32 | UNLUCKY_BNEG_PATIENT = ("Unlucky B- patient (No suitable donors available)", 1) 33 | UNLUCKY_BPOS_PATIENT = ("Unlucky B+ patient (No suitable donors available)", 1) 34 | UNLUCKY_ABNEG_PATIENT = ("Unlucky AB- patient (No suitable donors available)", 1) 35 | UNLUCKY_ABPOS_PATIENT = ("Unlucky AB+ patient (No suitable donors available)", 1) 36 | 37 | 38 | class ProblemTestCase(TestCase): 39 | def input_tuple(self) -> tuple: 40 | return self.input["patient_blood_type"], self.input["donated_blood"] 41 | 42 | def output_tuple(self) -> tuple: 43 | return (self.output["survive"],) 44 | 45 | 46 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 47 | test_case = ProblemTestCase(test_type) 48 | blood_types = PHYSICAL_CONSTANTS["blood_types"] 49 | 50 | if test_type is TestCaseType.NO_DONATIONS: 51 | patient_blood_type = choice(blood_types) 52 | donated_blood = [] 53 | 54 | elif test_type is TestCaseType.LUCKY_PATIENT: 55 | patient_blood_type = choice(blood_types) 56 | donated_blood = ["O-", "O-", "O-"] 57 | 58 | elif test_type is TestCaseType.LUCKY_ABP_PATIENT: 59 | patient_blood_type = "AB+" 60 | donated_blood = [choice(blood_types)] 61 | 62 | elif test_type is TestCaseType.SLIM_PICKINGS: 63 | patient_blood_type = choice(blood_types) 64 | donated_blood = choices(blood_types, k=randint(2, 3)) 65 | 66 | elif test_type is TestCaseType.WELL_STOCKED: 67 | patient_blood_type = choice(blood_types) 68 | donated_blood = choices(blood_types, k=randint(8, 25)) 69 | 70 | elif test_type is TestCaseType.UNLUCKY_ONEG_PATIENT: 71 | patient_blood_type = "O-" 72 | donated_blood = ["A-", "B-", "AB-", "A+", "B+", "AB+", "O+"] 73 | 74 | elif test_type is TestCaseType.UNLUCKY_OPOS_PATIENT: 75 | patient_blood_type = "O+" 76 | donated_blood = ["A-", "B-", "AB-", "A+", "B+", "AB+"] 77 | 78 | elif test_type is TestCaseType.UNLUCKY_ANEG_PATIENT: 79 | patient_blood_type = "A-" 80 | donated_blood = ["B-", "AB-", "A+", "B+", "AB+", "O+"] 81 | 82 | elif test_type is TestCaseType.UNLUCKY_APOS_PATIENT: 83 | patient_blood_type = "A+" 84 | donated_blood = ["B-", "AB-", "B+", "AB+"] 85 | 86 | elif test_type is TestCaseType.UNLUCKY_BNEG_PATIENT: 87 | patient_blood_type = "B-" 88 | donated_blood = ["A-", "AB-", "A+", "B+", "AB+", "O+"] 89 | 90 | elif test_type is TestCaseType.UNLUCKY_BPOS_PATIENT: 91 | patient_blood_type = "B+" 92 | donated_blood = ["A-", "AB-", "A+", "AB+"] 93 | 94 | elif test_type is TestCaseType.UNLUCKY_ABNEG_PATIENT: 95 | patient_blood_type = "AB-" 96 | donated_blood = ["A+", "B+", "AB+", "O+"] 97 | 98 | elif test_type is TestCaseType.UNLUCKY_ABPOS_PATIENT: 99 | patient_blood_type = "AB+" 100 | donated_blood = [] 101 | 102 | else: 103 | raise ValueError(f"Unrecognized test case: {test_type}") 104 | 105 | test_case.input = {"patient_blood_type": patient_blood_type, "donated_blood": donated_blood} 106 | 107 | test_case.output["survive"] = survive(patient_blood_type, donated_blood) 108 | 109 | return test_case 110 | -------------------------------------------------------------------------------- /problems/caesar_cipher.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import logging 4 | from typing import Tuple 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.caesar_cipher import break_caesar_cipher 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "break_caesar_cipher" 12 | INPUT_VARS = ['ciphertext', 'known_word'] 13 | OUTPUT_VARS = ['decrypted_message'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = {} 18 | ATOL = {} 19 | RTOL = {} 20 | 21 | 22 | class TestCaseType(TestCaseTypeEnum): 23 | MOBY_DICK = ('Moby dick', 1) 24 | THE_WIRE = ('Bunny Colvin (The Wire)', 1) 25 | RANDOM_STRING = ('random string', 1) 26 | 27 | 28 | class ProblemTestCase(TestCase): 29 | def input_tuple(self) -> tuple: 30 | return self.input['ciphertext'], self.input['known_word'] 31 | 32 | def output_tuple(self) -> tuple: 33 | return self.output['decrypted_message'], 34 | 35 | 36 | def generate_random_string(length): 37 | return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) 38 | 39 | 40 | def randomly_insert_spaces(s): 41 | length = len(s) 42 | n = random.randint(int(length/10), int(length/4)) 43 | for _ in range(n): 44 | pos = random.randint(0, length-1) 45 | s = s[:pos] + " " + s[pos:] 46 | return s 47 | 48 | 49 | def caesar_cipher(plaintext, shift): 50 | alphabet = string.ascii_lowercase 51 | shifted_alphabet = alphabet[shift:] + alphabet[:shift] 52 | table = str.maketrans(alphabet, shifted_alphabet) 53 | return plaintext.translate(table) 54 | 55 | 56 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 57 | test_case = ProblemTestCase(test_type) 58 | 59 | if test_type is TestCaseType.RANDOM_STRING: 60 | length = random.randint(50, 400) 61 | plaintext = randomly_insert_spaces(generate_random_string(length)) 62 | 63 | elif test_type is TestCaseType.MOBY_DICK: 64 | plaintext = "Call me Ishmael Some years ago never mind how long precisely having little or no money in my purse and nothing particular to interest me on shore I thought I would sail about a little and see the watery part of the world".lower() 65 | 66 | elif test_type is TestCaseType.THE_WIRE: 67 | plaintext = "This drug thing this aint police work I mean I can send any fool with a badge and a gun to a corner to jack a crew and grab vials But policing I mean you call something a war and pretty soon everyone is going to be running around acting like warriors They gonna be running around on a damn crusade storming corners racking up body counts And when you at war you need a fucking enemy And pretty soon damn near everybody on every corner is your fucking enemy And soon, the neighborhood youre supposed to be policing thats just occupied territory".lower() 68 | 69 | else: 70 | raise ValueError(f"Unrecognized test case: {test_type}") 71 | 72 | known_word = random.choice(plaintext.split()) 73 | 74 | shift = random.randint(1, 25) 75 | ciphertext = caesar_cipher(plaintext, shift).upper() 76 | 77 | test_case.input['ciphertext'] = ciphertext 78 | test_case.input['known_word'] = known_word 79 | test_case.output['decrypted_message'] = break_caesar_cipher(ciphertext, known_word) 80 | 81 | return test_case 82 | -------------------------------------------------------------------------------- /problems/chaos.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from problems.test_case import TestCase, TestCaseTypeEnum 5 | from problems.solutions.chaos import logistic_map 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | FUNCTION_NAME = "logistic_map" 10 | INPUT_VARS = ['r'] 11 | OUTPUT_VARS = ['x'] 12 | 13 | STATIC_RESOURCES = [] 14 | 15 | PHYSICAL_CONSTANTS = {} 16 | ATOL = {} 17 | RTOL = { 18 | 'x': 0.0001 19 | } 20 | 21 | 22 | class TestCaseType(TestCaseTypeEnum): 23 | DEATH = ('death', 1) 24 | QUICK_STABLE = ('quick stable', 1) 25 | FLUCTUATE_STABLE = ('fluctuate stable', 1) 26 | CHAOS = ('chaos', 1) 27 | DIVERGENCE = ('divergence', 1) 28 | 29 | 30 | class ProblemTestCase(TestCase): 31 | def input_tuple(self) -> tuple: 32 | return self.input['r'], 33 | 34 | def output_tuple(self) -> tuple: 35 | return self.output['x'], 36 | 37 | 38 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 39 | test_case = ProblemTestCase(test_type) 40 | 41 | if test_type is TestCaseType.DEATH: 42 | r = 1 43 | 44 | elif test_type is TestCaseType.QUICK_STABLE: 45 | r = 2 46 | 47 | elif test_type is TestCaseType.FLUCTUATE_STABLE: 48 | r = 3 49 | 50 | elif test_type is TestCaseType.CHAOS: 51 | r = 3.5 52 | 53 | elif test_type is TestCaseType.DIVERGENCE: 54 | r = 3.6 55 | 56 | else: 57 | raise ValueError(f"Unrecognized test case: {test_type}") 58 | 59 | test_case.input['r'] = float(r) 60 | test_case.output['x'] = logistic_map(r) 61 | 62 | return test_case 63 | -------------------------------------------------------------------------------- /problems/colorful_resistors.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | import numpy as np 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.colorful_resistors import resistance 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "resistance" 12 | STATIC_RESOURCES = [] 13 | 14 | INPUT_VARS = ['colors'] 15 | OUTPUT_VARS = ['nominal_resistance', 'minimum_resistance', 'maximum_resistance'] 16 | 17 | PHYSICAL_CONSTANTS = { 18 | 'digits': { 19 | 'black': 0, 20 | 'brown': 1, 21 | 'red': 2, 22 | 'orange': 3, 23 | 'yellow': 4, 24 | 'green': 5, 25 | 'blue': 6, 26 | 'violet': 7, 27 | 'grey': 8, 28 | 'white': 9 29 | }, 30 | 'multiplier': { 31 | 'pink': 0.001, 32 | 'silver': 0.01, 33 | 'gold': 0.1, 34 | 'black': 1, 35 | 'brown': 10, 36 | 'red': 100, 37 | 'orange': 10**3, 38 | 'yellow': 10**4, 39 | 'green': 10**5, 40 | 'blue': 10**6, 41 | 'violet': 10**7, 42 | 'grey': 10**8, 43 | 'white': 10**9 44 | }, 45 | 'tolerance': { 46 | 'none': 0.2, 47 | 'silver': 0.1, 48 | 'gold': 0.05, 49 | 'brown': 0.01, 50 | 'red': 0.02, 51 | 'green': 0.005, 52 | 'blue': 0.0025, 53 | 'violet': 0.001, 54 | 'grey': 0.0005 55 | } 56 | } 57 | 58 | ATOL = {} 59 | RTOL = { 60 | 'nominal_resistance': 1e-6, 61 | 'minimum_resistance': 1e-6, 62 | 'maximum_resistance': 1e-6 63 | } 64 | 65 | 66 | class TestCaseType(TestCaseTypeEnum): 67 | ZERO_RESISTOR = ('zero resistor', 1) 68 | FOUR_BAND = ('four band resistor', 3) 69 | FIVE_BAND = ('five band resistor', 3) 70 | 71 | 72 | class ProblemTestCase(TestCase): 73 | def input_tuple(self) -> tuple: 74 | return self.input['colors'], 75 | 76 | def output_tuple(self) -> tuple: 77 | nominal_R = self.output['nominal_resistance'] 78 | minimum_R = self.output['minimum_resistance'] 79 | maximum_R = self.output['maximum_resistance'] 80 | return nominal_R, minimum_R, maximum_R 81 | 82 | 83 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 84 | digits = PHYSICAL_CONSTANTS['digits'] 85 | multiplier = PHYSICAL_CONSTANTS['multiplier'] 86 | tolerance = PHYSICAL_CONSTANTS['tolerance'] 87 | 88 | test_case = ProblemTestCase(test_type) 89 | 90 | # We use the str() of each random choice as np.random.choice() 91 | # returns an np.str_ object and the test cases will fail if the user 92 | # hasn't imported numpy: we want a string, not an np.str_! 93 | if test_type is TestCaseType.ZERO_RESISTOR: 94 | colors = ['black'] 95 | 96 | elif test_type is TestCaseType.FOUR_BAND: 97 | band_color1 = str(np.random.choice(list(digits.keys()))) 98 | band_color2 = str(np.random.choice(list(digits.keys()))) 99 | multiplier_color = str(np.random.choice(list(multiplier.keys()))) 100 | tolerance_color = str(np.random.choice(list(tolerance.keys()))) 101 | colors = [band_color1, band_color2, multiplier_color, tolerance_color] 102 | 103 | elif test_type is TestCaseType.FIVE_BAND: 104 | band_color1 = str(np.random.choice(list(digits.keys()))) 105 | band_color2 = str(np.random.choice(list(digits.keys()))) 106 | band_color3 = str(np.random.choice(list(digits.keys()))) 107 | multiplier_color = str(np.random.choice(list(multiplier.keys()))) 108 | tolerance_color = str(np.random.choice(list(tolerance.keys()))) 109 | colors = [band_color1, band_color2, band_color3, multiplier_color, tolerance_color] 110 | 111 | else: 112 | raise ValueError(f"Unrecognized test case: {test_type}") 113 | 114 | test_case.input['colors'] = colors 115 | 116 | nominal_R, minimum_R, maximum_R = resistance(colors) 117 | test_case.output['nominal_resistance'] = nominal_R 118 | test_case.output['minimum_resistance'] = minimum_R 119 | test_case.output['maximum_resistance'] = maximum_R 120 | 121 | return test_case 122 | -------------------------------------------------------------------------------- /problems/compound_interest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from numpy.random import uniform, randint 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.compound_interest import compound_interest 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "compound_interest" 11 | INPUT_VARS = ["amount", "rate", "years"] 12 | OUTPUT_VARS = ["new_amount"] 13 | 14 | STATIC_RESOURCES = [] 15 | PHYSICAL_CONSTANTS = {} 16 | 17 | ATOL = {} 18 | RTOL = { 19 | "new_amount": 1e-6 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | NO_INTEREST = ("No interest", 1) 25 | SAVINGS = ("High-rate savings account", 1) 26 | SP_500 = ("S&P 500 average annual return", 1) 27 | CREDIT_CARD = ("Credit card debt", 1) 28 | RANDOM = ("Random", 2) 29 | 30 | 31 | class ProblemTestCase(TestCase): 32 | def input_tuple(self): 33 | return self.input["amount"], self.input["rate"], self.input["years"] 34 | 35 | def output_tuple(self): 36 | return self.output["new_amount"], 37 | 38 | 39 | def generate_test_case(test_type): 40 | test_case = ProblemTestCase(test_type) 41 | 42 | if test_type is TestCaseType.NO_INTEREST: 43 | amount = uniform(10, 1000) 44 | rate = 0.0 45 | years = randint(1, 10) 46 | 47 | elif test_type is TestCaseType.SAVINGS: 48 | amount = uniform(100, 25000) 49 | rate = 0.005 50 | years = randint(10, 25) 51 | 52 | elif test_type is TestCaseType.SP_500: 53 | amount = uniform(10000, 500000) 54 | rate = 0.1 55 | years = randint(7, 30) 56 | 57 | elif test_type is TestCaseType.CREDIT_CARD: 58 | amount = uniform(250, 10000) 59 | rate = 0.1 60 | years = randint(7, 30) 61 | 62 | elif test_type is TestCaseType.RANDOM: 63 | amount = uniform(0, 100000) 64 | rate = uniform(0, 0.25) 65 | years = randint(0, 30) 66 | 67 | else: 68 | raise ValueError(f"Unrecognized test case: {test_type}") 69 | 70 | test_case.input["amount"] = amount 71 | test_case.input["rate"] = rate 72 | test_case.input["years"] = years 73 | test_case.output["new_amount"] = compound_interest(amount, rate, years) 74 | 75 | return test_case 76 | -------------------------------------------------------------------------------- /problems/correlation_does_not_imply_causation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import logging 4 | from typing import Tuple 5 | 6 | from numpy import array 7 | from numpy.random import rand, randint 8 | 9 | from problems.test_case import TestCase, TestCaseTypeEnum 10 | from problems.solutions.correlation_does_not_imply_causation import correlation_coefficient 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | FUNCTION_NAME = "correlation_coefficient" 15 | INPUT_VARS = ['x', 'y'] 16 | OUTPUT_VARS = ['r'] 17 | 18 | STATIC_RESOURCES = ["spurious_xy.csv"] 19 | 20 | PHYSICAL_CONSTANTS = {} 21 | ATOL = { 22 | 'r': 0.0001 23 | } 24 | RTOL = {} 25 | 26 | 27 | class TestCaseType(TestCaseTypeEnum): 28 | SPURIOUS_DATASET = ('spurious dataset', 1) 29 | RANDOM_DATASET = ('random dataset', 1) 30 | UNKNOWN = ('unknown case', 0) 31 | 32 | 33 | class ProblemTestCase(TestCase): 34 | def input_tuple(self) -> tuple: 35 | return self.input['x'], self.input['y'] 36 | 37 | def output_tuple(self) -> tuple: 38 | return self.output['r'] 39 | 40 | 41 | def write_random_dataset_csv(x, y): 42 | cwd = os.path.dirname(os.path.abspath(__file__)) 43 | csv_filename = os.path.join(cwd, "..", "resources", "correlation_does_not_imply_causation", "random_xy.csv") 44 | with open(csv_filename, 'w') as outfile: 45 | xy_writer = csv.writer(outfile, delimiter=',') 46 | for i in range(len(x)): 47 | xy_writer.writerow((x[i], y[i])) 48 | 49 | 50 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 51 | test_case = ProblemTestCase(test_type) 52 | 53 | if test_type is TestCaseType.SPURIOUS_DATASET: 54 | csv_filepath = "../resources/correlation_does_not_imply_causation/spurious_xy.csv" 55 | x = [] 56 | y = [] 57 | 58 | with open(csv_filepath) as csvfile: 59 | xy_reader = csv.reader(csvfile, delimiter=',') 60 | for row in xy_reader: 61 | x.append(float(row[0])) 62 | y.append(float(row[1])) 63 | 64 | dataset_filename = "spurious_xy.csv" 65 | 66 | elif test_type is TestCaseType.RANDOM_DATASET: 67 | N = randint(10, 100) 68 | x = rand(N).tolist() 69 | y = rand(N).tolist() 70 | write_random_dataset_csv(x, y) 71 | dataset_filename = "random_xy.csv" 72 | test_case.input['DYNAMIC_RESOURCES'] = [dataset_filename] 73 | 74 | else: 75 | raise ValueError(f"Unrecognized test case: {test_type}") 76 | 77 | test_case.input['x'] = x 78 | test_case.input['y'] = y 79 | test_case.input['dataset_filename'] = dataset_filename 80 | test_case.output['r'] = correlation_coefficient(x, y) 81 | 82 | return test_case 83 | -------------------------------------------------------------------------------- /problems/definite_integrals.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | from numpy import pi, exp, sin, linspace, ndarray 4 | from numpy.random import randint, uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.definite_integrals import area_of_rectangles 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "area_of_rectangles" 12 | INPUT_VARS = ['rectangle_heights', 'rectangle_width'] 13 | OUTPUT_VARS = ['area'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = {} 18 | ATOL = { 19 | 'area': 1e-6 20 | } 21 | RTOL = {} 22 | 23 | 24 | class TestCaseType(TestCaseTypeEnum): 25 | ZERO = ("Zero function (rectangles with no height)", 1) 26 | CONSTANT = ("Constant function (all rectangles have the same height)", 1) 27 | LINEAR = ("Linear function", 1) 28 | QUADRATIC = ("Quadratic polynomial", 1) 29 | EXPONENTIAL = ("Exponential function", 1) 30 | SINE = ("Sine function", 1) 31 | RANDOM = ("Random function", 1) 32 | 33 | 34 | class ProblemTestCase(TestCase): 35 | def input_tuple(self) -> tuple: 36 | return self.input['rectangle_heights'], self.input['rectangle_width'] 37 | 38 | def output_tuple(self) -> tuple: 39 | return self.output['area'], 40 | 41 | 42 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 43 | test_case = ProblemTestCase(test_type) 44 | 45 | if test_type is TestCaseType.ZERO: 46 | N = randint(5, 10) 47 | f = [0.0 for _ in range(N)] 48 | dx = 1 49 | 50 | elif test_type is TestCaseType.CONSTANT: 51 | N = randint(5, 10) 52 | c = uniform(-5, 5) 53 | f = [c for _ in range(N)] 54 | dx = 1 55 | 56 | elif test_type is TestCaseType.LINEAR: 57 | N = randint(10, 20) 58 | x1, x2 = uniform(0, 2), uniform(3, 5) 59 | dx = (x2 - x1) / N 60 | 61 | a, b = uniform(-5, 5), uniform(-5, 5) 62 | f = [a*x + b for x in linspace(x1, x2, N)] 63 | 64 | elif test_type is TestCaseType.QUADRATIC: 65 | N = randint(15, 30) 66 | x1, x2 = uniform(-5, -1), uniform(2, 5) 67 | dx = (x2 - x1) / N 68 | 69 | a, b, c = uniform(-1, 1), uniform(-1, 1), uniform(-1, 1) 70 | f = [a*x**2 + b*x + c for x in linspace(x1, x2, N)] 71 | 72 | elif test_type is TestCaseType.EXPONENTIAL: 73 | N = randint(20, 40) 74 | x1, x2 = 0, uniform(3, 5) 75 | dx = (x2 - x1) / N 76 | f = [exp(x) for x in linspace(x1, x2, N)] 77 | 78 | elif test_type is TestCaseType.SINE: 79 | N = randint(50, 75) 80 | x1, x2 = 0, 2*pi 81 | dx = (x2 - x1) / N 82 | f = [sin(x) for x in linspace(x1, x2, N)] 83 | 84 | elif test_type is TestCaseType.RANDOM: 85 | N = randint(40, 60) 86 | x1, x2 = 0, 1 87 | dx = (x2 - x1) / N 88 | f = uniform(-1, 1, size=N).tolist() 89 | 90 | else: 91 | raise ValueError(f"Unrecognized test case: {test_type}") 92 | 93 | test_case.input = { 94 | "rectangle_heights": f, 95 | "rectangle_width": float(dx) 96 | } 97 | 98 | test_case.output['area'] = float(area_of_rectangles(f, dx)) 99 | 100 | return test_case 101 | -------------------------------------------------------------------------------- /problems/dna_transcription.py: -------------------------------------------------------------------------------- 1 | import random 2 | import logging 3 | from typing import Tuple 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.dna_transcription import rna 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "rna" 11 | INPUT_VARS = ['dna_str'] 12 | OUTPUT_VARS = ['rna_str'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = {} 19 | 20 | 21 | class TestCaseType(TestCaseTypeEnum): 22 | SHORT_TRNA = ('tRNA-SeC-TCA-2-1', 1) 23 | INSULIN = ('Insulin [Homo sapiens (human)]', 1) 24 | RANDOM = ('Randomly generated DNA sequence', 1) 25 | 26 | 27 | class ProblemTestCase(TestCase): 28 | def input_tuple(self) -> tuple: 29 | return self.input['dna_str'], 30 | 31 | def output_tuple(self) -> tuple: 32 | return self.output['rna_str'], 33 | 34 | 35 | def generate_dna_sequence(length): 36 | return "".join(random.choice("ATGC") for _ in range(length)) 37 | 38 | 39 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 40 | test_case = ProblemTestCase(test_type) 41 | 42 | if test_type == TestCaseType.RANDOM: 43 | dna_length = random.randint(5, 100) 44 | dna_str = generate_dna_sequence(dna_length) 45 | 46 | elif test_type == TestCaseType.SHORT_TRNA: 47 | dna_str = "CTCGGATGATCCTCAGTGGTCTGGGGTGCAGGCTTCAAACCTGTAGCTGTCTAGTGACAGAGTGGTTCAATTCCACCTTTGTAGG" 48 | 49 | elif test_type == TestCaseType.INSULIN: 50 | dna_str = "GAGAGCCACTGCATGCTGGGCCTGGCCGGCGTTGGCACCTGTGGGCACCCAGAGAGCGTGGAGAGAGCTGGGAGGGGCTCACAACAGTGCCGGGAAGTGGGGCTTGGCCCAGGGCCCCCAAGACACACAGACGGCACAGCAGGGCTGGTTCAAGGGCTTTATTCCATCTCTCTCGGTGCAGGAGGCGGCGGGTGTGGGGCTGCCTGCGGGCTGCGTCTAGTTGCAGTAGTTCTCCAGCTGGTAGAGGGAGCAGATGCTGGTACAGCATTGTTCCACAATGCCACGCTTCTGCAGGGACCCCTCCAGGGCCAAGGGCTGCAGGCTGCCTGCACCAGGGCCCCCGCCCAGCTCCACCTGCCCCACTGCCAGGACGTGCCGCGCAGAGCAGGTTCCGGAACAGCGGCGAGGCAGAGGGACACAGGAGGACACAGTCAGGGAGACACAGTGCCCGCCTGCCCGCCAGCCCTAGGTCGCACTCCCACCCATCTCCAGCCGGGCTGGACCCAGGTTAGAGGGAGGGTCACCCACACTGGGTGTGGACCTACAGGCCCCAACGCCCACATGTCCCACCTCCTTCCCCCGCCCCGGGGCAGCGTCACAGTGGGAGCCTGAACAGGTGATCCCAGTACTTCTCCCCAGGGCCTGTCCCCAGCATCTTCCCCATCTCCTGACTATGGAGCTGCCGTGAGGCCTGGCGACAGGGGTCTGGCCCACTCAGGCAGGCAGCCACGCCCTCCTCCGGGCGTGATGGGGTGTTCGCCCAGAGGCAGGCAGCGTGGGGCACCCTGTGACCCCAGGTCACCCAGGACTTTACTTAACAAAACACTTGAATCTGCGGTCATCAAATGAGGGTGGAGAAATGGGCTGCGGGGCATTTGTTTGAGGGGCGAGTGGAGGGAGGAGCGTGCCCACCCTCTGATGTATCTCGGGGCTGCCGAAGCCAACACCGTCCTCAGGCTGAGATTCTGACTGGGCCACAGGGAGCTGGTCACTTTTAGGACGTGACCAAGAGAACTTCTTTTTAAAAAAGTGCACCTGACCCCCTGCTGGGTGGCAGCCTCCTGCCCCCTTCTGCCCATGCTGGGTGGGAGCGCCAGGAGCAGGGGGTGGCTGGGGGCGGCCAGGGGCAGCAATGGGCAGTTGGCTCACCCTGCAGGTCCTCTGCCTCCCGGCGGGTCTTGGGTGTGTAGAAGAAGCCTCGTTCCCCGCACACTAGGTAGAGAGCTTCCACCAGGTGTGAGCCGCACAGGTGTTGGTTCACAAAGGCTGCGGCTGGGTCAGGTCCCCAGAGGGCCAGCAGCGCCAGCAGGGGCAGGAGGCGCATCCACAGGGCCATGGCAGAAGGACAGTGATCTGGGAGACAGGCAGGGCTGAGGCAGGCTGAAGGCCAGGTGCCCTGCCTTGGGGCCCCTGGGCTCACCCCCACATGCTTCACGAGCCCAGCCACGTCCTCCCTGCTGCAGAGCTGGGGCCTGGGGTCCAGCCACCCTGGAATCCTGAGCCCACCTGACGCAAAGGCCCTTGGAACAGACCTGCTTGATGGCCTCTTCTGATGCAGCCTGTCCTGGAGGGCTGAGGGCTGCTGGGCCCCCGCTGGCTTTATAGTCTCAGAGCCCATCTCCCCTACCTCTCAACCCCTGCCGCCTGGCCCATTAGGGCCTGGGGTGGGGGGGTCGGCAGATGGCTGGGGGCTGAGGCTGCAATTTCCGGACCATTT" 51 | 52 | else: 53 | raise ValueError(f"Unrecognized test case: {test_type}") 54 | 55 | test_case.input['dna_str'] = dna_str 56 | test_case.output['rna_str'] = rna(dna_str) 57 | 58 | return test_case 59 | -------------------------------------------------------------------------------- /problems/earthquake_epicenters.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy import array, pi, sin, cos 5 | from numpy.linalg import norm 6 | from numpy.random import uniform, choice 7 | 8 | from problems.test_case import TestCase, TestCaseTypeEnum 9 | from problems.solutions.earthquake_epicenters import earthquake_epicenter 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | FUNCTION_NAME = "earthquake_epicenter" 14 | INPUT_VARS = ['x1', 'y1', 't1', 'x2', 'y2', 't2', 'x3', 'y3', 't3'] 15 | OUTPUT_VARS = ['x', 'y'] 16 | 17 | STATIC_RESOURCES = [] 18 | 19 | PHYSICAL_CONSTANTS = { 20 | 'v': 6.0 # velocity of seismic waves [km/s] 21 | } 22 | 23 | ATOL = { 24 | 'x': 0.0001, # [km] 25 | 'y': 0.0001 # [km] 26 | } 27 | RTOL = {} 28 | 29 | 30 | class TestCaseType(TestCaseTypeEnum): 31 | GENERAL = ("General case", 3) 32 | ZERO_CASE = ("Zero case", 1) 33 | EQUIDISTANT = ("Equidistant case", 1) 34 | 35 | 36 | class ProblemTestCase(TestCase): 37 | def input_tuple(self) -> tuple: 38 | return (self.input['x1'], self.input['y1'], self.input['t1'], 39 | self.input['x2'], self.input['y2'], self.input['t2'], 40 | self.input['x3'], self.input['y3'], self.input['t3']) 41 | 42 | def output_tuple(self) -> tuple: 43 | return self.output['x'], self.output['y'] 44 | 45 | 46 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 47 | test_case = ProblemTestCase(test_type) 48 | 49 | if test_type is TestCaseType.GENERAL: 50 | r0 = uniform(-100, 100, 2) 51 | r1 = uniform(-100, 100, 2) 52 | r2 = uniform(-100, 100, 2) 53 | r3 = uniform(-100, 100, 2) 54 | 55 | elif test_type is TestCaseType.ZERO_CASE: 56 | r0 = uniform(-100, 100, 2) 57 | zero_station = choice([1, 2, 3]) 58 | if zero_station == 1: 59 | r1 = r0 60 | r2 = uniform(-100, 100, 2) 61 | r3 = uniform(-100, 100, 2) 62 | elif zero_station == 2: 63 | r1 = uniform(-100, 100, 2) 64 | r2 = r0 65 | r3 = uniform(-100, 100, 2) 66 | else: 67 | r1 = uniform(-100, 100, 2) 68 | r2 = uniform(-100, 100, 2) 69 | r3 = r0 70 | 71 | elif test_type is TestCaseType.EQUIDISTANT: 72 | r0 = uniform(-10, 10, 2) # Place the earthquake near the origin. 73 | d = uniform(10, 90) # Distance to all the stations. Max=90 ensures we stay inside the box. 74 | theta = uniform(0, 2*pi, 3) # Choose three angles to place the stations at a distance d away. 75 | 76 | r1 = r0 + d * array([cos(theta[0]), sin(theta[0])]) 77 | r2 = r0 + d * array([cos(theta[1]), sin(theta[1])]) 78 | r3 = r0 + d * array([cos(theta[2]), sin(theta[2])]) 79 | 80 | else: 81 | raise ValueError(f"Unrecognized test case: {test_type}") 82 | 83 | v = PHYSICAL_CONSTANTS['v'] 84 | 85 | t1 = norm(r1-r0) / v 86 | t2 = norm(r2-r0) / v 87 | t3 = norm(r3-r0) / v 88 | 89 | # Convert to float so the user gets Python floats and not numpy floats. 90 | test_case.input = { 91 | 'x1': float(r1[0]), 92 | 'y1': float(r1[1]), 93 | 't1': float(t1), 94 | 'x2': float(r2[0]), 95 | 'y2': float(r2[1]), 96 | 't2': float(t2), 97 | 'x3': float(r3[0]), 98 | 'y3': float(r3[1]), 99 | 't3': float(t3) 100 | } 101 | 102 | # We already know the solution by constructions. 103 | test_case.output['x'] = r0[0] 104 | test_case.output['y'] = r0[1] 105 | 106 | return test_case 107 | -------------------------------------------------------------------------------- /problems/el_nino_intensities.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import randint 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "enso_classification" 11 | INPUT_VARS = ['year'] 12 | OUTPUT_VARS = ['enso_classification', 'enso_intensity'] 13 | 14 | STATIC_RESOURCES = ["mei.ext_index.txt"] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = {} 19 | 20 | 21 | class TestCaseType(TestCaseTypeEnum): 22 | QUIET_YEAR = ("Quiet year", 1) 23 | WEAK_EL_NINO = ("Weak El Niño", 1) 24 | WEAK_LA_NINA = ("Weak La Niña", 1) 25 | MODERATE_EL_NINO = ("Moderate El Niño", 1) 26 | MODERATE_LA_NINA = ("Moderate La Niña", 1) 27 | STRONG_EL_NINO = ("Strong El Niño", 1) 28 | STRONG_LA_NINA = ("Strong La Niña", 1) 29 | VERY_STRONG_EL_NINO = ("Very strong El Niño", 1) 30 | VERY_STRONG_LA_NINA = ("Very strong La Niña", 1) 31 | RANDOM_YEAR = ("Random year", 5) 32 | 33 | 34 | class ProblemTestCase(TestCase): 35 | def input_tuple(self) -> tuple: 36 | return self.input["year"], 37 | 38 | def output_tuple(self) -> tuple: 39 | return self.output['enso_classification'], self.output['enso_intensity'] 40 | 41 | 42 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 43 | test_case = ProblemTestCase(test_type) 44 | 45 | if test_type is TestCaseType.QUIET_YEAR: 46 | year = 1996 47 | 48 | elif test_type is TestCaseType.WEAK_EL_NINO: 49 | year = 1948 50 | 51 | elif test_type is TestCaseType.WEAK_LA_NINA: 52 | year = 1871 53 | 54 | elif test_type is TestCaseType.MODERATE_EL_NINO: 55 | year = 1914 56 | 57 | elif test_type is TestCaseType.MODERATE_LA_NINA: 58 | year = 2000 59 | 60 | elif test_type is TestCaseType.STRONG_EL_NINO: 61 | year = 1993 62 | 63 | elif test_type is TestCaseType.STRONG_LA_NINA: 64 | year = 1890 65 | 66 | elif test_type is TestCaseType.VERY_STRONG_EL_NINO: 67 | year = 2016 68 | 69 | elif test_type is TestCaseType.VERY_STRONG_LA_NINA: 70 | year = 1955 71 | 72 | elif test_type is TestCaseType.RANDOM_YEAR: 73 | year = randint(1871, 2016) 74 | 75 | else: 76 | raise ValueError(f"Unrecognized test case: {test_type}") 77 | 78 | test_case.input['year'] = year 79 | 80 | # Delay solution import until tests can handle static resources: 81 | # https://github.com/project-lovelace/lovelace-problems/issues/60 82 | from problems.solutions.el_nino_intensities import enso_classification 83 | classification, intensity = enso_classification(year) 84 | 85 | test_case.output['enso_classification'] = classification 86 | test_case.output['enso_intensity'] = intensity 87 | 88 | return test_case 89 | -------------------------------------------------------------------------------- /problems/exponential_growth.py: -------------------------------------------------------------------------------- 1 | import random 2 | import logging 3 | 4 | from problems.test_case import TestCase, TestCaseTypeEnum 5 | from problems.solutions.exponential_growth import exponential_growth 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | FUNCTION_NAME = "exponential_growth" 10 | 11 | INPUT_VARS = ["x0", "k", "dt", "N"] 12 | OUTPUT_VARS = ["x"] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = { 19 | "x": 1e-8 20 | } 21 | 22 | class ProblemTestCase(TestCase): 23 | def input_tuple(self): 24 | return (self.input["x0"], self.input["k"], self.input["dt"], self.input["N"]) 25 | 26 | def output_tuple(self): 27 | return self.output["x"], 28 | 29 | class TestCaseType(TestCaseTypeEnum): 30 | NO_GROWTH_OR_DECAY = ("No growth or decay", 1) 31 | SLOW_GROWTH = ("Slow growth (small k)", 1) 32 | FAST_GROWTH = ("Fast growth (large k)", 1) 33 | DECAY = ("Exponential decay (k < 0)", 1) 34 | SMALL_TIME_STEPS = ("Small time steps (more accurate solution)", 1) 35 | LARGE_TIME_STEPS = ("Large time steps (less accurate solution)", 1) 36 | LONG_INTEGRATION = ("Long integration time", 1) 37 | NUMERICAL_NOISE = ("Numerical noise", 1) 38 | NUMERICAL_OSCILLATIONS = ("Numerical oscillations", 1) 39 | NUMERICAL_INSTABILITY = ("Numerical instability", 1) 40 | 41 | def generate_test_case(test_type): 42 | test_case = ProblemTestCase(test_type) 43 | 44 | if test_type is TestCaseType.NO_GROWTH_OR_DECAY: 45 | x0 = random.uniform(-10, 10) 46 | k = 0 47 | dt = random.uniform(0.1, 1) 48 | N = random.randint(10, 15) 49 | 50 | elif test_type is TestCaseType.SLOW_GROWTH: 51 | x0 = random.uniform(-10, 10) 52 | k = random.uniform(0.01, 0.1) 53 | dt = random.uniform(0.1, 0.2) 54 | N = random.randint(20, 50) 55 | 56 | elif test_type is TestCaseType.FAST_GROWTH: 57 | x0 = random.uniform(-10, 10) 58 | k = random.uniform(4, 6) 59 | dt = random.uniform(0.1, 0.2) 60 | N = random.randint(20, 50) 61 | 62 | elif test_type is TestCaseType.DECAY: 63 | x0 = random.uniform(-10, 10) 64 | k = - random.uniform(1, 2) 65 | dt = random.uniform(0.1, 0.2) 66 | N = random.randint(20, 50) 67 | 68 | elif test_type is TestCaseType.SMALL_TIME_STEPS: 69 | x0 = random.uniform(1, 10) 70 | k = random.uniform(1, 1.2) 71 | dt = random.uniform(0.01, 0.02) 72 | N = random.randint(10, 15) 73 | 74 | elif test_type is TestCaseType.LARGE_TIME_STEPS: 75 | x0 = random.uniform(1, 10) 76 | k = random.uniform(1, 1.2) 77 | dt = random.uniform(0.4, 0.5) 78 | N = random.randint(10, 15) 79 | 80 | elif test_type is TestCaseType.LONG_INTEGRATION: 81 | x0 = random.uniform(-10, 10) 82 | k = random.uniform(0.2, 0.5) 83 | dt = random.uniform(0.2, 0.3) 84 | N = random.randint(200, 300) 85 | 86 | elif test_type is TestCaseType.NUMERICAL_NOISE: 87 | x0 = random.uniform(1, 10) 88 | k = -2.5 89 | dt = 0.7 90 | N = random.randint(20, 30) 91 | 92 | elif test_type is TestCaseType.NUMERICAL_OSCILLATIONS: 93 | x0 = random.uniform(1, 10) 94 | k = -2.5 95 | dt = 0.8 96 | N = random.randint(20, 30) 97 | 98 | elif test_type is TestCaseType.NUMERICAL_INSTABILITY: 99 | x0 = random.uniform(1, 10) 100 | k = -2.5 101 | dt = 0.9 102 | N = random.randint(30, 40) 103 | 104 | else: 105 | raise ValueError(f"Unrecognized test case: {test_type}") 106 | 107 | test_case.input["x0"] = x0 108 | test_case.input["k"] = k 109 | test_case.input["dt"] = dt 110 | test_case.input["N"] = N 111 | test_case.output["x"] = exponential_growth(x0, k, dt, N) 112 | 113 | return test_case 114 | -------------------------------------------------------------------------------- /problems/flight_paths.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.flight_paths import haversine_distance 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "haversine_distance" 12 | INPUT_VARS = ['lat1', 'lon1', 'lat2', 'lon2'] 13 | OUTPUT_VARS = ['distance'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = { 18 | 'R': 6372.1, # Radius of the Earth [km] 19 | 20 | 'New_York_lat': 40.7128, 21 | 'New_York_lon': -74.0060, 22 | 'Madrid_lat': 40.4168, 23 | 'Madrid_lon': -3.7038, 24 | 'Vancouver_lat': 49.2827, 25 | 'Vancouver_lon': -123.1207, 26 | 'St_Johns_lat': 47.5615, 27 | 'St_Johns_lon': -52.7126, 28 | 'Ushuaia_lat': -54.8019, 29 | 'Ushuaia_lon': -68.3030, 30 | 'Alert_lat': 82.5018, 31 | 'Alert_lon': -62.3481, 32 | 'Dakar_lat': 14.7167, 33 | 'Dakar_lon': -17.4677, 34 | 'Antananarivo_lat': -18.8792, 35 | 'Antananarivo_lon': 47.5079, 36 | 'Rome_lat': 41.9028, 37 | 'Rome_lon': 12.4964, 38 | 'Istanbul_lat': 41.0082, 39 | 'Istanbul_lon': 28.9784, 40 | 'Bengaluru_lat': 12.9716, 41 | 'Bengaluru_lon': 77.5946, 42 | 'Lhasa_lat': 29.6548, 43 | 'Lhasa_lon': 91.1406, 44 | 'Manaus_lat': -3.1190, 45 | 'Manaus_lon': -60.0217, 46 | 'Bandung_lat': -6.9175, 47 | 'Bandung_lon': 107.6191, 48 | 49 | # Eurasian Pole of Inaccessibility or EPIA 50 | 'EPIA_lat': 46.283, 51 | 'EPIA_lon': 86.667, 52 | 53 | # Pacific Pole of Inaccessibility or PPIA 54 | 'PPIA_lat': -48.877, 55 | 'PPIA_lon': -123.393 56 | } 57 | ATOL = {} 58 | RTOL = { 59 | 'distance': 1e-3 60 | } 61 | 62 | 63 | class TestCaseType(TestCaseTypeEnum): 64 | SAME_POINT = ("Same point", 1) 65 | EQUATORIAL = ("Equatorial", 1) 66 | POLE_TO_POLE = ("Pole to pole", 1) 67 | NEW_YORK_TO_MADRID = ("New York to Madrid", 1) 68 | VANCOUVER_TO_ST_JOHNS = ("Vancouver to St. Johns", 1) 69 | DAKAR_TO_ANTANANARIVO = ("Dakar to Antananarivo", 1) 70 | ROME_TO_ISTANBUL = ("Rome to Istanbul", 1) 71 | BENGALURU_TO_LHASA = ("Bengaluru to Lhasa", 1) 72 | MANAUS_TO_BANDUNG = ("Manaus to Bandung", 1) 73 | USHUAIA_TO_ALERT = ("Ushuaia to Alert", 1) 74 | EPIA_TO_PPIA = ("Eurasian Pole of Inaccessibility to Pacific Pole of Inaccessibility", 1) 75 | RANDOM_POINTS = ("Two random points", 1) 76 | 77 | 78 | class ProblemTestCase(TestCase): 79 | def input_tuple(self) -> tuple: 80 | return self.input['lat1'], self.input['lon1'], self.input['lat2'], self.input['lon2'] 81 | 82 | def output_tuple(self) -> tuple: 83 | return self.output['distance'], 84 | 85 | 86 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 87 | test_case = ProblemTestCase(test_type) 88 | 89 | if test_type is TestCaseType.SAME_POINT: 90 | lat1 = uniform(-90, 90) 91 | lon1 = uniform(-180, 180) 92 | lat2 = lat1 93 | lon2 = lon1 94 | 95 | elif test_type is TestCaseType.EQUATORIAL: 96 | lat1 = 0.0 97 | lon1 = uniform(-180, 180) 98 | lat2 = 0.0 99 | lon2 = uniform(-180, 180) 100 | 101 | elif test_type is TestCaseType.POLE_TO_POLE: 102 | lat1 = 90.0 103 | lon1 = uniform(-180, 180) 104 | lat2 = -90.0 105 | lon2 = uniform(-180, 180) 106 | 107 | elif test_type is TestCaseType.NEW_YORK_TO_MADRID: 108 | lat1 = PHYSICAL_CONSTANTS['New_York_lat'] 109 | lon1 = PHYSICAL_CONSTANTS['New_York_lon'] 110 | lat2 = PHYSICAL_CONSTANTS['Madrid_lat'] 111 | lon2 = PHYSICAL_CONSTANTS['Madrid_lon'] 112 | 113 | elif test_type is TestCaseType.VANCOUVER_TO_ST_JOHNS: 114 | lat1 = PHYSICAL_CONSTANTS['Vancouver_lat'] 115 | lon1 = PHYSICAL_CONSTANTS['Vancouver_lon'] 116 | lat2 = PHYSICAL_CONSTANTS['St_Johns_lat'] 117 | lon2 = PHYSICAL_CONSTANTS['St_Johns_lon'] 118 | 119 | elif test_type is TestCaseType.DAKAR_TO_ANTANANARIVO: 120 | lat1 = PHYSICAL_CONSTANTS['Dakar_lat'] 121 | lon1 = PHYSICAL_CONSTANTS['Dakar_lon'] 122 | lat2 = PHYSICAL_CONSTANTS['Antananarivo_lat'] 123 | lon2 = PHYSICAL_CONSTANTS['Antananarivo_lon'] 124 | 125 | elif test_type is TestCaseType.ROME_TO_ISTANBUL: 126 | lat1 = PHYSICAL_CONSTANTS['Rome_lat'] 127 | lon1 = PHYSICAL_CONSTANTS['Rome_lon'] 128 | lat2 = PHYSICAL_CONSTANTS['Istanbul_lat'] 129 | lon2 = PHYSICAL_CONSTANTS['Istanbul_lon'] 130 | 131 | elif test_type is TestCaseType.BENGALURU_TO_LHASA: 132 | lat1 = PHYSICAL_CONSTANTS['Bengaluru_lat'] 133 | lon1 = PHYSICAL_CONSTANTS['Bengaluru_lon'] 134 | lat2 = PHYSICAL_CONSTANTS['Lhasa_lat'] 135 | lon2 = PHYSICAL_CONSTANTS['Lhasa_lon'] 136 | 137 | elif test_type is TestCaseType.MANAUS_TO_BANDUNG: 138 | lat1 = PHYSICAL_CONSTANTS['Manaus_lat'] 139 | lon1 = PHYSICAL_CONSTANTS['Manaus_lon'] 140 | lat2 = PHYSICAL_CONSTANTS['Bandung_lat'] 141 | lon2 = PHYSICAL_CONSTANTS['Bandung_lon'] 142 | 143 | elif test_type is TestCaseType.USHUAIA_TO_ALERT: 144 | lat1 = PHYSICAL_CONSTANTS['Ushuaia_lat'] 145 | lon1 = PHYSICAL_CONSTANTS['Ushuaia_lon'] 146 | lat2 = PHYSICAL_CONSTANTS['Alert_lat'] 147 | lon2 = PHYSICAL_CONSTANTS['Alert_lon'] 148 | 149 | elif test_type is TestCaseType.EPIA_TO_PPIA: 150 | lat1 = PHYSICAL_CONSTANTS['EPIA_lat'] 151 | lon1 = PHYSICAL_CONSTANTS['EPIA_lon'] 152 | lat2 = PHYSICAL_CONSTANTS['PPIA_lat'] 153 | lon2 = PHYSICAL_CONSTANTS['PPIA_lon'] 154 | 155 | elif test_type is TestCaseType.RANDOM_POINTS: 156 | lat1 = uniform(-90, 90) 157 | lon1 = uniform(-180, 180) 158 | lat2 = uniform(-90, 90) 159 | lon2 = uniform(-180, 180) 160 | 161 | else: 162 | raise ValueError(f"Unrecognized test case: {test_type}") 163 | 164 | test_case.input = { 165 | "lat1": lat1, 166 | "lon1": lon1, 167 | "lat2": lat2, 168 | "lon2": lon2 169 | } 170 | 171 | test_case.output['distance'] = haversine_distance(lat1, lon1, lat2, lon2) 172 | 173 | return test_case 174 | -------------------------------------------------------------------------------- /problems/game_of_life.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy import array, zeros, reshape 5 | from numpy.random import randint, choice 6 | 7 | from problems.test_case import TestCase, TestCaseTypeEnum 8 | from problems.solutions.game_of_life import game_of_life 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | FUNCTION_NAME = "game_of_life" 13 | INPUT_VARS = ['board', 'steps'] 14 | OUTPUT_VARS = ['board'] 15 | 16 | STATIC_RESOURCES = ["still_life.txt", "oscillators.txt", "spaceships.txt"] 17 | 18 | PHYSICAL_CONSTANTS = {} 19 | ATOL = {} 20 | RTOL = {} 21 | 22 | ALIVE = 1 23 | DEAD = 0 24 | 25 | # Still life patterns 26 | BLOCK = reshape(array(list("1111"), dtype=int), (2, 2)) 27 | BEEHIVE = reshape(array(list("011010010110"), dtype=int), (3, 4)) 28 | LOAF = reshape(array(list("0110100101010010"), dtype=int), (4, 4)) 29 | BOAT = reshape(array(list("110101010"), dtype=int), (3, 3)) 30 | TUB = reshape(array(list("010101010"), dtype=int), (3, 3)) 31 | 32 | # Oscillator patterns 33 | BLINKER = reshape(array(list("111"), dtype=int), (1, 3)) 34 | TOAD = reshape(array(list("01111110"), dtype=int), (2, 4)) 35 | BEACON = reshape(array(list("1100110000110011"), dtype=int), (4, 4)) 36 | 37 | PULSAR_STR = "0011100011100" + 13*"0" + 3*"1000010100001" + "0011100011100" + 13*"0" + "0011100011100" + 3*"1000010100001" + 13*"0" + "0011100011100" 38 | PULSAR = reshape(array(list(PULSAR_STR), dtype=int), (13, 13)) 39 | 40 | # Infinite growth patterns 41 | GLIDER_GUN_STR = 24*"0" + "1" + 11*"0" + \ 42 | 22*"0" + "101" + 11*"0" + \ 43 | 12*"0" + "11" + 6*"0" + "11" + 12*"0" + "11" + \ 44 | 11*"0" + "10001000011" + 12*"0" + "11" + \ 45 | "11" + 8*"0" + "100000100011" + 14*"0" + \ 46 | "11" + 8*"0" + "100010110000101" + 11*"0" + \ 47 | 10*"0" + "100000100000001" + 11*"0" + \ 48 | 11*"0" + "10001" + 20*"0" + \ 49 | 12*"0" + "11" + 22*"0" 50 | GLIDER_GUN = reshape(array(list(GLIDER_GUN_STR), dtype=int), (9, 36)) 51 | 52 | 53 | class TestCaseType(TestCaseTypeEnum): 54 | STILL_LIFE = ("still life", 1) 55 | OSCILLATORS = ("oscillators", 1) 56 | GLIDERS = ("gliders", 0) 57 | GLIDER_GUN = ("glider gun", 1) 58 | RANDOM_SMALL = ("small random", 1) 59 | RANDOM_LARGE = ("large random", 0) 60 | 61 | 62 | class ProblemTestCase(TestCase): 63 | def input_tuple(self) -> tuple: 64 | return self.input['board'], self.input['steps'] 65 | 66 | def output_tuple(self) -> tuple: 67 | return self.output['board'], 68 | 69 | 70 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 71 | test_case = ProblemTestCase(test_type) 72 | 73 | if test_type is TestCaseType.STILL_LIFE: 74 | grid = zeros((15, 15), dtype=int) 75 | steps = 5 76 | grid[1:3, 1:3] = BLOCK 77 | grid[5:8, 2:6] = BEEHIVE 78 | grid[1:5, 7:11] = LOAF 79 | grid[6:9, 10:13] = TUB 80 | 81 | elif test_type is TestCaseType.OSCILLATORS: 82 | grid = zeros((17, 36), dtype=int) 83 | steps = 31 84 | grid[3, 16:19] = BLINKER 85 | grid[8:10, 15:19] = TOAD 86 | grid[10:14, 1:5] = BEACON 87 | grid[2:15, 21:34] = PULSAR 88 | 89 | elif test_type is TestCaseType.GLIDERS: 90 | raise NotImplementedError 91 | 92 | elif test_type is TestCaseType.GLIDER_GUN: 93 | N = randint(15, 50) 94 | M = randint(50, 100) 95 | steps = 10 96 | grid = zeros((N, M), dtype=int) 97 | grid[2:11, 2:38] = GLIDER_GUN 98 | 99 | elif test_type is TestCaseType.RANDOM_SMALL: 100 | N = randint(5, 50) 101 | M = randint(5, 50) 102 | steps = randint(3, 10) 103 | grid = choice([ALIVE, DEAD], N*M, p=[0.5, 0.5]).reshape(N, M) 104 | 105 | elif test_type is TestCaseType.RANDOM_LARGE: 106 | N = randint(200, 250) 107 | M = randint(200, 250) 108 | steps = randint(3, 10) 109 | grid = choice([ALIVE, DEAD], N*M, p=[0.5, 0.5]).reshape(N, M) 110 | 111 | else: 112 | raise ValueError(f"Unrecognized test case: {test_type}") 113 | 114 | test_case.input['board'] = grid.tolist() 115 | test_case.input['steps'] = steps 116 | 117 | test_case.output['board'] = game_of_life(test_case.input['board'], steps) 118 | 119 | return test_case 120 | -------------------------------------------------------------------------------- /problems/habitable_exoplanets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.habitable_exoplanets import habitable_exoplanet 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "habitable_exoplanet" 12 | INPUT_VARS = ['L_star', 'r'] 13 | OUTPUT_VARS = ['habitability'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = { 18 | # For the sun L=1 and for the Earth r=1 by definition. 19 | 'L_sun': 1.00, # [L☉] 20 | 'r_Earth': 1.00, # [AU] 21 | 22 | # Source: https://en.wikipedia.org/wiki/Proxima_Centauri_b 23 | 'L_Proxima_Centauri': 0.0015, # [L☉] 24 | 'r_Proxima_Centauri_b': 0.05, # [AU] 25 | 26 | # Sources: 27 | # https://en.wikipedia.org/wiki/Kepler-440b (for semi-major axis of 0.242 AU) 28 | # https://exoplanetarchive.ipac.caltech.edu/cgi-bin/DisplayOverview/nph-DisplayOverview?objname=Kepler-440+b 29 | # &type=CONFIRMED_PLANET (for log10(L☉) of -1.102 +0.111 -0.142 => L☉ = 0.079) 30 | # https://iopscience.iop.org/article/10.1088/0004-637X/800/2/99 (Table 6 for L☉ of 0.079 +0.023 −0.022) 31 | 'L_Kepler_440': 0.079, 32 | 'r_Kepler_440b': 0.242 33 | } 34 | 35 | ATOL = {} 36 | RTOL = {} 37 | 38 | 39 | class TestCaseType(TestCaseTypeEnum): 40 | EARTH = ("Earth", 1) 41 | PROXIMA_CENTAURI_B = ("Proxima Centauri b", 1) 42 | KEPLER_440B = ("Kepler 440b", 1) 43 | RANDOM = ("Randomly generated exoplanet", 1) 44 | 45 | 46 | class ProblemTestCase(TestCase): 47 | def input_tuple(self) -> tuple: 48 | return self.input['L_star'], self.input['r'] 49 | 50 | def output_tuple(self) -> tuple: 51 | return self.output['habitability'], 52 | 53 | 54 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 55 | test_case = ProblemTestCase(test_type) 56 | 57 | if test_type is TestCaseType.EARTH: 58 | L_star = PHYSICAL_CONSTANTS['L_sun'] 59 | r = PHYSICAL_CONSTANTS['r_Earth'] 60 | 61 | elif test_type is TestCaseType.PROXIMA_CENTAURI_B: 62 | L_star = PHYSICAL_CONSTANTS['L_Proxima_Centauri'] 63 | r = PHYSICAL_CONSTANTS['r_Proxima_Centauri_b'] 64 | 65 | elif test_type is TestCaseType.KEPLER_440B: 66 | L_star = PHYSICAL_CONSTANTS['L_Kepler_440'] 67 | r = PHYSICAL_CONSTANTS['r_Kepler_440b'] 68 | 69 | elif test_type is TestCaseType.RANDOM: 70 | L_star = float(uniform(0.1, 5.0, 1)[0]) 71 | r = float(uniform(0.1, 5.0, 1)[0]) 72 | 73 | else: 74 | raise ValueError(f"Unrecognized test case: {test_type}") 75 | 76 | test_case.input['L_star'] = L_star 77 | test_case.input['r'] = r 78 | test_case.output['habitability'] = habitable_exoplanet(L_star, r) 79 | 80 | return test_case 81 | -------------------------------------------------------------------------------- /problems/molecular_mass_calculator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import choice 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "molecular_mass" 11 | INPUT_VARS = ['chemical_formula'] 12 | OUTPUT_VARS = ['mass'] 13 | 14 | STATIC_RESOURCES = ["periodic_table.csv"] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = { 19 | 'mass': 0.1 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | RUST = ("Rust (ferric oxide)", 1) 25 | PLUTONIUM = ("Plutonium", 1) 26 | LSD = ("LSD", 1) 27 | HIGH_T_SUPERCONDUCTOR = ("BSCCO (high temperature superconductor)", 1) 28 | RANDOM_CHEMICAL = ("random chemical", 2) 29 | 30 | 31 | class ProblemTestCase(TestCase): 32 | def input_tuple(self) -> tuple: 33 | return self.input['chemical_formula'], 34 | 35 | def output_tuple(self) -> tuple: 36 | return self.output['mass'], 37 | 38 | 39 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 40 | test_case = ProblemTestCase(test_type) 41 | 42 | if test_type is TestCaseType.RUST: 43 | chemical_formula = "Fe2O3" 44 | 45 | elif test_type is TestCaseType.PLUTONIUM: 46 | chemical_formula = "Pu" 47 | 48 | elif test_type is TestCaseType.HIGH_T_SUPERCONDUCTOR: 49 | chemical_formula = "Bi2Sr2Ca2Cu3O10" 50 | 51 | elif test_type is TestCaseType.LSD: 52 | chemical_formula = "C20H25N3O" 53 | 54 | elif test_type is TestCaseType.RANDOM_CHEMICAL: 55 | chemical_formula = choice(["CO2", "CH4", "C6H12O6", "PuCoGa5", "CH3NH2", "W", "C2H5OH"], 1)[0] 56 | 57 | else: 58 | raise ValueError(f"Unrecognized test case: {test_type}") 59 | 60 | 61 | # Delay solution import until tests can handle static resources: 62 | # https://github.com/project-lovelace/lovelace-problems/issues/60 63 | from problems.solutions.molecular_mass_calculator import molecular_mass 64 | 65 | test_case.input['chemical_formula'] = chemical_formula 66 | test_case.output['mass'] = molecular_mass(chemical_formula) 67 | 68 | return test_case 69 | -------------------------------------------------------------------------------- /problems/nand_gate.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from problems.test_case import TestCase, TestCaseTypeEnum 5 | from problems.solutions.nand_gate import NAND 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | FUNCTION_NAME = "NAND" 10 | INPUT_VARS = ['p', 'q'] 11 | OUTPUT_VARS = ['nand'] 12 | 13 | STATIC_RESOURCES = [] 14 | 15 | PHYSICAL_CONSTANTS = {} 16 | ATOL = {} 17 | RTOL = {} 18 | 19 | 20 | class TestCaseType(TestCaseTypeEnum): 21 | ZERO_ZERO = ("00", 1) 22 | ZERO_ONE = ("01", 1) 23 | ONE_ZERO = ("10", 1) 24 | ONE_ONE = ("11", 1) 25 | 26 | 27 | class ProblemTestCase(TestCase): 28 | def input_tuple(self) -> tuple: 29 | return self.input['p'], self.input['q'] 30 | 31 | def output_tuple(self) -> tuple: 32 | return self.output['nand'], 33 | 34 | 35 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 36 | test_case = ProblemTestCase(test_type) 37 | 38 | if test_type is TestCaseType.ZERO_ZERO: 39 | p, q = 0, 0 40 | 41 | elif test_type is TestCaseType.ZERO_ONE: 42 | p, q = 0, 1 43 | 44 | elif test_type is TestCaseType.ONE_ZERO: 45 | p, q = 1, 0 46 | 47 | elif test_type is TestCaseType.ONE_ONE: 48 | p, q = 1, 1 49 | 50 | else: 51 | raise ValueError(f"Unrecognized test case: {test_type}") 52 | 53 | test_case.input["p"], test_case.input["q"] = p, q 54 | test_case.output["nand"] = NAND(p, q) 55 | 56 | return test_case 57 | -------------------------------------------------------------------------------- /problems/numerical_diff.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from math import exp, sin 3 | from random import uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.numerical_diff import numerical_diff 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "numerical_diff" 11 | INPUT_VARS = ['f', 'x'] 12 | OUTPUT_VARS = ['df'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = { 19 | 'df': 0.005, # 0.5% relative tolerance 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | ZERO = ("Zero function", 1) 25 | CONSTANT = ("Constant function", 1) 26 | LINEAR = ("Linear polynomial", 1) 27 | QUADRATIC = ("Quadratic polynomial", 1) 28 | CUBIC = ("Cubic polynomial", 1) 29 | SINE = ("Sine function", 1) 30 | EXPONENTIAL = ("Exponential function", 1) 31 | 32 | 33 | class ProblemTestCase(TestCase): 34 | def input_tuple(self) -> tuple: 35 | return (self.input['f'], self.input['x']) 36 | 37 | def output_tuple(self) -> tuple: 38 | return self.output['df'], 39 | 40 | 41 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 42 | test_case = ProblemTestCase(test_type) 43 | 44 | x = uniform(-10, 10) 45 | 46 | if test_type is TestCaseType.ZERO: 47 | f = lambda x: 0 48 | 49 | elif test_type is TestCaseType.CONSTANT: 50 | f = lambda x: 2021 51 | 52 | elif test_type is TestCaseType.LINEAR: 53 | f = lambda x: 2021*x + 3 54 | 55 | elif test_type is TestCaseType.QUADRATIC: 56 | f = lambda x: x**2 + 2*x 57 | 58 | elif test_type is TestCaseType.CUBIC: 59 | f = lambda x: 3*x**3 60 | 61 | elif test_type is TestCaseType.SINE: 62 | f = sin 63 | 64 | elif test_type is TestCaseType.EXPONENTIAL: 65 | f = exp 66 | 67 | else: 68 | raise ValueError(f"Unrecognized test case: {test_type}") 69 | 70 | test_case.input = { 71 | 'f': f, 72 | 'x': x, 73 | } 74 | 75 | test_case.output['df'] = numerical_diff(f, x) 76 | 77 | return test_case 78 | -------------------------------------------------------------------------------- /problems/plump_moose.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from numpy.random import uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.plump_moose import moose_body_mass 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "moose_body_mass" 11 | INPUT_VARS = ["latitude"] 12 | OUTPUT_VARS = ["mass"] 13 | 14 | STATIC_RESOURCES = [] 15 | PHYSICAL_CONSTANTS = {} 16 | 17 | ATOL = {} 18 | RTOL = { 19 | "mass": 1e-6 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | MALMO = ("Moose from Malmö", 1) 25 | STOCKHOLM = ("Moose from Stockholm", 1) 26 | KIRUNA = ("Moose from Kiruna", 1) 27 | SOUTHERN = ("Southern moose", 1) 28 | NORTHERN = ("Northern moose", 1) 29 | RANDOM = ("Random", 2) 30 | 31 | 32 | class ProblemTestCase(TestCase): 33 | def input_tuple(self): 34 | return self.input["latitude"], 35 | 36 | def output_tuple(self): 37 | return self.output["mass"], 38 | 39 | 40 | def generate_test_case(test_type): 41 | test_case = ProblemTestCase(test_type) 42 | 43 | if test_type is TestCaseType.MALMO: 44 | latitude = 55.60587 45 | 46 | elif test_type is TestCaseType.STOCKHOLM: 47 | latitude = 59.33258 48 | 49 | elif test_type is TestCaseType.KIRUNA: 50 | latitude = 67.85507 51 | 52 | elif test_type is TestCaseType.SOUTHERN: 53 | latitude = uniform(58, 62) 54 | 55 | elif test_type is TestCaseType.NORTHERN: 56 | latitude = uniform(62, 66) 57 | 58 | elif test_type is TestCaseType.RANDOM: 59 | latitude = uniform(57, 67) 60 | 61 | else: 62 | raise ValueError(f"Unrecognized test case: {test_type}") 63 | 64 | test_case.input["latitude"] = latitude 65 | test_case.output["mass"] = moose_body_mass(latitude) 66 | 67 | return test_case 68 | -------------------------------------------------------------------------------- /problems/rna_translation.py: -------------------------------------------------------------------------------- 1 | import random 2 | import logging 3 | 4 | from problems.test_case import TestCase, TestCaseTypeEnum 5 | from problems.solutions.rna_translation import amino_acid_sequence, rna_codon_table 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | FUNCTION_NAME = "amino_acid_sequence" 10 | 11 | INPUT_VARS = ["rna"] 12 | OUTPUT_VARS = ["amino_acid_sequence"] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = {} 19 | 20 | class ProblemTestCase(TestCase): 21 | def input_tuple(self): 22 | return self.input["rna"], 23 | 24 | def output_tuple(self): 25 | return self.output["amino_acid_sequence"], 26 | 27 | class TestCaseType(TestCaseTypeEnum): 28 | NO_CODONS = ("No codons", 1) 29 | ONE_CODON = ("One codon", 5) 30 | FEW_CODONS = ("Few codons", 2) 31 | MANY_CODONS = ("Many codons", 2) 32 | ALL_CODONS = ("All codons", 1) 33 | END_CODON = ("End codon", 2) 34 | STOP_IN_MIDDLE = ("Stop codon in the middle", 1) 35 | 36 | start_codons = ["AUG"] 37 | stop_codons = ["UAA", "UAG", "UGA"] 38 | 39 | intermediate_codon_table = rna_codon_table.copy() 40 | [intermediate_codon_table.pop(codon) for codon in start_codons + stop_codons] 41 | 42 | def random_codons(N): 43 | return "".join(["".join(random.choices("ACGU", k=3)) for _ in range(N)]) 44 | 45 | def random_intermediate_codons(N): 46 | return "".join(random.choices(list(intermediate_codon_table.keys()), k=N)) 47 | 48 | def generate_test_case(test_type): 49 | test_case = ProblemTestCase(test_type) 50 | 51 | if test_type is TestCaseType.NO_CODONS: 52 | rna = "" 53 | 54 | elif test_type is TestCaseType.ONE_CODON: 55 | rna = random_intermediate_codons(1) 56 | 57 | elif test_type is TestCaseType.FEW_CODONS: 58 | N = random.randint(2, 5) 59 | rna = random_intermediate_codons(N) + random.choice(stop_codons) 60 | 61 | elif test_type is TestCaseType.MANY_CODONS: 62 | N = random.randint(10, 100) 63 | rna = random_intermediate_codons(N) 64 | 65 | elif test_type is TestCaseType.ALL_CODONS: 66 | rna = start_codons[0] + "".join(list(intermediate_codon_table.keys())) + random.choice(stop_codons) 67 | 68 | elif test_type is TestCaseType.END_CODON: 69 | rna = random.choice(stop_codons) 70 | 71 | elif test_type is TestCaseType.STOP_IN_MIDDLE: 72 | N1 = random.randint(10, 20) 73 | N2 = random.randint(5, 10) 74 | rna = random_intermediate_codons(N1) + random.choice(stop_codons) + random_intermediate_codons(N2) 75 | 76 | else: 77 | raise ValueError(f"Unrecognized test case: {test_type}") 78 | 79 | test_case.input["rna"] = rna 80 | test_case.output["amino_acid_sequence"] = amino_acid_sequence(rna) 81 | 82 | return test_case 83 | -------------------------------------------------------------------------------- /problems/rock_star_climate.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.rock_star_climate import rock_temperature 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "rock_temperature" 12 | INPUT_VARS = ['solar_constant', 'albedo', 'emissivity'] 13 | OUTPUT_VARS = ['T_rock'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = { 18 | # Earth 19 | 'S_Earth': 1361, # Solar constant [W/m^2] from Kopp & Lean (2011). 20 | 'a_Earth': 0.306, # Bond albedo from NASA Earth fact sheet: https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html 21 | 'ε_Earth': 0.612, # Effective emissivity. 22 | 23 | # Mars 24 | 'S_Mars': 586, # Assuming S falls off as 1/r^2 from Kopp & Lean (2011) and r = 1.524 AU. 25 | 'a_Mars': 0.24, # Bond albedo from NASA Mars fact sheet: https://nssdc.gsfc.nasa.gov/planetary/factsheet/marsfact.html 26 | 'ε_Mars': 0.9, # Can't find anything so picking a 0.9 which is close to limestone and brick. 27 | 28 | # Pluto 29 | 'S_Pluto': 0.87, # Assuming S falls off as 1/r^2 from Kopp & Lean (2011) and r = 39.48 AU (semi-major axis). 30 | 'a_Pluto': 0.72, # Bond albedo from NASA Pluto fact sheet: https://nssdc.gsfc.nasa.gov/planetary/factsheet/plutofact.html 31 | 'ε_Pluto': 0.9 32 | } 33 | 34 | ATOL = {} 35 | RTOL = { 36 | 'T_rock': 1e-6 37 | } 38 | 39 | 40 | class TestCaseType(TestCaseTypeEnum): 41 | EARTH = ("Earth", 1) 42 | BLACKBODY_EARTH = ("Blackbody Earth", 1) 43 | REFLECTIVE_EARTH = ("Reflective Earth", 1) 44 | MARS = ("Mars", 1) 45 | PLUTO = ("Pluto", 1) 46 | RANDOM = ("Random", 1) 47 | 48 | 49 | class ProblemTestCase(TestCase): 50 | def input_tuple(self) -> tuple: 51 | return self.input['solar_constant'], self.input['albedo'], self.input['emissivity'], 52 | 53 | def output_tuple(self) -> tuple: 54 | return self.output['T_rock'], 55 | 56 | 57 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 58 | test_case = ProblemTestCase(test_type) 59 | 60 | if test_type is TestCaseType.EARTH: 61 | S = PHYSICAL_CONSTANTS['S_Earth'] 62 | a = PHYSICAL_CONSTANTS['a_Earth'] 63 | ε = PHYSICAL_CONSTANTS['ε_Earth'] 64 | 65 | elif test_type is TestCaseType.BLACKBODY_EARTH: 66 | S = PHYSICAL_CONSTANTS['S_Earth'] 67 | a = PHYSICAL_CONSTANTS['a_Earth'] 68 | ε = 1.0 69 | 70 | elif test_type is TestCaseType.REFLECTIVE_EARTH: 71 | S = PHYSICAL_CONSTANTS['S_Earth'] 72 | a = 1 73 | ε = PHYSICAL_CONSTANTS['ε_Earth'] 74 | 75 | elif test_type is TestCaseType.MARS: 76 | S = PHYSICAL_CONSTANTS['S_Mars'] 77 | a = PHYSICAL_CONSTANTS['a_Mars'] 78 | ε = PHYSICAL_CONSTANTS['ε_Mars'] 79 | 80 | elif test_type is TestCaseType.PLUTO: 81 | S = PHYSICAL_CONSTANTS['S_Pluto'] 82 | a = PHYSICAL_CONSTANTS['a_Pluto'] 83 | ε = PHYSICAL_CONSTANTS['ε_Pluto'] 84 | 85 | elif test_type is TestCaseType.RANDOM: 86 | S = uniform(1000, 10000) 87 | a = uniform(0, 1) 88 | ε = uniform(0, 1) 89 | 90 | else: 91 | raise ValueError(f"Unrecognized test case: {test_type}") 92 | 93 | test_case.input = {'solar_constant': S, 'albedo': a, 'emissivity': ε} 94 | test_case.output['T_rock'] = rock_temperature(S, a, ε) 95 | 96 | return test_case 97 | -------------------------------------------------------------------------------- /problems/rocket_science.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.rocket_science import rocket_fuel 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "rocket_fuel" 12 | INPUT_VARS = ['v'] 13 | OUTPUT_VARS = ['m_fuel'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = { 18 | 'v_e': 2550.0, # [m/s] 19 | 'M': 250000.0, # [kg] 20 | 21 | # All escape velocities in [m/s]. 22 | # Source: https://en.wikipedia.org/wiki/Escape_velocity#List_of_escape_velocities 23 | 'v_Earth': 11186.0, 24 | 'v_Moon': 2380.0, 25 | 'v_Jupiter': 60200.0, 26 | 'v_Pluto': 1230.0, 27 | 'v_Phobos': 1.139 28 | } 29 | 30 | ATOL = {} 31 | RTOL = { 32 | 'm_fuel': 1e-6 33 | } 34 | 35 | 36 | class TestCaseType(TestCaseTypeEnum): 37 | EARTH = ('Earth', 1) 38 | MOON = ('Moon', 1) 39 | JUPITER = ('Jupiter', 1) 40 | PLUTO = ('Pluto', 1) 41 | PHOBOS = ('Phobos', 1) 42 | RANDOM = ('Random', 1) 43 | 44 | 45 | class ProblemTestCase(TestCase): 46 | def input_tuple(self) -> tuple: 47 | return self.input['v'], 48 | 49 | def output_tuple(self) -> tuple: 50 | return self.output['m_fuel'], 51 | 52 | 53 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 54 | test_case = ProblemTestCase(test_type) 55 | 56 | if test_type is TestCaseType.EARTH: 57 | v = PHYSICAL_CONSTANTS['v_Earth'] 58 | 59 | elif test_type is TestCaseType.MOON: 60 | v = PHYSICAL_CONSTANTS['v_Moon'] 61 | 62 | elif test_type is TestCaseType.JUPITER: 63 | v = PHYSICAL_CONSTANTS['v_Jupiter'] 64 | 65 | elif test_type is TestCaseType.PLUTO: 66 | v = PHYSICAL_CONSTANTS['v_Pluto'] 67 | 68 | elif test_type is TestCaseType.PHOBOS: 69 | v = PHYSICAL_CONSTANTS['v_Phobos'] 70 | 71 | elif test_type is TestCaseType.RANDOM: 72 | v = float(uniform(1.0, 100.0, 1)[0]) 73 | 74 | else: 75 | raise ValueError(f"Unrecognized test case: {test_type}") 76 | 77 | test_case.input['v'] = v 78 | test_case.output['m_fuel'] = rocket_fuel(v) 79 | 80 | return test_case 81 | -------------------------------------------------------------------------------- /problems/scientific_temperatures.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | from numpy.random import uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.scientific_temperatures import fahrenheit_to_celsius 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "fahrenheit_to_celsius" 11 | INPUT_VARS = ['F'] 12 | OUTPUT_VARS = ['C'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = { 19 | 'C': 1e-5 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | WARM_DAY = ("Warm day", 1) 25 | COLD_DAY = ("Cold day", 1) 26 | WATER_FREEZING_POINT = ("Freezing point of water", 1) 27 | WATER_BOILING_POINT = ("Boiling point of water", 1) 28 | MINUS_40 = ("-40°C", 1) 29 | ABSOLUTE_ZERO = ("Absolute zero", 1) 30 | SUN_SURFACE = ("Surface of the sun", 1) 31 | 32 | 33 | class ProblemTestCase(TestCase): 34 | def input_tuple(self) -> tuple: 35 | return self.input['F'], 36 | 37 | def output_tuple(self) -> tuple: 38 | return self.output['C'], 39 | 40 | 41 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 42 | test_case = ProblemTestCase(test_type) 43 | 44 | if test_type is TestCaseType.WARM_DAY: 45 | F = uniform(80, 110) 46 | 47 | elif test_type is TestCaseType.COLD_DAY: 48 | F = uniform(-20, 30) 49 | 50 | elif test_type is TestCaseType.WATER_FREEZING_POINT: 51 | F = 32.0 52 | 53 | elif test_type is TestCaseType.WATER_BOILING_POINT: 54 | F = 212.0 55 | 56 | elif test_type is TestCaseType.MINUS_40: 57 | F = -40.0 58 | 59 | elif test_type is TestCaseType.ABSOLUTE_ZERO: 60 | F = -459.67 61 | 62 | elif test_type is TestCaseType.SUN_SURFACE: 63 | F = 9940.73 64 | 65 | else: 66 | raise ValueError(f"Unrecognized test case: {test_type}") 67 | 68 | test_case.input['F'] = F 69 | test_case.output['C'] = fahrenheit_to_celsius(F) 70 | return test_case 71 | -------------------------------------------------------------------------------- /problems/sha_256.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | 4 | from numpy.random import uniform 5 | 6 | from problems.test_case import TestCase, TestCaseTypeEnum 7 | from problems.solutions.sha_256 import SHA256 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | FUNCTION_NAME = "SHA256" 12 | INPUT_VARS = ['message'] 13 | OUTPUT_VARS = ['digest'] 14 | 15 | STATIC_RESOURCES = [] 16 | 17 | PHYSICAL_CONSTANTS = {} 18 | ATOL = {} 19 | RTOL = {} 20 | 21 | 22 | class TestCaseType(TestCaseTypeEnum): 23 | EMPTY_STRING = ("Empty string", 1) 24 | ABC = ("ABC", 1) 25 | QUICK_BROWN_FOX = ("Quick brown fox", 1) 26 | 27 | 28 | class ProblemTestCase(TestCase): 29 | def input_tuple(self) -> tuple: 30 | return self.input['message'], 31 | 32 | def output_tuple(self) -> tuple: 33 | return self.output['digest'], 34 | 35 | 36 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 37 | test_case = ProblemTestCase(test_type) 38 | 39 | if test_type is TestCaseType.EMPTY_STRING: 40 | M = "" 41 | 42 | elif test_type is TestCaseType.ABC: 43 | M = "ABC" 44 | 45 | elif test_type is TestCaseType.QUICK_BROWN_FOX: 46 | M = "The quick brown fox jumps over the lazy dog" 47 | 48 | else: 49 | raise ValueError(f"Unrecognized test case: {test_type}") 50 | 51 | test_case.input['message'] = M 52 | test_case.output['digest'] = SHA256(M) 53 | 54 | return test_case 55 | -------------------------------------------------------------------------------- /problems/speed_of_light.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | from numpy.random import uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.speed_of_light import light_time 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "light_time" 11 | INPUT_VARS = ['distance'] 12 | OUTPUT_VARS = ['time'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = { 17 | # Source: https://en.wikipedia.org/wiki/Speed_of_light 18 | 'c': 299792458, # speed of light [m/s] 19 | 20 | # All distances in meters. 21 | 22 | # Time-averaged distance between the Earth and lunar surfaces. The average distance between the Earth and Moon is 23 | # 384,400 km but the radius of the Earth is 6,371 km and the radius of the Moon is 1,737 km, so in this case light 24 | # actually travels 384,400 - 6,371, - 1,737 = 376,292 km 25 | # Source: https://en.wikipedia.org/wiki/Lunar_distance_(astronomy) 26 | 'd_Earth_Moon': 376292e3, # 376,292 km 27 | 28 | # 1 Astronomical unit (almost equal to the average of Earth's aphelion and perihelion). 29 | # Source: https://en.wikipedia.org/wiki/Astronomical_unit 30 | 'd_Sun_Earth': 149597870700.0, # ~150 million kilometres 31 | 32 | # Maximum distance between the Earth and Mars. 33 | # Source: https://www.space.com/14729-spacekids-distance-earth-mars.html 34 | 'd_Earth_Mars': 401e9, # ~401 million km 35 | 36 | # Maximum distance between Earth and Jupiter. 37 | # Source: https://www.universetoday.com/14514/how-far-is-jupiter-from-earth/ 38 | 'd_Earth_Jupiter': 928e9, # ~928 million km 39 | } 40 | 41 | ATOL = {} 42 | RTOL = { 43 | 'time': 1e-5 44 | } 45 | 46 | 47 | class TestCaseType(TestCaseTypeEnum): 48 | EARTH_TO_MOON = ("Earth to moon", 1) 49 | SUN_TO_EARTH = ("Sun to Earth", 1) 50 | MAX_EARTH_TO_MARS = ("Earth to Mars", 1) 51 | MAX_EARTH_TO_JUPITER = ("Earth to Jupiter", 1) 52 | RANDOM = ("Random", 1) 53 | 54 | 55 | class ProblemTestCase(TestCase): 56 | def input_tuple(self) -> tuple: 57 | return self.input['distance'], 58 | 59 | def output_tuple(self) -> tuple: 60 | return self.output['time'], 61 | 62 | 63 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 64 | test_case = ProblemTestCase(test_type) 65 | 66 | if test_type is TestCaseType.EARTH_TO_MOON: 67 | distance = PHYSICAL_CONSTANTS['d_Earth_Moon'] 68 | 69 | elif test_type is TestCaseType.SUN_TO_EARTH: 70 | distance = PHYSICAL_CONSTANTS['d_Sun_Earth'] 71 | 72 | elif test_type is TestCaseType.MAX_EARTH_TO_MARS: 73 | distance = PHYSICAL_CONSTANTS['d_Earth_Mars'] 74 | 75 | elif test_type is TestCaseType.MAX_EARTH_TO_JUPITER: 76 | distance = PHYSICAL_CONSTANTS['d_Earth_Jupiter'] 77 | 78 | elif test_type is TestCaseType.RANDOM: 79 | c = PHYSICAL_CONSTANTS['c'] 80 | distance = uniform(c, 60*c) 81 | 82 | else: 83 | raise ValueError(f"Unrecognized test case: {test_type}") 84 | 85 | test_case.input['distance'] = distance 86 | test_case.output['time'] = light_time(distance) 87 | 88 | return test_case 89 | -------------------------------------------------------------------------------- /problems/temperature_variations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | from numpy.random import randint, uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.temperature_variations import temperature_statistics 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "temperature_statistics" 11 | INPUT_VARS = ['T'] 12 | OUTPUT_VARS = ['T_avg', 'T_std'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = { 17 | 'T': { 18 | 'Saskatoon, Canada': [-13.9, -11.4, -4.9, 5.2, 11.8, 16.1, 19.0, 18.2, 12.0, 4.4, -5.2, -12.4], 19 | 'Baku, Azerbaijan': [4.4, 4.2, 7.0, 12.9, 18.5, 23.5, 26.4, 26.3, 22.5, 16.6, 11.2, 7.3], 20 | 'Khartoum, Sudan': [23.2, 25.0, 28.7, 31.9, 34.5, 34.3, 32.1, 31.5, 32.5, 32.4, 28.1, 24.5], 21 | 'Singapore': [26.5, 27.1, 27.5, 28.0, 28.3, 28.3, 27.9, 27.9, 27.6, 27.6, 27.0, 26.4], 22 | 'San Juan, Argentina': [27.1, 25.5, 22.8, 17.2, 12.2, 8.3, 7.7, 10.6, 14.4, 19.8, 23.4, 26.3] 23 | } 24 | } 25 | 26 | ATOL = {} 27 | RTOL = { 28 | 'T_avg': 1e-8, 29 | 'T_std': 1e-8 30 | } 31 | 32 | 33 | class TestCaseType(TestCaseTypeEnum): 34 | ONE_VALUE = ("One value", 1) 35 | TWO_VALUES = ("Two values", 1) 36 | BUNCH_OF_VALUES = ("Bunch of values", 1) 37 | SASKATOON = ("Saskatoon, Canada", 1) 38 | BAKU = ("Baku, Azerbaijan", 1) 39 | KHARTOUM = ("Khartoum, Sudan", 1) 40 | SINGAPORE = ("Singapore", 1) 41 | SAN_JUAN = ("San Juan, Argentina", 1) 42 | 43 | 44 | class ProblemTestCase(TestCase): 45 | def input_tuple(self) -> tuple: 46 | return self.input['T'], 47 | 48 | def output_tuple(self) -> tuple: 49 | return self.output['T_avg'], self.output['T_std'] 50 | 51 | 52 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 53 | test_case = ProblemTestCase(test_type) 54 | 55 | if test_type is TestCaseType.ONE_VALUE: 56 | T = [uniform(-20, 35)] 57 | 58 | elif test_type is TestCaseType.TWO_VALUES: 59 | T = uniform(-20, 35, size=2).tolist() 60 | 61 | elif test_type is TestCaseType.BUNCH_OF_VALUES: 62 | N = randint(5, 20) 63 | T = uniform(-20, 35, size=N).tolist() 64 | 65 | elif test_type is TestCaseType.SASKATOON: 66 | T = PHYSICAL_CONSTANTS['T']['Saskatoon, Canada'] 67 | 68 | elif test_type is TestCaseType.BAKU: 69 | T = PHYSICAL_CONSTANTS['T']['Baku, Azerbaijan'] 70 | 71 | elif test_type is TestCaseType.KHARTOUM: 72 | T = PHYSICAL_CONSTANTS['T']['Khartoum, Sudan'] 73 | 74 | elif test_type is TestCaseType.SINGAPORE: 75 | T = PHYSICAL_CONSTANTS['T']['Singapore'] 76 | 77 | elif test_type is TestCaseType.SAN_JUAN: 78 | T = PHYSICAL_CONSTANTS['T']['San Juan, Argentina'] 79 | 80 | else: 81 | raise ValueError(f"Unrecognized test case: {test_type}") 82 | 83 | test_case.input['T'] = T 84 | test_case.output['T_avg'], test_case.output['T_std'] = temperature_statistics(T) 85 | 86 | return test_case 87 | -------------------------------------------------------------------------------- /problems/test_case.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from enum import Enum 3 | from math import isclose 4 | from typing import Tuple 5 | 6 | from numpy import ndarray, array_equal, allclose 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class TestCaseMismatchError(Exception): 12 | pass 13 | 14 | 15 | class TestCaseTypeEnum(Enum): 16 | def __init__(self, test_name, multiplicity): 17 | self.test_name = test_name 18 | self.multiplicity = multiplicity 19 | 20 | 21 | class TestCase(object): 22 | def __init__(self, test_type=None, input_vars=None, input_tuple=None, output_vars=None, output_tuple=None): 23 | self.test_type = test_type 24 | 25 | self.input = {} 26 | if input_vars is not None and input_tuple is not None: 27 | if len(input_vars) != len(input_tuple): 28 | raise ValueError("input_vars and input_tuple must be of the same length but input_vars={:} (len={:d}), " 29 | "input_tuple={:} (len={:d})".format(input_vars, len(input_vars), 30 | input_tuple, len(input_tuple))) 31 | for var, val in zip(input_vars, input_tuple): 32 | self.input[var] = val 33 | 34 | self.output = {} 35 | if output_vars is not None and output_tuple is not None: 36 | if len(output_vars) != len(output_tuple): 37 | raise ValueError("output_vars and output_tuple must be of the same length but output_vars={:} " 38 | "(len={:d}), output_tuple={:} (len={:d})".format(output_vars, len(output_vars), 39 | output_tuple, len(output_tuple))) 40 | for var, val in zip(output_vars, output_tuple): 41 | self.output[var] = val 42 | 43 | def input_tuple(self) -> tuple: 44 | pass 45 | 46 | def output_tuple(self) -> tuple: 47 | pass 48 | 49 | 50 | def values_match(v1, v2, tt, tol) -> bool: 51 | """ 52 | Check if v1 and v2 are equal. For ints and floats, an absolute or relative tolerance can be specified. 53 | Lists and tuples are checked recursively. 54 | 55 | :param v1: One value. 56 | :param v2: Another value. 57 | :param tt: Tolerance type: None, "absolute", or "relative". 58 | :param tol: Tolerance value if using "absolute" or "relative". 59 | :return: True or False 60 | """ 61 | 62 | if isinstance(v1, (int, float)): 63 | if tt is None: 64 | return v1 == v2 65 | elif tt == "absolute": 66 | return isclose(v1, v2, abs_tol=tol) 67 | elif tt == "relative": 68 | return isclose(v1, v2, rel_tol=tol) 69 | 70 | if isinstance(v1, (list, ndarray)) and isinstance(v2, (list, ndarray)): 71 | if tt is None: 72 | return array_equal(v1, v2) 73 | elif tt == "absolute": 74 | return allclose(v1, v2, atol=tol) 75 | elif tt == "relative": 76 | return allclose(v1, v2, rtol=tol) 77 | 78 | if isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple)): 79 | if len(v1) != len(v2): 80 | logger.debug("v1 and v2 lengths do not match: v1_len={:d}, v2_len={:d}".format(len(v1), len(v2))) 81 | return False 82 | 83 | for e1, e2 in zip(v1, v2): 84 | # Recursively check that each element of the two lists or tuples match. 85 | if values_match(e1, e2, tt, tol) is False: 86 | return False 87 | 88 | # We've gone through the entire list/tuple and each pair of elements match, so return True. 89 | return True 90 | 91 | # Values can't match if they're of different types (unless we're comparing ints with floats, 92 | # or lists with numpy arrays, or lists with tuples). 93 | if type(v1) != type(v2): 94 | logger.debug("v1 and v2 types do not match: v1_type={:}, v2_type={:}".format(type(v1), type(v2))) 95 | return False 96 | 97 | return v1 == v2 # Takes care of strings (and hopefully other data types not considered above). 98 | 99 | 100 | def test_case_solution_correct(correct_test_case: TestCase, user_test_case: TestCase, atol: dict, rtol: dict) -> Tuple[bool, TestCase]: 101 | """ 102 | Check whether user_test_case contains the correct output. Absolute and relative tolerances can be specified via 103 | the atol and rtol dictionaries. 104 | 105 | :param user_test_case: A TestCase object containing input given to the user and the output they produced. 106 | :param atol: A dictionary of absolute tolerances. 107 | :param rtol: A dictionary of relative tolerances. 108 | :param problem_test_case: A function that can be used to construct TestCase objects. 109 | :param solve_test_case: A function that can be used to fill the test case output with the correct output. 110 | :return: True or False, and a TestCase containing the correct output. 111 | """ 112 | logger.info("Verifying user solution for test case {:}...".format(correct_test_case.test_type)) 113 | 114 | input_vars = list(user_test_case.input.keys()) 115 | output_vars = list(user_test_case.output.keys()) 116 | 117 | for i, var in enumerate(input_vars): 118 | var_type = type(user_test_case.input[var]) 119 | val = user_test_case.input[var] 120 | logger.debug("Test case input {:d}: name={:s}, type={:}, value={:}".format(i, var, var_type, val)) 121 | 122 | # Assume user output is false until we can check that all values match. 123 | test_case_passed = False 124 | values_passed = [] 125 | 126 | # Verify that every output matches. 127 | for i, var in enumerate(output_vars): 128 | var_type = type(correct_test_case.output[var]) 129 | correct_val = correct_test_case.output[var] 130 | user_val = user_test_case.output[var] 131 | 132 | # Raise an error if both an absolute and relative tolerance are defined. Technically non-fatal as we could just 133 | # pick one, but this should encourage less sloppy problem modules. 134 | if var in atol.keys() and var in rtol.keys(): 135 | raise TestCaseMismatchError("Both atol={:} and rtol={:} are defined for output var {:s}! " 136 | "Only one must be defined.""".format(atol[var], rtol[var], var)) 137 | 138 | if var in atol.keys(): 139 | tolerance_type, tolerance = "absolute", atol[var] 140 | elif var in rtol.keys(): 141 | tolerance_type, tolerance = "relative", rtol[var] 142 | else: 143 | tolerance_type, tolerance = None, 0 144 | 145 | logger.debug("Test case output {:d}: name={:s}, type={:}, user_value={:}, correct_value={:}, " 146 | "tolerance_type={:}, tolerance={:}".format(i, var, var_type, user_val, correct_val, 147 | tolerance_type, tolerance)) 148 | 149 | output_correct = values_match(user_val, correct_val, tolerance_type, tolerance) 150 | 151 | values_passed.append(output_correct) 152 | if output_correct is False: 153 | logger.info("User output for {:s} is wrong.".format(var)) 154 | else: 155 | logger.info("User output for {:s} is correct.".format(var)) 156 | 157 | if all(values_passed): 158 | test_case_passed = True 159 | 160 | return test_case_passed, correct_test_case 161 | -------------------------------------------------------------------------------- /problems/wind_chill.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple 3 | from numpy.random import uniform 4 | 5 | from problems.test_case import TestCase, TestCaseTypeEnum 6 | from problems.solutions.wind_chill import wind_chill 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | FUNCTION_NAME = "wind_chill" 11 | INPUT_VARS = ['T_a', 'v'] 12 | OUTPUT_VARS = ['T_wc'] 13 | 14 | STATIC_RESOURCES = [] 15 | 16 | PHYSICAL_CONSTANTS = {} 17 | ATOL = {} 18 | RTOL = { 19 | 'T_wc': 1e-8 20 | } 21 | 22 | 23 | class TestCaseType(TestCaseTypeEnum): 24 | ZERO_TEMP = ("Zero celsius", 1) 25 | ZERO_WIND = ("Zero wind", 1) 26 | COLD_CALM = ("Cold calm weather", 1) 27 | COLD_WINDY = ("Cold windy weather", 1) 28 | VERY_COLD_CALM = ("Very cold and calm weather", 1) 29 | VERY_COLD_WINDY = ("Very cold and windy weather", 1) 30 | 31 | 32 | class ProblemTestCase(TestCase): 33 | def input_tuple(self) -> tuple: 34 | return self.input['T_a'], self.input['v'] 35 | 36 | def output_tuple(self) -> tuple: 37 | return self.output['T_wc'], 38 | 39 | 40 | def generate_test_case(test_type: TestCaseType) -> ProblemTestCase: 41 | test_case = ProblemTestCase(test_type) 42 | 43 | if test_type is TestCaseType.ZERO_TEMP: 44 | T_a = 0.0 45 | v = uniform(5, 40) 46 | 47 | elif test_type is TestCaseType.ZERO_WIND: 48 | T_a = uniform(-20, 20) 49 | v = 0.0 50 | 51 | elif test_type is TestCaseType.COLD_CALM: 52 | T_a = uniform(-15, 2) 53 | v = uniform(2, 8) 54 | 55 | elif test_type is TestCaseType.COLD_WINDY: 56 | T_a = uniform(-15, 2) 57 | v = uniform(25, 60) 58 | 59 | elif test_type is TestCaseType.VERY_COLD_CALM: 60 | T_a = uniform(-50, -20) 61 | v = uniform(2, 8) 62 | 63 | elif test_type is TestCaseType.VERY_COLD_WINDY: 64 | T_a = uniform(-50, -20) 65 | v = uniform(25, 60) 66 | 67 | else: 68 | raise ValueError(f"Unrecognized test case: {test_type}") 69 | 70 | test_case.input['T_a'], test_case.input['v'] = T_a, v 71 | test_case.output['T_wc'] = wind_chill(T_a, v) 72 | 73 | return test_case 74 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = 1 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format=%Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | atomicwrites==1.4.0 2 | attrs==20.3.0 3 | bitstring==3.1.7 4 | black==19.3b0 5 | certifi==2020.12.5 6 | chardet==4.0.0 7 | click==7.1.2 8 | docker==4.4.1 9 | entrypoints==0.3 10 | falcon==2.0.0 11 | filelock==3.0.12 12 | flake8==3.8.4 13 | flake8-bugbear==20.11.1 14 | gunicorn==20.0.4 15 | importlib-metadata==3.3.0 16 | iniconfig==1.1.1 17 | mccabe==0.6.1 18 | more-itertools==8.6.0 19 | numpy==1.19.4 20 | packaging==20.9 21 | pip==21.0.1 22 | pluggy==0.13.1 23 | py==1.10.0 24 | pycodestyle==2.6.0 25 | pyflakes==2.2.0 26 | pyparsing==2.4.7 27 | pytest==6.2.1 28 | scipy==1.5.4 29 | setuptools==52.0.0 30 | six==1.15.0 31 | toml==0.10.2 32 | urllib3==1.26.4 33 | virtualenv==20.4.3 34 | wcwidth==0.2.5 35 | websocket-client==0.57.0 36 | wheel==0.36.2 37 | zipp==3.4.0 38 | -------------------------------------------------------------------------------- /resources/correlation_does_not_imply_causation/spurious_xy.csv: -------------------------------------------------------------------------------- 1 | 5427,18.079 2 | 5688,18.594 3 | 6198,19.753 4 | 6462,20.734 5 | 6635,20.831 6 | 7336,23.029 7 | 7248,23.597 8 | 7491,23.584 9 | 8161,22.525 10 | 8578,27.731 11 | 9000,29.449 12 | -------------------------------------------------------------------------------- /resources/el_nino_intensities/mei.ext_index.txt: -------------------------------------------------------------------------------- 1 | YEAR DECJAN JANFEB FEBMAR MARAPR APRMAY MAYJUN JUNJUL JULAUG AUGSEP SEPOCT OCTNOV NOVDEC 2 | 1871 .032 -.055 .005 .42 .195 -.639 -.842 -.474 -.143 .072 .107 -.206 3 | 1872 -.633 -.875 -.864 -.664 -.59 -.618 -.658 -.795 -.8 -.669 -.651 -.847 4 | 1873 -.93 -.991 -1.301 -1.489 -1.066 -.689 -.657 -.646 -.318 -.012 -.355 -.54 5 | 1874 -.611 -.802 -1.036 -1.275 -1.258 -.964 -.902 -1.143 -1.148 -1.003 -.771 -.679 6 | 1875 -.774 -.686 -.67 -.958 -1.315 -1.812 -1.964 -1.553 -1.295 -.993 -.682 -.688 7 | 1876 -.878 -1.196 -1.504 -1.691 -1.878 -1.845 -1.435 -.867 -.393 -.071 .088 .17 8 | 1877 .193 .334 .373 .353 .441 .69 1.318 1.687 1.788 2.07 2.165 1.763 9 | 1878 1.877 2.361 2.495 1.883 1.006 .58 .54 .235 -.371 -.845 -.911 -.921 10 | 1879 -1.005 -.729 -.445 -.447 -.956 -1.18 -.827 -.607 -.523 -.557 -.72 -1.031 11 | 1880 -1.057 -.83 -.899 -1.069 -.965 -.595 .113 .394 .083 -.228 -.128 .17 12 | 1881 .148 .215 .174 .082 -.105 -.062 -.153 -.559 -.656 -.389 -.499 -.776 13 | 1882 -.598 -.461 -.585 -.799 -1.028 -.97 -.686 -.436 -.267 -.261 -.379 -.404 14 | 1883 -.562 -.628 -.562 -.672 -1.07 -.898 -.284 -.228 -.314 -.01 .004 -.012 15 | 1884 .094 .121 .112 .369 .786 .725 .368 .433 .628 .525 .333 .432 16 | 1885 .639 .599 .262 .247 .588 .414 -.044 .211 .506 .804 1.216 .914 17 | 1886 .384 .112 .016 -.103 -.619 -1.274 -1.466 -1.351 -1.228 -1.233 -1.306 -1.272 18 | 1887 -1.451 -1.573 -1.313 -1.088 -1.257 -.926 -.066 .194 .113 -.154 -.314 -.155 19 | 1888 .036 .389 .792 .948 .874 .921 1.038 1.34 1.732 1.788 1.723 1.851 20 | 1889 1.893 1.865 1.618 1.163 .481 -.068 -.555 -1.192 -1.494 -1.534 -1.532 -1.451 21 | 1890 -1.53 -1.731 -1.584 -1.456 -1.55 -1.564 -1.406 -1.264 -1.186 -1.006 -.876 -.638 22 | 1891 -.557 -.287 .17 .285 .061 .128 .36 .382 .192 .021 -.097 -.188 23 | 1892 -.128 -.184 -.708 -1.486 -1.572 -1.152 -1.027 -1.29 -1.619 -1.748 -1.682 -1.545 24 | 1893 -1.553 -1.625 -1.565 -1.524 -1.941 -2.356 -2.56 -2.524 -2.427 -2.167 -1.832 -1.368 25 | 1894 -1.206 -1.323 -1.448 -1.645 -1.32 -.771 -.92 -1.018 -.856 -.914 -.799 -.665 26 | 1895 -.706 -.775 -.772 -.803 -.83 -.624 -.182 .281 .453 .264 .082 .044 27 | 1896 -.16 -.208 -.065 -.161 .066 .657 .91 1.223 1.484 1.294 1.2 1.418 28 | 1897 1.543 1.351 1.15 1.085 .876 .369 -.031 -.147 -.296 -.449 -.348 -.384 29 | 1898 -.461 -.691 -.999 -.896 -.55 -.425 -.307 -.597 -1.093 -.85 -.312 -.334 30 | 1899 -.608 -.694 -.539 -.245 .03 .182 .482 .883 1.114 .966 .906 1.079 31 | 1900 1.056 1.05 1.187 1.46 1.713 1.653 1.361 .688 .314 .354 .467 .438 32 | 1901 .612 .533 .156 -.09 -.013 -.062 -.112 .074 -.121 -.383 -.576 -.425 33 | 1902 -.328 -.256 .098 .487 .955 1.519 1.917 1.591 1.395 1.706 2.039 1.713 34 | 1903 .925 .853 .978 .606 -.058 -.397 -.46 -.657 -.495 -.504 -.784 -.925 35 | 1904 -1.194 -1.251 -1.09 -.924 -.563 -.208 .3 .657 .707 .812 .743 .736 36 | 1905 .789 .893 1.124 1.261 1.129 1.457 1.954 1.869 1.641 1.482 1.221 1.232 37 | 1906 1.053 .664 .512 .477 .37 .169 -.28 -.492 -.792 -1.184 -.874 -.674 38 | 1907 -.828 -.727 -.669 -.89 -1.016 -.923 -.511 -.008 .143 .024 -.356 -.466 39 | 1908 -.447 -.465 -.644 -.988 -1.306 -1.32 -.873 -.672 -.885 -1.004 -1.064 -1.282 40 | 1909 -1.029 -.613 -.677 -.914 -.975 -1.203 -1.5 -1.673 -1.753 -1.6 -1.532 -1.498 41 | 1910 -1.385 -1.42 -1.623 -1.813 -1.911 -1.985 -2.008 -1.65 -1.488 -1.611 -1.426 -1.348 42 | 1911 -1.196 -.728 -.55 -1.047 -1.55 -1.556 -.919 -.05 .301 .502 .744 .789 43 | 1912 .913 1.004 .798 .619 .36 -.306 -.58 -.569 -.285 .003 .003 -.048 44 | 1913 -.019 .194 .17 -.548 -.944 -.376 .056 .342 .681 .531 .548 .78 45 | 1914 .805 .809 .796 .604 .451 .354 .613 1.219 1.328 .974 1.028 1.257 46 | 1915 1.308 1.173 .885 1.091 1.556 1.552 .983 .523 .21 -.317 -.688 -.589 47 | 1916 -.405 -.675 -.993 -.872 -.851 -1.053 -1.498 -2.099 -2.173 -1.773 -1.678 -1.899 48 | 1917 -1.915 -1.764 -1.606 -1.572 -1.745 -1.713 -1.127 -.967 -1.093 -1.116 -1.127 -1.202 49 | 1918 -1.328 -1.138 -.949 -.787 -.574 -.055 1.046 1.479 1.052 .969 1.051 1.158 50 | 1919 1.527 1.753 1.538 1.083 1.026 1.279 1.224 1.151 .995 .81 .5 .145 51 | 1920 .265 .564 .663 .567 .347 .248 -.197 -.342 -.235 -.3 -.054 .037 52 | 1921 .034 -.052 -.638 -1.124 -.834 -.578 -.605 -.563 -.595 -.617 -.368 -.296 53 | 1922 -.483 -.565 -.365 -.044 .029 -.223 -.492 -.401 -.37 -.506 -.465 -.691 54 | 1923 -.838 -.623 -.517 -.528 -.41 -.035 .494 .788 .895 .92 1.021 .929 55 | 1924 .664 .409 .124 -.216 -.685 -1.093 -1.244 -1.448 -1.458 -.996 -.845 -.956 56 | 1925 -.954 -.79 -.482 -.134 .048 .025 .217 .802 1.286 1.511 1.511 1.588 57 | 1926 1.407 1.35 1.602 1.71 1.33 1.06 1.067 .685 .179 -.03 -.025 -.001 58 | 1927 .014 .158 .193 -.274 -.399 -.239 -.405 -.195 .162 .206 .376 .343 59 | 1928 .312 .476 .346 -.001 -.131 .071 .346 .06 -.471 -.466 -.296 -.161 60 | 1929 -.064 -.054 -.189 -.237 .076 .418 .557 .734 .845 .677 .459 .468 61 | 1930 .506 .552 .767 1.038 1.199 1.054 1.143 1.395 1.507 1.652 1.723 1.925 62 | 1931 1.915 1.855 1.955 1.905 1.496 .877 .425 .262 .172 .179 .248 .197 63 | 1932 .187 .276 .485 .787 .964 .998 1.114 1.025 .483 -.05 -.147 -.023 64 | 1933 -.059 -.126 -.396 -.497 -.388 -.671 -.769 -.827 -1.014 -1.001 -.987 -1.093 65 | 1934 -1.251 -1.214 -1.007 -.651 -.246 -.074 -.133 -.174 .037 .114 -.084 -.141 66 | 1935 -.197 -.331 -.511 -.508 -.11 .096 -.055 .017 .322 .412 .364 .372 67 | 1936 .193 .156 .319 .44 .513 .474 .292 .249 .206 .138 .258 .361 68 | 1937 .34 .206 .218 .082 -.13 -.277 -.292 -.071 .186 .26 .157 -.081 69 | 1938 -.277 -.408 -.477 -.68 -.971 -1.035 -1.25 -1.392 -1.198 -.787 -.595 -.714 70 | 1939 -.786 -.824 -.979 -.93 -.433 .033 .269 .579 .877 .715 .313 .313 71 | 1940 .658 .921 1.163 1.419 1.441 1.419 1.352 1.119 .988 .965 .873 .967 72 | 1941 1.35 1.55 1.793 2.07 2.269 2.001 1.496 1.522 1.514 1.435 1.629 1.605 73 | 1942 1.324 1.029 .87 .798 .41 -.262 -.706 -.879 -.908 -.968 -1.089 -1.165 74 | 1943 -1.257 -1.166 -.846 -.463 -.004 .375 .504 .145 -.179 -.363 -.428 -.347 75 | 1944 -.289 -.035 .22 .258 .239 .161 .108 .029 -.257 -.273 -.256 -.425 76 | 1945 -.395 -.41 -.471 -.425 -.193 .024 -.362 -.645 -.462 -.01 .234 -.018 77 | 1946 -.213 -.247 -.223 -.158 -.214 -.193 -.072 -.043 .141 .36 .33 .212 78 | 1947 .197 .111 -.116 -.301 -.382 -.157 -.177 -.476 -.633 -.67 -.541 -.406 79 | 1948 -.063 .326 .606 .569 .259 .18 .067 -.042 -.027 -.083 -.279 -.175 80 | 1949 .066 -.063 -.252 -.133 .194 .007 -.519 -.698 -.611 -.726 -.957 -.924 81 | 1950 -.941 -1.131 -1.347 -1.377 -1.282 -1.232 -1.538 -1.797 -1.433 -1.03 -1.109 -1.378 82 | 1951 -1.264 -1.18 -1.176 -.795 -.13 .382 .782 1.116 1.233 1.04 .867 .738 83 | 1952 .613 .565 .449 .437 .434 .017 -.303 -.251 .004 .178 -.035 -.119 84 | 1953 .154 .403 .59 .69 .758 .581 .31 .447 .778 .568 .104 .138 85 | 1954 .103 -.137 -.024 .137 -.065 -.419 -.838 -1.041 -.962 -.954 -1.049 -1.155 86 | 1955 -.955 -.858 -1.147 -1.292 -1.497 -1.887 -1.749 -1.543 -1.647 -1.96 -2.081 -1.965 87 | 1956 -1.763 -1.614 -1.532 -1.329 -1.366 -1.538 -1.371 -.898 -.754 -.95 -1.076 -.947 88 | 1957 -.736 -.387 .049 .41 .792 1.371 1.628 1.551 1.436 1.152 1.157 1.312 89 | 1958 1.478 1.549 1.505 1.38 1.5 1.602 1.419 1.02 .604 .424 .373 .534 90 | 1959 .639 .747 .86 .748 .797 .864 .549 .308 .232 .142 .048 -.125 91 | 1960 -.07 .073 .106 .167 .177 .071 -.078 -.303 -.253 -.256 -.286 -.197 92 | 1961 -.011 .016 -.065 .156 .273 .193 .087 .108 .006 -.396 -.508 -.526 93 | 1962 -.894 -.891 -.434 -.436 -.975 -.894 -.408 -.408 -.534 -.59 -.616 -.3 94 | 1963 -.371 -.579 -.461 -.233 .037 .232 .508 .91 1.059 1.11 1.07 .974 95 | 1964 .933 .73 .25 -.349 -.827 -1.093 -1.277 -1.289 -1.191 -1.171 -1.059 -.895 96 | 1965 -.66 -.333 -.132 .076 .519 1.025 1.363 1.731 1.73 1.42 1.523 1.726 97 | 1966 1.682 1.526 1.213 .889 .823 .648 .357 .264 .076 -.05 .07 .023 98 | 1967 -.208 -.501 -.753 -.727 -.55 -.582 -.741 -.828 -.837 -.785 -.591 -.372 99 | 1968 -.456 -.62 -.658 -.759 -.733 -.525 -.544 -.338 .204 .619 .726 .655 100 | 1969 .755 1.04 1.027 .941 1.092 1.219 1.077 .906 .857 .831 .76 .744 101 | 1970 .675 .575 .439 .309 .09 -.501 -1.153 -1.347 -1.228 -1.1 -1.06 -1.128 102 | 1971 -1.281 -1.563 -1.873 -1.979 -1.764 -1.534 -1.304 -1.041 -1.296 -1.398 -1.102 -.986 103 | 1972 -.651 -.258 -.146 .08 .751 1.546 2.087 2.209 2.068 1.88 1.844 2.008 104 | 1973 2.023 1.756 1.303 .743 .154 -.52 -1.077 -1.363 -1.664 -1.742 -1.591 -1.768 105 | 1974 -2.024 -2.188 -2.088 -1.802 -1.241 -.821 -.976 -.858 -.543 -.815 -1.094 -1.033 106 | 1975 -.703 -.479 -.625 -.953 -1.083 -1.168 -1.408 -1.744 -2.055 -2.27 -2.19 -1.909 107 | 1976 -1.764 -1.626 -1.459 -1.289 -.777 -.007 .674 .771 .948 1.272 .982 .731 108 | 1977 .71 .651 .548 .558 .511 .421 .641 .749 .715 .831 1.01 1.112 109 | 1978 .939 .846 .895 .57 .023 -.235 -.137 .19 -.056 -.142 .39 .626 110 | 1979 .577 .402 .132 .164 .623 .637 .346 .584 .876 .818 .831 1.012 111 | 1980 1.014 .813 .832 1.167 1.249 .919 .783 .689 .411 .336 .226 .189 112 | 1981 -.087 -.337 -.128 .362 .29 .013 -.096 -.076 .082 .201 .177 .064 113 | 1982 .077 .019 .107 .44 .949 1.48 1.768 1.841 1.871 2.254 2.593 2.777 114 | 1983 3.08 3.31 3.349 3.084 2.665 2.337 1.922 1.155 .462 .105 -.099 .054 115 | 1984 .054 -.166 -.192 .096 -.052 -.402 -.416 -.365 -.214 -.102 -.359 -.66 116 | 1985 -.744 -.777 -.863 -.772 -.791 -.533 -.102 -.365 -.601 -.257 .015 -.151 117 | 1986 -.278 -.228 -.087 -.004 .141 .319 .377 .736 1.147 1.198 1.237 1.342 118 | 1987 1.38 1.529 1.883 2.171 2.162 2.069 2.2 2.343 2.054 1.824 1.575 1.49 119 | 1988 1.387 1.02 .703 .429 -.047 -.735 -1.416 -1.704 -1.783 -1.76 -1.778 -1.67 120 | 1989 -1.468 -1.33 -1.198 -1.143 -1.124 -1.027 -.951 -.637 -.35 -.389 -.33 .001 121 | 1990 .225 .462 .742 .723 .531 .422 .352 .147 .257 .454 .382 .298 122 | 1991 .407 .413 .493 .763 1.014 1.184 1.102 .553 .296 .8 1.257 1.45 123 | 1992 1.857 2.039 2.052 2.289 2.404 2.293 1.742 .723 .464 .751 .765 .808 124 | 1993 .925 1.049 1.177 1.44 1.86 1.835 1.228 .724 .795 1.186 1.158 .784 125 | 1994 .644 .422 .278 .646 1.011 .992 .885 .731 .796 1.355 1.447 1.319 126 | 1995 1.337 1.196 .935 .83 .94 .813 .483 .258 -.081 -.274 -.407 -.432 127 | 1996 -.437 -.421 -.348 -.361 -.435 -.272 -.214 -.218 -.22 -.327 -.213 -.203 128 | 1997 -.453 -.577 -.323 .529 1.61 2.602 3.12 3.181 3.168 3.214 3.201 3.022 129 | 1998 2.822 2.823 2.944 2.821 2.336 1.355 .18 -.731 -.863 -.759 -.857 -.973 130 | 1999 -1.108 -1.231 -1.121 -1.098 -1.187 -.975 -.826 -.801 -.727 -.847 -1.045 -1.182 131 | 2000 -1.255 -1.295 -1.319 -1.168 -.96 -.711 -.479 -.298 -.113 -.109 -.397 -.642 132 | 2001 -.706 -.796 -.71 -.398 -.322 -.349 -.225 -.041 -.098 -.256 -.29 -.199 133 | 2002 -.064 -.091 .045 .359 .669 .912 .667 .636 .903 1.09 1.207 1.223 134 | 2003 1.252 1.084 .953 .816 .262 -.094 .004 .083 .36 .533 .594 .646 135 | 2004 .526 .331 .118 .125 .299 .214 .271 .582 .803 .68 .69 .74 136 | 2005 .432 .28 .631 .773 .588 .737 .8 .492 .08 -.661 -.998 -.954 137 | 2006 -.438 -.424 -.527 -.575 .043 .546 .718 .77 .84 .955 1.286 .951 138 | 2007 .985 .528 .12 .02 .354 -.136 -.247 -.427 -1.175 -1.217 -1.165 -1.193 139 | 2008 -1.02 -1.388 -1.579 -.879 -.349 .164 .089 -.252 -.545 -.692 -.597 -.663 140 | 2009 -.726 -.707 -.723 -.105 .328 .779 1.06 1.073 .745 .909 1.121 1.045 141 | 2010 1.067 1.52 1.469 .99 .668 -.211 -1.099 -1.662 -1.86 -1.899 -1.49 -1.577 142 | 2011 -1.739 -1.563 -1.575 -1.399 -.202 .016 -.191 -.502 -.751 -.933 -.949 -.957 143 | 2012 -.993 -.695 -.398 .112 .769 .866 1.128 .628 .351 .081 .125 .094 144 | 2013 .096 -.08 -.037 .095 .203 -.078 -.311 -.466 -.125 .13 -.053 -.248 145 | 2014 -.275 -.266 .027 .312 1.01 1.057 .921 .961 .593 .438 .763 .558 146 | 2015 .42 .459 .631 .943 1.592 2.101 1.987 2.365 2.532 2.241 2.297 2.112 147 | 2016 2.227 2.169 1.984 2.124 1.77 1.069 .354 .186 -.091 -.379 -.212 -.121 -------------------------------------------------------------------------------- /resources/game_of_life/oscillators.txt: -------------------------------------------------------------------------------- 1 | ............................. 2 | ............................. 3 | ........................ooo.. 4 | ............................. 5 | ............................. 6 | ............................. 7 | ............................. 8 | ............................. -------------------------------------------------------------------------------- /resources/game_of_life/spaceships.txt: -------------------------------------------------------------------------------- 1 | ............................. 2 | ....o........................ 3 | ..o.o........................ 4 | ...oo........................ 5 | ............................. 6 | ............................. 7 | ............................. 8 | ............................. -------------------------------------------------------------------------------- /resources/game_of_life/still_life.txt: -------------------------------------------------------------------------------- 1 | .....................o....... 2 | ....................o.o...... 3 | .....................o....... 4 | ............................. 5 | ............................. 6 | ............................. 7 | ............................. 8 | ............................. -------------------------------------------------------------------------------- /resources/molecular_mass_calculator/periodic_table.csv: -------------------------------------------------------------------------------- 1 | H,1.008 2 | He,4.0026022 3 | Li,6.94 4 | Be,9.01218315 5 | B,10.81 6 | C,12.011 7 | N,14.007 8 | O,15.999 9 | F,18.99840316 10 | Ne,20.17976 11 | Na,22.98976928 12 | Mg,24.305 13 | Al,26.98153857 14 | Si,28.085 15 | P,30.973762 16 | S,32.06 17 | Cl,35.45 18 | Ar,39.9481 19 | K,39.09831 20 | Ca,40.0784 21 | Sc,44.9559085 22 | Ti,47.8671 23 | V,50.94151 24 | Cr,51.99616 25 | Mn,54.9380443 26 | Fe,55.8452 27 | Co,58.9331944 28 | Ni,58.69344 29 | Cu,63.5463 30 | Zn,65.382 31 | Ga,69.7231 32 | Ge,72.6308 33 | As,74.9215956 34 | Se,78.9718 35 | Br,79.904 36 | Kr,83.7982 37 | Rb,85.46783 38 | Sr,87.621 39 | Y,88.905842 40 | Zr,91.2242 41 | Nb,92.906372 42 | Mo,95.951 43 | Tc,98 44 | Ru,101.072 45 | Rh,102.905502 46 | Pd,106.421 47 | Ag,107.86822 48 | Cd,112.4144 49 | In,114.8181 50 | Sn,118.7107 51 | Sb,121.7601 52 | Te,127.603 53 | I,126.904473 54 | Xe,131.2936 55 | Cs,132.905452 56 | Ba,137.3277 57 | La,138.905477 58 | Ce,140.1161 59 | Pr,140.907662 60 | Nd,144.2423 61 | Pm,145 62 | Sm,150.362 63 | Eu,151.9641 64 | Gd,157.253 65 | Tb,158.925352 66 | Dy,162.5001 67 | Ho,164.930332 68 | Er,167.2593 69 | Tm,168.934222 70 | Yb,173.0451 71 | Lu,174.96681 72 | Hf,178.492 73 | Ta,180.947882 74 | W,183.841 75 | Re,186.2071 76 | Os,190.233 77 | Ir,192.2173 78 | Pt,195.0849 79 | Au,196.9665695 80 | Hg,200.5923 81 | Tl,204.38 82 | Pb,207.21 83 | Bi,208.980401 84 | Po,209 85 | At,210 86 | Rn,222 87 | Fr,223 88 | Ra,226 89 | Ac,227 90 | Th,232.03774 91 | Pa,231.035882 92 | U,238.028913 93 | Np,237 94 | Pu,244 95 | Am,243 96 | Cm,247 97 | Bk,247 98 | Cf,251 99 | Es,252 100 | Fm,257 101 | Md,258 102 | No,259 103 | Lr,266 104 | Db,268 105 | Sg,269 106 | Bh,270 107 | Hs,269 108 | Mt,278 109 | Ds,281 110 | Rg,282 111 | Cn,285 112 | Nh,286 113 | Fl,289 114 | Mc,290 115 | Lv,293 116 | Ts,294 117 | Og,294 118 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name="lovelace_problems", packages=find_packages()) 4 | -------------------------------------------------------------------------------- /tests/test_problems.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pkgutil 3 | import importlib 4 | import pytest 5 | import problems 6 | 7 | from types import ModuleType 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | problem_modules = pkgutil.iter_modules(problems.__path__) 12 | problem_names = [m.name for m in problem_modules] 13 | 14 | # test_case.py is not a problem module 15 | problem_names = list(filter(lambda s: "test_case" not in s, problem_names)) 16 | 17 | @pytest.mark.parametrize("problem_name", problem_names) 18 | def test_importing_problem_module(problem_name): 19 | problem_module = importlib.import_module("problems." + problem_name) 20 | assert isinstance(problem_module, ModuleType) 21 | 22 | @pytest.mark.parametrize("problem_name", problem_names) 23 | def test_function_name_exists(problem_name): 24 | problem_module = importlib.import_module("problems." + problem_name) 25 | assert isinstance(problem_module.FUNCTION_NAME, str) 26 | 27 | @pytest.mark.parametrize("problem_name", problem_names) 28 | def test_input_output_vars(problem_name): 29 | problem_module = importlib.import_module("problems." + problem_name) 30 | assert isinstance(problem_module.INPUT_VARS, list) 31 | assert isinstance(problem_module.OUTPUT_VARS, list) 32 | 33 | @pytest.mark.parametrize("problem_name", problem_names) 34 | def test_problem_parameters(problem_name): 35 | problem_module = importlib.import_module("problems." + problem_name) 36 | assert isinstance(problem_module.STATIC_RESOURCES, list) 37 | assert isinstance(problem_module.PHYSICAL_CONSTANTS, dict) 38 | assert isinstance(problem_module.ATOL, dict) 39 | assert isinstance(problem_module.RTOL, dict) 40 | 41 | @pytest.mark.parametrize("problem_name", problem_names) 42 | def test_problem_test_case_types(problem_name): 43 | problem_module = importlib.import_module("problems." + problem_name) 44 | assert len(problem_module.TestCaseType) > 0 45 | for test_case_type in problem_module.TestCaseType: 46 | assert isinstance(test_case_type.test_name, str) 47 | assert isinstance(test_case_type.multiplicity, int) 48 | 49 | ##### 50 | ##### Test each test case for each problem 51 | ##### 52 | 53 | def problem_test_cases(problem_name): 54 | problem_module = importlib.import_module("problems." + problem_name) 55 | return [_ for _ in problem_module.TestCaseType] 56 | 57 | problem_test_cases_dict = {problem_name: problem_test_cases(problem_name) for problem_name in problem_names} 58 | 59 | problem_test_case_combos = [(name, test_case) for name, test_cases in problem_test_cases_dict.items() for test_case in test_cases] 60 | 61 | @pytest.mark.parametrize("problem_name, test_case_type", problem_test_case_combos) 62 | def test_problem_test_case_generation(problem_name, test_case_type): 63 | 64 | problem_module = importlib.import_module("problems." + problem_name) 65 | assert len(problem_module.TestCaseType) > 0 66 | 67 | if len(problem_module.STATIC_RESOURCES) > 0: 68 | log.info(f"Skipping problem {problem_name} as we can't test problems with static resources yet.") 69 | return True 70 | 71 | if test_case_type.multiplicity == 0: 72 | # These are usually disabled or not yet implemented tests. 73 | log.info(f"Skipping problem {problem_name} test case type {test_case_type} with multiplicity 0.") 74 | return True 75 | 76 | test_case = problem_module.generate_test_case(test_case_type) 77 | 78 | assert test_case.test_type == test_case_type 79 | assert isinstance(test_case.input, dict) 80 | assert isinstance(test_case.output, dict) 81 | 82 | for input_name, input_value in test_case.input.items(): 83 | assert isinstance(input_name, str) 84 | assert input_name in problem_module.INPUT_VARS 85 | 86 | for output_name, output_value in test_case.output.items(): 87 | assert isinstance(output_name, str) 88 | assert output_name in problem_module.OUTPUT_VARS 89 | 90 | input_tuple = test_case.input_tuple() 91 | assert len(input_tuple) == len(problem_module.INPUT_VARS) 92 | for input_name, input_value in zip(problem_module.INPUT_VARS, input_tuple): 93 | assert test_case.input[input_name] == input_value 94 | 95 | output_tuple = test_case.output_tuple() 96 | assert len(output_tuple) == len(problem_module.OUTPUT_VARS) 97 | for output_name, output_value in zip(problem_module.OUTPUT_VARS, output_tuple): 98 | assert test_case.output[output_name] == output_value 99 | --------------------------------------------------------------------------------