From 0854ffd1826146932982edda2b28995e9813e970 Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Thu, 14 Sep 2023 15:01:31 -0700 Subject: [PATCH 1/8] Feature: new eqdsk loader class skeleton --- INGRID/EQLoader.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 INGRID/EQLoader.py diff --git a/INGRID/EQLoader.py b/INGRID/EQLoader.py new file mode 100644 index 0000000..1933331 --- /dev/null +++ b/INGRID/EQLoader.py @@ -0,0 +1,50 @@ +from freeqdsk import geqdsk +from pathlib import Path + +class EQLoader: + """ + Loader for G-EQDSK files. + + This class will leverage the FreeQDSK G-EQDSK loader + + """ + def __init__(self, fpath: str) -> None: + self.fpath = fpath + + def validate_eqdsk(self, fpath: str): + """ + Helper function for checking provided fpath + exists + + Parameters + ---------- + fpath : str + The fpath to validate exists + + Returns + ------- + Bool indicating file existence + """ + raise FileNotFoundError + + def load(self, fpath: str = None): + """ + Read the G-EQDSK data in the fpath provided. + + If fpath is None, use the fpath provided at + initialization. Otherwise, we will clobber the + stored fpath with the new fpath and reload the internal + data. + + Parameters + ---------- + fpath : optional + Filepath to load. If None, use initialization time fpath + + Returns + ------- + A dict of loaded G-EQDSK data + """ + + if fpath is None: + fpath = self.eqdsk_path From 7207d7a4806ec8f428e2bebfebeba3c0c8c25b22 Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Fri, 15 Sep 2023 08:49:18 -0700 Subject: [PATCH 2/8] Cleanup: remove unused imports --- INGRID/ingrid.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/INGRID/ingrid.py b/INGRID/ingrid.py index 9b0df22..73ac74b 100644 --- a/INGRID/ingrid.py +++ b/INGRID/ingrid.py @@ -17,7 +17,6 @@ pass import matplotlib.pyplot as plt import pathlib -import inspect import functools from scipy.optimize import root, minimize @@ -25,9 +24,7 @@ import os from pathlib import Path from time import time, perf_counter -from collections import OrderedDict -from INGRID.OMFITgeqdsk import OMFITgeqdsk from INGRID.interpol import EfitData from INGRID.utils import IngridUtils from INGRID.topologies.snl import SNL @@ -38,7 +35,6 @@ from INGRID.topologies.sf135 import SF135 from INGRID.topologies.sf165 import SF165 from INGRID.topologies.udn import UDN -from INGRID.line_tracing import LineTracing from INGRID.geometry import Point, Line From ccc8142f1fdf8e8f491fa3ee2edb3a044c89da19 Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Fri, 15 Sep 2023 10:26:47 -0700 Subject: [PATCH 3/8] Feature: Integrate FreeQDSK loading of EFIT data. Minor fixes and cleanup --- INGRID/EQLoader.py | 50 ------------ INGRID/gui/ingrid_gui.py | 7 +- INGRID/ingrid.py | 6 +- INGRID/interpol.py | 22 +++-- INGRID/utils.py | 168 +++++++++++++++++++++++---------------- setup.py | 3 +- 6 files changed, 122 insertions(+), 134 deletions(-) delete mode 100644 INGRID/EQLoader.py diff --git a/INGRID/EQLoader.py b/INGRID/EQLoader.py deleted file mode 100644 index 1933331..0000000 --- a/INGRID/EQLoader.py +++ /dev/null @@ -1,50 +0,0 @@ -from freeqdsk import geqdsk -from pathlib import Path - -class EQLoader: - """ - Loader for G-EQDSK files. - - This class will leverage the FreeQDSK G-EQDSK loader - - """ - def __init__(self, fpath: str) -> None: - self.fpath = fpath - - def validate_eqdsk(self, fpath: str): - """ - Helper function for checking provided fpath - exists - - Parameters - ---------- - fpath : str - The fpath to validate exists - - Returns - ------- - Bool indicating file existence - """ - raise FileNotFoundError - - def load(self, fpath: str = None): - """ - Read the G-EQDSK data in the fpath provided. - - If fpath is None, use the fpath provided at - initialization. Otherwise, we will clobber the - stored fpath with the new fpath and reload the internal - data. - - Parameters - ---------- - fpath : optional - Filepath to load. If None, use initialization time fpath - - Returns - ------- - A dict of loaded G-EQDSK data - """ - - if fpath is None: - fpath = self.eqdsk_path diff --git a/INGRID/gui/ingrid_gui.py b/INGRID/gui/ingrid_gui.py index b69f820..af2813d 100644 --- a/INGRID/gui/ingrid_gui.py +++ b/INGRID/gui/ingrid_gui.py @@ -1,12 +1,7 @@ import matplotlib -import os try: - if os.environ.get('DISPLAY','') == '': - print('no display found. Using non-interactive Agg backend') - matplotlib.use('Agg') - else: - matplotlib.use("TkAgg") + matplotlib.use("TkAgg") except: active_backend = matplotlib.get_backend() msg = 'Warning: Could not set matplotlib backend to "TkAgg". ' diff --git a/INGRID/ingrid.py b/INGRID/ingrid.py index 73ac74b..2d4557d 100644 --- a/INGRID/ingrid.py +++ b/INGRID/ingrid.py @@ -124,9 +124,9 @@ def wrapper_timer(*args, **kwargs): return value return wrapper_timer - def LoadEFIT(self, fpath: str) -> None: + def LoadGEQDSK(self, fpath: str) -> None: self.settings['eqdsk'] = fpath - self.OMFIT_read_psi() + self.LoadGEQDSK(geqdsk_path=fpath) def StartGUI(self, test_initialization: bool = False) -> None: """ @@ -1287,7 +1287,7 @@ def StartSetup(self, **kwargs) -> None: + 'Must be <= 2).' raise ValueError(v_error_str) - self.LoadEFIT(self.settings['eqdsk']) + self.LoadGEQDSK(self.settings['eqdsk']) self.AutoRefineMagAxis() self.AutoRefineXPoint() if topology == 'DNL': diff --git a/INGRID/interpol.py b/INGRID/interpol.py index 9bb968a..6b324bd 100644 --- a/INGRID/interpol.py +++ b/INGRID/interpol.py @@ -1,10 +1,7 @@ -#!/usr/bin/env pythonu -# -*- coding: utf-8 -*- """ Module containing EfitData class for handling all interpolation related computations. """ -from __future__ import division, print_function, absolute_import import numpy as np import matplotlib.pyplot as plt from scipy.ndimage import zoom @@ -42,9 +39,22 @@ class EfitData: Specify the title of the figure the data will be plotted on. """ - def __init__(self, rmin=0.0, rmax=1.0, nr=10, zmin=0.0, zmax=2.0, nz=20, - rcenter=1.6955000, bcenter=-2.1094041, rlimiter=None, zlimiter=None, - rmagx=0.0, zmagx=0.0, name='unnamed', parent=None): + def __init__(self, + rmin=0.0, + rmax=1.0, + nr=10, + zmin=0.0, + zmax=2.0, + nz=20, + rcenter=1.6955000, + bcenter=-2.1094041, + rlimiter=None, + zlimiter=None, + rmagx=0.0, + zmagx=0.0, + name='unnamed', + parent=None): + r, dr = np.linspace(rmin, rmax, nr, retstep=True) z, dz = np.linspace(zmin, zmax, nz, retstep=True) rgrid, zgrid = np.meshgrid(r, z, indexing='ij') diff --git a/INGRID/utils.py b/INGRID/utils.py index 9462e45..f494011 100644 --- a/INGRID/utils.py +++ b/INGRID/utils.py @@ -15,20 +15,15 @@ except: pass import matplotlib.pyplot as plt -import pathlib import inspect -from scipy.optimize import root, minimize +from scipy.optimize import root -import yaml as yml -import os from pathlib import Path -from time import time -from collections import OrderedDict +from freeqdsk import geqdsk -from INGRID.OMFITgeqdsk import OMFITgeqdsk from INGRID.interpol import EfitData from INGRID.line_tracing import LineTracing -from INGRID.geometry import Point, Line, Patch, segment_intersect, orientation_between +from INGRID.geometry import Point, Line, Patch, orientation_between class IngridUtils(): @@ -99,9 +94,6 @@ class IngridUtils(): Dictionary containing Line objects that correspond to target plates loaded by the user. - OMFIT_psi : OMFITgeqdsk - Instance of class used to interface G files generated by EFIT. - PsiUNorm : EfitData `EfitData` class object containing unormalized psi data from provided neqdsk file in settings. @@ -463,46 +455,59 @@ def ProcessPaths(self) -> None: self.settings['patch_data']['preferences']['new_fname'] = str((path_obj / self.settings['patch_data']['preferences']['new_fname']).absolute()) continue - def OMFIT_read_psi(self) -> None: + def LoadGEQDSK(self, geqdsk_path: str) -> None: """ Python class to read the psi data in from an ascii file. Saves the boundary information and generates an EfitData instance. """ - g = OMFITgeqdsk(self.settings['eqdsk']) - - nxefit = g['NW'] - nyefit = g['NH'] - rdim = g['RDIM'] - zdim = g['ZDIM'] - zmid = g['ZMID'] - rgrid1 = g['RLEFT'] - - rcenter = g['RCENTR'] - bcenter = g['BCENTR'] - - rmagx = g['RMAXIS'] - zmagx = g['ZMAXIS'] - - rlimiter = g['RLIM'] - zlimiter = g['ZLIM'] - - psi = g['PSIRZ'].T - - # calc array for r and z - rmin = rgrid1 + with open(geqdsk_path, 'r') as f: + geqdsk_data = geqdsk.read(f) + + # + # Extract quantities needed to initialize EfitData class + # + nx = geqdsk_data['nx'] + ny = geqdsk_data['ny'] + rdim = geqdsk_data['rdim'] + zdim = geqdsk_data['zdim'] + zmid = geqdsk_data['zmid'] + rleft = geqdsk_data['rleft'] + rcenter = geqdsk_data['rcentr'] + bcenter = geqdsk_data['bcentr'] + rmagx = geqdsk_data['rmagx'] + zmagx = geqdsk_data['zmagx'] + rlimiter = geqdsk_data['rlim'] + zlimiter = geqdsk_data['zlim'] + psi = geqdsk_data['psi'] + + # + # Derived values + # + rmin = rleft rmax = rmin + rdim zmin = (zmid - 0.5 * zdim) zmax = zmin + zdim - # reproduce efit grid - self.PsiUNorm = EfitData(rmin, rmax, nxefit, - zmin, zmax, nyefit, - rcenter, bcenter, - rlimiter, zlimiter, - rmagx, zmagx, - name='Efit Data', parent=self) + # + # Reproduce efit grid + # + self.PsiUNorm = EfitData( + rmin=rmin, + rmax=rmax, + nr=nx, + zmin=zmin, + zmax=zmax, + nz=ny, + rcenter=rcenter, + bcenter=bcenter, + rlimiter=rlimiter, + zlimiter=zlimiter, + rmagx=rmagx, + zmagx=zmagx, + name='Efit Data', + parent=self) self.PsiUNorm.init_bivariate_spline(self.PsiUNorm.r[:, 0], self.PsiUNorm.z[0, :], @@ -511,7 +516,7 @@ def OMFIT_read_psi(self) -> None: if self.settings['grid_settings']['rmagx'] is None or self.settings['grid_settings']['zmagx'] is None: self.settings['grid_settings']['rmagx'], self.settings['grid_settings']['zmagx'] = (self.PsiUNorm.rmagx, self.PsiUNorm.zmagx) - self.OMFIT_psi = g + self.geqdsk_data = geqdsk_data def ParseTxtCoordinates(self, fpath: str, rshift: float = 0.0, zshift: float = 0.0) -> tuple: """ @@ -628,43 +633,70 @@ def SetLimiter(self, fpath: str = '', coordinates: list = [], rshift: float = 0. use_efit_bounds = self.settings['limiter']['use_efit_bounds'] + # + # TODO: Refactor the codebase to leverage numpy arrays.... + # + coordinates = np.array(coordinates).T + + # + # If we have no coordinates or fpath provided, we need to + # leverage efit boundaries. Overrides any user settings. + # + if coordinates.shape[0] == 0 and fpath == '': + use_efit_bounds = True + if fpath not in ['', '.']: try: print('# Processing file for limiter data : {}'.format(fpath)) - self.OMFIT_psi['RLIM'], self.OMFIT_psi['ZLIM'] = self.ParseTxtCoordinates(fpath) + self.geqdsk_data['rlim'], self.geqdsk_data['rlim'] = self.ParseTxtCoordinates(fpath) except: raise ValueError(f"# Error in method 'SetLimiter' with fpath={fpath}") - elif coordinates == []: - g = OMFITgeqdsk(self.settings['eqdsk']) - RLIM, ZLIM = g['RLIM'], g['ZLIM'] - self.OMFIT_psi['RLIM'], self.OMFIT_psi['ZLIM'] = RLIM + rshift, ZLIM + zshift - - elif coordinates != []: - RLIM, ZLIM = coordinates - self.OMFIT_psi['RLIM'], self.OMFIT_psi['ZLIM'] = [r + rshift for r in RLIM], [z + zshift for z in ZLIM] + # + # List of coordinates provided for initialization + # + elif len(coordinates) > 0: + self.geqdsk_data['rlim'] = coordinates[:, 0] + rshift + self.geqdsk_data['zlim'] = coordinates[:, 1] + zshift - if len(RLIM) == 0 or len(ZLIM) == 0: - use_efit_bounds = True + # + # Empty list of coordinates falls back on using eqdsk limiter settings + # + else: + self.LoadGEQDSK(geqdsk_path=self.settings['eqdsk']) + self.geqdsk_data['rlim'] += rshift + self.geqdsk_data['zlim'] += zshift if use_efit_bounds: + + # + # The bounding box starts with using the computational domain bounds. + # Buffer values shrink the bounding box to allow us to avoid stepping + # outside the domain during integration/line-tracing + # rbuff = self.settings['limiter']['efit_buffer_r'] zbuff = self.settings['limiter']['efit_buffer_z'] - coordinates = [(self.PsiUNorm.rmin + rbuff, self.PsiUNorm.zmin + zbuff), - (self.PsiUNorm.rmax - rbuff, self.PsiUNorm.zmin + zbuff), - (self.PsiUNorm.rmax - rbuff, self.PsiUNorm.zmax - zbuff), - (self.PsiUNorm.rmin + rbuff, self.PsiUNorm.zmax - zbuff), - (self.PsiUNorm.rmin + rbuff, self.PsiUNorm.zmin + zbuff)] - - RLIM, ZLIM = [], [] - for c in coordinates: - r, z = c - RLIM.append(r) - ZLIM.append(z) - - self.OMFIT_psi['RLIM'], self.OMFIT_psi['ZLIM'] = np.array(RLIM) + rshift, np.array(ZLIM) + zshift - self.LimiterData = Line([Point(p) for p in zip(self.OMFIT_psi['RLIM'], self.OMFIT_psi['ZLIM'])]) + # + # Define the bounding box vertices as: + # - LL : Lower left + # - LR : Lower right + # - UL : Upper left + # - UR : Upper right + # + LL = np.array([self.PsiUNorm.rmin + rbuff, self.PsiUNorm.zmin + zbuff]) + LR = np.array([self.PsiUNorm.rmax - rbuff, self.PsiUNorm.zmin + zbuff]) + UL = np.array([self.PsiUNorm.rmin + rbuff, self.PsiUNorm.zmax - zbuff]) + UR = np.array([self.PsiUNorm.rmax - rbuff, self.PsiUNorm.zmax - zbuff]) + + # + # Define the simple limiter path and translate by shift values + # + limiter_path = np.vstack([LL, LR, UR, UL, LL]) + self.geqdsk_data['rlim'] = limiter_path[:, 0] + rshift + self.geqdsk_data['zlim'] = limiter_path[:, 1] + zshift + + self.LimiterData = Line([Point(p) for p in zip(self.geqdsk_data['rlim'], self.geqdsk_data['zlim'])]) def SetTargetPlate(self, settings: dict, rshift: float = 0.0, zshift: float = 0.0) -> None: """ @@ -1278,7 +1310,7 @@ def ReconstructPatches(self, raw_patch_list: list) -> dict: patches[patch.patch_name] = patch - patches = OrderedDict([(k, v) for k, v in patches.items()]) + patches = {k: v for k, v in patches.items()} return patches diff --git a/setup.py b/setup.py index a436821..e27fa6e 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,8 @@ 'pyyaml', 'matplotlib', 'tk', - 'sympy' + 'sympy', + 'freeqdsk' ] ) From ca3f3d85d4a0e97289b2f2dad7722318b4ffc81e Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Fri, 15 Sep 2023 11:26:04 -0700 Subject: [PATCH 4/8] Feature: Add pytest.ini and initial efit test. Small restructure in ingrid.py --- INGRID/ingrid.py | 4 ---- INGRID/utils.py | 8 ++++++++ tests/component/test_efit.py | 38 ++++++++++++++++++++++++++++++++++++ tests/conftest.py | 10 ++++++++++ tests/helpers/utils.py | 21 ++++++++++++++++++++ tests/pytest.ini | 3 +++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 tests/component/test_efit.py create mode 100644 tests/helpers/utils.py create mode 100644 tests/pytest.ini diff --git a/INGRID/ingrid.py b/INGRID/ingrid.py index 2d4557d..034655e 100644 --- a/INGRID/ingrid.py +++ b/INGRID/ingrid.py @@ -124,10 +124,6 @@ def wrapper_timer(*args, **kwargs): return value return wrapper_timer - def LoadGEQDSK(self, fpath: str) -> None: - self.settings['eqdsk'] = fpath - self.LoadGEQDSK(geqdsk_path=fpath) - def StartGUI(self, test_initialization: bool = False) -> None: """ Start GUI for Ingrid. diff --git a/INGRID/utils.py b/INGRID/utils.py index f494011..69958dd 100644 --- a/INGRID/utils.py +++ b/INGRID/utils.py @@ -516,7 +516,15 @@ def LoadGEQDSK(self, geqdsk_path: str) -> None: if self.settings['grid_settings']['rmagx'] is None or self.settings['grid_settings']['zmagx'] is None: self.settings['grid_settings']['rmagx'], self.settings['grid_settings']['zmagx'] = (self.PsiUNorm.rmagx, self.PsiUNorm.zmagx) + # + # Save the stored data in the object + # self.geqdsk_data = geqdsk_data + # + # Update the eqdsk file referenced in settings to that of the loaded data + # + self.settings['eqdsk'] = geqdsk_path + def ParseTxtCoordinates(self, fpath: str, rshift: float = 0.0, zshift: float = 0.0) -> tuple: """ diff --git a/tests/component/test_efit.py b/tests/component/test_efit.py new file mode 100644 index 0000000..ebfec31 --- /dev/null +++ b/tests/component/test_efit.py @@ -0,0 +1,38 @@ +import numpy as np +import pytest +import utils +from INGRID.ingrid import Ingrid +from freeqdsk import geqdsk + +def test_load_geqdsk(data_dir): + """ + Test loading of eqdsk files via the Ingrid class + + Parameters + ---------- + data_dir : pathlib.Path + Pytest fixture for the data dir + """ + eqdsk_path = data_dir / 'SNL' / 'DIII-D' / 'neqdsk' + eqdsk_path = utils.resolve_path(eqdsk_path, as_str=True) + + # + # Load data using FreeQDSK + # + with open(eqdsk_path, 'r') as f: + baseline = geqdsk.read(f) + + # + # Load data using Ingrid class + # + session = Ingrid() + session.LoadGEQDSK(geqdsk_path=eqdsk_path) + + # + # Check keys and values of loaded SNL eqdsk are consistent + # + for baseline_k, test_k in zip(sorted(baseline), sorted(session.geqdsk_data)): + assert baseline_k == test_k, "Key mismatch in loaded data." + k = baseline_k + assert np.allclose(baseline[k], session.geqdsk_data[k]), "Numerics mismatch in data." + diff --git a/tests/conftest.py b/tests/conftest.py index 9e302dc..1342b0d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,8 @@ import os +import sys +import pytest +from pathlib import Path +sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers')) def pytest_addoption(parser): """ @@ -22,3 +26,9 @@ def pytest_configure(config): os.system('Xvfb :1 -screen 0 1600x1200x16 &') os.environ['DISPLAY']=':1.0' +@pytest.fixture +def data_dir(): + """ + Fixture for root of data dir. + """ + return Path(__file__).parent.parent / 'data' diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py new file mode 100644 index 0000000..316f0da --- /dev/null +++ b/tests/helpers/utils.py @@ -0,0 +1,21 @@ +from pathlib import Path + +def resolve_path(path: str, as_str: bool = True): + """ + Helper function for resolving paths. + + Parameters + ---------- + path : str, pathlib.Path + Path to fully resolve + as_str : bool, optional + Bool indicating to return resolved path as a str + or PosixPath obj + + Returns + ------- + Resolved path + """ + path = Path(path).resolve() + return str(path) if as_str else path + diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000..158d709 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = -vv +norecursedirs = tests/helpers tests/gui From 44b1a8c9b5b401a9ce135fb1cf22cf5f2002082b Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Fri, 15 Sep 2023 12:03:43 -0700 Subject: [PATCH 5/8] Test suite: cleanup pytest.ini --- tests/pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pytest.ini b/tests/pytest.ini index 158d709..c97282f 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,3 @@ [pytest] addopts = -vv -norecursedirs = tests/helpers tests/gui +norecursedirs = tests/helpers From 8d643db4ae0d27d2b8017a4f0832002844fd8bdb Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Fri, 15 Sep 2023 15:47:59 -0700 Subject: [PATCH 6/8] Test suite: update ci yaml --- .github/workflows/ingrid_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ingrid_ci.yml b/.github/workflows/ingrid_ci.yml index 23a5bc3..0a12948 100644 --- a/.github/workflows/ingrid_ci.yml +++ b/.github/workflows/ingrid_ci.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 From e00eb304caa6647153841dd8f8e56e3f3a05fd3d Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Sun, 17 Sep 2023 12:20:32 -0700 Subject: [PATCH 7/8] Feature: Update package requirements --- setup.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index e27fa6e..0c9defb 100644 --- a/setup.py +++ b/setup.py @@ -28,22 +28,18 @@ # This should be a valid email address corresponding to the author listed # above. - author_email='bgarci26@ucsc.edu, umansky1@llnl.gov', + author_email='garcia299@llnl.gov, umansky1@llnl.gov', classifiers=[ 'Development Status :: 5 - Production/Stable', #'Intended Audience :: Any UEDGE users', 'Topic :: Software Development', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.9' ], keywords='grid, mesh, generator, tokamak, edge, plasma, efit, uedge', packages=find_packages(), - python_requires='>=3.5, <4', + python_requires='>=3.8, <4', install_requires=[ 'm2r2', 'scipy >= 1.3.1', From aea34262c5963f648257af31a79053f243a25893 Mon Sep 17 00:00:00 2001 From: bryan-garcia Date: Sun, 17 Sep 2023 12:24:06 -0700 Subject: [PATCH 8/8] Fix: remove pycrypto from docs requirements.txt --- docs/source/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index 3308f28..2f5314a 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -12,7 +12,6 @@ mkl-random==1.1.0 mkl-service==2.3.0 mpmath==1.1.0 numpy==1.17.2 -pycrypto==2.6.1 PyInstaller==3.6 pyparsing==2.4.2 python-dateutil==2.8.0