From 5e397728c32ebcaf0db6862acfe86a7bb50f6029 Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 13:17:52 +0800 Subject: [PATCH 01/20] edit: libgwmodel update --- libgwmodel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libgwmodel b/libgwmodel index dafda54..7da2d15 160000 --- a/libgwmodel +++ b/libgwmodel @@ -1 +1 @@ -Subproject commit dafda5461e2ad3ae4483698259e9602efd7995c6 +Subproject commit 7da2d157a371c59bc885159645762c5ba7d781a9 From 3e11f3f1dd59717bf51edc18e0afb23d7e9ef2a6 Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 14:49:13 +0800 Subject: [PATCH 02/20] add: mgwr interface basic --- src/CMakeLists.txt | 9 +- src/base.cpp | 22 ++++ src/common.hpp | 2 +- src/gwr_multiscale.cpp | 81 +++++++++++++ src/pygwmodel/__init__.py | 1 + src/pygwmodel/gwr_multiscale.py | 195 ++++++++++++++++++++++++++++++++ src/regression.cpp | 2 + test/CMakeLists.txt | 10 +- test/test_gwr_multiscale.py | 167 +++++++++++++++++++++++++++ 9 files changed, 480 insertions(+), 9 deletions(-) create mode 100644 src/gwr_multiscale.cpp create mode 100644 src/pygwmodel/gwr_multiscale.py create mode 100644 test/test_gwr_multiscale.py 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..7c40925 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -32,7 +32,7 @@ using array_for_arma_t = nb::ndarray< >; template -struct nb::detail::type_caster>> +struct nb::detail::type_caster>> { using Scalar = typename T::elem_type; using NDArray = array_for_arma_t; 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_multiscale.py b/src/pygwmodel/gwr_multiscale.py new file mode 100644 index 0000000..fea996a --- /dev/null +++ b/src/pygwmodel/gwr_multiscale.py @@ -0,0 +1,195 @@ +from typing import List, Optional +from enum import IntEnum +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. + """ + + class BandwidthInitilizeType(IntEnum): + Null = _GWRMultiscale.BandwidthInitilizeType.Null + Initial = _GWRMultiscale.BandwidthInitilizeType.Initial + Specified = _GWRMultiscale.BandwidthInitilizeType.Specified + + class BandwidthSelectionCriterionType(IntEnum): + CV = _GWRMultiscale.BandwidthSelectionCriterionType.CV + AIC = _GWRMultiscale.BandwidthSelectionCriterionType.AIC + + class BackFittingCriterionType(IntEnum): + CVR = _GWRMultiscale.BackFittingCriterionType.CVR + dCVR = _GWRMultiscale.BackFittingCriterionType.dCVR + + 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/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_multiscale.py b/test/test_gwr_multiscale.py new file mode 100644 index 0000000..dfead1b --- /dev/null +++ b/test/test_gwr_multiscale.py @@ -0,0 +1,167 @@ +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 + +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(sys.argv[1]) + 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() + } + 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) From f686672661026344cd257ac1b29a8de7be02b5d7 Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 16:57:38 +0800 Subject: [PATCH 03/20] edit: migrate to nanobind v2 --- pyproject.toml | 2 +- src/common.hpp | 20 ++++++++++---------- src/pygwmodel/gwr_basic.py | 5 +---- src/pygwmodel/gwr_multiscale.py | 16 +++------------- src/pygwmodel/parallel.py | 7 +------ src/pygwmodel/spatial_weight.py | 8 +------- test/test_gwr_basic.py | 2 +- test/test_gwr_multiscale.py | 2 +- test/test_gwss.py | 2 +- 9 files changed, 20 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d627381..ded539b 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] diff --git a/src/common.hpp b/src/common.hpp index 7c40925..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, @@ -32,7 +32,7 @@ using array_for_arma_t = nb::ndarray< >; template -struct nb::detail::type_caster>> +struct nb::detail::type_caster>> { using Scalar = typename T::elem_type; using NDArray = array_for_arma_t; @@ -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/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 index fea996a..8732d7b 100644 --- a/src/pygwmodel/gwr_multiscale.py +++ b/src/pygwmodel/gwr_multiscale.py @@ -1,5 +1,4 @@ from typing import List, Optional -from enum import IntEnum import numpy as np import geopandas as gp from .spatial_weight import SpatialWeight, Distance, BandwidthWeight, CRSDistance @@ -12,18 +11,9 @@ class GWRMultiscale: Multiscale GWR python high api class. """ - class BandwidthInitilizeType(IntEnum): - Null = _GWRMultiscale.BandwidthInitilizeType.Null - Initial = _GWRMultiscale.BandwidthInitilizeType.Initial - Specified = _GWRMultiscale.BandwidthInitilizeType.Specified - - class BandwidthSelectionCriterionType(IntEnum): - CV = _GWRMultiscale.BandwidthSelectionCriterionType.CV - AIC = _GWRMultiscale.BandwidthSelectionCriterionType.AIC - - class BackFittingCriterionType(IntEnum): - CVR = _GWRMultiscale.BackFittingCriterionType.CVR - dCVR = _GWRMultiscale.BackFittingCriterionType.dCVR + 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(), 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/test/test_gwr_basic.py b/test/test_gwr_basic.py index dc435d7..2ebdf6a 100644 --- a/test/test_gwr_basic.py +++ b/test/test_gwr_basic.py @@ -17,7 +17,7 @@ def setUp(self): 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 index dfead1b..c41880e 100644 --- a/test/test_gwr_multiscale.py +++ b/test/test_gwr_multiscale.py @@ -17,7 +17,7 @@ def setUp(self): self.depen = 'PURCHASE' self.indep = ["FLOORSZ", "UNEMPLOY", "PROF"] self.parallel_case = { - ParallelType.Serial: dict() + ParallelType.SerialOnly: dict() } if ENABLE_OPENMP: self.parallel_case[ParallelType.OpenMP] = {'threads': 4} diff --git a/test/test_gwss.py b/test/test_gwss.py index d328505..c7202d4 100644 --- a/test/test_gwss.py +++ b/test/test_gwss.py @@ -17,7 +17,7 @@ def setUp(self): 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} From cfceffc5472b82624fd01a61835fd0672fc765fa Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 17:15:43 +0800 Subject: [PATCH 04/20] edit: libgwmodel fix problem --- libgwmodel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libgwmodel b/libgwmodel index 7da2d15..78db196 160000 --- a/libgwmodel +++ b/libgwmodel @@ -1 +1 @@ -Subproject commit 7da2d157a371c59bc885159645762c5ba7d781a9 +Subproject commit 78db196b60455fb0637bb5d5d9248d241a6bc709 From 94e4f4dca78c61772d29897bd21580c4226a91b8 Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 17:31:25 +0800 Subject: [PATCH 05/20] edit: docs --- README.md | 21 +++++- doc/index.rst | 162 +++++++++++++++++++++++++++++++++++++++++----- doc/pygwmodel.rst | 14 +++- 3 files changed, 174 insertions(+), 23 deletions(-) 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/index.rst b/doc/index.rst index 05885c8..3e76613 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,28 +1,156 @@ -.. 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 parameter-specific bandwidths and + backfitting algorithm. +* **GWSS** — Geographically Weighted Summary Statistics (averages and correlations). + +Installation +------------ + +We highly recommend installing in a conda environment: + +.. code-block:: bash + + conda install armadillo gsl openblas numpy geopandas nanobind scikit-build-core + git clone https://github.com/GWmodel-Lab/pygwmodel.git + cd pygwmodel + git submodule update --init --recursive + pip install . + +On Windows, set the environment variable to use OpenBLAS: + +.. code-block:: shell + + set CMAKE_ARGS="-DBLA_VENDOR=OpenBLAS" + pip install . + +Quick Start +----------- + +Basic GWR +~~~~~~~~~ + +.. code-block:: python + + from pygwmodel import GWRBasic, BandwidthWeight, CRSDistance + + algorithm = GWRBasic( + data, # GeoDataFrame + depen_var="PURCHASE", + indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"], + weight=BandwidthWeight(36.0, adaptive=True), + distance=CRSDistance() + ).fit() + + print(algorithm.diagnostic) # {'AIC': ..., 'RSquare': ...} + print(algorithm.result_layer) # GeoDataFrame with betas, SE, fitted + +Multiscale GWR (MGWR) +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + 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, + depen_var="PURCHASE", + indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"], + weights=weights, + ).fit() + + print(algorithm.diagnostic) + # Bandwidths are auto-optimized per variable + for w in algorithm.weights: + print(w.bandwidth) + +Algorithm Parameters +-------------------- + +GWRBasic +~~~~~~~~ + +* ``weight`` — A single :class:`~pygwmodel.spatial_weight.BandwidthWeight` shared + by all variables. +* ``fit(optimize_bw=...)`` — Optionally auto-select bandwidth via CV or AIC. +* ``fit(optimize_var=...)`` — Optionally auto-select variables via forward + selection. + +GWRMultiscale +~~~~~~~~~~~~~ + +* ``weights`` — A list of :class:`~pygwmodel.spatial_weight.BandwidthWeight`, one + per variable (including intercept). Each variable gets its own bandwidth. +* ``bandwidth_initilize`` — Per-variable initialization strategy: + :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null` (auto-select), + ``Specified`` (fixed), or ``Initial``. +* ``bandwidth_selection_approach`` — Criterion per variable: + :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` + or ``AIC``. +* ``criterion_type`` — Backfitting convergence criterion: + :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` + or ``dCVR``. +* ``max_iteration`` — Maximum backfitting iterations (default 500). +* ``preditor_centered`` — Whether to center each predictor before fitting. + +Spatial Weights +--------------- + +.. code-block:: python + + from pygwmodel import BandwidthWeight, CRSDistance + + # Adaptive bandwidth: number of nearest neighbors + bw = BandwidthWeight(bandwidth=36, adaptive=True) + + # Fixed bandwidth: distance in coordinate units + bw = BandwidthWeight(bandwidth=5000.0, adaptive=False) + + # Kernel function (default: Gaussian) + bw = BandwidthWeight(bandwidth=36, adaptive=True, + kernel=BandwidthWeight.Kernel.Bisquare) + +Available kernels: ``Gaussian``, ``Exponential``, ``Bisquare``, ``Tricube``, ``Boxcar``. + +Parallel Computing +------------------ + +Enable multi-threading via OpenMP: + +.. code-block:: python + + from pygwmodel import ParallelType + + algorithm = GWRBasic(data, y, x, w).enable_parallel( + ParallelType.OpenMP, threads=4 + ).fit() + +API Reference +------------- .. toctree:: - :maxdepth: 2 - :caption: Contents: + :maxdepth: 3 + :caption: Module Reference: modules.rst -Indices and tables +Indices and Tables ================== * :ref:`genindex` 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 ------------------------- From a10b45de142011f84ab917dd566cb517b0a68aa8 Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 17:49:37 +0800 Subject: [PATCH 06/20] add: zh-CN Translation --- doc/conf.py | 13 ++ doc/locale/zh_CN/LC_MESSAGES/index.po | 156 ++++++++++++++++++++++ doc/locale/zh_CN/LC_MESSAGES/modules.po | 20 +++ doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po | 64 +++++++++ pyproject.toml | 8 +- 5 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 doc/locale/zh_CN/LC_MESSAGES/index.po create mode 100644 doc/locale/zh_CN/LC_MESSAGES/modules.po create mode 100644 doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po diff --git a/doc/conf.py b/doc/conf.py index cb7e032..056d1b2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -37,6 +37,16 @@ # 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 +58,9 @@ 'libgwmodel' ] +# Suppress strict reference-consistency check across translations +suppress_warnings = ['i18n.inconsistent_references'] + # -- Options for HTML output ------------------------------------------------- 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..e2572a6 --- /dev/null +++ b/doc/locale/zh_CN/LC_MESSAGES/index.po @@ -0,0 +1,156 @@ +# Chinese translation for pygwmodel documentation. +# Copyright (C) 2023, GWmodel-Lab +# This file is distributed under the same license as the pygwmodel package. +# +msgid "" +msgstr "" +"Project-Id-Version: pygwmodel 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-19 17:38+0800\n" +"PO-Revision-Date: 2026-05-19 17:40+0800\n" +"Last-Translator: GWmodel-Lab\n" +"Language-Team: zh_CN <>\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: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** 是一个 Python 包,为基于 `libgwmodel `_ 和 **GeoPandas** 的高性能 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 parameter-specific 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 "Installation" +msgstr "安装" + +#: ../../index.rst:23 +msgid "We highly recommend installing in a conda environment:" +msgstr "强烈推荐在 conda 环境中安装:" + +#: ../../index.rst:33 +msgid "On Windows, set the environment variable to use OpenBLAS:" +msgstr "在 Windows 上,需设置环境变量以使用 OpenBLAS:" + +#: ../../index.rst:41 +msgid "Quick Start" +msgstr "快速入门" + +#: ../../index.rst:44 +msgid "Basic GWR" +msgstr "基本地理加权回归" + +#: ../../index.rst:62 +msgid "Multiscale GWR (MGWR)" +msgstr "多尺度GWR(MGWR)" + +#: ../../index.rst:84 +msgid "Algorithm Parameters" +msgstr "算法参数" + +#: ../../index.rst:87 +msgid "GWRBasic" +msgstr "GWRBasic" + +#: ../../index.rst:89 +msgid "``weight`` — A single :class:`~pygwmodel.spatial_weight.BandwidthWeight` shared by all variables." +msgstr "``weight`` — 所有变量共用的单一 :class:`~pygwmodel.spatial_weight.BandwidthWeight`。" + +#: ../../index.rst:91 +msgid "``fit(optimize_bw=...)`` — Optionally auto-select bandwidth via CV or AIC." +msgstr "``fit(optimize_bw=...)`` — 可选地通过 CV 或 AIC 自动优选带宽。" + +#: ../../index.rst:92 +msgid "``fit(optimize_var=...)`` — Optionally auto-select variables via forward selection." +msgstr "``fit(optimize_var=...)`` — 可选地通过前向选择自动优选变量。" + +#: ../../index.rst:96 +msgid "GWRMultiscale" +msgstr "GWRMultiscale" + +#: ../../index.rst:98 +msgid "``weights`` — A list of :class:`~pygwmodel.spatial_weight.BandwidthWeight`, one per variable (including intercept). Each variable gets its own bandwidth." +msgstr "``weights`` — 一列 :class:`~pygwmodel.spatial_weight.BandwidthWeight`,每个变量(含截距)一个。每个变量拥有独立的带宽。" + +#: ../../index.rst:100 +msgid "``bandwidth_initilize`` — Per-variable initialization strategy: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null` (auto-select), ``Specified`` (fixed), or ``Initial``." +msgstr "``bandwidth_initilize`` — 每个变量的带宽初始值类型::class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null`(自动优选)、``Specified``(指定值)或 ``Initial``。" + +#: ../../index.rst:103 +msgid "``bandwidth_selection_approach`` — Criterion per variable: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` or ``AIC``." +msgstr "``bandwidth_selection_approach`` — 每个变量的带宽选择指标: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` 或 ``AIC``。" + +#: ../../index.rst:106 +msgid "``criterion_type`` — Backfitting convergence criterion: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` or ``dCVR``." +msgstr "``criterion_type`` — 后向迭代算法收敛指标值类型: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` 或 ``dCVR``。" + +#: ../../index.rst:109 +msgid "``max_iteration`` — Maximum backfitting iterations (default 500)." +msgstr "``max_iteration`` — 最大迭代次数(默认 500)。" + +#: ../../index.rst:110 +msgid "``preditor_centered`` — Whether to center each predictor before fitting." +msgstr "``preditor_centered`` — 是否在拟合前中心化每个自变量。" + +#: ../../index.rst:113 +msgid "Spatial Weights" +msgstr "空间权重" + +#: ../../index.rst:129 +msgid "Available kernels: ``Gaussian``, ``Exponential``, ``Bisquare``, ``Tricube``, ``Boxcar``." +msgstr "可用的核函数:``Gaussian``、``Exponential``、``Bisquare``、``Tricube``、``Boxcar``。" + +#: ../../index.rst:132 +msgid "Parallel Computing" +msgstr "并行计算" + +#: ../../index.rst:134 +msgid "Enable multi-threading via OpenMP:" +msgstr "通过 OpenMP 启用多线程并行:" + +#: ../../index.rst:145 +msgid "API Reference" +msgstr "API 参考" + +#: ../../index.rst:147 +msgid "Module Reference:" +msgstr "模块参考:" + +#: ../../index.rst:154 +msgid "Indices and Tables" +msgstr "索引和列表" + +#: ../../index.rst:156 +msgid ":ref:`genindex`" +msgstr ":ref:`genindex`" + +#: ../../index.rst:157 +msgid ":ref:`modindex`" +msgstr ":ref:`modindex`" + +#: ../../index.rst:158 +msgid ":ref:`search`" +msgstr ":ref:`search`" 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..6c8e0b3 --- /dev/null +++ b/doc/locale/zh_CN/LC_MESSAGES/modules.po @@ -0,0 +1,20 @@ +# Chinese translation for pygwmodel documentation. +# Copyright (C) 2023, GWmodel-Lab +# This file is distributed under the same license as the pygwmodel package. +# +msgid "" +msgstr "" +"Project-Id-Version: pygwmodel 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-19 17:38+0800\n" +"PO-Revision-Date: 2026-05-19 17:40+0800\n" +"Last-Translator: GWmodel-Lab\n" +"Language-Team: zh_CN <>\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/pygwmodel.po b/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po new file mode 100644 index 0000000..8c15482 --- /dev/null +++ b/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po @@ -0,0 +1,64 @@ +# Chinese translation for pygwmodel documentation. +# Copyright (C) 2023, GWmodel-Lab +# This file is distributed under the same license as the pygwmodel package. +# +msgid "" +msgstr "" +"Project-Id-Version: pygwmodel 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-19 17:38+0800\n" +"PO-Revision-Date: 2026-05-19 17:40+0800\n" +"Last-Translator: GWmodel-Lab\n" +"Language-Team: zh_CN <>\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 "基本地理加权回归 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/pyproject.toml b/pyproject.toml index ded539b..ddd6aa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" From de66be8d987b5e65bad8d6b9bff200b890e7093e Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 18:04:05 +0800 Subject: [PATCH 07/20] add: github workflow --- .github/workflows/cd.yml | 98 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 66 +++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..6f627d9 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,98 @@ +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] + + defaults: + run: + working-directory: pygwmodel + + 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: 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_BEFORE_ALL_MACOS: brew install armadillo gsl openblas + CIBW_BEFORE_ALL_WINDOWS: | + $vcpkg = "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" + & $vcpkg install armadillo gsl openblas --triplet x64-windows + CIBW_ENVIRONMENT_WINDOWS: > + CMAKE_ARGS="-DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="10.14" + CIBW_TEST_COMMAND: "pytest {project}/tests -v" + CIBW_TEST_REQUIRES: pytest + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: pygwmodel/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..0003d4f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +jobs: + test: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + defaults: + run: + working-directory: pygwmodel + + 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: Install native dependencies (Windows) + if: runner.os == 'Windows' + 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]" pytest + + - name: Build package + if: runner.os != 'Windows' + run: pip install --no-build-isolation -ve . + + - name: Build package (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $env:CMAKE_ARGS = "-DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + pip install --no-build-isolation -ve . + + - name: Run tests + run: python -m pytest test/ -v From 11d859d36559d3eea6068ffc6d8af8e1be2ee3cb Mon Sep 17 00:00:00 2001 From: yigong Date: Tue, 19 May 2026 23:47:19 +0800 Subject: [PATCH 08/20] add: instructions --- doc/index.rst | 143 +--- doc/locale/zh_CN/LC_MESSAGES/index.po | 131 +--- doc/locale/zh_CN/LC_MESSAGES/models.po | 683 ++++++++++++++++++ doc/locale/zh_CN/LC_MESSAGES/modules.po | 11 +- doc/locale/zh_CN/LC_MESSAGES/performance.po | 210 ++++++ doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po | 23 +- doc/locale/zh_CN/LC_MESSAGES/quickstart.po | 97 +++ .../zh_CN/LC_MESSAGES/spatial_weight.po | 263 +++++++ doc/models.rst | 8 + doc/models/gwr.rst | 166 +++++ doc/models/gwr_multiscale.rst | 196 +++++ doc/models/gwss.rst | 117 +++ doc/performance.rst | 143 ++++ doc/quickstart.rst | 116 +++ doc/spatial_weight.rst | 206 ++++++ 15 files changed, 2265 insertions(+), 248 deletions(-) create mode 100644 doc/locale/zh_CN/LC_MESSAGES/models.po create mode 100644 doc/locale/zh_CN/LC_MESSAGES/performance.po create mode 100644 doc/locale/zh_CN/LC_MESSAGES/quickstart.po create mode 100644 doc/locale/zh_CN/LC_MESSAGES/spatial_weight.po create mode 100644 doc/models.rst create mode 100644 doc/models/gwr.rst create mode 100644 doc/models/gwr_multiscale.rst create mode 100644 doc/models/gwss.rst create mode 100644 doc/performance.rst create mode 100644 doc/quickstart.rst create mode 100644 doc/spatial_weight.rst diff --git a/doc/index.rst b/doc/index.rst index 3e76613..a09ee9d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,141 +13,38 @@ Implemented Models ------------------ * **GWRBasic** — Basic Geographically Weighted Regression with a single bandwidth. -* **GWRMultiscale** — Multiscale GWR (MGWR) with parameter-specific bandwidths and +* **GWRMultiscale** — Multiscale GWR (MGWR) with per-variable bandwidths and backfitting algorithm. * **GWSS** — Geographically Weighted Summary Statistics (averages and correlations). -Installation ------------- - -We highly recommend installing in a conda environment: - -.. code-block:: bash - - conda install armadillo gsl openblas numpy geopandas nanobind scikit-build-core - git clone https://github.com/GWmodel-Lab/pygwmodel.git - cd pygwmodel - git submodule update --init --recursive - pip install . - -On Windows, set the environment variable to use OpenBLAS: - -.. code-block:: shell - - set CMAKE_ARGS="-DBLA_VENDOR=OpenBLAS" - pip install . - Quick Start ----------- -Basic GWR -~~~~~~~~~ - .. code-block:: python - from pygwmodel import GWRBasic, BandwidthWeight, CRSDistance - - algorithm = GWRBasic( - data, # GeoDataFrame - depen_var="PURCHASE", - indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"], - weight=BandwidthWeight(36.0, adaptive=True), - distance=CRSDistance() - ).fit() - - print(algorithm.diagnostic) # {'AIC': ..., 'RSquare': ...} - print(algorithm.result_layer) # GeoDataFrame with betas, SE, fitted - -Multiscale GWR (MGWR) -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - 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, - depen_var="PURCHASE", - indep_vars=["FLOORSZ", "UNEMPLOY", "PROF"], - weights=weights, - ).fit() - - print(algorithm.diagnostic) - # Bandwidths are auto-optimized per variable - for w in algorithm.weights: - print(w.bandwidth) - -Algorithm Parameters --------------------- - -GWRBasic -~~~~~~~~ + from pygwmodel import GWRBasic, GWRMultiscale, BandwidthWeight, CRSDistance -* ``weight`` — A single :class:`~pygwmodel.spatial_weight.BandwidthWeight` shared - by all variables. -* ``fit(optimize_bw=...)`` — Optionally auto-select bandwidth via CV or AIC. -* ``fit(optimize_var=...)`` — Optionally auto-select variables via forward - selection. + # Basic GWR + algorithm = GWRBasic(data, y, x, + weight=BandwidthWeight(36.0, adaptive=True), + distance=CRSDistance()).fit() + print(algorithm.diagnostic['RSquare']) -GWRMultiscale -~~~~~~~~~~~~~ - -* ``weights`` — A list of :class:`~pygwmodel.spatial_weight.BandwidthWeight`, one - per variable (including intercept). Each variable gets its own bandwidth. -* ``bandwidth_initilize`` — Per-variable initialization strategy: - :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null` (auto-select), - ``Specified`` (fixed), or ``Initial``. -* ``bandwidth_selection_approach`` — Criterion per variable: - :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` - or ``AIC``. -* ``criterion_type`` — Backfitting convergence criterion: - :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` - or ``dCVR``. -* ``max_iteration`` — Maximum backfitting iterations (default 500). -* ``preditor_centered`` — Whether to center each predictor before fitting. - -Spatial Weights ---------------- - -.. code-block:: python - - from pygwmodel import BandwidthWeight, CRSDistance - - # Adaptive bandwidth: number of nearest neighbors - bw = BandwidthWeight(bandwidth=36, adaptive=True) - - # Fixed bandwidth: distance in coordinate units - bw = BandwidthWeight(bandwidth=5000.0, adaptive=False) - - # Kernel function (default: Gaussian) - bw = BandwidthWeight(bandwidth=36, adaptive=True, - kernel=BandwidthWeight.Kernel.Bisquare) - -Available kernels: ``Gaussian``, ``Exponential``, ``Bisquare``, ``Tricube``, ``Boxcar``. - -Parallel Computing ------------------- - -Enable multi-threading via OpenMP: - -.. code-block:: python - - from pygwmodel import ParallelType - - algorithm = GWRBasic(data, y, x, w).enable_parallel( - ParallelType.OpenMP, threads=4 - ).fit() - -API Reference -------------- + # Multiscale GWR + mgwr = GWRMultiscale(data, y, x, + weights=[BandwidthWeight(36.0, adaptive=True) for _ in range(4)] + ).fit() + print(mgwr.diagnostic) .. toctree:: - :maxdepth: 3 - :caption: Module Reference: - + :maxdepth: 2 + :caption: Contents: + + quickstart + spatial_weight + models + models/gwss + performance modules.rst Indices and Tables diff --git a/doc/locale/zh_CN/LC_MESSAGES/index.po b/doc/locale/zh_CN/LC_MESSAGES/index.po index e2572a6..729213c 100644 --- a/doc/locale/zh_CN/LC_MESSAGES/index.po +++ b/doc/locale/zh_CN/LC_MESSAGES/index.po @@ -1,31 +1,36 @@ -# Chinese translation for pygwmodel documentation. +# 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 0.1.0\n" +"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 17:40+0800\n" -"Last-Translator: GWmodel-Lab\n" -"Language-Team: zh_CN <>\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** 是一个 Python 包,为基于 `libgwmodel `_ 和 **GeoPandas** 的高性能 C++ 地理加权(GW)模型实现提供清晰、易用的接口。" +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 模型是空间统计学的一个分支,适用于数据不能被某个全局模型很好描述,但存在某些空间区域通过适当的局部拟合能提供更好描述的场合。" +msgstr "GW 模型是空间统计学的一个分支,适用于数据不能被某个全局模型很好描述,但存在空间区域可通过适当的局部标定提供更优描述的情形。" #: ../../index.rst:13 msgid "Implemented Models" @@ -33,124 +38,32 @@ msgstr "已实现的模型" #: ../../index.rst:15 msgid "**GWRBasic** — Basic Geographically Weighted Regression with a single bandwidth." -msgstr "**GWRBasic** — 基本地理加权回归,使用单一带宽。" +msgstr "**GWRBasic** — 基础地理加权回归,使用单一带宽。" #: ../../index.rst:16 -msgid "**GWRMultiscale** — Multiscale GWR (MGWR) with parameter-specific bandwidths and backfitting algorithm." -msgstr "**GWRMultiscale** — 多尺度GWR(MGWR),每个变量拥有独立的带宽,采用后向迭代算法。" +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 "Installation" -msgstr "安装" - -#: ../../index.rst:23 -msgid "We highly recommend installing in a conda environment:" -msgstr "强烈推荐在 conda 环境中安装:" - -#: ../../index.rst:33 -msgid "On Windows, set the environment variable to use OpenBLAS:" -msgstr "在 Windows 上,需设置环境变量以使用 OpenBLAS:" - -#: ../../index.rst:41 msgid "Quick Start" msgstr "快速入门" -#: ../../index.rst:44 -msgid "Basic GWR" -msgstr "基本地理加权回归" - -#: ../../index.rst:62 -msgid "Multiscale GWR (MGWR)" -msgstr "多尺度GWR(MGWR)" - -#: ../../index.rst:84 -msgid "Algorithm Parameters" -msgstr "算法参数" - -#: ../../index.rst:87 -msgid "GWRBasic" -msgstr "GWRBasic" - -#: ../../index.rst:89 -msgid "``weight`` — A single :class:`~pygwmodel.spatial_weight.BandwidthWeight` shared by all variables." -msgstr "``weight`` — 所有变量共用的单一 :class:`~pygwmodel.spatial_weight.BandwidthWeight`。" - -#: ../../index.rst:91 -msgid "``fit(optimize_bw=...)`` — Optionally auto-select bandwidth via CV or AIC." -msgstr "``fit(optimize_bw=...)`` — 可选地通过 CV 或 AIC 自动优选带宽。" - -#: ../../index.rst:92 -msgid "``fit(optimize_var=...)`` — Optionally auto-select variables via forward selection." -msgstr "``fit(optimize_var=...)`` — 可选地通过前向选择自动优选变量。" - -#: ../../index.rst:96 -msgid "GWRMultiscale" -msgstr "GWRMultiscale" - -#: ../../index.rst:98 -msgid "``weights`` — A list of :class:`~pygwmodel.spatial_weight.BandwidthWeight`, one per variable (including intercept). Each variable gets its own bandwidth." -msgstr "``weights`` — 一列 :class:`~pygwmodel.spatial_weight.BandwidthWeight`,每个变量(含截距)一个。每个变量拥有独立的带宽。" - -#: ../../index.rst:100 -msgid "``bandwidth_initilize`` — Per-variable initialization strategy: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null` (auto-select), ``Specified`` (fixed), or ``Initial``." -msgstr "``bandwidth_initilize`` — 每个变量的带宽初始值类型::class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthInitilizeType.Null`(自动优选)、``Specified``(指定值)或 ``Initial``。" - -#: ../../index.rst:103 -msgid "``bandwidth_selection_approach`` — Criterion per variable: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` or ``AIC``." -msgstr "``bandwidth_selection_approach`` — 每个变量的带宽选择指标: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BandwidthSelectionCriterionType.CV` 或 ``AIC``。" - -#: ../../index.rst:106 -msgid "``criterion_type`` — Backfitting convergence criterion: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` or ``dCVR``." -msgstr "``criterion_type`` — 后向迭代算法收敛指标值类型: :class:`~pygwmodel.gwr_multiscale.GWRMultiscale.BackFittingCriterionType.CVR` 或 ``dCVR``。" - -#: ../../index.rst:109 -msgid "``max_iteration`` — Maximum backfitting iterations (default 500)." -msgstr "``max_iteration`` — 最大迭代次数(默认 500)。" - -#: ../../index.rst:110 -msgid "``preditor_centered`` — Whether to center each predictor before fitting." -msgstr "``preditor_centered`` — 是否在拟合前中心化每个自变量。" - -#: ../../index.rst:113 -msgid "Spatial Weights" -msgstr "空间权重" - -#: ../../index.rst:129 -msgid "Available kernels: ``Gaussian``, ``Exponential``, ``Bisquare``, ``Tricube``, ``Boxcar``." -msgstr "可用的核函数:``Gaussian``、``Exponential``、``Bisquare``、``Tricube``、``Boxcar``。" - -#: ../../index.rst:132 -msgid "Parallel Computing" -msgstr "并行计算" - -#: ../../index.rst:134 -msgid "Enable multi-threading via OpenMP:" -msgstr "通过 OpenMP 启用多线程并行:" - -#: ../../index.rst:145 -msgid "API Reference" -msgstr "API 参考" - -#: ../../index.rst:147 -msgid "Module Reference:" -msgstr "模块参考:" - -#: ../../index.rst:154 +#: ../../index.rst:51 msgid "Indices and Tables" -msgstr "索引和列表" +msgstr "索引与表格" -#: ../../index.rst:156 +#: ../../index.rst:53 msgid ":ref:`genindex`" msgstr ":ref:`genindex`" -#: ../../index.rst:157 +#: ../../index.rst:54 msgid ":ref:`modindex`" msgstr ":ref:`modindex`" -#: ../../index.rst:158 +#: ../../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 index 6c8e0b3..6776d23 100644 --- a/doc/locale/zh_CN/LC_MESSAGES/modules.po +++ b/doc/locale/zh_CN/LC_MESSAGES/modules.po @@ -1,15 +1,16 @@ -# Chinese translation for pygwmodel documentation. +# 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 0.1.0\n" +"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 17:40+0800\n" -"Last-Translator: GWmodel-Lab\n" -"Language-Team: zh_CN <>\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" 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 index 8c15482..e5f42ae 100644 --- a/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po +++ b/doc/locale/zh_CN/LC_MESSAGES/pygwmodel.po @@ -1,15 +1,16 @@ -# Chinese translation for pygwmodel documentation. +# 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 0.1.0\n" +"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 17:40+0800\n" -"Last-Translator: GWmodel-Lab\n" -"Language-Team: zh_CN <>\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" @@ -17,7 +18,7 @@ msgstr "" #: ../../pygwmodel.rst:2 msgid "pygwmodel package" -msgstr "pygwmodel 包" +msgstr "pygwmodel 软件包" #: ../../pygwmodel.rst:5 msgid "Submodules" @@ -25,11 +26,11 @@ msgstr "子模块" #: ../../pygwmodel.rst:8 msgid "pygwmodel.gwr\\_basic module" -msgstr "pygwmodel.gwr_basic 模块" +msgstr "pygwmodel.gwr\\_basic 模块" #: ../../../src/pygwmodel/gwr_basic.py:docstring of pygwmodel.gwr_basic.GWRBasic:1 msgid "Basic GWR python high api class." -msgstr "基本地理加权回归 Python 高层 API 类。" +msgstr "基础 GWR Python 高层 API 类。" #: ../../../src/pygwmodel/gwr_basic.py:docstring of pygwmodel.gwr_basic.GWRBasic.fit:1 msgid "Run algorithm and return result" @@ -45,11 +46,11 @@ 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 类。" +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算法并返回自身。" +msgstr "运行多尺度 GWR 算法并返回自身。" #: ../../pygwmodel.rst:32 msgid "pygwmodel.parallel module" @@ -57,7 +58,7 @@ msgstr "pygwmodel.parallel 模块" #: ../../pygwmodel.rst:40 msgid "pygwmodel.spatial\\_weight module" -msgstr "pygwmodel.spatial_weight 模块" +msgstr "pygwmodel.spatial\\_weight 模块" #: ../../pygwmodel.rst:48 msgid "Module contents" 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/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/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) + ) From b3c059f3c8235a53cebc361f433abc454f2d1e6d Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 09:25:30 +0800 Subject: [PATCH 09/20] edit: workflow config --- .github/workflows/cd.yml | 6 +----- .github/workflows/ci.yml | 4 ---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6f627d9..2f8cef0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,10 +14,6 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - defaults: - run: - working-directory: pygwmodel - steps: - name: Checkout uses: actions/checkout@v4 @@ -54,7 +50,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }} - path: pygwmodel/wheelhouse/*.whl + path: wheelhouse/*.whl publish-pypi: name: Publish to PyPI diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0003d4f..fbcc83f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,6 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - defaults: - run: - working-directory: pygwmodel - steps: - name: Checkout uses: actions/checkout@v4 From e8ece80b34587297fa1a888ee4ea068aa5b9d5d5 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 10:00:04 +0800 Subject: [PATCH 10/20] edit: disable catch2 test --- .github/workflows/cd.yml | 7 +++++-- .github/workflows/ci.yml | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2f8cef0..74ba026 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -36,13 +36,16 @@ jobs: 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" CIBW_BEFORE_ALL_MACOS: brew install armadillo gsl openblas + CIBW_ENVIRONMENT_MACOS: > + CMAKE_ARGS="-DWITH_TESTS=OFF" + 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: > - CMAKE_ARGS="-DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" - CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="10.14" + CMAKE_ARGS="-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" CIBW_TEST_COMMAND: "pytest {project}/tests -v" CIBW_TEST_REQUIRES: pytest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbcc83f..fadd0e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,12 +50,14 @@ jobs: - name: Build package if: runner.os != 'Windows' run: pip install --no-build-isolation -ve . + env: + CMAKE_ARGS: "-DWITH_TESTS=OFF" - name: Build package (Windows) if: runner.os == 'Windows' shell: pwsh run: | - $env:CMAKE_ARGS = "-DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + $env:CMAKE_ARGS = "-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" pip install --no-build-isolation -ve . - name: Run tests From fde4fbee80aac61ef2ecf5611cf9f912c3b36524 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 10:04:35 +0800 Subject: [PATCH 11/20] edit: workflow install python dependencies --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fadd0e6..a07f090 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: python-version: '3.12' - name: Install build dependencies - run: pip install nanobind "scikit-build-core[pyproject]" pytest + run: pip install nanobind "scikit-build-core[pyproject]" numpy geopandas pytest - name: Build package if: runner.os != 'Windows' From b04cf6f3a688f510f42289197b6d007b0b994c16 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 12:59:11 +0800 Subject: [PATCH 12/20] edit: disable gwss test --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 74ba026..9fa3d19 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -46,7 +46,7 @@ jobs: & $vcpkg install armadillo gsl openblas --triplet x64-windows CIBW_ENVIRONMENT_WINDOWS: > CMAKE_ARGS="-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" - CIBW_TEST_COMMAND: "pytest {project}/tests -v" + CIBW_TEST_COMMAND: "pytest {project}/tests -v --ignore={project}/tests/test_gwss.py" CIBW_TEST_REQUIRES: pytest - name: Upload wheels diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a07f090..380465e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,4 +61,4 @@ jobs: pip install --no-build-isolation -ve . - name: Run tests - run: python -m pytest test/ -v + run: python -m pytest test/ -v --ignore=test/test_gwss.py From 6fa970a78a9dced7ecfa409fafbaf33b8825ed71 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 13:40:05 +0800 Subject: [PATCH 13/20] fix: use env var for test data path and configure CI/CD workflows --- .github/workflows/cd.yml | 6 +++++- .github/workflows/ci.yml | 2 ++ test/test_gwr_basic.py | 3 ++- test/test_gwr_multiscale.py | 3 ++- test/test_gwss.py | 3 ++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9fa3d19..1aed155 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -36,16 +36,20 @@ jobs: 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" + 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: > CMAKE_ARGS="-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 380465e..a586fbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,3 +62,5 @@ jobs: - name: Run tests run: python -m pytest test/ -v --ignore=test/test_gwss.py + env: + PYGW_TEST_DATA: test/londonhp100.csv diff --git a/test/test_gwr_basic.py b/test/test_gwr_basic.py index 2ebdf6a..1271794 100644 --- a/test/test_gwr_basic.py +++ b/test/test_gwr_basic.py @@ -6,13 +6,14 @@ 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"] diff --git a/test/test_gwr_multiscale.py b/test/test_gwr_multiscale.py index c41880e..bcc5a72 100644 --- a/test/test_gwr_multiscale.py +++ b/test/test_gwr_multiscale.py @@ -6,13 +6,14 @@ 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(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"] diff --git a/test/test_gwss.py b/test/test_gwss.py index c7202d4..b1104f8 100644 --- a/test/test_gwss.py +++ b/test/test_gwss.py @@ -8,12 +8,13 @@ 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 = { From be7e4dbb670a82ac25191a5aafda10e22c1251cf Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 14:45:51 +0800 Subject: [PATCH 14/20] fix: use openblas on windows --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1aed155..ab659a7 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -48,7 +48,7 @@ jobs: $vcpkg = "$env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" & $vcpkg install armadillo gsl openblas --triplet x64-windows CIBW_ENVIRONMENT_WINDOWS: > - CMAKE_ARGS="-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + 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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a586fbc..cd0e109 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | - $env:CMAKE_ARGS = "-DWITH_TESTS=OFF -DBLA_VENDOR=OpenBLAS -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + $env:CMAKE_ARGS = "-DWITH_TESTS=OFF -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" pip install --no-build-isolation -ve . - name: Run tests From 03be28b953a2b741bffb7ce3769a681d36da7e8d Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 15:59:50 +0800 Subject: [PATCH 15/20] fix: dll load failed on windows --- .github/workflows/cd.yml | 1 + .github/workflows/ci.yml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ab659a7..46aef45 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -48,6 +48,7 @@ jobs: $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" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd0e109..c048a16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,16 @@ jobs: pip install --no-build-isolation -ve . - name: Run tests + if: runner.os != 'Windows' run: python -m pytest test/ -v --ignore=test/test_gwss.py env: PYGW_TEST_DATA: test/londonhp100.csv + + - name: Run tests (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $env:PATH = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/bin;$env:PATH" + python -m pytest test/ -v --ignore=test/test_gwss.py + env: + PYGW_TEST_DATA: test/londonhp100.csv From 6dea0409fe5b4b9a31eb899ef0855456499bba45 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 16:20:27 +0800 Subject: [PATCH 16/20] add: readthedoc connfigure --- .readthedocs.yaml | 19 +++++++++++++++++++ doc/conf.py | 9 +++++++++ doc/requirements.txt | 2 ++ 3 files changed, 30 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 doc/requirements.txt 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/doc/conf.py b/doc/conf.py index 056d1b2..cb64bf4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,6 +34,15 @@ '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'] 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 From 539ad99b36446f917c95cd95d754463130a70760 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 16:33:15 +0800 Subject: [PATCH 17/20] edit: vcpkg package cache --- .github/workflows/cd.yml | 25 ++++++++++++++ .github/workflows/ci.yml | 72 ++++++++++++++++++++++++++++++---------- vcpkg.json | 10 ++++++ 3 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 vcpkg.json diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 46aef45..20ec7f9 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -13,6 +13,14 @@ jobs: 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 @@ -28,6 +36,23 @@ jobs: - 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 2025.02.14 --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: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c048a16..f78f071 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,13 +8,13 @@ on: workflow_dispatch: jobs: - test: + test-unix: name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest] steps: - name: Checkout @@ -32,8 +32,58 @@ jobs: if: runner.os == 'macOS' run: brew install armadillo gsl openblas - - name: Install native dependencies (Windows) - if: runner.os == '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 + 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 2025.02.14 --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" @@ -48,26 +98,12 @@ jobs: run: pip install nanobind "scikit-build-core[pyproject]" numpy geopandas pytest - name: Build package - if: runner.os != 'Windows' - run: pip install --no-build-isolation -ve . - env: - CMAKE_ARGS: "-DWITH_TESTS=OFF" - - - name: Build package (Windows) - if: runner.os == 'Windows' 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 -ve . - name: Run tests - if: runner.os != 'Windows' - run: python -m pytest test/ -v --ignore=test/test_gwss.py - env: - PYGW_TEST_DATA: test/londonhp100.csv - - - name: Run tests (Windows) - if: runner.os == 'Windows' shell: pwsh run: | $env:PATH = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/bin;$env:PATH" diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..7e03b21 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,10 @@ +{ + "dependencies": [ + "armadillo", + "gsl", + { + "name": "openblas", + "features": ["threads"] + } + ] +} From cd17978c3128e5b6945ad9cecfe6cefd0d5d2e1a Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 16:46:56 +0800 Subject: [PATCH 18/20] edit: vcpkg not in manifest mode --- vcpkg.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 vcpkg.json diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index 7e03b21..0000000 --- a/vcpkg.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dependencies": [ - "armadillo", - "gsl", - { - "name": "openblas", - "features": ["threads"] - } - ] -} From 35ad0f1a4c90e06d43b99bb05d87e99a1be63ddb Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 16:51:18 +0800 Subject: [PATCH 19/20] edit: update vcpkg version --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 20ec7f9..dd5d571 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -42,7 +42,7 @@ jobs: run: | cd "$env:VCPKG_INSTALLATION_ROOT" git fetch - git switch 2025.02.14 --detach + git switch 2026.04.27 --detach - name: Setup NuGet Credentials if: runner.os == 'Windows' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f78f071..c345f2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: run: | cd "$env:VCPKG_INSTALLATION_ROOT" git fetch - git switch 2025.02.14 --detach + git switch 2026.04.27 --detach - name: Setup NuGet Credentials shell: pwsh From 446de7c8553f304df1774bf50744ba0dae3a10a4 Mon Sep 17 00:00:00 2001 From: yigong Date: Wed, 20 May 2026 20:20:51 +0800 Subject: [PATCH 20/20] fix: resolve Windows DLL load error by switching to regular install and persisting vcpkg PATH --- .github/workflows/ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c345f2f..2c44116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,12 +101,13 @@ jobs: 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 -ve . + pip install --no-build-isolation -v . - - name: Run tests + - name: Add vcpkg DLLs to PATH shell: pwsh - run: | - $env:PATH = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/bin;$env:PATH" - python -m pytest test/ -v --ignore=test/test_gwss.py + 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