diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000..dd5d571
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,127 @@
+name: CD
+
+on:
+ push:
+ tags: ["v*"]
+ workflow_dispatch:
+
+jobs:
+ build-wheels:
+ name: Build wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ env:
+ USERNAME: GWmodel-Lab
+ FEED_URL: https://nuget.pkg.github.com/GWmodel-Lab/index.json
+ VCPKG_BINARY_SOURCES: "clear;nuget,github,readwrite"
+ VCPKG_USE_NUGET_CACHE: 1
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install cibuildwheel
+ run: pip install cibuildwheel
+
+ - name: Restore Vcpkg
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ cd "$env:VCPKG_INSTALLATION_ROOT"
+ git fetch
+ git switch 2026.04.27 --detach
+
+ - name: Setup NuGet Credentials
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $VCPKG_EXE="$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe"
+ $NUGET_EXE="$(.$VCPKG_EXE fetch nuget)"
+ .$NUGET_EXE sources add -Source ${{ env.FEED_URL }} -Name github -UserName ${{ env.USERNAME }} -Password ${{ secrets.GITHUB_TOKEN }} -StorePasswordInClearText
+ .$NUGET_EXE setapikey ${{ secrets.GITHUB_TOKEN }} -Source "${{ env.FEED_URL }}"
+
+ - name: Build wheels
+ run: python -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_BUILD: "cp312-*"
+ CIBW_SKIP: "pp* *-musllinux* *-manylinux_i686* *-win32 *-macosx_x86_64"
+ CIBW_BEFORE_ALL_LINUX: >
+ apt-get update -qq &&
+ apt-get install -qq libarmadillo-dev libopenblas-dev libgsl-dev
+ CIBW_ENVIRONMENT_LINUX: >
+ CMAKE_ARGS="-DWITH_TESTS=OFF"
+ PYGW_TEST_DATA="{project}/tests/londonhp100.csv"
+ CIBW_BEFORE_ALL_MACOS: brew install armadillo gsl openblas
+ CIBW_ENVIRONMENT_MACOS: >
+ CMAKE_ARGS="-DWITH_TESTS=OFF"
+ PYGW_TEST_DATA="{project}/tests/londonhp100.csv"
+ MACOSX_DEPLOYMENT_TARGET="10.14"
+ CIBW_BEFORE_ALL_WINDOWS: |
+ $vcpkg = "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe"
+ & $vcpkg install armadillo gsl openblas --triplet x64-windows
+ CIBW_ENVIRONMENT_WINDOWS: >
+ PATH="C:/vcpkg/installed/x64-windows/bin"
+ CMAKE_ARGS="-DWITH_TESTS=OFF -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
+ PYGW_TEST_DATA="{project}/tests/londonhp100.csv"
+ CIBW_TEST_COMMAND: "pytest {project}/tests -v --ignore={project}/tests/test_gwss.py"
+ CIBW_TEST_REQUIRES: pytest
+
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-${{ matrix.os }}
+ path: wheelhouse/*.whl
+
+ publish-pypi:
+ name: Publish to PyPI
+ needs: build-wheels
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/v')
+ permissions:
+ id-token: write
+
+ steps:
+ - name: Download wheels
+ uses: actions/download-artifact@v4
+ with:
+ pattern: wheels-*
+ path: dist
+ merge-multiple: true
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+
+ github-release:
+ name: Create GitHub Release
+ needs: build-wheels
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/v')
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download wheels
+ uses: actions/download-artifact@v4
+ with:
+ pattern: wheels-*
+ path: dist
+ merge-multiple: true
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: dist/*.whl
+ generate_release_notes: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..2c44116
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,113 @@
+name: CI
+
+on:
+ push:
+ branches: [main, master]
+ pull_request:
+ branches: [main, master]
+ workflow_dispatch:
+
+jobs:
+ test-unix:
+ name: ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install native dependencies (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -qq libarmadillo-dev libopenblas-dev libgsl-dev
+
+ - name: Install native dependencies (macOS)
+ if: runner.os == 'macOS'
+ run: brew install armadillo gsl openblas
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install build dependencies
+ run: pip install nanobind "scikit-build-core[pyproject]" numpy geopandas pytest
+
+ - name: Build package
+ run: pip install --no-build-isolation -ve .
+ env:
+ CMAKE_ARGS: "-DWITH_TESTS=OFF"
+
+ - name: Run tests
+ run: python -m pytest test/ -v --ignore=test/test_gwss.py
+ env:
+ PYGW_TEST_DATA: test/londonhp100.csv
+
+ test-windows:
+ name: windows-latest
+ runs-on: windows-latest
+ env:
+ USERNAME: GWmodel-Lab
+ FEED_URL: https://nuget.pkg.github.com/GWmodel-Lab/index.json
+ VCPKG_BINARY_SOURCES: "clear;nuget,github,readwrite"
+ VCPKG_USE_NUGET_CACHE: 1
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Restore Vcpkg
+ shell: pwsh
+ run: |
+ cd "$env:VCPKG_INSTALLATION_ROOT"
+ git fetch
+ git switch 2026.04.27 --detach
+
+ - name: Setup NuGet Credentials
+ shell: pwsh
+ run: |
+ $VCPKG_EXE="$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe"
+ $NUGET_EXE="$(.$VCPKG_EXE fetch nuget)"
+ .$NUGET_EXE sources add -Source ${{ env.FEED_URL }} -Name github -UserName ${{ env.USERNAME }} -Password ${{ secrets.GITHUB_TOKEN }} -StorePasswordInClearText
+ .$NUGET_EXE setapikey ${{ secrets.GITHUB_TOKEN }} -Source "${{ env.FEED_URL }}"
+
+ - name: Install native dependencies
+ shell: pwsh
+ run: |
+ $vcpkg = "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe"
+ & $vcpkg install armadillo gsl openblas[threads] --triplet x64-windows
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install build dependencies
+ run: pip install nanobind "scikit-build-core[pyproject]" numpy geopandas pytest
+
+ - name: Build package
+ shell: pwsh
+ run: |
+ $env:CMAKE_ARGS = "-DWITH_TESTS=OFF -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
+ pip install --no-build-isolation -v .
+
+ - name: Add vcpkg DLLs to PATH
+ shell: pwsh
+ run: echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+
+ - name: Run tests
+ run: python -m pytest test/ -v --ignore=test/test_gwss.py
+ env:
+ PYGW_TEST_DATA: test/londonhp100.csv
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..22ca8a1
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,19 @@
+# Read the Docs configuration file
+# https://docs.readthedocs.io/en/stable/config-file/v2.html
+
+version: 2
+
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3.12"
+ jobs:
+ post_checkout:
+ - git submodule update --init --recursive
+
+sphinx:
+ configuration: doc/conf.py
+
+python:
+ install:
+ - requirements: doc/requirements.txt
diff --git a/README.md b/README.md
index 655fde2..eda5b1d 100644
--- a/README.md
+++ b/README.md
@@ -49,11 +49,26 @@ pip install . --config-settings=cmake.args=-DBLA_VENDOR=OpenBLAS
## Getting started
```py
-from pygwmodel import GWRBasic
-algorithm = GWRBasic(data, y, x, 36.0).fit()
+from pygwmodel import GWRBasic, BandwidthWeight, CRSDistance
+algorithm = GWRBasic(data, y, x,
+ weight=BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()).fit()
```
-For full usage, please see the unit tests in `test` directory.
+Multiscale GWR (MGWR) assigns a separate bandwidth to each predictor:
+
+```py
+from pygwmodel import GWRMultiscale, BandwidthWeight
+
+n_var = 4 # intercept + 3 predictors
+weights = [BandwidthWeight(36.0, adaptive=True) for _ in range(n_var)]
+
+algorithm = GWRMultiscale(data, y, x, weights=weights).fit()
+print(algorithm.diagnostic)
+```
+
+For full usage, please see the unit tests in `test` directory and the
+`documentation `_.
## Related work
diff --git a/doc/conf.py b/doc/conf.py
index cb7e032..cb64bf4 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -34,9 +34,28 @@
'sphinx.ext.autodoc'
]
+# Mock C++ extension modules for ReadTheDocs (or other environments
+# where the native libraries cannot be built).
+autodoc_mock_imports = [
+ 'pygwmodel._spatial_weight',
+ 'pygwmodel._parallel',
+ 'pygwmodel._regression',
+ 'pygwmodel._analysis',
+]
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used for content translation via gettext catalogs.
+# Usually you set "language" from the command line.
+language = 'en'
+
+# Locale directories for gettext-based translation
+locale_dirs = ['locale/']
+
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
@@ -48,6 +67,9 @@
'libgwmodel'
]
+# Suppress strict reference-consistency check across translations
+suppress_warnings = ['i18n.inconsistent_references']
+
# -- Options for HTML output -------------------------------------------------
diff --git a/doc/index.rst b/doc/index.rst
index 05885c8..a09ee9d 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,28 +1,53 @@
-.. pygwmodel documentation master file, created by
- sphinx-quickstart on Sat Nov 18 16:45:14 2023.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
+pygwmodel Documentation
+=========================
-Welcome to pygwmodel's documentation!
-=====================================
+**pygwmodel** is a Python package providing conscious and easy-to-use interfaces to
+high-performance C++ implementations of geographically weighted (GW) models,
+based on `libgwmodel `_ and **GeoPandas**.
-The **pygwmodel** is a Python package of bindings to geographically weighted (GW) models.
-GW modelling is a special branch of spatial statistics.
-GW models suit situations when data are not described well by some global model,
-but where there are spatial regions where a suitably localized calibration provides a better description.
+GW models are a branch of spatial statistics suited to situations where data are
+not well described by some global model, but where spatial regions exist where a
+suitably localized calibration provides a better description.
-The goal of **pygwmodel** is to provide conscious and easy-to-use user interface
-to high-performance C++ implementations of GW models based on **GeoPandas**.
-We believe with the newly designed interfaces and the underlying C++ core,
-users will get fluent experiences.
+Implemented Models
+------------------
+
+* **GWRBasic** — Basic Geographically Weighted Regression with a single bandwidth.
+* **GWRMultiscale** — Multiscale GWR (MGWR) with per-variable bandwidths and
+ backfitting algorithm.
+* **GWSS** — Geographically Weighted Summary Statistics (averages and correlations).
+
+Quick Start
+-----------
+
+.. code-block:: python
+
+ from pygwmodel import GWRBasic, GWRMultiscale, BandwidthWeight, CRSDistance
+
+ # Basic GWR
+ algorithm = GWRBasic(data, y, x,
+ weight=BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()).fit()
+ print(algorithm.diagnostic['RSquare'])
+
+ # Multiscale GWR
+ mgwr = GWRMultiscale(data, y, x,
+ weights=[BandwidthWeight(36.0, adaptive=True) for _ in range(4)]
+ ).fit()
+ print(mgwr.diagnostic)
.. toctree::
:maxdepth: 2
:caption: Contents:
+ quickstart
+ spatial_weight
+ models
+ models/gwss
+ performance
modules.rst
-Indices and tables
+Indices and Tables
==================
* :ref:`genindex`
diff --git a/doc/locale/zh_CN/LC_MESSAGES/index.po b/doc/locale/zh_CN/LC_MESSAGES/index.po
new file mode 100644
index 0000000..729213c
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/index.po
@@ -0,0 +1,69 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 18:20+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../index.rst:39
+msgid "Contents:"
+msgstr "目录:"
+
+#: ../../index.rst:2
+msgid "pygwmodel Documentation"
+msgstr "pygwmodel 文档"
+
+#: ../../index.rst:4
+msgid "**pygwmodel** is a Python package providing conscious and easy-to-use interfaces to high-performance C++ implementations of geographically weighted (GW) models, based on `libgwmodel `_ and **GeoPandas**\ ."
+msgstr "**pygwmodel** 是一个基于 `libgwmodel `_ 和 **GeoPandas** 的 Python 软件包,为高性能 C++ 地理加权(GW)模型实现提供简洁易用的接口。"
+
+#: ../../index.rst:8
+msgid "GW models are a branch of spatial statistics suited to situations where data are not well described by some global model, but where spatial regions exist where a suitably localized calibration provides a better description."
+msgstr "GW 模型是空间统计学的一个分支,适用于数据不能被某个全局模型很好描述,但存在空间区域可通过适当的局部标定提供更优描述的情形。"
+
+#: ../../index.rst:13
+msgid "Implemented Models"
+msgstr "已实现的模型"
+
+#: ../../index.rst:15
+msgid "**GWRBasic** — Basic Geographically Weighted Regression with a single bandwidth."
+msgstr "**GWRBasic** — 基础地理加权回归,使用单一带宽。"
+
+#: ../../index.rst:16
+msgid "**GWRMultiscale** — Multiscale GWR (MGWR) with per-variable bandwidths and backfitting algorithm."
+msgstr "**GWRMultiscale** — 多尺度 GWR (MGWR),支持逐变量带宽和后向迭代算法。"
+
+#: ../../index.rst:18
+msgid "**GWSS** — Geographically Weighted Summary Statistics (averages and correlations)."
+msgstr "**GWSS** — 地理加权汇总统计(均值和相关性)。"
+
+#: ../../index.rst:21
+msgid "Quick Start"
+msgstr "快速入门"
+
+#: ../../index.rst:51
+msgid "Indices and Tables"
+msgstr "索引与表格"
+
+#: ../../index.rst:53
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+#: ../../index.rst:54
+msgid ":ref:`modindex`"
+msgstr ":ref:`modindex`"
+
+#: ../../index.rst:55
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/models.po b/doc/locale/zh_CN/LC_MESSAGES/models.po
new file mode 100644
index 0000000..4aa49d6
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/models.po
@@ -0,0 +1,683 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 23:29+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../models.rst:2
+msgid "Regression Models"
+msgstr "回归模型"
+
+#: ../../models/gwr.rst:2
+msgid "Basic Geographically Weighted Regression (GWR)"
+msgstr "基础地理加权回归 (GWR)"
+
+#: ../../models/gwr.rst:7
+#: ../../models/gwr_multiscale.rst:7
+msgid "Mathematical Foundation"
+msgstr "数学原理"
+
+#: ../../models/gwr.rst:9
+msgid "For a dataset of :math:`n` samples and :math:`p` independent variables, the basic GWR model at sample :math:`i` is defined as:"
+msgstr "对于一个包含 :math:`n` 个样本和 :math:`p` 个自变量的数据集,样本 :math:`i` 处的基础 GWR 模型定义为:"
+
+#: ../../models/gwr.rst:12
+msgid "y_i = \\beta_{i0} + \\sum_{k=1}^{p} \\beta_{ik} x_{ik} + \\varepsilon_i"
+msgstr "y_i = \\beta_{i0} + \\sum_{k=1}^{p} \\beta_{ik} x_{ik} + \\varepsilon_i"
+
+#: ../../models/gwr.rst:16
+msgid "where:"
+msgstr "其中:"
+
+#: ../../models/gwr.rst:18
+msgid ":math:`y_i` is the dependent variable,"
+msgstr ":math:`y_i` 为因变量,"
+
+#: ../../models/gwr.rst:19
+msgid ":math:`x_{ik}` is the :math:`k`-th independent variable,"
+msgstr ":math:`x_{ik}` 为第 :math:`k` 个自变量,"
+
+#: ../../models/gwr.rst:20
+msgid ":math:`\\beta_{ik}` is the :math:`k`-th coefficient,"
+msgstr ":math:`\\beta_{ik}` 为第 :math:`k` 个回归系数估计值,"
+
+#: ../../models/gwr.rst:21
+msgid ":math:`\\beta_{i0}` is the intercept,"
+msgstr ":math:`\\beta_{i0}` 为截距,"
+
+#: ../../models/gwr.rst:22
+msgid ":math:`\\varepsilon_i \\sim \\mathcal{N}(0, \\sigma^2)` is the random error."
+msgstr ":math:`\\varepsilon_i \\sim \\mathcal{N}(0, \\sigma^2)` 为随机误差。"
+
+#: ../../models/gwr.rst:24
+msgid "The locally weighted least-squares estimator of the coefficients is:"
+msgstr "系数的局部加权最小二乘估计为:"
+
+#: ../../models/gwr.rst:26
+msgid "\\hat{\\boldsymbol{\\beta}}_i = \\left( \\mathbf{X}^\\top \\mathbf{W}_i \\mathbf{X} \\right)^{-1} \\mathbf{X}^\\top \\mathbf{W}_i \\mathbf{y}"
+msgstr "\\hat{\\boldsymbol{\\beta}}_i = \\left( \\mathbf{X}^\\top \\mathbf{W}_i \\mathbf{X} \\right)^{-1} \\mathbf{X}^\\top \\mathbf{W}_i \\mathbf{y}"
+
+#: ../../models/gwr.rst:30
+msgid "where :math:`\\mathbf{W}_i = \\operatorname{diag}(w_{i1}, w_{i2}, \\dots, w_{in})` is the spatial weighting matrix. Each :math:`w_{ij}` is computed by a kernel function :math:`k(d_{ij}; b)` based on the distance from sample :math:`i` to sample :math:`j`."
+msgstr "其中 :math:`\\mathbf{W}_i = \\operatorname{diag}(w_{i1}, w_{i2}, \\dots, w_{in})` 为空间权重矩阵。每个 :math:`w_{ij}` 由核函数 :math:`k(d_{ij}; b)` 根据样本 :math:`i` 到样本 :math:`j` 的距离计算得出。"
+
+#: ../../models/gwr.rst:36
+msgid "Diagnostic Information"
+msgstr "诊断信息"
+
+#: ../../models/gwr.rst:38
+msgid "After fitting, the algorithm computes the following diagnostics:"
+msgstr "拟合后,算法会计算以下诊断信息:"
+
+#: ../../models/gwr.rst:44
+msgid "Metric"
+msgstr "指标"
+
+#: ../../models/gwr.rst:45
+#: ../../models/gwr_multiscale.rst:109
+msgid "Meaning"
+msgstr "含义"
+
+#: ../../models/gwr.rst:46
+msgid "Key"
+msgstr "键名"
+
+#: ../../models/gwr.rst:47
+msgid "RSS"
+msgstr "RSS"
+
+#: ../../models/gwr.rst:48
+msgid "Residual sum of squares :math:`\\sum (y_i - \\hat{y}_i)^2`"
+msgstr "残差平方和 :math:`\\sum (y_i - \\hat{y}_i)^2`"
+
+#: ../../models/gwr.rst:49
+msgid "``diagnostic['RSS']``"
+msgstr "``diagnostic['RSS']``"
+
+#: ../../models/gwr.rst:50
+msgid "AICc"
+msgstr "AICc"
+
+#: ../../models/gwr.rst:51
+msgid "Corrected Akaike information criterion (smaller is better)"
+msgstr "修正赤池信息准则(越小越好)"
+
+#: ../../models/gwr.rst:52
+msgid "``diagnostic['AICc']``"
+msgstr "``diagnostic['AICc']``"
+
+#: ../../models/gwr.rst:53
+msgid "ENP"
+msgstr "ENP"
+
+#: ../../models/gwr.rst:54
+msgid "Effective number of parameters"
+msgstr "有效参数数"
+
+#: ../../models/gwr.rst:55
+msgid "``diagnostic['ENP']``"
+msgstr "``diagnostic['ENP']``"
+
+#: ../../models/gwr.rst:56
+msgid "EDF"
+msgstr "EDF"
+
+#: ../../models/gwr.rst:57
+msgid "Effective degrees of freedom"
+msgstr "有效自由度"
+
+#: ../../models/gwr.rst:58
+msgid "``diagnostic['EDF']``"
+msgstr "``diagnostic['EDF']``"
+
+#: ../../models/gwr.rst:59
+msgid "R²"
+msgstr "R²"
+
+#: ../../models/gwr.rst:60
+msgid "Coefficient of determination"
+msgstr "决定系数"
+
+#: ../../models/gwr.rst:61
+msgid "``diagnostic['RSquare']``"
+msgstr "``diagnostic['RSquare']``"
+
+#: ../../models/gwr.rst:62
+msgid "Adjusted R²"
+msgstr "修正 R²"
+
+#: ../../models/gwr.rst:63
+msgid "Adjusted R-squared"
+msgstr "修正决定系数"
+
+#: ../../models/gwr.rst:64
+msgid "``diagnostic['RSquareAdjust']``"
+msgstr "``diagnostic['RSquareAdjust']``"
+
+#: ../../models/gwr.rst:69
+#: ../../models/gwr_multiscale.rst:58
+msgid "Key Parameters"
+msgstr "重要设置项"
+
+#: ../../models/gwr.rst:75
+#: ../../models/gwr_multiscale.rst:64
+msgid "Parameter"
+msgstr "参数"
+
+#: ../../models/gwr.rst:76
+#: ../../models/gwr_multiscale.rst:65
+msgid "Description"
+msgstr "描述"
+
+#: ../../models/gwr.rst:77
+#: ../../models/gwr_multiscale.rst:66
+msgid "Default"
+msgstr "默认值"
+
+#: ../../models/gwr.rst:78
+msgid "``weight``"
+msgstr "``weight``"
+
+#: ../../models/gwr.rst:79
+msgid "A single bandwidth weight shared by all variables"
+msgstr "所有变量共用的单一带宽权重"
+
+#: ../../models/gwr.rst:80
+#: ../../models/gwr_multiscale.rst:69
+msgid "Required"
+msgstr "必需"
+
+#: ../../models/gwr.rst:81
+#: ../../models/gwr_multiscale.rst:70
+msgid "``distance``"
+msgstr "``distance``"
+
+#: ../../models/gwr.rst:82
+msgid "The distance metric to use"
+msgstr "使用的距离度量"
+
+#: ../../models/gwr.rst:83
+#: ../../models/gwr_multiscale.rst:72
+msgid "``CRSDistance()``"
+msgstr "``CRSDistance()``"
+
+#: ../../models/gwr.rst:84
+#: ../../models/gwr_multiscale.rst:73
+msgid "``has_intercept``"
+msgstr "``has_intercept``"
+
+#: ../../models/gwr.rst:85
+#: ../../models/gwr_multiscale.rst:74
+msgid "Whether to include an intercept term"
+msgstr "是否包含截距项"
+
+#: ../../models/gwr.rst:86
+#: ../../models/gwr_multiscale.rst:75
+#: ../../models/gwr_multiscale.rst:96
+msgid "``True``"
+msgstr "``True``"
+
+#: ../../models/gwr.rst:87
+msgid "``fit(optimize_bw=...)``"
+msgstr "``fit(optimize_bw=...)``"
+
+#: ../../models/gwr.rst:88
+msgid "Auto-select bandwidth: ``CV`` or ``AIC``"
+msgstr "自动优选带宽:``CV`` 或 ``AIC``"
+
+#: ../../models/gwr.rst:89
+#: ../../models/gwr.rst:92
+msgid "``None``"
+msgstr "``None``"
+
+#: ../../models/gwr.rst:90
+msgid "``fit(optimize_var=...)``"
+msgstr "``fit(optimize_var=...)``"
+
+#: ../../models/gwr.rst:91
+msgid "Auto-select variables via forward selection: AIC change threshold"
+msgstr "通过前向选择自动优选变量:AIC 变化阈值"
+
+#: ../../models/gwr.rst:97
+#: ../../models/gwr_multiscale.rst:120
+#: ../../models/gwss.rst:84
+msgid "Code Examples"
+msgstr "代码示例"
+
+#: ../../models/gwr.rst:100
+msgid "Basic Usage"
+msgstr "基本用法"
+
+#: ../../models/gwr.rst:123
+msgid "Bandwidth Selection"
+msgstr "带宽优选"
+
+#: ../../models/gwr.rst:134
+msgid "Independent Variable Selection"
+msgstr "自变量优选"
+
+#: ../../models/gwr.rst:148
+msgid "Prediction"
+msgstr "预测"
+
+#: ../../models/gwr.rst:158
+#: ../../models/gwr_multiscale.rst:188
+msgid "References"
+msgstr "参考文献"
+
+#: ../../models/gwr.rst:160
+msgid "Brunsdon, C., Fotheringham, A. S., & Charlton, M. E. (1996). *Geographically weighted regression: a method for exploring spatial nonstationarity*. Geographical Analysis, 28(4), 281-298."
+msgstr "Brunsdon, C., Fotheringham, A. S., & Charlton, M. E. (1996). *Geographically weighted regression: a method for exploring spatial nonstationarity*. Geographical Analysis, 28(4), 281-298."
+
+#: ../../models/gwr.rst:164
+msgid "Fotheringham, A. S., Brunsdon, C., & Charlton, M. (2002). *Geographically weighted regression: the analysis of spatially varying relationships*. John Wiley & Sons."
+msgstr "Fotheringham, A. S., Brunsdon, C., & Charlton, M. (2002). *Geographically weighted regression: the analysis of spatially varying relationships*. John Wiley & Sons."
+
+#: ../../models/gwr_multiscale.rst:2
+msgid "Multiscale Geographically Weighted Regression (GWRMultiscale)"
+msgstr "多尺度地理加权回归 (GWRMultiscale)"
+
+#: ../../models/gwr_multiscale.rst:9
+msgid "Multiscale GWR (MGWR) extends basic GWR by allowing each independent variable to have its own bandwidth. Different spatial processes may operate at different spatial scales — the influence of some variables may be highly local (small bandwidth), while others may have regional or global scale (large bandwidth)."
+msgstr "多尺度 GWR (MGWR) 扩展了基础 GWR,允许每个自变量拥有各自的带宽。不同的空间过程可能在不同的空间尺度上运行——某些变量的影响可能高度局部化(小带宽),而其他变量可能具有区域或全局尺度(大带宽)。"
+
+#: ../../models/gwr_multiscale.rst:14
+msgid "The MGWR model form is the same as basic GWR:"
+msgstr "MGWR 模型形式与基础 GWR 相同:"
+
+#: ../../models/gwr_multiscale.rst:16
+msgid "y_i = \\beta_{i0}(b_0) + \\sum_{k=1}^{p} \\beta_{ik}(b_k) x_{ik} + \\varepsilon_i"
+msgstr "y_i = \\beta_{i0}(b_0) + \\sum_{k=1}^{p} \\beta_{ik}(b_k) x_{ik} + \\varepsilon_i"
+
+#: ../../models/gwr_multiscale.rst:20
+msgid "However, each coefficient :math:`\\beta_{ik}` is estimated using its own bandwidth :math:`b_k`. Bandwidth calibration is carried out via the **backfitting** algorithm [#mgwr]_:"
+msgstr "然而,每个系数 :math:`\\beta_{ik}` 使用各自的带宽 :math:`b_k` 进行估计。带宽校准通过**后向迭代算法**\ 执行 [#mgwr]_:"
+
+#: ../../models/gwr_multiscale.rst:24
+msgid "**Initialisation**\ : Compute initial coefficient estimates :math:`\\boldsymbol{\\beta}^{(0)}` using standard GWR with an initial spatial weighting configuration."
+msgstr "**初始化**\ :使用标准 GWR 配合初始空间权重配置计算初始系数估计 :math:`\\boldsymbol{\\beta}^{(0)}`。"
+
+#: ../../models/gwr_multiscale.rst:27
+msgid "**Backfitting**\ : For each iteration :math:`t = 1, 2, \\dots`:"
+msgstr "**后向迭代**\ :对于每次迭代 :math:`t = 1, 2, \\dots`:"
+
+#: ../../models/gwr_multiscale.rst:29
+msgid "For each variable :math:`k`:"
+msgstr "对于每个变量 :math:`k`:"
+
+#: ../../models/gwr_multiscale.rst:31
+msgid "Fix the coefficients of all other variables and compute the residual of the dependent variable for the current variable."
+msgstr "固定所有其他变量的系数,计算当前变量对应的因变量残差。"
+
+#: ../../models/gwr_multiscale.rst:32
+msgid "Select the optimal bandwidth :math:`b_k` for variable :math:`k` (golden section search with CV/AIC criterion)."
+msgstr "为变量 :math:`k`\ 优选最优带宽 :math:`b_k`\ (黄金分割算法配合 CV/AIC 准则)。"
+
+#: ../../models/gwr_multiscale.rst:33
+msgid "Fit new coefficients for variable :math:`k` using :math:`b_k`."
+msgstr "使用 :math:`b_k` 为变量 :math:`k` 拟合新的回归系数估计值。"
+
+#: ../../models/gwr_multiscale.rst:34
+msgid "Update the residuals."
+msgstr "更新残差。"
+
+#: ../../models/gwr_multiscale.rst:36
+msgid "Check the convergence criterion (CVR or dCVR); stop if satisfied."
+msgstr "检查收敛准则(CVR 或 dCVR);满足条件则停止。"
+
+#: ../../models/gwr_multiscale.rst:38
+msgid "**Diagnostics**\ : Compute RSS, AICc, ENP, EDF, R², and other diagnostic metrics."
+msgstr "**诊断**\ :计算 RSS、AICc、ENP、EDF、R² 及其他诊断指标。"
+
+#: ../../models/gwr_multiscale.rst:41
+msgid "Convergence Criteria"
+msgstr "收敛准则"
+
+#: ../../models/gwr_multiscale.rst:43
+msgid "**CVR** (Change in RSS):"
+msgstr "**CVR**\ (残差平方和变化量):"
+
+#: ../../models/gwr_multiscale.rst:45
+msgid "\\text{CVR} = |RSS_t - RSS_{t-1}|"
+msgstr "\\text{CVR} = |RSS_t - RSS_{t-1}|"
+
+#: ../../models/gwr_multiscale.rst:49
+msgid "**dCVR** (relative Change in RSS, default):"
+msgstr "**dCVR**\ (残差平方和相对变化量,默认):"
+
+#: ../../models/gwr_multiscale.rst:51
+msgid "\\text{dCVR} = \\sqrt{\\frac{|RSS_t - RSS_{t-1}|}{RSS_t}}"
+msgstr "\\text{dCVR} = \\sqrt{\\frac{|RSS_t - RSS_{t-1}|}{RSS_t}}"
+
+#: ../../models/gwr_multiscale.rst:67
+msgid "``weights``"
+msgstr "``weights``"
+
+#: ../../models/gwr_multiscale.rst:68
+msgid "A list of bandwidth weights, one per variable (including intercept)"
+msgstr "带宽权重列表,每个变量一个(包括截距)"
+
+#: ../../models/gwr_multiscale.rst:71
+msgid "The distance metric shared by all variables"
+msgstr "所有变量共用的距离度量"
+
+#: ../../models/gwr_multiscale.rst:76
+msgid "``bandwidth_initilize``"
+msgstr "``bandwidth_initilize``"
+
+#: ../../models/gwr_multiscale.rst:77
+msgid "Bandwidth initialisation type per variable"
+msgstr "各变量的带宽初始值类型"
+
+#: ../../models/gwr_multiscale.rst:78
+msgid "All ``Null`` (auto-select)"
+msgstr "``Null``\ (自动选择)"
+
+#: ../../models/gwr_multiscale.rst:79
+msgid "``bandwidth_selection_approach``"
+msgstr "``bandwidth_selection_approach``"
+
+#: ../../models/gwr_multiscale.rst:80
+msgid "Bandwidth selection criterion per variable"
+msgstr "各变量的带宽优选准则"
+
+#: ../../models/gwr_multiscale.rst:81
+msgid "All ``CV``"
+msgstr "全部 ``CV``"
+
+#: ../../models/gwr_multiscale.rst:82
+msgid "``preditor_centered``"
+msgstr "``preditor_centered``"
+
+#: ../../models/gwr_multiscale.rst:83
+msgid "Whether to centre each predictor before fitting"
+msgstr "拟合前是否对每个自变量进行中心化"
+
+#: ../../models/gwr_multiscale.rst:84
+msgid "All ``False``"
+msgstr "全部 ``False``"
+
+#: ../../models/gwr_multiscale.rst:85
+msgid "``criterion_type``"
+msgstr "``criterion_type``"
+
+#: ../../models/gwr_multiscale.rst:86
+msgid "Backfitting convergence criterion type"
+msgstr "后向迭代算法收敛准则类型"
+
+#: ../../models/gwr_multiscale.rst:87
+msgid "``dCVR``"
+msgstr "``dCVR``"
+
+#: ../../models/gwr_multiscale.rst:88
+msgid "``max_iteration``"
+msgstr "``max_iteration``"
+
+#: ../../models/gwr_multiscale.rst:89
+msgid "Maximum number of iterations"
+msgstr "最大迭代次数"
+
+#: ../../models/gwr_multiscale.rst:90
+msgid "``500``"
+msgstr "``500``"
+
+#: ../../models/gwr_multiscale.rst:91
+msgid "``criterion_threshold``"
+msgstr "``criterion_threshold``"
+
+#: ../../models/gwr_multiscale.rst:92
+msgid "Convergence threshold"
+msgstr "收敛阈值"
+
+#: ../../models/gwr_multiscale.rst:93
+msgid "``1e-6``"
+msgstr "``1e-6``"
+
+#: ../../models/gwr_multiscale.rst:94
+msgid "``has_hat_matrix``"
+msgstr "``has_hat_matrix``"
+
+#: ../../models/gwr_multiscale.rst:95
+msgid "Whether to compute the hat matrix (for diagnostics)"
+msgstr "是否计算帽子矩阵(用于诊断)"
+
+#: ../../models/gwr_multiscale.rst:97
+msgid "``adaptive_lower``"
+msgstr "``adaptive_lower``"
+
+#: ../../models/gwr_multiscale.rst:98
+msgid "Lower bound on neighbour count for adaptive bandwidth selection"
+msgstr "可变带宽优选时的邻居数下限"
+
+#: ../../models/gwr_multiscale.rst:99
+msgid "``10``"
+msgstr "``10``"
+
+#: ../../models/gwr_multiscale.rst:102
+msgid "Bandwidth Initialisation Types"
+msgstr "带宽初始值类型"
+
+#: ../../models/gwr_multiscale.rst:108
+msgid "Type"
+msgstr "类型"
+
+#: ../../models/gwr_multiscale.rst:110
+msgid "``BandwidthInitilizeType.Null``"
+msgstr "``BandwidthInitilizeType.Null``"
+
+#: ../../models/gwr_multiscale.rst:111
+msgid "Not specified; auto-select via golden section search during backfitting"
+msgstr "未指定;后向迭代过程中通过黄金分割算法自动选择"
+
+#: ../../models/gwr_multiscale.rst:112
+msgid "``BandwidthInitilizeType.Specified``"
+msgstr "``BandwidthInitilizeType.Specified``"
+
+#: ../../models/gwr_multiscale.rst:113
+msgid "User-specified; skip bandwidth selection and use the fixed value from ``weights``"
+msgstr "用户指定;跳过带宽优选,使用 ``weights`` 中的固定值"
+
+#: ../../models/gwr_multiscale.rst:114
+msgid "``BandwidthInitilizeType.Initial``"
+msgstr "``BandwidthInitilizeType.Initial``"
+
+#: ../../models/gwr_multiscale.rst:115
+msgid "Initially optimised; may still be adjusted in later backfitting iterations"
+msgstr "初始已优化;在后续后向迭代中仍可被调整"
+
+#: ../../models/gwr_multiscale.rst:123
+msgid "Basic Usage (Auto-Select Bandwidths)"
+msgstr "基本用法(自动优选带宽)"
+
+#: ../../models/gwr_multiscale.rst:154
+msgid "Specified Bandwidths"
+msgstr "指定带宽"
+
+#: ../../models/gwr_multiscale.rst:171
+msgid "Note: when ``bandwidth_initilize`` is set to ``Specified``, the bandwidths remain fixed; otherwise they are iteratively optimised during backfitting."
+msgstr "注意:当 ``bandwidth_initilize`` 设置为 ``Specified`` 时,带宽保持固定;否则将在后向迭代过程中逐步优化。"
+
+#: ../../models/gwr_multiscale.rst:175
+msgid "Adjusting Convergence"
+msgstr "调整收敛准则"
+
+#: ../../models/gwr_multiscale.rst:190
+msgid "Fotheringham, A. S., Yang, W., & Kang, W. (2017). *Multiscale geographically weighted regression (MGWR)*. Annals of the American Association of Geographers, 107(6), 1247-1265."
+msgstr "Fotheringham, A. S., Yang, W., & Kang, W. (2017). *Multiscale geographically weighted regression (MGWR)*. Annals of the American Association of Geographers, 107(6), 1247-1265."
+
+#: ../../models/gwr_multiscale.rst:194
+msgid "Yu, H., Fotheringham, A. S., Li, Z., Oshan, T., Kang, W., & Wolf, L. J. (2020). *Inference in multiscale geographically weighted regression*. Geographical Analysis, 52(1), 87-106."
+msgstr "Yu, H., Fotheringham, A. S., Li, Z., Oshan, T., Kang, W., & Wolf, L. J. (2020). *Inference in multiscale geographically weighted regression*. Geographical Analysis, 52(1), 87-106."
+
+#: ../../models/gwss.rst:2
+msgid "Geographically Weighted Summary Statistics (GWSS)"
+msgstr "地理加权汇总统计 (GWSS)"
+
+#: ../../models/gwss.rst:7
+msgid "Model Overview"
+msgstr "模型概述"
+
+#: ../../models/gwss.rst:9
+msgid "Geographically Weighted Summary Statistics (GWSS) performs locally weighted descriptive statistics on multivariate data, revealing spatial heterogeneity in the statistical characteristics of variables."
+msgstr "地理加权汇总统计 (GWSS) 对多维数据执行局部加权描述性统计,揭示变量统计特征的空间异质性。"
+
+#: ../../models/gwss.rst:13
+msgid "GWSS supports two modes:"
+msgstr "GWSS 支持两种模式:"
+
+#: ../../models/gwss.rst:15
+msgid "Average mode — computes local mean, standard deviation, variance, skewness, coefficient of variation, and optionally local median, interquartile range, and quantile imbalance."
+msgstr "均值模式 — 计算局部均值、局部标准差、局部方差、局部偏度、局部变异系数,以及可选的局部中位数、四分位距和分位数不平衡度。"
+
+#: ../../models/gwss.rst:18
+msgid "Correlation mode — computes local Pearson correlation coefficients and Spearman rank correlation coefficients."
+msgstr "相关性模式 — 计算局部皮尔逊相关系数和局部斯皮尔曼秩相关系数。"
+
+#: ../../models/gwss.rst:24
+msgid "Two Modes"
+msgstr "两种模式"
+
+#: ../../models/gwss.rst:27
+#: ../../models/gwss.rst:87
+msgid "Average Mode"
+msgstr "均值模式"
+
+#: ../../models/gwss.rst:29
+msgid "For each variable, the following local statistics are computed:"
+msgstr "对于每个变量,计算以下局部统计量:"
+
+#: ../../models/gwss.rst:35
+#: ../../models/gwss.rst:57
+msgid "Statistic"
+msgstr "统计量"
+
+#: ../../models/gwss.rst:36
+#: ../../models/gwss.rst:58
+msgid "Attribute"
+msgstr "属性"
+
+#: ../../models/gwss.rst:37
+#: ../../models/gwss.rst:59
+msgid "Column Name"
+msgstr "列名"
+
+#: ../../models/gwss.rst:38
+msgid "Local Mean"
+msgstr "局部均值"
+
+#: ../../models/gwss.rst:39
+msgid "``local_mean``"
+msgstr "``local_mean``"
+
+#: ../../models/gwss.rst:40
+msgid "``{variable}_Mean``"
+msgstr "``{variable}_Mean``"
+
+#: ../../models/gwss.rst:41
+msgid "Local Std Dev"
+msgstr "局部标准差"
+
+#: ../../models/gwss.rst:42
+msgid "``local_sdev``"
+msgstr "``local_sdev``"
+
+#: ../../models/gwss.rst:43
+msgid "``{variable}_SDev``"
+msgstr "``{variable}_SDev``"
+
+#: ../../models/gwss.rst:44
+msgid "Local Skewness"
+msgstr "局部偏度"
+
+#: ../../models/gwss.rst:45
+msgid "``local_skewness``"
+msgstr "``local_skewness``"
+
+#: ../../models/gwss.rst:46
+msgid "``{variable}_Skew``"
+msgstr "``{variable}_Skew``"
+
+#: ../../models/gwss.rst:47
+msgid "Local CV"
+msgstr "局部变异系数"
+
+#: ../../models/gwss.rst:48
+msgid "``local_cv``"
+msgstr "``local_cv``"
+
+#: ../../models/gwss.rst:49
+msgid "``{variable}_CV``"
+msgstr "``{variable}_CV``"
+
+#: ../../models/gwss.rst:51
+msgid "When ``quantile=True``:"
+msgstr "当 ``quantile=True`` 时:"
+
+#: ../../models/gwss.rst:60
+msgid "Local Median"
+msgstr "局部中位数"
+
+#: ../../models/gwss.rst:61
+msgid "``local_median``"
+msgstr "``local_median``"
+
+#: ../../models/gwss.rst:62
+msgid "``{variable}_Median``"
+msgstr "``{variable}_Median``"
+
+#: ../../models/gwss.rst:63
+msgid "IQR"
+msgstr "IQR"
+
+#: ../../models/gwss.rst:64
+msgid "``iqr``"
+msgstr "``iqr``"
+
+#: ../../models/gwss.rst:65
+msgid "``{variable}_IQR``"
+msgstr "``{variable}_IQR``"
+
+#: ../../models/gwss.rst:66
+msgid "Quantile Imbalance"
+msgstr "分位数不平衡度"
+
+#: ../../models/gwss.rst:67
+msgid "``qi``"
+msgstr "``qi``"
+
+#: ../../models/gwss.rst:68
+msgid "``{variable}_QI``"
+msgstr "``{variable}_QI``"
+
+#: ../../models/gwss.rst:71
+#: ../../models/gwss.rst:108
+msgid "Correlation Mode"
+msgstr "相关性模式"
+
+#: ../../models/gwss.rst:73
+msgid "For each pair of variables :math:`(X_i, X_j)`, the following are computed:"
+msgstr "对于每对变量 :math:`(X_i, X_j)`,计算以下内容:"
+
+#: ../../models/gwss.rst:75
+msgid "Local Pearson correlation coefficient — via locally weighted covariance"
+msgstr "局部皮尔逊相关系数 — 通过局部加权协方差"
+
+#: ../../models/gwss.rst:76
+msgid "Local Spearman rank correlation coefficient — via locally weighted correlation on ranked data"
+msgstr "局部斯皮尔曼秩相关系数 — 通过对排序数据计算局部加权相关性"
+
+#: ../../models/gwss.rst:79
+msgid "Column name format: ``{var1}.{var2}_Corr`` and ``{var1}.{var2}_SCorr``."
+msgstr "列名格式:``{var1}.{var2}_Corr`` 和 ``{var1}.{var2}_SCorr``。"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/modules.po b/doc/locale/zh_CN/LC_MESSAGES/modules.po
new file mode 100644
index 0000000..6776d23
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/modules.po
@@ -0,0 +1,21 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 17:38+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../modules.rst:2
+msgid "pygwmodel"
+msgstr "pygwmodel"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/performance.po b/doc/locale/zh_CN/LC_MESSAGES/performance.po
new file mode 100644
index 0000000..518b4bd
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/performance.po
@@ -0,0 +1,210 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 23:29+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../performance.rst:2
+msgid "High-Performance Computing"
+msgstr "高性能计算"
+
+#: ../../performance.rst:7
+msgid "Supported Parallel Algorithms"
+msgstr "支持的高性能算法"
+
+#: ../../performance.rst:13
+msgid "Algorithm"
+msgstr "算法"
+
+#: ../../performance.rst:14
+msgid "Serial"
+msgstr "串行"
+
+#: ../../performance.rst:15
+msgid "OpenMP"
+msgstr "OpenMP"
+
+#: ../../performance.rst:16
+msgid "CUDA"
+msgstr "CUDA"
+
+#: ../../performance.rst:17
+msgid "MPI"
+msgstr "MPI"
+
+#: ../../performance.rst:18
+msgid "GWRBasic"
+msgstr "GWRBasic"
+
+#: ../../performance.rst:19
+#: ../../performance.rst:20
+#: ../../performance.rst:21
+#: ../../performance.rst:22
+#: ../../performance.rst:24
+#: ../../performance.rst:25
+#: ../../performance.rst:26
+#: ../../performance.rst:29
+#: ../../performance.rst:30
+#: ../../performance.rst:34
+#: ../../performance.rst:35
+msgid "✅"
+msgstr "✅"
+
+#: ../../performance.rst:23
+msgid "GWRMultiscale"
+msgstr "GWRMultiscale"
+
+#: ../../performance.rst:27
+msgid "Serial × :math:`n_{var}`"
+msgstr "串行 × :math:`n_{var}`"
+
+#: ../../performance.rst:28
+msgid "GWSS (Average)"
+msgstr "GWSS(均值)"
+
+#: ../../performance.rst:31
+#: ../../performance.rst:32
+#: ../../performance.rst:36
+#: ../../performance.rst:37
+msgid "—"
+msgstr "—"
+
+#: ../../performance.rst:33
+msgid "GWSS (Correlation)"
+msgstr "GWSS(相关性)"
+
+#: ../../performance.rst:39
+msgid "Notes:"
+msgstr "注意事项:"
+
+#: ../../performance.rst:41
+msgid "**Serial** (SerialOnly): single-threaded; suitable for small datasets or debugging."
+msgstr "**串行** (SerialOnly):单线程;适用于小数据集或调试。"
+
+#: ../../performance.rst:42
+msgid "**OpenMP**\ : shared-memory multi-threading; suitable for single-node multi-core machines. Requires ``ENABLE_OPENMP`` at build time."
+msgstr "**OpenMP**\ :共享内存多线程并行;适用于单节点多核机器。需要在构建时启用 ``ENABLE_OPENMP``。"
+
+#: ../../performance.rst:44
+msgid "**CUDA**\ : NVIDIA GPU acceleration; suitable for large datasets. Requires ``ENABLE_CUDA`` at build time."
+msgstr "**CUDA**\ :NVIDIA GPU 加速;适用于大数据集。需要在构建时启用 ``ENABLE_CUDA``。"
+
+#: ../../performance.rst:46
+msgid "**MPI**\ : distributed computing; suitable for multi-node clusters. Requires ``ENABLE_MPI`` at build time."
+msgstr "**MPI**\ :分布式计算;适用于多节点集群。需要在构建时启用 ``ENABLE_MPI``。"
+
+#: ../../performance.rst:52
+msgid "Multi-Threading (OpenMP)"
+msgstr "多线程并行 (OpenMP)"
+
+#: ../../performance.rst:54
+msgid "Enables shared-memory parallel computation via OpenMP. The core per-sample model fitting loop is parallelised, with each thread independently computing coefficient estimates for a subset of samples."
+msgstr "通过 OpenMP 启用共享内存并行计算。核心的逐样本模型拟合循环被并行化,每个线程独立计算一部分样本的回归系数估计值。"
+
+#: ../../performance.rst:58
+msgid "Setting the number of threads:"
+msgstr "设置线程数:"
+
+#: ../../performance.rst:70
+msgid "GWRMultiscale also supports OpenMP:"
+msgstr "GWRMultiscale 也支持 OpenMP:"
+
+#: ../../performance.rst:80
+msgid "Recommended thread count: set to the number of physical CPU cores."
+msgstr "推荐线程数:设置为物理 CPU 核心数。"
+
+#: ../../performance.rst:85
+msgid "GPU Acceleration (CUDA)"
+msgstr "GPU 加速 (CUDA)"
+
+#: ../../performance.rst:87
+msgid "Offloads the locally weighted regression matrix operations to a NVIDIA GPU, suitable for larger datasets."
+msgstr "将局部加权回归矩阵运算卸载到 NVIDIA GPU,适用于较大的数据集。"
+
+#: ../../performance.rst:91
+msgid "Group Size (``group_size``)"
+msgstr "组大小 (``group_size``)"
+
+#: ../../performance.rst:93
+msgid "``group_size`` controls how many samples' coefficient estimates are computed together on the GPU in one batch. Larger groups make better use of GPU parallelism but are constrained by GPU memory. The internal constraint is:"
+msgstr "``group_size`` 控制每次在 GPU 上批量计算多少个样本的系数估计值。较大的组能更好地利用 GPU 并行性,但受 GPU 内存限制。内部约束为:"
+
+#: ../../performance.rst:97
+msgid "k \\times n \\times g \\times 8 < \\text{GPU Memory}"
+msgstr "k \\times n \\times g \\times 8 < \\text{GPU 显存}"
+
+#: ../../performance.rst:101
+msgid "where :math:`k` is the number of independent variables, :math:`n` is the number of samples, and :math:`g` is the group size."
+msgstr "其中 :math:`k` 为自变量个数,:math:`n` 为样本数,:math:`g` 为组大小。"
+
+#: ../../performance.rst:104
+msgid "Usage:"
+msgstr "用法:"
+
+#: ../../performance.rst:114
+msgid "For GWRMultiscale:"
+msgstr "对于 GWRMultiscale:"
+
+#: ../../performance.rst:125
+msgid "Performance Tips"
+msgstr "性能建议"
+
+#: ../../performance.rst:131
+msgid "Scenario"
+msgstr "场景"
+
+#: ../../performance.rst:132
+msgid "Recommendation"
+msgstr "建议"
+
+#: ../../performance.rst:133
+msgid "Small datasets (:math:`n < 1000`)"
+msgstr "小数据集 (:math:`n < 1000`)"
+
+#: ../../performance.rst:134
+msgid "Serial is sufficient; overhead is negligible"
+msgstr "串行即可;开销可忽略不计"
+
+#: ../../performance.rst:135
+msgid "Medium datasets (:math:`n < 10^4`)"
+msgstr "中等数据集 (:math:`n < 10^4`)"
+
+#: ../../performance.rst:136
+msgid "Use OpenMP with thread count equal to CPU cores"
+msgstr "使用 OpenMP,线程数等于 CPU 核心数"
+
+#: ../../performance.rst:137
+msgid "Large datasets (:math:`n > 10^4`)"
+msgstr "大数据集 (:math:`n > 10^4`)"
+
+#: ../../performance.rst:138
+msgid "Use CUDA if available; otherwise OpenMP"
+msgstr "如有 CUDA 则使用 CUDA;否则使用 OpenMP"
+
+#: ../../performance.rst:139
+msgid "Speeding up GWRMultiscale"
+msgstr "加速 GWRMultiscale"
+
+#: ../../performance.rst:140
+msgid "Set ``has_hat_matrix=False`` to skip hat matrix computation, significantly reducing memory and computational cost"
+msgstr "设置 ``has_hat_matrix=False`` 跳过帽子矩阵计算,显著减少内存和计算开销"
+
+#: ../../performance.rst:142
+msgid "GWRMultiscale convergence"
+msgstr "GWRMultiscale 收敛"
+
+#: ../../performance.rst:143
+msgid "Increase ``criterion_threshold`` to reduce iterations (trades slight accuracy)"
+msgstr "增大 ``criterion_threshold`` 以减少迭代次数(以微小精度损失为代价)"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po b/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po
new file mode 100644
index 0000000..e5f42ae
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po
@@ -0,0 +1,65 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 17:38+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../pygwmodel.rst:2
+msgid "pygwmodel package"
+msgstr "pygwmodel 软件包"
+
+#: ../../pygwmodel.rst:5
+msgid "Submodules"
+msgstr "子模块"
+
+#: ../../pygwmodel.rst:8
+msgid "pygwmodel.gwr\\_basic module"
+msgstr "pygwmodel.gwr\\_basic 模块"
+
+#: ../../../src/pygwmodel/gwr_basic.py:docstring of pygwmodel.gwr_basic.GWRBasic:1
+msgid "Basic GWR python high api class."
+msgstr "基础 GWR Python 高层 API 类。"
+
+#: ../../../src/pygwmodel/gwr_basic.py:docstring of pygwmodel.gwr_basic.GWRBasic.fit:1
+msgid "Run algorithm and return result"
+msgstr "运行算法并返回结果"
+
+#: ../../../src/pygwmodel/gwr_basic.py:docstring of pygwmodel.gwr_basic.GWRBasic.predict:1
+msgid "Predict"
+msgstr "预测"
+
+#: ../../pygwmodel.rst:16
+msgid "pygwmodel.gwr_multiscale module"
+msgstr "pygwmodel.gwr_multiscale 模块"
+
+#: ../../../src/pygwmodel/gwr_multiscale.py:docstring of pygwmodel.gwr_multiscale.GWRMultiscale:1
+msgid "Multiscale GWR python high api class."
+msgstr "多尺度 GWR Python 高层 API 类。"
+
+#: ../../../src/pygwmodel/gwr_multiscale.py:docstring of pygwmodel.gwr_multiscale.GWRMultiscale.fit:1
+msgid "Run the multiscale GWR algorithm and return self."
+msgstr "运行多尺度 GWR 算法并返回自身。"
+
+#: ../../pygwmodel.rst:32
+msgid "pygwmodel.parallel module"
+msgstr "pygwmodel.parallel 模块"
+
+#: ../../pygwmodel.rst:40
+msgid "pygwmodel.spatial\\_weight module"
+msgstr "pygwmodel.spatial\\_weight 模块"
+
+#: ../../pygwmodel.rst:48
+msgid "Module contents"
+msgstr "模块内容"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/quickstart.po b/doc/locale/zh_CN/LC_MESSAGES/quickstart.po
new file mode 100644
index 0000000..1ab9c13
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/quickstart.po
@@ -0,0 +1,97 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 23:29+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../quickstart.rst:2
+msgid "Quick Start"
+msgstr "快速入门"
+
+#: ../../quickstart.rst:7
+msgid "Overview"
+msgstr "概览"
+
+#: ../../quickstart.rst:9
+msgid "**pygwmodel** is a Python binding for `libgwmodel `_ that provides clear, high-performance interfaces for geographically weighted (GW) models based on **GeoPandas**\ ."
+msgstr "**pygwmodel** 是 `libgwmodel `_ 的 Python 绑定,为基于 **GeoPandas** 的地理加权(GW)模型提供清晰、高性能的接口。"
+
+#: ../../quickstart.rst:14
+msgid "Currently implemented models:"
+msgstr "当前已实现的模型:"
+
+#: ../../quickstart.rst:16
+msgid "**Geographically Weighted Regression (GWR)** — :class:`~pygwmodel.gwr_basic.GWRBasic`"
+msgstr "**地理加权回归 (GWR)** — :class:`~pygwmodel.gwr_basic.GWRBasic`"
+
+#: ../../quickstart.rst:17
+msgid "**Multiscale GWR (MGWR)** — :class:`~pygwmodel.gwr_multiscale.GWRMultiscale`"
+msgstr "**多尺度 GWR (MGWR)** — :class:`~pygwmodel.gwr_multiscale.GWRMultiscale`"
+
+#: ../../quickstart.rst:18
+msgid "**Geographically Weighted Summary Statistics (GWSS)** — :class:`~pygwmodel.gwss.GWSS`"
+msgstr "**地理加权汇总统计 (GWSS)** — :class:`~pygwmodel.gwss.GWSS`"
+
+#: ../../quickstart.rst:20
+msgid "All algorithms use a C++17 core, exposed to Python via `nanobind `_, with support for OpenMP multi-threading and CUDA GPU acceleration."
+msgstr "所有算法均使用 C++17 核心,通过 `nanobind `_ 暴露给 Python,并支持 OpenMP 多线程并行和 CUDA GPU 加速。"
+
+#: ../../quickstart.rst:27
+msgid "Installation"
+msgstr "安装"
+
+#: ../../quickstart.rst:30
+msgid "System Dependencies"
+msgstr "系统依赖"
+
+#: ../../quickstart.rst:32
+msgid "Install the required native libraries:"
+msgstr "安装所需的原生库:"
+
+#: ../../quickstart.rst:43
+msgid "Python Installation"
+msgstr "Python 安装"
+
+#: ../../quickstart.rst:52
+msgid "On Windows, you **must** use OpenBLAS to avoid segmentation faults:"
+msgstr "在 Windows 上,您**必须**\ 使用 OpenBLAS 以避免段错误:"
+
+#: ../../quickstart.rst:59
+msgid "Or pass the setting directly to pip:"
+msgstr "或直接将设置传递给 pip:"
+
+#: ../../quickstart.rst:68
+msgid "Development Guide"
+msgstr "开发指南"
+
+#: ../../quickstart.rst:71
+msgid "Editable Install"
+msgstr "可编辑安装"
+
+#: ../../quickstart.rst:73
+msgid "An editable install lets you edit Python code without reinstalling:"
+msgstr "可编辑安装允许您修改 Python 代码而无需重新安装:"
+
+#: ../../quickstart.rst:81
+msgid "Running Tests"
+msgstr "运行测试"
+
+#: ../../quickstart.rst:96
+msgid "Building Documentation"
+msgstr "构建文档"
+
+#: ../../quickstart.rst:105
+msgid "Project Structure"
+msgstr "项目结构"
diff --git a/doc/locale/zh_CN/LC_MESSAGES/spatial_weight.po b/doc/locale/zh_CN/LC_MESSAGES/spatial_weight.po
new file mode 100644
index 0000000..0ac18ff
--- /dev/null
+++ b/doc/locale/zh_CN/LC_MESSAGES/spatial_weight.po
@@ -0,0 +1,263 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, GWmodel-Lab
+# This file is distributed under the same license as the pygwmodel package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pygwmodel \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-05-19 23:29+0800\n"
+"PO-Revision-Date: 2026-05-19 18:20+0800\n"
+"Last-Translator: pygwmodel team \n"
+"Language-Team: Chinese (Simplified) \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../spatial_weight.rst:2
+msgid "Spatial Weights"
+msgstr "空间权重"
+
+#: ../../spatial_weight.rst:4
+msgid "Spatial weights are the core concept of GW models. For each target point :math:`i`, the distance to every other data point :math:`j` is computed via a distance metric, and a kernel function converts the distance into a weight :math:`w_{ij}`. Larger distance means smaller weight."
+msgstr "空间权重是 GW 模型的核心概念。对于每个目标点 :math:`i`,通过距离度量计算其到每个其他数据点 :math:`j` 的距离,再通过核函数将距离转换为权重 :math:`w_{ij}`。距离越大,权重越小。"
+
+#: ../../spatial_weight.rst:12
+msgid "Distance Metrics"
+msgstr "距离度量"
+
+#: ../../spatial_weight.rst:14
+msgid "pygwmodel supports the following distance metrics:"
+msgstr "pygwmodel 支持以下距离度量:"
+
+#: ../../spatial_weight.rst:20
+msgid "Distance Type"
+msgstr "距离类型"
+
+#: ../../spatial_weight.rst:21
+msgid "Description"
+msgstr "描述"
+
+#: ../../spatial_weight.rst:22
+msgid "Python Class"
+msgstr "Python 类"
+
+#: ../../spatial_weight.rst:23
+#: ../../spatial_weight.rst:44
+msgid "CRS Distance"
+msgstr "坐标系距离"
+
+#: ../../spatial_weight.rst:24
+msgid "Automatic selection based on coordinate reference system: Euclidean for projected coordinates, great-circle for geographic coordinates."
+msgstr "基于坐标参考系自动选择:投影坐标系使用欧几里得距离,地理坐标系使用大圆距离。"
+
+#: ../../spatial_weight.rst:26
+msgid ":class:`~pygwmodel.spatial_weight.CRSDistance`"
+msgstr ":class:`~pygwmodel.spatial_weight.CRSDistance`"
+
+#: ../../spatial_weight.rst:27
+msgid "Minkowski Distance"
+msgstr "闵可夫斯基距离"
+
+#: ../../spatial_weight.rst:28
+msgid "Generalised distance parameterised by :math:`p`: :math:`p=1` is Manhattan, :math:`p=2` is Euclidean, :math:`p \\to \\infty` is Chebyshev."
+msgstr "由 :math:`p` 参数化的广义距离::math:`p=1` 为曼哈顿距离,:math:`p=2` 为欧几里得距离,:math:`p \\to \\infty` 为切比雪夫距离。"
+
+#: ../../spatial_weight.rst:30
+#: ../../spatial_weight.rst:34
+#: ../../spatial_weight.rst:37
+#: ../../spatial_weight.rst:41
+msgid "(C++ implemented; Python binding pending)"
+msgstr "(C++ 已实现;Python 绑定待完成)"
+
+#: ../../spatial_weight.rst:31
+msgid "One-Dimensional Distance"
+msgstr "一维距离"
+
+#: ../../spatial_weight.rst:32
+msgid "Absolute difference along a single spatial or temporal dimension, used as the temporal component in spatiotemporal models."
+msgstr "沿单一空间或时间维度的绝对差,在时空模型中作为时间分量使用。"
+
+#: ../../spatial_weight.rst:35
+msgid "Distance Matrix File"
+msgstr "距离矩阵文件"
+
+#: ../../spatial_weight.rst:36
+msgid "Reads distances from a precomputed ``.dmat`` binary distance matrix file."
+msgstr "从预先计算的 ``.dmat`` 二进制距离矩阵文件中读取距离。"
+
+#: ../../spatial_weight.rst:38
+msgid "Spatiotemporal Distance"
+msgstr "时空距离"
+
+#: ../../spatial_weight.rst:39
+msgid "Weighted combination of spatial and temporal distances, supporting orthogonal and oblique modes."
+msgstr "空间距离与时间距离的加权组合,支持正交和斜交模式。"
+
+#: ../../spatial_weight.rst:46
+msgid "The coordinate-reference-system distance is the most commonly used metric, automatically selecting the calculation method based on the CRS."
+msgstr "坐标参考系距离是最常用的度量,会根据 CRS 自动选择计算方法。"
+
+#: ../../spatial_weight.rst:49
+msgid "**Projected coordinates** (``is_geographic=False``) — Euclidean distance:"
+msgstr "**投影坐标系** (``is_geographic=False``) — 欧几里得距离:"
+
+#: ../../spatial_weight.rst:51
+msgid "d_{ij} = \\sqrt{(u_i - u_j)^2 + (v_i - v_j)^2}"
+msgstr "d_{ij} = \\sqrt{(u_i - u_j)^2 + (v_i - v_j)^2}"
+
+#: ../../spatial_weight.rst:55
+msgid "**Geographic coordinates** (``is_geographic=True``) — great-circle distance (geodesic distance)."
+msgstr "**地理坐标系** (``is_geographic=True``) — 大圆距离(测地线距离)。"
+
+#: ../../spatial_weight.rst:58
+#: ../../spatial_weight.rst:109
+#: ../../spatial_weight.rst:142
+msgid "Usage:"
+msgstr "用法:"
+
+#: ../../spatial_weight.rst:73
+msgid "Kernel Functions and Weights"
+msgstr "核函数与权重"
+
+#: ../../spatial_weight.rst:75
+msgid "Kernel functions convert distances :math:`d_{ij}` into weights :math:`w_{ij}`. pygwmodel supports five kernel functions, configured via :class:`~pygwmodel.spatial_weight.BandwidthWeight`."
+msgstr "核函数将距离 :math:`d_{ij}` 转换为权重 :math:`w_{ij}`。pygwmodel 支持五种核函数,通过 :class:`~pygwmodel.spatial_weight.BandwidthWeight` 配置。"
+
+#: ../../spatial_weight.rst:83
+msgid "Kernel"
+msgstr "核函数"
+
+#: ../../spatial_weight.rst:84
+msgid "Formula"
+msgstr "公式"
+
+#: ../../spatial_weight.rst:85
+msgid "Enum Value"
+msgstr "枚举值"
+
+#: ../../spatial_weight.rst:86
+msgid "Gaussian"
+msgstr "高斯"
+
+#: ../../spatial_weight.rst:87
+msgid ":math:`w_{ij} = \\exp\\left(-\\dfrac{d_{ij}^2}{2b^2}\\right)`"
+msgstr ":math:`w_{ij} = \\exp\\left(-\\dfrac{d_{ij}^2}{2b^2}\\right)`"
+
+#: ../../spatial_weight.rst:88
+msgid "``BandwidthWeight.Kernel.Gaussian``"
+msgstr "``BandwidthWeight.Kernel.Gaussian``"
+
+#: ../../spatial_weight.rst:89
+msgid "Exponential"
+msgstr "指数"
+
+#: ../../spatial_weight.rst:90
+msgid ":math:`w_{ij} = \\exp\\left(-\\dfrac{|d_{ij}|}{b}\\right)`"
+msgstr ":math:`w_{ij} = \\exp\\left(-\\dfrac{|d_{ij}|}{b}\\right)`"
+
+#: ../../spatial_weight.rst:91
+msgid "``BandwidthWeight.Kernel.Exponential``"
+msgstr "``BandwidthWeight.Kernel.Exponential``"
+
+#: ../../spatial_weight.rst:92
+msgid "Bisquare"
+msgstr "双平方"
+
+#: ../../spatial_weight.rst:93
+msgid ":math:`w_{ij} = \\begin{cases} \\left(1 - \\left(\\frac{d_{ij}}{b}\\right)^2\\right)^2, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+msgstr ":math:`w_{ij} = \\begin{cases} \\left(1 - \\left(\\frac{d_{ij}}{b}\\right)^2\\right)^2, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+
+#: ../../spatial_weight.rst:94
+msgid "``BandwidthWeight.Kernel.Bisquare``"
+msgstr "``BandwidthWeight.Kernel.Bisquare``"
+
+#: ../../spatial_weight.rst:95
+msgid "Tricube"
+msgstr "三次方"
+
+#: ../../spatial_weight.rst:96
+msgid ":math:`w_{ij} = \\begin{cases} \\left(1 - \\left(\\frac{d_{ij}}{b}\\right)^3\\right)^3, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+msgstr ":math:`w_{ij} = \\begin{cases} \\left(1 - \\left(\\frac{d_{ij}}{b}\\right)^3\\right)^3, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+
+#: ../../spatial_weight.rst:97
+msgid "``BandwidthWeight.Kernel.Tricube``"
+msgstr "``BandwidthWeight.Kernel.Tricube``"
+
+#: ../../spatial_weight.rst:98
+msgid "Boxcar"
+msgstr "矩形"
+
+#: ../../spatial_weight.rst:99
+msgid ":math:`w_{ij} = \\begin{cases} 1, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+msgstr ":math:`w_{ij} = \\begin{cases} 1, & d_{ij} < b \\\\ 0, & \\text{otherwise} \\end{cases}`"
+
+#: ../../spatial_weight.rst:100
+msgid "``BandwidthWeight.Kernel.Boxcar``"
+msgstr "``BandwidthWeight.Kernel.Boxcar``"
+
+#: ../../spatial_weight.rst:102
+msgid "Here :math:`b` is the bandwidth and :math:`d_{ij}` is the distance from sample :math:`i` to sample :math:`j`."
+msgstr "其中 :math:`b` 为带宽,:math:`d_{ij}` 为样本 :math:`i` 到样本 :math:`j` 的距离。"
+
+#: ../../spatial_weight.rst:105
+msgid "Gaussian and Exponential are continuous kernels — all samples receive non-zero weights. Bisquare, Tricube, and Boxcar are truncated kernels — samples beyond the bandwidth receive zero weight, which is useful for emphasising local effects."
+msgstr "高斯和指数是连续核函数——所有样本都获得非零权重。双平方、三次方和矩形是截断核函数——超出带宽范围的样本权重为零,这在强调局部效应时很有用。"
+
+#: ../../spatial_weight.rst:126
+msgid "Bandwidth"
+msgstr "带宽"
+
+#: ../../spatial_weight.rst:128
+msgid "The bandwidth :math:`b` controls the rate at which spatial weights decay and is the most important parameter in GW models."
+msgstr "带宽 :math:`b` 控制空间权重的衰减速率,是 GW 模型中最重要的参数。"
+
+#: ../../spatial_weight.rst:132
+msgid "Bandwidth Types"
+msgstr "带宽类型"
+
+#: ../../spatial_weight.rst:134
+msgid "**Fixed bandwidth**\ : The bandwidth value is a distance. Weights are computed directly as :math:`w = k(d; b)`."
+msgstr "**固定带宽**\ :带宽值是一个距离值。权重直接按 :math:`w = k(d; b)` 计算。"
+
+#: ../../spatial_weight.rst:136
+msgid "**Adaptive bandwidth**\ : The bandwidth value is a neighbour count. For focus point :math:`i`, the effective distance bandwidth is the distance to the :math:`b`-th nearest neighbour. Different locations can use different effective distance bandwidths, making this suitable for datasets with non-uniform sample density."
+msgstr "**可变带宽**\ :带宽值是一个邻居数。对于焦点 :math:`i`,有效距离带宽为到第 :math:`b` 个最近邻的距离。不同位置可以使用不同的有效距离带宽,适用于样本密度不均匀的数据集。"
+
+#: ../../spatial_weight.rst:153
+msgid "Bandwidth Selection"
+msgstr "带宽优选"
+
+#: ../../spatial_weight.rst:155
+msgid "If you are unsure about the right bandwidth value, the algorithm can select it automatically."
+msgstr "如果不确定合适的带宽值,算法可以自动选择。"
+
+#: ../../spatial_weight.rst:158
+msgid "**GWRBasic**\ :"
+msgstr "**GWRBasic**\ :"
+
+#: ../../spatial_weight.rst:170
+msgid "**GWRMultiscale** (each variable's bandwidth is optimised independently during backfitting):"
+msgstr "**GWRMultiscale**\ (各变量的带宽在后向迭代过程中独立优化):"
+
+#: ../../spatial_weight.rst:188
+msgid "Bandwidth selection criteria:"
+msgstr "带宽优选准则:"
+
+#: ../../spatial_weight.rst:190
+msgid "**CV** (Cross-Validation): Minimizes the cross-validation residual sum of squares."
+msgstr "**CV**\ (交叉验证):最小化交叉验证残差平方和。"
+
+#: ../../spatial_weight.rst:191
+msgid "**AIC** (Akaike Information Criterion): Minimizes the AIC value."
+msgstr "**AIC**\ (赤池信息准则):最小化 AIC 值。"
+
+#: ../../spatial_weight.rst:194
+msgid "Spatial Weight Configuration"
+msgstr "空间权重配置"
+
+#: ../../spatial_weight.rst:196
+msgid "A :class:`~pygwmodel.spatial_weight.SpatialWeight` combines a distance metric and a bandwidth weight, created via the factory method:"
+msgstr "一个 :class:`~pygwmodel.spatial_weight.SpatialWeight` 组合了距离度量和带宽权重,通过工厂方法创建:"
diff --git a/doc/models.rst b/doc/models.rst
new file mode 100644
index 0000000..20c4348
--- /dev/null
+++ b/doc/models.rst
@@ -0,0 +1,8 @@
+Regression Models
+=================
+
+.. toctree::
+ :maxdepth: 2
+
+ models/gwr
+ models/gwr_multiscale
diff --git a/doc/models/gwr.rst b/doc/models/gwr.rst
new file mode 100644
index 0000000..8c0d984
--- /dev/null
+++ b/doc/models/gwr.rst
@@ -0,0 +1,166 @@
+Basic Geographically Weighted Regression (GWR)
+================================================
+
+.. _gwr-math:
+
+Mathematical Foundation
+-----------------------
+
+For a dataset of :math:`n` samples and :math:`p` independent variables,
+the basic GWR model at sample :math:`i` is defined as:
+
+.. math::
+
+ y_i = \beta_{i0} + \sum_{k=1}^{p} \beta_{ik} x_{ik} + \varepsilon_i
+
+where:
+
+- :math:`y_i` is the dependent variable,
+- :math:`x_{ik}` is the :math:`k`-th independent variable,
+- :math:`\beta_{ik}` is the :math:`k`-th coefficient,
+- :math:`\beta_{i0}` is the intercept,
+- :math:`\varepsilon_i \sim \mathcal{N}(0, \sigma^2)` is the random error.
+
+The locally weighted least-squares estimator of the coefficients is:
+
+.. math::
+
+ \hat{\boldsymbol{\beta}}_i = \left( \mathbf{X}^\top \mathbf{W}_i \mathbf{X} \right)^{-1} \mathbf{X}^\top \mathbf{W}_i \mathbf{y}
+
+where :math:`\mathbf{W}_i = \operatorname{diag}(w_{i1}, w_{i2}, \dots, w_{in})`
+is the spatial weighting matrix. Each :math:`w_{ij}` is computed by a kernel
+function :math:`k(d_{ij}; b)` based on the distance from sample :math:`i` to
+sample :math:`j`.
+
+Diagnostic Information
+~~~~~~~~~~~~~~~~~~~~~~
+
+After fitting, the algorithm computes the following diagnostics:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 40 40
+
+ * - Metric
+ - Meaning
+ - Key
+ * - RSS
+ - Residual sum of squares :math:`\sum (y_i - \hat{y}_i)^2`
+ - ``diagnostic['RSS']``
+ * - AICc
+ - Corrected Akaike information criterion (smaller is better)
+ - ``diagnostic['AICc']``
+ * - ENP
+ - Effective number of parameters
+ - ``diagnostic['ENP']``
+ * - EDF
+ - Effective degrees of freedom
+ - ``diagnostic['EDF']``
+ * - R²
+ - Coefficient of determination
+ - ``diagnostic['RSquare']``
+ * - Adjusted R²
+ - Adjusted R-squared
+ - ``diagnostic['RSquareAdjust']``
+
+.. _gwr-params:
+
+Key Parameters
+--------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 50 25
+
+ * - Parameter
+ - Description
+ - Default
+ * - ``weight``
+ - A single bandwidth weight shared by all variables
+ - Required
+ * - ``distance``
+ - The distance metric to use
+ - ``CRSDistance()``
+ * - ``has_intercept``
+ - Whether to include an intercept term
+ - ``True``
+ * - ``fit(optimize_bw=...)``
+ - Auto-select bandwidth: ``CV`` or ``AIC``
+ - ``None``
+ * - ``fit(optimize_var=...)``
+ - Auto-select variables via forward selection: AIC change threshold
+ - ``None``
+
+.. _gwr-examples:
+
+Code Examples
+-------------
+
+Basic Usage
+~~~~~~~~~~~
+
+.. code-block:: python
+
+ from pygwmodel import GWRBasic, BandwidthWeight, CRSDistance
+
+ algorithm = GWRBasic(
+ data,
+ depen_var="PURCHASE",
+ indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"],
+ weight=BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()
+ ).fit()
+
+ # View diagnostic information
+ print(algorithm.diagnostic['RSquare']) # 0.708
+ print(algorithm.diagnostic['AICc']) # 2448.27
+
+ # Get the result layer (GeoDataFrame)
+ result = algorithm.result_layer
+ print(result.columns) # Intercept, FLOORSZ, ..., Intercept_SE, ..., fitted
+
+Bandwidth Selection
+~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ algorithm = GWRBasic(data, y, x, BandwidthWeight(adaptive=True),
+ distance=CRSDistance()).fit(
+ optimize_bw=GWRBasic.BandwidthSelectionCriterionType.CV
+ )
+ print(algorithm.weight.bandwidth) # Optimised bandwidth: 67
+
+Independent Variable Selection
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ algorithm = GWRBasic(data, y, x, BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()).fit(optimize_var=3.0)
+
+ # Variable combinations and their AICc values
+ for vars, aicc in algorithm.indep_var_select_criterions:
+ print(f"{vars}: {aicc:.2f}")
+
+ print(algorithm.indep_vars) # ['FLOORSZ', 'PROF'] — optimal subset
+
+Prediction
+~~~~~~~~~~
+
+.. code-block:: python
+
+ prediction = algorithm.predict(new_data)
+ print(prediction.columns) # Intercept, FLOORSZ, ..., y_hat, residual
+
+.. _gwr-refs:
+
+References
+----------
+
+* Brunsdon, C., Fotheringham, A. S., & Charlton, M. E. (1996).
+ *Geographically weighted regression: a method for exploring spatial nonstationarity*.
+ Geographical Analysis, 28(4), 281-298.
+
+* Fotheringham, A. S., Brunsdon, C., & Charlton, M. (2002).
+ *Geographically weighted regression: the analysis of spatially varying relationships*.
+ John Wiley & Sons.
diff --git a/doc/models/gwr_multiscale.rst b/doc/models/gwr_multiscale.rst
new file mode 100644
index 0000000..968ab57
--- /dev/null
+++ b/doc/models/gwr_multiscale.rst
@@ -0,0 +1,196 @@
+Multiscale Geographically Weighted Regression (GWRMultiscale)
+=============================================================
+
+.. _mgwr-math:
+
+Mathematical Foundation
+-----------------------
+
+Multiscale GWR (MGWR) extends basic GWR by allowing each independent variable
+to have its own bandwidth. Different spatial processes may operate at different
+spatial scales — the influence of some variables may be highly local (small
+bandwidth), while others may have regional or global scale (large bandwidth).
+
+The MGWR model form is the same as basic GWR:
+
+.. math::
+
+ y_i = \beta_{i0}(b_0) + \sum_{k=1}^{p} \beta_{ik}(b_k) x_{ik} + \varepsilon_i
+
+However, each coefficient :math:`\beta_{ik}` is estimated using its own bandwidth
+:math:`b_k`. Bandwidth calibration is carried out via the **backfitting**
+algorithm [#mgwr]_:
+
+1. **Initialisation**: Compute initial coefficient estimates
+ :math:`\boldsymbol{\beta}^{(0)}` using standard GWR with an initial spatial
+ weighting configuration.
+2. **Backfitting**: For each iteration :math:`t = 1, 2, \dots`:
+
+ a. For each variable :math:`k`:
+
+ - Fix the coefficients of all other variables and compute the residual of the dependent variable for the current variable.
+ - Select the optimal bandwidth :math:`b_k` for variable :math:`k` (golden section search with CV/AIC criterion).
+ - Fit new coefficients for variable :math:`k` using :math:`b_k`.
+ - Update the residuals.
+
+ b. Check the convergence criterion (CVR or dCVR); stop if satisfied.
+
+3. **Diagnostics**: Compute RSS, AICc, ENP, EDF, R², and other diagnostic metrics.
+
+Convergence Criteria
+~~~~~~~~~~~~~~~~~~~~
+
+- **CVR** (Change in RSS):
+
+ .. math::
+
+ \text{CVR} = |RSS_t - RSS_{t-1}|
+
+- **dCVR** (relative Change in RSS, default):
+
+ .. math::
+
+ \text{dCVR} = \sqrt{\frac{|RSS_t - RSS_{t-1}|}{RSS_t}}
+
+.. _mgwr-params:
+
+Key Parameters
+--------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 55 15
+
+ * - Parameter
+ - Description
+ - Default
+ * - ``weights``
+ - A list of bandwidth weights, one per variable (including intercept)
+ - Required
+ * - ``distance``
+ - The distance metric shared by all variables
+ - ``CRSDistance()``
+ * - ``has_intercept``
+ - Whether to include an intercept term
+ - ``True``
+ * - ``bandwidth_initilize``
+ - Bandwidth initialisation type per variable
+ - All ``Null`` (auto-select)
+ * - ``bandwidth_selection_approach``
+ - Bandwidth selection criterion per variable
+ - All ``CV``
+ * - ``preditor_centered``
+ - Whether to centre each predictor before fitting
+ - All ``False``
+ * - ``criterion_type``
+ - Backfitting convergence criterion type
+ - ``dCVR``
+ * - ``max_iteration``
+ - Maximum number of iterations
+ - ``500``
+ * - ``criterion_threshold``
+ - Convergence threshold
+ - ``1e-6``
+ * - ``has_hat_matrix``
+ - Whether to compute the hat matrix (for diagnostics)
+ - ``True``
+ * - ``adaptive_lower``
+ - Lower bound on neighbour count for adaptive bandwidth selection
+ - ``10``
+
+Bandwidth Initialisation Types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Type
+ - Meaning
+ * - ``BandwidthInitilizeType.Null``
+ - Not specified; auto-select via golden section search during backfitting
+ * - ``BandwidthInitilizeType.Specified``
+ - User-specified; skip bandwidth selection and use the fixed value from ``weights``
+ * - ``BandwidthInitilizeType.Initial``
+ - Initially optimised; may still be adjusted in later backfitting iterations
+
+.. _mgwr-examples:
+
+Code Examples
+-------------
+
+Basic Usage (Auto-Select Bandwidths)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from pygwmodel import GWRMultiscale, BandwidthWeight, CRSDistance
+
+ n_var = 4 # intercept + 3 independent variables
+ weights = [BandwidthWeight(36.0, adaptive=True) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ data,
+ depen_var="PURCHASE",
+ indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"],
+ weights=weights,
+ distance=CRSDistance()
+ ).fit()
+
+ # Diagnostic information
+ print(algorithm.diagnostic)
+
+ # Optimised bandwidths per variable
+ for w in algorithm.weights:
+ print(f"bandwidth={w.bandwidth}, adaptive={w.adaptive}")
+
+ # Result layer
+ result = algorithm.result_layer
+ print(result.columns)
+ # Intercept, FLOORSZ, UNEMPLOY, PROF,
+ # Intercept_SE, FLOORSZ_SE, ..., Intercept_TV, ..., fitted
+
+Specified Bandwidths
+~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from pygwmodel import GWRMultiscale
+
+ n_var = 4
+ spec = GWRMultiscale.BandwidthInitilizeType.Specified
+ cv = GWRMultiscale.BandwidthSelectionCriterionType.CV
+
+ algorithm = GWRMultiscale(
+ data, y, x,
+ weights=[BandwidthWeight(36.0, adaptive=True) for _ in range(n_var)],
+ bandwidth_initilize=[spec] * n_var,
+ bandwidth_selection_approach=[cv] * n_var
+ ).fit()
+
+Note: when ``bandwidth_initilize`` is set to ``Specified``, the bandwidths
+remain fixed; otherwise they are iteratively optimised during backfitting.
+
+Adjusting Convergence
+~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ algorithm = GWRMultiscale(data, y, x, weights)
+ algorithm.criterion_type = GWRMultiscale.BackFittingCriterionType.CVR
+ algorithm.criterion_threshold = 1e-4
+ algorithm.max_iteration = 100
+ algorithm.fit()
+
+.. _mgwr-refs:
+
+References
+----------
+
+.. [#mgwr] Fotheringham, A. S., Yang, W., & Kang, W. (2017).
+ *Multiscale geographically weighted regression (MGWR)*.
+ Annals of the American Association of Geographers, 107(6), 1247-1265.
+
+* Yu, H., Fotheringham, A. S., Li, Z., Oshan, T., Kang, W., & Wolf, L. J. (2020).
+ *Inference in multiscale geographically weighted regression*.
+ Geographical Analysis, 52(1), 87-106.
diff --git a/doc/models/gwss.rst b/doc/models/gwss.rst
new file mode 100644
index 0000000..4870d7e
--- /dev/null
+++ b/doc/models/gwss.rst
@@ -0,0 +1,117 @@
+Geographically Weighted Summary Statistics (GWSS)
+=================================================
+
+.. _gwss-overview:
+
+Model Overview
+--------------
+
+Geographically Weighted Summary Statistics (GWSS) performs locally weighted
+descriptive statistics on multivariate data, revealing spatial heterogeneity
+in the statistical characteristics of variables.
+
+GWSS supports two modes:
+
+- Average mode — computes local mean, standard deviation, variance, skewness,
+ coefficient of variation, and optionally local median, interquartile range,
+ and quantile imbalance.
+- Correlation mode — computes local Pearson correlation coefficients and
+ Spearman rank correlation coefficients.
+
+.. _gwss-modes:
+
+Two Modes
+---------
+
+Average Mode
+~~~~~~~~~~~~
+
+For each variable, the following local statistics are computed:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 35 40
+
+ * - Statistic
+ - Attribute
+ - Column Name
+ * - Local Mean
+ - ``local_mean``
+ - ``{variable}_Mean``
+ * - Local Std Dev
+ - ``local_sdev``
+ - ``{variable}_SDev``
+ * - Local Skewness
+ - ``local_skewness``
+ - ``{variable}_Skew``
+ * - Local CV
+ - ``local_cv``
+ - ``{variable}_CV``
+
+When ``quantile=True``:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 35 40
+
+ * - Statistic
+ - Attribute
+ - Column Name
+ * - Local Median
+ - ``local_median``
+ - ``{variable}_Median``
+ * - IQR
+ - ``iqr``
+ - ``{variable}_IQR``
+ * - Quantile Imbalance
+ - ``qi``
+ - ``{variable}_QI``
+
+Correlation Mode
+~~~~~~~~~~~~~~~~
+
+For each pair of variables :math:`(X_i, X_j)`, the following are computed:
+
+- Local Pearson correlation coefficient — via locally weighted covariance
+- Local Spearman rank correlation coefficient — via locally weighted
+ correlation on ranked data
+
+Column name format: ``{var1}.{var2}_Corr`` and ``{var1}.{var2}_SCorr``.
+
+.. _gwss-examples:
+
+Code Examples
+-------------
+
+Average Mode
+~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from pygwmodel import GWSS, BandwidthWeight
+
+ vars = ["PURCHASE", "FLOORSZ", "UNEMPLOY", "PROF"]
+
+ gwss = GWSS(
+ data, vars,
+ weight=BandwidthWeight(36.0, adaptive=True),
+ mode=GWSS.Mode.Average,
+ quantile=False
+ ).run()
+
+ result = gwss.result_layer
+ print(result.columns)
+ # PURCHASE_Mean, PURCHASE_SDev, PURCHASE_Skew, PURCHASE_CV,
+ # FLOORSZ_Mean, ...
+
+Correlation Mode
+~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ gwss = GWSS(data, vars, weight=BandwidthWeight(36.0, adaptive=True))
+ result = gwss.run(mode=GWSS.Mode.Correlation).result_layer
+
+ print(result.columns)
+ # PURCHASE.FLOORSZ_Corr, PURCHASE.FLOORSZ_SCorr,
+ # PURCHASE.UNEMPLOY_Corr, ...
diff --git a/doc/performance.rst b/doc/performance.rst
new file mode 100644
index 0000000..fe09822
--- /dev/null
+++ b/doc/performance.rst
@@ -0,0 +1,143 @@
+High-Performance Computing
+===========================
+
+.. _perf-overview:
+
+Supported Parallel Algorithms
+-----------------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 20 20 20 15
+
+ * - Algorithm
+ - Serial
+ - OpenMP
+ - CUDA
+ - MPI
+ * - GWRBasic
+ - ✅
+ - ✅
+ - ✅
+ - ✅
+ * - GWRMultiscale
+ - ✅
+ - ✅
+ - ✅
+ - Serial × :math:`n_{var}`
+ * - GWSS (Average)
+ - ✅
+ - ✅
+ - —
+ - —
+ * - GWSS (Correlation)
+ - ✅
+ - ✅
+ - —
+ - —
+
+Notes:
+
+- **Serial** (SerialOnly): single-threaded; suitable for small datasets or debugging.
+- **OpenMP**: shared-memory multi-threading; suitable for single-node multi-core
+ machines. Requires ``ENABLE_OPENMP`` at build time.
+- **CUDA**: NVIDIA GPU acceleration; suitable for large datasets. Requires
+ ``ENABLE_CUDA`` at build time.
+- **MPI**: distributed computing; suitable for multi-node clusters. Requires
+ ``ENABLE_MPI`` at build time.
+
+.. _perf-openmp:
+
+Multi-Threading (OpenMP)
+------------------------
+
+Enables shared-memory parallel computation via OpenMP. The core per-sample model
+fitting loop is parallelised, with each thread independently computing coefficient
+estimates for a subset of samples.
+
+Setting the number of threads:
+
+.. code-block:: python
+
+ from pygwmodel import GWRBasic, ParallelType, BandwidthWeight, CRSDistance
+
+ algorithm = GWRBasic(data, y, x,
+ weight=BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()).enable_parallel(
+ ParallelType.OpenMP, threads=4
+ ).fit()
+
+GWRMultiscale also supports OpenMP:
+
+.. code-block:: python
+
+ from pygwmodel import GWRMultiscale
+
+ algorithm = GWRMultiscale(data, y, x, weights).enable_parallel(
+ ParallelType.OpenMP, threads=8
+ ).fit()
+
+Recommended thread count: set to the number of physical CPU cores.
+
+.. _perf-cuda:
+
+GPU Acceleration (CUDA)
+-----------------------
+
+Offloads the locally weighted regression matrix operations to a NVIDIA GPU,
+suitable for larger datasets.
+
+Group Size (``group_size``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``group_size`` controls how many samples' coefficient estimates are computed
+together on the GPU in one batch. Larger groups make better use of GPU
+parallelism but are constrained by GPU memory. The internal constraint is:
+
+.. math::
+
+ k \times n \times g \times 8 < \text{GPU Memory}
+
+where :math:`k` is the number of independent variables, :math:`n` is the number
+of samples, and :math:`g` is the group size.
+
+Usage:
+
+.. code-block:: python
+
+ algorithm = GWRBasic(data, y, x,
+ weight=BandwidthWeight(36.0, adaptive=True),
+ distance=CRSDistance()).enable_parallel(
+ ParallelType.CUDA, gpu_id=0, group_size=64
+ ).fit()
+
+For GWRMultiscale:
+
+.. code-block:: python
+
+ algorithm = GWRMultiscale(data, y, x, weights).enable_parallel(
+ ParallelType.CUDA, gpu_id=0, group_size=128
+ ).fit()
+
+.. _perf-tips:
+
+Performance Tips
+----------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Scenario
+ - Recommendation
+ * - Small datasets (:math:`n < 1000`)
+ - Serial is sufficient; overhead is negligible
+ * - Medium datasets (:math:`n < 10^4`)
+ - Use OpenMP with thread count equal to CPU cores
+ * - Large datasets (:math:`n > 10^4`)
+ - Use CUDA if available; otherwise OpenMP
+ * - Speeding up GWRMultiscale
+ - Set ``has_hat_matrix=False`` to skip hat matrix computation, significantly
+ reducing memory and computational cost
+ * - GWRMultiscale convergence
+ - Increase ``criterion_threshold`` to reduce iterations (trades slight accuracy)
diff --git a/doc/pygwmodel.rst b/doc/pygwmodel.rst
index b227b87..c5bd1bb 100644
--- a/doc/pygwmodel.rst
+++ b/doc/pygwmodel.rst
@@ -12,14 +12,22 @@ pygwmodel.gwr\_basic module
:undoc-members:
:show-inheritance:
-pygwmodel.gwss module
----------------------
+pygwmodel.gwr_multiscale module
+---------------------------------
-.. automodule:: pygwmodel.gwss
+.. automodule:: pygwmodel.gwr_multiscale
:members:
:undoc-members:
:show-inheritance:
+.. pygwmodel.gwss module (disabled — _analysis module needs _GWSS binding update)
+.. ---------------------
+
+.. .. automodule:: pygwmodel.gwss
+.. :members:
+.. :undoc-members:
+.. :show-inheritance:
+
pygwmodel.parallel module
-------------------------
diff --git a/doc/quickstart.rst b/doc/quickstart.rst
new file mode 100644
index 0000000..b776444
--- /dev/null
+++ b/doc/quickstart.rst
@@ -0,0 +1,116 @@
+Quick Start
+===========
+
+.. _quickstart-overview:
+
+Overview
+--------
+
+**pygwmodel** is a Python binding for
+`libgwmodel `_ that provides
+clear, high-performance interfaces for geographically weighted (GW) models
+based on **GeoPandas**.
+
+Currently implemented models:
+
+- **Geographically Weighted Regression (GWR)** — :class:`~pygwmodel.gwr_basic.GWRBasic`
+- **Multiscale GWR (MGWR)** — :class:`~pygwmodel.gwr_multiscale.GWRMultiscale`
+- **Geographically Weighted Summary Statistics (GWSS)** — :class:`~pygwmodel.gwss.GWSS`
+
+All algorithms use a C++17 core, exposed to Python via
+`nanobind `_, with support for
+OpenMP multi-threading and CUDA GPU acceleration.
+
+.. _quickstart-install:
+
+Installation
+------------
+
+System Dependencies
+~~~~~~~~~~~~~~~~~~~
+
+Install the required native libraries:
+
+.. code-block:: bash
+
+ # Ubuntu/Debian
+ sudo apt install libarmadillo-dev libgsl-dev libopenblas-dev
+
+ # or via conda/mamba
+ conda install armadillo gsl openblas -c conda-forge
+
+Python Installation
+~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ git clone https://github.com/GWmodel-Lab/pygwmodel.git
+ cd pygwmodel
+ git submodule update --init --recursive
+ pip install .
+
+On Windows, you **must** use OpenBLAS to avoid segmentation faults:
+
+.. code-block:: powershell
+
+ $Env:CMAKE_ARGS="-DBLA_VENDOR=OpenBLAS"
+ pip install .
+
+Or pass the setting directly to pip:
+
+.. code-block:: powershell
+
+ pip install . --config-settings=cmake.args=-DBLA_VENDOR=OpenBLAS
+
+.. _quickstart-dev:
+
+Development Guide
+-----------------
+
+Editable Install
+~~~~~~~~~~~~~~~~
+
+An editable install lets you edit Python code without reinstalling:
+
+.. code-block:: bash
+
+ pip install nanobind scikit-build-core[pyproject]
+ pip install --no-build-isolation -ve .
+
+Running Tests
+~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ # Run directly
+ python test/test_gwr_basic.py test/londonhp100.csv
+ python test/test_gwr_multiscale.py test/londonhp100.csv
+
+ # Enable OpenMP test cases
+ ENABLE_OPENMP=true python test/test_gwr_multiscale.py test/londonhp100.csv
+
+ # Via CTest
+ ctest
+
+Building Documentation
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ pip install furo sphinx
+ sphinx-build -b html -D language=en doc doc/_build/en # English
+ sphinx-build -b html -D language=zh_CN doc doc/_build/zh_CN # Chinese
+
+Project Structure
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: text
+
+ pygwmodel/
+ ├── libgwmodel/ # C++ core algorithms (git submodule)
+ ├── src/ # nanobind C++ bindings + Python API
+ │ ├── pygwmodel/ # Python wrapper layer
+ │ └── *.cpp # C++ binding source files
+ ├── test/ # Integration tests
+ ├── doc/ # Sphinx documentation
+ └── pyproject.toml # Build configuration
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..3ef203c
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,2 @@
+sphinx
+furo
diff --git a/doc/spatial_weight.rst b/doc/spatial_weight.rst
new file mode 100644
index 0000000..f2ec541
--- /dev/null
+++ b/doc/spatial_weight.rst
@@ -0,0 +1,206 @@
+Spatial Weights
+===============
+
+Spatial weights are the core concept of GW models. For each target point :math:`i`,
+the distance to every other data point :math:`j` is computed via a distance metric,
+and a kernel function converts the distance into a weight :math:`w_{ij}`.
+Larger distance means smaller weight.
+
+.. _distance-metrics:
+
+Distance Metrics
+----------------
+
+pygwmodel supports the following distance metrics:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 40 40
+
+ * - Distance Type
+ - Description
+ - Python Class
+ * - CRS Distance
+ - Automatic selection based on coordinate reference system: Euclidean for
+ projected coordinates, great-circle for geographic coordinates.
+ - :class:`~pygwmodel.spatial_weight.CRSDistance`
+ * - Minkowski Distance
+ - Generalised distance parameterised by :math:`p`: :math:`p=1` is Manhattan,
+ :math:`p=2` is Euclidean, :math:`p \to \infty` is Chebyshev.
+ - (C++ implemented; Python binding pending)
+ * - One-Dimensional Distance
+ - Absolute difference along a single spatial or temporal dimension, used as
+ the temporal component in spatiotemporal models.
+ - (C++ implemented; Python binding pending)
+ * - Distance Matrix File
+ - Reads distances from a precomputed ``.dmat`` binary distance matrix file.
+ - (C++ implemented; Python binding pending)
+ * - Spatiotemporal Distance
+ - Weighted combination of spatial and temporal distances, supporting
+ orthogonal and oblique modes.
+ - (C++ implemented; Python binding pending)
+
+CRS Distance
+~~~~~~~~~~~~
+
+The coordinate-reference-system distance is the most commonly used metric,
+automatically selecting the calculation method based on the CRS.
+
+- **Projected coordinates** (``is_geographic=False``) — Euclidean distance:
+
+ .. math::
+
+ d_{ij} = \sqrt{(u_i - u_j)^2 + (v_i - v_j)^2}
+
+- **Geographic coordinates** (``is_geographic=True``) — great-circle distance
+ (geodesic distance).
+
+Usage:
+
+.. code-block:: python
+
+ from pygwmodel import CRSDistance
+
+ # Projected coordinate system (default)
+ dist = CRSDistance(is_geographic=False)
+
+ # Geographic coordinate system
+ dist = CRSDistance(is_geographic=True)
+
+.. _kernel-functions:
+
+Kernel Functions and Weights
+-----------------------------
+
+Kernel functions convert distances :math:`d_{ij}` into weights :math:`w_{ij}`.
+pygwmodel supports five kernel functions, configured via
+:class:`~pygwmodel.spatial_weight.BandwidthWeight`.
+
+.. list-table::
+ :header-rows: 1
+ :widths: 15 35 50
+
+ * - Kernel
+ - Formula
+ - Enum Value
+ * - Gaussian
+ - :math:`w_{ij} = \exp\left(-\dfrac{d_{ij}^2}{2b^2}\right)`
+ - ``BandwidthWeight.Kernel.Gaussian``
+ * - Exponential
+ - :math:`w_{ij} = \exp\left(-\dfrac{|d_{ij}|}{b}\right)`
+ - ``BandwidthWeight.Kernel.Exponential``
+ * - Bisquare
+ - :math:`w_{ij} = \begin{cases} \left(1 - \left(\frac{d_{ij}}{b}\right)^2\right)^2, & d_{ij} < b \\ 0, & \text{otherwise} \end{cases}`
+ - ``BandwidthWeight.Kernel.Bisquare``
+ * - Tricube
+ - :math:`w_{ij} = \begin{cases} \left(1 - \left(\frac{d_{ij}}{b}\right)^3\right)^3, & d_{ij} < b \\ 0, & \text{otherwise} \end{cases}`
+ - ``BandwidthWeight.Kernel.Tricube``
+ * - Boxcar
+ - :math:`w_{ij} = \begin{cases} 1, & d_{ij} < b \\ 0, & \text{otherwise} \end{cases}`
+ - ``BandwidthWeight.Kernel.Boxcar``
+
+Here :math:`b` is the bandwidth and :math:`d_{ij}` is the distance from sample
+:math:`i` to sample :math:`j`.
+
+Gaussian and Exponential are continuous kernels — all samples receive non-zero
+weights. Bisquare, Tricube, and Boxcar are truncated kernels — samples beyond
+the bandwidth receive zero weight, which is useful for emphasising local effects.
+
+Usage:
+
+.. code-block:: python
+
+ from pygwmodel import BandwidthWeight
+
+ # Gaussian kernel (default)
+ bw = BandwidthWeight(bandwidth=36.0, adaptive=True,
+ kernel=BandwidthWeight.Kernel.Gaussian)
+
+ # Bisquare kernel
+ bw = BandwidthWeight(bandwidth=5000.0, adaptive=False,
+ kernel=BandwidthWeight.Kernel.Bisquare)
+
+.. _bandwidth:
+
+Bandwidth
+---------
+
+The bandwidth :math:`b` controls the rate at which spatial weights decay
+and is the most important parameter in GW models.
+
+Bandwidth Types
+~~~~~~~~~~~~~~~
+
+- **Fixed bandwidth**: The bandwidth value is a distance. Weights are computed
+ directly as :math:`w = k(d; b)`.
+- **Adaptive bandwidth**: The bandwidth value is a neighbour count. For focus
+ point :math:`i`, the effective distance bandwidth is the distance to the
+ :math:`b`-th nearest neighbour. Different locations can use different effective
+ distance bandwidths, making this suitable for datasets with non-uniform sample
+ density.
+
+Usage:
+
+.. code-block:: python
+
+ # Adaptive bandwidth: b=36 means use distance to the 36th nearest neighbour
+ bw = BandwidthWeight(bandwidth=36.0, adaptive=True)
+
+ # Fixed bandwidth: b=5000.0 means distance threshold of 5000 coordinate units
+ bw = BandwidthWeight(bandwidth=5000.0, adaptive=False)
+
+Bandwidth Selection
+~~~~~~~~~~~~~~~~~~~
+
+If you are unsure about the right bandwidth value, the algorithm can select it
+automatically.
+
+**GWRBasic**:
+
+.. code-block:: python
+
+ from pygwmodel import GWRBasic
+
+ algorithm = GWRBasic(data, y, x, weight, distance).fit(
+ optimize_bw=GWRBasic.BandwidthSelectionCriterionType.CV
+ )
+ # The optimised bandwidth
+ print(algorithm.weight.bandwidth)
+
+**GWRMultiscale** (each variable's bandwidth is optimised independently
+during backfitting):
+
+.. code-block:: python
+
+ from pygwmodel import GWRMultiscale
+
+ # BandwidthInitilizeType.Null (default) lets the algorithm auto-select
+ algorithm = GWRMultiscale(
+ data, y, x, weights,
+ bandwidth_initilize=None, # all auto-select
+ bandwidth_selection_approach=None # all use CV
+ ).fit()
+
+ # Check the optimised bandwidths
+ for w in algorithm.weights:
+ print(w.bandwidth)
+
+Bandwidth selection criteria:
+
+- **CV** (Cross-Validation): Minimizes the cross-validation residual sum of squares.
+- **AIC** (Akaike Information Criterion): Minimizes the AIC value.
+
+Spatial Weight Configuration
+----------------------------
+
+A :class:`~pygwmodel.spatial_weight.SpatialWeight` combines a distance metric
+and a bandwidth weight, created via the factory method:
+
+.. code-block:: python
+
+ from pygwmodel import SpatialWeight, BandwidthWeight, CRSDistance
+
+ sw = SpatialWeight.create(
+ distance=CRSDistance(is_geographic=False),
+ weight=BandwidthWeight(bandwidth=36.0, adaptive=True)
+ )
diff --git a/libgwmodel b/libgwmodel
index dafda54..78db196 160000
--- a/libgwmodel
+++ b/libgwmodel
@@ -1 +1 @@
-Subproject commit dafda5461e2ad3ae4483698259e9602efd7995c6
+Subproject commit 78db196b60455fb0637bb5d5d9248d241a6bc709
diff --git a/pyproject.toml b/pyproject.toml
index d627381..ddd6aa1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[build-system]
-requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2", "numpy", "geopandas"]
+requires = ["scikit-build-core >=0.4.3", "nanobind >=2.0", "numpy", "geopandas"]
build-backend = "scikit_build_core.build"
[project]
@@ -25,13 +25,11 @@ build-dir = "build/{wheel_tag}"
wheel.py-api = "cp312"
[tool.cibuildwheel]
-# Necessary to see build output from the actual compilation
build-verbosity = 1
-
-# Run pytest to ensure that the package was correctly built
-test-command = "pytest {project}/tests"
+build = "cp312-*"
+skip = "pp* *-musllinux* *-manylinux_i686* *-win32 *-macosx_x86_64"
+test-command = "pytest {project}/tests -v"
test-requires = "pytest"
-# Needed for full C++17 support
[tool.cibuildwheel.macos.environment]
MACOSX_DEPLOYMENT_TARGET = "10.14"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 29ef480..e88cc1f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -19,7 +19,7 @@ else(ARMADILLO_FOUND)
endif(ARMADILLO_FOUND)
set(LIBGWMODEL_INCLUDE_DIR ../libgwmodel/include)
-include_directories(${LIBGWMODEL_INCLUDE_DIR}/gwmodelpp)
+include_directories(${LIBGWMODEL_INCLUDE_DIR} ${LIBGWMODEL_INCLUDE_DIR}/gwmodelpp)
nanobind_add_module(_parallel STABLE_ABI parallel.cpp)
target_link_libraries(_parallel PRIVATE gwmodel)
@@ -27,15 +27,14 @@ target_link_libraries(_parallel PRIVATE gwmodel)
nanobind_add_module(_spatial_weight STABLE_ABI spatial_weight.cpp)
target_link_libraries(_spatial_weight PRIVATE gwmodel)
-nanobind_add_module(_regression STABLE_ABI common.hpp parallel.hpp base.cpp gwr_basic.cpp regression.cpp)
+nanobind_add_module(_regression STABLE_ABI common.hpp parallel.hpp base.cpp gwr_basic.cpp gwr_multiscale.cpp regression.cpp)
target_link_libraries(_regression PRIVATE gwmodel)
-nanobind_add_module(_analysis STABLE_ABI common.hpp base.cpp parallel.hpp analysis.cpp gwss.cpp)
-target_link_libraries(_analysis PRIVATE gwmodel)
+# nanobind_add_module(_analysis STABLE_ABI common.hpp base.cpp parallel.hpp analysis.cpp gwss.cpp)
+# target_link_libraries(_analysis PRIVATE gwmodel)
install(TARGETS
_regression
_spatial_weight
_parallel
- _analysis
LIBRARY DESTINATION pygwmodel)
diff --git a/src/base.cpp b/src/base.cpp
index 2efba05..f61f0f5 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -1,6 +1,8 @@
#include
#include
+#include
#include
+#include
#include
#include
#include
@@ -33,4 +35,24 @@ void init_base(nb::module_& m)
}
)
;
+
+ nb::class_(m, "_SpatialMultiscaleAlgorithm")
+ .def_prop_rw(
+ "spatial_weights",
+ [](gwm::SpatialMultiscaleAlgorithm &instance)
+ {
+ nb::list result;
+ for (const auto& sw : instance.spatialWeights())
+ result.append(nb::cast(sw));
+ return result;
+ },
+ [](gwm::SpatialMultiscaleAlgorithm &instance, nb::list sw_list)
+ {
+ std::vector weights;
+ for (nb::handle item : sw_list)
+ weights.push_back(nb::cast(item));
+ instance.setSpatialWeights(weights);
+ }
+ )
+ ;
}
\ No newline at end of file
diff --git a/src/common.hpp b/src/common.hpp
index 20d704a..6d9567f 100644
--- a/src/common.hpp
+++ b/src/common.hpp
@@ -17,12 +17,12 @@ constexpr int ndim_v = is_vector_v ? 1 : 2;
template
using array_for_arma_t = nb::ndarray<
- nb::numpy,
Scalar,
+ nb::numpy,
std::conditional_t<
is_vector_v == 1,
- nb::shape,
- nb::shape
+ nb::ndim<1>,
+ nb::ndim<2>
>,
std::conditional_t<
is_vector_v == 1,
@@ -68,20 +68,20 @@ struct nb::detail::type_caster];
- int64_t strides[ndim_v];
+ size_t shape[2];
+ int64_t strides[2];
if constexpr (is_vector_v == 1)
{
- shape[0] = v.n_elem;
+ shape[0] = (size_t)v.n_elem;
strides[0] = 1;
}
else
{
- shape[0] = v.n_rows;
- shape[1] = v.n_cols;
+ shape[0] = (size_t)v.n_rows;
+ shape[1] = (size_t)v.n_cols;
strides[0] = 1;
- strides[1] = v.n_rows;
+ strides[1] = (int64_t)v.n_rows;
}
void *ptr = (void *)v.memptr();
diff --git a/src/gwr_multiscale.cpp b/src/gwr_multiscale.cpp
new file mode 100644
index 0000000..8580ca8
--- /dev/null
+++ b/src/gwr_multiscale.cpp
@@ -0,0 +1,81 @@
+#include
+#include
+#include
+#include "common.hpp"
+#include "parallel.hpp"
+
+namespace nb = nanobind;
+
+void init_gwr_multiscale(nb::module_& m)
+{
+ nb::class_ _GWRMultiscale(m, "_GWRMultiscale");
+
+ nb::enum_(_GWRMultiscale, "BandwidthInitilizeType")
+ .value("Null", gwm::GWRMultiscale::BandwidthInitilizeType::Null)
+ .value("Initial", gwm::GWRMultiscale::BandwidthInitilizeType::Initial)
+ .value("Specified", gwm::GWRMultiscale::BandwidthInitilizeType::Specified)
+ .export_values();
+
+ nb::enum_(_GWRMultiscale, "BandwidthSelectionCriterionType")
+ .value("CV", gwm::GWRMultiscale::BandwidthSelectionCriterionType::CV)
+ .value("AIC", gwm::GWRMultiscale::BandwidthSelectionCriterionType::AIC)
+ .export_values();
+
+ nb::enum_(_GWRMultiscale, "BackFittingCriterionType")
+ .value("CVR", gwm::GWRMultiscale::BackFittingCriterionType::CVR)
+ .value("dCVR", gwm::GWRMultiscale::BackFittingCriterionType::dCVR)
+ .export_values();
+
+ _GWRMultiscale
+ .def(nb::init<>())
+ // IRegressionAnalysis interface
+ .def_prop_rw("dependent", &gwm::GWRMultiscale::dependentVariable, &gwm::GWRMultiscale::setDependentVariable, nb::rv_policy::move)
+ .def_prop_rw("independent", &gwm::GWRMultiscale::independentVariables, &gwm::GWRMultiscale::setIndependentVariables, nb::rv_policy::move)
+ .def_prop_rw("has_intercept", &gwm::GWRMultiscale::hasIntercept, &gwm::GWRMultiscale::setHasIntercept)
+ // Configuration
+ .def_prop_ro("bandwidth_initilize", &gwm::GWRMultiscale::bandwidthInitilize)
+ .def("set_bandwidth_initilize", &gwm::GWRMultiscale::setBandwidthInitilize)
+ .def_prop_ro("bandwidth_selection_approach", &gwm::GWRMultiscale::bandwidthSelectionApproach)
+ .def("set_bandwidth_selection_approach", &gwm::GWRMultiscale::setBandwidthSelectionApproach)
+ .def_prop_rw(
+ "preditor_centered",
+ [](gwm::GWRMultiscale &instance)
+ {
+ nb::list result;
+ for (bool v : instance.preditorCentered())
+ result.append(v);
+ return result;
+ },
+ [](gwm::GWRMultiscale &instance, nb::list lst)
+ {
+ std::vector vec;
+ for (nb::handle item : lst)
+ vec.push_back(nb::cast(item));
+ instance.setPreditorCentered(vec);
+ }
+ )
+ .def_prop_rw("bandwidth_select_threshold", &gwm::GWRMultiscale::bandwidthSelectThreshold, &gwm::GWRMultiscale::setBandwidthSelectThreshold)
+ .def_prop_rw("bandwidth_select_retry_times", &gwm::GWRMultiscale::bandwidthSelectRetryTimes, &gwm::GWRMultiscale::setBandwidthSelectRetryTimes)
+ .def_prop_rw("max_iteration", &gwm::GWRMultiscale::maxIteration, &gwm::GWRMultiscale::setMaxIteration)
+ .def_prop_rw("criterion_type", &gwm::GWRMultiscale::criterionType, &gwm::GWRMultiscale::setCriterionType)
+ .def_prop_rw("criterion_threshold", &gwm::GWRMultiscale::criterionThreshold, &gwm::GWRMultiscale::setCriterionThreshold)
+ .def_prop_rw("has_hat_matrix", &gwm::GWRMultiscale::hasHatMatrix, &gwm::GWRMultiscale::setHasHatMatrix)
+ .def_prop_rw("adaptive_lower", &gwm::GWRMultiscale::adaptiveLower, &gwm::GWRMultiscale::setAdaptiveLower)
+ .def("set_golden_lower_bounds", &gwm::GWRMultiscale::setGoldenLowerBounds)
+ .def("set_golden_upper_bounds", &gwm::GWRMultiscale::setGoldenUpperBounds)
+ // Fit
+ .def("fit", [](gwm::GWRMultiscale &instance){ instance.fit(); })
+ // Results
+ .def_prop_ro("betas", &gwm::GWRMultiscale::betas, nb::rv_policy::move)
+ .def_prop_ro("betasSE", &gwm::GWRMultiscale::betasSE, nb::rv_policy::move)
+ .def_prop_ro("betasTV", &gwm::GWRMultiscale::betasTV, nb::rv_policy::move)
+ .def_prop_ro(
+ "diagnostic",
+ [](gwm::GWRMultiscale &instance){ return wrap(instance.diagnostic()); }
+ )
+ ;
+
+ def_parallel_info(_GWRMultiscale);
+ def_parallel_openmp(_GWRMultiscale);
+ def_parallel_cuda(_GWRMultiscale);
+}
diff --git a/src/pygwmodel/__init__.py b/src/pygwmodel/__init__.py
index ee543f9..93cd285 100644
--- a/src/pygwmodel/__init__.py
+++ b/src/pygwmodel/__init__.py
@@ -1,4 +1,5 @@
from .gwr_basic import GWRBasic, ParallelType
+from .gwr_multiscale import GWRMultiscale
from .spatial_weight import SpatialWeight, BandwidthWeight, CRSDistance
if __name__ == "__main__":
diff --git a/src/pygwmodel/gwr_basic.py b/src/pygwmodel/gwr_basic.py
index ab73415..75db815 100644
--- a/src/pygwmodel/gwr_basic.py
+++ b/src/pygwmodel/gwr_basic.py
@@ -1,7 +1,6 @@
from typing import List, Union, Optional
import numpy as np
import geopandas as gp
-from enum import IntEnum
from .spatial_weight import SpatialWeight, Distance, BandwidthWeight
from .parallel import ParallelType
from ._regression import _GWRBasic
@@ -12,9 +11,7 @@ class GWRBasic:
Basic GWR python high api class.
"""
- class BandwidthSelectionCriterionType(IntEnum):
- AIC = _GWRBasic.AIC
- CV = _GWRBasic.CV
+ BandwidthSelectionCriterionType = _GWRBasic.BandwidthSelectionCriterionType
def __init__(self, sdf: gp.GeoDataFrame, depen_var: str, indep_vars: List[str], weight: BandwidthWeight, distance: Distance, has_intercept=True):
"""
diff --git a/src/pygwmodel/gwr_multiscale.py b/src/pygwmodel/gwr_multiscale.py
new file mode 100644
index 0000000..8732d7b
--- /dev/null
+++ b/src/pygwmodel/gwr_multiscale.py
@@ -0,0 +1,185 @@
+from typing import List, Optional
+import numpy as np
+import geopandas as gp
+from .spatial_weight import SpatialWeight, Distance, BandwidthWeight, CRSDistance
+from .parallel import ParallelType
+from ._regression import _GWRMultiscale
+
+
+class GWRMultiscale:
+ """
+ Multiscale GWR python high api class.
+ """
+
+ BandwidthInitilizeType = _GWRMultiscale.BandwidthInitilizeType
+ BandwidthSelectionCriterionType = _GWRMultiscale.BandwidthSelectionCriterionType
+ BackFittingCriterionType = _GWRMultiscale.BackFittingCriterionType
+
+ def __init__(self, sdf: gp.GeoDataFrame, depen_var: str, indep_vars: List[str],
+ weights: List[BandwidthWeight], distance: Distance = CRSDistance(),
+ has_intercept: bool = True,
+ bandwidth_initilize: Optional[List[BandwidthInitilizeType]] = None,
+ bandwidth_selection_approach: Optional[List[BandwidthSelectionCriterionType]] = None,
+ preditor_centered: Optional[List[bool]] = None,
+ has_hat_matrix: bool = True):
+ """
+ Initialize Multiscale GWR.
+
+ Parameters
+ ----------
+ sdf : GeoDataFrame
+ Input spatial data.
+ depen_var : str
+ Name of the dependent variable column.
+ indep_vars : List[str]
+ Names of independent variable columns.
+ weights : List[BandwidthWeight]
+ Bandwidth weights, one per independent variable (plus intercept if has_intercept=True).
+ distance : Distance
+ Distance metric. Defaults to CRSDistance.
+ has_intercept : bool
+ Whether to include an intercept term.
+ bandwidth_initilize : Optional[List[BandwidthInitilizeType]]
+ Bandwidth initialization types, one per variable.
+ Defaults to Null (auto-select) for all.
+ bandwidth_selection_approach : Optional[List[BandwidthSelectionCriterionType]]
+ Bandwidth selection criterion, one per variable.
+ Defaults to CV for all.
+ preditor_centered : Optional[List[bool]]
+ Whether to center each predictor. Defaults to False for all.
+ has_hat_matrix : bool
+ Whether to store the hat matrix S for diagnostic computation.
+ """
+ if not isinstance(sdf, gp.GeoDataFrame):
+ raise ValueError("sdf must be a GeoDataFrame")
+ self.geometry = sdf.geometry
+ self.depen_var: str = depen_var
+ self.indep_vars: List[str] = indep_vars
+ self.has_intercept: bool = has_intercept
+ self.distance = distance
+ self.result_layer: Optional[gp.GeoDataFrame] = None
+
+ n_var = len(indep_vars) + int(has_intercept)
+
+ if len(weights) != n_var:
+ raise ValueError(f"weights must have length {n_var} (one per variable including intercept), got {len(weights)}")
+
+ # Default bandwidth initilize: Null (auto-select) for all
+ if bandwidth_initilize is None:
+ bandwidth_initilize = [GWRMultiscale.BandwidthInitilizeType.Null] * n_var
+ if len(bandwidth_initilize) != n_var:
+ raise ValueError(f"bandwidth_initilize must have length {n_var}")
+
+ # Default bandwidth selection approach: CV for all
+ if bandwidth_selection_approach is None:
+ bandwidth_selection_approach = [GWRMultiscale.BandwidthSelectionCriterionType.CV] * n_var
+ if len(bandwidth_selection_approach) != n_var:
+ raise ValueError(f"bandwidth_selection_approach must have length {n_var}")
+
+ # Default preditor_centered: False for all
+ if preditor_centered is None:
+ preditor_centered = [False] * n_var
+ if len(preditor_centered) != n_var:
+ raise ValueError(f"preditor_centered must have length {n_var}")
+
+ self.weights = weights
+ self.bandwidth_initilize: List[GWRMultiscale.BandwidthInitilizeType] = bandwidth_initilize
+ self.bandwidth_selection_approach: List[GWRMultiscale.BandwidthSelectionCriterionType] = bandwidth_selection_approach
+
+ # Build independent variable matrix
+ indep_vars_data = np.asfortranarray(sdf[indep_vars], dtype=np.float64)
+ if has_intercept:
+ indep_vars_data = np.hstack([np.ones((indep_vars_data.shape[0], 1)), indep_vars_data])
+
+ # Create spatial weights (one per variable)
+ spatial_weights = [SpatialWeight.create(distance, w) for w in weights]
+
+ self.algorithm = _GWRMultiscale()
+ self.algorithm.coords = np.asfortranarray(sdf.geometry.centroid.get_coordinates(), dtype=np.float64)
+ self.algorithm.independent = indep_vars_data
+ self.algorithm.dependent = np.asfortranarray(sdf[depen_var], dtype=np.float64)
+ self.algorithm.spatial_weights = spatial_weights
+ self.algorithm.has_intercept = has_intercept
+ self.algorithm.has_hat_matrix = has_hat_matrix
+
+ # Set bandwidth configuration
+ self.algorithm.set_bandwidth_initilize(
+ [_GWRMultiscale.BandwidthInitilizeType(t.value) for t in bandwidth_initilize]
+ )
+ self.algorithm.set_bandwidth_selection_approach(
+ [_GWRMultiscale.BandwidthSelectionCriterionType(t.value) for t in bandwidth_selection_approach]
+ )
+ self.algorithm.preditor_centered = preditor_centered
+
+ # Default threshold values (one per variable)
+ self.algorithm.bandwidth_select_threshold = [1.0e-6] * n_var
+
+ def enable_parallel_omp(self, threads: int = 8):
+ if self.algorithm is None:
+ raise ValueError("Not initialized")
+ if isinstance(threads, int) and threads > 0:
+ self.algorithm.parallel_omp(threads)
+ else:
+ raise ValueError("threads must be a positive integer")
+ return self
+
+ def enable_parallel_cuda(self, gpu_id: int = 0, group_size: int = 64):
+ if self.algorithm is None:
+ raise ValueError("Not initialized")
+ if all([(isinstance(x, int) and x > 0) for x in [gpu_id, group_size]]):
+ self.algorithm.parallel_cuda(gpu_id, group_size)
+ else:
+ raise ValueError("gpu_id and group_size must be positive integers")
+ return self
+
+ def enable_parallel(self, type: ParallelType, **kvargs):
+ if type == ParallelType.OpenMP:
+ self.enable_parallel_omp(**kvargs)
+ elif type == ParallelType.CUDA:
+ self.enable_parallel_cuda(**kvargs)
+ return self
+
+ def fit(self):
+ """
+ Run the multiscale GWR algorithm and return self.
+ """
+ self.algorithm.fit()
+
+ # Update bandwidths from fitted results
+ for i, sw in enumerate(self.algorithm.spatial_weights):
+ bw_info = sw.weight()
+ self.weights[i].bandwidth = bw_info[1]
+ self.weights[i].adaptive = bw_info[2]
+ self.weights[i].kernel = BandwidthWeight.Kernel(bw_info[3])
+
+ # Build result GeoDataFrame
+ indep_var_names = (['Intercept'] if self.has_intercept else []) + self.indep_vars
+ result_data = {
+ **{f: self.algorithm.betas[:, i] for i, f in enumerate(indep_var_names)},
+ **{f'{f}_SE': self.algorithm.betasSE[:, i] for i, f in enumerate(indep_var_names)},
+ }
+ if self.algorithm.has_hat_matrix:
+ result_data.update(
+ {f'{f}_TV': self.algorithm.betasTV[:, i] for i, f in enumerate(indep_var_names)}
+ )
+ result_data['fitted'] = np.sum(
+ self.algorithm.independent * self.algorithm.betas, axis=1
+ )
+ self.result_layer = gp.GeoDataFrame(result_data, geometry=self.geometry)
+ return self
+
+ @property
+ def diagnostic(self):
+ return self.algorithm.diagnostic if self.algorithm else None
+
+ @property
+ def betas(self):
+ return self.algorithm.betas if self.algorithm else None
+
+ @property
+ def betasSE(self):
+ return self.algorithm.betasSE if self.algorithm else None
+
+ @property
+ def betasTV(self):
+ return self.algorithm.betasTV if self.algorithm else None
diff --git a/src/pygwmodel/parallel.py b/src/pygwmodel/parallel.py
index 764e34f..7dbd5d2 100644
--- a/src/pygwmodel/parallel.py
+++ b/src/pygwmodel/parallel.py
@@ -1,8 +1,3 @@
-from enum import IntEnum
from ._parallel import _ParallelType
-
-class ParallelType(IntEnum):
- Serial = _ParallelType.SerialOnly
- OpenMP = _ParallelType.OpenMP
- CUDA = _ParallelType.CUDA
\ No newline at end of file
+ParallelType = _ParallelType
\ No newline at end of file
diff --git a/src/pygwmodel/spatial_weight.py b/src/pygwmodel/spatial_weight.py
index c69d2ec..6cff712 100644
--- a/src/pygwmodel/spatial_weight.py
+++ b/src/pygwmodel/spatial_weight.py
@@ -1,5 +1,4 @@
from typing import Optional
-from enum import IntEnum
from ._spatial_weight import _SpatialWeight
from ._spatial_weight import _BandwidthWeight
@@ -29,12 +28,7 @@ def as_args(self) -> tuple:
class BandwidthWeight(Weight):
- class Kernel(IntEnum):
- Gaussian = _BandwidthWeight.Gaussian
- Exponential = _BandwidthWeight.Exponential
- Bisquare = _BandwidthWeight.Bisquare
- Tricube = _BandwidthWeight.Tricube
- Boxcar = _BandwidthWeight.Boxcar
+ Kernel = _BandwidthWeight.BandwidthKernelType
bandwidth: Optional[float] = None
adaptive: bool = False
diff --git a/src/regression.cpp b/src/regression.cpp
index 746ba7d..31484d2 100644
--- a/src/regression.cpp
+++ b/src/regression.cpp
@@ -6,6 +6,7 @@ namespace nb = nanobind;
void init_base(nb::module_& m);
void init_gwr_basic(nb::module_& m);
+void init_gwr_multiscale(nb::module_& m);
NB_MODULE(_regression, m)
{
@@ -36,4 +37,5 @@ NB_MODULE(_regression, m)
;
init_gwr_basic(m);
+ init_gwr_multiscale(m);
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 30a02e4..eca7354 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -25,7 +25,6 @@ add_custom_target(testPythonDeps ALL
$
$
$
- $
${PYTHON_TEST_SCRIPT_DIR}/pygwmodel
COMMAND ${CMAKE_COMMAND} -E
copy_if_different
@@ -39,9 +38,14 @@ add_test(
COMMAND ${Python_EXECUTABLE} test_gwr_basic.py ${PYTHON_TEST_DATA}
WORKING_DIRECTORY ${PYTHON_TEST_SCRIPT_DIR}
)
+# add_test(
+# NAME testPythonGWSS
+# COMMAND ${Python_EXECUTABLE} test_gwss.py ${PYTHON_TEST_DATA}
+# WORKING_DIRECTORY ${PYTHON_TEST_SCRIPT_DIR}
+# )
add_test(
- NAME testPythonGWSS
- COMMAND ${Python_EXECUTABLE} test_gwss.py ${PYTHON_TEST_DATA}
+ NAME testPythonGWRMultiscale
+ COMMAND ${Python_EXECUTABLE} test_gwr_multiscale.py ${PYTHON_TEST_DATA}
WORKING_DIRECTORY ${PYTHON_TEST_SCRIPT_DIR}
)
# add_test(
diff --git a/test/test_gwr_basic.py b/test/test_gwr_basic.py
index dc435d7..1271794 100644
--- a/test/test_gwr_basic.py
+++ b/test/test_gwr_basic.py
@@ -6,18 +6,19 @@
import geopandas as gp
from pygwmodel import GWRBasic, ParallelType, BandwidthWeight, CRSDistance
+TEST_DATA = os.environ.get("PYGW_TEST_DATA", sys.argv[1] if len(sys.argv) > 1 else "test/londonhp100.csv")
ENABLE_OPENMP = (lambda s: False if s is None else (s.lower() in ['true', '1', 't', 'y', 'yes', 'on']))(os.getenv("ENABLE_OPENMP"))
class TestGWRBasic(unittest.TestCase):
def setUp(self):
- londonhp_csv = pd.read_csv(sys.argv[1])
+ londonhp_csv = pd.read_csv(TEST_DATA)
self.londonhp = gp.GeoDataFrame(londonhp_csv, geometry=gp.points_from_xy(londonhp_csv.x, londonhp_csv.y))
self.depen = 'PURCHASE'
self.indep = ["FLOORSZ", "UNEMPLOY", "PROF"]
self.parallel_case = {
- ParallelType.Serial: dict()
+ ParallelType.SerialOnly: dict()
}
self.weight = BandwidthWeight(36.0, True)
self.distance = CRSDistance(False)
diff --git a/test/test_gwr_multiscale.py b/test/test_gwr_multiscale.py
new file mode 100644
index 0000000..bcc5a72
--- /dev/null
+++ b/test/test_gwr_multiscale.py
@@ -0,0 +1,168 @@
+import sys
+import os
+import unittest
+import numpy as np
+import pandas as pd
+import geopandas as gp
+from pygwmodel import GWRMultiscale, ParallelType, BandwidthWeight, CRSDistance
+
+TEST_DATA = os.environ.get("PYGW_TEST_DATA", sys.argv[1] if len(sys.argv) > 1 else "test/londonhp100.csv")
+ENABLE_OPENMP = (lambda s: False if s is None else (s.lower() in ['true', '1', 't', 'y', 'yes', 'on']))(os.getenv("ENABLE_OPENMP"))
+
+
+class TestGWRMultiscale(unittest.TestCase):
+
+ def setUp(self):
+ londonhp_csv = pd.read_csv(TEST_DATA)
+ self.londonhp = gp.GeoDataFrame(londonhp_csv, geometry=gp.points_from_xy(londonhp_csv.x, londonhp_csv.y))
+ self.depen = 'PURCHASE'
+ self.indep = ["FLOORSZ", "UNEMPLOY", "PROF"]
+ self.parallel_case = {
+ ParallelType.SerialOnly: dict()
+ }
+ if ENABLE_OPENMP:
+ self.parallel_case[ParallelType.OpenMP] = {'threads': 4}
+
+ def test_fit_specified_bandwidths(self):
+ """Test fit with manually specified bandwidths (fast and deterministic)."""
+ n_var = len(self.indep) + 1 # including intercept
+ weights = [BandwidthWeight(36.0, True) for _ in range(n_var)]
+
+ for p, pargs in self.parallel_case.items():
+ with self.subTest(parallel=p):
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=None, # defaults to Null (auto)
+ bandwidth_selection_approach=None, # defaults to CV
+ has_hat_matrix=True
+ )
+ algorithm.enable_parallel(p, **pargs).fit()
+
+ # Check result structure
+ self.assertIsNotNone(algorithm.result_layer)
+ expected_cols = ['Intercept', 'Intercept_SE', 'Intercept_TV']
+ for v in self.indep:
+ expected_cols += [v, f'{v}_SE', f'{v}_TV']
+ expected_cols.append('fitted')
+ for col in expected_cols:
+ self.assertIn(col, algorithm.result_layer.columns)
+
+ # Check betas shape: (n_samples, n_vars)
+ self.assertEqual(algorithm.betas.shape, (len(self.londonhp), n_var))
+ self.assertEqual(algorithm.betasSE.shape, (len(self.londonhp), n_var))
+ self.assertEqual(algorithm.betasTV.shape, (len(self.londonhp), n_var))
+
+ # Check diagnostic
+ diag = algorithm.diagnostic
+ self.assertIsNotNone(diag)
+ for key in ['RSS', 'AICc', 'ENP', 'EDF', 'RSquare', 'RSquareAdjust']:
+ self.assertIn(key, diag)
+ self.assertGreater(diag['RSquare'], 0)
+ self.assertLess(diag['RSquare'], 1)
+
+ def test_fit_result_layer(self):
+ """Test that result_layer contains finite values."""
+ n_var = len(self.indep) + 1
+ weights = [BandwidthWeight(50.0, True) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=None,
+ bandwidth_selection_approach=None,
+ has_hat_matrix=True
+ )
+ algorithm.max_iteration = 10
+ algorithm.fit()
+
+ result = algorithm.result_layer
+ self.assertIsNotNone(result)
+ self.assertEqual(len(result), len(self.londonhp))
+ # Check no NaN in fitted values
+ self.assertFalse(np.any(np.isnan(result['fitted'].values)))
+
+ def test_specified_bandwidths_skip_selection(self):
+ """Test with Specified bandwidth init type skips auto-selection."""
+ n_var = len(self.indep) + 1
+ weights = [BandwidthWeight(36.0, True) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=[GWRMultiscale.BandwidthInitilizeType.Specified] * n_var,
+ bandwidth_selection_approach=[GWRMultiscale.BandwidthSelectionCriterionType.CV] * n_var,
+ has_hat_matrix=True
+ )
+ algorithm.max_iteration = 10
+ algorithm.fit()
+
+ # Bandwidths should remain as specified (not auto-optimized)
+ for w in algorithm.weights:
+ self.assertEqual(w.bandwidth, 36.0)
+
+ # Verify result layer is produced
+ self.assertIsNotNone(algorithm.result_layer)
+ diag = algorithm.diagnostic
+ self.assertIsNotNone(diag)
+
+ def test_no_hat_matrix(self):
+ """Test fitting without hat matrix (faster, less memory)."""
+ n_var = len(self.indep) + 1
+ weights = [BandwidthWeight(36.0, True) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=[GWRMultiscale.BandwidthInitilizeType.Specified] * n_var,
+ bandwidth_selection_approach=[GWRMultiscale.BandwidthSelectionCriterionType.CV] * n_var,
+ has_hat_matrix=False
+ )
+ algorithm.max_iteration = 10
+ algorithm.fit()
+
+ self.assertIsNotNone(algorithm.result_layer)
+ diag = algorithm.diagnostic
+ self.assertIsNotNone(diag)
+
+ def test_fixed_bandwidth(self):
+ """Test with fixed (non-adaptive) bandwidths."""
+ n_var = len(self.indep) + 1
+ weights = [BandwidthWeight(5000.0, False) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=[GWRMultiscale.BandwidthInitilizeType.Specified] * n_var,
+ bandwidth_selection_approach=[GWRMultiscale.BandwidthSelectionCriterionType.CV] * n_var,
+ has_hat_matrix=True
+ )
+ algorithm.max_iteration = 10
+ algorithm.fit()
+
+ self.assertIsNotNone(algorithm.result_layer)
+ diag = algorithm.diagnostic
+ self.assertIsNotNone(diag)
+ self.assertGreater(diag['RSquare'], 0)
+ self.assertLess(diag['RSquare'], 1)
+
+ def test_backfitting_criterion(self):
+ """Test with CVR backfitting criterion type."""
+ n_var = len(self.indep) + 1
+ weights = [BandwidthWeight(36.0, True) for _ in range(n_var)]
+
+ algorithm = GWRMultiscale(
+ self.londonhp, self.depen, self.indep,
+ weights,
+ bandwidth_initilize=[GWRMultiscale.BandwidthInitilizeType.Specified] * n_var,
+ has_hat_matrix=True
+ )
+ algorithm.criterion_type = GWRMultiscale.BackFittingCriterionType.CVR
+ algorithm.max_iteration = 10
+ algorithm.fit()
+
+ self.assertIsNotNone(algorithm.result_layer)
+
+
+if __name__ == '__main__':
+ unittest.main(argv=[''], verbosity=2)
diff --git a/test/test_gwss.py b/test/test_gwss.py
index d328505..b1104f8 100644
--- a/test/test_gwss.py
+++ b/test/test_gwss.py
@@ -8,16 +8,17 @@
from pygwmodel.spatial_weight import BandwidthWeight
from pygwmodel.gwss import GWSS
+TEST_DATA = os.environ.get("PYGW_TEST_DATA", sys.argv[1] if len(sys.argv) > 1 else "test/londonhp100.csv")
ENABLE_OPENMP = (lambda s: False if s is None else (s.lower() in ['true', '1', 't', 'y', 'yes', 'on']))(os.getenv("ENABLE_OPENMP"))
class TestGWSS(unittest.TestCase):
def setUp(self):
- londonhp_csv = pd.read_csv(sys.argv[1])
+ londonhp_csv = pd.read_csv(TEST_DATA)
self.londonhp = gp.GeoDataFrame(londonhp_csv, geometry=gp.points_from_xy(londonhp_csv.x, londonhp_csv.y))
self.londonhp_vars = ["PURCHASE", "FLOORSZ", "UNEMPLOY", "PROF"]
self.parallel_case = {
- ParallelType.Serial: dict()
+ ParallelType.SerialOnly: dict()
}
if ENABLE_OPENMP:
self.parallel_case[ParallelType.OpenMP] = {'threads': 4}