Skip to content

Commit

Permalink
innate 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Vital committed Jun 11, 2024
0 parents commit 08b50ba
Show file tree
Hide file tree
Showing 15 changed files with 1,064 additions and 0 deletions.
706 changes: 706 additions & 0 deletions LICENSE.rst

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions README.rst
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.
23 changes: 23 additions & 0 deletions pyproject.toml
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
36 changes: 36 additions & 0 deletions setup.py
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 added src/innate/__init__.py
Empty file.
Empty file added src/innate/changelog.txt
Empty file.
13 changes: 13 additions & 0 deletions src/innate/config.toml
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.
132 changes: 132 additions & 0 deletions src/innate/interpolation/pytensor.py
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)
107 changes: 107 additions & 0 deletions src/innate/io.py
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
33 changes: 33 additions & 0 deletions src/innate/main.py
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 added src/innate/plotting.py
Empty file.
Empty file added src/innate/read_files.py
Empty file.
Empty file.
Empty file added src/innate/tools.py
Empty file.

0 comments on commit 08b50ba

Please sign in to comment.