diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf6c4c5..4651d21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,48 +8,109 @@ on: branches: [ main ] jobs: - - build: + lint: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/ruff-action@v3 + - run: ruff check + - run: ruff format --check --diff + build-binaries: + needs: [ lint ] strategy: matrix: include: - - name: x86-windows - image: windows-2022 - arch: x86 + - name: x86_64-linux + image: ubuntu-22.04 + cmake-args: -D ARCHITECTURE=x86_64 - name: x86_64-windows image: windows-2022 - arch: x86_64 - - name: x86_64-linux - image: ubuntu-20.04 - arch: x86_64 + cmake-args: -A x64 -D ARCHITECTURE=x86_64 - name: x86_64-darwin - image: macos-12 - arch: x86_64 + image: macos-13 + cmake-args: -D CMAKE_OSX_ARCHITECTURES=x86_64 -D ARCHITECTURE=x86_64 + - name: aarch64-darwin + image: macos-13 + cmake-args: -D CMAKE_OSX_ARCHITECTURES=arm64 -D ARCHITECTURE=aarch64 runs-on: ${{ matrix.image }} steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Query Python executable - run: which python - - name: Install dependencies - run: python -m pip install h5py matplotlib numpy pytest scipy setuptools wheel - - name: Build Python Wheel - run: python setup.py bdist_wheel - - name: Install Python Wheel - shell: bash + with: + submodules: true + - name: Build NDTable run: | - for f in dist/SDF-*-py3-none-any.whl; do - python -m pip install $f --no-deps -vv - done - - name: Run Python tests - run: pytest tests - - name: Upload artifacts - uses: actions/upload-artifact@v4 + cmake -S C -B C/${{ matrix.name }} ${{ matrix.cmake-args }} + cmake --build C/${{ matrix.name }} --target install + - uses: actions/upload-artifact@v4 with: name: ${{ matrix.name }} - path: dist/*.whl + path: src/sdf/ndtable/${{ matrix.name }} if-no-files-found: error + build-wheel: + runs-on: ubuntu-22.04 + needs: [build-binaries] + steps: + - uses: astral-sh/setup-uv@v4 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: x86_64-linux + path: src/sdf/ndtable/x86_64-linux + - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4 + with: + name: x86_64-windows + path: src/sdf/ndtable/x86_64-windows + - uses: actions/download-artifact@v4 + with: + name: x86_64-darwin + path: src/sdf/ndtable/x86_64-darwin + - uses: actions/download-artifact@v4 + with: + name: aarch64-darwin + path: src/sdf/ndtable/aarch64-darwin + - run: uv build --wheel + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist + if-no-files-found: error + run-tests: + needs: [ build-wheel ] + strategy: + matrix: + include: + - name: x86_64-linux + image: ubuntu-22.04 + - name: x86_64-windows + image: windows-2022 + - name: x86_64-darwin + image: macos-13 + - name: aarch64-darwin + image: macos-13 + runs-on: ${{ matrix.image }} + steps: + - uses: actions/checkout@v4 + - run: rm pyproject.toml + - uses: astral-sh/setup-uv@v4 + - uses: actions/download-artifact@v4 + with: + name: dist + - if: matrix.name == 'x86_64-windows' + run: | + uv venv --python 3.10 + .venv\Scripts\activate + uv pip install pytest + $files = Get-ChildItem "sdf-*-py3-none-any.whl" + foreach ($f in $files) { + uv pip install $f.FullName + } + uv run pytest + - if: matrix.name != 'x86_64-windows' + run: | + uv venv --python 3.10 + source .venv/bin/activate + uv pip install pytest + uv pip install sdf-*-py3-none-any.whl + uv run pytest diff --git a/.gitignore b/.gitignore index 4901689..c76a762 100644 --- a/.gitignore +++ b/.gitignore @@ -1,61 +1,19 @@ # SDF files *.sdf -# Build artefacts -*.o - # PyCharm projects .idea/ -# compiled code -*.py[cod] - -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg -lib -lib64 +# Python artifacts __pycache__ -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox -nosetests.xml - -# Translations -*.mo - -# data files -*.h5 -*.mat -!DoublePendulum.mat - -# temp files -~* - -# Eclipse user settings -.settings/ - -# PDFs -*.pdf - -# auto generated manifest -MANIFEST - -# automation scripts -# *.bat +# native binaries +*.dll +*.dylib +*.lib +*.so -# test file -!IntegerNetwork1.mat +# build artifacts +C/aarch64-*/ +C/x86-*/ +C/x86_64-*/ diff --git a/C/CMakeLists.txt b/C/CMakeLists.txt new file mode 100644 index 0000000..41646cb --- /dev/null +++ b/C/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required (VERSION 3.20) + +cmake_policy(SET CMP0177 NEW) + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +# set(CMAKE_POLICY_DEFAULT_CMP0091 NEW) + +project (NDTable) + +set(ARCHITECTURE "" CACHE STRING "Architecture") +set_property(CACHE ARCHITECTURE PROPERTY STRINGS "" "aarch64" "x86" "x86_64") + +if (NOT ARCHITECTURE) + if (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "AMD64|x86_64") + set(ARCHITECTURE "x86_64") + elseif (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64") + set(ARCHITECTURE "aarch64") + else () + message(FATAL_ERROR "Unknown System Architecture: ${CMAKE_SYSTEM_PROCESSOR}") + endif () +endif () + +if (WIN32) + set(PLATFORM "${ARCHITECTURE}-windows") +elseif (APPLE) + set(PLATFORM "${ARCHITECTURE}-darwin") +else () + set(PLATFORM "${ARCHITECTURE}-linux") +endif () + +message(STATUS "PLATFORM: ${PLATFORM}") + +add_library(NDTable SHARED + "include/Python.h" + "src/Python.c" + "NDTable/include/NDTable.h" + "NDTable/src/Core.c" + "NDTable/src/Interpolation.c" +) + +target_include_directories(NDTable PRIVATE + "include" + "NDTable/include" +) + +install(TARGETS NDTable DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/../src/sdf/ndtable/${PLATFORM}") diff --git a/C/VisualStudio/.gitignore b/C/VisualStudio/.gitignore deleted file mode 100644 index 67e7fda..0000000 --- a/C/VisualStudio/.gitignore +++ /dev/null @@ -1,190 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# Generated Files -GeneratedFiles/ - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Roslyn cache directories -*.ide/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -#NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# If using the old MSBuild-Integrated Package Restore, uncomment this: -#!**/packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ diff --git a/C/VisualStudio/Python.vcxproj b/C/VisualStudio/Python.vcxproj deleted file mode 100644 index f0a3310..0000000 --- a/C/VisualStudio/Python.vcxproj +++ /dev/null @@ -1,173 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {AE364D82-652E-45D5-B800-0578B0410E18} - Win32Proj - Python - - - - DynamicLibrary - true - NotSet - v110 - - - DynamicLibrary - true - NotSet - v110 - - - DynamicLibrary - false - true - NotSet - v110 - - - DynamicLibrary - false - true - NotSet - v110 - - - - - - - - - - - - - - - - - - - true - ndtable - - - true - ndtable - - - false - ndtable - - - false - ndtable - - - - NotUsing - Level3 - Disabled - WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\..\C\include;..\..\C\NDTable\include;%(AdditionalIncludeDirectories) - MultiThreadedDebug - - - Windows - true - - - copy /Y $(TargetPath) ..\..\sdf\win$(PlatformArchitecture)\ - - - - - NotUsing - Level3 - Disabled - WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\..\C\include;..\..\C\NDTable\include;%(AdditionalIncludeDirectories) - MultiThreadedDebug - - - Windows - true - - - copy /Y $(TargetPath) ..\..\sdf\win$(PlatformArchitecture)\ - - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\..\C\include;..\..\C\NDTable\include;%(AdditionalIncludeDirectories) - MultiThreaded - - - Windows - true - true - true - - - copy /Y $(TargetPath) ..\..\sdf\win$(PlatformArchitecture)\ - - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\..\C\include;..\..\C\NDTable\include;%(AdditionalIncludeDirectories) - MultiThreaded - - - Windows - true - true - true - - - copy /Y $(TargetPath) ..\..\sdf\win$(PlatformArchitecture)\ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/C/VisualStudio/Python.vcxproj.filters b/C/VisualStudio/Python.vcxproj.filters deleted file mode 100644 index 3697dbd..0000000 --- a/C/VisualStudio/Python.vcxproj.filters +++ /dev/null @@ -1,36 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/C/Xcode/.gitignore b/C/Xcode/.gitignore deleted file mode 100644 index e7ab489..0000000 --- a/C/Xcode/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xccheckout -*.xcscmblueprint \ No newline at end of file diff --git a/C/Xcode/NDTable.xcodeproj/project.pbxproj b/C/Xcode/NDTable.xcodeproj/project.pbxproj deleted file mode 100644 index d1558f7..0000000 --- a/C/Xcode/NDTable.xcodeproj/project.pbxproj +++ /dev/null @@ -1,261 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - D97FF93A1F0696B2007AE393 /* Interpolation.c in Sources */ = {isa = PBXBuildFile; fileRef = D97FF9371F0696B2007AE393 /* Interpolation.c */; }; - D97FF93B1F0696B2007AE393 /* NDTable.c in Sources */ = {isa = PBXBuildFile; fileRef = D97FF9381F0696B2007AE393 /* NDTable.c */; }; - D97FF93C1F0696B2007AE393 /* Python.c in Sources */ = {isa = PBXBuildFile; fileRef = D97FF9391F0696B2007AE393 /* Python.c */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - D97FF9301F06966F007AE393 /* libNDTable.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNDTable.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - D97FF9371F0696B2007AE393 /* Interpolation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = Interpolation.c; path = ../src/Interpolation.c; sourceTree = ""; }; - D97FF9381F0696B2007AE393 /* NDTable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = NDTable.c; path = ../src/NDTable.c; sourceTree = ""; }; - D97FF9391F0696B2007AE393 /* Python.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = Python.c; path = ../src/Python.c; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - D97FF92D1F06966F007AE393 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - D97FF9271F06966F007AE393 = { - isa = PBXGroup; - children = ( - D97FF9371F0696B2007AE393 /* Interpolation.c */, - D97FF9381F0696B2007AE393 /* NDTable.c */, - D97FF9391F0696B2007AE393 /* Python.c */, - D97FF9311F06966F007AE393 /* Products */, - ); - sourceTree = ""; - }; - D97FF9311F06966F007AE393 /* Products */ = { - isa = PBXGroup; - children = ( - D97FF9301F06966F007AE393 /* libNDTable.dylib */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - D97FF92E1F06966F007AE393 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - D97FF92F1F06966F007AE393 /* NDTable */ = { - isa = PBXNativeTarget; - buildConfigurationList = D97FF9341F06966F007AE393 /* Build configuration list for PBXNativeTarget "NDTable" */; - buildPhases = ( - D97FF92C1F06966F007AE393 /* Sources */, - D97FF92D1F06966F007AE393 /* Frameworks */, - D97FF92E1F06966F007AE393 /* Headers */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = NDTable; - productName = NDTable; - productReference = D97FF9301F06966F007AE393 /* libNDTable.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - D97FF9281F06966F007AE393 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0830; - ORGANIZATIONNAME = "Dassault Systèmes"; - TargetAttributes = { - D97FF92F1F06966F007AE393 = { - CreatedOnToolsVersion = 8.3.3; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = D97FF92B1F06966F007AE393 /* Build configuration list for PBXProject "NDTable" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = D97FF9271F06966F007AE393; - productRefGroup = D97FF9311F06966F007AE393 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - D97FF92F1F06966F007AE393 /* NDTable */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - D97FF92C1F06966F007AE393 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D97FF93B1F0696B2007AE393 /* NDTable.c in Sources */, - D97FF93C1F0696B2007AE393 /* Python.c in Sources */, - D97FF93A1F0696B2007AE393 /* Interpolation.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - D97FF9321F06966F007AE393 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - D97FF9331F06966F007AE393 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - }; - name = Release; - }; - D97FF9351F06966F007AE393 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - EXECUTABLE_PREFIX = lib; - HEADER_SEARCH_PATHS = ../include; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - D97FF9361F06966F007AE393 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - EXECUTABLE_PREFIX = lib; - HEADER_SEARCH_PATHS = ../include; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - D97FF92B1F06966F007AE393 /* Build configuration list for PBXProject "NDTable" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D97FF9321F06966F007AE393 /* Debug */, - D97FF9331F06966F007AE393 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D97FF9341F06966F007AE393 /* Build configuration list for PBXNativeTarget "NDTable" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D97FF9351F06966F007AE393 /* Debug */, - D97FF9361F06966F007AE393 /* Release */, - ); - defaultConfigurationIsVisible = 0; - }; -/* End XCConfigurationList section */ - }; - rootObject = D97FF9281F06966F007AE393 /* Project object */; -} diff --git a/C/Xcode/NDTable.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/C/Xcode/NDTable.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 503bf8d..0000000 --- a/C/Xcode/NDTable.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index e0cfb7e..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -recursive-include sdf *.py *.xlsx diff --git a/build.sh b/build.sh deleted file mode 100755 index 7741392..0000000 --- a/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -echo Build the shared library -gcc -c -Wall -Werror -fpic -IC/include -IC/NDTable/include C/src/Python.c -gcc -c -Wall -Werror -fpic -IC/NDTable/include C/NDTable/src/Core.c -gcc -c -Wall -Werror -fpic -IC/NDTable/include C/NDTable/src/Interpolation.c -gcc -shared -o sdf/linux64/libNDTable.so Python.o Core.o Interpolation.o - -echo Build the distribution archive -python setup.py sdist diff --git a/build_linux.sh b/build_linux.sh deleted file mode 100755 index 304ebba..0000000 --- a/build_linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -echo Build the shared library -gcc -c -Wall -Werror -fpic -IC/include -IC/NDTable/include C/src/Python.c -gcc -c -Wall -Werror -fpic -IC/NDTable/include C/NDTable/src/Core.c -gcc -c -Wall -Werror -fpic -IC/NDTable/include C/NDTable/src/Interpolation.c -gcc -shared -o sdf/linux64/libNDTable.so Python.o Core.o Interpolation.o - -echo List exported symbols -nm -g sdf/linux64/libndtable.so - -echo Build the distribution archive -python setup.py sdist diff --git a/build_mac.sh b/build_mac.sh deleted file mode 100755 index 37cde15..0000000 --- a/build_mac.sh +++ /dev/null @@ -1,8 +0,0 @@ -echo Build the shared library -clang -dynamiclib C/src/Python.c C/NDTable/src/Core.c C/NDTable/src/Interpolation.c -IC/include -IC/NDTable/include -o sdf/darwin64/libNDTable.dylib - -echo List exported symbols -nm -gU sdf/darwin64/libNDTable.dylib - -echo Build the distribution archive -python setup.py sdist diff --git a/build_windows.bat b/build_windows.bat deleted file mode 100644 index e609a70..0000000 --- a/build_windows.bat +++ /dev/null @@ -1,18 +0,0 @@ -echo Remember the current folder -set start_dir=%cd% - -echo Change into the script's folder -cd %~dp0 - -echo Set the environment variables -call "%vs140comntools%vsvars32.bat" - -echo Build the DLLs -msbuild C\VisualStudio\Python.vcxproj /t:Clean,Build /p:Configuration=Release /p:Platform=win32 -msbuild C\VisualStudio\Python.vcxproj /t:Clean,Build /p:Configuration=Release /p:Platform=x64 - -echo Build the source distribution archive -python setup.py sdist - -echo Change back to the original folder -cd %start_dir% diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d98446d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[project] +name = "SDF" +version = "0.3.6" +description = "Work with Scientific Data Format files in Python" +readme = "README.rst" +requires-python = ">=3.10" +dependencies = [ + "attrs>=25.3.0", + "h5py>=3.13.0", + "matplotlib>=3.10.3", + "numpy>=2.2.6", + "scipy>=1.15.3", + "xlrd>=2.0.1", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel.force-include] +"src/sdf/ndtable/aarch64-darwin/libNDTable.dylib" = "sdf/ndtable/aarch64-darwin/libNDTable.dylib" +"src/sdf/ndtable/x86_64-darwin/libNDTable.dylib" = "sdf/ndtable/x86_64-darwin/libNDTable.dylib" +"src/sdf/ndtable/x86_64-linux/libNDTable.so" = "sdf/ndtable/x86_64-linux/libNDTable.so" +"src/sdf/ndtable/x86_64-windows/NDTable.dll" = "sdf/ndtable/x86_64-windows/NDTable.dll" +"src/sdf/examples/IntegerNetwork1.mat" = "sdf/examples/IntegerNetwork1.mat" +"src/sdf/examples/time_series.xlsx" = "sdf/examples/time_series.xlsx" + +[dependency-groups] +dev = [ + "pytest>=8.3.3", + "ruff>=0.11.12", +] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a9104e0..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -h5py -matplotlib -numpy>=2 -xlrd -scipy diff --git a/sdf/__init__.py b/sdf/__init__.py deleted file mode 100644 index b3058c7..0000000 --- a/sdf/__init__.py +++ /dev/null @@ -1,218 +0,0 @@ -import numpy as np -from .units import convert_unit -import re -from copy import copy - -from . import hdf5 - -__version__ = '0.3.6' - -_object_name_pattern = re.compile('[a-zA-Z][a-zA-Z0-9_]*') - - -class Group(object): - """ SDF Group """ - - def __init__(self, name, comment=None, attributes=dict(), groups=[], datasets=[]): - self.name = name - self.comment = comment - self.attributes = copy(attributes) - self.groups = copy(groups) - self.datasets = copy(datasets) - - def __contains__(self, key): - for obj in self.datasets + self.groups: - if obj.name == key: - return True - return False - - def __getitem__(self, key): - for obj in self.datasets + self.groups: - if obj.name == key: - return obj - return None - - def __iter__(self): - for obj in self.groups + self.datasets: - yield obj - - def __repr__(self): - return '' - - -class Dataset(object): - """ SDF Dataset """ - - def __init__(self, name, - comment=None, - attributes=dict(), - data=np.empty(0), - display_name=None, - relative_quantity=False, - unit=None, - display_unit=None, - is_scale=False, - scales=[] - ): - self.name = name - self.comment = comment - self.attributes = copy(attributes) - self.data = data - self._display_name = display_name - self.relative_quantity = relative_quantity - self.unit = unit - self._display_unit = display_unit - self.is_scale = is_scale - self.scales = scales - - @property - def display_data(self): - return convert_unit(self.data, self.unit, self.display_unit) - - @display_data.setter - def display_data(self, value): - self.data = convert_unit(value, self.display_unit, self.unit) - - @property - def display_name(self): - return self._display_name if self._display_name else self.name - - @display_name.setter - def display_name(self, value): - self._display_name = value - - @property - def display_unit(self): - return self._display_unit if self._display_unit else self.unit - - @display_unit.setter - def display_unit(self, value): - self._display_unit = value - - def validate(self): - if self.display_unit and not self.unit: - return 'ERROR', 'display_unit was set but no unit' - - return 'OK' - - # some shorthand aliases - @property - def d(self): - return self.data - - dd = display_data - - def __repr__(self): - text = '' - - if self.unit is not None: - text += ' ' + self.unit - - if any(self.scales): - text += ' w.r.t. ' + ', '.join(map(lambda s: s.name if s is not None else 'None', self.scales)) - - text += '>' - - return text - - -def validate(obj): - """ Validate an sdf.Group or sdf.Dataset """ - - errors = [] - - if isinstance(obj, Group): - errors += _validate_group(obj, is_root=True) - elif isinstance(obj, Dataset): - errors += _validate_dataset(obj) - else: - errors.append('Unknown object type: %s' % type(obj)) - - return errors - - -def _validate_group(group, is_root=False): - errors = [] - - if not is_root and not _object_name_pattern.match(group.name): - errors += [ - "Object names must only contain letters, digits and underscores (\"_\") and must start with a letter"] - - for child_group in group.groups: - errors += _validate_dataset(child_group) - - for ds in group.datasets: - errors += _validate_dataset(ds) - - return errors - - -def _validate_dataset(ds): - - if not type(ds.data) is np.ndarray: - return ['Dataset.data must be a numpy.ndarray'] - - elif ds.data.size < 1: - return ['Dataset.data must not be empty'] - - elif not np.issubdtype(ds.data.dtype, np.float64): - return ['Dataset.data.dtype must be numpy.float64'] - - if ds.is_scale: - if len(ds.data.shape) != 1: - return ['Scales must be one-dimensional'] - if np.any(np.diff(ds.data) <= 0): - return ['Scales must be strictly monotonic increasing'] - else: - if (len(ds.data.shape) >= 1) and (ds.data.shape[0] > 0) and not (len(ds.data.shape) == len(ds.scales)): - return ['The number of scales does not match the number of dimensions'] - - return [] - - -def load(filename, objectname='/', unit=None, scale_units=None): - """ Load a dataset or group from an SDF file """ - - obj = None - - if filename.endswith('.mat'): - from . import dsres - obj = dsres.load(filename, objectname) - else: - obj = hdf5.load(filename, objectname) - - if isinstance(obj, Dataset): - - # check the unit - if unit is not None and unit != obj.unit: - raise Exception("Dataset '%s' has the wrong unit. Expected '%s' but was '%s'." % (obj.name, unit, obj.unit)) - - # check the number of the scale units - if scale_units is not None: - - if len(scale_units) != obj.data.ndim: - raise Exception("The number of scale units must be equal to the number of dimensions. " + - "Dataset '%s' has %d dimension(s) but %d scale units where given." - % (obj.name, obj.data.ndim, len(scale_units))) - - # check the scale units - for i, scale_unit in enumerate(scale_units): - scale = obj.scales[i] - if scale.unit != scale_unit: - raise Exception(("The scale for dimension %d of '%s' has the wrong unit. " + - "Expected '%s' but was '%s'.") % (i + 1, obj.name, scale_unit, scale.unit)) - - return obj - - -def save(filename, group): - """ Save an SDF group to a file """ - - hdf5.save(filename, group) diff --git a/sdf/darwin64/libNDTable.dylib b/sdf/darwin64/libNDTable.dylib deleted file mode 100644 index 6604f9f..0000000 Binary files a/sdf/darwin64/libNDTable.dylib and /dev/null differ diff --git a/sdf/dsres.py b/sdf/dsres.py deleted file mode 100644 index a2ebcd3..0000000 --- a/sdf/dsres.py +++ /dev/null @@ -1,149 +0,0 @@ -import numpy as np -from sdf import Group, Dataset -import scipy.io - -# extract strings from the matrix -strMatNormal = lambda a: [''.join(s).rstrip() for s in a] -strMatTrans = lambda a: [''.join(s).rstrip() for s in zip(*a)] - - -def _split_description(comment): - - unit = None - display_unit = None - info = dict() - - if comment.endswith(']'): - i = comment.rfind('[') - unit = comment[i + 1:-1] - comment = comment[0:i].strip() - - if unit is not None: - - if ':#' in unit: - segments = unit.split(':#') - unit = segments[0] - for segment in segments[1:]: - key, value = segment[1:-1].split('=') - info[key] = value - - if '|' in unit: - unit, display_unit = unit.split('|') - - return unit, display_unit, comment, info - - -def load(filename, objectname): - - g_root = _load_mat(filename) - - if objectname == '/': - return g_root - else: - obj = g_root - segments = objectname.split('/') - for s in segments: - if s: - obj = obj[s] - return obj - - -def _load_mat(filename): - - mat = scipy.io.loadmat(filename, chars_as_strings=False) - - _vars = {} - _blocks = [] - - try: - fileInfo = strMatNormal(mat['Aclass']) - except KeyError: - raise Exception('File structure not supported!') - - if fileInfo[1] == '1.1': - if fileInfo[3] == 'binTrans': - # usually files from OpenModelica or Dymola auto saved, - # all methods rely on this structure since this was the only - # one understand by earlier versions - names = strMatTrans(mat['name']) # names - descr = strMatTrans(mat['description']) # descriptions - - cons = mat['data_1'] - traj = mat['data_2'] - - d = mat['dataInfo'][0, :] - x = mat['dataInfo'][1, :] - - elif fileInfo[3] == 'binNormal': - # usually files from dymola, save as..., - # variables are mapped to the structure above ('binTrans') - names = strMatNormal(mat['name']) # names - descr = strMatNormal(mat['description']) # descriptions - - cons = mat['data_1'].T - traj = mat['data_2'].T - - d = mat['dataInfo'][:, 0] - x = mat['dataInfo'][:, 1] - else: - raise Exception('File structure not supported!') - - c = np.abs(x) - 1 # column - s = np.sign(x) # sign - - vars = zip(names, descr, d, c, s) - elif fileInfo[1] == '1.0': - # files generated with dymola, save as..., only plotted ... - # fake the structure of a 1.1 transposed file - names = strMatNormal(mat['names']) # names - _blocks.append(0) - mat['data_0'] = mat['data'].transpose() - del mat['data'] - _absc = (names[0], '') - for i in range(1, len(names)): - _vars[names[i]] = ('', 0, i, 1) - else: - raise Exception('File structure not supported!') - - # build the SDF tree - g_root = Group('/') - - ds_time = None - - for name, desc, d, c, s in vars: - - unit, display_unit, comment, info = _split_description(desc) - - path = name.split('.') - - g_parent = g_root - - for segment in path[:-1]: - if segment in g_parent: - g_parent = g_parent[segment] - else: - g_child = Group(segment) - g_parent.groups.append(g_child) - g_parent = g_child - pass - - if d == 1: - data = cons[c, 0] * s - else: - data = traj[c, :] * s - - if 'type' in info: - if info['type'] == 'Integer' or 'Boolean': - data = np.asarray(data, dtype=np.int32) - - if d == 0: - ds = Dataset(path[-1], comment="Simulation time", unit=unit, display_unit=display_unit, data=data) - ds_time = ds - elif d == 1: - ds = Dataset(path[-1], comment=comment, unit=unit, display_unit=display_unit, data=data) - else: - ds = Dataset(path[-1], comment=comment, unit=unit, display_unit=display_unit, data=data, scales=[ds_time]) - - g_parent.datasets.append(ds) - - return g_root diff --git a/sdf/examples/sine.py b/sdf/examples/sine.py deleted file mode 100644 index 1950257..0000000 --- a/sdf/examples/sine.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Create a simple SDF file -""" - -import sdf -import numpy as np - -# create the data arrays -t = np.linspace(0, 10, 100) -v = np.sin(t) - -# create the datasets -ds_t = sdf.Dataset('t', data=t, unit='s', is_scale=True, display_name='Time') -ds_v = sdf.Dataset('v', data=v, unit='V', scales=[ds_t]) - -# create the root group -g = sdf.Group('/', comment='A sine voltage', datasets=[ds_t, ds_v]) - -# write the SDF file -sdf.save('sine.sdf', g) - -# read the SDF file -ds_v2 = sdf.load('sine.sdf', '/v', unit='V', scale_units=['s']) -ds_t2 = ds_v2.scales[0] - -t2 = ds_t2.data -v2 = ds_v2.data diff --git a/sdf/linux64/libndtable.so b/sdf/linux64/libndtable.so deleted file mode 100644 index ab7cf4d..0000000 Binary files a/sdf/linux64/libndtable.so and /dev/null differ diff --git a/sdf/ndtable.py b/sdf/ndtable.py deleted file mode 100644 index 2038fd2..0000000 --- a/sdf/ndtable.py +++ /dev/null @@ -1,226 +0,0 @@ -from ctypes import c_double, c_void_p, c_int, byref, cdll -import numpy as np -import sys -import os - - -_arch = '64' if sys.maxsize > 2 ** 32 else '32' -_dll_path = os.path.join(os.path.abspath(os.path.dirname(__file__))) - -if sys.platform.startswith('darwin'): - _dll_path = os.path.join(_dll_path, 'darwin64', 'libNDTable.dylib') -elif sys.platform.startswith('win'): - _dll_path = os.path.join(_dll_path, 'win' + _arch, 'ndtable.dll') -elif sys.platform.startswith('linux'): - _dll_path = os.path.join(_dll_path, 'linux' + _arch, 'libndtable.so') -else: - raise Exception("Unsupported platform: " + sys.platform) - -_ndtable = cdll.LoadLibrary(_dll_path) - -# PYTHON_API ModelicaNDTable_h create_table(int ndims, const int *dims, const double *data, const double **scales) { -_create_table = _ndtable.create_table -_create_table.argtypes = [c_int, c_void_p, c_void_p, (c_void_p * 32)] -_create_table.restype = c_void_p - -# PYTHON_API int evaluate( -# ModelicaNDTable_h table, -# int ndims, -# const double **params, -# ModelicaNDTable_InterpMethod_t interp_method, -# ModelicaNDTable_ExtrapMethod_t extrap_method, -# int nvalues, -# double *value); -_evaluate = _ndtable.evaluate -_evaluate.argtypes = [c_void_p, c_int, c_void_p, c_int, c_int, c_int, c_void_p] -_evaluate.restype = c_int - -# PYTHON_API int evaluate_derivative( -# ModelicaNDTable_h table, -# int nparams, -# const double params[], -# const double delta_params[], -# ModelicaNDTable_InterpMethod_t interp_method, -# ModelicaNDTable_ExtrapMethod_t extrap_method, -# double *value); -_evaluate_derivative = _ndtable.evaluate_derivative -_evaluate_derivative.argtypes = [c_void_p, c_int, c_void_p, c_void_p, c_int, c_int, c_void_p] -_evaluate_derivative.restype = c_int - -_close_table = _ndtable.close_table -_close_table.argtypes = [c_void_p] - - -class NDTable(object): - """ - An n-dimensional lookup table - - Attributes - ---------- - data : ndarray - The values to interpolate. - scales : tuple of ndarrays - The scales for `data`. There must be one scale for every dimension of `data`. - The values must be strictly monotonic increasing. - """ - - _interp_methods = {'hold': 1, 'nearest': 2, 'linear': 3, 'akima': 4, 'fritsch-butland': 5, 'steffen': 6} - _extrap_methods = {'hold': 1, 'linear': 2} - - def __init__(self, data, scales): - - scales = list(scales) - - # convert the arguments to double arrays - data = np.asanyarray(data, dtype=np.float64, order='C') - - for i, scale in enumerate(scales): - scales[i] = np.asanyarray(scale, dtype=np.float64, order='C') - - # check the arguments - assert data.ndim <= 32, 'Max. number of dimensions is 32' - assert len(scales) == data.ndim, 'The number of scales must match the number of dimensions' - for i, scale in enumerate(scales): - assert np.all(np.isfinite(scale)), 'The scale for dimension %d is not finite' % i - assert scale.ndim == 1, 'Scales must be one-dimensional' - assert scale.size == data.shape[i], 'The scale for dimension %d does not match the shape of data' % i - - dims = np.asarray(data.shape, np.int32) - scales_ = (c_void_p * 32)() - for i, scale in enumerate(scales): - scales_[i] = scale.ctypes.data_as(c_void_p) - self._table = _create_table(c_int(data.ndim), - dims.ctypes.data_as(c_void_p), - data.ctypes.data_as(c_void_p), - scales_) - - # save close function from garbage collection - self._close_table = _close_table - - def evaluate(self, points, interp='linear', extrap='hold'): - """ - Evaluate the lookup table at the coordinates in `points`. - - Returns an array of the same shape as the coordinates in `points`. - - Parameters - ---------- - points : tuple of ndarrays - The coordinates of the points to evaluate. - interp : string, optional - The interpolation method (one of 'nearest', 'linear' or 'akima') - Default is 'linear'. - extrap : string, optional - The extrapolation method (one of 'hold' or 'linear') - Default is 'hold'. - - Returns - ------- - samples : ndarray - The evaluated points. - - Example - -------- - >>> import numpy as np - >>> from sdf.ndtable import NDTable - >>> x = y = np.linspace(-1, 1, 3) - >>> x, y - (array([-1., 0., 1.]), array([-1., 0., 1.])) - >>> X, Y = np.meshgrid(x, y, indexing='ij') - >>> Z = X * Y - >>> Z - array([[ 1., -0., -1.], - [-0., 0., 0.], - [-1., 0., 1.]]) - >>> lut = NDTable(Z, (x, y)) - >>> x2 = y2 = np.linspace(-1, 1, 5) - >>> x2, y2 - (array([-1. , -0.5, 0. , 0.5, 1. ]), array([-1. , -0.5, 0. , 0.5, 1. ])) - >>> X2, Y2 = np.meshgrid(x2, y2, indexing='ij') - >>> Z2 = lut.evaluate((X2, Y2)) - >>> Z2 - array([[ 1. , 0.5 , 0. , -0.5 , -1. ], - [ 0.5 , 0.25, 0. , -0.25, -0.5 ], - [ 0. , 0. , 0. , 0. , 0. ], - [-0.5 , -0.25, 0. , 0.25, 0.5 ], - [-1. , -0.5 , 0. , 0.5 , 1. ]]) - - """ - - points = list(points) - - for i, _ in enumerate(points): - points[i] = np.asarray(points[i], np.float64) - - shape = points[0].shape - - for p in points[1:]: - assert p.shape == shape, 'The arrays in points must have the same shape' - - assert interp in self._interp_methods, 'Unknown interpolation method "%s"' % interp - assert extrap in self._extrap_methods, 'Unknown extrapolation method "%s"' % extrap - - interp_method = c_int(self._interp_methods[interp]) - extrap_method = c_int(self._extrap_methods[extrap]) - - values = np.empty(shape) - params = (c_void_p * len(points))() - for i, param in enumerate(points): - params[i] = param.ctypes.data_as(c_void_p) - - ret = _evaluate(c_void_p(self._table), - c_int(len(params)), - params, - interp_method, - extrap_method, - c_int(values.size), - values.ctypes.data_as(c_void_p)) - - assert ret == 0, 'An error occurred during interpolation' - - return values - - def evaluate_derivative(self, points, deltas, interp='linear', extrap='hold'): - - points = list(points) - deltas = list(deltas) - - for i, _ in enumerate(points): - points[i] = np.asarray(points[i], np.float64) - deltas[i] = np.asarray(deltas[i], np.float64) - - shape = points[0].shape - - for p in points[1:]: - assert p.shape == shape, 'The arrays in points must have the same shape' - - assert interp in self._interp_methods, 'Unknown interpolation method "%s"' % interp - assert extrap in self._extrap_methods, 'Unknown extrapolation method "%s"' % extrap - - interp_method = c_int(self._interp_methods[interp]) - extrap_method = c_int(self._extrap_methods[extrap]) - value = c_double() - - out = np.empty(shape) - params = np.empty(len(points)) - delta_params = np.empty(len(points)) - - for index in np.ndindex(shape): - for i, point in enumerate(points): - params[i] = point[index] - for i, delta in enumerate(deltas): - delta_params[i] = delta[index] - - _evaluate_derivative(c_void_p(self._table), - c_int(params.size), - params.ctypes.data_as(c_void_p), - delta_params.ctypes.data_as(c_void_p), - interp_method, - extrap_method, - byref(value)) - out[index] = value.value - - return out - - def __del__(self): - self._close_table(self._table) diff --git a/sdf/plot/contour_plot_3d.py b/sdf/plot/contour_plot_3d.py deleted file mode 100644 index c78bf74..0000000 --- a/sdf/plot/contour_plot_3d.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Create a contour plot from an SDF dataset -""" -import traceback - -import numpy as np -import sdf -import matplotlib.pyplot as plt -from matplotlib import colors -import sys - -import matplotlib.pylab as pylab - - -def create_plot(filename, datasets): - - params = { - # 'legend.fontsize': 'medium', - # 'figure.figsize': (10, 8), - 'legend.fontsize': 'small', - 'axes.labelsize': 'small', - 'axes.titlesize': 'small', - 'xtick.labelsize': 'small', - 'ytick.labelsize': 'small' - } - - pylab.rcParams.update(params) - - figure = plt.figure(figsize=(12, 8)) - figure.patch.set_facecolor('white') - - nrows = len(datasets) - - for row, dataset in enumerate(datasets): - - # load the datasets - C1 = sdf.load(filename, dataset) - - ncols = C1.scales[2].data.size - - norm = colors.Normalize(vmin=np.nanmin(C1.data), vmax=np.nanmax(C1.data)) - - subs = [0] * C1.data.ndim - subs[0] = slice(None) - subs[1] = slice(None) - - ax0 = None - - for i in range(ncols): - - x = C1.scales[1].data - y = C1.scales[0].data - - subs[2] = i - - Z = C1.data[subs].T - - X, Y = np.meshgrid(x, y, indexing='ij') - - ax = figure.add_subplot(len(datasets), C1.data.shape[2], (row * ncols) + i + 1, sharex=ax0, sharey=ax0) - - if i == 0: - ax0 = ax - ax.set_ylabel(C1.display_name + " / " + C1.unit + "\n" + C1.scales[0].display_name + " / " + C1.scales[0].unit) - # else: - # ax.get_yaxis().set_ticklabels([]) - - ax.grid(True) - - CSF = plt.contourf(X, Y, Z, 10, cmap=plt.cm.viridis, norm=norm) - - CS = plt.contour(X, Y, Z, 10, colors='k') - - plt.clabel(CS=CS, fontsize=9, inline=1, colors='k') - - scale3 = C1.scales[2] - - ax.set_title(scale3.display_name + "=" + ("%g" % scale3.data[i]) + " " + scale3.unit) - ax.set_xlabel(C1.scales[1].display_name + " / " + C1.scales[1].unit) - - cbar = figure.colorbar(CSF) - - plt.tight_layout() - - plt.show() - - -if __name__ == '__main__': - - try: - create_plot(filename=sys.argv[1], datasets=sys.argv[2:]) - except: - traceback.print_exc() - input("Press Enter to continue...") - exit(1) diff --git a/sdf/units.py b/sdf/units.py deleted file mode 100644 index 66dc78d..0000000 --- a/sdf/units.py +++ /dev/null @@ -1,148 +0,0 @@ -import math - - -class Table(object): - """ - Utility class to store the unit conversions (inspired by Guava's com.google.common.collect.Table) - """ - - def __init__(self): - self._rows = dict() - - def put(self, row_key, column_key, value): - if row_key in self._rows: - self._rows[row_key][column_key] = value - else: - self._rows[row_key] = {column_key: value} - - def get(self, row_key, column_key): - if row_key in self._rows: - return self._rows[row_key][column_key] - else: - return None - - -global _converters -_converters = Table() - - -def convert_unit(value, from_unit, to_unit): - - if from_unit == to_unit: - return value # nothing to do - - converter = _converters.get(from_unit, to_unit) - - if not converter: - raise Exception('No conversion defined for "' + from_unit + '" -> "' + to_unit + '"') - - return converter.convert(value) - - -class LinearUnitConverter(object): - - def __init__(self, from_unit, to_unit, factor, offset=0): - self.from_unit = from_unit - self.to_unit = to_unit - self.factor = factor - self.offset = offset - - def convert(self, value): - return value * self.factor + self.offset - - -def define_unit_conversion(from_unit, to_unit, factor, offset=0): - global _converters - _converters.put(from_unit, to_unit, LinearUnitConverter(from_unit, to_unit, factor, offset)) - _converters.put(to_unit, from_unit, LinearUnitConverter(to_unit, from_unit, 1/factor, -offset/factor)) - -# Time -define_unit_conversion('s', 'ms', 1000) -define_unit_conversion('s', 'min', 1.0/60.0) -define_unit_conversion('s', 'h', 1.0/3600.0) -define_unit_conversion('s', 'd', 1.0/86400.0) - -# Angle -define_unit_conversion('rad', 'deg', 180/math.pi) - -# Angular velocity -define_unit_conversion('rad/s', 'deg/s', 180/math.pi) -define_unit_conversion('rad/s', 'rpm', 30/math.pi) -define_unit_conversion('rad/s', '1/min', 30/math.pi) -define_unit_conversion('rad/s', 'r/min', 30/math.pi) - -# Length, distance -define_unit_conversion('m', 'km', 0.001) -# define_unit_conversion('m', 'cm', 100) -define_unit_conversion('m', 'mm', 1000) - -# Area -# define_unit_conversion('m2','cm2',1e4) - -# Volume -define_unit_conversion('m3', 'l', 1e3) -define_unit_conversion('m3', 'ml', 1e6) - -# Pressure -define_unit_conversion('Pa', 'kPa', 1e-3) -define_unit_conversion('Pa', 'MPa', 1e-6) -define_unit_conversion('Pa', 'bar', 1e-5) - -# Bulk Modulus -define_unit_conversion('N/m2','bar',1/1e5) - -# Volume Flow Rate -define_unit_conversion('m3/s', 'l/min', 1e3*60) - -# Density -define_unit_conversion('kg/m3', 'kg/dm3', 1e-3) -define_unit_conversion('kg/m3', 'kg/l', 1e-3) -# define_unit_conversion('kg/m3', 'g/cm3', 1e-3) -# define_unit_conversion('kg/s', 'g/s', 1e3) - -# Speed -define_unit_conversion('m/s', 'km/h', 3.6) -define_unit_conversion('m/s', 'mm/s', 1e3) -# define_unit_conversion('m/s', 'knots', 1.9438445) - -# Force -define_unit_conversion('N', 'mN', 1000) -define_unit_conversion('N', 'kN', 1e-3) -define_unit_conversion('N', 'MN', 1e-6) - -# Work, Energy -define_unit_conversion('J', 'kWh', 1.0/3600.0/1000.0) -define_unit_conversion('J', 'mJ', 1000) -define_unit_conversion('J', 'kJ', 1e-3) -define_unit_conversion('J', 'MJ', 1e-6) - -# Power -define_unit_conversion('W', 'mW', 1000) -define_unit_conversion('W', 'kW', 1e-3) -define_unit_conversion('W', 'MW', 1e-6) - -# Temperature -define_unit_conversion('K', 'degC', 1, -273.15) - -# Voltage -define_unit_conversion('V', 'mV', 1000) -define_unit_conversion('V', 'kV', 0.001) - -# Currrent -define_unit_conversion('A', 'mA', 1000) -define_unit_conversion('A', 'kA', 0.001) - -# Capacitance -define_unit_conversion('F', 'uF', 1e6) - -# Leakage -define_unit_conversion('m3/(s.Pa)','l/(min.bar)', 6e9) - -# Viscous Friction -#define_unit_conversion('N.m/(rad/s)', 'N.m/(rev/min)',0.10471975512) - -# Kinematic Viscosity -define_unit_conversion('m2/s', 'mm2/s', 1e6) - -# Temperature coefficient (e.g. of resistance) -define_unit_conversion('1/K', 'ppm/K', 1e6) diff --git a/sdf/win32/ndtable.dll b/sdf/win32/ndtable.dll deleted file mode 100644 index 646d6fe..0000000 Binary files a/sdf/win32/ndtable.dll and /dev/null differ diff --git a/sdf/win64/ndtable.dll b/sdf/win64/ndtable.dll deleted file mode 100644 index 67209be..0000000 Binary files a/sdf/win64/ndtable.dll and /dev/null differ diff --git a/setup.py b/setup.py deleted file mode 100644 index b9b9201..0000000 --- a/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -from setuptools import setup - - -def readme(): - """ Get the content of README.rst without the CI badges """ - with open('README.rst') as f: - lines = f.readlines() - while not lines[0].startswith("Scientific Data Format"): - lines = lines[1:] - return ''.join(lines) - - -setup(name='SDF', - version='0.3.6', - description="Work with Scientific Data Format files in Python", - long_description=readme(), - url="https://github.com/ScientificDataFormat/SDF-Python", - author="Torsten Sommer", - author_email='torsten.sommer@3ds.com', - license="Standard 3-clause BSD", - packages=['sdf', 'sdf.examples', 'sdf.plot'], - package_data={'sdf': ['examples/IntegerNetwork1.mat', - 'win32/ndtable.dll', - 'win64/ndtable.dll', - 'linux64/libndtable.so', - 'darwin64/libNDTable.dylib']}, - platforms=['darwin64', 'linux64', 'win32', 'win64'], - install_requires=['numpy>=2', 'h5py', 'matplotlib', 'scipy']) diff --git a/src/sdf/__init__.py b/src/sdf/__init__.py new file mode 100644 index 0000000..57db5e3 --- /dev/null +++ b/src/sdf/__init__.py @@ -0,0 +1,197 @@ +from __future__ import annotations +from os import PathLike +import numpy as np +from .units import convert_unit +import re +from attrs import define, field + +from . import hdf5 + +__version__ = "0.3.6" + +_object_name_pattern = re.compile("[a-zA-Z][a-zA-Z0-9_]*") + + +@define(eq=False) +class Group: + """SDF Group""" + + name: str = None + comment: str = None + attributes: dict[str, str] = field(factory=dict) + groups: list[Group] = field(factory=list) + datasets: list[Dataset] = field(factory=list) + + def __contains__(self, key): + for obj in self.datasets + self.groups: + if obj.name == key: + return True + return False + + def __getitem__(self, key): + for obj in self.datasets + self.groups: + if obj.name == key: + return obj + return None + + def __iter__(self): + for obj in self.groups + self.datasets: + yield obj + + +@define(eq=False) +class Dataset: + """SDF Dataset""" + + name: str = None + comment: str = None + attributes: dict[str, str] = field(factory=dict) + data: np.typing.NDArray = None + _display_name: str = None + relative_quantity: bool = False + unit: str = None + _display_unit: str = None + is_scale: bool = False + scales: list[Dataset] = field(factory=list) + + @property + def display_data(self): + return convert_unit(self.data, self.unit, self.display_unit) + + @display_data.setter + def display_data(self, value): + self.data = convert_unit(value, self.display_unit, self.unit) + + @property + def display_name(self): + return self._display_name if self._display_name else self.name + + @display_name.setter + def display_name(self, value): + self._display_name = value + + @property + def display_unit(self): + return self._display_unit if self._display_unit else self.unit + + @display_unit.setter + def display_unit(self, value): + self._display_unit = value + + # some shorthand aliases + @property + def d(self): + return self.data + + dd = display_data + + +def validate(obj: Group | Dataset) -> list[str]: + """Validate an sdf.Group or sdf.Dataset""" + + problems = [] + + if isinstance(obj, Group): + problems += _validate_group(obj, is_root=True) + elif isinstance(obj, Dataset): + problems += _validate_dataset(obj) + else: + problems.append(f"Unknown object type: {type(obj)}") + + return problems + + +def _validate_group(group, is_root=False): + problems = [] + + if not is_root and not _object_name_pattern.match(group.name): + problems.append( + 'Object names must only contain letters, digits, and underscores ("_") and must start with a letter.' + ) + + for child_group in group.groups: + problems += _validate_dataset(child_group) + + for ds in group.datasets: + problems += _validate_dataset(ds) + + return problems + + +def _validate_dataset(ds: Dataset) -> list[str]: + if type(ds.data) is not np.ndarray: + return ["Dataset.data must be a numpy.ndarray"] + + elif ds.data.size < 1: + return ["Dataset.data must not be empty"] + + elif not np.issubdtype(ds.data.dtype, np.float64): + return ["Dataset.data.dtype must be numpy.float64"] + + if ds.is_scale: + if len(ds.data.shape) != 1: + return ["Scales must be one-dimensional"] + if np.any(np.diff(ds.data) <= 0): + return ["Scales must be strictly monotonic increasing"] + else: + if ( + (len(ds.data.shape) >= 1) + and (ds.data.shape[0] > 0) + and not (len(ds.data.shape) == len(ds.scales)) + ): + return ["The number of scales does not match the number of dimensions"] + + return [] + + +def load( + filename: str | PathLike, + objectname: str = "/", + unit: str = None, + scale_units: list[str] = None, +) -> Dataset | Group: + """Load a Dataset or Group from an SDF file""" + + if filename.endswith(".mat"): + from . import dsres + + obj = dsres.load(filename, objectname) + else: + obj = hdf5.load(filename, objectname) + + if isinstance(obj, Dataset): + # check the unit + if unit is not None and unit != obj.unit: + raise Exception( + "Dataset '%s' has the wrong unit. Expected '%s' but was '%s'." + % (obj.name, unit, obj.unit) + ) + + # check the number of the scale units + if scale_units is not None: + if len(scale_units) != obj.data.ndim: + raise Exception( + "The number of scale units must be equal to the number of dimensions. " + + "Dataset '%s' has %d dimension(s) but %d scale units where given." + % (obj.name, obj.data.ndim, len(scale_units)) + ) + + # check the scale units + for i, scale_unit in enumerate(scale_units): + scale = obj.scales[i] + if scale.unit != scale_unit: + raise Exception( + ( + "The scale for dimension %d of '%s' has the wrong unit. " + + "Expected '%s' but was '%s'." + ) + % (i + 1, obj.name, scale_unit, scale.unit) + ) + + return obj + + +def save(filename: str | PathLike, group: Group): + """Save an SDF group to a file""" + + hdf5.save(filename, group) diff --git a/src/sdf/dsres.py b/src/sdf/dsres.py new file mode 100644 index 0000000..93b88ca --- /dev/null +++ b/src/sdf/dsres.py @@ -0,0 +1,172 @@ +from os import PathLike + +import numpy as np +from sdf import Group, Dataset +import scipy.io + + +# extract strings from the matrix +def strMatNormal(a): + return ["".join(s).rstrip() for s in a] + + +def strMatTrans(a): + return ["".join(s).rstrip() for s in zip(*a)] + + +def _split_description( + comment: str, +) -> tuple[str | None, str | None, str | None, dict[str, str]]: + unit = None + display_unit = None + info = dict() + + if comment.endswith("]"): + i = comment.rfind("[") + unit = comment[i + 1 : -1] + comment = comment[0:i].strip() + + if unit is not None: + if ":#" in unit: + segments = unit.split(":#") + unit = segments[0] + for segment in segments[1:]: + key, value = segment[1:-1].split("=") + info[key] = value + + if "|" in unit: + unit, display_unit = unit.split("|") + + return unit, display_unit, comment, info + + +def load(filename: str | PathLike, objectname: str) -> Dataset | Group: + g_root = _load_mat(filename) + + if objectname == "/": + return g_root + else: + obj = g_root + segments = objectname.split("/") + for s in segments: + if s: + obj = obj[s] + return obj + + +def _load_mat(filename: str) -> Group: + mat = scipy.io.loadmat(filename, chars_as_strings=False) + + _vars = {} + _blocks = [] + + try: + fileInfo = strMatNormal(mat["Aclass"]) + except KeyError: + raise Exception("File structure not supported!") + + if fileInfo[1] == "1.1": + if fileInfo[3] == "binTrans": + # usually files from OpenModelica or Dymola auto saved, + # all methods rely on this structure since this was the only + # one understand by earlier versions + names = strMatTrans(mat["name"]) # names + descr = strMatTrans(mat["description"]) # descriptions + + cons = mat["data_1"] + traj = mat["data_2"] + + d = mat["dataInfo"][0, :] + x = mat["dataInfo"][1, :] + + elif fileInfo[3] == "binNormal": + # usually files from dymola, save as..., + # variables are mapped to the structure above ('binTrans') + names = strMatNormal(mat["name"]) # names + descr = strMatNormal(mat["description"]) # descriptions + + cons = mat["data_1"].T + traj = mat["data_2"].T + + d = mat["dataInfo"][:, 0] + x = mat["dataInfo"][:, 1] + else: + raise Exception("File structure not supported!") + + c = np.abs(x) - 1 # column + s = np.sign(x) # sign + + vars = zip(names, descr, d, c, s) + elif fileInfo[1] == "1.0": + # files generated with dymola, save as..., only plotted ... + # fake the structure of a 1.1 transposed file + names = strMatNormal(mat["names"]) # names + _blocks.append(0) + mat["data_0"] = mat["data"].transpose() + del mat["data"] + _absc = (names[0], "") + for i in range(1, len(names)): + _vars[names[i]] = ("", 0, i, 1) + else: + raise Exception("File structure not supported!") + + # build the SDF tree + g_root = Group("/") + + ds_time = None + + for name, desc, d, c, s in vars: + unit, display_unit, comment, info = _split_description(desc) + + path = name.split(".") + + g_parent = g_root + + for segment in path[:-1]: + if segment in g_parent: + g_parent = g_parent[segment] + else: + g_child = Group(segment) + g_parent.groups.append(g_child) + g_parent = g_child + pass + + if d == 1: + data = cons[c, 0] * s + else: + data = traj[c, :] * s + + if "type" in info: + if info["type"] == "Integer" or "Boolean": + data = np.asarray(data, dtype=np.int32) + + if d == 0: + ds = Dataset( + path[-1], + comment="Simulation time", + unit=unit, + display_unit=display_unit, + data=data, + ) + ds_time = ds + elif d == 1: + ds = Dataset( + path[-1], + comment=comment, + unit=unit, + display_unit=display_unit, + data=data, + ) + else: + ds = Dataset( + path[-1], + comment=comment, + unit=unit, + display_unit=display_unit, + data=data, + scales=[ds_time], + ) + + g_parent.datasets.append(ds) + + return g_root diff --git a/sdf/examples/IntegerNetwork1.mat b/src/sdf/examples/IntegerNetwork1.mat similarity index 100% rename from sdf/examples/IntegerNetwork1.mat rename to src/sdf/examples/IntegerNetwork1.mat diff --git a/sdf/examples/IntegerNetwork1.xlsx b/src/sdf/examples/IntegerNetwork1.xlsx similarity index 100% rename from sdf/examples/IntegerNetwork1.xlsx rename to src/sdf/examples/IntegerNetwork1.xlsx diff --git a/sdf/examples/__init__.py b/src/sdf/examples/__init__.py similarity index 100% rename from sdf/examples/__init__.py rename to src/sdf/examples/__init__.py diff --git a/sdf/examples/excel2sdf.py b/src/sdf/examples/excel2sdf.py similarity index 79% rename from sdf/examples/excel2sdf.py rename to src/sdf/examples/excel2sdf.py index befb86c..f4989ea 100644 --- a/sdf/examples/excel2sdf.py +++ b/src/sdf/examples/excel2sdf.py @@ -1,46 +1,46 @@ -""" -Import data from an Excel sheet to SDF -""" - -import os.path -import xlrd -from numpy import array -import sdf - - -# name of the Excel file to import -filename = 'time_series.xlsx' - -# open the workbook -book = xlrd.open_workbook(filename) - -# get the first sheet -sh = book.sheet_by_index(0) - -# get the names, quantities and units -n_t = sh.cell_value(0, 1) -u_t = sh.cell_value(1, 1) - -n_u = sh.cell_value(0, 2) -u_u = sh.cell_value(1, 2) - -# get the data -col_t = sh.col_values(1, 2, sh.nrows) -col_u = sh.col_values(2, 2, sh.nrows) - -# create the data arrays -t = array(col_t) -u = array(col_u) - -# create the datasets -ds_t = sdf.Dataset(n_t, data=t, unit=u_t, is_scale=True, display_name='Time') -ds_u = sdf.Dataset(n_u, data=u, unit=u_u, scales=[ds_t]) - -# create the root group -g = sdf.Group('/', comment='Imported from ' + filename, datasets=[ds_t, ds_u]) - -# change the file extension -outfile = os.path.splitext(filename)[0] + '.sdf' - -# write the SDF file -sdf.save(outfile, g) +""" +Import data from an Excel sheet to SDF +""" + +import os.path +import xlrd +from numpy import array +import sdf + + +# name of the Excel file to import +filename = "time_series.xlsx" + +# open the workbook +book = xlrd.open_workbook(filename) + +# get the first sheet +sh = book.sheet_by_index(0) + +# get the names, quantities and units +n_t = sh.cell_value(0, 1) +u_t = sh.cell_value(1, 1) + +n_u = sh.cell_value(0, 2) +u_u = sh.cell_value(1, 2) + +# get the data +col_t = sh.col_values(1, 2, sh.nrows) +col_u = sh.col_values(2, 2, sh.nrows) + +# create the data arrays +t = array(col_t) +u = array(col_u) + +# create the datasets +ds_t = sdf.Dataset(n_t, data=t, unit=u_t, is_scale=True, display_name="Time") +ds_u = sdf.Dataset(n_u, data=u, unit=u_u, scales=[ds_t]) + +# create the root group +g = sdf.Group("/", comment="Imported from " + filename, datasets=[ds_t, ds_u]) + +# change the file extension +outfile = os.path.splitext(filename)[0] + ".sdf" + +# write the SDF file +sdf.save(outfile, g) diff --git a/sdf/examples/interp_1d.py b/src/sdf/examples/interp_1d.py similarity index 58% rename from sdf/examples/interp_1d.py rename to src/sdf/examples/interp_1d.py index e939035..fc5f853 100644 --- a/sdf/examples/interp_1d.py +++ b/src/sdf/examples/interp_1d.py @@ -1,39 +1,46 @@ -""" -Interpolation Methods -""" - -import numpy as np -import matplotlib.pyplot as plt -from sdf.ndtable import NDTable - -x = np.linspace(0, 2 * np.pi, 6) -y = np.sin(x) - -table = NDTable(y, (x,)) - -xi = np.linspace(-1.5, 2 * np.pi + 1.5, 1000) -dxi = np.ones_like(xi) - -methods = [('hold', 'hold'), ('nearest', 'hold'), ('linear', 'linear'), ('akima', 'linear')] - -figure, axes = plt.subplots(len(methods), sharex=True) - -figure.set_facecolor('white') - -for ax, method in zip(axes, methods): - yi = table.evaluate((xi,), interp=method[0], extrap=method[1]) - dyi = table.evaluate_derivative((xi,), (dxi,), interp=method[0], extrap=method[1]) - ax.set_title("interp='%s', extrap='%s'" % method) - ax.grid(True, color='0.9') - ax.plot(x, y, 'or', label='samples', zorder=300) - ax.plot(xi, yi, 'b', label='interpolated', zorder=100) - ax.plot(xi, dyi, 'r', label='derivative', zorder=100) - ax.set_xlim([-1.5, 2 * np.pi + 1.5]) - ax.set_ylim([-2, 2]) - -plt.legend(bbox_to_anchor=(0, -0.75, 1., 0.1), loc=8, ncol=3, mode="normal", borderaxespad=0.5) - -figure.set_size_inches(w=8, h=8, forward=True) -figure.tight_layout() -figure.subplots_adjust(bottom=0.15, hspace=0.5) -plt.show() +""" +Interpolation Methods +""" + +import numpy as np +import matplotlib.pyplot as plt +from sdf.ndtable import NDTable + +x = np.linspace(0, 2 * np.pi, 6) +y = np.sin(x) + +table = NDTable(y, (x,)) + +xi = np.linspace(-1.5, 2 * np.pi + 1.5, 1000) +dxi = np.ones_like(xi) + +methods = [ + ("hold", "hold"), + ("nearest", "hold"), + ("linear", "linear"), + ("akima", "linear"), +] + +figure, axes = plt.subplots(len(methods), sharex=True) + +figure.set_facecolor("white") + +for ax, method in zip(axes, methods): + yi = table.evaluate((xi,), interp=method[0], extrap=method[1]) + dyi = table.evaluate_derivative((xi,), (dxi,), interp=method[0], extrap=method[1]) + ax.set_title("interp='%s', extrap='%s'" % method) + ax.grid(True, color="0.9") + ax.plot(x, y, "or", label="samples", zorder=300) + ax.plot(xi, yi, "b", label="interpolated", zorder=100) + ax.plot(xi, dyi, "r", label="derivative", zorder=100) + ax.set_xlim([-1.5, 2 * np.pi + 1.5]) + ax.set_ylim([-2, 2]) + +plt.legend( + bbox_to_anchor=(0, -0.75, 1.0, 0.1), loc=8, ncol=3, mode="normal", borderaxespad=0.5 +) + +figure.set_size_inches(w=8, h=8, forward=True) +figure.tight_layout() +figure.subplots_adjust(bottom=0.15, hspace=0.5) +plt.show() diff --git a/sdf/examples/interp_2d.py b/src/sdf/examples/interp_2d.py similarity index 70% rename from sdf/examples/interp_2d.py rename to src/sdf/examples/interp_2d.py index 7101ad1..187d94b 100644 --- a/sdf/examples/interp_2d.py +++ b/src/sdf/examples/interp_2d.py @@ -1,48 +1,53 @@ -""" -2d interpolation example -""" - -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.image import NonUniformImage -from sdf.ndtable import NDTable - - -def peaks(x=np.linspace(-3, 3, 49), y=np.linspace(-3, 3, 49)): - X, Y = np.meshgrid(x, y) - Z = 3*(1-X)**2 * np.e**(-(X**2) - (Y+1)**2) - 10*(X/5 - X**3 - Y**5) * np.e**(-X**2-Y**2) - 1/3 * np.e**(-(X+1)**2 - Y**2) - return X, Y, Z - -x = y = np.linspace(-3, 3, 15) -_, _, Z = peaks(x, y) -table = NDTable(Z, (x, y)) - -xi = yi = np.linspace(-6, 6, 200) -XI, YI = np.meshgrid(xi, yi, indexing='ij') - -figure, axes = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True) -figure.set_facecolor('white') - -axes = axes.flatten() - -ax = axes[0] -ax.set_title('original') -im = NonUniformImage(ax) -im.set_data(x, y, Z) -im.set_extent((-3, 3, -3, 3)) -ax.add_image(im) -ax.set_xlim([-6, 6]) -ax.set_ylim([-6, 6]) - -methods = [('nearest', 'hold'), ('linear', 'linear'), ('akima', 'linear')] - -for ax, method in zip(axes[1:], methods): - ZI = table.evaluate((XI, YI), interp=method[0], extrap=method[1]) - ax.set_title("interp='%s', extrap='%s'" % method) - im = NonUniformImage(ax) - im.set_data(xi, yi, ZI) - im.set_extent((-6, 6, -6, 6)) - ax.add_image(im) - -figure.tight_layout() -plt.show() +""" +2d interpolation example +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.image import NonUniformImage +from sdf.ndtable import NDTable + + +def peaks(x=np.linspace(-3, 3, 49), y=np.linspace(-3, 3, 49)): + X, Y = np.meshgrid(x, y) + Z = ( + 3 * (1 - X) ** 2 * np.e ** (-(X**2) - (Y + 1) ** 2) + - 10 * (X / 5 - X**3 - Y**5) * np.e ** (-(X**2) - Y**2) + - 1 / 3 * np.e ** (-((X + 1) ** 2) - Y**2) + ) + return X, Y, Z + + +x = y = np.linspace(-3, 3, 15) +_, _, Z = peaks(x, y) +table = NDTable(Z, (x, y)) + +xi = yi = np.linspace(-6, 6, 200) +XI, YI = np.meshgrid(xi, yi, indexing="ij") + +figure, axes = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True) +figure.set_facecolor("white") + +axes = axes.flatten() + +ax = axes[0] +ax.set_title("original") +im = NonUniformImage(ax) +im.set_data(x, y, Z) +im.set_extent((-3, 3, -3, 3)) +ax.add_image(im) +ax.set_xlim([-6, 6]) +ax.set_ylim([-6, 6]) + +methods = [("nearest", "hold"), ("linear", "linear"), ("akima", "linear")] + +for ax, method in zip(axes[1:], methods): + ZI = table.evaluate((XI, YI), interp=method[0], extrap=method[1]) + ax.set_title("interp='%s', extrap='%s'" % method) + im = NonUniformImage(ax) + im.set_data(xi, yi, ZI) + im.set_extent((-6, 6, -6, 6)) + ax.add_image(im) + +figure.tight_layout() +plt.show() diff --git a/sdf/examples/plot_dymola_result.py b/src/sdf/examples/plot_dymola_result.py similarity index 69% rename from sdf/examples/plot_dymola_result.py rename to src/sdf/examples/plot_dymola_result.py index 63724f7..0ef8a5d 100644 --- a/sdf/examples/plot_dymola_result.py +++ b/src/sdf/examples/plot_dymola_result.py @@ -1,19 +1,18 @@ import sdf -from sdf.plot.plot_time_series import plot_time_series import matplotlib.pyplot as plt # the Dymola result file -filename = 'IntegerNetwork1.mat' +filename = "IntegerNetwork1.mat" # model variables to plot -variables = ['sum.y', 'product.y', 'triggeredAdd.y', 'multiSwitch1.y'] +variables = ["sum.y", "product.y", "triggeredAdd.y", "multiSwitch1.y"] datasets = [] for variable in variables: # convert the Modelica path to an absolute SDF/HDF5 path - path = '/' + variable.replace('.', '/') + path = "/" + variable.replace(".", "/") # read the dataset datasets.append(sdf.load(filename, path)) diff --git a/src/sdf/examples/sine.py b/src/sdf/examples/sine.py new file mode 100644 index 0000000..3f75ef4 --- /dev/null +++ b/src/sdf/examples/sine.py @@ -0,0 +1,27 @@ +""" +Create a simple SDF file +""" + +import sdf +import numpy as np + +# create the data arrays +t = np.linspace(0, 10, 100) +v = np.sin(t) + +# create the datasets +ds_t = sdf.Dataset("t", data=t, unit="s", is_scale=True, display_name="Time") +ds_v = sdf.Dataset("v", data=v, unit="V", scales=[ds_t]) + +# create the root group +g = sdf.Group("/", comment="A sine voltage", datasets=[ds_t, ds_v]) + +# write the SDF file +sdf.save("sine.sdf", g) + +# read the SDF file +ds_v2 = sdf.load("sine.sdf", "/v", unit="V", scale_units=["s"]) +ds_t2 = ds_v2.scales[0] + +t2 = ds_t2.data +v2 = ds_v2.data diff --git a/sdf/examples/spline_1d.py b/src/sdf/examples/spline_1d.py similarity index 53% rename from sdf/examples/spline_1d.py rename to src/sdf/examples/spline_1d.py index 455ec3e..c97c80e 100644 --- a/sdf/examples/spline_1d.py +++ b/src/sdf/examples/spline_1d.py @@ -1,39 +1,41 @@ -""" -Spline based interpolation methods -""" - -import numpy as np -import matplotlib.pyplot as plt -from sdf.ndtable import NDTable - - -x = np.array([0, 1, 2, 3, 4, 5]) -y = np.array([0, 1, 2, 2, 3, 4]) - -table = NDTable(y, (x,)) - -xi = np.linspace(-1, 6, 1000) -dxi = np.ones_like(xi) - - -methods = ['akima', 'fritsch-butland', 'steffen'] - -figure, axes = plt.subplots(len(methods), sharex=True) - -figure.set_facecolor('white') - -for ax, method in zip(axes, methods): - yi = table.evaluate((xi,), interp=method, extrap='linear') - dyi = table.evaluate_derivative((xi,), (dxi,), interp=method, extrap='linear') - ax.set_title(method) - ax.grid(True, color='0.9') - ax.plot(x, y, 'or', label='samples', zorder=300) - ax.plot(xi, yi, 'b', label='interpolated', zorder=100) - ax.plot(xi, dyi, 'r', label='derivative', zorder=100) - ax.margins(x=0, y=0.1) - -plt.legend(bbox_to_anchor=(0, -0.75, 1., 0.1), loc=8, ncol=3, mode="normal", borderaxespad=0.5) - -figure.tight_layout() -figure.subplots_adjust(bottom=0.18, hspace=0.5) -plt.show() +""" +Spline based interpolation methods +""" + +import numpy as np +import matplotlib.pyplot as plt +from sdf.ndtable import NDTable + + +x = np.array([0, 1, 2, 3, 4, 5]) +y = np.array([0, 1, 2, 2, 3, 4]) + +table = NDTable(y, (x,)) + +xi = np.linspace(-1, 6, 1000) +dxi = np.ones_like(xi) + + +methods = ["akima", "fritsch-butland", "steffen"] + +figure, axes = plt.subplots(len(methods), sharex=True) + +figure.set_facecolor("white") + +for ax, method in zip(axes, methods): + yi = table.evaluate((xi,), interp=method, extrap="linear") + dyi = table.evaluate_derivative((xi,), (dxi,), interp=method, extrap="linear") + ax.set_title(method) + ax.grid(True, color="0.9") + ax.plot(x, y, "or", label="samples", zorder=300) + ax.plot(xi, yi, "b", label="interpolated", zorder=100) + ax.plot(xi, dyi, "r", label="derivative", zorder=100) + ax.margins(x=0, y=0.1) + +plt.legend( + bbox_to_anchor=(0, -0.75, 1.0, 0.1), loc=8, ncol=3, mode="normal", borderaxespad=0.5 +) + +figure.tight_layout() +figure.subplots_adjust(bottom=0.18, hspace=0.5) +plt.show() diff --git a/sdf/examples/time_series.xlsx b/src/sdf/examples/time_series.xlsx similarity index 100% rename from sdf/examples/time_series.xlsx rename to src/sdf/examples/time_series.xlsx diff --git a/sdf/hdf5.py b/src/sdf/hdf5.py similarity index 62% rename from sdf/hdf5.py rename to src/sdf/hdf5.py index 1caa448..84b2fab 100644 --- a/sdf/hdf5.py +++ b/src/sdf/hdf5.py @@ -1,206 +1,214 @@ -import h5py -import sdf -import numpy as np -import os -import sys - - -def _to_python_str(s): - """ Convert to Python string """ - - if isinstance(s, bytes): - return s.decode('utf-8') - else: - return s - - -def load(filename, objectname): - - with h5py.File(filename, 'r') as f: - - datasets = {} - - dsobj = f[objectname] - class_name = dsobj.__class__.__name__ - - if class_name == 'Group': - group = _create_group(dsobj, datasets) - _restore_scales(datasets) - return group - elif class_name == 'Dataset': - dataset = _create_dataset(dsobj, datasets) - - for ri in range(dsobj.ndim): - if dsobj.dims[ri]: - sobj = dsobj.dims[ri][0] - s = _create_dataset(sobj, dict()) - s.is_scale = True - dataset.scales[ri] = s - - return dataset - - else: - raise Exception('Unexpected object') - - -def save(filename, group): - - with h5py.File(filename, 'w') as f: - - datasets = dict() - _write_group(f, group, '/', datasets) - - # attach the scales - for ds, h5ds in datasets.items(): - for i, s in enumerate(ds.scales): - if s is None: - continue - elif s in datasets: - h5s = datasets[s] - dimname = s._display_name - if dimname is None: - dimname = '' - h5s.make_scale(_str(dimname)) - h5ds.dims[i].attach_scale(h5s) - else: - print("Cannot attach scale for '" + h5ds.name + - "' because the referenced scale for dimension " + str(i) + " is not part of the file") - - -def _create_group(gobj, datasets): - """ Create an sdf.Group from an h5py group """ - - ds_obj_list = [] - g_obj_list = [] - - group_attrs = {key: gobj.attrs[key] for key in gobj.attrs.keys() if key != 'COMMENT'} - comment = gobj.attrs.get('COMMENT') - - for ds_name in gobj.keys(): - # TODO: fix this? - if isinstance(gobj[ds_name], h5py._hl.dataset.Dataset): - ds_obj_list.append(gobj[ds_name]) - elif isinstance(gobj[ds_name], h5py._hl.group.Group): - g_obj_list.append(gobj[ds_name]) - - child_groups = [] - - for cgobj in g_obj_list: - child_groups.append(_create_group(cgobj, datasets)) - - ds_list = [_create_dataset(dsobj, datasets) for dsobj in ds_obj_list] - - name = gobj.name.split('/')[-1] - - return sdf.Group(name=name, comment=comment, attributes=group_attrs, groups=child_groups, datasets=ds_list) - - -def _create_dataset(dsobj, datasets): - """ Create a dataset from an h5py dataset """ - - _, name = os.path.split(dsobj.name) - ds = sdf.Dataset(name, data=dsobj[()]) - - for attr in dsobj.attrs: - if attr == 'COMMENT': - ds.comment = _to_python_str(dsobj.attrs[attr]) - elif attr == 'NAME': - ds.display_name = _to_python_str(dsobj.attrs[attr]) - elif attr == 'RELATIVE_QUANTITY' and _to_python_str(dsobj.attrs[attr]) == 'TRUE': - ds.relative_quantity = True - elif attr == 'UNIT': - ds.unit = _to_python_str(dsobj.attrs[attr]) - elif attr == 'DISPLAY_UNIT': - ds.display_unit = _to_python_str(dsobj.attrs[attr]) - elif attr == 'CLASS' and _to_python_str(dsobj.attrs[attr]) == 'DIMENSION_SCALE': - ds.is_scale = True - elif attr == 'REFERENCE_LIST': - ds.is_scale = True - elif attr in ['REFERENCE_LIST', 'DIMENSION_LIST']: - pass - else: - ds.attributes[attr] = _to_python_str(dsobj.attrs[attr]) - - ds.scales = [None] * ds.data.ndim - - datasets[dsobj] = ds - - return ds - - -def _restore_scales(datasets): - - for dsobj, ds in datasets.items(): - - for i in range(ds.data.ndim): - if dsobj.dims[i]: - sobj = dsobj.dims[i][0] - scale = datasets[sobj] - scale.is_scale = True - ds.scales[i] = scale - pass - - -def _str(s): - """ Convert to byte string """ - - if sys.version_info.major >= 3 and isinstance(s, bytes): - return s - else: - # convert the string to an fixed-length utf-8 byte string - return np.bytes_(s.encode('utf-8')) - - -def _write_group(f, g, path, datasets): - - if path == '/': - gobj = f - else: - gobj = f.create_group(path) - - # iterate over the child groups - for subgroup in g.groups: - _write_group(f, subgroup, path + subgroup.name + '/', datasets) - - if g.comment is not None: - gobj.attrs['COMMENT'] = _str(g.comment) - - for key, value in g.attributes.items(): - gobj.attrs[key] = _str(value) - - # write the datasets - for ds in g.datasets: - _write_dataset(f, ds, path, datasets) - - -def _write_dataset(f, ds, path, datasets): - - f[path + ds.name] = ds.data - dsobj = f[path + ds.name] - - datasets[ds] = dsobj - - if ds.comment: - dsobj.attrs['COMMENT'] = _str(ds.comment) - - if ds._display_name: - dsobj.attrs['NAME'] = _str(ds.display_name) - - if ds.relative_quantity: - dsobj.attrs['RELATIVE_QUANTITY'] = _str('TRUE') - - if ds.unit: - dsobj.attrs['UNIT'] = _str(ds.unit) - - if ds.display_unit != ds.unit: - dsobj.attrs['DISPLAY_UNIT'] = _str(ds.display_unit) - - if ds.is_scale: - dimname = ds.display_name - - if dimname is None: - dimname = '' - - h5py.h5ds.set_scale(dsobj.id, _str(dimname)) - - return dsobj +from __future__ import annotations +import h5py +import sdf +import numpy as np +import os +import sys + + +def _to_python_str(s): + """Convert to Python string""" + + if isinstance(s, bytes): + return s.decode("utf-8") + else: + return s + + +def load(filename: str | os.PathLike, objectname: str) -> sdf.Dataset | sdf.Group: + with h5py.File(filename, "r") as f: + datasets = {} + + dsobj = f[objectname] + class_name = dsobj.__class__.__name__ + + if class_name == "Group": + group = _create_group(dsobj, datasets) + _restore_scales(datasets) + return group + elif class_name == "Dataset": + dataset = _create_dataset(dsobj, datasets) + + for ri in range(dsobj.ndim): + if dsobj.dims[ri]: + sobj = dsobj.dims[ri][0] + s = _create_dataset(sobj, dict()) + s.is_scale = True + dataset.scales[ri] = s + + return dataset + + else: + raise Exception("Unexpected object") + + +def save(filename: str | os.PathLike, group: sdf.Group) -> None: + with h5py.File(filename, "w") as f: + datasets = dict() + _write_group(f, group, "/", datasets) + + # attach the scales + for ds, h5ds in datasets.items(): + for i, s in enumerate(ds.scales): + if s is None: + continue + elif s in datasets: + h5s = datasets[s] + dimname = s._display_name + if dimname is None: + dimname = "" + h5s.make_scale(_str(dimname)) + h5ds.dims[i].attach_scale(h5s) + else: + print( + "Cannot attach scale for '" + + h5ds.name + + "' because the referenced scale for dimension " + + str(i) + + " is not part of the file" + ) + + +def _create_group(gobj, datasets): + """Create an sdf.Group from an h5py group""" + + ds_obj_list = [] + g_obj_list = [] + + group_attrs = { + key: gobj.attrs[key] for key in gobj.attrs.keys() if key != "COMMENT" + } + comment = gobj.attrs.get("COMMENT") + + for ds_name in gobj.keys(): + # TODO: fix this? + if isinstance(gobj[ds_name], h5py._hl.dataset.Dataset): + ds_obj_list.append(gobj[ds_name]) + elif isinstance(gobj[ds_name], h5py._hl.group.Group): + g_obj_list.append(gobj[ds_name]) + + child_groups = [] + + for cgobj in g_obj_list: + child_groups.append(_create_group(cgobj, datasets)) + + ds_list = [_create_dataset(dsobj, datasets) for dsobj in ds_obj_list] + + name = gobj.name.split("/")[-1] + + return sdf.Group( + name=name, + comment=comment, + attributes=group_attrs, + groups=child_groups, + datasets=ds_list, + ) + + +def _create_dataset(dsobj, datasets): + """Create a dataset from an h5py dataset""" + + _, name = os.path.split(dsobj.name) + ds = sdf.Dataset(name, data=dsobj[()]) + + for attr in dsobj.attrs: + if attr == "COMMENT": + ds.comment = _to_python_str(dsobj.attrs[attr]) + elif attr == "NAME": + ds.display_name = _to_python_str(dsobj.attrs[attr]) + elif ( + attr == "RELATIVE_QUANTITY" and _to_python_str(dsobj.attrs[attr]) == "TRUE" + ): + ds.relative_quantity = True + elif attr == "UNIT": + ds.unit = _to_python_str(dsobj.attrs[attr]) + elif attr == "DISPLAY_UNIT": + ds.display_unit = _to_python_str(dsobj.attrs[attr]) + elif attr == "CLASS" and _to_python_str(dsobj.attrs[attr]) == "DIMENSION_SCALE": + ds.is_scale = True + elif attr == "REFERENCE_LIST": + ds.is_scale = True + elif attr in ["REFERENCE_LIST", "DIMENSION_LIST"]: + pass + else: + ds.attributes[attr] = _to_python_str(dsobj.attrs[attr]) + + ds.scales = [None] * ds.data.ndim + + datasets[dsobj] = ds + + return ds + + +def _restore_scales(datasets): + for dsobj, ds in datasets.items(): + for i in range(ds.data.ndim): + if dsobj.dims[i]: + sobj = dsobj.dims[i][0] + scale = datasets[sobj] + scale.is_scale = True + ds.scales[i] = scale + pass + + +def _str(s): + """Convert to byte string""" + + if sys.version_info.major >= 3 and isinstance(s, bytes): + return s + else: + # convert the string to an fixed-length utf-8 byte string + return np.bytes_(s.encode("utf-8")) + + +def _write_group(f, g, path, datasets): + if path == "/": + gobj = f + else: + gobj = f.create_group(path) + + # iterate over the child groups + for subgroup in g.groups: + _write_group(f, subgroup, path + subgroup.name + "/", datasets) + + if g.comment is not None: + gobj.attrs["COMMENT"] = _str(g.comment) + + for key, value in g.attributes.items(): + gobj.attrs[key] = _str(value) + + # write the datasets + for ds in g.datasets: + _write_dataset(f, ds, path, datasets) + + +def _write_dataset(f, ds, path, datasets): + f[path + ds.name] = ds.data + dsobj = f[path + ds.name] + + datasets[ds] = dsobj + + if ds.comment: + dsobj.attrs["COMMENT"] = _str(ds.comment) + + if ds._display_name: + dsobj.attrs["NAME"] = _str(ds.display_name) + + if ds.relative_quantity: + dsobj.attrs["RELATIVE_QUANTITY"] = _str("TRUE") + + if ds.unit: + dsobj.attrs["UNIT"] = _str(ds.unit) + + if ds.display_unit != ds.unit: + dsobj.attrs["DISPLAY_UNIT"] = _str(ds.display_unit) + + if ds.is_scale: + dimname = ds.display_name + + if dimname is None: + dimname = "" + + h5py.h5ds.set_scale(dsobj.id, _str(dimname)) + + return dsobj diff --git a/src/sdf/ndtable/__init__.py b/src/sdf/ndtable/__init__.py new file mode 100644 index 0000000..dfa4ee5 --- /dev/null +++ b/src/sdf/ndtable/__init__.py @@ -0,0 +1,273 @@ +from ctypes import c_double, c_void_p, c_int, byref, cdll +from pathlib import Path + +import numpy as np +import sys +from platform import machine + + +if machine().lower() in {"aarch64", "arm64"}: + _platform = "aarch64" +elif machine().lower() in {"amd64", "i386", "i686", "x86", "x86_64", "x86pc"}: + if sys.maxsize > 2**32: + _platform = "x86_64" + else: + _platform = "x86" +else: + raise Exception(f"Unsupported architecture: {machine()}") + +if sys.platform.startswith("win"): + _platform += "-windows" + _sl_name = "NDTable.dll" +elif sys.platform.startswith("linux"): + _platform += "-linux" + _sl_name = "libNDTable.so" +elif sys.platform.startswith("darwin"): + _platform += "-darwin" + _sl_name = "libndtable.dylib" +else: + raise Exception("Unsupported platform: " + sys.platform) + +_shared_library = Path(__file__).parent / _platform / _sl_name + +_ndtable = cdll.LoadLibrary(str(_shared_library)) + +# PYTHON_API ModelicaNDTable_h create_table(int ndims, const int *dims, const double *data, const double **scales) { +_create_table = _ndtable.create_table +_create_table.argtypes = [c_int, c_void_p, c_void_p, (c_void_p * 32)] +_create_table.restype = c_void_p + +# PYTHON_API int evaluate( +# ModelicaNDTable_h table, +# int ndims, +# const double **params, +# ModelicaNDTable_InterpMethod_t interp_method, +# ModelicaNDTable_ExtrapMethod_t extrap_method, +# int nvalues, +# double *value); +_evaluate = _ndtable.evaluate +_evaluate.argtypes = [c_void_p, c_int, c_void_p, c_int, c_int, c_int, c_void_p] +_evaluate.restype = c_int + +# PYTHON_API int evaluate_derivative( +# ModelicaNDTable_h table, +# int nparams, +# const double params[], +# const double delta_params[], +# ModelicaNDTable_InterpMethod_t interp_method, +# ModelicaNDTable_ExtrapMethod_t extrap_method, +# double *value); +_evaluate_derivative = _ndtable.evaluate_derivative +_evaluate_derivative.argtypes = [ + c_void_p, + c_int, + c_void_p, + c_void_p, + c_int, + c_int, + c_void_p, +] +_evaluate_derivative.restype = c_int + +_close_table = _ndtable.close_table +_close_table.argtypes = [c_void_p] + + +class NDTable(object): + """ + An n-dimensional lookup table + + Attributes + ---------- + data : ndarray + The values to interpolate. + scales : tuple of ndarrays + The scales for `data`. There must be one scale for every dimension of `data`. + The values must be strictly monotonic increasing. + """ + + _interp_methods = { + "hold": 1, + "nearest": 2, + "linear": 3, + "akima": 4, + "fritsch-butland": 5, + "steffen": 6, + } + _extrap_methods = {"hold": 1, "linear": 2} + + def __init__(self, data, scales): + scales = list(scales) + + # convert the arguments to double arrays + data = np.asanyarray(data, dtype=np.float64, order="C") + + for i, scale in enumerate(scales): + scales[i] = np.asanyarray(scale, dtype=np.float64, order="C") + + # check the arguments + assert data.ndim <= 32, "Max. number of dimensions is 32" + assert len(scales) == data.ndim, ( + "The number of scales must match the number of dimensions" + ) + for i, scale in enumerate(scales): + assert np.all(np.isfinite(scale)), ( + "The scale for dimension %d is not finite" % i + ) + assert scale.ndim == 1, "Scales must be one-dimensional" + assert scale.size == data.shape[i], ( + "The scale for dimension %d does not match the shape of data" % i + ) + + dims = np.asarray(data.shape, np.int32) + scales_ = (c_void_p * 32)() + for i, scale in enumerate(scales): + scales_[i] = scale.ctypes.data_as(c_void_p) + self._table = _create_table( + c_int(data.ndim), + dims.ctypes.data_as(c_void_p), + data.ctypes.data_as(c_void_p), + scales_, + ) + + # save close function from garbage collection + self._close_table = _close_table + + def evaluate(self, points, interp="linear", extrap="hold"): + """ + Evaluate the lookup table at the coordinates in `points`. + + Returns an array of the same shape as the coordinates in `points`. + + Parameters + ---------- + points : tuple of ndarrays + The coordinates of the points to evaluate. + interp : string, optional + The interpolation method (one of 'nearest', 'linear' or 'akima') + Default is 'linear'. + extrap : string, optional + The extrapolation method (one of 'hold' or 'linear') + Default is 'hold'. + + Returns + ------- + samples : ndarray + The evaluated points. + + Example + -------- + >>> import numpy as np + >>> from sdf.ndtable import NDTable + >>> x = y = np.linspace(-1, 1, 3) + >>> x, y + (array([-1., 0., 1.]), array([-1., 0., 1.])) + >>> X, Y = np.meshgrid(x, y, indexing='ij') + >>> Z = X * Y + >>> Z + array([[ 1., -0., -1.], + [-0., 0., 0.], + [-1., 0., 1.]]) + >>> lut = NDTable(Z, (x, y)) + >>> x2 = y2 = np.linspace(-1, 1, 5) + >>> x2, y2 + (array([-1. , -0.5, 0. , 0.5, 1. ]), array([-1. , -0.5, 0. , 0.5, 1. ])) + >>> X2, Y2 = np.meshgrid(x2, y2, indexing='ij') + >>> Z2 = lut.evaluate((X2, Y2)) + >>> Z2 + array([[ 1. , 0.5 , 0. , -0.5 , -1. ], + [ 0.5 , 0.25, 0. , -0.25, -0.5 ], + [ 0. , 0. , 0. , 0. , 0. ], + [-0.5 , -0.25, 0. , 0.25, 0.5 ], + [-1. , -0.5 , 0. , 0.5 , 1. ]]) + + """ + + points = list(points) + + for i, _ in enumerate(points): + points[i] = np.asarray(points[i], np.float64) + + shape = points[0].shape + + for p in points[1:]: + assert p.shape == shape, "The arrays in points must have the same shape" + + assert interp in self._interp_methods, ( + 'Unknown interpolation method "%s"' % interp + ) + assert extrap in self._extrap_methods, ( + 'Unknown extrapolation method "%s"' % extrap + ) + + interp_method = c_int(self._interp_methods[interp]) + extrap_method = c_int(self._extrap_methods[extrap]) + + values = np.empty(shape) + params = (c_void_p * len(points))() + for i, param in enumerate(points): + params[i] = param.ctypes.data_as(c_void_p) + + ret = _evaluate( + c_void_p(self._table), + c_int(len(params)), + params, + interp_method, + extrap_method, + c_int(values.size), + values.ctypes.data_as(c_void_p), + ) + + assert ret == 0, "An error occurred during interpolation" + + return values + + def evaluate_derivative(self, points, deltas, interp="linear", extrap="hold"): + points = list(points) + deltas = list(deltas) + + for i, _ in enumerate(points): + points[i] = np.asarray(points[i], np.float64) + deltas[i] = np.asarray(deltas[i], np.float64) + + shape = points[0].shape + + for p in points[1:]: + assert p.shape == shape, "The arrays in points must have the same shape" + + assert interp in self._interp_methods, ( + 'Unknown interpolation method "%s"' % interp + ) + assert extrap in self._extrap_methods, ( + 'Unknown extrapolation method "%s"' % extrap + ) + + interp_method = c_int(self._interp_methods[interp]) + extrap_method = c_int(self._extrap_methods[extrap]) + value = c_double() + + out = np.empty(shape) + params = np.empty(len(points)) + delta_params = np.empty(len(points)) + + for index in np.ndindex(shape): + for i, point in enumerate(points): + params[i] = point[index] + for i, delta in enumerate(deltas): + delta_params[i] = delta[index] + + _evaluate_derivative( + c_void_p(self._table), + c_int(params.size), + params.ctypes.data_as(c_void_p), + delta_params.ctypes.data_as(c_void_p), + interp_method, + extrap_method, + byref(value), + ) + out[index] = value.value + + return out + + def __del__(self): + self._close_table(self._table) diff --git a/sdf/plot/contour_plot.py b/src/sdf/plot/contour_plot.py similarity index 77% rename from sdf/plot/contour_plot.py rename to src/sdf/plot/contour_plot.py index 1a04c20..855bf1e 100644 --- a/sdf/plot/contour_plot.py +++ b/src/sdf/plot/contour_plot.py @@ -1,52 +1,52 @@ -""" -Create a contour plot from an SDF dataset -""" - -import numpy as np -import sdf -import sys -import matplotlib.pyplot as plt - - -filename = sys.argv[1] -objects = sys.argv[2] - -dataset = sdf.load(filename, objects) - -delta = 0.025 - - -x = dataset.scales[1].data -y = dataset.scales[0].data - -subs = [0] * dataset.data.ndim -subs[0] = slice(None) -subs[1] = slice(None) - -Z = dataset.data[subs].T - -X, Y = np.meshgrid(x, y, indexing='ij') - -figure = plt.figure() - -figure.patch.set_facecolor('white') - -ax = figure.add_subplot(1, 1, 1) - -ax.grid(True) - -CS = plt.contourf(X, Y, Z, 10, cmap=plt.cm.viridis) - -cbar = figure.colorbar(CS) - -CS = plt.contour(X, Y, Z, 10, colors='k') - -plt.clabel(CS=CS, fontsize=9, inline=1, colors='k') - -ax.set_title(dataset.display_name + " / " + dataset.unit) -ax.set_xlabel(dataset.scales[1].display_name + " / " + dataset.scales[1].unit) -ax.set_ylabel(dataset.scales[0].display_name + " / " + dataset.scales[0].unit) - -plt.tight_layout() - -plt.show() +""" +Create a contour plot from an SDF dataset +""" + +import numpy as np +import sdf +import sys +import matplotlib.pyplot as plt + + +filename = sys.argv[1] +objects = sys.argv[2] + +dataset = sdf.load(filename, objects) + +delta = 0.025 + + +x = dataset.scales[1].data +y = dataset.scales[0].data + +subs = [0] * dataset.data.ndim +subs[0] = slice(None) +subs[1] = slice(None) + +Z = dataset.data[subs].T + +X, Y = np.meshgrid(x, y, indexing="ij") + +figure = plt.figure() + +figure.patch.set_facecolor("white") + +ax = figure.add_subplot(1, 1, 1) + +ax.grid(True) + +CS = plt.contourf(X, Y, Z, 10, cmap=plt.cm.viridis) + +cbar = figure.colorbar(CS) + +CS = plt.contour(X, Y, Z, 10, colors="k") + +plt.clabel(CS=CS, fontsize=9, inline=1, colors="k") + +ax.set_title(dataset.display_name + " / " + dataset.unit) +ax.set_xlabel(dataset.scales[1].display_name + " / " + dataset.scales[1].unit) +ax.set_ylabel(dataset.scales[0].display_name + " / " + dataset.scales[0].unit) + +plt.tight_layout() + +plt.show() diff --git a/src/sdf/plot/contour_plot_3d.py b/src/sdf/plot/contour_plot_3d.py new file mode 100644 index 0000000..43e9f4a --- /dev/null +++ b/src/sdf/plot/contour_plot_3d.py @@ -0,0 +1,105 @@ +""" +Create a contour plot from an SDF dataset +""" + +import numpy as np +import sdf +import matplotlib.pyplot as plt +from matplotlib import colors + +import matplotlib.pylab as pylab + + +def create_plot(filename, datasets): + params = { + # 'legend.fontsize': 'medium', + # 'figure.figsize': (10, 8), + "legend.fontsize": "small", + "axes.labelsize": "small", + "axes.titlesize": "small", + "xtick.labelsize": "small", + "ytick.labelsize": "small", + } + + pylab.rcParams.update(params) + + figure = plt.figure(figsize=(12, 8)) + figure.patch.set_facecolor("white") + + for row, dataset in enumerate(datasets): + # load the datasets + C1 = sdf.load(filename, dataset) + + ncols = C1.scales[2].data.size + + norm = colors.Normalize(vmin=np.nanmin(C1.data), vmax=np.nanmax(C1.data)) + + subs = [0] * C1.data.ndim + subs[0] = slice(None) + subs[1] = slice(None) + + ax0 = None + + for i in range(ncols): + x = C1.scales[1].data + y = C1.scales[0].data + + subs[2] = i + + Z = C1.data[subs].T + + X, Y = np.meshgrid(x, y, indexing="ij") + + ax = figure.add_subplot( + len(datasets), + C1.data.shape[2], + (row * ncols) + i + 1, + sharex=ax0, + sharey=ax0, + ) + + if i == 0: + ax0 = ax + ax.set_ylabel( + C1.display_name + + " / " + + C1.unit + + "\n" + + C1.scales[0].display_name + + " / " + + C1.scales[0].unit + ) + # else: + # ax.get_yaxis().set_ticklabels([]) + + ax.grid(True) + + CSF = plt.contourf(X, Y, Z, 10, cmap=plt.cm.viridis, norm=norm) + + CS = plt.contour(X, Y, Z, 10, colors="k") + + plt.clabel(CS=CS, fontsize=9, inline=1, colors="k") + + scale3 = C1.scales[2] + + ax.set_title( + scale3.display_name + "=" + ("%g" % scale3.data[i]) + " " + scale3.unit + ) + ax.set_xlabel(C1.scales[1].display_name + " / " + C1.scales[1].unit) + + figure.colorbar(CSF) + + plt.tight_layout() + + plt.show() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("filename") + parser.add_argument("datasets", nargs="+") + args = parser.parse_args() + + create_plot(filename=args.filename, datasets=args.datasets) diff --git a/sdf/plot/plot_time_series.py b/src/sdf/plot/plot_time_series.py similarity index 63% rename from sdf/plot/plot_time_series.py rename to src/sdf/plot/plot_time_series.py index b4cc8e4..b6de8aa 100644 --- a/sdf/plot/plot_time_series.py +++ b/src/sdf/plot/plot_time_series.py @@ -1,70 +1,67 @@ -import sdf -import sys -import matplotlib.pyplot as plt -import numpy as np -import matplotlib.pylab as pylab - - -def plot_time_series(filename, datasets): - - params = { - # 'legend.fontsize': 'medium', - 'figure.figsize': (10, 8), - 'axes.labelsize': 'small', - # 'axes.titlesize': 'medium', - 'xtick.labelsize': 'small', - 'ytick.labelsize': 'small' - } - - pylab.rcParams.update(params) - - figure, axes = plt.subplots(len(datasets), sharex=True) - - # figure = plt.figure() - figure.patch.set_facecolor('white') - - for ax, path in zip(axes, datasets): - - dataset = sdf.load(filename, path) - - scale = dataset.scales[0] - - y = dataset.data - x = scale.data if scale is not None else range(len(y)) - - # ax = figure.add_subplot(len(datasets), 1, i + 1) - - ax.plot(x, y, 'b') - - ax.grid(b=True, which='both', color='0.8', linestyle='-') - - ax.set_xlim([np.nanmin(x), np.nanmax(x)]) - ylabel = path - if dataset.unit is not None: - ylabel += " / %s" % dataset.unit - ax.set_ylabel(ylabel) - ax.margins(y=0.05) - - # x-axis label - if scale is not None: - xlabel = scale.name - if scale.unit is not None: - xlabel += " / %s" % scale.unit - else: - xlabel = 'Index' - ax.set_xlabel(xlabel) - - figure.tight_layout() - - plt.show() - - -if __name__ == '__main__': - - try: - plot_time_series(filename=sys.argv[1], datasets=sys.argv[2:]) - except: - import traceback - traceback.print_exc() - input("Press Enter to continue...") - exit(1) +import sdf +import matplotlib.pyplot as plt +import numpy as np +import matplotlib.pylab as pylab + + +def plot_time_series(filename, datasets): + params = { + # 'legend.fontsize': 'medium', + "figure.figsize": (10, 8), + "axes.labelsize": "small", + # 'axes.titlesize': 'medium', + "xtick.labelsize": "small", + "ytick.labelsize": "small", + } + + pylab.rcParams.update(params) + + figure, axes = plt.subplots(len(datasets), sharex=True) + + # figure = plt.figure() + figure.patch.set_facecolor("white") + + for ax, path in zip(axes, datasets): + dataset = sdf.load(filename, path) + + scale = dataset.scales[0] + + y = dataset.data + x = scale.data if scale is not None else range(len(y)) + + # ax = figure.add_subplot(len(datasets), 1, i + 1) + + ax.plot(x, y, "b") + + ax.grid(b=True, which="both", color="0.8", linestyle="-") + + ax.set_xlim([np.nanmin(x), np.nanmax(x)]) + ylabel = path + if dataset.unit is not None: + ylabel += " / %s" % dataset.unit + ax.set_ylabel(ylabel) + ax.margins(y=0.05) + + # x-axis label + if scale is not None: + xlabel = scale.name + if scale.unit is not None: + xlabel += " / %s" % scale.unit + else: + xlabel = "Index" + ax.set_xlabel(xlabel) + + figure.tight_layout() + + plt.show() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("filename") + parser.add_argument("datasets", nargs="+") + args = parser.parse_args() + + plot_time_series(filename=args.filename, datasets=args.datasets) diff --git a/src/sdf/units.py b/src/sdf/units.py new file mode 100644 index 0000000..c6b8058 --- /dev/null +++ b/src/sdf/units.py @@ -0,0 +1,155 @@ +import math + + +class Table(object): + """ + Utility class to store the unit conversions (inspired by Guava's com.google.common.collect.Table) + """ + + def __init__(self): + self._rows = dict() + + def put(self, row_key, column_key, value): + if row_key in self._rows: + self._rows[row_key][column_key] = value + else: + self._rows[row_key] = {column_key: value} + + def get(self, row_key, column_key): + if row_key in self._rows: + return self._rows[row_key][column_key] + else: + return None + + +global _converters +_converters = Table() + + +def convert_unit(value, from_unit, to_unit): + if from_unit == to_unit: + return value # nothing to do + + converter = _converters.get(from_unit, to_unit) + + if not converter: + raise Exception( + 'No conversion defined for "' + from_unit + '" -> "' + to_unit + '"' + ) + + return converter.convert(value) + + +class LinearUnitConverter(object): + def __init__(self, from_unit, to_unit, factor, offset=0): + self.from_unit = from_unit + self.to_unit = to_unit + self.factor = factor + self.offset = offset + + def convert(self, value): + return value * self.factor + self.offset + + +def define_unit_conversion(from_unit, to_unit, factor, offset=0): + global _converters + _converters.put( + from_unit, to_unit, LinearUnitConverter(from_unit, to_unit, factor, offset) + ) + _converters.put( + to_unit, + from_unit, + LinearUnitConverter(to_unit, from_unit, 1 / factor, -offset / factor), + ) + + +# Time +define_unit_conversion("s", "ms", 1000) +define_unit_conversion("s", "min", 1.0 / 60.0) +define_unit_conversion("s", "h", 1.0 / 3600.0) +define_unit_conversion("s", "d", 1.0 / 86400.0) + +# Angle +define_unit_conversion("rad", "deg", 180 / math.pi) + +# Angular velocity +define_unit_conversion("rad/s", "deg/s", 180 / math.pi) +define_unit_conversion("rad/s", "rpm", 30 / math.pi) +define_unit_conversion("rad/s", "1/min", 30 / math.pi) +define_unit_conversion("rad/s", "r/min", 30 / math.pi) + +# Length, distance +define_unit_conversion("m", "km", 0.001) +# define_unit_conversion('m', 'cm', 100) +define_unit_conversion("m", "mm", 1000) + +# Area +# define_unit_conversion('m2','cm2',1e4) + +# Volume +define_unit_conversion("m3", "l", 1e3) +define_unit_conversion("m3", "ml", 1e6) + +# Pressure +define_unit_conversion("Pa", "kPa", 1e-3) +define_unit_conversion("Pa", "MPa", 1e-6) +define_unit_conversion("Pa", "bar", 1e-5) + +# Bulk Modulus +define_unit_conversion("N/m2", "bar", 1 / 1e5) + +# Volume Flow Rate +define_unit_conversion("m3/s", "l/min", 1e3 * 60) + +# Density +define_unit_conversion("kg/m3", "kg/dm3", 1e-3) +define_unit_conversion("kg/m3", "kg/l", 1e-3) +# define_unit_conversion('kg/m3', 'g/cm3', 1e-3) +# define_unit_conversion('kg/s', 'g/s', 1e3) + +# Speed +define_unit_conversion("m/s", "km/h", 3.6) +define_unit_conversion("m/s", "mm/s", 1e3) +# define_unit_conversion('m/s', 'knots', 1.9438445) + +# Force +define_unit_conversion("N", "mN", 1000) +define_unit_conversion("N", "kN", 1e-3) +define_unit_conversion("N", "MN", 1e-6) + +# Work, Energy +define_unit_conversion("J", "kWh", 1.0 / 3600.0 / 1000.0) +define_unit_conversion("J", "mJ", 1000) +define_unit_conversion("J", "kJ", 1e-3) +define_unit_conversion("J", "MJ", 1e-6) + +# Power +define_unit_conversion("W", "mW", 1000) +define_unit_conversion("W", "kW", 1e-3) +define_unit_conversion("W", "MW", 1e-6) + +# Temperature +define_unit_conversion("K", "degC", 1, -273.15) + +# Voltage +define_unit_conversion("V", "mV", 1000) +define_unit_conversion("V", "kV", 0.001) + +# Currrent +define_unit_conversion("A", "mA", 1000) +define_unit_conversion("A", "kA", 0.001) + +# Capacitance +define_unit_conversion("F", "uF", 1e6) + +# Leakage +define_unit_conversion("m3/(s.Pa)", "l/(min.bar)", 6e9) + +# Viscous Friction +# define_unit_conversion('N.m/(rad/s)', 'N.m/(rev/min)',0.10471975512) + +# Kinematic Viscosity +define_unit_conversion("m2/s", "mm2/s", 1e6) + +# Temperature coefficient (e.g. of resistance) +define_unit_conversion("1/K", "ppm/K", 1e6) diff --git a/tests/test_sdf.py b/tests/test_sdf.py index afaf709..5bb1cb0 100644 --- a/tests/test_sdf.py +++ b/tests/test_sdf.py @@ -10,11 +10,9 @@ class Test(unittest.TestCase): - @classmethod def setUpClass(cls): - - if platform.system() == 'Windows': + if platform.system() == "Windows": # plot tests currently only work on Windows anyways import matplotlib.pyplot as plt @@ -26,9 +24,15 @@ def no_show(): plt.show = no_show def assertDatasetsEqual(self, ds1, ds2): - # compare string attributes - for attr in ['name', 'comment', 'display_name', 'relative_quantity', 'unit', 'display_unit']: + for attr in [ + "name", + "comment", + "display_name", + "relative_quantity", + "unit", + "display_unit", + ]: a = getattr(ds1, attr) b = getattr(ds2, attr) self.assertEqual(a, b) @@ -37,183 +41,217 @@ def assertDatasetsEqual(self, ds1, ds2): self.assertTrue(np.all(ds1.data == ds2.data)) def test_data_types(self): + ds_f = sdf.Dataset(name="f", data=np.asarray([1, 2, 3], dtype=np.float32)) + ds_d = sdf.Dataset(name="d", data=np.asarray([1, 2, 3], dtype=np.float64)) + ds_i = sdf.Dataset(name="i", data=np.asarray([1, 2, 3], dtype=np.int32)) - ds_f = sdf.Dataset(name='f', data=np.asarray([1, 2, 3], dtype=np.float32)) - ds_d = sdf.Dataset(name='d', data=np.asarray([1, 2, 3], dtype=np.float64)) - ds_i = sdf.Dataset(name='i', data=np.asarray([1, 2, 3], dtype=np.int32)) - - g = sdf.Group(name='/', datasets=[ds_f, ds_d, ds_i]) + g = sdf.Group(name="/", datasets=[ds_f, ds_d, ds_i]) - sdf.save('data_types.sdf', g) + sdf.save("data_types.sdf", g) - g = sdf.load('data_types.sdf') + g = sdf.load("data_types.sdf") - self.assertEqual(g['f'].data.dtype, np.float32) - self.assertEqual(g['d'].data.dtype, np.float64) - self.assertEqual(g['i'].data.dtype, np.int32) + self.assertEqual(g["f"].data.dtype, np.float32) + self.assertEqual(g["d"].data.dtype, np.float64) + self.assertEqual(g["i"].data.dtype, np.int32) def test_roundtrip(self): - # create a scale - ds1 = sdf.Dataset('DS1', - comment="dataset 1", - data=np.array([0.1, 0.2, 0.3]), - display_name='Scale 1', - unit='U1', - display_unit='DU1', - is_scale=True) - + ds1 = sdf.Dataset( + "DS1", + comment="dataset 1", + data=np.array([0.1, 0.2, 0.3]), + display_name="Scale 1", + unit="U1", + display_unit="DU1", + is_scale=True, + ) + # create a 1D dataset - ds2 = sdf.Dataset('DS2', - comment="dataset 2", - data=np.array([1, 2, 3]), - display_name='Dataset 2', - relative_quantity=True, - unit='U2', - display_unit='DU2', - scales=[ds1]) - + ds2 = sdf.Dataset( + "DS2", + comment="dataset 2", + data=np.array([1, 2, 3]), + display_name="Dataset 2", + relative_quantity=True, + unit="U2", + display_unit="DU2", + scales=[ds1], + ) + # create a group - g = sdf.Group(name='/', - comment="my comment", - attributes={'A1': 'my string'}, - datasets=[ds1, ds2]) - - g2 = sdf.Group(name='G2') + g = sdf.Group( + name="/", + comment="my comment", + attributes={"A1": "my string"}, + datasets=[ds1, ds2], + ) + + g2 = sdf.Group(name="G2") g.groups.append(g2) - + # save the group - sdf.save('test.sdf', g) + sdf.save("test.sdf", g) - # load DS2 from the file - ds2r = sdf.load('test.sdf', '/DS2') + # load DS2 from the file + ds2r = sdf.load("test.sdf", "/DS2") # make sure the content is still the same self.assertDatasetsEqual(ds2, ds2r) self.assertDatasetsEqual(ds2.scales[0], ds2r.scales[0]) def test_hierarchy(self): - # create a scale - ds_time = sdf.Dataset('Time', - comment="A scale", - data=np.linspace(0, 10, 101), - unit='s', - is_scale=True) - - ds_sine = sdf.Dataset('sine', - comment="A 1-d dataset /w attached scale", - data=np.sin(ds_time.data), - scales=[ds_time]) - + ds_time = sdf.Dataset( + "Time", + comment="A scale", + data=np.linspace(0, 10, 101), + unit="s", + is_scale=True, + ) + + ds_sine = sdf.Dataset( + "sine", + comment="A 1-d dataset /w attached scale", + data=np.sin(ds_time.data), + scales=[ds_time], + ) + # create the root group - g = sdf.Group(name='/', - comment="A test file", - attributes={'A1': "my string"}, - datasets=[ds_time, ds_sine]) + g = sdf.Group( + name="/", + comment="A test file", + attributes={"A1": "my string"}, + datasets=[ds_time, ds_sine], + ) # create a scalar dataset - ds_alpha = sdf.Dataset('alpha', - comment="A scalar /w unit, display unit and display name", - data=np.pi, - display_name='Angle', - unit='rad', - display_unit='deg') + ds_alpha = sdf.Dataset( + "alpha", + comment="A scalar /w unit, display unit and display name", + data=np.pi, + display_name="Angle", + unit="rad", + display_unit="deg", + ) # create a sub group - g1 = sdf.Group(name='g1', - comment="A sub-group", - attributes={'A2': "Attribute in sub group"}, - datasets=[ds_alpha]) + g1 = sdf.Group( + name="g1", + comment="A sub-group", + attributes={"A2": "Attribute in sub group"}, + datasets=[ds_alpha], + ) g.groups.append(g1) # save the group - sdf.save('roundtrip.sdf', g) - - # load the group from the file - g2 = sdf.load('roundtrip.sdf', '/') - + sdf.save("roundtrip.sdf", g) + + # load the group from the file + sdf.load("roundtrip.sdf", "/") + # TODO: compare the objects - #self.assertEqual(pickle.dumps(g), pickle.dumps(g2)) - + # self.assertEqual(pickle.dumps(g), pickle.dumps(g2)) + def test_3D_example(self): - RPM2RADS = 2 * math.pi / 60 - - kfric = 1 # [Ws/rad] angular damping coefficient [0;100] + + kfric = 1 # [Ws/rad] angular damping coefficient [0;100] kfric3 = 1.5e-6 # [Ws3/rad3] angular damping coefficient (3rd order) [0;10-3] - psi = 0.2 # [Vs] flux linkage [0.001;10] - res = 5e-3 # [Ohm] resistance [0;100] - v_ref = 200 # [V] reference DC voltage [0;1000] - k_u = 5 # linear voltage coefficient [-100;100] - + psi = 0.2 # [Vs] flux linkage [0.001;10] + res = 5e-3 # [Ohm] resistance [0;100] + v_ref = 200 # [V] reference DC voltage [0;1000] + k_u = 5 # linear voltage coefficient [-100;100] + tau = np.arange(0.0, 230.0 + 10.0, 10) - w = np.concatenate((np.arange(0.0, 500.0, 100), np.arange(500.0, 12e3+500, 500))) * RPM2RADS + w = ( + np.concatenate( + (np.arange(0.0, 500.0, 100), np.arange(500.0, 12e3 + 500, 500)) + ) + * RPM2RADS + ) u = np.asarray([200.0, 300.0, 400.0]) - + # calculate the power losses - TAU, W, U = np.meshgrid(tau, w, u, indexing='ij') - - P_loss = kfric * W + kfric3 * W ** 3 + (res * (TAU / psi) ** 2) + k_u * (U - v_ref) - + TAU, W, U = np.meshgrid(tau, w, u, indexing="ij") + + P_loss = ( + kfric * W + kfric3 * W**3 + (res * (TAU / psi) ** 2) + k_u * (U - v_ref) + ) + # create the scales - ds_tau = sdf.Dataset('tau', - comment='Torque', - data=tau, - display_name='Torque', - unit='N.m', - is_scale=True) - - ds_w = sdf.Dataset('w', - comment='Speed', - data=w, - display_name='Speed', - unit='rad/s', - display_unit='rpm', - is_scale=True) - - ds_v = sdf.Dataset('v', - comment='DC voltage', - data=u, - display_name='DC voltage', - unit='V', - is_scale=True) - + ds_tau = sdf.Dataset( + "tau", + comment="Torque", + data=tau, + display_name="Torque", + unit="N.m", + is_scale=True, + ) + + ds_w = sdf.Dataset( + "w", + comment="Speed", + data=w, + display_name="Speed", + unit="rad/s", + display_unit="rpm", + is_scale=True, + ) + + ds_v = sdf.Dataset( + "v", + comment="DC voltage", + data=u, + display_name="DC voltage", + unit="V", + is_scale=True, + ) + # create the dataset - ds_P_loss = sdf.Dataset('P_loss', - comment='Power losses', - display_name='Power Losses', - data=P_loss, - unit='W', - scales=[ds_tau, ds_w, ds_v]) - + ds_P_loss = sdf.Dataset( + "P_loss", + comment="Power losses", + display_name="Power Losses", + data=P_loss, + unit="W", + scales=[ds_tau, ds_w, ds_v], + ) + # create a group - g = sdf.Group(name='/', - comment="Example loss characteristics of an e-machine w.r.t. torque, speed and DC voltage", - datasets=[ds_tau, ds_w, ds_v, ds_P_loss]) + g = sdf.Group( + name="/", + comment="Example loss characteristics of an e-machine w.r.t. torque, speed and DC voltage", + datasets=[ds_tau, ds_w, ds_v, ds_P_loss], + ) errors = sdf.validate(g) self.assertEqual([], errors) - - sdf.save('emachine.sdf', g) - + + sdf.save("emachine.sdf", g) + def test_validate_group(self): - g = sdf.Group('8') + g = sdf.Group("8") errors = sdf._validate_group(g, is_root=False) - self.assertEqual(["Object names must only contain letters, digits and underscores (\"_\") and must start with a letter"], errors) - - g.name = 'G1' + self.assertEqual( + [ + 'Object names must only contain letters, digits, and underscores ("_") and must start with a letter.' + ], + errors, + ) + + g.name = "G1" errors = sdf._validate_group(g, is_root=False) self.assertEqual([], errors) def test_validate_dataset(self): - ds1 = sdf.Dataset('DS1') - + ds1 = sdf.Dataset("DS1") + ds1.data = 1 errors = sdf._validate_dataset(ds1) self.assertEqual(["Dataset.data must be a numpy.ndarray"], errors) - + ds1.data = np.array([]) errors = sdf._validate_dataset(ds1) self.assertEqual(["Dataset.data must not be empty"], errors) @@ -221,66 +259,67 @@ def test_validate_dataset(self): ds1.data = np.array(1).astype(np.float32) errors = sdf._validate_dataset(ds1) self.assertEqual(["Dataset.data.dtype must be numpy.float64"], errors) - + ds1.data = np.array([1.0, 2.0]) errors = sdf._validate_dataset(ds1) - self.assertEqual(["The number of scales does not match the number of dimensions"], errors) + self.assertEqual( + ["The number of scales does not match the number of dimensions"], errors + ) - ds2 = sdf.Dataset('DS2', data=np.array([0.0, 1.0, 2.0]), is_scale=True) + ds2 = sdf.Dataset("DS2", data=np.array([0.0, 1.0, 2.0]), is_scale=True) ds1.scales = [ds2] errors = sdf._validate_dataset(ds1) self.assertEqual([], errors) - + ds2.data = np.array([[1.0, 2.0], [3.0, 4.0]]) errors = sdf._validate_dataset(ds2) self.assertEqual(["Scales must be one-dimensional"], errors) - + ds2.data = np.array([0, 1.0, 1.0]) errors = sdf._validate_dataset(ds2) self.assertEqual(["Scales must be strictly monotonic increasing"], errors) def test_dsres_load_all(self): path, _ = os.path.split(sdf.__file__) - filename = os.path.join(path, 'examples', 'IntegerNetwork1.mat') + filename = os.path.join(path, "examples", "IntegerNetwork1.mat") g = sdf.load(filename) - s = g['Time'] + s = g["Time"] self.assertEqual(s.data.size, 552) # self.assertEqual(s.data.dtype, np.dtype(np.float32)) - self.assertEqual(s.unit, 's') - self.assertEqual(s.comment, 'Simulation time') + self.assertEqual(s.unit, "s") + self.assertEqual(s.comment, "Simulation time") - ds = g['booleanPulse2']['period'] + ds = g["booleanPulse2"]["period"] self.assertEqual(ds.data, 2.0) - self.assertEqual(ds.unit, 's') - self.assertEqual(ds.comment, 'Time for one period') + self.assertEqual(ds.unit, "s") + self.assertEqual(ds.comment, "Time for one period") - ds = g['booleanPulse2']['y'] + ds = g["booleanPulse2"]["y"] self.assertEqual(ds.data.dtype, np.int32) self.assertEqual(ds.data.size, 552) self.assertEqual(ds.data[0], True) self.assertEqual(ds.data[93], False) self.assertEqual(ds.scales[0], s) - ds = g['integerConstant']['k'] + ds = g["integerConstant"]["k"] self.assertEqual(ds.data.dtype, np.int32) self.assertEqual(ds.data, 1) # sdf.save(filename=os.path.join(path, 'examples', 'IntegerNetwork1.sdf'), group=g) def test_dsres_load_dataset(self): - path, _ = os.path.split(sdf.__file__) - filename = os.path.join(path, 'examples', 'IntegerNetwork1.mat') + filename = os.path.join(path, "examples", "IntegerNetwork1.mat") - ds = sdf.load(filename, objectname='/booleanPulse2/period') + ds = sdf.load(filename, objectname="/booleanPulse2/period") self.assertEqual(ds.data, 2.0) - self.assertEqual(ds.unit, 's') - self.assertEqual(ds.comment, 'Time for one period') + self.assertEqual(ds.unit, "s") + self.assertEqual(ds.comment, "Time for one period") - ds = sdf.load(filename, objectname='/booleanPulse2/y') + ds = sdf.load(filename, objectname="/booleanPulse2/y") self.assertEqual(ds.data.dtype, np.dtype(np.int32)) self.assertEqual(ds.data.size, 552) self.assertEqual(ds.data[0], True) @@ -289,34 +328,34 @@ def test_dsres_load_dataset(self): s = ds.scales[0] self.assertEqual(s.data.size, 552) # self.assertEqual(s.data.dtype, np.dtype(np.float32)) - self.assertEqual(s.unit, 's') - self.assertEqual(s.comment, 'Simulation time') + self.assertEqual(s.unit, "s") + self.assertEqual(s.comment, "Simulation time") - ds = sdf.load(filename, objectname='/integerConstant/k') + ds = sdf.load(filename, objectname="/integerConstant/k") self.assertEqual(ds.data.dtype, np.dtype(np.int32)) self.assertEqual(ds.data, 1) def test_dsres_inverted_signals(self): path = os.path.dirname(__file__) - filename = os.path.join(path, 'DoublePendulum.mat') - rvisobj = sdf.load(filename, '/world/y_label/cylinders[2]/rvisobj[1]') + filename = os.path.join(path, "DoublePendulum.mat") + rvisobj = sdf.load(filename, "/world/y_label/cylinders[2]/rvisobj[1]") self.assertTrue(rvisobj.data < 0) - @skipIf(platform.system() != 'Windows', "Test requires display") + @skipIf(platform.system() != "Windows", "Test requires display") def test_interp_1d_example(self): - runpy.run_module('sdf.examples.interp_1d') + runpy.run_module("sdf.examples.interp_1d") - @skipIf(platform.system() != 'Windows', "Test requires display") + @skipIf(platform.system() != "Windows", "Test requires display") def test_interp_2d_example(self): - runpy.run_module('sdf.examples.interp_2d') + runpy.run_module("sdf.examples.interp_2d") - @skipIf(platform.system() != 'Windows', "Test requires display") + @skipIf(platform.system() != "Windows", "Test requires display") def test_sine_example(self): - runpy.run_module('sdf.examples.sine') + runpy.run_module("sdf.examples.sine") - @skipIf(platform.system() != 'Windows', "Test requires display") + @skipIf(platform.system() != "Windows", "Test requires display") def test_spline_1d_example(self): - runpy.run_module('sdf.examples.spline_1d') + runpy.run_module("sdf.examples.spline_1d") if __name__ == "__main__": diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1671195 --- /dev/null +++ b/uv.lock @@ -0,0 +1,706 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "fonttools" +version = "4.58.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/7a/30c581aeaa86d94e7a29344bccefd2408870bf5b0e7640b6f4ffede61bd0/fonttools-4.58.1.tar.gz", hash = "sha256:cbc8868e0a29c3e22628dfa1432adf7a104d86d1bc661cecc3e9173070b6ab2d", size = 3519505, upload-time = "2025-05-28T15:29:26.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ed/94a7310e6ee87f6164d7cf273335445fb12b70625582df137b3692ec495b/fonttools-4.58.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ebd423034ac4f74196c1ae29f8ed3b862f820345acbf35600af8596ebf62573", size = 2734333, upload-time = "2025-05-28T15:27:59.568Z" }, + { url = "https://files.pythonhosted.org/packages/09/d9/7f16d4aea0494dc02a284cb497ddd37a5b88d0d3da4ea41f7298ce96ca1a/fonttools-4.58.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9dc36f4b4044d95e6fb358da4c3e6a5c07c9b6f4c1e8c396e89bee3b65dae902", size = 2306563, upload-time = "2025-05-28T15:28:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/cf/16/abdecf240d4fcc8badf6dbe3941500b64acd1401288bd9515e936ab2d27f/fonttools-4.58.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4b74d7bb84189fe264d56a544ac5c818f8f1e8141856746768691fe185b229", size = 4717603, upload-time = "2025-05-28T15:28:03.849Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3c/ad9bc6cfb4c4260689808b083c1d1a0c15b11d7c87bf7f6e61f77d4c106c/fonttools-4.58.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa4fa41e9cb43f78881a5896d6e41b6a0ec54e9d68e7eaaff6d7a1769b17017", size = 4750798, upload-time = "2025-05-28T15:28:05.956Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/d32080afcd754b78c7bedfa8475b6887792fca81a95ff7c634a59dc8eb4c/fonttools-4.58.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91335202f19c9edc04f2f6a7d9bb269b0a435d7de771e3f33c3ea9f87f19c8d4", size = 4800201, upload-time = "2025-05-28T15:28:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/46/21/68f5285ba7c59c9df8fdc045b55a149c10af865b2615ea426daa47bcf287/fonttools-4.58.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6b0ec2171e811a0d9e467225dc06b0fac39a84b4704f263c2d538c3c67b99b2", size = 4908504, upload-time = "2025-05-28T15:28:10.095Z" }, + { url = "https://files.pythonhosted.org/packages/66/77/abf1739cee99672b9bc3701bc3a51b01d325c4e117d7efd7e69315c28ce5/fonttools-4.58.1-cp310-cp310-win32.whl", hash = "sha256:a788983d522d02a9b457cc98aa60fc631dabae352fb3b30a56200890cd338ca0", size = 2190748, upload-time = "2025-05-28T15:28:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/5e/18/e5a239f913f51e48a2d620be07a8f942fb8018850e0fbfeee2c11dd72723/fonttools-4.58.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8c848a2d5961d277b85ac339480cecea90599059f72a42047ced25431e8b72a", size = 2235207, upload-time = "2025-05-28T15:28:14.687Z" }, + { url = "https://files.pythonhosted.org/packages/50/3f/9fecd69149b0eec5ca46ec58de83b2fd34d07204fe2c12c209255082507a/fonttools-4.58.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9966e14729669bcfbb56f83b747a2397c4d97c6d4798cb2e2adc28f9388fa008", size = 2754713, upload-time = "2025-05-28T15:28:18.998Z" }, + { url = "https://files.pythonhosted.org/packages/c8/19/d04ea5f3ab2afa7799f2b1ebe1d57ff71b479f99f29b82bddc7197d50220/fonttools-4.58.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64cc1647bbe83dea57f5496ec878ad19ccdba7185b0dd34955d3e6f03dc789e6", size = 2316637, upload-time = "2025-05-28T15:28:21.016Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3f/375f59d756b17318336c050363849011e03ac82904538f39ebe8189835bc/fonttools-4.58.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464f790ce681d08d1583df0735776aa9cb1999594bf336ddd0bf962c17b629ac", size = 4915730, upload-time = "2025-05-28T15:28:22.633Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/069f859d6f6480503574cda21b84ceee98bf5f5fd1764f26674e828a2600/fonttools-4.58.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c53c6a720ee70cc25746d511ba88c45c95ec510fd258026ed209b0b9e3ba92f", size = 4936194, upload-time = "2025-05-28T15:28:24.704Z" }, + { url = "https://files.pythonhosted.org/packages/01/11/339973e588e1c27f20c578f845bdcf84376c5e42bd35fca05419fd8d1648/fonttools-4.58.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6823a633bbce29cf3033508ebb54a433c473fb9833eff7f936bfdc5204fd98d", size = 4978982, upload-time = "2025-05-28T15:28:26.633Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/1c627532a69715f54b8d96ab3a7bc8628f6e89989e9275dfc067dc2d6d56/fonttools-4.58.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5701fe66a1408c1974d2f78c00f964f8aad17cccbc32bc041e1b81421f31f448", size = 5090087, upload-time = "2025-05-28T15:28:29.608Z" }, + { url = "https://files.pythonhosted.org/packages/77/ce/cf7b624db35bce589ac1f2c98329ea91b28f0283d3b7e9e6126dfaeb5abd/fonttools-4.58.1-cp311-cp311-win32.whl", hash = "sha256:4cad2c74adf9ee31ae43be6b0b376fdb386d4d50c60979790e32c3548efec051", size = 2188923, upload-time = "2025-05-28T15:28:31.797Z" }, + { url = "https://files.pythonhosted.org/packages/b9/22/c4f1f76eeb1b9353e9cc81451d0ae08acc3d3aa31b9ab8f3791a18af1f89/fonttools-4.58.1-cp311-cp311-win_amd64.whl", hash = "sha256:7ade12485abccb0f6b6a6e2a88c50e587ff0e201e48e0153dd9b2e0ed67a2f38", size = 2236853, upload-time = "2025-05-28T15:28:33.381Z" }, + { url = "https://files.pythonhosted.org/packages/32/97/ed1078b1e138fbc0b4ee75878000d549a70c02d83bb4e557e416efc34140/fonttools-4.58.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f56085a65769dc0100822c814069327541db9c3c4f21e599c6138f9dbda75e96", size = 2740473, upload-time = "2025-05-28T15:28:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/28/35/53d49fb7d6b30128153d11628b976fda3ce8ae44234b5a81c4edb3023798/fonttools-4.58.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19c65a88e522c9f1be0c05d73541de20feada99d23d06e9b5354023cc3e517b0", size = 2309936, upload-time = "2025-05-28T15:28:37.145Z" }, + { url = "https://files.pythonhosted.org/packages/0c/db/8b63c1d673b2bf0cfed77500d47769dc4aa85453b5f0ef525db2cf952895/fonttools-4.58.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b01bb37006e97703300bfde7a73d1c7038574dd1df9d8d92ca99af151becf2ca", size = 4814671, upload-time = "2025-05-28T15:28:39.339Z" }, + { url = "https://files.pythonhosted.org/packages/a6/13/0b96eeb148b77c521b8e94628c59d15e4fb0e76191c41f5616a656d6adb9/fonttools-4.58.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d629dea240f0fc826d8bb14566e95c663214eece21b5932c9228d3e8907f55aa", size = 4881493, upload-time = "2025-05-28T15:28:41.586Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b0/9f8aa60e8e5be91aba8dfaa3fa6b33fd950511686921cf27e97bf4154e3d/fonttools-4.58.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef0b33ff35421a04a638e736823c2dee9d200cdd275cfdb43e875ca745150aae", size = 4874960, upload-time = "2025-05-28T15:28:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7e/83b409659eb4818f1283a8319f3570497718d6d3b70f4fca2ddf962e948e/fonttools-4.58.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4db9399ee633855c718fe8bea5eecbdc5bf3fdbed2648e50f67f8946b943ed1c", size = 5026677, upload-time = "2025-05-28T15:28:45.354Z" }, + { url = "https://files.pythonhosted.org/packages/34/52/1eb69802d3b54e569158c97810195f317d350f56390b83c43e1c999551d8/fonttools-4.58.1-cp312-cp312-win32.whl", hash = "sha256:5cf04c4f73d36b30ea1cff091a7a9e65f8d5b08345b950f82679034e9f7573f4", size = 2176201, upload-time = "2025-05-28T15:28:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/6f/25/8dcfeb771de8d9cdffab2b957a05af4395d41ec9a198ec139d2326366a07/fonttools-4.58.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a3841b59c67fa1f739542b05211609c453cec5d11d21f863dd2652d5a81ec9b", size = 2225519, upload-time = "2025-05-28T15:28:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/7ed2e4e381f9b1f5122d33b7e626a40f646cacc1ef72d8806aacece9e580/fonttools-4.58.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68379d1599fc59569956a97eb7b07e0413f76142ac8513fa24c9f2c03970543a", size = 2731231, upload-time = "2025-05-28T15:28:51.435Z" }, + { url = "https://files.pythonhosted.org/packages/e7/28/74864dc9248e917cbe07c903e0ce1517c89d42e2fab6b0ce218387ef0e24/fonttools-4.58.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8631905657de4f9a7ae1e12186c1ed20ba4d6168c2d593b9e0bd2908061d341b", size = 2305224, upload-time = "2025-05-28T15:28:53.114Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/ced758896188c1632c5b034a0741457f305e087eb4fa762d86aa3c1ae422/fonttools-4.58.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ecea7289061c2c71468723409a8dd6e70d1ecfce6bc7686e5a74b9ce9154fe", size = 4793934, upload-time = "2025-05-28T15:28:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/c1/46/8b46469c6edac393de1c380c7ec61922d5440f25605dfca7849e5ffff295/fonttools-4.58.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b8860f8cd48b345bd1df1d7be650f600f69ee971ffe338c5bd5bcb6bdb3b92c", size = 4863415, upload-time = "2025-05-28T15:28:56.917Z" }, + { url = "https://files.pythonhosted.org/packages/12/1b/82aa678bb96af6663fe163d51493ffb8622948f4908c886cba6b67fbf6c5/fonttools-4.58.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7c9a0acdefcb8d7ccd7c59202056166c400e797047009ecb299b75ab950c2a9c", size = 4865025, upload-time = "2025-05-28T15:28:58.926Z" }, + { url = "https://files.pythonhosted.org/packages/7d/26/b66ab2f2dc34b962caecd6fa72a036395b1bc9fb849f52856b1e1144cd63/fonttools-4.58.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1fac0be6be3e4309058e156948cb73196e5fd994268b89b5e3f5a26ee2b582", size = 5002698, upload-time = "2025-05-28T15:29:01.118Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/cdddc63333ed77e810df56e5e7fb93659022d535a670335d8792be6d59fd/fonttools-4.58.1-cp313-cp313-win32.whl", hash = "sha256:aed7f93a9a072f0ce6fb46aad9474824ac6dd9c7c38a72f8295dd14f2215950f", size = 2174515, upload-time = "2025-05-28T15:29:03.424Z" }, + { url = "https://files.pythonhosted.org/packages/ba/81/c7f395718e44cebe1010fcd7f1b91957d65d512d5f03114d2d6d00cae1c4/fonttools-4.58.1-cp313-cp313-win_amd64.whl", hash = "sha256:b27d69c97c20c9bca807f7ae7fc7df459eb62994859ff6a2a489e420634deac3", size = 2225290, upload-time = "2025-05-28T15:29:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/21/ff/995277586691c0cc314c28b24b4ec30610440fd7bf580072aed1409f95b0/fonttools-4.58.1-py3-none-any.whl", hash = "sha256:db88365d0962cd6f5bce54b190a4669aeed9c9941aa7bd60a5af084d8d9173d6", size = 1113429, upload-time = "2025-05-28T15:29:24.185Z" }, +] + +[[package]] +name = "h5py" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876, upload-time = "2025-02-18T16:04:01.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/8a/bc76588ff1a254e939ce48f30655a8f79fac614ca8bd1eda1a79fa276671/h5py-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5540daee2b236d9569c950b417f13fd112d51d78b4c43012de05774908dff3f5", size = 3413286, upload-time = "2025-02-18T16:02:11.355Z" }, + { url = "https://files.pythonhosted.org/packages/19/bd/9f249ecc6c517b2796330b0aab7d2351a108fdbd00d4bb847c0877b5533e/h5py-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:10894c55d46df502d82a7a4ed38f9c3fdbcb93efb42e25d275193e093071fade", size = 2915673, upload-time = "2025-02-18T16:02:15.687Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/0dd079208d7d3c3988cebc0776c2de58b4d51d8eeb6eab871330133dfee6/h5py-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb267ce4b83f9c42560e9ff4d30f60f7ae492eacf9c7ede849edf8c1b860e16b", size = 4283822, upload-time = "2025-02-18T16:02:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/0b6a59a1043c53d5d287effa02303bd248905ee82b25143c7caad8b340ad/h5py-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2cf6a231a07c14acd504a945a6e9ec115e0007f675bde5e0de30a4dc8d86a31", size = 4548100, upload-time = "2025-02-18T16:02:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/ad555a7ff7836c943fe97009405566dc77bcd2a17816227c10bd067a3ee1/h5py-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:851ae3a8563d87a5a0dc49c2e2529c75b8842582ccaefbf84297d2cfceeacd61", size = 2950547, upload-time = "2025-02-18T16:02:32.758Z" }, + { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922, upload-time = "2025-02-18T16:02:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619, upload-time = "2025-02-18T16:02:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366, upload-time = "2025-02-18T16:02:44.544Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058, upload-time = "2025-02-18T16:02:49.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428, upload-time = "2025-02-18T16:02:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442, upload-time = "2025-02-18T16:02:56.545Z" }, + { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567, upload-time = "2025-02-18T16:03:00.079Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544, upload-time = "2025-02-18T16:03:05.675Z" }, + { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139, upload-time = "2025-02-18T16:03:10.129Z" }, + { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179, upload-time = "2025-02-18T16:03:13.716Z" }, + { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040, upload-time = "2025-02-18T16:03:20.579Z" }, + { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766, upload-time = "2025-02-18T16:03:26.831Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255, upload-time = "2025-02-18T16:03:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580, upload-time = "2025-02-18T16:03:36.429Z" }, + { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890, upload-time = "2025-02-18T16:03:41.037Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720, upload-time = "2024-12-24T18:28:19.158Z" }, + { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413, upload-time = "2024-12-24T18:28:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826, upload-time = "2024-12-24T18:28:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231, upload-time = "2024-12-24T18:28:23.851Z" }, + { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938, upload-time = "2024-12-24T18:28:26.687Z" }, + { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799, upload-time = "2024-12-24T18:28:30.538Z" }, + { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362, upload-time = "2024-12-24T18:28:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695, upload-time = "2024-12-24T18:28:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802, upload-time = "2024-12-24T18:28:38.357Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646, upload-time = "2024-12-24T18:28:40.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260, upload-time = "2024-12-24T18:28:42.273Z" }, + { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633, upload-time = "2024-12-24T18:28:44.87Z" }, + { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885, upload-time = "2024-12-24T18:28:47.346Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175, upload-time = "2024-12-24T18:28:49.651Z" }, + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" }, + { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186, upload-time = "2024-12-24T18:30:45.654Z" }, + { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279, upload-time = "2024-12-24T18:30:47.951Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" }, + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442, upload-time = "2025-04-12T17:47:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553, upload-time = "2025-04-12T17:47:13.153Z" }, + { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503, upload-time = "2025-04-12T17:47:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648, upload-time = "2025-04-12T17:47:17.37Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937, upload-time = "2025-04-12T17:47:19.066Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802, upload-time = "2025-04-12T17:47:21.404Z" }, + { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717, upload-time = "2025-04-12T17:47:23.571Z" }, + { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874, upload-time = "2025-04-12T17:47:25.783Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717, upload-time = "2025-04-12T17:47:28.922Z" }, + { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204, upload-time = "2025-04-12T17:47:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767, upload-time = "2025-04-12T17:47:34.655Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, + { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727, upload-time = "2025-04-12T17:49:31.898Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833, upload-time = "2025-04-12T17:49:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472, upload-time = "2025-04-12T17:49:36.294Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976, upload-time = "2025-04-12T17:49:38.988Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133, upload-time = "2025-04-12T17:49:40.985Z" }, + { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555, upload-time = "2025-04-12T17:49:42.964Z" }, + { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713, upload-time = "2025-04-12T17:49:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" }, + { url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" }, + { url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" }, + { url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" }, + { url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "sdf" +version = "0.3.6" +source = { editable = "." } +dependencies = [ + { name = "attrs" }, + { name = "h5py" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "xlrd" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "attrs", specifier = ">=25.3.0" }, + { name = "h5py", specifier = ">=3.13.0" }, + { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "numpy", specifier = ">=2.2.6" }, + { name = "scipy", specifier = ">=1.15.3" }, + { name = "xlrd", specifier = ">=2.0.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.3" }, + { name = "ruff", specifier = ">=0.11.12" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "xlrd" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259, upload-time = "2020-12-11T10:14:22.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531, upload-time = "2020-12-11T10:14:20.877Z" }, +]