-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Vital
committed
Jun 11, 2024
0 parents
commit 08b50ba
Showing
15 changed files
with
1,064 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
This library provides a set of tools to approximate data grids, either via interpolation or regression. This project aims | ||
to provide an uniform and fast workflow for tensor operations while maintaining the accuracy and precision on the approximation. | ||
|
||
Installation | ||
============ | ||
|
||
The library can be installed directly from its PyPi_ project page running the command: | ||
|
||
``pip install innate-stable`` | ||
|
||
Development | ||
=========== | ||
|
||
INNATE is currently on an alpha release. Any comment/issue/request can be added as an issue on the github page. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[project] | ||
name = "innate-stable" | ||
version = "0.0.1" | ||
readme = "README.rst" | ||
requires-python = ">=3.10" | ||
license = {file = "COPYING"} | ||
authors = [{name = "Vital Fernández", email = "[email protected]"}] | ||
description = "Interpolator and Neural Network Architecture for TEnsors" | ||
|
||
dependencies = ["numpy~=1.24", | ||
"pandas~=2.0", | ||
"toml~=0.10", | ||
"tomli >= 2.0.0 ; python_version < '3.11'"] | ||
|
||
classifiers = ["License :: OSI Approved :: MIT License", | ||
"Programming Language :: Python :: 3", | ||
"Programming Language :: Python :: 3.7"] | ||
|
||
[tool.pytest.ini_options] | ||
pythonpath = ["src"] | ||
mpl-baseline-path = 'tests/baseline' | ||
mpl-results-path = 'tests/outputs' | ||
mpl-results-always = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import os | ||
import pathlib | ||
import configparser | ||
from setuptools import setup | ||
from setuptools import find_packages | ||
|
||
# The directory containing this file | ||
HERE = pathlib.Path(__file__).parent | ||
|
||
# README | ||
README = (HERE/"README.rst").read_text() | ||
|
||
# Read lime configuration | ||
_dir_path = os.path.dirname(os.path.realpath(__file__)) | ||
_setup_cfg = configparser.ConfigParser() | ||
_setup_cfg.optionxform = str | ||
_setup_cfg.read(HERE/'setup.cfg') | ||
|
||
# Setup | ||
setup( | ||
name=_setup_cfg['metadata']['name'], | ||
version=_setup_cfg['metadata']['version'], | ||
author=_setup_cfg['metadata']['author'], | ||
author_email=_setup_cfg['metadata']['author_email'], | ||
description=_setup_cfg['metadata']['description'], | ||
long_description=README, | ||
long_description_content_type=_setup_cfg['metadata']['long_description_content_type'], | ||
url=_setup_cfg['metadata']['url'], | ||
license=_setup_cfg['metadata']['licence'], | ||
packages=find_packages('src'), | ||
package_dir={'': 'src'}, | ||
package_data={'': ['config.toml', 'resources/*']}, | ||
include_package_data=True, | ||
install_requires=['numpy', 'matplotlib', 'pandas', 'astropy', 'lmfit', 'scipy', 'pylatex', 'openpyxl', | ||
'joblib'], | ||
) |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[metadata] | ||
version = '0.0.1' | ||
|
||
[technique_labels] | ||
|
||
# Interpolation | ||
|
||
|
||
# Regression | ||
equat = 'equation' | ||
nn = 'neural networks' | ||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import itertools | ||
import logging | ||
from ..io import InnateError | ||
|
||
|
||
try: | ||
import pytensor.tensor as tt | ||
pytensor_check = True | ||
except ImportError: | ||
pytensor_check = False | ||
|
||
|
||
def as_tensor_variable(x, dtype="float64", **kwargs): | ||
t = tt.as_tensor_variable(x, **kwargs) | ||
if dtype is None: | ||
return t | ||
return t.astype(dtype) | ||
|
||
|
||
def interpolation_selection(grid_dict, x_range, y_range, z_range=None, interp_type='point'): | ||
|
||
# Container for interpolators | ||
interp_dict = {} | ||
|
||
for line, data_grid in grid_dict.items(): | ||
|
||
# 2D Point interpolation | ||
if interp_type == 'point': | ||
interp_i = RegularGridInterpolator([x_range, y_range], data_grid[:, :, None], nout=1) | ||
|
||
# Line interpolation | ||
elif interp_type == 'axis': | ||
data_grid_reshape = data_grid.reshape((x_range.size, y_range.size, -1)) | ||
interp_i = RegularGridInterpolator([x_range, y_range], data_grid_reshape) | ||
|
||
# 3D point | ||
elif interp_type == 'cube': | ||
interp_i = RegularGridInterpolator([x_range, y_range, z_range], data_grid) | ||
|
||
# Evaluate and store it | ||
interp_dict[line] = interp_i.evaluate | ||
|
||
return interp_dict | ||
|
||
|
||
def regular_grid_interp(points, values, coords, *, fill_value=None): | ||
"""Perform a linear interpolation in N-dimensions w a regular grid | ||
The data must be defined on a filled regular grid, but the spacing may be | ||
uneven in any of the dimensions. | ||
This implementation is based on the implementation in the | ||
``scipy.interpolate.RegularGridInterpolator`` class which, in turn, is | ||
based on the implementation from Johannes Buchner's ``regulargrid`` | ||
package https://github.com/JohannesBuchner/regulargrid. | ||
Args: | ||
points: A list of vectors with shapes ``(m1,), ... (mn,)``. These | ||
define the grid points in each dimension. | ||
values: A tensor defining the values at each point in the grid | ||
defined by ``points``. This must have the shape | ||
``(m1, ... mn, ..., nout)``. | ||
coords: A matrix defining the coordinates where the interpolation | ||
should be evaluated. This must have the shape ``(ntest, ndim)``. | ||
""" | ||
points = [as_tensor_variable(p) for p in points] | ||
ndim = len(points) | ||
values = as_tensor_variable(values) | ||
coords = as_tensor_variable(coords) | ||
|
||
# Find where the points should be inserted | ||
indices = [] | ||
norm_distances = [] | ||
out_of_bounds = tt.zeros(coords.shape[:-1], dtype=bool) | ||
for n, grid in enumerate(points): | ||
x = coords[..., n] | ||
i = tt.extra_ops.searchsorted(grid, x) - 1 | ||
out_of_bounds |= (i < 0) | (i >= grid.shape[0] - 1) | ||
i = tt.clip(i, 0, grid.shape[0] - 2) | ||
indices.append(i) | ||
norm_distances.append((x - grid[i]) / (grid[i + 1] - grid[i])) | ||
|
||
result = tt.zeros(tuple(coords.shape[:-1]) + tuple(values.shape[ndim:])) | ||
for edge_indices in itertools.product(*((i, i + 1) for i in indices)): | ||
weight = tt.ones(coords.shape[:-1]) | ||
for ei, i, yi in zip(edge_indices, indices, norm_distances): | ||
weight *= tt.where(tt.eq(ei, i), 1 - yi, yi) | ||
result += values[edge_indices] * weight | ||
|
||
if fill_value is not None: | ||
result = tt.switch(out_of_bounds, fill_value, result) | ||
|
||
return result | ||
|
||
|
||
class RegularGridInterpolator: | ||
|
||
"""Linear interpolation on a regular grid in arbitrary dimensions | ||
The data must be defined on a filled regular grid, but the spacing may be | ||
uneven in any of the dimensions. | ||
Args: | ||
points: A list of vectors with shapes ``(m1,), ... (mn,)``. These | ||
define the grid points in each dimension. | ||
values: A tensor defining the values at each point in the grid | ||
defined by ``points``. This must have the shape | ||
``(m1, ... mn, ..., nout)``. | ||
""" | ||
|
||
def __init__(self, points, values, fill_value=None, **kwargs): | ||
|
||
# Check pytensor has been installed | ||
if pytensor_check: | ||
self.ndim = len(points) | ||
self.points = points | ||
self.values = values | ||
self.fill_value = fill_value | ||
|
||
else: | ||
raise InnateError(f'Need to install PyTensor to use this function') | ||
|
||
def evaluate(self, t): | ||
"""Interpolate the data | ||
Args: | ||
t: A matrix defining the coordinates where the interpolation | ||
should be evaluated. This must have the shape | ||
``(ntest, ndim)``. | ||
""" | ||
return regular_grid_interp(self.points, self.values, t, fill_value=self.fill_value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from pathlib import Path | ||
|
||
try: | ||
from astropy.io import fits | ||
astropy_check = True | ||
except ImportError: | ||
astropy_check = False | ||
|
||
try: | ||
import h5netcdf | ||
h5netcdf_check = True | ||
except ImportError: | ||
h5netcdf_check = False | ||
|
||
|
||
# Define library error function | ||
class InnateError(Exception): | ||
"""InnateError exception function""" | ||
|
||
|
||
def load_dataset(fname): | ||
|
||
# Container | ||
grid_dict = None | ||
|
||
# Check there is an input grid file | ||
if fname is not None: | ||
|
||
# Locate the file | ||
if fname.is_file(): | ||
|
||
# Container for output grid | ||
grid_dict = {} | ||
|
||
ext = fname.suffix | ||
print(f'\n- Loading emissivity grid at {fname}') | ||
if ext == '.fits': | ||
|
||
if astropy_check: | ||
with fits.open(fname) as hdu_list: | ||
for i in range(1, len(hdu_list)): | ||
grid_dict[hdu_list[i].name] = hdu_list[i].data | ||
else: | ||
raise InnateError(f'To open {ext} (astropy.io.fits) files you need to install the astropy package') | ||
|
||
elif ext == '.nc': | ||
|
||
if h5netcdf_check: | ||
with h5netcdf.File(fname, 'r') as f: | ||
for var_name in f.variables: | ||
grid_dict[var_name] = f.variables[var_name][...] | ||
else: | ||
raise InnateError(f'To open {ext} (h5netcdf) files you need to install the h5netcdf package') | ||
|
||
return grid_dict | ||
|
||
|
||
def save_dataset(fname, grid_dict): | ||
|
||
fname = Path(fname) | ||
ext = fname.suffix | ||
|
||
# Case of a .fits astropy file: | ||
if fname.suffix == '.fits': | ||
|
||
if astropy_check: | ||
|
||
# Create a primary HDU | ||
hdu_list = fits.HDUList([fits.PrimaryHDU()]) | ||
|
||
# Generate the fits file | ||
for key, grid in grid_dict.items(): | ||
hdu_i = fits.ImageHDU(grid, name=key) | ||
hdu_list.append(hdu_i) | ||
|
||
# Write the fits file | ||
hdu_list.writeto(fname, overwrite=True) | ||
|
||
else: | ||
raise InnateError(f'To open {ext} (astropy.io.fits) files you need to install the astropy package') | ||
|
||
# Case of a .fits astropy file: | ||
elif ext == '.nc': | ||
|
||
if h5netcdf_check: | ||
|
||
# Use the first item for the dimensions | ||
grid_0 = grid_dict[list(grid_dict.keys())[0]] | ||
m, n = grid_0.shape | ||
|
||
with h5netcdf.File(fname, 'w') as f: | ||
|
||
# Unique dimensions for all the datase | ||
f.dimensions['m'], f.dimensions['n'] = m, n | ||
|
||
# Iterate over the dictionary and create a variable for each array | ||
for key, grid in grid_dict.items(): | ||
var = f.create_variable(key, ('m', 'n'), data=grid) | ||
var.attrs['description'] = f'{key} emissivity' | ||
|
||
else: | ||
raise InnateError(f'To open {ext} (h5netcdf) files you need to install the h5netcdf package') | ||
|
||
else: | ||
raise KeyError(f'The extension "{ext}" is not recognized, please use ".nc" or ".fits"') | ||
|
||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import numpy as np | ||
from pathlib import Path | ||
from .io import load_dataset | ||
from .interpolation.pytensor import interpolation_selection | ||
|
||
|
||
class Innate: | ||
|
||
def __init__(self, grid=None, x_space=None, y_space=None, interpolator='pytensor'): | ||
|
||
# Object attributes | ||
self.grid = None | ||
self.interpl = None | ||
self.lib_interpl = None | ||
self.x_range, self.y_range = None, None | ||
|
||
# Initiate data and interpolators | ||
grid_path = Path(grid) | ||
self.grid = load_dataset(grid_path) if grid_path.is_file() else grid | ||
|
||
if interpolator is not None: | ||
|
||
print(f'\n- Compiling {interpolator} interpolator') | ||
|
||
if interpolator == 'pytensor': | ||
self.lib_interpl = interpolator | ||
self.x_range = np.linspace(x_space[0], x_space[1], x_space[2]) | ||
self.y_range = np.linspace(y_space[0], y_space[1], y_space[2]) | ||
self.interpl = interpolation_selection(self.grid, self.x_range, self.y_range, z_range=None, | ||
interp_type='point') | ||
print('-- done') | ||
|
||
return |
Empty file.
Empty file.
Empty file.
Empty file.