├── .coveragerc ├── .gitignore ├── .mailmap ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── appveyor.yml ├── doc ├── Makefile ├── _static │ ├── RTD-advanced-conf.png │ ├── git.png │ └── logo.png ├── _templates │ ├── class.rst │ └── function.rst ├── api.rst ├── conf.py ├── index.rst ├── sphinxext │ ├── docscrape.py │ ├── docscrape_sphinx.py │ ├── github.py │ ├── math_dollar.py │ └── numpydoc.py └── theory.rst ├── examples ├── README.txt ├── model_fitting │ ├── README.txt │ ├── plot_function_fit.py │ └── plot_function_stability.py └── sg_tutorial │ ├── README.txt │ └── plot_sg_tutorial.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── scripts └── Figure1.ipynb ├── setup.py └── shablona ├── __init__.py ├── data ├── ortho.csv └── para.csv ├── due.py ├── shablona.py ├── tests ├── __init__.py └── test_shablona.py └── version.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = shablona/* 4 | include = shablona/* 5 | omit = */setup.py 6 | [report] 7 | include = shablona/* 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *~ 3 | *.DS_Store 4 | _build/ 5 | auto_examples/ 6 | gen_api/ 7 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Sample file to define mail aliases also used by git shortlog -sn to 2 | # join multiple identities 3 | Ariel Rokem arokem 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | deploy: 5 | provider: pypi 6 | user: arokem 7 | password: 8 | secure: U8Bfe8mXUSp5HYzGsyJfLuVzu+uEt6hid6AQb+HafJMHhhmoiX4cL8IH1xYaT/ZDXR02IO5bpQpjLLvKizDCXrfEgM/YTWhi1lldVh8O6cdP0gBeNfYNQz0qmbWhJFGzN+ad4VXBXrCVIPtF88mmowzRr6P0vLHW8GudSPInJKl6ofUwJDkOOFL6tqAiSLTIlwQQtvKTMqxCAQ3U+kliznpbbjFJSCjf29IQ/tUqzflbKmVdpr5nXCwVONNnydnY8fFK9Vrhl2UchFKjE5KbfoYxFs9udhlgxZMoKSDmTcvhhjwZ7HN8WPABxvY+IvrDi+CUjFDKBKZhSan0fQBfc8aMN0YVE2/TtEJXwsA9g4c3/BkcoF532mc7e2dzExQwOh/xS/iLPhn0qtf+VQn7po7iXwSMbpPIqEOYbgpaWXzgo0Pf5O5Q0pKzuXpcN4femZ1hEtTZ9f13LHuyjLPN9GAZjQmdUGbIkPFY/bbu6ssbkcgKZTABODln68LOnLDQGqpog5bYQ9Myr5mLhF3duLjnoZIcuReyQ7Ed8Z7bcNeiu7t4PAWbgm2DJ3VrUkzq24iUvGedwh3Eqr4eQnedrk2Bn3+rqVv00FjX9elmR7S3TJ4l4jPkfg8R48tDan6wGqhu3s2S97MSmgi+/gWDAEpTBHaHc5DuZ4TcJXTb3To= 9 | on: 10 | tags: true 11 | repo: uwescience/shablona 12 | 13 | 14 | python: 15 | - '3.6' 16 | 17 | 18 | install: 19 | - travis_retry pip install -r requirements-dev.txt 20 | - travis_retry pip install -r requirements.txt 21 | - travis_retry pip install -e . 22 | 23 | script: 24 | - flake8 --ignore N802,N806 `find . -name \*.py | grep -v setup.py | grep -v version.py | grep -v __init__.py | grep -v /doc/` 25 | - mkdir for_test 26 | - cd for_test 27 | - py.test --pyargs shablona --cov-report term-missing --cov=shablona 28 | 29 | after_success: 30 | - coveralls 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ariel Rokem, The University of Washington eScience Institute 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 SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | flake8: 2 | @if command -v flake8 > /dev/null; then \ 3 | echo "Running flake8"; \ 4 | flake8 flake8 --ignore N802,N806 `find . -name \*.py | grep -v setup.py | grep -v /doc/`; \ 5 | else \ 6 | echo "flake8 not found, please install it!"; \ 7 | exit 1; \ 8 | fi; 9 | @echo "flake8 passed" 10 | 11 | test: 12 | py.test --pyargs shablona --cov-report term-missing --cov=shablona 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## shablona 2 | [![Build Status](https://travis-ci.org/uwescience/shablona.svg?branch=master)](https://travis-ci.org/uwescience/shablona) 3 | 4 | Shablona is a template project for small scientific python projects. The 5 | recommendations we make here follow the standards and conventions of much of 6 | the scientific Python eco-system. Following these standards and recommendations 7 | will make it easier for others to use your code, and can make it easier for you 8 | to port your code into other projects and collaborate with other users of this 9 | eco-system. 10 | 11 | To use it as a template for your own project, click the green "use this template" 12 | button at the top of the front page of this repo. 13 | 14 | First, let me explain all the different moving parts that make up a small 15 | scientific python project, and all the elements which allow us to effectively 16 | share it with others, test it, document it, and track its evolution. 17 | 18 | ### Organization of the project 19 | 20 | The project has the following structure: 21 | 22 | shablona/ 23 | |- README.md 24 | |- shablona/ 25 | |- __init__.py 26 | |- shablona.py 27 | |- due.py 28 | |- data/ 29 | |- ... 30 | |- tests/ 31 | |- ... 32 | |- doc/ 33 | |- Makefile 34 | |- conf.py 35 | |- sphinxext/ 36 | |- ... 37 | |- _static/ 38 | |- ... 39 | |- setup.py 40 | |- .travis.yml 41 | |- .mailmap 42 | |- appveyor.yml 43 | |- LICENSE 44 | |- Makefile 45 | |- ipynb/ 46 | |- ... 47 | 48 | 49 | In the following sections we will examine these elements one by one. First, 50 | let's consider the core of the project. This is the code inside of 51 | `shablona/shablona.py`. The code provided in this file is intentionally rather 52 | simple. It implements some simple curve-fitting to data from a psychophysical 53 | experiment. It's not too important to know what it does, but if you are really 54 | interested, you can read all about it 55 | [here](http://arokem.github.io/2014-08-12-learn-optimization.html). 56 | 57 | ### Module code 58 | 59 | We place the module code in a file called `shablona.py` in directory called 60 | `shablona`. This structure is a bit confusing at first, but it is a simple way 61 | to create a structure where when we type `import shablona as sb` in an 62 | interactive Python session, the classes and functions defined inside of the 63 | `shablona.py` file are available in the `sb` namespace. For this to work, we 64 | need to also create a file in `__init__.py` which contains code that imports 65 | everything in that file into the namespace of the project: 66 | 67 | from .shablona import * 68 | 69 | In the module code, we follow the convention that all functions are either 70 | imported from other places, or are defined in lines that precede the lines that 71 | use that function. This helps readability of the code, because you know that if 72 | you see some name, the definition of that name will appear earlier in the file, 73 | either as a function/variable definition, or as an import from some other module 74 | or package. 75 | 76 | In the case of the shablona module, the main classes defined at the bottom of 77 | the file make use of some of the functions defined in preceding lines. 78 | 79 | Remember that code will be probably be read more times than it will be written. 80 | Make it easy to read (for others, but also for yourself when you come back to 81 | it), by following a consistent formatting style. We strongly recommend 82 | following the 83 | [PEP8 code formatting standard](https://www.python.org/dev/peps/pep-0008/), and 84 | we enforce this by running a code-linter called 85 | [`flake8`](http://flake8.pycqa.org/en/latest/), which automatically checks the 86 | code and reports any violations of the PEP8 standard (and checks for other 87 | general code hygiene issues), see below. 88 | 89 | ### Project Data 90 | 91 | In this case, the project data is rather small, and recorded in csv 92 | files. Thus, it can be stored alongside the module code. Even if the 93 | data that you are analyzing is too large, and cannot be effectively 94 | tracked with github, you might still want to store some data for 95 | testing purposes. 96 | 97 | Either way, you can create a `shablona/data` folder in which you can 98 | organize the data. As you can see in the test scripts, and in the 99 | analysis scripts, this provides a standard file-system location for 100 | the data at: 101 | 102 | import os.path as op 103 | import shablona as sb 104 | data_path = op.join(sb.__path__[0], 'data') 105 | 106 | 107 | ### Testing 108 | 109 | Most scientists who write software constantly test their code. That is, if you 110 | are a scientist writing software, I am sure that you have tried to see how well 111 | your code works by running every new function you write, examining the inputs 112 | and the outputs of the function, to see if the code runs properly (without 113 | error), and to see whether the results make sense. 114 | 115 | Automated code testing takes this informal practice, makes it formal, and 116 | automates it, so that you can make sure that your code does what it is supposed 117 | to do, even as you go about making changes around it. 118 | 119 | Most scientists writing code are not really in a position to write a complete 120 | [specification](http://www.wired.com/2013/01/code-bugs-programming-why-we-need-specs/) 121 | of their software, because when they start writing their code they don't quite 122 | know what they will discover in their data, and these chance discoveries might 123 | affect how the software evolves. Nor do most scientists have the inclination to 124 | write complete specs - scientific code often needs to be good enough to cover 125 | our use-case, and not any possible use-case. Testing the code serves as a way to 126 | provide a reader of the code with very rough specification, in the sense that it 127 | at least specifies certain input/output relationships that will certainly hold 128 | in your code. 129 | 130 | We recommend using the ['pytest'](http://pytest.org/latest/) library for 131 | testing. The `py.test` application traverses the directory tree in which it is 132 | issued, looking for files with the names that match the pattern `test_*.py` 133 | (typically, something like our `shablona/tests/test_shablona.py`). Within each 134 | of these files, it looks for functions with names that match the pattern 135 | `test_*`. Typically each function in the module would have a corresponding test 136 | (e.g. `test_transform_data`). This is sometimes called 'unit testing', because 137 | it independently tests each atomic unit in the software. Other tests might run a 138 | more elaborate sequence of functions ('end-to-end testing' if you run through 139 | the entire analysis), and check that particular values in the code evaluate to 140 | the same values over time. This is sometimes called 'regression testing'. We 141 | have one such test in `shablona/tests/test_shablona.py` called 142 | `test_params_regression`. Regressions in the code are often canaries in the coal 143 | mine, telling you that you need to examine changes in your software 144 | dependencies, the platform on which you are running your software, etc. 145 | 146 | Test functions should contain assertion statements that check certain relations 147 | in the code. Most typically, they will test for equality between an explicit 148 | calculation of some kind and a return of some function. For example, in the 149 | `test_cumgauss` function, we test that our implmentation of the cumulative 150 | Gaussian function evaluates at the mean minus 1 standard deviation to 151 | approximately (1-0.68)/2, which is the theoretical value this calculation should 152 | have. We recommend using functions from the `numpy.testing` module (which we 153 | import as `npt`) to assert certain relations on arrays and floating point 154 | numbers. This is because `npt` contains functions that are specialized for 155 | handling `numpy` arrays, and they allow to specify the tolerance of the 156 | comparison through the `decimal` key-word argument. 157 | 158 | To run the tests on the command line, change your present working directory to 159 | the top-level directory of the repository (e.g. `/Users/arokem/code/shablona`), 160 | and type: 161 | 162 | py.test shablona 163 | 164 | This will exercise all of the tests in your code directory. If a test fails, you 165 | will see a message such as: 166 | 167 | 168 | shablona/tests/test_shablona.py .F... 169 | 170 | =================================== FAILURES =================================== 171 | ________________________________ test_cum_gauss ________________________________ 172 | 173 | def test_cum_gauss(): 174 | sigma = 1 175 | mu = 0 176 | x = np.linspace(-1, 1, 12) 177 | y = sb.cumgauss(x, mu, sigma) 178 | # A basic test that the input and output have the same shape: 179 | npt.assert_equal(y.shape, x.shape) 180 | # The function evaluated over items symmetrical about mu should be 181 | # symmetrical relative to 0 and 1: 182 | npt.assert_equal(y[0], 1 - y[-1]) 183 | # Approximately 68% of the Gaussian distribution is in mu +/- sigma, so 184 | # the value of the cumulative Gaussian at mu - sigma should be 185 | # approximately equal to (1 - 0.68/2). Note the low precision! 186 | > npt.assert_almost_equal(y[0], (1 - 0.68) / 2, decimal=3) 187 | E AssertionError: 188 | E Arrays are not almost equal to 3 decimals 189 | E ACTUAL: 0.15865525393145707 190 | E DESIRED: 0.15999999999999998 191 | 192 | shablona/tests/test_shablona.py:49: AssertionError 193 | ====================== 1 failed, 4 passed in 0.82 seconds ====================== 194 | 195 | This indicates to you that a test has failed. In this case, the calculation is 196 | accurate up to 2 decimal places, but not beyond, so the `decimal` key-word 197 | argument needs to be adjusted (or the calculation needs to be made more 198 | accurate). 199 | 200 | As your code grows and becomes more complicated, you might develop new features 201 | that interact with your old features in all kinds of unexpected and surprising 202 | ways. As you develop new features of your code, keep running the tests, to make 203 | sure that you haven't broken the old features. Keep writing new tests for your 204 | new code, and recording these tests in your testing scripts. That way, you can 205 | be confident that even as the software grows, it still keeps doing correctly at 206 | least the few things that are codified in the tests. 207 | 208 | We have also provided a `Makefile` that allows you to run the tests with more 209 | verbose and informative output from the top-level directory, by issuing the 210 | following from the command line: 211 | 212 | make test 213 | 214 | ### Styling 215 | 216 | It is a good idea to follow the PEP8 standard for code formatting. Common code 217 | formatting makes code more readable, and using tools such as `flake8` (which 218 | combines the tools `pep8` and `pyflakes`) can help make your code more readable, 219 | avoid extraneous imports and lines of code, and overall keep a clean project 220 | code-base. 221 | 222 | Some projects include `flake8` inside their automated tests, so that every pull 223 | request is examined for code cleanliness. 224 | 225 | In this project, we have run `flake8` most (but not all) files, on 226 | most (but not all) checks: 227 | 228 | ``` 229 | flake8 --ignore N802,N806 `find . -name *.py | grep -v setup.py | grep -v /doc/` 230 | ``` 231 | 232 | This means, check all .py files, but exclude setup.py and everything in 233 | directories named "doc". Do all checks except N802 and N806, which enforce 234 | lowercase-only names for variables and functions. 235 | 236 | The `Makefile` contains an instruction for running this command as well: 237 | 238 | make flake8 239 | 240 | ### Documentation 241 | 242 | Documenting your software is a good idea. Not only as a way to communicate to 243 | others about how to use the software, but also as a way of reminding yourself 244 | what the issues are that you faced, and how you dealt with them, in a few 245 | months/years, when you return to look at the code. 246 | 247 | The first step in this direction is to document every function in your module 248 | code. We recommend following the [numpy docstring 249 | standard](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt), 250 | which specifies in detail the inputs/outputs of every function, and specifies 251 | how to document additional details, such as references to scientific articles, 252 | notes about the mathematics behind the implementation, etc. 253 | 254 | This standard also plays well with a system that allows you to create more 255 | comprehensive documentation of your project. Writing such documentation allows 256 | you to provide more elaborate explanations of the decisions you made when you 257 | were developing the software, as well as provide some examples of usage, 258 | explanations of the relevant scientific concepts, and references to the relevant 259 | literature. 260 | 261 | To document `shablona` we use the [sphinx documentation 262 | system](http://sphinx-doc.org/). You can follow the instructions on the sphinx 263 | website, and the example [here](http://matplotlib.org/sampledoc/) to set up the 264 | system, but we have also already initialized and commited a skeleton 265 | documentation system in the `docs` directory, that you can build upon. 266 | 267 | Sphinx uses a `Makefile` to build different outputs of your documentation. For 268 | example, if you want to generate the HTML rendering of the documentation (web 269 | pages that you can upload to a website to explain the software), you will type: 270 | 271 | make html 272 | 273 | This will generate a set of static webpages in the `doc/_build/html`, which you 274 | can then upload to a website of your choice. 275 | 276 | Alternatively, [readthedocs.org](https://readthedocs.org) (careful, 277 | *not* readthedocs.**com**) is a service that will run sphinx for you, 278 | and upload the documentation to their website. To use this service, 279 | you will need to register with RTD. After you have done that, you will 280 | need to "import your project" from your github account, through the 281 | RTD web interface. To make things run smoothly, you also will need to 282 | go to the "admin" panel of the project on RTD, and navigate into the 283 | "advanced settings" so that you can tell it that your Python 284 | configuration file is in `doc/conf.py`: 285 | 286 | ![RTD conf](https://github.com/uwescience/shablona/blob/master/doc/_static/RTD-advanced-conf.png) 287 | 288 | http://shablona.readthedocs.org/en/latest/ 289 | 290 | 291 | ### Installation 292 | 293 | For installation and distribution we will use the python standard 294 | library `setuptools` module. This module uses a `setup.py` file to 295 | figure out how to install your software on a particular system. For a 296 | small project such as this one, managing installation of the software 297 | modules and the data is rather simple. 298 | 299 | A `shablona/version.py` contains all of the information needed for the 300 | installation and for setting up the [PyPI 301 | page](https://pypi.python.org/pypi/shablona) for the software. This 302 | also makes it possible to install your software with using `pip` and 303 | `easy_install`, which are package managers for Python software. The 304 | `setup.py` file reads this information from there and passes it to the 305 | `setup` function which takes care of the rest. 306 | 307 | Much more information on packaging Python software can be found in the 308 | [Hitchhiker's guide to 309 | packaging](https://the-hitchhikers-guide-to-packaging.readthedocs.org). 310 | 311 | 312 | ### Continuous integration 313 | 314 | Travis-CI is a system that can be used to automatically test every revision of 315 | your code directly from github, including testing of github pull requests, 316 | before they are merged into the `master` branch. This provides you with 317 | information needed in order to evaluate contributions made by others. It also 318 | serves as a source of information for others interested in using or contributing 319 | to your project about the degree of test coverage of your project. 320 | 321 | You will need a .travis.yml file in your repo. This file contains the 322 | configuration of your testing environment. This includes the different 323 | environments in which you will test the source code (for example, we test 324 | `shablona` against Python 2.7, Python 3.3 and Python 3.4). It includes steps 325 | that need to be taken before installation of the software. For example, 326 | installation of the software dependencies. For `shablona`, we use the 327 | [`Miniconda`](http://conda.pydata.org/miniconda.html) software distribution (not 328 | to be confused with [`Anaconda`](https://store.continuum.io/cshop/anaconda/), 329 | though they are similar and both produced by Continuum). 330 | 331 | For details on setting up Travis-CI with github, see Travis-CI's 332 | [getting started 333 | page](https://docs.travis-ci.com/user/getting-started/#To-get-started-with-Travis-CI%3A). To 334 | summarize: 335 | 336 | First, go to the Travis-CI [website](https://travis-ci.org/) and get a 337 | Travis user account, linked to your github user account. 338 | 339 | You will need to set up your github repo to talk to Travis (More explanation + 340 | pictures will come here). 341 | 342 | You will need to go back to travis-ci, and flip on the switch on that side as 343 | well. 344 | 345 | The travis output will also report to you about test coverage, if you set it up 346 | that way. 347 | 348 | You will start getting emails telling you the state of the testing suite on 349 | every pull request for the software, and also when you break the test suite on 350 | the `master` branch. That way, you can be pretty sure that the `master` is 351 | working (or at least know when it isn't...). 352 | 353 | You can also continuously test your code on a Windows system. This is done on 354 | another CI system called [Appveyor](http://www.appveyor.com/). In prinicple, it 355 | does something that is very similar to what Travis does: downloads your code, 356 | installs it on a Windows machine, with various versions of python, and runs the 357 | tests. Appveyor is controlled through another configuration file: the 358 | `appveyor.yml`. In addition to committing this file into the repository, you 359 | will need to activate Appveyor for your project. This is done by signing into 360 | the Appveyor interface with your Github account, clicking on the "projects" tab 361 | at the top of the page, then clicking on the "+" sign for "New project" and 362 | selecting the project you would like to add from the menu that appears (you 363 | might need to give Appveyor the permission to see projects in your Github 364 | account). 365 | 366 | ### Distribution 367 | 368 | The main venue for distribution of Python software is the [Python 369 | Package Index](https://pypi.python.org/), or PyPI, also lovingly known 370 | as "the cheese-shop". 371 | 372 | To distribute your software on PyPI, you will need to create a user account on 373 | [PyPI](http://python-packaging-user-guide.readthedocs.org/en/latest/distributing/#register-your-project). 374 | It is recommended that you upload your software using 375 | [twine](http://python-packaging-user-guide.readthedocs.org/en/latest/distributing/#upload-your-distributions). 376 | 377 | Using Travis, you can automatically upload your software to PyPI, 378 | every time you push a tag of your software to github. The instructions 379 | on setting this up can be found 380 | [here](http://docs.travis-ci.com/user/deployment/pypi/). You will need 381 | to install the travis command-line interface 382 | 383 | ### Licensing 384 | 385 | License your code! A repository like this without a license maintains 386 | copyright to the author, but does not provide others with any 387 | conditions under which they can use the software. In this case, we use 388 | the MIT license. You can read the conditions of the license in the 389 | `LICENSE` file. As you can see, this is not an Apple software license 390 | agreement (has anyone ever actually tried to read one of those?). It's 391 | actually all quite simple, and boils down to "You can do whatever you 392 | want with my software, but I take no responsibility for what you do 393 | with my software" 394 | 395 | For more details on what you need to think about when considering 396 | choosing a license, see this 397 | [article](http://www.astrobetter.com/blog/2014/03/10/the-whys-and-hows-of-licensing-scientific-code/)! 398 | 399 | ### Getting cited 400 | 401 | When others use your code in their research, they should probably cite you. To 402 | make their life easier, we use [duecredit](http://www.duecredit.org). This is a software 403 | library that allows you to annotate your code with the correct way to cite it. 404 | To enable `duecredit`, we have added a file `due.py` into the main directory. 405 | This file does not need to change at all (though you might want to occasionally 406 | update it from duecredit itself. It's 407 | [here](https://github.com/duecredit/duecredit/blob/master/duecredit/stub.py), 408 | under the name `stub.py`). 409 | 410 | In addition, you will want to provide a digital object identifier (DOI) to the 411 | article you want people to cite. 412 | 413 | To get a DOI, use the instructions in [this page](https://guides.github.com/activities/citable-code/) 414 | 415 | Another way to get your software cited is by writing a paper. There are several 416 | [journals that publish papers about software](https://www.software.ac.uk/resources/guides/which-journals-should-i-publish-my-software). 417 | 418 | ### Scripts 419 | 420 | A scripts directory can be used as a place to experiment with your 421 | module code, and as a place to produce scripts that contain a 422 | narrative structure, demonstrating the use of the code, or producing 423 | scientific results from your code and your data and telling a story 424 | with these elements. 425 | 426 | For example, this repository contains an [IPython notebook] that reads 427 | in some data, and creates a figure. Maybe this is *Figure 1* from some 428 | future article? You can see this notebook fully rendered 429 | [here](https://github.com/uwescience/shablona/blob/master/scripts/Figure1.ipynb). 430 | 431 | 432 | ### Git Configuration 433 | 434 | Currently there are two files in the repository which help working 435 | with this repository, and which you could extend further: 436 | 437 | - `.gitignore` -- specifies intentionally untracked files (such as 438 | compiled `*.pyc` files), which should not typically be committed to 439 | git (see `man gitignore`) 440 | - `.mailmap` -- if any of the contributors used multiple names/email 441 | addresses or their git commit identity is just an alias, you could 442 | specify the ultimate name/email(s) for each contributor, so such 443 | commands as `git shortlog -sn` could take them into account (see 444 | `git shortlog --help`) 445 | 446 | 447 | ### Using `shablona` as a template 448 | 449 | Let's assume that you want to create a small scientific Python project 450 | called `smallish`. Maybe you already have some code that you are 451 | interested in plugging into the module file, and some ideas about what 452 | the tests might look like. 453 | 454 | To use this repository as a template, click the green "use this template" 455 | button on the front page of the "shablona" repository. 456 | 457 | In "Repository name" enter the name of your project. For example, enter 458 | `smallish` here. After that, you can hit the "Create repository from template" 459 | button. 460 | 461 | You should then be able to clone the new repo into your machine. You will want 462 | to change the names of the files. For example, you will want to move 463 | `shablona/shablona.py` to be called `smallish/smallish.py` 464 | 465 | git mv shablona smallish 466 | git mv smallish/shablona.py smallish/smallish.py 467 | git mv smallish/tests/test_shablona.py smallish/tests/test_smallish.py 468 | 469 | Make a commit recording these changes. Something like: 470 | 471 | git commit -a -m"Moved names from `shablona` to `smallish`" 472 | 473 | You will probably want to remove all the example data: 474 | 475 | git rm smallish/data/* 476 | git commit -a -m"Removed example `shablona` data" 477 | 478 | Possibly, you will want to add some of your own data in there. 479 | 480 | You will want to edit a few more places that still have `shablona` in them. Type 481 | the following to see where all these files are: 482 | 483 | git grep shablona 484 | 485 | You can replace `shablona` for `smallish` quickly with: 486 | 487 | git grep -l 'shablona' | xargs sed -i 's/shablona/smallish/g' 488 | 489 | This very file (README.md) should be edited to reflect what your project is 490 | about. 491 | 492 | Other places that contain this name include the `doc/conf.py` file, which 493 | configures the sphinx documentation, as well as the `doc/Makefile` file (edit 494 | carefully!), and the `doc/index.rst` file. 495 | 496 | The `.coveragerc` file contains a few mentions of that name, as well as the 497 | `.travis.yml` file. This one will also have to be edited to reflect your PyPI 498 | credentials (see [above](### Distribution)). 499 | 500 | Edit all the mentions of `shablona` in the `shablona/__init__.py` file, and in 501 | the `shablona/version.py` file as well. 502 | 503 | Finally, you will probably want to change the copyright holder in the `LICENSE` 504 | file to be you. You can also replace the text of that file, if it doesn't match 505 | your needs. 506 | 507 | At this point, make another commit, and continue to develop your own code based 508 | on this template. 509 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: false 2 | 3 | environment: 4 | matrix: 5 | - PYTHON: "C:\\Python35" 6 | PYTHON_VERSION: "3.5.1" 7 | PYTHON_ARCH: "32" 8 | MINICONDA: C:\Miniconda35 9 | 10 | init: 11 | - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% %MINICONDA%" 12 | 13 | install: 14 | - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" 15 | - conda config --set always_yes yes --set changeps1 no 16 | - conda update -q conda 17 | - conda info -a 18 | - "conda create -q -n test-environment python=%PYTHON_VERSION% numpy scipy matplotlib pytest pytest-cov pandas" 19 | - activate test-environment 20 | - pip install coverage 21 | - python setup.py install 22 | 23 | test_script: 24 | - mkdir for_test 25 | - cd for_test 26 | - py.test --pyargs shablona --cov-report term-missing --cov=shablona 27 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | 50 | clean: 51 | rm -rf $(BUILDDIR)/* 52 | rm -rf reference/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/shablona.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/shablona.qhc" 93 | 94 | devhelp: 95 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 96 | @echo 97 | @echo "Build finished." 98 | @echo "To view the help file:" 99 | @echo "# mkdir -p $$HOME/.local/share/devhelp/shablona" 100 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/shablona" 101 | @echo "# devhelp" 102 | 103 | epub: 104 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 105 | @echo 106 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 107 | 108 | latex: 109 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 110 | @echo 111 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 112 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 113 | "(use \`make latexpdf' here to do that automatically)." 114 | 115 | latexpdf: 116 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 117 | @echo "Running LaTeX files through pdflatex..." 118 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 119 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 120 | 121 | latexpdfja: 122 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 123 | @echo "Running LaTeX files through platex and dvipdfmx..." 124 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 125 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 126 | 127 | text: 128 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 129 | @echo 130 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 131 | 132 | man: 133 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 134 | @echo 135 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 136 | 137 | texinfo: 138 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 139 | @echo 140 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 141 | @echo "Run \`make' in that directory to run these through makeinfo" \ 142 | "(use \`make info' here to do that automatically)." 143 | 144 | info: 145 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 146 | @echo "Running Texinfo files through makeinfo..." 147 | make -C $(BUILDDIR)/texinfo info 148 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 149 | 150 | gettext: 151 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 152 | @echo 153 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 154 | 155 | changes: 156 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 157 | @echo 158 | @echo "The overview file is in $(BUILDDIR)/changes." 159 | 160 | linkcheck: 161 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 162 | @echo 163 | @echo "Link check complete; look for any errors in the above output " \ 164 | "or in $(BUILDDIR)/linkcheck/output.txt." 165 | 166 | doctest: 167 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 168 | @echo "Testing of doctests in the sources finished, look at the " \ 169 | "results in $(BUILDDIR)/doctest/output.txt." 170 | 171 | xml: 172 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 173 | @echo 174 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 175 | 176 | pseudoxml: 177 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 178 | @echo 179 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 180 | 181 | show: 182 | @python -c "import webbrowser; webbrowser.open_new_tab('file://$(PWD)/_build/html/index.html')" 183 | -------------------------------------------------------------------------------- /doc/_static/RTD-advanced-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwescience/shablona/ab082ab54c933eec2f4a49a9c80c99f31532ac17/doc/_static/RTD-advanced-conf.png -------------------------------------------------------------------------------- /doc/_static/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwescience/shablona/ab082ab54c933eec2f4a49a9c80c99f31532ac17/doc/_static/git.png -------------------------------------------------------------------------------- /doc/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwescience/shablona/ab082ab54c933eec2f4a49a9c80c99f31532ac17/doc/_static/logo.png -------------------------------------------------------------------------------- /doc/_templates/class.rst: -------------------------------------------------------------------------------- 1 | {{ fullname }} 2 | {{ underline }} 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autoclass:: {{ objname }} 7 | :special-members: __contains__,__getitem__,__iter__,__len__,__add__,__sub__,__mul__,__div__,__neg__,__hash__ 8 | 9 | .. include:: {{module}}.{{objname}}.examples 10 | 11 | .. raw:: html 12 | 13 |
-------------------------------------------------------------------------------- /doc/_templates/function.rst: -------------------------------------------------------------------------------- 1 | {{ fullname }} 2 | {{ underline }} 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autofunction:: {{ objname }} 7 | 8 | .. include:: {{module}}.{{objname}}.examples 9 | 10 | .. raw:: html 11 | 12 |
13 | -------------------------------------------------------------------------------- /doc/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | 5 | Classes 6 | ------- 7 | 8 | .. currentmodule:: shablona 9 | 10 | .. autosummary:: 11 | :template: class.rst 12 | :toctree: gen_api 13 | 14 | Model 15 | 16 | 17 | Functions 18 | --------- 19 | 20 | .. autosummary:: 21 | :template: function.rst 22 | :toctree: gen_api 23 | 24 | transform_data 25 | cumgauss 26 | opt_err_func 27 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # shablona documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Apr 14 10:29:06 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # General information about the project. 20 | project = 'shablona' 21 | copyright = '2015, Ariel Rokem' 22 | 23 | currentdir = os.path.abspath(os.path.dirname(__file__)) 24 | ver_file = os.path.join(currentdir, '..', project, 'version.py') 25 | with open(ver_file) as f: 26 | exec(f.read()) 27 | source_version = __version__ 28 | 29 | currentdir = os.path.abspath(os.path.dirname(__file__)) 30 | sys.path.append(os.path.join(currentdir, 'tools')) 31 | 32 | # If extensions (or modules to document with autodoc) are in another directory, 33 | # add these directories to sys.path here. If the directory is relative to the 34 | # documentation root, use os.path.abspath to make it absolute, like shown here. 35 | sys.path.insert(0, os.path.abspath('../')) 36 | 37 | # -- General configuration ------------------------------------------------ 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | needs_sphinx = '1.0' # numpydoc requires sphinc >= 1.0 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | sys.path.append(os.path.abspath('sphinxext')) 46 | 47 | extensions = ['sphinx.ext.autodoc', 48 | 'sphinx.ext.doctest', 49 | 'sphinx.ext.intersphinx', 50 | 'sphinx.ext.todo', 51 | 'sphinx.ext.coverage', 52 | 'sphinx.ext.ifconfig', 53 | 'sphinx.ext.autosummary', 54 | 'sphinx.ext.mathjax', 55 | 'math_dollar', # has to go before numpydoc 56 | 'numpydoc', 57 | 'github', 58 | 'sphinx_gallery.gen_gallery'] 59 | 60 | # Add any paths that contain templates here, relative to this directory. 61 | templates_path = ['_templates'] 62 | 63 | # The suffix of source filenames. 64 | source_suffix = '.rst' 65 | 66 | # The encoding of source files. 67 | # source_encoding = 'utf-8-sig' 68 | 69 | # The master toctree document. 70 | master_doc = 'index' 71 | 72 | # --- Sphinx Gallery --- 73 | sphinx_gallery_conf = { 74 | # path to your examples scripts 75 | 'examples_dirs': '../examples', 76 | # path where to save gallery generated examples 77 | 'gallery_dirs': 'auto_examples', 78 | # To auto-generate example sections in the API 79 | 'doc_module': ('shablona',), 80 | # Auto-generated mini-galleries go here 81 | 'backreferences_dir': 'gen_api' 82 | } 83 | 84 | # Automatically generate stub pages for API 85 | autosummary_generate = True 86 | autodoc_default_flags = ['members', 'inherited-members'] 87 | 88 | # The version info for the project you're documenting, acts as replacement for 89 | # |version| and |release|, also used in various other places throughout the 90 | # built documents. 91 | # 92 | # The short X.Y version. 93 | version = '0.1' 94 | # The full version, including alpha/beta/rc tags. 95 | release = '0.1' 96 | 97 | # The language for content autogenerated by Sphinx. Refer to documentation 98 | # for a list of supported languages. 99 | #language = None 100 | 101 | # There are two options for replacing |today|: either, you set today to some 102 | # non-false value, then it is used: 103 | #today = '' 104 | # Else, today_fmt is used as the format for a strftime call. 105 | #today_fmt = '%B %d, %Y' 106 | 107 | # List of patterns, relative to source directory, that match files and 108 | # directories to ignore when looking for source files. 109 | exclude_patterns = ['_build'] 110 | 111 | # The reST default role (used for this markup: `text`) to use for all 112 | # documents. 113 | #default_role = None 114 | 115 | # If true, '()' will be appended to :func: etc. cross-reference text. 116 | #add_function_parentheses = True 117 | 118 | # If true, the current module name will be prepended to all description 119 | # unit titles (such as .. function::). 120 | #add_module_names = True 121 | 122 | # If true, sectionauthor and moduleauthor directives will be shown in the 123 | # output. They are ignored by default. 124 | #show_authors = False 125 | 126 | # The name of the Pygments (syntax highlighting) style to use. 127 | pygments_style = 'sphinx' 128 | 129 | # A list of ignored prefixes for module index sorting. 130 | #modindex_common_prefix = [] 131 | 132 | # If true, keep warnings as "system message" paragraphs in the built documents. 133 | #keep_warnings = False 134 | 135 | 136 | # -- Options for HTML output ---------------------------------------------- 137 | 138 | # The theme to use for HTML and HTML Help pages. See the documentation for 139 | # a list of builtin themes. 140 | html_theme = 'alabaster' 141 | 142 | # Theme options are theme-specific and customize the look and feel of a theme 143 | # further. For a list of options available for each theme, see the 144 | # documentation. 145 | #html_theme_options = {} 146 | 147 | # Add any paths that contain custom themes here, relative to this directory. 148 | #html_theme_path = [] 149 | 150 | # The name for this set of Sphinx documents. If None, it defaults to 151 | # " v documentation". 152 | #html_title = None 153 | 154 | # A shorter title for the navigation bar. Default is the same as html_title. 155 | #html_short_title = None 156 | 157 | # The name of an image file (relative to this directory) to place at the top 158 | # of the sidebar. 159 | html_logo = '_static/logo.png' 160 | 161 | # The name of an image file (within the static path) to use as favicon of the 162 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 163 | # pixels large. 164 | #html_favicon = None 165 | 166 | # Add any paths that contain custom static files (such as style sheets) here, 167 | # relative to this directory. They are copied after the builtin static files, 168 | # so a file named "default.css" will overwrite the builtin "default.css". 169 | html_static_path = ['_static'] 170 | 171 | # Add any extra paths that contain custom files (such as robots.txt or 172 | # .htaccess) here, relative to this directory. These files are copied 173 | # directly to the root of the documentation. 174 | #html_extra_path = [] 175 | 176 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 177 | # using the given strftime format. 178 | #html_last_updated_fmt = '%b %d, %Y' 179 | 180 | # If true, SmartyPants will be used to convert quotes and dashes to 181 | # typographically correct entities. 182 | #html_use_smartypants = True 183 | 184 | # Custom sidebar templates, maps document names to template names. 185 | html_sidebars = {'**': ['localtoc.html', 'searchbox.html']} 186 | 187 | # Additional templates that should be rendered to pages, maps page names to 188 | # template names. 189 | #html_additional_pages = {} 190 | 191 | # If false, no module index is generated. 192 | #html_domain_indices = True 193 | html_domain_indices = False 194 | 195 | # If false, no index is generated. 196 | #html_use_index = True 197 | 198 | # If true, the index is split into individual pages for each letter. 199 | #html_split_index = False 200 | 201 | # If true, links to the reST sources are added to the pages. 202 | #html_show_sourcelink = True 203 | 204 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 205 | #html_show_sphinx = True 206 | 207 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 208 | #html_show_copyright = True 209 | 210 | # If true, an OpenSearch description file will be output, and all pages will 211 | # contain a tag referring to it. The value of this option must be the 212 | # base URL from which the finished HTML is served. 213 | #html_use_opensearch = '' 214 | 215 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 216 | #html_file_suffix = None 217 | 218 | # Output file base name for HTML help builder. 219 | htmlhelp_basename = 'shablonadoc' 220 | 221 | 222 | # -- Options for LaTeX output --------------------------------------------- 223 | 224 | latex_elements = { 225 | # The paper size ('letterpaper' or 'a4paper'). 226 | #'papersize': 'letterpaper', 227 | 228 | # The font size ('10pt', '11pt' or '12pt'). 229 | #'pointsize': '10pt', 230 | 231 | # Additional stuff for the LaTeX preamble. 232 | #'preamble': '', 233 | } 234 | 235 | # Grouping the document tree into LaTeX files. List of tuples 236 | # (source start file, target name, title, 237 | # author, documentclass [howto, manual, or own class]). 238 | latex_documents = [ 239 | ('index', 'shablona.tex', 'shablona Documentation', 240 | 'Ariel Rokem', 'manual'), 241 | ] 242 | 243 | # The name of an image file (relative to this directory) to place at the top of 244 | # the title page. 245 | #latex_logo = None 246 | 247 | # For "manual" documents, if this is true, then toplevel headings are parts, 248 | # not chapters. 249 | #latex_use_parts = False 250 | 251 | # If true, show page references after internal links. 252 | #latex_show_pagerefs = False 253 | 254 | # If true, show URL addresses after external links. 255 | #latex_show_urls = False 256 | 257 | # Documents to append as an appendix to all manuals. 258 | #latex_appendices = [] 259 | 260 | # If false, no module index is generated. 261 | #latex_domain_indices = True 262 | 263 | 264 | # -- Options for manual page output --------------------------------------- 265 | 266 | # One entry per manual page. List of tuples 267 | # (source start file, name, description, authors, manual section). 268 | man_pages = [ 269 | ('index', 'shablona', 'shablona Documentation', 270 | ['Ariel Rokem'], 1) 271 | ] 272 | 273 | # If true, show URL addresses after external links. 274 | #man_show_urls = False 275 | 276 | 277 | # -- Options for Texinfo output ------------------------------------------- 278 | 279 | # Grouping the document tree into Texinfo files. List of tuples 280 | # (source start file, target name, title, author, 281 | # dir menu entry, description, category) 282 | texinfo_documents = [ 283 | ('index', 'shablona', 'shablona Documentation', 284 | 'Ariel Rokem', 'shablona', 'One line description of project.', 285 | 'Miscellaneous'), 286 | ] 287 | 288 | # Documents to append as an appendix to all manuals. 289 | #texinfo_appendices = [] 290 | 291 | # If false, no module index is generated. 292 | texinfo_domain_indices = False 293 | 294 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 295 | #texinfo_show_urls = 'footnote' 296 | 297 | # If true, do not generate a @detailmenu in the "Top" node's menu. 298 | #texinfo_no_detailmenu = False 299 | 300 | # Example configuration for intersphinx: refer to the Python standard library. 301 | intersphinx_mapping = {'http://docs.python.org/': None} 302 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. shablona documentation master file, created by sphinx-quickstart on Tue Apr 14 10:29:06 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. 2 | 3 | Welcome to shablona's documentation! 4 | ==================================== 5 | 6 | `Shablona` is a template for a small scientific Python project. 7 | 8 | To see how to use it, please refer to the `README file 9 | `_ in the Github repository. 10 | 11 | This is an example of documentation of the software, using sphinx_. 12 | 13 | .. _sphinx: http://sphinx-doc.org/ 14 | 15 | 16 | Contents: 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | theory 22 | auto_examples/index 23 | api 24 | -------------------------------------------------------------------------------- /doc/sphinxext/docscrape.py: -------------------------------------------------------------------------------- 1 | """Extract reference documentation from the NumPy source tree. 2 | 3 | """ 4 | from __future__ import division, absolute_import, print_function 5 | 6 | import inspect 7 | import textwrap 8 | import re 9 | import pydoc 10 | from warnings import warn 11 | import collections 12 | import sys 13 | 14 | 15 | class Reader(object): 16 | """A line-based string reader. 17 | 18 | """ 19 | def __init__(self, data): 20 | """ 21 | Parameters 22 | ---------- 23 | data : str 24 | String with lines separated by '\n'. 25 | 26 | """ 27 | if isinstance(data, list): 28 | self._str = data 29 | else: 30 | self._str = data.split('\n') # store string as list of lines 31 | 32 | self.reset() 33 | 34 | def __getitem__(self, n): 35 | return self._str[n] 36 | 37 | def reset(self): 38 | self._l = 0 # current line nr 39 | 40 | def read(self): 41 | if not self.eof(): 42 | out = self[self._l] 43 | self._l += 1 44 | return out 45 | else: 46 | return '' 47 | 48 | def seek_next_non_empty_line(self): 49 | for l in self[self._l:]: 50 | if l.strip(): 51 | break 52 | else: 53 | self._l += 1 54 | 55 | def eof(self): 56 | return self._l >= len(self._str) 57 | 58 | def read_to_condition(self, condition_func): 59 | start = self._l 60 | for line in self[start:]: 61 | if condition_func(line): 62 | return self[start:self._l] 63 | self._l += 1 64 | if self.eof(): 65 | return self[start:self._l+1] 66 | return [] 67 | 68 | def read_to_next_empty_line(self): 69 | self.seek_next_non_empty_line() 70 | 71 | def is_empty(line): 72 | return not line.strip() 73 | 74 | return self.read_to_condition(is_empty) 75 | 76 | def read_to_next_unindented_line(self): 77 | def is_unindented(line): 78 | return (line.strip() and (len(line.lstrip()) == len(line))) 79 | return self.read_to_condition(is_unindented) 80 | 81 | def peek(self, n=0): 82 | if self._l + n < len(self._str): 83 | return self[self._l + n] 84 | else: 85 | return '' 86 | 87 | def is_empty(self): 88 | return not ''.join(self._str).strip() 89 | 90 | 91 | class NumpyDocString(collections.Mapping): 92 | def __init__(self, docstring, config={}): 93 | docstring = textwrap.dedent(docstring).split('\n') 94 | 95 | self._doc = Reader(docstring) 96 | self._parsed_data = { 97 | 'Signature': '', 98 | 'Summary': [''], 99 | 'Extended Summary': [], 100 | 'Parameters': [], 101 | 'Returns': [], 102 | 'Yields': [], 103 | 'Raises': [], 104 | 'Warns': [], 105 | 'Other Parameters': [], 106 | 'Attributes': [], 107 | 'Methods': [], 108 | 'See Also': [], 109 | 'Notes': [], 110 | 'Warnings': [], 111 | 'References': '', 112 | 'Examples': '', 113 | 'index': {} 114 | } 115 | 116 | self._parse() 117 | 118 | def __getitem__(self, key): 119 | return self._parsed_data[key] 120 | 121 | def __setitem__(self, key, val): 122 | if key not in self._parsed_data: 123 | warn("Unknown section %s" % key) 124 | else: 125 | self._parsed_data[key] = val 126 | 127 | def __iter__(self): 128 | return iter(self._parsed_data) 129 | 130 | def __len__(self): 131 | return len(self._parsed_data) 132 | 133 | def _is_at_section(self): 134 | self._doc.seek_next_non_empty_line() 135 | 136 | if self._doc.eof(): 137 | return False 138 | 139 | l1 = self._doc.peek().strip() # e.g. Parameters 140 | 141 | if l1.startswith('.. index::'): 142 | return True 143 | 144 | l2 = self._doc.peek(1).strip() # ---------- or ========== 145 | return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) 146 | 147 | def _strip(self, doc): 148 | i = 0 149 | j = 0 150 | for i, line in enumerate(doc): 151 | if line.strip(): 152 | break 153 | 154 | for j, line in enumerate(doc[::-1]): 155 | if line.strip(): 156 | break 157 | 158 | return doc[i:len(doc)-j] 159 | 160 | def _read_to_next_section(self): 161 | section = self._doc.read_to_next_empty_line() 162 | 163 | while not self._is_at_section() and not self._doc.eof(): 164 | if not self._doc.peek(-1).strip(): # previous line was empty 165 | section += [''] 166 | 167 | section += self._doc.read_to_next_empty_line() 168 | 169 | return section 170 | 171 | def _read_sections(self): 172 | while not self._doc.eof(): 173 | data = self._read_to_next_section() 174 | name = data[0].strip() 175 | 176 | if name.startswith('..'): # index section 177 | yield name, data[1:] 178 | elif len(data) < 2: 179 | yield StopIteration 180 | else: 181 | yield name, self._strip(data[2:]) 182 | 183 | def _parse_param_list(self, content): 184 | r = Reader(content) 185 | params = [] 186 | while not r.eof(): 187 | header = r.read().strip() 188 | if ' : ' in header: 189 | arg_name, arg_type = header.split(' : ')[:2] 190 | else: 191 | arg_name, arg_type = header, '' 192 | 193 | desc = r.read_to_next_unindented_line() 194 | desc = dedent_lines(desc) 195 | 196 | params.append((arg_name, arg_type, desc)) 197 | 198 | return params 199 | 200 | _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" 201 | r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) 202 | 203 | def _parse_see_also(self, content): 204 | """ 205 | func_name : Descriptive text 206 | continued text 207 | another_func_name : Descriptive text 208 | func_name1, func_name2, :meth:`func_name`, func_name3 209 | 210 | """ 211 | items = [] 212 | 213 | def parse_item_name(text): 214 | """Match ':role:`name`' or 'name'""" 215 | m = self._name_rgx.match(text) 216 | if m: 217 | g = m.groups() 218 | if g[1] is None: 219 | return g[3], None 220 | else: 221 | return g[2], g[1] 222 | raise ValueError("%s is not a item name" % text) 223 | 224 | def push_item(name, rest): 225 | if not name: 226 | return 227 | name, role = parse_item_name(name) 228 | items.append((name, list(rest), role)) 229 | del rest[:] 230 | 231 | current_func = None 232 | rest = [] 233 | 234 | for line in content: 235 | if not line.strip(): 236 | continue 237 | 238 | m = self._name_rgx.match(line) 239 | if m and line[m.end():].strip().startswith(':'): 240 | push_item(current_func, rest) 241 | current_func, line = line[:m.end()], line[m.end():] 242 | rest = [line.split(':', 1)[1].strip()] 243 | if not rest[0]: 244 | rest = [] 245 | elif not line.startswith(' '): 246 | push_item(current_func, rest) 247 | current_func = None 248 | if ',' in line: 249 | for func in line.split(','): 250 | if func.strip(): 251 | push_item(func, []) 252 | elif line.strip(): 253 | current_func = line 254 | elif current_func is not None: 255 | rest.append(line.strip()) 256 | push_item(current_func, rest) 257 | return items 258 | 259 | def _parse_index(self, section, content): 260 | """ 261 | .. index: default 262 | :refguide: something, else, and more 263 | 264 | """ 265 | def strip_each_in(lst): 266 | return [s.strip() for s in lst] 267 | 268 | out = {} 269 | section = section.split('::') 270 | if len(section) > 1: 271 | out['default'] = strip_each_in(section[1].split(','))[0] 272 | for line in content: 273 | line = line.split(':') 274 | if len(line) > 2: 275 | out[line[1]] = strip_each_in(line[2].split(',')) 276 | return out 277 | 278 | def _parse_summary(self): 279 | """Grab signature (if given) and summary""" 280 | if self._is_at_section(): 281 | return 282 | 283 | # If several signatures present, take the last one 284 | while True: 285 | summary = self._doc.read_to_next_empty_line() 286 | summary_str = " ".join([s.strip() for s in summary]).strip() 287 | if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): 288 | self['Signature'] = summary_str 289 | if not self._is_at_section(): 290 | continue 291 | break 292 | 293 | if summary is not None: 294 | self['Summary'] = summary 295 | 296 | if not self._is_at_section(): 297 | self['Extended Summary'] = self._read_to_next_section() 298 | 299 | def _parse(self): 300 | self._doc.reset() 301 | self._parse_summary() 302 | 303 | sections = list(self._read_sections()) 304 | section_names = set([section for section, content in sections]) 305 | 306 | has_returns = 'Returns' in section_names 307 | has_yields = 'Yields' in section_names 308 | # We could do more tests, but we are not. Arbitrarily. 309 | if has_returns and has_yields: 310 | msg = 'Docstring contains both a Returns and Yields section.' 311 | raise ValueError(msg) 312 | 313 | for (section, content) in sections: 314 | if not section.startswith('..'): 315 | section = (s.capitalize() for s in section.split(' ')) 316 | section = ' '.join(section) 317 | if section in ('Parameters', 'Returns', 'Yields', 'Raises', 318 | 'Warns', 'Other Parameters', 'Attributes', 319 | 'Methods'): 320 | self[section] = self._parse_param_list(content) 321 | elif section.startswith('.. index::'): 322 | self['index'] = self._parse_index(section, content) 323 | elif section == 'See Also': 324 | self['See Also'] = self._parse_see_also(content) 325 | else: 326 | self[section] = content 327 | 328 | # string conversion routines 329 | 330 | def _str_header(self, name, symbol='-'): 331 | return [name, len(name)*symbol] 332 | 333 | def _str_indent(self, doc, indent=4): 334 | out = [] 335 | for line in doc: 336 | out += [' '*indent + line] 337 | return out 338 | 339 | def _str_signature(self): 340 | if self['Signature']: 341 | return [self['Signature'].replace('*', '\*')] + [''] 342 | else: 343 | return [''] 344 | 345 | def _str_summary(self): 346 | if self['Summary']: 347 | return self['Summary'] + [''] 348 | else: 349 | return [] 350 | 351 | def _str_extended_summary(self): 352 | if self['Extended Summary']: 353 | return self['Extended Summary'] + [''] 354 | else: 355 | return [] 356 | 357 | def _str_param_list(self, name): 358 | out = [] 359 | if self[name]: 360 | out += self._str_header(name) 361 | for param, param_type, desc in self[name]: 362 | if param_type: 363 | out += ['%s : %s' % (param, param_type)] 364 | else: 365 | out += [param] 366 | out += self._str_indent(desc) 367 | out += [''] 368 | return out 369 | 370 | def _str_section(self, name): 371 | out = [] 372 | if self[name]: 373 | out += self._str_header(name) 374 | out += self[name] 375 | out += [''] 376 | return out 377 | 378 | def _str_see_also(self, func_role): 379 | if not self['See Also']: 380 | return [] 381 | out = [] 382 | out += self._str_header("See Also") 383 | last_had_desc = True 384 | for func, desc, role in self['See Also']: 385 | if role: 386 | link = ':%s:`%s`' % (role, func) 387 | elif func_role: 388 | link = ':%s:`%s`' % (func_role, func) 389 | else: 390 | link = "`%s`_" % func 391 | if desc or last_had_desc: 392 | out += [''] 393 | out += [link] 394 | else: 395 | out[-1] += ", %s" % link 396 | if desc: 397 | out += self._str_indent([' '.join(desc)]) 398 | last_had_desc = True 399 | else: 400 | last_had_desc = False 401 | out += [''] 402 | return out 403 | 404 | def _str_index(self): 405 | idx = self['index'] 406 | out = [] 407 | out += ['.. index:: %s' % idx.get('default', '')] 408 | for section, references in idx.items(): 409 | if section == 'default': 410 | continue 411 | out += [' :%s: %s' % (section, ', '.join(references))] 412 | return out 413 | 414 | def __str__(self, func_role=''): 415 | out = [] 416 | out += self._str_signature() 417 | out += self._str_summary() 418 | out += self._str_extended_summary() 419 | for param_list in ('Parameters', 'Returns', 'Yields', 420 | 'Other Parameters', 'Raises', 'Warns'): 421 | out += self._str_param_list(param_list) 422 | out += self._str_section('Warnings') 423 | out += self._str_see_also(func_role) 424 | for s in ('Notes', 'References', 'Examples'): 425 | out += self._str_section(s) 426 | for param_list in ('Attributes', 'Methods'): 427 | out += self._str_param_list(param_list) 428 | out += self._str_index() 429 | return '\n'.join(out) 430 | 431 | 432 | def indent(str, indent=4): 433 | indent_str = ' '*indent 434 | if str is None: 435 | return indent_str 436 | lines = str.split('\n') 437 | return '\n'.join(indent_str + l for l in lines) 438 | 439 | 440 | def dedent_lines(lines): 441 | """Deindent a list of lines maximally""" 442 | return textwrap.dedent("\n".join(lines)).split("\n") 443 | 444 | 445 | def header(text, style='-'): 446 | return text + '\n' + style*len(text) + '\n' 447 | 448 | 449 | class FunctionDoc(NumpyDocString): 450 | def __init__(self, func, role='func', doc=None, config={}): 451 | self._f = func 452 | self._role = role # e.g. "func" or "meth" 453 | 454 | if doc is None: 455 | if func is None: 456 | raise ValueError("No function or docstring given") 457 | doc = inspect.getdoc(func) or '' 458 | NumpyDocString.__init__(self, doc) 459 | 460 | if not self['Signature'] and func is not None: 461 | func, func_name = self.get_func() 462 | try: 463 | # try to read signature 464 | if sys.version_info[0] >= 3: 465 | argspec = inspect.getfullargspec(func) 466 | else: 467 | argspec = inspect.getargspec(func) 468 | argspec = inspect.formatargspec(*argspec) 469 | argspec = argspec.replace('*', '\*') 470 | signature = '%s%s' % (func_name, argspec) 471 | except TypeError as e: 472 | signature = '%s()' % func_name 473 | self['Signature'] = signature 474 | 475 | def get_func(self): 476 | func_name = getattr(self._f, '__name__', self.__class__.__name__) 477 | if inspect.isclass(self._f): 478 | func = getattr(self._f, '__call__', self._f.__init__) 479 | else: 480 | func = self._f 481 | return func, func_name 482 | 483 | def __str__(self): 484 | out = '' 485 | 486 | func, func_name = self.get_func() 487 | signature = self['Signature'].replace('*', '\*') 488 | 489 | roles = {'func': 'function', 490 | 'meth': 'method'} 491 | 492 | if self._role: 493 | if self._role not in roles: 494 | print("Warning: invalid role %s" % self._role) 495 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role, ''), 496 | func_name) 497 | 498 | out += super(FunctionDoc, self).__str__(func_role=self._role) 499 | return out 500 | 501 | 502 | class ClassDoc(NumpyDocString): 503 | 504 | extra_public_methods = ['__call__'] 505 | 506 | def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, 507 | config={}): 508 | if not inspect.isclass(cls) and cls is not None: 509 | raise ValueError("Expected a class or None, but got %r" % cls) 510 | self._cls = cls 511 | 512 | self.show_inherited_members = config.get( 513 | 'show_inherited_class_members', True) 514 | 515 | if modulename and not modulename.endswith('.'): 516 | modulename += '.' 517 | self._mod = modulename 518 | 519 | if doc is None: 520 | if cls is None: 521 | raise ValueError("No class or documentation string given") 522 | doc = pydoc.getdoc(cls) 523 | 524 | NumpyDocString.__init__(self, doc) 525 | 526 | if config.get('show_class_members', True): 527 | def splitlines_x(s): 528 | if not s: 529 | return [] 530 | else: 531 | return s.splitlines() 532 | 533 | for field, items in [('Methods', self.methods), 534 | ('Attributes', self.properties)]: 535 | if not self[field]: 536 | doc_list = [] 537 | for name in sorted(items): 538 | try: 539 | doc_item = pydoc.getdoc(getattr(self._cls, name)) 540 | doc_list.append((name, '', splitlines_x(doc_item))) 541 | except AttributeError: 542 | pass # method doesn't exist 543 | self[field] = doc_list 544 | 545 | @property 546 | def methods(self): 547 | if self._cls is None: 548 | return [] 549 | return [name for name, func in inspect.getmembers(self._cls) 550 | if ((not name.startswith('_') 551 | or name in self.extra_public_methods) 552 | and isinstance(func, collections.Callable) 553 | and self._is_show_member(name))] 554 | 555 | @property 556 | def properties(self): 557 | if self._cls is None: 558 | return [] 559 | return [name for name, func in inspect.getmembers(self._cls) 560 | if (not name.startswith('_') and 561 | (func is None or isinstance(func, property) or 562 | inspect.isgetsetdescriptor(func)) 563 | and self._is_show_member(name))] 564 | 565 | def _is_show_member(self, name): 566 | if self.show_inherited_members: 567 | return True # show all class members 568 | if name not in self._cls.__dict__: 569 | return False # class member is inherited, we do not show it 570 | return True 571 | -------------------------------------------------------------------------------- /doc/sphinxext/docscrape_sphinx.py: -------------------------------------------------------------------------------- 1 | import re, inspect, textwrap, pydoc 2 | import sphinx 3 | from docscrape import NumpyDocString, FunctionDoc, ClassDoc 4 | 5 | class SphinxDocString(NumpyDocString): 6 | def __init__(self, docstring, config={}): 7 | self.use_plots = config.get('use_plots', False) 8 | NumpyDocString.__init__(self, docstring, config=config) 9 | 10 | # string conversion routines 11 | def _str_header(self, name, symbol='`'): 12 | return ['.. rubric:: ' + name, ''] 13 | 14 | def _str_field_list(self, name): 15 | return [':' + name + ':'] 16 | 17 | def _str_indent(self, doc, indent=4): 18 | out = [] 19 | for line in doc: 20 | out += [' '*indent + line] 21 | return out 22 | 23 | def _str_signature(self): 24 | return [''] 25 | if self['Signature']: 26 | return ['``%s``' % self['Signature']] + [''] 27 | else: 28 | return [''] 29 | 30 | def _str_summary(self): 31 | return self['Summary'] + [''] 32 | 33 | def _str_extended_summary(self): 34 | return self['Extended Summary'] + [''] 35 | 36 | def _str_param_list(self, name): 37 | out = [] 38 | if self[name]: 39 | out += self._str_field_list(name) 40 | out += [''] 41 | for param,param_type,desc in self[name]: 42 | out += self._str_indent(['**%s** : %s' % (param.strip(), 43 | param_type)]) 44 | out += [''] 45 | out += self._str_indent(desc,8) 46 | out += [''] 47 | return out 48 | 49 | @property 50 | def _obj(self): 51 | if hasattr(self, '_cls'): 52 | return self._cls 53 | elif hasattr(self, '_f'): 54 | return self._f 55 | return None 56 | 57 | def _str_member_list(self, name): 58 | """ 59 | Generate a member listing, autosummary:: table where possible, 60 | and a table where not. 61 | 62 | """ 63 | out = [] 64 | if self[name]: 65 | out += ['.. rubric:: %s' % name, ''] 66 | prefix = getattr(self, '_name', '') 67 | 68 | if prefix: 69 | prefix = '~%s.' % prefix 70 | 71 | autosum = [] 72 | others = [] 73 | for param, param_type, desc in self[name]: 74 | param = param.strip() 75 | if not self._obj or hasattr(self._obj, param): 76 | autosum += [" %s%s" % (prefix, param)] 77 | else: 78 | others.append((param, param_type, desc)) 79 | 80 | if autosum: 81 | out += ['.. autosummary::', ' :toctree:', ''] 82 | out += autosum 83 | 84 | if others: 85 | maxlen_0 = max([len(x[0]) for x in others]) 86 | maxlen_1 = max([len(x[1]) for x in others]) 87 | hdr = "="*maxlen_0 + " " + "="*maxlen_1 + " " + "="*10 88 | fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1) 89 | n_indent = maxlen_0 + maxlen_1 + 4 90 | out += [hdr] 91 | for param, param_type, desc in others: 92 | out += [fmt % (param.strip(), param_type)] 93 | out += self._str_indent(desc, n_indent) 94 | out += [hdr] 95 | out += [''] 96 | return out 97 | 98 | def _str_section(self, name): 99 | out = [] 100 | if self[name]: 101 | out += self._str_header(name) 102 | out += [''] 103 | content = textwrap.dedent("\n".join(self[name])).split("\n") 104 | out += content 105 | out += [''] 106 | return out 107 | 108 | def _str_see_also(self, func_role): 109 | out = [] 110 | if self['See Also']: 111 | see_also = super(SphinxDocString, self)._str_see_also(func_role) 112 | out = ['.. seealso::', ''] 113 | out += self._str_indent(see_also[2:]) 114 | return out 115 | 116 | def _str_warnings(self): 117 | out = [] 118 | if self['Warnings']: 119 | out = ['.. warning::', ''] 120 | out += self._str_indent(self['Warnings']) 121 | return out 122 | 123 | def _str_index(self): 124 | idx = self['index'] 125 | out = [] 126 | if len(idx) == 0: 127 | return out 128 | 129 | out += ['.. index:: %s' % idx.get('default','')] 130 | for section, references in idx.iteritems(): 131 | if section == 'default': 132 | continue 133 | elif section == 'refguide': 134 | out += [' single: %s' % (', '.join(references))] 135 | else: 136 | out += [' %s: %s' % (section, ','.join(references))] 137 | return out 138 | 139 | def _str_references(self): 140 | out = [] 141 | if self['References']: 142 | out += self._str_header('References') 143 | if isinstance(self['References'], str): 144 | self['References'] = [self['References']] 145 | out.extend(self['References']) 146 | out += [''] 147 | # Latex collects all references to a separate bibliography, 148 | # so we need to insert links to it 149 | if sphinx.__version__ >= "0.6": 150 | out += ['.. only:: latex',''] 151 | else: 152 | out += ['.. latexonly::',''] 153 | items = [] 154 | for line in self['References']: 155 | m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) 156 | if m: 157 | items.append(m.group(1)) 158 | out += [' ' + ", ".join(["[%s]_" % item for item in items]), ''] 159 | return out 160 | 161 | def _str_examples(self): 162 | examples_str = "\n".join(self['Examples']) 163 | 164 | if (self.use_plots and 'import matplotlib' in examples_str 165 | and 'plot::' not in examples_str): 166 | out = [] 167 | out += self._str_header('Examples') 168 | out += ['.. plot::', ''] 169 | out += self._str_indent(self['Examples']) 170 | out += [''] 171 | return out 172 | else: 173 | return self._str_section('Examples') 174 | 175 | def __str__(self, indent=0, func_role="obj"): 176 | out = [] 177 | out += self._str_signature() 178 | out += self._str_index() + [''] 179 | out += self._str_summary() 180 | out += self._str_extended_summary() 181 | for param_list in ('Parameters', 'Returns', 'Other Parameters', 182 | 'Raises', 'Warns'): 183 | out += self._str_param_list(param_list) 184 | out += self._str_warnings() 185 | out += self._str_see_also(func_role) 186 | out += self._str_section('Notes') 187 | out += self._str_references() 188 | out += self._str_examples() 189 | for param_list in ('Attributes', 'Methods'): 190 | out += self._str_member_list(param_list) 191 | out = self._str_indent(out,indent) 192 | return '\n'.join(out) 193 | 194 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 195 | def __init__(self, obj, doc=None, config={}): 196 | self.use_plots = config.get('use_plots', False) 197 | FunctionDoc.__init__(self, obj, doc=doc, config=config) 198 | 199 | class SphinxClassDoc(SphinxDocString, ClassDoc): 200 | def __init__(self, obj, doc=None, func_doc=None, config={}): 201 | self.use_plots = config.get('use_plots', False) 202 | ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) 203 | 204 | class SphinxObjDoc(SphinxDocString): 205 | def __init__(self, obj, doc=None, config={}): 206 | self._f = obj 207 | SphinxDocString.__init__(self, doc, config=config) 208 | 209 | def get_doc_object(obj, what=None, doc=None, config={}): 210 | if what is None: 211 | if inspect.isclass(obj): 212 | what = 'class' 213 | elif inspect.ismodule(obj): 214 | what = 'module' 215 | elif callable(obj): 216 | what = 'function' 217 | else: 218 | what = 'object' 219 | if what == 'class': 220 | return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, 221 | config=config) 222 | elif what in ('function', 'method'): 223 | return SphinxFunctionDoc(obj, doc=doc, config=config) 224 | else: 225 | if doc is None: 226 | doc = pydoc.getdoc(obj) 227 | return SphinxObjDoc(obj, doc, config=config) 228 | -------------------------------------------------------------------------------- /doc/sphinxext/github.py: -------------------------------------------------------------------------------- 1 | """Define text roles for GitHub 2 | 3 | * ghissue - Issue 4 | * ghpull - Pull Request 5 | * ghuser - User 6 | 7 | Adapted from bitbucket example here: 8 | https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py 9 | 10 | Authors 11 | ------- 12 | 13 | * Doug Hellmann 14 | * Min RK 15 | """ 16 | # 17 | # Original Copyright (c) 2010 Doug Hellmann. All rights reserved. 18 | # 19 | 20 | from docutils import nodes, utils 21 | from docutils.parsers.rst.roles import set_classes 22 | 23 | def make_link_node(rawtext, app, type, slug, options): 24 | """Create a link to a github resource. 25 | 26 | :param rawtext: Text being replaced with link node. 27 | :param app: Sphinx application context 28 | :param type: Link type (issues, changeset, etc.) 29 | :param slug: ID of the thing to link to 30 | :param options: Options dictionary passed to role func. 31 | """ 32 | 33 | try: 34 | base = app.config.github_project_url 35 | if not base: 36 | raise AttributeError 37 | if not base.endswith('/'): 38 | base += '/' 39 | except AttributeError as err: 40 | raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) 41 | 42 | ref = base + type + '/' + slug + '/' 43 | set_classes(options) 44 | prefix = "#" 45 | if type == 'pull': 46 | prefix = "PR " + prefix 47 | node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref, 48 | **options) 49 | return node 50 | 51 | def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): 52 | """Link to a GitHub issue. 53 | 54 | Returns 2 part tuple containing list of nodes to insert into the 55 | document and a list of system messages. Both are allowed to be 56 | empty. 57 | 58 | :param name: The role name used in the document. 59 | :param rawtext: The entire markup snippet, with role. 60 | :param text: The text marked with the role. 61 | :param lineno: The line number where rawtext appears in the input. 62 | :param inliner: The inliner instance that called us. 63 | :param options: Directive options for customization. 64 | :param content: The directive content for customization. 65 | """ 66 | 67 | try: 68 | issue_num = int(text) 69 | if issue_num <= 0: 70 | raise ValueError 71 | except ValueError: 72 | msg = inliner.reporter.error( 73 | 'GitHub issue number must be a number greater than or equal to 1; ' 74 | '"%s" is invalid.' % text, line=lineno) 75 | prb = inliner.problematic(rawtext, rawtext, msg) 76 | return [prb], [msg] 77 | app = inliner.document.settings.env.app 78 | #app.info('issue %r' % text) 79 | if 'pull' in name.lower(): 80 | category = 'pull' 81 | elif 'issue' in name.lower(): 82 | category = 'issues' 83 | else: 84 | msg = inliner.reporter.error( 85 | 'GitHub roles include "ghpull" and "ghissue", ' 86 | '"%s" is invalid.' % name, line=lineno) 87 | prb = inliner.problematic(rawtext, rawtext, msg) 88 | return [prb], [msg] 89 | node = make_link_node(rawtext, app, category, str(issue_num), options) 90 | return [node], [] 91 | 92 | def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): 93 | """Link to a GitHub user. 94 | 95 | Returns 2 part tuple containing list of nodes to insert into the 96 | document and a list of system messages. Both are allowed to be 97 | empty. 98 | 99 | :param name: The role name used in the document. 100 | :param rawtext: The entire markup snippet, with role. 101 | :param text: The text marked with the role. 102 | :param lineno: The line number where rawtext appears in the input. 103 | :param inliner: The inliner instance that called us. 104 | :param options: Directive options for customization. 105 | :param content: The directive content for customization. 106 | """ 107 | app = inliner.document.settings.env.app 108 | #app.info('user link %r' % text) 109 | ref = 'https://www.github.com/' + text 110 | node = nodes.reference(rawtext, text, refuri=ref, **options) 111 | return [node], [] 112 | 113 | def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): 114 | """Link to a GitHub commit. 115 | 116 | Returns 2 part tuple containing list of nodes to insert into the 117 | document and a list of system messages. Both are allowed to be 118 | empty. 119 | 120 | :param name: The role name used in the document. 121 | :param rawtext: The entire markup snippet, with role. 122 | :param text: The text marked with the role. 123 | :param lineno: The line number where rawtext appears in the input. 124 | :param inliner: The inliner instance that called us. 125 | :param options: Directive options for customization. 126 | :param content: The directive content for customization. 127 | """ 128 | app = inliner.document.settings.env.app 129 | #app.info('user link %r' % text) 130 | try: 131 | base = app.config.github_project_url 132 | if not base: 133 | raise AttributeError 134 | if not base.endswith('/'): 135 | base += '/' 136 | except AttributeError as err: 137 | raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) 138 | 139 | ref = base + text 140 | node = nodes.reference(rawtext, text[:6], refuri=ref, **options) 141 | return [node], [] 142 | 143 | 144 | def setup(app): 145 | """Install the plugin. 146 | 147 | :param app: Sphinx application context. 148 | """ 149 | app.info('Initializing GitHub plugin') 150 | app.add_role('ghissue', ghissue_role) 151 | app.add_role('ghpull', ghissue_role) 152 | app.add_role('ghuser', ghuser_role) 153 | app.add_role('ghcommit', ghcommit_role) 154 | app.add_config_value('github_project_url', None, 'env') 155 | return 156 | -------------------------------------------------------------------------------- /doc/sphinxext/math_dollar.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def dollars_to_math(source): 4 | r""" 5 | Replace dollar signs with backticks. 6 | 7 | More precisely, do a regular expression search. Replace a plain 8 | dollar sign ($) by a backtick (`). Replace an escaped dollar sign 9 | (\$) by a dollar sign ($). Don't change a dollar sign preceded or 10 | followed by a backtick (`$ or $`), because of strings like 11 | "``$HOME``". Don't make any changes on lines starting with 12 | spaces, because those are indented and hence part of a block of 13 | code or examples. 14 | 15 | This also doesn't replaces dollar signs enclosed in curly braces, 16 | to avoid nested math environments, such as :: 17 | 18 | $f(n) = 0 \text{ if $n$ is prime}$ 19 | 20 | Thus the above line would get changed to 21 | 22 | `f(n) = 0 \text{ if $n$ is prime}` 23 | """ 24 | s = "\n".join(source) 25 | if s.find("$") == -1: 26 | return 27 | # This searches for "$blah$" inside a pair of curly braces -- 28 | # don't change these, since they're probably coming from a nested 29 | # math environment. So for each match, we replace it with a temporary 30 | # string, and later on we substitute the original back. 31 | global _data 32 | _data = {} 33 | def repl(matchobj): 34 | global _data 35 | s = matchobj.group(0) 36 | t = "___XXX_REPL_%d___" % len(_data) 37 | _data[t] = s 38 | return t 39 | s = re.sub(r"({[^{}$]*\$[^{}$]*\$[^{}]*})", repl, s) 40 | # matches $...$ 41 | dollars = re.compile(r"(?= 3: 35 | sixu = lambda s: s 36 | else: 37 | sixu = lambda s: unicode(s, 'unicode_escape') 38 | 39 | 40 | def mangle_docstrings(app, what, name, obj, options, lines, 41 | reference_offset=[0]): 42 | 43 | cfg = {'use_plots': app.config.numpydoc_use_plots, 44 | 'show_class_members': app.config.numpydoc_show_class_members, 45 | 'show_inherited_class_members': 46 | app.config.numpydoc_show_inherited_class_members, 47 | 'class_members_toctree': app.config.numpydoc_class_members_toctree} 48 | 49 | u_NL = sixu('\n') 50 | if what == 'module': 51 | # Strip top title 52 | pattern = '^\\s*[#*=]{4,}\\n[a-z0-9 -]+\\n[#*=]{4,}\\s*' 53 | title_re = re.compile(sixu(pattern), re.I | re.S) 54 | lines[:] = title_re.sub(sixu(''), u_NL.join(lines)).split(u_NL) 55 | else: 56 | doc = get_doc_object(obj, what, u_NL.join(lines), config=cfg) 57 | if sys.version_info[0] >= 3: 58 | doc = str(doc) 59 | else: 60 | doc = unicode(doc) 61 | lines[:] = doc.split(u_NL) 62 | 63 | if (app.config.numpydoc_edit_link and hasattr(obj, '__name__') and 64 | obj.__name__): 65 | if hasattr(obj, '__module__'): 66 | v = dict(full_name=sixu("%s.%s") % (obj.__module__, obj.__name__)) 67 | else: 68 | v = dict(full_name=obj.__name__) 69 | lines += [sixu(''), sixu('.. htmlonly::'), sixu('')] 70 | lines += [sixu(' %s') % x for x in 71 | (app.config.numpydoc_edit_link % v).split("\n")] 72 | 73 | # replace reference numbers so that there are no duplicates 74 | references = [] 75 | for line in lines: 76 | line = line.strip() 77 | m = re.match(sixu('^.. \\[([a-z0-9_.-])\\]'), line, re.I) 78 | if m: 79 | references.append(m.group(1)) 80 | 81 | # start renaming from the longest string, to avoid overwriting parts 82 | references.sort(key=lambda x: -len(x)) 83 | if references: 84 | for i, line in enumerate(lines): 85 | for r in references: 86 | if re.match(sixu('^\\d+$'), r): 87 | new_r = sixu("R%d") % (reference_offset[0] + int(r)) 88 | else: 89 | new_r = sixu("%s%d") % (r, reference_offset[0]) 90 | lines[i] = lines[i].replace(sixu('[%s]_') % r, 91 | sixu('[%s]_') % new_r) 92 | lines[i] = lines[i].replace(sixu('.. [%s]') % r, 93 | sixu('.. [%s]') % new_r) 94 | 95 | reference_offset[0] += len(references) 96 | 97 | 98 | def mangle_signature(app, what, name, obj, options, sig, retann): 99 | # Do not try to inspect classes that don't define `__init__` 100 | if (inspect.isclass(obj) and 101 | (not hasattr(obj, '__init__') or 102 | 'initializes x; see ' in pydoc.getdoc(obj.__init__))): 103 | return '', '' 104 | 105 | if not (isinstance(obj, collections.Callable) or 106 | hasattr(obj, '__argspec_is_invalid_')): 107 | return 108 | 109 | if not hasattr(obj, '__doc__'): 110 | return 111 | 112 | doc = SphinxDocString(pydoc.getdoc(obj)) 113 | if doc['Signature']: 114 | sig = re.sub(sixu("^[^(]*"), sixu(""), doc['Signature']) 115 | return sig, sixu('') 116 | 117 | 118 | def setup(app, get_doc_object_=get_doc_object): 119 | if not hasattr(app, 'add_config_value'): 120 | return # probably called by nose, better bail out 121 | 122 | global get_doc_object 123 | get_doc_object = get_doc_object_ 124 | 125 | app.connect('autodoc-process-docstring', mangle_docstrings) 126 | app.connect('autodoc-process-signature', mangle_signature) 127 | app.add_config_value('numpydoc_edit_link', None, False) 128 | app.add_config_value('numpydoc_use_plots', None, False) 129 | app.add_config_value('numpydoc_show_class_members', True, True) 130 | app.add_config_value('numpydoc_show_inherited_class_members', True, True) 131 | app.add_config_value('numpydoc_class_members_toctree', True, True) 132 | 133 | # Extra mangling domains 134 | app.add_domain(NumpyPythonDomain) 135 | app.add_domain(NumpyCDomain) 136 | 137 | # ------------------------------------------------------------------------------ 138 | # Docstring-mangling domains 139 | # ------------------------------------------------------------------------------ 140 | 141 | from docutils.statemachine import ViewList 142 | from sphinx.domains.c import CDomain 143 | from sphinx.domains.python import PythonDomain 144 | 145 | 146 | class ManglingDomainBase(object): 147 | directive_mangling_map = {} 148 | 149 | def __init__(self, *a, **kw): 150 | super(ManglingDomainBase, self).__init__(*a, **kw) 151 | self.wrap_mangling_directives() 152 | 153 | def wrap_mangling_directives(self): 154 | for name, objtype in list(self.directive_mangling_map.items()): 155 | self.directives[name] = wrap_mangling_directive( 156 | self.directives[name], objtype) 157 | 158 | 159 | class NumpyPythonDomain(ManglingDomainBase, PythonDomain): 160 | name = 'np' 161 | directive_mangling_map = { 162 | 'function': 'function', 163 | 'class': 'class', 164 | 'exception': 'class', 165 | 'method': 'function', 166 | 'classmethod': 'function', 167 | 'staticmethod': 'function', 168 | 'attribute': 'attribute', 169 | } 170 | indices = [] 171 | 172 | 173 | class NumpyCDomain(ManglingDomainBase, CDomain): 174 | name = 'np-c' 175 | directive_mangling_map = { 176 | 'function': 'function', 177 | 'member': 'attribute', 178 | 'macro': 'function', 179 | 'type': 'class', 180 | 'var': 'object', 181 | } 182 | 183 | 184 | def wrap_mangling_directive(base_directive, objtype): 185 | class directive(base_directive): 186 | def run(self): 187 | env = self.state.document.settings.env 188 | 189 | name = None 190 | if self.arguments: 191 | m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) 192 | name = m.group(2).strip() 193 | 194 | if not name: 195 | name = self.arguments[0] 196 | 197 | lines = list(self.content) 198 | mangle_docstrings(env.app, objtype, name, None, None, lines) 199 | self.content = ViewList(lines, self.content.parent) 200 | 201 | return base_directive.run(self) 202 | 203 | return directive 204 | -------------------------------------------------------------------------------- /doc/theory.rst: -------------------------------------------------------------------------------- 1 | 2 | This is a theory section 3 | ======================== 4 | 5 | I might want to describe some equations: 6 | 7 | .. math:: 8 | 9 | \int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2} 10 | 11 | 12 | And refer to a paper [author2015]_. 13 | 14 | 15 | .. [author2015] first a., second a., cheese b. (2015). The title of their 16 | paper. Journal of papers, *15*: 1023-1049. 17 | 18 | -------------------------------------------------------------------------------- /examples/README.txt: -------------------------------------------------------------------------------- 1 | Shablona Gallery 2 | ================ 3 | 4 | This is the main gallery page for sphinx-gallery. It contains preview 5 | images of the scripts contained in all of the sub-folders of this folder. 6 | It also breaks these images into subsections based on folder. Click one of 7 | the images below to be taken its corresponding example. -------------------------------------------------------------------------------- /examples/model_fitting/README.txt: -------------------------------------------------------------------------------- 1 | Model fitting examples 2 | ---------------------- 3 | 4 | These are a collection of examples demonstrating the model fitting 5 | functionality of shablona, as well as how examples can be compiled into 6 | a gallery with `sphinx-gallery`. 7 | -------------------------------------------------------------------------------- /examples/model_fitting/plot_function_fit.py: -------------------------------------------------------------------------------- 1 | """ 2 | ================================ 3 | Fitting a function with shablona 4 | ================================ 5 | 6 | Shablona contains a number of tools for fitting functions to 7 | data. This example shows us how to load data into python, fit 8 | a function to our datapoints with shablona, and then plot the 9 | result. 10 | 11 | This example is meant to demonstrate the functionality of 12 | sphinx-gallery, which allows you to generate narrative-style 13 | documents from python files. 14 | """ 15 | 16 | import os.path as op 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | import shablona as sb 20 | plt.style.use('ggplot') 21 | 22 | ############################################################################### 23 | # Loading data 24 | # ------------ 25 | # 26 | # First, we'll load some data into shablona. 27 | data_path = op.join(sb.__path__[0], 'data') 28 | 29 | ortho_x, ortho_y, ortho_n = sb.transform_data(op.join(data_path, 'ortho.csv')) 30 | para_x, para_y, para_n = sb.transform_data(op.join(data_path, 'para.csv')) 31 | 32 | ############################################################################### 33 | # Fitting a model 34 | # --------------- 35 | # 36 | # With shablona, models are created with the :ref:Model class. 37 | # This class has a `fit` method that returns the coefficients for the given 38 | # input data. 39 | 40 | # Instantiate our model and fit it on two datasets 41 | model = sb.Model() 42 | ortho_fit = model.fit(ortho_x, ortho_y) 43 | para_fit = model.fit(para_x, para_y) 44 | 45 | # These are the parameters that our model has discovered 46 | print(ortho_fit.params) 47 | print(para_fit.params) 48 | 49 | ############################################################################### 50 | # Visualizing results 51 | # ------------------- 52 | # 53 | # Now we will visualize the results of our model fit. We'll generate 54 | # a vector of input points, and use them to determine the model's output 55 | # for each input. Then we'll plot what these curves look like. 56 | 57 | # Create figure and generate input points 58 | fig, ax = plt.subplots(1) 59 | x_predict = np.linspace(0, 1, 100) 60 | 61 | # Make the first plot 62 | for x, y, n in zip(ortho_x, ortho_y, ortho_n): 63 | ax.plot(x, y, 'bo', markersize=n) 64 | ax.plot(x_predict, ortho_fit.predict(x_predict), 'b') 65 | 66 | # Make the second plot 67 | for x, y, n in zip(para_x, para_y, para_n): 68 | ax.plot(x, y, 'go', markersize=n) 69 | ax.plot(x_predict, para_fit.predict(x_predict), 'g') 70 | 71 | ax.set_xlabel('Contrast in interval 1') 72 | ax.set_ylabel("Proportion answers '1'") 73 | ax.set_ylim([-0.1, 1.1]) 74 | ax.set_xlim([-0.1, 1.1]) 75 | fig.set_size_inches([8, 8]) 76 | -------------------------------------------------------------------------------- /examples/model_fitting/plot_function_stability.py: -------------------------------------------------------------------------------- 1 | """ 2 | ======================== 3 | Plotting model stability 4 | ======================== 5 | 6 | Next we'll show off another demonstration of model fitting with shablona. 7 | We'll generate a bunch of data with varying levels of signal to noise, and then 8 | show the stability of the model coefficients for each set of data. 9 | """ 10 | 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | import shablona as sb 14 | plt.style.use('ggplot') 15 | 16 | # Set seed for reproducibility 17 | np.random.seed(1337) 18 | 19 | ############################################################################### 20 | # Generating data 21 | # --------------- 22 | # 23 | # We'll generate some random data for this example. 24 | 25 | N = 100 26 | w = .3 27 | noise_levels = np.linspace(.01, 10, 5) # These are our SNR levels 28 | n_boots = 1000 # Number of bootstraps for the coefficients 29 | x = np.random.randn(N) 30 | 31 | ############################################################################### 32 | # Fitting the model 33 | # ----------------- 34 | # 35 | # Now, we'll loop through the varying levels of noise, and fit several models 36 | # to different subsets of data. This will give us a distribution of model 37 | # coefficients for each noise level. 38 | 39 | 40 | def error_func(x, w): 41 | return x * w 42 | 43 | 44 | coefs = np.zeros([len(noise_levels), n_boots]) 45 | for ii, n_level in enumerate(noise_levels): 46 | # Generate y for this noise level 47 | y = w * x + n_level * np.random.randn(N) 48 | for jj in range(n_boots): 49 | # Pull subsets of data 50 | ixs_boot = np.random.randint(0, N, N) 51 | x_boot = x[ixs_boot] 52 | y_boot = y[ixs_boot] 53 | # Fit the model and return the coefs 54 | model = sb.Model(error_func) 55 | fit = model.fit(x_boot, y_boot, (.5,)) 56 | coefs[ii, jj] = fit.params[0] 57 | 58 | ############################################################################### 59 | # Assessing coefficient stability 60 | # ------------------------------- 61 | # 62 | # Now we'll assess the stability of the fitted coefficient for varying levels 63 | # of noise. Let's plot the raw values for each noise level, as well as the 64 | # 95% confidence interval. 65 | 66 | percentiles = np.percentile(coefs, [2.5, 97.5], axis=1).T 67 | fig, ax = plt.subplots() 68 | for n_level, i_coefs, percs in zip(noise_levels, coefs, percentiles): 69 | ax.scatter(np.repeat(n_level, len(i_coefs)), i_coefs) 70 | ax.hlines(percs, n_level - .2, n_level + .2, lw=2, color='r', alpha=.6) 71 | ax.set(xlabel='Noise level', ylabel='Boostrapped coefficients', 72 | title='Bootstrapped coefficients and 95% CI\nfor many noise levels') 73 | -------------------------------------------------------------------------------- /examples/sg_tutorial/README.txt: -------------------------------------------------------------------------------- 1 | Sphinx-Gallery tutorial 2 | ----------------------- 3 | 4 | This folder contains a tutorial to demonstrate some functionality of 5 | sphinx-gallery. It is a quick tour of features but doesn't cover 6 | everything that you can do with rST. -------------------------------------------------------------------------------- /examples/sg_tutorial/plot_sg_tutorial.py: -------------------------------------------------------------------------------- 1 | """ 2 | ====================================== 3 | A quick tour of sphinx-gallery and rST 4 | ====================================== 5 | 6 | One of the most important components of any package is its documentation. 7 | For packages that involve data analysis, visualization of results / data is 8 | a key element of the docs. Sphinx-gallery is an excellent tool for building 9 | narrative-style documentation in the form of `.py` files, as well as for 10 | generating a gallery of sample images that are generated by your various 11 | scripts. 12 | 13 | This is a short demo for how sphinx-gallery can be used to generate beautiful, 14 | HTML-rendered documentation using regular python files. 15 | """ 16 | 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | 20 | ############################################################################### 21 | # reStructuredText 22 | # ---------------- 23 | # 24 | # The primary benefit of sphinx-gallery is that it allows you to interweave 25 | # `reStructuredText `_ along with 26 | # your regular python code. This means that you can include formatted text 27 | # with the script, all using regular text files. rST has a particular structure 28 | # it expects in order to render properly (it is what sphinx uses as well). 29 | # 30 | # File headers and naming 31 | # ----------------------- 32 | # Sphinx-gallery files must be initialized with a header like the one above. 33 | # It must exist as a part of the triple-quotes docstring at the start of the 34 | # file, and tells SG the title of the page. If you wish, you can include text 35 | # that comes after the header, which will be rendered as a contextual bit of 36 | # information. 37 | # 38 | # In addition, if you want to render a file with sphinx-gallery, it must match 39 | # the file naming structure that the gallery is configured to look for. By 40 | # default, this is `plot_*.py`. 41 | # 42 | # Interweaving code with text 43 | # --------------------------- 44 | # 45 | # Sphinx-gallery allows you to interweave code with your text. For example, if 46 | # put a few lines of text below... 47 | 48 | N = 1000 49 | 50 | # They will be rendered as regular code. Note that now I am typing in a 51 | # comment, because we've broken the chain of commented lines above. 52 | x = np.random.randn(N) 53 | 54 | # If we want to create another formatted block of text, we need to add a line 55 | # of `#` spanning the whole line below. Like this: 56 | 57 | ############################################################################### 58 | # Now we can once again have nicely formatted $t_{e}\chi^t$! 59 | 60 | # Let's create our y-variable so we can make some plots 61 | y = .2 * x + .4 * np.random.randn(N) 62 | 63 | ############################################################################### 64 | # Plotting images 65 | # --------------- 66 | # 67 | # Sphinx-gallery captures the images generated by matplotlib. This means that 68 | # we can plot things as normal, and these images will be grouped with the 69 | # text block that the fall underneath. For example, we could plot these two 70 | # variables and the image will be shown below: 71 | 72 | fig, ax = plt.subplots() 73 | ax.plot(x, y, 'o') 74 | 75 | ############################################################################### 76 | # Multiple images 77 | # --------------- 78 | # 79 | # If we want multiple images, this is easy too. Sphinx-gallery will group 80 | # everything together that's within the latest text block. 81 | 82 | fig, axs = plt.subplots(1, 2) 83 | axs[0].hist(x, bins=20) 84 | axs[1].hist(y, bins=20) 85 | 86 | fig, ax = plt.subplots() 87 | ax.hist2d(x, y, bins=20) 88 | 89 | ############################################################################### 90 | # Other kinds of formatting 91 | # ------------------------- 92 | # 93 | # Remember, rST can do all kinds of other cool stuff. We can even do things 94 | # like add references to other packages and insert images. Check out this 95 | # `guide `_ for 96 | # some sample rST code. 97 | # 98 | # .. image:: http://www.sphinx-doc.org/en/stable/_static/sphinxheader.png 99 | # :width: 80% 100 | # 101 | # In the meantime, enjoy sphinx-gallery! 102 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # Setuptools version should match setup.py; wheel because pip will insert it noisily 3 | requires = ["setuptools >= 24.2.0", "wheel"] 4 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==4.6.3 2 | coveralls 3 | pytest-cov 4 | flake8 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | matplotlib 4 | pandas 5 | -------------------------------------------------------------------------------- /scripts/Figure1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os.path as op\n", 12 | "import numpy as np\n", 13 | "import matplotlib.pyplot as plt\n", 14 | "%matplotlib inline" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 2, 20 | "metadata": { 21 | "collapsed": false 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "plt.style.use('ggplot')" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 3, 31 | "metadata": { 32 | "collapsed": false 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import shablona as sb\n", 37 | "data_path = op.join(sb.__path__[0], 'data')" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 4, 43 | "metadata": { 44 | "collapsed": false 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "ortho_x, ortho_y, ortho_n = sb.transform_data(op.join(data_path, 'ortho.csv'))\n", 49 | "para_x, para_y, para_n = sb.transform_data(op.join(data_path, 'para.csv'))" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 5, 55 | "metadata": { 56 | "collapsed": false 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "model = sb.Model()" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 6, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "ortho_fit = model.fit(ortho_x, ortho_y)\n", 72 | "para_fit = model.fit(para_x, para_y)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 7, 78 | "metadata": { 79 | "collapsed": false 80 | }, 81 | "outputs": [ 82 | { 83 | "data": { 84 | "text/plain": [ 85 | "array([ 0.46438638, 0.13845926])" 86 | ] 87 | }, 88 | "execution_count": 7, 89 | "metadata": {}, 90 | "output_type": "execute_result" 91 | } 92 | ], 93 | "source": [ 94 | "ortho_fit.params" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 8, 100 | "metadata": { 101 | "collapsed": false 102 | }, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "text/plain": [ 107 | "array([ 0.57456788, 0.13684096])" 108 | ] 109 | }, 110 | "execution_count": 8, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "para_fit.params" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 9, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [ 126 | { 127 | "data": { 128 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfUAAAHwCAYAAAC/hfaiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlclXXe//HX4bDIZgqKCK65R2YU5jZmWjp30aiVWjll\nk1PZoj/tnprRStvGsrLyHh3LJVPLpXQqTSYnu1Nvk6YRUzNyX1JBQEFDQdnO+f1xBCVZLuCcc13A\n+/l49GA517nO228HPlzf67vYnE6nExEREan1fMwOICIiIu6hou4BycnJZkeoNdRWxqidjFE7Gae2\nMqa2tZOKugfUtjeBmdRWxqidjFE7Gae2Mqa2tZOKuoiISB2hoi4iIlJH2DT6XUREpG7wNTuAO6Sm\nppodoZTQ0FDOnDljdoxaQW1ljNrJGLWTcWorY6zYTlFRUeU+pu53ERGROkJFXUREpI5QURcREakj\nVNRFRETqCBV1ERGROkJFXUREpI5QURcREakjVNRFRETqCBV1ERGROkJFXUREpI5QURcREakjVNRF\nRETqCBV1ERGROkJFXUREpI5QURcREakjVNRFRETqCBV1ERGROkJFXUREpI5QURcREakjVNRFRETq\nCBV1ERGROkJFXUREpI5QURcREakjVNRFRETqCBV1ERGROkJFXUREpI5QURcREakjfM0OICIi9Vdq\naiqzPpjFkawj5BXlEWAPoFVYK8beP5aoqCiz49U6KuoiIuJ1KSkp/OmNP7GrYBcnO56E9pc8mAMJ\nUxO4yu8qpj89nejoaNNy1jYq6iIiHpCdnc0773zKzp2nycuzERDgpGvXRjz22B00bNjQ7Him2pG8\ngz/89UEy+qaDXxkHBMPJa0/yfwX/x+1PDWbhcwvoFtPN6zlrIxV1ERE3ys3NZeLEeWzZ0oAjRx4E\n2pQ8tn79YT777H26dz/PtGkPExQUZFpOs+xI3sGwJ39P7l2nKh/V5QcZN6UxbMLvWTljiQq7AV4r\n6rNnz2bbtm00bNiQN998s8xjFixYwPbt2wkICODxxx+nbdu23oonIlJjubm53H33a3z//ctAZBlH\ntOHIkRc5ciSNw4cns3z5X+pVYU9JSeGBlx80VtCL+UDusFP84a+jWTN9tbriK+G10e/9+/fnmWee\nKffx77//nvT0dP72t7/xyCOPMH/+fG9FExFxi4kT51VQ0C8VydatLzNx4jxvxLKMP73xJ07cmF71\n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\nSUVORK5CYII=\n", 129 | "text/plain": [ 130 | "" 131 | ] 132 | }, 133 | "metadata": {}, 134 | "output_type": "display_data" 135 | } 136 | ], 137 | "source": [ 138 | "fig, ax = plt.subplots(1)\n", 139 | "x_predict = np.linspace(0, 1, 100)\n", 140 | "for x, y, n in zip(ortho_x, ortho_y, ortho_n):\n", 141 | " ax.plot(x, y, 'bo', markersize=n)\n", 142 | " ax.plot(x_predict, ortho_fit.predict(x_predict), 'b')\n", 143 | "\n", 144 | "for x,y,n in zip(para_x, para_y, para_n):\n", 145 | " ax.plot(x, y, 'go', markersize=n)\n", 146 | " ax.plot(x_predict, para_fit.predict(x_predict), 'g')\n", 147 | "\n", 148 | "ax.set_xlabel('Contrast in interval 1')\n", 149 | "ax.set_ylabel(\"Proportion answers '1'\")\n", 150 | "ax.set_ylim([-0.1, 1.1])\n", 151 | "ax.set_xlim([-0.1, 1.1])\n", 152 | "fig.set_size_inches([8,8])" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": { 159 | "collapsed": true 160 | }, 161 | "outputs": [], 162 | "source": [] 163 | } 164 | ], 165 | "metadata": { 166 | "kernelspec": { 167 | "display_name": "Python 3", 168 | "language": "python", 169 | "name": "python3" 170 | }, 171 | "language_info": { 172 | "codemirror_mode": { 173 | "name": "ipython", 174 | "version": 3 175 | }, 176 | "file_extension": ".py", 177 | "mimetype": "text/x-python", 178 | "name": "python", 179 | "nbconvert_exporter": "python", 180 | "pygments_lexer": "ipython3", 181 | "version": "3.4.3" 182 | } 183 | }, 184 | "nbformat": 4, 185 | "nbformat_minor": 0 186 | } 187 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from setuptools import setup, find_packages 4 | PACKAGES = find_packages() 5 | 6 | # Get version and release info, which is all stored in shablona/version.py 7 | ver_file = os.path.join('shablona', 'version.py') 8 | with open(ver_file) as f: 9 | exec(f.read()) 10 | 11 | # Give setuptools a hint to complain if it's too old a version 12 | # 24.2.0 added the python_requires option 13 | # Should match pyproject.toml 14 | SETUP_REQUIRES = ['setuptools >= 24.2.0'] 15 | # This enables setuptools to install wheel on-the-fly 16 | SETUP_REQUIRES += ['wheel'] if 'bdist_wheel' in sys.argv else [] 17 | 18 | opts = dict(name=NAME, 19 | maintainer=MAINTAINER, 20 | maintainer_email=MAINTAINER_EMAIL, 21 | description=DESCRIPTION, 22 | long_description=LONG_DESCRIPTION, 23 | url=URL, 24 | download_url=DOWNLOAD_URL, 25 | license=LICENSE, 26 | classifiers=CLASSIFIERS, 27 | author=AUTHOR, 28 | author_email=AUTHOR_EMAIL, 29 | platforms=PLATFORMS, 30 | version=VERSION, 31 | packages=PACKAGES, 32 | package_data=PACKAGE_DATA, 33 | install_requires=REQUIRES, 34 | python_requires=PYTHON_REQUIRES, 35 | setup_requires=SETUP_REQUIRES, 36 | requires=REQUIRES) 37 | 38 | 39 | if __name__ == '__main__': 40 | setup(**opts) 41 | -------------------------------------------------------------------------------- /shablona/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from .version import __version__ # noqa 3 | from .shablona import * # noqa 4 | -------------------------------------------------------------------------------- /shablona/data/ortho.csv: -------------------------------------------------------------------------------- 1 | contrast1,contrast2,answer 2 | 0.59999999999999998,0.29999999999999999,1 3 | 0.65000000000000002,0.29999999999999999,1 4 | 0.69999999999999996,0.29999999999999999,1 5 | 0.5,0.29999999999999999,1 6 | 0.10000000000000001,0.29999999999999999,2 7 | 0.01,0.29999999999999999,2 8 | 0.20000000000000001,0.29999999999999999,2 9 | 0.5,0.29999999999999999,1 10 | 0.10000000000000001,0.29999999999999999,2 11 | 0.90000000000000002,0.29999999999999999,1 12 | 0.90000000000000002,0.29999999999999999,1 13 | 0.55000000000000004,0.29999999999999999,1 14 | 0.40000000000000002,0.29999999999999999,1 15 | 0.20000000000000001,0.29999999999999999,2 16 | 0.20000000000000001,0.29999999999999999,2 17 | 0.29999999999999999,0.29999999999999999,2 18 | 0.65000000000000002,0.29999999999999999,1 19 | 0.20000000000000001,0.29999999999999999,2 20 | 0.5,0.29999999999999999,1 21 | 0.5,0.29999999999999999,1 22 | 0.01,0.29999999999999999,1 23 | 0.90000000000000002,0.29999999999999999,1 24 | 0.65000000000000002,0.29999999999999999,1 25 | 0.90000000000000002,0.29999999999999999,1 26 | 0.5,0.29999999999999999,1 27 | 0.01,0.29999999999999999,2 28 | 0.59999999999999998,0.29999999999999999,1 29 | 0.65000000000000002,0.29999999999999999,1 30 | 0.5,0.29999999999999999,2 31 | 0.01,0.29999999999999999,2 32 | 0.69999999999999996,0.29999999999999999,1 33 | 0.69999999999999996,0.29999999999999999,1 34 | 0.29999999999999999,0.29999999999999999,2 35 | 0.20000000000000001,0.29999999999999999,2 36 | 0.90000000000000002,0.29999999999999999,1 37 | 0.5,0.29999999999999999,2 38 | 0.10000000000000001,0.29999999999999999,1 39 | 0.01,0.29999999999999999,2 40 | 0.29999999999999999,0.29999999999999999,2 41 | 0.5,0.29999999999999999,2 42 | 0.69999999999999996,0.29999999999999999,1 43 | 0.69999999999999996,0.29999999999999999,1 44 | 0.90000000000000002,0.29999999999999999,1 45 | 0.90000000000000002,0.29999999999999999,1 46 | 0.65000000000000002,0.29999999999999999,1 47 | 0.40000000000000002,0.29999999999999999,1 48 | 0.65000000000000002,0.29999999999999999,1 49 | 0.01,0.29999999999999999,2 50 | 0.29999999999999999,0.29999999999999999,2 51 | 0.59999999999999998,0.29999999999999999,2 52 | 0.01,0.29999999999999999,2 53 | 0.20000000000000001,0.29999999999999999,2 54 | 0.10000000000000001,0.29999999999999999,2 55 | 0.5,0.29999999999999999,1 56 | 0.69999999999999996,0.29999999999999999,1 57 | 0.01,0.29999999999999999,2 58 | 0.10000000000000001,0.29999999999999999,2 59 | 0.55000000000000004,0.29999999999999999,1 60 | 0.01,0.29999999999999999,2 61 | 0.29999999999999999,0.29999999999999999,2 62 | 0.40000000000000002,0.29999999999999999,2 63 | 0.20000000000000001,0.29999999999999999,1 64 | 0.29999999999999999,0.29999999999999999,2 65 | 0.55000000000000004,0.29999999999999999,2 66 | 0.10000000000000001,0.29999999999999999,2 67 | 0.40000000000000002,0.29999999999999999,1 68 | 0.10000000000000001,0.29999999999999999,2 69 | 0.10000000000000001,0.29999999999999999,2 70 | 0.69999999999999996,0.29999999999999999,1 71 | 0.59999999999999998,0.29999999999999999,1 72 | 0.59999999999999998,0.29999999999999999,1 73 | 0.5,0.29999999999999999,1 74 | 0.40000000000000002,0.29999999999999999,2 75 | 0.55000000000000004,0.29999999999999999,1 76 | 0.29999999999999999,0.29999999999999999,2 77 | 0.55000000000000004,0.29999999999999999,2 78 | 0.10000000000000001,0.29999999999999999,2 79 | 0.65000000000000002,0.29999999999999999,1 80 | 0.5,0.29999999999999999,1 81 | 0.5,0.29999999999999999,2 82 | 0.65000000000000002,0.29999999999999999,1 83 | 0.55000000000000004,0.29999999999999999,2 84 | 0.29999999999999999,0.29999999999999999,2 85 | 0.55000000000000004,0.29999999999999999,1 86 | 0.55000000000000004,0.29999999999999999,1 87 | 0.65000000000000002,0.29999999999999999,1 88 | 0.5,0.29999999999999999,2 89 | 0.10000000000000001,0.29999999999999999,2 90 | 0.10000000000000001,0.29999999999999999,2 91 | 0.90000000000000002,0.29999999999999999,1 92 | 0.55000000000000004,0.29999999999999999,2 93 | 0.20000000000000001,0.29999999999999999,2 94 | 0.20000000000000001,0.29999999999999999,2 95 | 0.20000000000000001,0.29999999999999999,1 96 | 0.40000000000000002,0.29999999999999999,2 97 | 0.65000000000000002,0.29999999999999999,1 98 | 0.65000000000000002,0.29999999999999999,1 99 | 0.5,0.29999999999999999,1 100 | 0.59999999999999998,0.29999999999999999,2 101 | 0.65000000000000002,0.29999999999999999,1 102 | 0.5,0.29999999999999999,2 103 | 0.59999999999999998,0.29999999999999999,2 104 | 0.59999999999999998,0.29999999999999999,1 105 | 0.59999999999999998,0.29999999999999999,1 106 | 0.40000000000000002,0.29999999999999999,2 107 | 0.90000000000000002,0.29999999999999999,1 108 | 0.90000000000000002,0.29999999999999999,1 109 | 0.29999999999999999,0.29999999999999999,2 110 | 0.40000000000000002,0.29999999999999999,2 111 | 0.40000000000000002,0.29999999999999999,2 112 | 0.59999999999999998,0.29999999999999999,1 113 | 0.69999999999999996,0.29999999999999999,1 114 | 0.10000000000000001,0.29999999999999999,2 115 | 0.59999999999999998,0.29999999999999999,1 116 | 0.5,0.29999999999999999,1 117 | 0.55000000000000004,0.29999999999999999,1 118 | 0.55000000000000004,0.29999999999999999,1 119 | 0.5,0.29999999999999999,1 120 | 0.55000000000000004,0.29999999999999999,1 121 | 0.40000000000000002,0.29999999999999999,1 122 | 0.59999999999999998,0.29999999999999999,1 123 | 0.10000000000000001,0.29999999999999999,2 124 | 0.59999999999999998,0.29999999999999999,1 125 | 0.59999999999999998,0.29999999999999999,1 126 | 0.20000000000000001,0.29999999999999999,2 127 | 0.01,0.29999999999999999,2 128 | 0.5,0.29999999999999999,1 129 | 0.90000000000000002,0.29999999999999999,1 130 | 0.65000000000000002,0.29999999999999999,2 131 | 0.65000000000000002,0.29999999999999999,1 132 | 0.29999999999999999,0.29999999999999999,2 133 | 0.55000000000000004,0.29999999999999999,1 134 | 0.5,0.29999999999999999,2 135 | 0.90000000000000002,0.29999999999999999,1 136 | 0.90000000000000002,0.29999999999999999,1 137 | 0.65000000000000002,0.29999999999999999,1 138 | 0.20000000000000001,0.29999999999999999,2 139 | 0.40000000000000002,0.29999999999999999,2 140 | 0.65000000000000002,0.29999999999999999,1 141 | 0.69999999999999996,0.29999999999999999,1 142 | 0.90000000000000002,0.29999999999999999,1 143 | 0.69999999999999996,0.29999999999999999,1 144 | 0.40000000000000002,0.29999999999999999,1 145 | 0.55000000000000004,0.29999999999999999,1 146 | 0.40000000000000002,0.29999999999999999,2 147 | 0.29999999999999999,0.29999999999999999,2 148 | 0.69999999999999996,0.29999999999999999,1 149 | 0.29999999999999999,0.29999999999999999,2 150 | 0.59999999999999998,0.29999999999999999,1 151 | 0.40000000000000002,0.29999999999999999,2 152 | 0.55000000000000004,0.29999999999999999,2 153 | 0.20000000000000001,0.29999999999999999,2 154 | 0.01,0.29999999999999999,2 155 | 0.10000000000000001,0.29999999999999999,2 156 | 0.90000000000000002,0.29999999999999999,1 157 | 0.29999999999999999,0.29999999999999999,2 158 | 0.10000000000000001,0.29999999999999999,2 159 | 0.59999999999999998,0.29999999999999999,1 160 | 0.01,0.29999999999999999,2 161 | 0.20000000000000001,0.29999999999999999,2 162 | 0.65000000000000002,0.29999999999999999,1 163 | 0.59999999999999998,0.29999999999999999,1 164 | 0.69999999999999996,0.29999999999999999,1 165 | 0.20000000000000001,0.29999999999999999,2 166 | 0.65000000000000002,0.29999999999999999,1 167 | 0.65000000000000002,0.29999999999999999,1 168 | 0.10000000000000001,0.29999999999999999,1 169 | 0.59999999999999998,0.29999999999999999,1 170 | 0.90000000000000002,0.29999999999999999,1 171 | 0.29999999999999999,0.29999999999999999,2 172 | 0.69999999999999996,0.29999999999999999,1 173 | 0.40000000000000002,0.29999999999999999,1 174 | 0.20000000000000001,0.29999999999999999,2 175 | 0.69999999999999996,0.29999999999999999,1 176 | 0.01,0.29999999999999999,2 177 | 0.20000000000000001,0.29999999999999999,2 178 | 0.5,0.29999999999999999,2 179 | -------------------------------------------------------------------------------- /shablona/data/para.csv: -------------------------------------------------------------------------------- 1 | contrast1,contrast2,answer 2 | 0.10000000000000001,0.29999999999999999,2 3 | 0.90000000000000002,0.29999999999999999,1 4 | 0.90000000000000002,0.29999999999999999,1 5 | 0.40000000000000002,0.29999999999999999,2 6 | 0.01,0.29999999999999999,2 7 | 0.40000000000000002,0.29999999999999999,2 8 | 0.20000000000000001,0.29999999999999999,2 9 | 0.40000000000000002,0.29999999999999999,2 10 | 0.20000000000000001,0.29999999999999999,2 11 | 0.55000000000000004,0.29999999999999999,2 12 | 0.65000000000000002,0.29999999999999999,2 13 | 0.5,0.29999999999999999,2 14 | 0.29999999999999999,0.29999999999999999,2 15 | 0.20000000000000001,0.29999999999999999,2 16 | 0.90000000000000002,0.29999999999999999,1 17 | 0.5,0.29999999999999999,1 18 | 0.69999999999999996,0.29999999999999999,2 19 | 0.59999999999999998,0.29999999999999999,2 20 | 0.90000000000000002,0.29999999999999999,1 21 | 0.55000000000000004,0.29999999999999999,2 22 | 0.55000000000000004,0.29999999999999999,2 23 | 0.5,0.29999999999999999,2 24 | 0.20000000000000001,0.29999999999999999,2 25 | 0.01,0.29999999999999999,2 26 | 0.29999999999999999,0.29999999999999999,2 27 | 0.29999999999999999,0.29999999999999999,2 28 | 0.20000000000000001,0.29999999999999999,2 29 | 0.69999999999999996,0.29999999999999999,1 30 | 0.69999999999999996,0.29999999999999999,1 31 | 0.5,0.29999999999999999,2 32 | 0.10000000000000001,0.29999999999999999,2 33 | 0.59999999999999998,0.29999999999999999,2 34 | 0.20000000000000001,0.29999999999999999,2 35 | 0.59999999999999998,0.29999999999999999,2 36 | 0.65000000000000002,0.29999999999999999,1 37 | 0.55000000000000004,0.29999999999999999,1 38 | 0.01,0.29999999999999999,2 39 | 0.55000000000000004,0.29999999999999999,2 40 | 0.29999999999999999,0.29999999999999999,2 41 | 0.01,0.29999999999999999,2 42 | 0.5,0.29999999999999999,2 43 | 0.20000000000000001,0.29999999999999999,2 44 | 0.69999999999999996,0.29999999999999999,1 45 | 0.5,0.29999999999999999,1 46 | 0.90000000000000002,0.29999999999999999,1 47 | 0.55000000000000004,0.29999999999999999,1 48 | 0.59999999999999998,0.29999999999999999,1 49 | 0.59999999999999998,0.29999999999999999,2 50 | 0.01,0.29999999999999999,2 51 | 0.65000000000000002,0.29999999999999999,1 52 | 0.90000000000000002,0.29999999999999999,1 53 | 0.55000000000000004,0.29999999999999999,1 54 | 0.59999999999999998,0.29999999999999999,2 55 | 0.5,0.29999999999999999,2 56 | 0.55000000000000004,0.29999999999999999,2 57 | 0.5,0.29999999999999999,2 58 | 0.29999999999999999,0.29999999999999999,2 59 | 0.01,0.29999999999999999,2 60 | 0.55000000000000004,0.29999999999999999,2 61 | 0.59999999999999998,0.29999999999999999,1 62 | 0.01,0.29999999999999999,2 63 | 0.10000000000000001,0.29999999999999999,2 64 | 0.90000000000000002,0.29999999999999999,1 65 | 0.10000000000000001,0.29999999999999999,2 66 | 0.29999999999999999,0.29999999999999999,2 67 | 0.20000000000000001,0.29999999999999999,2 68 | 0.40000000000000002,0.29999999999999999,2 69 | 0.5,0.29999999999999999,2 70 | 0.65000000000000002,0.29999999999999999,1 71 | 0.69999999999999996,0.29999999999999999,1 72 | 0.5,0.29999999999999999,2 73 | 0.90000000000000002,0.29999999999999999,1 74 | 0.65000000000000002,0.29999999999999999,1 75 | 0.10000000000000001,0.29999999999999999,2 76 | 0.90000000000000002,0.29999999999999999,1 77 | 0.59999999999999998,0.29999999999999999,1 78 | 0.69999999999999996,0.29999999999999999,1 79 | 0.5,0.29999999999999999,1 80 | 0.20000000000000001,0.29999999999999999,2 81 | 0.10000000000000001,0.29999999999999999,2 82 | 0.55000000000000004,0.29999999999999999,1 83 | 0.10000000000000001,0.29999999999999999,2 84 | 0.59999999999999998,0.29999999999999999,1 85 | 0.90000000000000002,0.29999999999999999,1 86 | 0.5,0.29999999999999999,2 87 | 0.5,0.29999999999999999,2 88 | 0.5,0.29999999999999999,2 89 | 0.40000000000000002,0.29999999999999999,2 90 | 0.69999999999999996,0.29999999999999999,1 91 | 0.55000000000000004,0.29999999999999999,2 92 | 0.90000000000000002,0.29999999999999999,1 93 | 0.5,0.29999999999999999,1 94 | 0.01,0.29999999999999999,2 95 | 0.65000000000000002,0.29999999999999999,1 96 | 0.20000000000000001,0.29999999999999999,2 97 | 0.55000000000000004,0.29999999999999999,1 98 | 0.59999999999999998,0.29999999999999999,1 99 | 0.40000000000000002,0.29999999999999999,2 100 | 0.10000000000000001,0.29999999999999999,2 101 | 0.90000000000000002,0.29999999999999999,1 102 | 0.40000000000000002,0.29999999999999999,2 103 | 0.65000000000000002,0.29999999999999999,1 104 | 0.90000000000000002,0.29999999999999999,1 105 | 0.29999999999999999,0.29999999999999999,2 106 | 0.59999999999999998,0.29999999999999999,1 107 | 0.01,0.29999999999999999,2 108 | 0.29999999999999999,0.29999999999999999,2 109 | 0.65000000000000002,0.29999999999999999,1 110 | 0.10000000000000001,0.29999999999999999,2 111 | 0.29999999999999999,0.29999999999999999,2 112 | 0.59999999999999998,0.29999999999999999,1 113 | 0.01,0.29999999999999999,2 114 | 0.01,0.29999999999999999,2 115 | 0.40000000000000002,0.29999999999999999,2 116 | 0.90000000000000002,0.29999999999999999,1 117 | 0.29999999999999999,0.29999999999999999,2 118 | 0.20000000000000001,0.29999999999999999,2 119 | 0.40000000000000002,0.29999999999999999,2 120 | 0.20000000000000001,0.29999999999999999,1 121 | 0.5,0.29999999999999999,2 122 | 0.90000000000000002,0.29999999999999999,1 123 | 0.20000000000000001,0.29999999999999999,2 124 | 0.59999999999999998,0.29999999999999999,2 125 | 0.10000000000000001,0.29999999999999999,2 126 | 0.40000000000000002,0.29999999999999999,2 127 | 0.29999999999999999,0.29999999999999999,2 128 | 0.20000000000000001,0.29999999999999999,2 129 | 0.90000000000000002,0.29999999999999999,1 130 | 0.01,0.29999999999999999,2 131 | 0.65000000000000002,0.29999999999999999,2 132 | 0.01,0.29999999999999999,2 133 | 0.29999999999999999,0.29999999999999999,2 134 | 0.5,0.29999999999999999,1 135 | 0.69999999999999996,0.29999999999999999,2 136 | 0.10000000000000001,0.29999999999999999,2 137 | 0.01,0.29999999999999999,2 138 | 0.59999999999999998,0.29999999999999999,2 139 | 0.65000000000000002,0.29999999999999999,1 140 | 0.10000000000000001,0.29999999999999999,1 141 | 0.5,0.29999999999999999,1 142 | 0.55000000000000004,0.29999999999999999,2 143 | 0.90000000000000002,0.29999999999999999,1 144 | 0.55000000000000004,0.29999999999999999,1 145 | 0.20000000000000001,0.29999999999999999,2 146 | 0.69999999999999996,0.29999999999999999,1 147 | 0.29999999999999999,0.29999999999999999,2 148 | 0.40000000000000002,0.29999999999999999,2 149 | 0.01,0.29999999999999999,2 150 | 0.5,0.29999999999999999,2 151 | 0.59999999999999998,0.29999999999999999,1 152 | 0.01,0.29999999999999999,2 153 | 0.40000000000000002,0.29999999999999999,2 154 | 0.10000000000000001,0.29999999999999999,2 155 | 0.5,0.29999999999999999,1 156 | 0.69999999999999996,0.29999999999999999,2 157 | 0.90000000000000002,0.29999999999999999,1 158 | 0.10000000000000001,0.29999999999999999,2 159 | 0.40000000000000002,0.29999999999999999,2 160 | 0.59999999999999998,0.29999999999999999,1 161 | 0.29999999999999999,0.29999999999999999,2 162 | 0.20000000000000001,0.29999999999999999,2 163 | 0.40000000000000002,0.29999999999999999,2 164 | 0.40000000000000002,0.29999999999999999,2 165 | 0.55000000000000004,0.29999999999999999,2 166 | 0.40000000000000002,0.29999999999999999,2 167 | 0.55000000000000004,0.29999999999999999,1 168 | -------------------------------------------------------------------------------- /shablona/due.py: -------------------------------------------------------------------------------- 1 | # emacs: at the end of the file 2 | # ex: set sts=4 ts=4 sw=4 et: 3 | # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### # 4 | """ 5 | 6 | Stub file for a guaranteed safe import of duecredit constructs: if duecredit 7 | is not available. 8 | 9 | To use it, place it into your project codebase to be imported, e.g. copy as 10 | 11 | cp stub.py /path/tomodule/module/due.py 12 | 13 | Note that it might be better to avoid naming it duecredit.py to avoid shadowing 14 | installed duecredit. 15 | 16 | Then use in your code as 17 | 18 | from .due import due, Doi, BibTeX 19 | 20 | See https://github.com/duecredit/duecredit/blob/master/README.md for examples. 21 | 22 | Origin: Originally a part of the duecredit 23 | Copyright: 2015-2016 DueCredit developers 24 | License: BSD-2 25 | """ 26 | from __future__ import absolute_import, division, print_function 27 | 28 | __version__ = '0.0.5' 29 | 30 | 31 | class InactiveDueCreditCollector(object): 32 | """Just a stub at the Collector which would not do anything""" 33 | def _donothing(self, *args, **kwargs): 34 | """Perform no good and no bad""" 35 | pass 36 | 37 | def dcite(self, *args, **kwargs): 38 | """If I could cite I would""" 39 | def nondecorating_decorator(func): 40 | return func 41 | return nondecorating_decorator 42 | 43 | cite = load = add = _donothing 44 | 45 | def __repr__(self): 46 | return self.__class__.__name__ + '()' 47 | 48 | 49 | def _donothing_func(*args, **kwargs): 50 | """Perform no good and no bad""" 51 | pass 52 | 53 | 54 | try: 55 | from duecredit import due, BibTeX, Doi, Url 56 | if 'due' in locals() and not hasattr(due, 'cite'): 57 | raise RuntimeError( 58 | "Imported due lacks .cite. DueCredit is now disabled") 59 | except Exception as e: 60 | if type(e).__name__ != 'ImportError': 61 | import logging 62 | logging.getLogger("duecredit").error( 63 | "Failed to import duecredit due to %s" % str(e)) 64 | # Initiate due stub 65 | due = InactiveDueCreditCollector() 66 | BibTeX = Doi = Url = _donothing_func 67 | 68 | # Emacs mode definitions 69 | # Local Variables: 70 | # mode: python 71 | # py-indent-offset: 4 72 | # tab-width: 4 73 | # indent-tabs-mode: nil 74 | # End: 75 | -------------------------------------------------------------------------------- /shablona/shablona.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | import numpy as np 3 | import pandas as pd 4 | import scipy.optimize as opt 5 | from scipy.special import erf 6 | from .due import due, Doi 7 | 8 | __all__ = ["Model", "Fit", "opt_err_func", "transform_data", "cumgauss"] 9 | 10 | 11 | # Use duecredit (duecredit.org) to provide a citation to relevant work to 12 | # be cited. This does nothing, unless the user has duecredit installed, 13 | # And calls this with duecredit (as in `python -m duecredit script.py`): 14 | due.cite(Doi("10.1167/13.9.30"), 15 | description="Template project for small scientific Python projects", 16 | tags=["reference-implementation"], 17 | path='shablona') 18 | 19 | 20 | def transform_data(data): 21 | """ 22 | Function that takes experimental data and gives us the 23 | dependent/independent variables for analysis. 24 | 25 | Parameters 26 | ---------- 27 | data : Pandas DataFrame or string. 28 | If this is a DataFrame, it should have the columns `contrast1` and 29 | `answer` from which the dependent and independent variables will be 30 | extracted. If this is a string, it should be the full path to a csv 31 | file that contains data that can be read into a DataFrame with this 32 | specification. 33 | 34 | Returns 35 | ------- 36 | x : array 37 | The unique contrast differences. 38 | y : array 39 | The proportion of '2' answers in each contrast difference 40 | n : array 41 | The number of trials in each x,y condition 42 | """ 43 | if isinstance(data, str): 44 | data = pd.read_csv(data) 45 | 46 | contrast1 = data['contrast1'] 47 | answers = data['answer'] 48 | 49 | x = np.unique(contrast1) 50 | y = [] 51 | n = [] 52 | 53 | for c in x: 54 | idx = np.where(contrast1 == c) 55 | n.append(float(len(idx[0]))) 56 | answer1 = len(np.where(answers[idx[0]] == 1)[0]) 57 | y.append(answer1 / n[-1]) 58 | return x, y, n 59 | 60 | 61 | def cumgauss(x, mu, sigma): 62 | """ 63 | The cumulative Gaussian at x, for the distribution with mean mu and 64 | standard deviation sigma. 65 | 66 | Parameters 67 | ---------- 68 | x : float or array 69 | The values of x over which to evaluate the cumulative Gaussian function 70 | 71 | mu : float 72 | The mean parameter. Determines the x value at which the y value is 0.5 73 | 74 | sigma : float 75 | The variance parameter. Determines the slope of the curve at the point 76 | of Deflection 77 | 78 | Returns 79 | ------- 80 | 81 | g : float or array 82 | The cumulative gaussian with mean $\\mu$ and variance $\\sigma$ 83 | evaluated at all points in `x`. 84 | 85 | Notes 86 | ----- 87 | Based on: 88 | http://en.wikipedia.org/wiki/Normal_distribution#Cumulative_distribution_function 89 | 90 | The cumulative Gaussian function is defined as: 91 | 92 | .. math:: 93 | 94 | \\Phi(x) = \\frac{1}{2} [1 + erf(\\frac{x}{\\sqrt{2}})] 95 | 96 | Where, $erf$, the error function is defined as: 97 | 98 | .. math:: 99 | 100 | erf(x) = \\frac{1}{\\sqrt{\\pi}} \\int_{-x}^{x} e^{t^2} dt 101 | """ 102 | return 0.5 * (1 + erf((x - mu) / (np.sqrt(2) * sigma))) 103 | 104 | 105 | def opt_err_func(params, x, y, func): 106 | """ 107 | Error function for fitting a function using non-linear optimization. 108 | 109 | Parameters 110 | ---------- 111 | params : tuple 112 | A tuple with the parameters of `func` according to their order of 113 | input 114 | 115 | x : float array 116 | An independent variable. 117 | 118 | y : float array 119 | The dependent variable. 120 | 121 | func : function 122 | A function with inputs: `(x, *params)` 123 | 124 | Returns 125 | ------- 126 | float array 127 | The marginals of the fit to x/y given the params 128 | """ 129 | return y - func(x, *params) 130 | 131 | 132 | class Model(object): 133 | """Class for fitting cumulative Gaussian functions to data""" 134 | def __init__(self, func=cumgauss): 135 | """ Initialize a model object. 136 | 137 | Parameters 138 | ---------- 139 | data : Pandas DataFrame 140 | Data from a subjective contrast judgement experiment 141 | 142 | func : callable, optional 143 | A function that relates x and y through a set of parameters. 144 | Default: :func:`cumgauss` 145 | """ 146 | self.func = func 147 | 148 | def fit(self, x, y, initial=[0.5, 1]): 149 | """ 150 | Fit a Model to data. 151 | 152 | Parameters 153 | ---------- 154 | x : float or array 155 | The independent variable: contrast values presented in the 156 | experiment 157 | y : float or array 158 | The dependent variable 159 | 160 | Returns 161 | ------- 162 | fit : :class:`Fit` instance 163 | A :class:`Fit` object that contains the parameters of the model. 164 | 165 | """ 166 | params, _ = opt.leastsq(opt_err_func, initial, 167 | args=(x, y, self.func)) 168 | return Fit(self, params) 169 | 170 | 171 | class Fit(object): 172 | """ 173 | Class for representing a fit of a model to data 174 | """ 175 | def __init__(self, model, params): 176 | """ 177 | Initialize a :class:`Fit` object. 178 | 179 | Parameters 180 | ---------- 181 | model : a :class:`Model` instance 182 | An object representing the model used 183 | 184 | params : array or list 185 | The parameters of the model evaluated for the data 186 | 187 | """ 188 | self.model = model 189 | self.params = params 190 | 191 | def predict(self, x): 192 | """ 193 | Predict values of the dependent variable based on values of the 194 | indpendent variable. 195 | 196 | Parameters 197 | ---------- 198 | x : float or array 199 | Values of the independent variable. Can be values presented in 200 | the experiment. For out-of-sample prediction (e.g. in 201 | cross-validation), these can be values 202 | that were not presented in the experiment. 203 | 204 | Returns 205 | ------- 206 | y : float or array 207 | Predicted values of the dependent variable, corresponding to 208 | values of the independent variable. 209 | """ 210 | return self.model.func(x, *self.params) 211 | -------------------------------------------------------------------------------- /shablona/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwescience/shablona/ab082ab54c933eec2f4a49a9c80c99f31532ac17/shablona/tests/__init__.py -------------------------------------------------------------------------------- /shablona/tests/test_shablona.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | import os.path as op 3 | import numpy as np 4 | import pandas as pd 5 | import numpy.testing as npt 6 | import shablona as sb 7 | 8 | data_path = op.join(sb.__path__[0], 'data') 9 | 10 | 11 | def test_transform_data(): 12 | """ 13 | Testing the transformation of the data from raw data to functions 14 | used for fitting a function. 15 | 16 | """ 17 | # We start with actual data. We test here just that reading the data in 18 | # different ways ultimately generates the same arrays. 19 | ortho = pd.read_csv(op.join(data_path, 'ortho.csv')) 20 | x1, y1, n1 = sb.transform_data(ortho) 21 | x2, y2, n2 = sb.transform_data(op.join(data_path, 'ortho.csv')) 22 | npt.assert_equal(x1, x2) 23 | npt.assert_equal(y1, y2) 24 | # We can also be a bit more critical, by testing with data that we 25 | # generate, and should produce a particular answer: 26 | my_data = pd.DataFrame( 27 | np.array([[0.1, 2], [0.1, 1], [0.2, 2], [0.2, 2], [0.3, 1], 28 | [0.3, 1]]), 29 | columns=['contrast1', 'answer']) 30 | my_x, my_y, my_n = sb.transform_data(my_data) 31 | npt.assert_equal(my_x, np.array([0.1, 0.2, 0.3])) 32 | npt.assert_equal(my_y, np.array([0.5, 0, 1.0])) 33 | npt.assert_equal(my_n, np.array([2, 2, 2])) 34 | 35 | 36 | def test_cum_gauss(): 37 | sigma = 1 38 | mu = 0 39 | x = np.linspace(-1, 1, 12) 40 | y = sb.cumgauss(x, mu, sigma) 41 | # A basic test that the input and output have the same shape: 42 | npt.assert_equal(y.shape, x.shape) 43 | # The function evaluated over items symmetrical about mu should be 44 | # symmetrical relative to 0 and 1: 45 | npt.assert_equal(y[0], 1 - y[-1]) 46 | # Approximately 68% of the Gaussian distribution is in mu +/- sigma, so 47 | # the value of the cumulative Gaussian at mu - sigma should be 48 | # approximately equal to (1 - 0.68/2). Note the low precision! 49 | npt.assert_almost_equal(y[0], (1 - 0.68) / 2, decimal=2) 50 | 51 | 52 | def test_opt_err_func(): 53 | # We define a truly silly function, that returns its input, regardless of 54 | # the params: 55 | def my_silly_func(x, my_first_silly_param, my_other_silly_param): 56 | return x 57 | 58 | # The silly function takes two parameters and ignores them 59 | my_params = [1, 10] 60 | my_x = np.linspace(-1, 1, 12) 61 | my_y = my_x 62 | my_err = sb.opt_err_func(my_params, my_x, my_y, my_silly_func) 63 | # Since x and y are equal, the error is zero: 64 | npt.assert_equal(my_err, np.zeros(my_x.shape[0])) 65 | 66 | # Let's consider a slightly less silly function, that implements a linear 67 | # relationship between inputs and outputs: 68 | def not_so_silly_func(x, a, b): 69 | return x * a + b 70 | 71 | my_params = [1, 10] 72 | my_x = np.linspace(-1, 1, 12) 73 | # To test this, we calculate the relationship explicitely: 74 | my_y = my_x * my_params[0] + my_params[1] 75 | my_err = sb.opt_err_func(my_params, my_x, my_y, not_so_silly_func) 76 | # Since x and y are equal, the error is zero: 77 | npt.assert_equal(my_err, np.zeros(my_x.shape[0])) 78 | 79 | 80 | def test_Model(): 81 | """ """ 82 | M = sb.Model() 83 | x = np.linspace(0.1, 0.9, 22) 84 | target_mu = 0.5 85 | target_sigma = 1 86 | target_y = sb.cumgauss(x, target_mu, target_sigma) 87 | F = M.fit(x, target_y, initial=[target_mu, target_sigma]) 88 | npt.assert_equal(F.predict(x), target_y) 89 | 90 | 91 | def test_params_regression(): 92 | """ 93 | Test for regressions in model parameter values from provided data 94 | """ 95 | 96 | model = sb.Model() 97 | ortho_x, ortho_y, ortho_n = sb.transform_data(op.join(data_path, 98 | 'ortho.csv')) 99 | 100 | para_x, para_y, para_n = sb.transform_data(op.join(data_path, 101 | 'para.csv')) 102 | 103 | ortho_fit = model.fit(ortho_x, ortho_y) 104 | para_fit = model.fit(para_x, para_y) 105 | 106 | npt.assert_almost_equal(ortho_fit.params[0], 0.46438638) 107 | npt.assert_almost_equal(ortho_fit.params[1], 0.13845926) 108 | npt.assert_almost_equal(para_fit.params[0], 0.57456788) 109 | npt.assert_almost_equal(para_fit.params[1], 0.13684096) 110 | -------------------------------------------------------------------------------- /shablona/version.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from os.path import join as pjoin 3 | 4 | # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" 5 | _version_major = 0 6 | _version_minor = 1 7 | _version_micro = '' # use '' for first of series, number for 1 and above 8 | _version_extra = 'dev' 9 | # _version_extra = '' # Uncomment this for full releases 10 | 11 | # Construct full version string from these. 12 | _ver = [_version_major, _version_minor] 13 | if _version_micro: 14 | _ver.append(_version_micro) 15 | if _version_extra: 16 | _ver.append(_version_extra) 17 | 18 | __version__ = '.'.join(map(str, _ver)) 19 | 20 | CLASSIFIERS = ["Development Status :: 3 - Alpha", 21 | "Environment :: Console", 22 | "Intended Audience :: Science/Research", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | "Programming Language :: Python", 26 | "Topic :: Scientific/Engineering"] 27 | 28 | # Description should be a one-liner: 29 | description = "shablona: a template for small scientific Python projects" 30 | # Long description will go up on the pypi page 31 | long_description = """ 32 | 33 | Shablona 34 | ======== 35 | Shablona is a template project for small scientific Python projects. 36 | 37 | It contains software implementations of an analysis of some simple data, but 38 | more importantly, it contains infrastructure for testing, documentation, 39 | continuous integration and deployment, which can be easily adapted 40 | to use in other projects. 41 | 42 | To get started using these components in your own software, please go to the 43 | repository README_. 44 | 45 | .. _README: https://github.com/uwescience/shablona/blob/master/README.md 46 | 47 | License 48 | ======= 49 | ``shablona`` is licensed under the terms of the MIT license. See the file 50 | "LICENSE" for information on the history of this software, terms & conditions 51 | for usage, and a DISCLAIMER OF ALL WARRANTIES. 52 | 53 | All trademarks referenced herein are property of their respective holders. 54 | 55 | Copyright (c) 2015--, Ariel Rokem, The University of Washington 56 | eScience Institute. 57 | """ 58 | 59 | NAME = "shablona" 60 | MAINTAINER = "Ariel Rokem" 61 | MAINTAINER_EMAIL = "arokem@gmail.com" 62 | DESCRIPTION = description 63 | LONG_DESCRIPTION = long_description 64 | URL = "http://github.com/uwescience/shablona" 65 | DOWNLOAD_URL = "" 66 | LICENSE = "MIT" 67 | AUTHOR = "Ariel Rokem" 68 | AUTHOR_EMAIL = "arokem@gmail.com" 69 | PLATFORMS = "OS Independent" 70 | MAJOR = _version_major 71 | MINOR = _version_minor 72 | MICRO = _version_micro 73 | VERSION = __version__ 74 | PACKAGE_DATA = {'shablona': [pjoin('data', '*')]} 75 | REQUIRES = ["numpy"] 76 | PYTHON_REQUIRES = ">= 3.5" 77 | --------------------------------------------------------------------------------