Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tilt resize #237

Merged
merged 12 commits into from
Jul 14, 2023
Merged
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ jobs:
- name: install environment
run: |
python -m pip install --upgrade pip
pip install --quiet ".[tests]"
pip install --quiet .
pip install --quiet pytest

# ===== Tests =====
- name: install tests
Expand Down
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
args: [--line-length=79]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.278
hooks:
- id: ruff
args: [--fix]
128 changes: 35 additions & 93 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,125 +1,67 @@
# Contributing Guide!
# Contributing Guide

Note: This document is out of date and in the process of being updated!
dLux is an open-source framework and as such is very welcoming to contributions via pull requests! If you would like to contribute but are unfamiliar with any of this process, as I imagine many coming from a science background will be, please feel free to reach out to me at my [email]([email protected]) or on [twitter](https://twitter.com/gradientrider) and I will be happy to help you through the process!

---
## Units:
All units within ∂Lux are SI!

---
## Adding to documentation
When adding to the documentation, there are a few steps that need to be done:
1. Create the class or function and add the docstring
2. Create a `.md` file in the docs/ directory
3. Point to the class/function as so `::: dLux.module.new_class`
4. Edit the `mkdocs.yml` file to add the new class
5. Run `mkdocs serve` to deploy the docs locally at `localhost:8000` (put this into a browser)
## Getting Started

---
## Building and running unit-tests
Any added functionality should be unit-tested! This is done with `pytest` in the `tests/` directory. Please have a look at the testing module and try to create tests to match the style!
Firstly, you will need to fork the repository to your own github account. This will allow you to make changes to the code and then submit a pull request to the main repository. To do this, click the fork button in the top right of the repository page. This will create a copy of the repository in your own account that you can make changes to and then request to merge with the main repository.

There are three main things we want to test:
1. Constructor assert statements and input types. Ensure that the correct error is thrown and that classes cant be constructed with incompatible inputs.
2. Test that the different run time logic branches evaluate.
3. Test that the different run time logic branches do not return nan or infinite values.
---
Next, you will need to clone the repository to your local machine. To do this, open a terminal and navigate to the directory you would like to clone the repository to. Then run the following command:

## Typing conventions
So we have worked out a general typing convention for the software. There are two main data-types we care about: Jax data types, and others. We denote any jax-types with the `Array` typing hint. This is simply defined as a`jax.numpy.ndarray`, so at the start of any files one should have `Array = np.ndarray`. *Any* jax arrays should use this type-hint, regardless of data-type. All other type-hints for ∂Lux type objects should refer to the *base* object type assuming it is not defined inside that script, i.e.:
> dLux.wavefronts.Wavefront
> dLux.propagators.Propagator
> dLux.optics.OpticalLayer
> dLux.base.Instrument
```bash
https://github.com/your-username-here/dLux.git
cd dLux
pip install '.[dev]'
```

For classes defined in the script, add `from __future__ import annotations` to the first line of the script in order to be able to reference these types within the script.
Then you will need to install the pre-commit hooks. This will ensure that the code is formatted correctly and that the unit tests pass before you can commit your changes. To do this, run the following command:

```bash
pre-commit install
```

---
## Class Inheritance
∂Lux classes are built from [`Equinox`](https://github.com/patrick-kidger/equinox) and [`Zodiax`](https://github.com/LouisDesdoigts/zodiax). Any new class types should inherit from either the `zodiax.Base` or `zodiax.ExtendedBase` classes, which inherit from `equinox.Module`, giving the full functionality from both of these packages.
This will ensure that any changes you make will adhere to the code style and formatting guidelines of the rest of the package!

---
## Code Style & Formatting

> All imported functionality should import the specific method used, as opposed to a full package.

For example rather than importing whole package:
```python
import abc
## Making Changes

class Foo(abc.ABC):
pass
```
Next you can start to make any changes you desire!

We want to specifically import the function used:
```python
from abc import ABC

class Foo(ABC):
pass
```
**Unit Tests**

> Functions and methods with more than two inputs should spread those inputs over multiple lines, using spaces to format alignment of type hints and default values to help with code readability.
It is important that any changes you make are tested to ensure that they work as intended and do not break any existing functionality. If you are creating _new_ functionality you will need to create some new unit tests, otherwise you should be able to modify the existing tests.

For example rather than this:
```python
def Foo(self: Foo, a: Array = None, b: Array=np.ones(2), c: int=2) -> float:
pass
```
To ensure that everything is working as expected, you can run the unit tests by running the following command:

Format like this:
```python
def Foo(self : Foo,
a : Array = None,
b : Array = np.ones(2),
c : int = 2) -> float:
pass
```bash
pytest tests/*
```

### Constructors
> Data type enforcing for jax-type arrays: Constructors should be able to take in lists, numpy arrays and python floats, but ensure that they are correctly formatted into jax-type arrays like follows:
This will run all the scripts labelled with `test_` in the `tests` directory. If you would like to run a specific test, you can run the following command:

```python
self.parameter = np.asarray(input_parameter, dtype=float)
```bash
pytest tests/test_file.py
```

This ensures flexibility in the input types and that all data types will match the default jax-32 or -64 bit types.
Note that just because the tests pass on your local machine, that does not mean that it will necessarily pass on all others! This can be due to a number of reasons such a different operating system, different python version, or different dependencies. This is why github actions are used to run the unit tests on a number of different operating systems and python versions. This should help ensure that the code works as expected on all platforms.

**Documentation**

> All constructor methods should use assert statements to ensure that inputs are correctly formatted at init time. The goal should be if no errors are thrown at init time then all the class methods should work as intended and have correct dimensionality. For ∂Lux this would typically be testing jax-type and array dimensionality. Do be sure that these enforce correct dimensionality, not just that errors are not thrown.
Any changes you make should also be appropriately documented! For small API changes this shouldn't require any changes, however if you are adding new functionality you will need to add some documentation. This can be done by modifying the appropriates files in the `docs` directory.

For example if an attribute is a scalar, most methods will work identically if it is zero- or one-dimensional. For example, enforcing zero-dimensionality for scalars:
```python
input_parameter = [1e3]
To build the documentation locally and make sure everything is working correctly, you can run the following command:

self.parameter = np.asarray(input_parameter, dtype=float)
assert self.parameter.ndim == 0, \
("input_parameter must a scalar array, i.e. zero dimensional")
```bash
mkdocs serve
```

### Non-differentiable parameters
> By default, all parameters should be jax-types. However, parameters that define array shapes or logical flow can be python types (ie int, bool). This prevents issues with jax tracing through arrays of integers which can cause jax errors at runtime.

### Internal logic flow
> Most internal logic within ∂Lux should be achieved using regular python logic. In some rare cases, `jax.lax.cond` should be used, but *only* if the internal logical flow can change during run time. Ie any logic that uses a boolean class attribute can not change during run time, and so should use regular python logic. This helps jax trace through the code and reduces compile time.

### Setter and Getters
> By default, getters and setter should be avoided. Most getting and setting can be achieved with nice syntax using the `Zodiax` methods. There are however some exceptions.

> Setter: Some class types such as `Wavefronts` track a lot of parameters that change a lot throughout runtime and so the setter methods are used in conjunction with assert statements to ensure that parameters are not set incorrectly and that the errors are raised at the point where the issue arises, as opposed to some other method receiving an incorrectly shaped array.

> Getters: For some classes we want to have parameterised values, i.e. binary stars using separation and field angle. However, optical modelling methods use Cartesian position inputs, so the base `Source` class implements an abstract `.get_position()` method to be overwritten by all child classes. In the case of the `BinarySource` class this method uses the separation and field angle to generate these Cartesian values at run time. This means that all other classes in ∂Lux can assume Cartesian positional values and use a single method when working with any `Source` class. Furthermore, getters can be used to generate useful values. For example wavefronts are store pixel scale and npixels, but implement a class property method for the diameter, which is much more useful for the `Propagator` classes.


### Hidden methods
> In general hidden class methods should be avoided and classes should try to implement methods that allow for them to used stand-alone. For example the `CompoundAperture` class implements the `construct_combined_aperture` and `get_aperture` methods that allow for users to directly construct and output the internally stored apertures, or use the `make_aperture` method to construct individual apertures.


This will build the documentation and serve it on a local server. You can then navigate to `localhost:8000` in your browser to view the documentation.

---
## Non-Circular imports
To prevent circular imports, you can not import any specific classes or methods within the package, you instead need to import the whole pacakge (ie `import dLux`) and then in the code, refer to specific function (ie `dLux.sources.Source`). This is inconvenient, but just the way python works.

> exception: The utils package
> The utils package is the collection of functions that operate *independently* of the package, and so can be imported as normal, i.e. `from dLux.utils.coordinates import radians_to_arcseconds` etc.
## Contributing the Changes

After these steps have been completed, you can commit your changes and push them to your forked repository. These changes should have its formatting and linting checked by the pre-commit hooks. If there are any issues, you will need to fix them before you can commit your changes. Once you have pushed your changes to your forked repository, you can submit a pull request to the main repository. This will allow the maintainers to review your changes and merge them into the main repository!
15 changes: 3 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ The ∂Lux framework is built in [Zodiax](https://github.com/LouisDesdoigts/zodi
>
> - [Automatic Vectorisation](https://jax.readthedocs.io/en/latest/jax-101/03-vectorization.html): Allows for simple parallelism across hardware and asynchronous execution
>
> - [Object-Oriented Jax](https://docs.kidger.site/equinox/all-of-equinox/): Allows for differentiable classes that are recognised as a valid _Jax_ type
>
> - [Inbuilt Neural Networks](https://docs.kidger.site/equinox/api/nn/linear/): Has pre-built neural network layer classes
>
> - [Path-Based Pytree Interface](docs/usage.md): Path based indexing allows for easy interfacing with large and highly nested physical models
>
> - [Leaf Manipulation Methods](docs/usage.md): Inbuilt methods allow for easy manipulation of Pytrees mirroring the _Jax_ Array API

For an overview of these capabilities and different optimisation methods in [Zodiax](https://github.com/LouisDesdoigts/zodiax), please go through this [Zodiax Tutorial](https://louisdesdoigts.github.io/zodiax/docs/usage/).

Expand All @@ -41,15 +35,12 @@ Requires: Python 3.8+, Jax 0.4.3+, Zodiax 0.4+

Installation: ```pip install dLux```

Docs installation: ```pip install "dLux[docs]"```

Test installation: ```pip install "dLux[tests]"```

If you want to run the tutorials locally, you can install the 'extra' dependencies like so: ```pip install 'dLux[extras]'```

## Collaboration & Development

We are always looking to collaborate and further develop this software! We have focused on flexibility and ease of development, so if you have a project you want to use ∂Lux for, but it currently does not have the required capabilities, don't hesitate to [email me]([email protected]) and we can discuss how to implement and merge it! Similarly, you can take a look at the `CONTRIBUTING.md` file.
We are always looking to collaborate and further develop this software! We have focused on flexibility and ease of development, so if you have a project you want to use ∂Lux for, but it currently does not have the required capabilities, have general questions, thoughts or ideas, don't hesitate to [email me]([email protected]) or contact me on [twitter](https://twitter.com/gradientrider)! More details about contributing can be found in our [contributing guide](CONTRIBUTING.md).

## Publications

We have a multitude of publications in the pipeline using dLux, some built from our tutorials. To start we would recommend looking at [this invited talk](https://louisdesdoigts.github.io/diff_optics/#/0/3) on ∂Lux which gives a good overview and has an attached recording of it being presented! We also have [this poster](https://spie.org/astronomical-telescopes-instrumentation/presentation/Optical-design-analysis-and-calibration-using-Lux/12180-160)!
We have a multitude of publications in the pipeline using dLux, some built from our tutorials. To start we would recommend looking at [this invited talk](https://louisdesdoigts.github.io/diff_optics/#/0/3) on ∂Lux which gives a good overview and has an attached recording of it being presented! We also have [this poster](https://spie.org/astronomical-telescopes-instrumentation/presentation/Optical-design-analysis-and-calibration-using-Lux/12180-160)!
94 changes: 71 additions & 23 deletions dLux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name = "dLux"
__version__ = "0.13.1"
import importlib.metadata


__version__ = importlib.metadata.version("dLux")

# Import as modules
# Wavefronts and Optics
Expand All @@ -22,31 +24,80 @@
from . import spectra

# Sub Modules
from . import utils
from . import utils as utils


# Import core functions from modules
# Wavefronts and Optics
from .wavefronts import *
from .optics import *
from .optical_layers import *
from .propagators import *
from .apertures import *
from .aberrations import *
from .wavefronts import (
Wavefront as Wavefront,
FresnelWavefront as FresnelWavefront,
)
from .optics import (
AngularOptics as AngularOptics,
CartesianOptics as CartesianOptics,
FlexibleOptics as FlexibleOptics,
LayeredOptics as LayeredOptics,
)
from .optical_layers import (
Optic as Optic,
PhaseOptic as PhaseOptic,
BasisOptic as BasisOptic,
PhaseBasisOptic as PhaseBasisOptic,
Tilt as Tilt,
Normalise as Normalise,
Rotate as Rotate,
Flip as Flip,
Resize as Resize,
)
from .propagators import (
MFT as MFT,
FFT as FFT,
ShiftedMFT as ShiftedMFT,
FarFieldFresnel as FarFieldFresnel,
)
from .apertures import (
CircularAperture as CircularAperture,
RectangularAperture as RectangularAperture,
RegPolyAperture as RegPolyAperture,
IrregPolyAperture as IrregPolyAperture,
AberratedAperture as AberratedAperture,
UniformSpider as UniformSpider,
CompoundAperture as CompoundAperture,
MultiAperture as MultiAperture,
ApertureFactory as ApertureFactory,
)
from .aberrations import (
Zernike as Zernike,
ZernikeBasis as ZernikeBasis,
)

# Images and Detectors
from .images import *
from .detectors import *
from .detector_layers import *
from .images import Image as Image
from .detectors import LayeredDetector as LayeredDetector
from .detector_layers import (
ApplyPixelResponse as ApplyPixelResponse,
ApplyJitter as ApplyJitter,
ApplySaturation as ApplySaturation,
AddConstant as AddConstant,
IntegerDownsample as IntegerDownsample,
RotateDetector as RotateDetector,
)

# All other classes
from .sources import *
from .spectra import *
from .instruments import *
from .observations import *

# Sub Modules
# from .utils import *
from .sources import (
PointSource as PointSource,
PointSources as PointSources,
BinarySource as BinarySource,
ResolvedSource as ResolvedSource,
PointResolvedSource as PointResolvedSource,
)
from .spectra import (
Spectrum as Spectrum,
PolySpectrum as PolySpectrum,
)
from .instruments import Instrument as Instrument
from .observations import Dither as Dither


# Add to __all__
Expand All @@ -57,16 +108,13 @@
propagators,
apertures,
aberrations,

images,
detectors,
detector_layers,

sources,
spectra,

instruments,
observations,
]

__all__ = [module.__all__ for module in modules]
__all__ = [module.__all__ for module in modules]
Loading