chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:27:18 +08:00
commit d72d1a58f0
553 changed files with 214565 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
version: 4.6.0.99.{build}
image: Visual Studio 2017
platform: x64
configuration:
- '3.10'
# only build on 'master' / 'main' and pull requests targeting it
branches:
only:
- master
- main
environment:
matrix:
- COMPILER: MSVC
TASK: python
- COMPILER: MINGW
TASK: python
clone_depth: 5
install:
- git submodule update --init --recursive # get `external_libs` folder
- set PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH%
- set PYTHON_VERSION=%CONFIGURATION%
- powershell.exe -ExecutionPolicy ByPass -c "Invoke-RestMethod https://pixi.sh/install.ps1 | Invoke-Expression"
- set PATH=C:\Users\appveyor\.pixi\bin;%PATH%
- ps: |
$env:APPVEYOR = "true"
$env:CMAKE_BUILD_PARALLEL_LEVEL = 4
$env:BUILD_SOURCESDIRECTORY = "$env:APPVEYOR_BUILD_FOLDER"
# tell scripts where to put artifacts
# (this variable name is left over from when jobs ran on Azure DevOps)
$env:BUILD_ARTIFACTSTAGINGDIRECTORY = "$env:APPVEYOR_BUILD_FOLDER/artifacts"
build: false
test_script:
- powershell.exe -ExecutionPolicy Bypass -File %APPVEYOR_BUILD_FOLDER%\.ci\test-windows.ps1
+6
View File
@@ -0,0 +1,6 @@
Helper Scripts for CI
=====================
This folder contains scripts which are run on CI services.
Dockerfile used on CI service is maintained in a separate [GitHub repository](https://github.com/guolinke/lightgbm-ci-docker) and can be pulled from [Docker Hub](https://hub.docker.com/r/lightgbm/vsts-agent).
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
#
# [description]
# Post a comment to a pull request.
#
# [usage]
# append-comment.sh <PULL_REQUEST_ID> <BODY>
#
# PULL_REQUEST_ID: ID of PR to post the comment on.
#
# BODY: Text of the comment to be posted.
set -e -E -u -o pipefail
if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit 1
fi
if [ $# -ne 2 ]; then
echo "Usage: $0 <PULL_REQUEST_ID> <BODY>"
exit 1
fi
pr_id=$1
body=$2
body=${body/failure/failure ❌}
body=${body/error/failure ❌}
body=${body/cancelled/failure ❌}
body=${body/timed_out/failure ❌}
body=${body/success/success ✔️}
data=$(
jq -n \
--argjson body "\"$body\"" \
'{"body": $body}'
)
curl -sL \
--fail \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-d "$data" \
"${GITHUB_API_URL}/repos/lightgbm-org/LightGBM/issues/${pr_id}/comments"
+19
View File
@@ -0,0 +1,19 @@
loadNamespace("pkgdown")
loadNamespace("roxygen2")
roxygen2::roxygenize(
"R-package/"
, load = "installed"
)
pkgdown::build_site(
"R-package/"
, lazy = FALSE
, install = FALSE
, devel = FALSE
, examples = TRUE
, run_dont_run = TRUE
, seed = 42L
, preview = FALSE
, new_process = TRUE
)
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
set -e -E -u -o pipefail
conda env create \
--name test-env \
--file ./docs/env.yml \
|| exit 1
# shellcheck disable=SC1091
source activate test-env
make -C docs html || exit 1
+129
View File
@@ -0,0 +1,129 @@
#!/bin/bash
#
# [description]
# Helper script for checking versions in the dynamic symbol table.
# This script checks that LightGBM library is linked to the appropriate symbol versions.
# Linking to newer symbol versions at compile time is problematic because it could result
# in built artifacts being unusable on older platforms.
#
# Version history for these symbols can be found at the following:
# * GLIBC: https://sourceware.org/glibc/wiki/Glibc%20Timeline
# * GLIBCXX: https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
# * OMP/GOMP: https://github.com/gcc-mirror/gcc/blob/master/libgomp/libgomp.map
#
# [usage]
# check-dynamic-dependencies.sh <PATH>
#
# PATH: Path to the file.
# Path to the file with the dynamic symbol table entries of the file
# (result of `objdump -T` command).
set -e -E -u -o pipefail
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <PATH>"
exit 1
fi
INPUT_FILE="$1"
if [ ! -f "$INPUT_FILE" ]; then
echo "Error: File '$INPUT_FILE' not found."
exit 1
fi
awk '
BEGIN {
glibc_count = 0
glibcxx_count = 0
gomp_count = 0
has_error = 0
}
# --- Check GLIBC ---
/0{16}[ \t\(]+GLIBC_[0-9]+\.[0-9]+/ {
match($0, /GLIBC_([0-9]+)\.([0-9]+)/, parts)
if (RSTART > 0) {
match($0, /GLIBC_[0-9]+\.[0-9]+/)
ver_str = substr($0, RSTART+6, RLENGTH-6) # skip "GLIBC_"
split(ver_str, v, ".")
major = v[1] + 0
minor = v[2] + 0
glibc_count++
if (major > 2 || (major == 2 && minor > 28)) {
print "Error: found unexpected GLIBC version: \x27" major "." minor "\x27"
has_error = 1
}
}
}
# --- Check GLIBCXX ---
/0{16}[ \t\(]+GLIBCXX_[0-9]+\.[0-9]+/ {
match($0, /GLIBCXX_[0-9]+\.[0-9]+(\.[0-9]+)?/)
if (RSTART > 0) {
ver_str = substr($0, RSTART+8, RLENGTH-8) # skip "GLIBCXX_"
n = split(ver_str, v, ".")
major = v[1] + 0
minor = v[2] + 0
patch = (n >= 3) ? v[3] + 0 : 0
patch_str = (n >= 3) ? v[3] : ""
glibcxx_count++
msg_ver = major "." minor
if (n >= 3) msg_ver = msg_ver "." patch
if (major != 3 || minor != 4) {
print "Error: found unexpected GLIBCXX version: \x27" msg_ver "\x27"
has_error = 1
}
if (n >= 3 && patch > 22) {
print "Error: found unexpected GLIBCXX version: \x27" msg_ver "\x27"
has_error = 1
}
}
}
# --- Check OMP/GOMP ---
/0{16}[ \t\(]+G?OMP_[0-9]+\.[0-9]+/ {
match($0, /G?OMP_[0-9]+\.[0-9]+/)
if (RSTART > 0) {
full_match = substr($0, RSTART, RLENGTH)
us_idx = index(full_match, "_")
ver_str = substr(full_match, us_idx + 1)
split(ver_str, v, ".")
major = v[1] + 0
minor = v[2] + 0
gomp_count++
if (major > 4 || (major == 4 && minor > 5)) {
print "Error: found unexpected OMP/GOMP version: \x27" major "." minor "\x27"
has_error = 1
}
}
}
END {
if (glibc_count <= 1) {
print "Error: Not enough GLIBC symbols found (found " glibc_count ", expected > 1)"
has_error = 1
}
if (glibcxx_count <= 1) {
print "Error: Not enough GLIBCXX symbols found (found " glibcxx_count ", expected > 1)"
has_error = 1
}
if (gomp_count <= 1) {
print "Error: Not enough OMP/GOMP symbols found (found " gomp_count ", expected > 1)"
has_error = 1
}
if (has_error == 1) {
exit 1
}
}
' "$INPUT_FILE"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
set -e -u
echo "checking that all OpenMP pragmas specify num_threads()"
get_omp_pragmas_without_num_threads() {
grep \
-n \
-R \
--include='*.c' \
--include='*.cc' \
--include='*.cpp' \
--include='*.h' \
--include='*.hpp' \
'pragma omp parallel' \
| grep -v ' num_threads'
}
# 'grep' returns a non-0 exit code if 0 lines were found.
# Turning off '-e -o pipefail' options here so that bash doesn't
# consider this a failure and stop execution of the script.
#
# ref: https://www.gnu.org/software/grep/manual/html_node/Exit-Status.html
set +e
PROBLEMATIC_LINES=$(
get_omp_pragmas_without_num_threads
)
set -e
if test "${PROBLEMATIC_LINES}" != ""; then
get_omp_pragmas_without_num_threads
echo "Found '#pragma omp parallel' not using explicit num_threads() configuration. Fix those."
echo "For details, see https://www.openmp.org/spec-html/5.0/openmpse14.html#x54-800002.6"
exit 1
fi
echo "done checking OpenMP pragmas"
+58
View File
@@ -0,0 +1,58 @@
#!/bin/sh
set -e -u
DIST_DIR=${1}
# defaults
METHOD=${METHOD:-""}
TASK=${TASK:-""}
echo "checking Python-package distributions in '${DIST_DIR}'"
pip install \
-qq \
check-wheel-contents \
twine || exit 1
echo "twine check..."
twine check --strict "$(echo "${DIST_DIR}"/*)" || exit 1
if { test "${TASK}" = "bdist" || test "${METHOD}" = "wheel"; }; then
echo "check-wheel-contents..."
check-wheel-contents "$(echo "${DIST_DIR}"/*.whl)" || exit 1
fi
PY_MINOR_VER=$(python -c "import sys; print(sys.version_info.minor)")
if [ "$PY_MINOR_VER" -gt 7 ]; then
echo "pydistcheck..."
pip install 'pydistcheck>=0.9.1'
if { test "${TASK}" = "cuda" || test "${METHOD}" = "wheel"; }; then
pydistcheck \
--inspect \
--ignore 'compiled-objects-have-debug-symbols'\
--ignore 'distro-too-large-compressed' \
--max-allowed-size-uncompressed '550M' \
--max-allowed-files 806 \
"$(echo "${DIST_DIR}"/*)" || exit 1
elif { test "$(uname -m)" = "aarch64"; }; then
pydistcheck \
--inspect \
--ignore 'compiled-objects-have-debug-symbols' \
--max-allowed-size-compressed '5M' \
--max-allowed-size-uncompressed '15M' \
--max-allowed-files 806 \
"$(echo "${DIST_DIR}"/*)" || exit 1
else
pydistcheck \
--inspect \
--max-allowed-size-compressed '5M' \
--max-allowed-size-uncompressed '15M' \
--max-allowed-files 806 \
"$(echo "${DIST_DIR}"/*)" || exit 1
fi
else
echo "skipping pydistcheck (does not support Python 3.${PY_MINOR_VER})"
fi
echo "done checking Python-package distributions"
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
# [description]
#
# Look for the last run of a given GitHub Actions workflow on a given branch.
# If there's never been one (as might be the case with optional workflows like valgrind),
# exit with 0.
#
# Otherwise, check the status of that latest run.
# If it wasn't successful, exit with a non-0 exit code.
#
# [usage]
#
# check-workflow-status.sh <BRANCH> <WORKFLOW_FILE>
#
# BRANCH: name of a branch involved in a pull request.
#
# WORKFLOW_FILE: filename (e.g. 'r_valgrind.yml') defining the GitHub Actions workflow.
#
set -e -u -o pipefail
BRANCH="${1}"
WORKFLOW_FILE="${2}"
PR_NUMBER="${3}"
# Limit how much data is pulled from the API and needs to be parsed locally.
OLDEST_ALLOWED_RUN_DATE=$(date --date='7 days ago' '+%F')
echo "Searching for latest run of '${WORKFLOW_FILE}' on branch '${BRANCH}' "
LATEST_RUN_ID=$(
gh run list \
--repo 'lightgbm-org/LightGBM' \
--event 'workflow_dispatch' \
--created ">= ${OLDEST_ALLOWED_RUN_DATE}" \
--workflow "${WORKFLOW_FILE}" \
--json 'createdAt,databaseId,name' \
--jq "sort_by(.createdAt) | reverse | map(select(.name | contains (\"pr=${PR_NUMBER}\"))) | .[0] | .databaseId"
)
if [[ "${LATEST_RUN_ID}" == "" ]]; then
echo "No runs of '${WORKFLOW_FILE}' found on branch from pull request ${PR_NUMBER} (on or after ${OLDEST_ALLOWED_RUN_DATE})."
exit 0
fi
echo "Checking status of workflow run '${LATEST_RUN_ID}'"
gh run view \
--repo "lightgbm-org/LightGBM" \
--exit-status \
"${LATEST_RUN_ID}"
+11
View File
@@ -0,0 +1,11 @@
# conda-envs
This directory contains files used to create `conda` environments for development
and testing of LightGBM.
The `.txt` files here are intended to be used with `conda create --file`.
For details on that, see the `conda` docs:
* `conda create` docs ([link](https://conda.io/projects/conda/en/latest/commands/create.html))
* "Managing Environments" ([link](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html))
+45
View File
@@ -0,0 +1,45 @@
# [description]
#
# Core dependencies used across most LightGBM continuous integration (CI) jobs.
#
# 'python' constraint is intentionally omitted, so this file can be reused across
# Python versions.
#
# These floors are not the oldest versions LightGBM supports... they're here just to make conda
# solves faster, and should generally be the latest versions that work for all CI jobs using this.
#
# [usage]
#
# conda create \
# --name test-env \
# --file ./.ci/conda-envs/ci-core.txt \
# python=3.10
#
# direct imports
cffi>=1.16
dask>=2025.4.0
joblib>=1.3.2
matplotlib-base>=3.7.3
narwhals>=1.15
numpy>=1.24.4
pandas>2.0
polars>=1.0.0
pyarrow-core>=16.0
python-graphviz>=0.20.3
scikit-learn>=1.3.2
scipy>=1.7
# starting some time around 2026, 'pip' is no longer guaranteed to be installed
pip>=24
# testing-only dependencies
cloudpickle>=3.0.0
psutil>=5.9.8
pytest>=8.1.1
# other recursive dependencies, just
# pinned here to help speed up solves
pluggy>=1.4.0
setuptools>=69.2
wheel>=0.43
+85
View File
@@ -0,0 +1,85 @@
# coding: utf-8
"""Script for generating files with NuGet package metadata."""
import datetime
import sys
from pathlib import Path
from shutil import copyfile
if __name__ == "__main__":
source = Path(sys.argv[1])
nuget_dir = Path(__file__).absolute().parent / "nuget"
print(f"Creating nuget directory '{nuget_dir}'")
linux_folder_path = nuget_dir / "runtimes" / "linux-x64" / "native"
linux_folder_path.mkdir(parents=True, exist_ok=True)
osx_folder_path = nuget_dir / "runtimes" / "osx-x64" / "native"
osx_folder_path.mkdir(parents=True, exist_ok=True)
windows_folder_path = nuget_dir / "runtimes" / "win-x64" / "native"
windows_folder_path.mkdir(parents=True, exist_ok=True)
build_folder_path = nuget_dir / "build"
build_folder_path.mkdir(parents=True, exist_ok=True)
print(f"Looking for libraries in '{source}'")
copyfile(source / "lib_lightgbm.so", linux_folder_path / "lib_lightgbm.so")
copyfile(source / "lib_lightgbm.dylib", osx_folder_path / "lib_lightgbm.dylib")
copyfile(source / "lib_lightgbm.dll", windows_folder_path / "lib_lightgbm.dll")
copyfile(source / "lightgbm.exe", windows_folder_path / "lightgbm.exe")
version = (nuget_dir.parents[1] / "VERSION.txt").read_text(encoding="utf-8").strip().replace("rc", "-rc")
print(f"Setting version to '{version}'")
nuget_str = rf"""<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>LightGBM</id>
<version>{version}</version>
<authors>Guolin Ke</authors>
<owners>Guolin Ke</owners>
<license type="expression">MIT</license>
<projectUrl>https://github.com/lightgbm-org/LightGBM</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A fast, distributed, high performance gradient boosting framework</description>
<copyright>Copyright {datetime.datetime.now().year} @ Microsoft</copyright>
<tags>machine-learning data-mining distributed native boosting gbdt</tags>
<dependencies> </dependencies>
</metadata>
<files>
<file src="build\**" target="build"/>
<file src="runtimes\**" target="runtimes"/>
</files>
</package>
"""
prop_str = r"""
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="Exists('packages.config') OR
Exists('$(MSBuildProjectName).packages.config') OR
Exists('packages.$(MSBuildProjectName).config')">
<Content Include="$(MSBuildThisFileDirectory)/../runtimes/win-x64/native/*.dll"
Condition="'$(PlatformTarget)' == 'x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildThisFileDirectory)/../runtimes/win-x64/native/*.exe"
Condition="'$(PlatformTarget)' == 'x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
</ItemGroup>
</Project>
"""
target_str = r"""
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<EnableLightGBMUnsupportedPlatformTargetCheck Condition="'$(EnableLightGBMUnsupportedPlatformTargetCheck)' == ''">true</EnableLightGBMUnsupportedPlatformTargetCheck>
</PropertyGroup>
<Target Name="_LightGBMCheckForUnsupportedPlatformTarget"
Condition="'$(EnableLightGBMUnsupportedPlatformTargetCheck)' == 'true'"
AfterTargets="_CheckForInvalidConfigurationAndPlatform">
<Error Condition="'$(PlatformTarget)' != 'x64' AND
('$(OutputType)' == 'Exe' OR '$(OutputType)'=='WinExe') AND
!('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND '$(PlatformTarget)' == '')"
Text="LightGBM currently supports 'x64' processor architectures. Please ensure your application is targeting 'x64'." />
</Target>
</Project>
"""
(nuget_dir / "LightGBM.nuspec").write_text(nuget_str, encoding="utf-8")
(nuget_dir / "build" / "LightGBM.props").write_text(prop_str, encoding="utf-8")
(nuget_dir / "build" / "LightGBM.targets").write_text(target_str, encoding="utf-8")
print("Done creating NuGet package")
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# [description]
# Collect and download artifacts from all workflow runs for a commit.
#
# [usage]
# ./download-artifacts.sh <COMMIT_ID>
#
set -e -u -E -o pipefail
COMMIT_ID="${1}"
OUTPUT_DIR="./release-artifacts"
get-latest-run-id() {
gh run list \
--repo "lightgbm-org/LightGBM" \
--commit "${1}" \
--workflow "${2}" \
--json 'createdAt,databaseId' \
--jq 'sort_by(.createdAt) | reverse | .[0] | .databaseId'
}
# ensure directory for storing artifacts exists
echo "preparing to download artifacts for commit '${COMMIT_ID}' to '${OUTPUT_DIR}'"
mkdir -p "${OUTPUT_DIR}"
# get core artifacts
echo "downloading core artifacts"
gh run download \
--repo "lightgbm-org/LightGBM" \
--dir "${OUTPUT_DIR}" \
"$(get-latest-run-id "${COMMIT_ID}" 'build.yml')"
echo "done downloading core artifacts"
# get python-package artifacts
echo "downloading python-package artifacts and NuGet package"
gh run download \
--repo "lightgbm-org/LightGBM" \
--dir "${OUTPUT_DIR}" \
"$(get-latest-run-id "${COMMIT_ID}" 'python_package.yml')"
echo "done downloading python-package artifacts and NuGet package"
# get R-package artifacts
echo "downloading R-package artifacts"
gh run download \
--repo "lightgbm-org/LightGBM" \
--dir "${OUTPUT_DIR}" \
"$(get-latest-run-id "${COMMIT_ID}" 'r_package.yml')"
echo "done downloading R-package artifacts"
# get SWIG artifacts
echo "downloading SWIG artifacts"
gh run download \
--repo "lightgbm-org/LightGBM" \
--dir "${OUTPUT_DIR}" \
"$(get-latest-run-id "${COMMIT_ID}" 'swig.yml')"
echo "done downloading SWIG artifacts"
# 'gh run download' unpackages into nested directories like {artifact-name}/{file}.
#
# This moves all files to the top level and then deletes those {artifact-name}/ directories,
# to make it easier to bulk upload all files to a release.
echo "flattening directory structure"
find "${OUTPUT_DIR}" -type f -mindepth 2 -exec mv -i '{}' "${OUTPUT_DIR}" \;
find "${OUTPUT_DIR}" -type d -mindepth 1 -exec rm -r '{}' \+
echo "downloaded artifacts:"
find "${OUTPUT_DIR}" -type f
+40
View File
@@ -0,0 +1,40 @@
Write-Output "Installing OpenCL CPU platform"
$installer = "AMD-APP-SDKInstaller-v3.0.130.135-GA-windows-F-x64.exe"
Write-Output "Downloading OpenCL platform installer"
$ProgressPreference = "SilentlyContinue" # progress bar bug extremely slows down download speed
$params = @{
OutFile = "$installer"
Uri = "https://github.com/lightgbm-org/LightGBM/releases/download/v2.0.12/$installer"
}
Invoke-WebRequest @params
if (Test-Path "$installer") {
Write-Output "Successfully downloaded OpenCL platform installer"
} else {
Write-Output "Unable to download OpenCL platform installer"
Write-Output "Setting EXIT"
$host.SetShouldExit(-1)
exit 1
}
# Install OpenCL platform from installer executable
Write-Output "Running OpenCL installer"
Invoke-Command -ScriptBlock {
Start-Process "$installer" -ArgumentList '/S /V"/quiet /norestart /passive /log opencl.log"' -Wait
}
$property = Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenCL\Vendors
if ($null -eq $property) {
Write-Output "Unable to install OpenCL CPU platform"
Write-Output "OpenCL installation log:"
Get-Content "opencl.log"
Write-Output "Setting EXIT"
$host.SetShouldExit(-1)
exit 1
} else {
Write-Output "Successfully installed OpenCL CPU platform"
Write-Output "Current OpenCL drivers:"
Write-Output $property
}
+125
View File
@@ -0,0 +1,125 @@
# Install R dependencies, using only base R.
#
# Supported arguments:
#
# --all Install all the 'Depends', 'Imports', 'LinkingTo', and 'Suggests' dependencies
# (automatically implies --build --test).
#
# --build Install the packages needed to build.
#
# --exclude=<pkg1,pkg2,...> Comma-delimited list of packages to NOT install.
#
# --include=<pkg1,pkg2,...> Comma-delimited list of additional packages to install.
# These will always be installed, unless also used in "--exclude".
#
# --test Install packages needed to run tests.
#
# [description] Parse command line arguments into an R list.
# Returns a list where keys are arguments and values
# are either TRUE (for flags) or a vector of values passed via a
# comma-delimited list.
.parse_args <- function(args) {
out <- list(
"--all" = FALSE
, "--build" = FALSE
, "--exclude" = character(0L)
, "--include" = character(0L)
, "--test" = FALSE
)
for (arg in args) {
parsed_arg <- unlist(strsplit(arg, "=", fixed = TRUE))
arg_name <- parsed_arg[[1L]]
if (!(arg_name %in% names(out))) {
stop(sprintf("Unrecognized argument: '%s'", arg_name))
}
if (length(parsed_arg) == 2L) {
# lists, like "--include=roxygen2,testthat"
values <- unlist(strsplit(parsed_arg[[2L]], ",", fixed = TRUE))
out[[arg_name]] <- values
} else {
# flags, like "--build"
out[[arg]] <- TRUE
}
}
return(out)
}
args <- .parse_args(
commandArgs(trailingOnly = TRUE)
)
# which dependencies to install
ALL_DEPS <- isTRUE(args[["--all"]])
BUILD_DEPS <- ALL_DEPS || isTRUE(args[["--build"]])
TEST_DEPS <- ALL_DEPS || isTRUE(args[["--test"]])
# force downloading of binary packages on macOS
COMPILE_FROM_SOURCE <- "both"
PACKAGE_TYPE <- getOption("pkgType")
# CRAN has precompiled binaries for macOS and Windows... prefer those,
# for faster installation.
if (Sys.info()[["sysname"]] == "Darwin" || .Platform$OS.type == "windows") {
COMPILE_FROM_SOURCE <- "never"
PACKAGE_TYPE <- "binary"
}
options(
install.packages.check.source = "no"
, install.packages.compile.from.source = COMPILE_FROM_SOURCE
)
# always use the same CRAN mirror
CRAN_MIRROR <- Sys.getenv("CRAN_MIRROR", unset = "https://cran.r-project.org")
# we always want these
deps_to_install <- c(
"data.table"
, "jsonlite"
, "Matrix"
, "R6"
)
if (isTRUE(BUILD_DEPS)) {
deps_to_install <- c(
deps_to_install
, "knitr"
, "markdown"
)
}
if (isTRUE(TEST_DEPS)) {
deps_to_install <- c(
deps_to_install
, "RhpcBLASctl"
, "testthat"
)
}
# add packages passed through '--include'
deps_to_install <- unique(c(
deps_to_install
, args[["--include"]]
))
# remove packages passed through '--exclude'
deps_to_install <- setdiff(
x = deps_to_install
, args[["--exclude"]]
)
msg <- sprintf(
"[install-r-deps] installing R packages: %s\n"
, toString(sort(deps_to_install))
)
cat(msg)
install.packages( # nolint: undesirable_function.
pkgs = deps_to_install
, dependencies = c("Depends", "Imports", "LinkingTo")
, lib = Sys.getenv("R_LIB_PATH", unset = .libPaths()[[1L]])
, repos = CRAN_MIRROR
, type = PACKAGE_TYPE
, Ncpus = parallel::detectCores()
)
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -e -E -u -o pipefail
pwsh -command "Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -SkipPublisherCheck"
echo "Linting PowerShell code"
pwsh -file ./.ci/lint-powershell.ps1 || exit 1
conda create -q -y -n test-env \
"python=3.14[build=*_cp*]" \
'pre-commit>=3.8.0' \
'r-lintr>=3.3.0'
# shellcheck disable=SC1091
source activate test-env
echo "Running pre-commit checks"
pre-commit run --all-files || exit 1
echo "Linting R code"
Rscript ./.ci/lint-r-code.R "$(pwd)" || exit 1
+87
View File
@@ -0,0 +1,87 @@
$ErrorActionPreference = 'Stop'
$settings = @{
Severity = @(
'Information',
'Warning',
'Error'
)
IncludeDefaultRules = $true
ExcludeRules = @(
'PSAvoidUsingInvokeExpression'
)
# Additional rules that are disabled by default.
#
# Some of the skips could be replaced with inline comments if PSScriptAnalyzer
# supports that in the future (https://github.com/PowerShell/PSScriptAnalyzer/issues/849).
Rules = @{
PSAvoidExclaimOperator = @{
Enable = $true
}
PSAvoidLongLines = @{
Enable = $true
MaximumLineLength = 120
}
PSAvoidSemicolonsAsLineTerminators = @{
Enable = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NoEmptyLineBefore = $true
IgnoreOneLineBlock = $true
NewLineAfter = $false
}
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSUseConsistentIndentation = @{
Enable = $true
IndentationSize = 4
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
Kind = 'space'
}
PSUseConsistentWhitespace = @{
Enable = $true
CheckInnerBrace = $true
CheckOpenBrace = $true
CheckOpenParen = $true
CheckOperator = $true
CheckSeparator = $true
CheckPipe = $true
CheckPipeForRedundantWhitespace = $true
CheckParameter = $true
IgnoreAssignmentOperatorInsideHashTable = $false
}
PSUseCorrectCasing = @{
Enable = $true
}
}
}
# this pre-listing of files can be removed whenever PSScriptAnalyzer adds support for exclusions.
#
# see:
#
# * https://github.com/PowerShell/PSScriptAnalyzer/issues/561
# * https://github.com/PowerShell/vscode-powershell/issues/3048
#
# lint-powershell.ps1 itself is included here because linting this script itself
# sometimes fails (non-deterministically!) with an error like "Object reference not set to an instance of an object"
#
$files = @(
Get-ChildItem -Path ./ -Recurse -Force -Filter '*.ps1' |
Where-Object { $_.FullName -notmatch '[/\\]bin[/\\]' } |
Where-Object { $_.FullName -notmatch '[/\\]external_libs[/\\]' } |
Where-Object { $_.FullName -notmatch '[/\\]\.pixi[/\\]' } |
Where-Object { $_.FullName -notmatch '[/\\]venv[/\\]' } |
Where-Object { $_.Name -ne 'lint-powershell.ps1' } |
ForEach-Object { $_.FullName }
)
foreach ($file in $files) {
Write-Output "linting '$file'"
Invoke-ScriptAnalyzer -Path $file -EnableExit -Settings $settings
}
+154
View File
@@ -0,0 +1,154 @@
loadNamespace("lintr")
args <- commandArgs(
trailingOnly = TRUE
)
SOURCE_DIR <- args[[1L]]
FILES_TO_LINT <- list.files(
path = SOURCE_DIR
, pattern = "\\.r$|\\.rmd$"
, all.files = TRUE
, ignore.case = TRUE
, full.names = TRUE
, recursive = TRUE
, include.dirs = FALSE
)
# text to use for pipe operators from packages like 'magrittr'
pipe_text <- paste0(
"For consistency and the sake of being explicit, this project's code "
, "does not use the pipe operator."
)
# text to use for functions that should only be called interactively
interactive_text <- paste0(
"Functions like '?', 'help', and 'install.packages()' should only be used "
, "interactively, not in package code."
)
LINTERS_TO_USE <- list(
"absolute_path" = lintr::absolute_path_linter()
, "any_duplicated" = lintr::any_duplicated_linter()
, "any_is_na" = lintr::any_is_na_linter()
, "assignment" = lintr::assignment_linter()
, "backport" = lintr::backport_linter()
, "boolean_arithmetic" = lintr::boolean_arithmetic_linter()
, "braces" = lintr::brace_linter()
, "class_equals" = lintr::class_equals_linter()
, "commas" = lintr::commas_linter()
, "conjunct_test" = lintr::conjunct_test_linter()
, "duplicate_argument" = lintr::duplicate_argument_linter()
, "empty_assignment" = lintr::empty_assignment_linter()
, "equals_na" = lintr::equals_na_linter()
, "fixed_regex" = lintr::fixed_regex_linter()
, "for_loop_index" = lintr::for_loop_index_linter()
, "function_left" = lintr::function_left_parentheses_linter()
, "function_return" = lintr::function_return_linter()
, "implicit_assignment" = lintr::implicit_assignment_linter()
, "implicit_integers" = lintr::implicit_integer_linter()
, "infix_spaces" = lintr::infix_spaces_linter()
, "inner_combine" = lintr::inner_combine_linter()
, "is_numeric" = lintr::is_numeric_linter()
, "lengths" = lintr::lengths_linter()
, "length_levels" = lintr::length_levels_linter()
, "length_test" = lintr::length_test_linter()
, "line_length" = lintr::line_length_linter(length = 120L)
, "literal_coercion" = lintr::literal_coercion_linter()
, "matrix" = lintr::matrix_apply_linter()
, "missing_argument" = lintr::missing_argument_linter()
, "non_portable_path" = lintr::nonportable_path_linter()
, "numeric_leading_zero" = lintr::numeric_leading_zero_linter()
, "outer_negation" = lintr::outer_negation_linter()
, "package_hooks" = lintr::package_hooks_linter()
, "paren_body" = lintr::paren_body_linter()
, "paste" = lintr::paste_linter()
, "quotes" = lintr::quotes_linter()
, "redundant_equals" = lintr::redundant_equals_linter()
, "regex_subset" = lintr::regex_subset_linter()
, "routine_registration" = lintr::routine_registration_linter()
, "scalar_in" = lintr::scalar_in_linter()
, "semicolon" = lintr::semicolon_linter()
, "seq" = lintr::seq_linter()
, "spaces_inside" = lintr::spaces_inside_linter()
, "spaces_left_parens" = lintr::spaces_left_parentheses_linter()
, "sprintf" = lintr::sprintf_linter()
, "string_boundary" = lintr::string_boundary_linter()
, "todo_comments" = lintr::todo_comment_linter(c("todo", "fixme", "to-do"))
, "trailing_blank" = lintr::trailing_blank_lines_linter()
, "trailing_white" = lintr::trailing_whitespace_linter()
, "true_false" = lintr::T_and_F_symbol_linter()
, "undesirable_function" = lintr::undesirable_function_linter(
fun = c(
"cbind" = paste0(
"cbind is an unsafe way to build up a data frame. merge() or direct "
, "column assignment is preferred."
)
, "dyn.load" = "Directly loading or unloading .dll or .so files in package code should not be necessary."
, "dyn.unload" = "Directly loading or unloading .dll or .so files in package code should not be necessary."
, "help" = interactive_text
, "ifelse" = "The use of ifelse() is dangerous because it will silently allow mixing types."
, "install.packages" = interactive_text
, "is.list" = paste0(
"This project uses data.table, and is.list(x) is TRUE for a data.table. "
, "identical(class(x), 'list') is a safer way to check that something is an R list object."
)
, "rbind" = "data.table::rbindlist() is faster and safer than rbind(), and is preferred in this project."
, "require" = paste0(
"library() is preferred to require() because it will raise an error immediately "
, "if a package is missing."
)
)
)
, "undesirable_operator" = lintr::undesirable_operator_linter(
op = c(
"%>%" = pipe_text
, "%.%" = pipe_text
, "%..%" = pipe_text
, "|>" = pipe_text
, "?" = interactive_text
, "??" = interactive_text
)
)
, "unnecessary_concatenation" = lintr::unnecessary_concatenation_linter()
, "unnecessary_lambda" = lintr::unnecessary_lambda_linter()
, "unreachable_code" = lintr::unreachable_code_linter()
, "unused_import" = lintr::unused_import_linter()
, "vector_logic" = lintr::vector_logic_linter()
, "whitespace" = lintr::whitespace_linter()
)
noquote(paste0(length(FILES_TO_LINT), " R files need linting"))
results <- NULL
for (r_file in FILES_TO_LINT) {
this_result <- lintr::lint(
filename = r_file
, linters = LINTERS_TO_USE
, cache = FALSE
)
print(
sprintf(
"Found %i linting errors in %s"
, length(this_result)
, r_file
)
, quote = FALSE
)
results <- c(results, this_result)
}
issues_found <- length(results)
noquote(paste0("Total linting issues found: ", issues_found))
if (issues_found > 0L) {
print(results)
}
quit(save = "no", status = issues_found)
+408
View File
@@ -0,0 +1,408 @@
# coding: utf-8
"""Helper script for generating config file and parameters list.
This script generates LightGBM/src/io/config_auto.cpp file
with list of all parameters, aliases table and other routines
along with parameters description in LightGBM/docs/Parameters.rst file
from the information in LightGBM/include/LightGBM/config.h file.
"""
import re
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Tuple
def get_parameter_infos(config_hpp: Path) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]:
"""Parse config header file.
Parameters
----------
config_hpp : pathlib.Path
Path to the config header file.
Returns
-------
infos : tuple
Tuple with names and content of sections.
"""
is_inparameter = False
cur_key = None
key_lvl = 0
cur_info: Dict[str, List] = {}
keys = []
member_infos: List[List[Dict[str, List]]] = []
with open(config_hpp) as config_hpp_file:
for line in config_hpp_file:
if line.strip() in {"#ifndef __NVCC__", "#endif // __NVCC__"}:
continue
if "#pragma region Parameters" in line:
is_inparameter = True
elif "#pragma region" in line and "Parameters" in line:
key_lvl += 1
cur_key = line.split("region")[1].strip()
keys.append((cur_key, key_lvl))
member_infos.append([])
elif "#pragma endregion" in line:
key_lvl -= 1
if cur_key is not None:
cur_key = None
elif is_inparameter:
is_inparameter = False
elif cur_key is not None:
line = line.strip()
if line.startswith("//"):
key, _, val = line[2:].partition("=")
key = key.strip()
val = val.strip()
if key not in cur_info:
if key == "descl2" and "desc" not in cur_info:
cur_info["desc"] = []
elif key != "descl2":
cur_info[key] = []
if key == "desc":
cur_info["desc"].append(("l1", val))
elif key == "descl2":
cur_info["desc"].append(("l2", val))
else:
cur_info[key].append(val)
elif line:
has_eqsgn = False
tokens = line.split("=")
if len(tokens) == 2:
if "default" not in cur_info:
cur_info["default"] = [tokens[1][:-1].strip()]
has_eqsgn = True
tokens = line.split()
cur_info["inner_type"] = [tokens[0].strip()]
if "name" not in cur_info:
if has_eqsgn:
cur_info["name"] = [tokens[1].strip()]
else:
cur_info["name"] = [tokens[1][:-1].strip()]
member_infos[-1].append(cur_info)
cur_info = {}
return keys, member_infos
def get_names(infos: List[List[Dict[str, List]]]) -> List[str]:
"""Get names of all parameters.
Parameters
----------
infos : list
Content of the config header file.
Returns
-------
names : list
Names of all parameters.
"""
names = []
for x in infos:
for y in x:
names.append(y["name"][0])
return names
def get_alias(infos: List[List[Dict[str, List]]]) -> List[Tuple[str, str]]:
"""Get aliases of all parameters.
Parameters
----------
infos : list
Content of the config header file.
Returns
-------
pairs : list
List of tuples (param alias, param name).
"""
pairs = []
for x in infos:
for y in x:
if "alias" in y:
name = y["name"][0]
alias = y["alias"][0].split(",")
for name2 in alias:
pairs.append((name2.strip(), name))
return pairs
def parse_check(check: str, reverse: bool = False) -> Tuple[str, str]:
"""Parse the constraint.
Parameters
----------
check : str
String representation of the constraint.
reverse : bool, optional (default=False)
Whether to reverse the sign of the constraint.
Returns
-------
pair : tuple
Parsed constraint in the form of tuple (value, sign).
"""
try:
idx = 1
float(check[idx:])
except ValueError:
idx = 2
float(check[idx:])
if reverse:
reversed_sign = {"<": ">", ">": "<", "<=": ">=", ">=": "<="}
return check[idx:], reversed_sign[check[:idx]]
else:
return check[idx:], check[:idx]
def set_one_var_from_string(name: str, param_type: str, checks: List[str]) -> str:
"""Construct code for auto config file for one param value.
Parameters
----------
name : str
Name of the parameter.
param_type : str
Type of the parameter.
checks : list
Constraints of the parameter.
Returns
-------
ret : str
Lines of auto config file with getting and checks of one parameter value.
"""
ret = ""
univar_mapper = {"int": "GetInt", "double": "GetDouble", "bool": "GetBool", "std::string": "GetString"}
if "vector" not in param_type:
ret += f' {univar_mapper[param_type]}(params, "{name}", &{name});\n'
if len(checks) > 0:
check_mapper = {"<": "LT", ">": "GT", "<=": "LE", ">=": "GE"}
for check in checks:
value, sign = parse_check(check)
ret += f" CHECK_{check_mapper[sign]}({name}, {value});\n"
ret += "\n"
else:
ret += f' if (GetString(params, "{name}", &tmp_str)) {{\n'
type2 = param_type.split("<")[1][:-1]
if type2 == "std::string":
ret += f" {name} = Common::Split(tmp_str.c_str(), ',');\n"
else:
ret += f" {name} = Common::StringToArray<{type2}>(tmp_str, ',');\n"
ret += " }\n\n"
return ret
def gen_parameter_description(
sections: List[Tuple[str, int]], descriptions: List[List[Dict[str, List]]], params_rst: Path
) -> None:
"""Write descriptions of parameters to the documentation file.
Parameters
----------
sections : list
Names of parameters sections.
descriptions : list
Structured descriptions of parameters.
params_rst : pathlib.Path
Path to the file with parameters documentation.
"""
params_to_write = []
lvl_mapper = {1: "-", 2: "~"}
for (section_name, section_lvl), section_params in zip(sections, descriptions, strict=True):
heading_sign = lvl_mapper[section_lvl]
params_to_write.append(f"{section_name}\n{heading_sign * len(section_name)}")
for param_desc in section_params:
name = param_desc["name"][0]
default_raw = param_desc["default"][0]
default = default_raw.strip('"') if len(default_raw.strip('"')) > 0 else default_raw
param_type = param_desc.get("type", param_desc["inner_type"])[0].split(":")[-1].split("<")[-1].strip(">")
options = param_desc.get("options", [])
if len(options) > 0:
opts = "``, ``".join([x.strip() for x in options[0].split(",")])
options_str = f", options: ``{opts}``"
else:
options_str = ""
aliases = param_desc.get("alias", [])
if len(aliases) > 0:
aliases_joined = "``, ``".join([x.strip() for x in aliases[0].split(",")])
aliases_str = f", aliases: ``{aliases_joined}``"
else:
aliases_str = ""
checks = sorted(param_desc.get("check", []))
checks_len = len(checks)
if checks_len > 1:
number1, sign1 = parse_check(checks[0])
number2, sign2 = parse_check(checks[1], reverse=True)
checks_str = f", constraints: ``{number2} {sign2} {name} {sign1} {number1}``"
elif checks_len == 1:
number, sign = parse_check(checks[0])
checks_str = f", constraints: ``{name} {sign} {number}``"
else:
checks_str = ""
main_desc = f'- ``{name}`` :raw-html:`<a id="{name}" title="Permalink to this parameter" href="#{name}">&#x1F517;&#xFE0E;</a>`, default = ``{default}``, type = {param_type}{options_str}{aliases_str}{checks_str}'
params_to_write.append(main_desc)
params_to_write.extend([f"{' ' * 3 * int(desc[0][-1])}- {desc[1]}" for desc in param_desc["desc"]])
with open(params_rst) as original_params_file:
all_lines = original_params_file.read()
before, start_sep, _ = all_lines.partition(".. start params list\n\n")
_, end_sep, after = all_lines.partition("\n\n.. end params list")
with open(params_rst, "w") as new_params_file:
new_params_file.write(before)
new_params_file.write(start_sep)
new_params_file.write("\n\n".join(params_to_write))
new_params_file.write(end_sep)
new_params_file.write(after)
def gen_parameter_code(
config_hpp: Path, config_out_cpp: Path
) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]:
"""Generate auto config file.
Parameters
----------
config_hpp : pathlib.Path
Path to the config header file.
config_out_cpp : pathlib.Path
Path to the auto config file.
Returns
-------
infos : tuple
Tuple with names and content of sections.
"""
keys, infos = get_parameter_infos(config_hpp)
names = get_names(infos)
alias = get_alias(infos)
names_with_aliases = defaultdict(list)
str_to_write = r"""/*!
* Copyright (c) 2018-2026 Microsoft Corporation. All rights reserved.
* Copyright (c) 2018-2026 The LightGBM developers. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*
* \note
* This file is auto generated by LightGBM\.ci\parameter-generator.py from LightGBM\include\LightGBM\config.h file.
*/
"""
str_to_write += "#include <LightGBM/config.h>\n\n"
str_to_write += "#include <string>\n"
str_to_write += "#include <unordered_map>\n"
str_to_write += "#include <unordered_set>\n"
str_to_write += "#include <vector>\n\n"
str_to_write += "namespace LightGBM {\n"
# alias table
str_to_write += "const std::unordered_map<std::string, std::string>& Config::alias_table() {\n"
str_to_write += " static std::unordered_map<std::string, std::string> aliases({\n"
for pair in alias:
str_to_write += f' {{"{pair[0]}", "{pair[1]}"}},\n'
names_with_aliases[pair[1]].append(pair[0])
str_to_write += " });\n"
str_to_write += " return aliases;\n"
str_to_write += "}\n\n"
# names
str_to_write += "const std::unordered_set<std::string>& Config::parameter_set() {\n"
str_to_write += " static std::unordered_set<std::string> params({\n"
for name in names:
str_to_write += f' "{name}",\n'
str_to_write += " });\n"
str_to_write += " return params;\n"
str_to_write += "}\n\n"
# from strings
str_to_write += "void Config::GetMembersFromString(const std::unordered_map<std::string, std::string>& params) {\n"
str_to_write += ' std::string tmp_str = "";\n'
for x in infos:
for y in x:
if "[no-automatically-extract]" in y:
continue
param_type = y["inner_type"][0]
name = y["name"][0]
checks = []
if "check" in y:
checks = y["check"]
tmp = set_one_var_from_string(name, param_type, checks)
str_to_write += tmp
# tails
str_to_write = f"{str_to_write.strip()}\n}}\n\n"
str_to_write += "std::string Config::SaveMembersToString() const {\n"
str_to_write += " std::stringstream str_buf;\n"
for x in infos:
for y in x:
if "[no-save]" in y:
continue
param_type = y["inner_type"][0]
name = y["name"][0]
if "vector" in param_type:
if "int8" in param_type:
str_to_write += f' str_buf << "[{name}: " << Common::Join(Common::ArrayCast<int8_t, int>({name}), ",") << "]\\n";\n'
else:
str_to_write += f' str_buf << "[{name}: " << Common::Join({name}, ",") << "]\\n";\n'
else:
str_to_write += f' str_buf << "[{name}: " << {name} << "]\\n";\n'
# tails
str_to_write += " return str_buf.str();\n"
str_to_write += "}\n\n"
str_to_write += """const std::unordered_map<std::string, std::vector<std::string>>& Config::parameter2aliases() {
static std::unordered_map<std::string, std::vector<std::string>> map({"""
for name in names:
str_to_write += '\n {"' + name + '", '
if names_with_aliases[name]:
str_to_write += '{"' + '", "'.join(names_with_aliases[name]) + '"}},'
else:
str_to_write += "{}},"
str_to_write += """
});
return map;
}
"""
str_to_write += """const std::unordered_map<std::string, std::string>& Config::ParameterTypes() {
static std::unordered_map<std::string, std::string> map({"""
int_t_pat = re.compile(r"int\d+_t")
# the following are stored as comma separated strings but are arrays in the wrappers
overrides = {
"categorical_feature": "vector<int>",
"ignore_column": "vector<int>",
"interaction_constraints": "vector<vector<int>>",
}
for x in infos:
for y in x:
name = y["name"][0]
if name == "task":
continue
if name in overrides:
param_type = overrides[name]
else:
param_type = int_t_pat.sub("int", y["inner_type"][0]).replace("std::", "")
str_to_write += '\n {"' + name + '", "' + param_type + '"},'
str_to_write += """
});
return map;
}
"""
str_to_write += "} // namespace LightGBM\n"
with open(config_out_cpp, "w") as config_out_cpp_file:
config_out_cpp_file.write(str_to_write)
return keys, infos
if __name__ == "__main__":
current_dir = Path(__file__).absolute().parent
config_hpp = current_dir.parent / "include" / "LightGBM" / "config.h"
config_out_cpp = current_dir.parent / "src" / "io" / "config_auto.cpp"
params_rst = current_dir.parent / "docs" / "Parameters.rst"
sections, descriptions = gen_parameter_code(config_hpp, config_out_cpp)
gen_parameter_description(sections, descriptions, params_rst)
+24
View File
@@ -0,0 +1,24 @@
# nightlies for: matplotlib, numpy, pandas, pyarrow, scipy, scikit-learn
--extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
# runtime dependencies (using `.dev0` suffix to allow nightlies)
#
# pinning rules:
#
# * latest versions of lightgbm's dependencies,
# * including pre-releases and nightlies
#
cffi>=2.0.0
matplotlib>=3.11.0.dev0
narwhals>=2.21.0.dev0
numpy>=2.5.0.dev0
pandas>=3.1.0.dev0
polars>=1.40.1.dev0
pyarrow>=24.0.0.dev0
scikit-learn>=1.9.dev0
scipy>=1.18.0.dev0
# testing-only dependencies
cloudpickle>=3.1.2
psutil>=7.2.2
pytest>=9.0.2
+15
View File
@@ -0,0 +1,15 @@
# oldest versions of dependencies published after
# minimum supported Python version's first release,
# for which there are wheels compatible with the
# python:{version} image
#
# see https://devguide.python.org/versions/
#
cffi==1.15.1
narwhals==1.15.0
numpy==1.22.4
pandas==1.3.4
polars==1.0.0
pyarrow==16.0.0
scikit-learn==1.0.2
scipy==1.7.2
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
#
# [description]
# Rerun specified workflow for given pull request.
#
# [usage]
# rerun-workflow.sh <WORKFLOW_ID> <PR_BRANCH>
#
# WORKFLOW_ID: Identifier (config name of ID) of a workflow to be rerun.
#
# PR_BRANCH: Name of pull request's branch.
set -e -E -u -o pipefail
if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit 1
fi
if [ $# -ne 2 ]; then
echo "Usage: $0 <WORKFLOW_ID> <PR_BRANCH>"
exit 1
fi
workflow_id=$1
pr_branch=$2
# --branch for some GitHub GLI commands does not respect the difference between forks and branches
# on the main repo. While some parts of the GitHub API refer to the branch of a workflow
# as '{org}:{branch}' for branches from forks, others expect only '{branch}'.
#
# This expansion trims a leading '{org}:' from 'pr_branch' if one is present.
pr_branch_no_fork_prefix="${pr_branch/*:/}"
RUN_ID=$(
gh run list \
--repo 'lightgbm-org/LightGBM' \
--workflow "${workflow_id}" \
--event "pull_request" \
--branch "${pr_branch_no_fork_prefix}" \
--json 'createdAt,databaseId' \
--jq 'sort_by(.createdAt) | reverse | .[0] | .databaseId'
)
if [[ -z "${RUN_ID}" ]]; then
echo "ERROR: failed to find a run of workflow '${workflow_id}' for branch '${pr_branch}'"
exit 1
fi
echo "Re-running workflow '${workflow_id}' (run ID ${RUN_ID})"
gh run rerun \
--repo 'lightgbm-org/LightGBM' \
"${RUN_ID}"
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
set -e -u -o pipefail
PKG_TARBALL="${1}"
declare -i ALLOWED_CHECK_NOTES=${2}
# 'R CMD check' redirects installation logs to a file, and returns
# a non-0 exit code if ERRORs are raised.
#
# The '||' here gives us an opportunity to echo out the installation
# logs prior to exiting the script.
check_succeeded="yes"
R CMD check "${PKG_TARBALL}" \
--as-cran \
--run-donttest \
|| check_succeeded="no"
CHECK_LOG_FILE=lightgbm.Rcheck/00check.log
BUILD_LOG_FILE=lightgbm.Rcheck/00install.out
echo "R CMD check build logs:"
cat "${BUILD_LOG_FILE}"
if [[ $check_succeeded == "no" ]]; then
echo "R CMD check failed"
exit 1
fi
# WARNINGs or ERRORs should be treated as a failure
if grep -q -E "WARNING|ERROR" "${CHECK_LOG_FILE}"; then
echo "WARNINGs or ERRORs have been found by R CMD check"
exit 1
fi
# Allow a configurable number of NOTEs.
# Sometimes NOTEs are raised in CI that wouldn't show up on an actual CRAN submission.
set +e
NUM_CHECK_NOTES=$(
grep -o -E '[0-9]+ NOTE' "${CHECK_LOG_FILE}" \
| sed 's/[^0-9]*//g'
)
if [[ ${NUM_CHECK_NOTES} -gt ${ALLOWED_CHECK_NOTES} ]]; then
echo "Found ${NUM_CHECK_NOTES} NOTEs from R CMD check. Only ${ALLOWED_CHECK_NOTES} are allowed"
exit 1
fi
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
#
# [description]
# Set a status with a given name to the specified commit.
#
# [usage]
# set-commit-status.sh <NAME> <STATUS> <SHA>
#
# NAME: Name of status.
# Status with existing name overwrites a previous one.
#
# STATUS: Status to be set.
# Can be "error", "failure", "pending" or "success".
#
# SHA: SHA of a commit to set a status on.
set -e -E -u -o pipefail
if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit 1
fi
if [ $# -ne 3 ]; then
echo "Usage: $0 <NAME> <STATUS> <SHA>"
exit 1
fi
name=$1
status=$2
status=${status/error/failure}
status=${status/cancelled/failure}
status=${status/timed_out/failure}
status=${status/in_progress/pending}
status=${status/queued/pending}
sha=$3
data=$(
jq -n \
--arg state "${status}" \
--arg url "${GITHUB_SERVER_URL}/lightgbm-org/LightGBM/actions/runs/${GITHUB_RUN_ID}" \
--arg name "${name}" \
'{"state":$state,"target_url":$url,"context":$name}'
)
curl -sL \
--fail \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-d "$data" \
"${GITHUB_API_URL}/repos/lightgbm-org/LightGBM/statuses/$sha"
Executable
+192
View File
@@ -0,0 +1,192 @@
#!/bin/bash
set -e -E -u -o pipefail
# defaults
IN_UBUNTU_BASE_CONTAINER=${IN_UBUNTU_BASE_CONTAINER:-"false"}
SETUP_CONDA=${SETUP_CONDA:-"true"}
ARCH=$(uname -m)
if [[ $OS_NAME == "macos" ]]; then
# Check https://github.com/actions/runner-images/tree/main/images/macos for available
# versions of Xcode
macos_ver=$(sw_vers --productVersion)
if [[ "${macos_ver}" =~ 15. ]]; then
xcode_path="/Applications/Xcode_16.0.app/Contents/Developer"
else
xcode_path="/Applications/Xcode_15.0.app/Contents/Developer"
fi
sudo xcode-select -s "${xcode_path}" || exit 1
if [[ $COMPILER == "clang" ]]; then
brew install libomp
else # gcc
brew install 'gcc@14'
fi
if [[ $TASK == "mpi" ]]; then
brew install open-mpi
fi
if [[ $TASK == "swig" ]]; then
brew install swig
fi
else # Linux
if type -f apt > /dev/null 2>&1; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
ca-certificates \
curl
else
sudo yum update -y
sudo yum install -y \
ca-certificates \
curl
fi
CMAKE_VERSION="3.30.0"
curl -O -L \
"https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" \
|| exit 1
sudo mkdir /opt/cmake || exit 1
sudo sh "cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" --skip-license --prefix=/opt/cmake || exit 1
sudo ln -sf /opt/cmake/bin/cmake /usr/local/bin/cmake || exit 1
if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then
# fixes error "unable to initialize frontend: Dialog"
# https://github.com/moby/moby/issues/27988#issuecomment-462809153
echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
software-properties-common
sudo apt-get install --no-install-recommends -y \
build-essential \
git \
libcurl4 \
libicu-dev \
libssl-dev \
locales \
locales-all || exit 1
if [[ $COMPILER == "clang" ]]; then
sudo apt-get install --no-install-recommends -y \
clang \
libomp-dev
elif [[ $COMPILER == "clang-17" ]]; then
sudo apt-get install --no-install-recommends -y \
wget
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo apt-add-repository deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main
sudo apt-add-repository deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main
sudo apt-get update
sudo apt-get install -y \
clang-17 \
libomp-17-dev
fi
export LANG="en_US.UTF-8"
sudo update-locale LANG=${LANG}
export LC_ALL="${LANG}"
fi
if [[ $TASK == "r-package" ]] && [[ $COMPILER == "clang" ]]; then
sudo apt-get install --no-install-recommends -y \
libomp-dev
fi
if [[ $TASK == "mpi" ]]; then
if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
libopenmpi-dev \
openmpi-bin
else # in manylinux image
sudo yum update -y
sudo yum install -y \
openmpi-devel \
|| exit 1
fi
fi
if [[ $TASK == "gpu" ]]; then
if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
libboost1.74-dev \
libboost-filesystem1.74-dev \
ocl-icd-opencl-dev
else # in manylinux image
sudo yum update -y
sudo yum install -y \
boost-devel \
ocl-icd-devel \
opencl-headers \
|| exit 1
fi
fi
if [[ $TASK == "gpu" || $TASK == "bdist" ]]; then
if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
patchelf \
pocl-opencl-icd
elif [[ $(uname -m) == "x86_64" ]]; then
sudo yum update -y
sudo yum install -y \
ocl-icd-devel \
opencl-headers \
|| exit 1
fi
fi
if [[ $TASK == "cuda" ]]; then
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
apt-get update
APT_INSTALL_PACKAGES=()
# only re-install NCCL if there wasn't one already pre-installed in the image
# (for example, nvidia/cuda 12.9.1 images excluded it)
if ! apt list --installed | grep -E 'libnccl\-dev' >/dev/null 2>&1; then
echo "libnccl-dev not found, manually installing it"
# find a compatible libnccl-dev for this CUDA version
#
# (just 'apt-get install libnccl-dev' can result in mixing across CUDA versions)
IFS='.' read -r CUDA_MAJOR CUDA_MINOR _ <<< "${CI_CUDA_VERSION}"
NCCL_VERSION=$(
apt-cache madison libnccl-dev \
| awk -F'|' '{print $2}' \
| tr -d ' ' \
| grep "+cuda${CUDA_MAJOR}.${CUDA_MINOR}$" \
| sort -V \
| tail -1
)
APT_INSTALL_PACKAGES+=(
"libnccl-dev=${NCCL_VERSION}"
"libnccl2=${NCCL_VERSION}"
)
else
echo "libnccl-dev already installed"
fi
if [[ $COMPILER == "clang" ]]; then
APT_INSTALL_PACKAGES+=(
clang
libomp-dev
)
fi
apt-get install --no-install-recommends -y \
"${APT_INSTALL_PACKAGES[@]}"
fi
fi
if [[ "${TASK}" != "cpp-tests" ]] && [[ "${TASK}" != "r-package" ]] && [[ "${TASK}" != "swig" ]]; then
if [[ $SETUP_CONDA != "false" ]]; then
curl \
-sL \
-o miniforge.sh \
"https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-${ARCH}.sh"
sh miniforge.sh -b -p "${CONDA}"
fi
conda config --set always_yes yes --set changeps1 no
conda update -q -y conda
# print output of 'conda info', to help in submitting bug reports
echo "conda info:"
conda info
fi
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -e -E -u -o pipefail
echo "installing lightgbm and its dependencies"
pip install \
--prefer-binary \
--upgrade \
-r ./.ci/pip-envs/requirements-latest.txt \
dist/*.whl
echo "installed package versions:"
pip freeze
echo ""
echo "running tests"
pytest tests/c_api_test/
pytest tests/python_package_test/
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
set -e -E -u -o pipefail
echo "installing lightgbm and its dependencies"
pip install \
--prefer-binary \
--upgrade \
--constraint ./.ci/pip-envs/requirements-oldest.txt \
"$(echo dist/*.whl)[arrow,pandas,scikit-learn]"
echo "installed package versions:"
pip freeze
echo ""
echo "checking that examples run without error"
# run a few examples to test that Python-package minimally works
echo ""
echo "--- advanced_example.py ---"
echo ""
python ./examples/python-guide/advanced_example.py || exit 1
echo ""
echo "--- logistic_regression.py ---"
echo ""
python ./examples/python-guide/logistic_regression.py || exit 1
echo ""
echo "--- simple_example.py ---"
echo ""
python ./examples/python-guide/simple_example.py || exit 1
echo ""
echo "--- sklearn_example.py ---"
echo ""
python ./examples/python-guide/sklearn_example.py || exit 1
echo ""
echo "checking that imports work without any optional dependencies"
pip uninstall --yes \
cffi \
dask \
distributed \
graphviz \
joblib \
matplotlib \
pandas \
psutil \
pyarrow \
scikit-learn
python -c "import lightgbm"
echo ""
echo "done testing on oldest supported Python version"
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
set -e -E -u -o pipefail
RDscriptvalgrind ./.ci/install-r-deps.R --test || exit 1
sh build-cran-package.sh \
--r-executable=RDvalgrind \
--no-build-vignettes \
|| exit 1
RDvalgrind CMD INSTALL --preclean --install-tests lightgbm_*.tar.gz || exit 1
cd R-package/tests
ALL_LOGS_FILE="out.log"
VALGRIND_LOGS_FILE="valgrind-logs.log"
RDvalgrind \
--no-readline \
--vanilla \
-d "valgrind --tool=memcheck --leak-check=full --track-origins=yes --gen-suppressions=all" \
-f testthat.R \
> ${ALL_LOGS_FILE} 2>&1 || exit 1
cat ${ALL_LOGS_FILE}
echo "writing valgrind output to ${VALGRIND_LOGS_FILE}"
cat ${ALL_LOGS_FILE} | grep -E "^\=" > ${VALGRIND_LOGS_FILE}
bytes_definitely_lost=$(
cat ${VALGRIND_LOGS_FILE} \
| grep -E "definitely lost\: .*" \
| sed 's/^.*definitely lost\: \(.*\) bytes.*$/\1/' \
| tr -d ","
)
echo "valgrind found ${bytes_definitely_lost} bytes definitely lost"
if [[ ${bytes_definitely_lost} -gt 0 ]]; then
exit 1
fi
bytes_indirectly_lost=$(
cat ${VALGRIND_LOGS_FILE} \
| grep -E "indirectly lost\: .*" \
| sed 's/^.*indirectly lost\: \(.*\) bytes.*$/\1/' \
| tr -d ","
)
echo "valgrind found ${bytes_indirectly_lost} bytes indirectly lost"
if [[ ${bytes_indirectly_lost} -gt 0 ]]; then
exit 1
fi
# one error caused by a false positive between valgrind and openmp is allowed
# ==2063== 352 bytes in 1 blocks are possibly lost in loss record 153 of 2,709
# ==2063== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
# ==2063== by 0x40149CA: allocate_dtv (dl-tls.c:286)
# ==2063== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532)
# ==2063== by 0x5702322: allocate_stack (allocatestack.c:622)
# ==2063== by 0x5702322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660)
# ==2063== by 0x56D0DDA: ??? (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
# ==2063== by 0x56C88E0: GOMP_parallel (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
# ==2063== by 0x1544D29C: LGBM_DatasetCreateFromCSC (c_api.cpp:1286)
# ==2063== by 0x1546F980: LGBM_DatasetCreateFromCSC_R (lightgbm_R.cpp:91)
# ==2063== by 0x4941E2F: R_doDotCall (dotcode.c:634)
# ==2063== by 0x494CCC6: do_dotcall (dotcode.c:1281)
# ==2063== by 0x499FB01: bcEval (eval.c:7078)
# ==2063== by 0x498B67F: Rf_eval (eval.c:727)
# ==2063== by 0x498E414: R_execClosure (eval.c:1895)
bytes_possibly_lost=$(
cat ${VALGRIND_LOGS_FILE} \
| grep -E "possibly lost\: .*" \
| sed 's/^.*possibly lost\: \(.*\) bytes.*$/\1/' \
| tr -d ","
)
echo "valgrind found ${bytes_possibly_lost} bytes possibly lost"
if [[ ${bytes_possibly_lost} -gt 1104 ]]; then
exit 1
fi
# ensure 'grep --count' doesn't cause failures
set +e
echo "checking for invalid reads"
invalid_reads=$(
cat ${VALGRIND_LOGS_FILE} \
| grep --count -i "Invalid read"
)
if [[ ${invalid_reads} -gt 0 ]]; then
echo "valgrind found invalid reads: ${invalid_reads}"
exit 1
fi
echo "checking for invalid writes"
invalid_writes=$(
cat ${VALGRIND_LOGS_FILE} \
| grep --count -i "Invalid write"
)
if [[ ${invalid_writes} -gt 0 ]]; then
echo "valgrind found invalid writes: ${invalid_writes}"
exit 1
fi
+337
View File
@@ -0,0 +1,337 @@
# Download a file and retry upon failure. This looks like
# an infinite loop but CI-level timeouts will kill it
function Get-File-With-Tenacity {
param(
[Parameter(Mandatory = $true)][string]$url,
[Parameter(Mandatory = $true)][string]$destfile
)
$ProgressPreference = "SilentlyContinue" # progress bar bug extremely slows down download speed
do {
Write-Output "Downloading ${url}"
sleep 5
Invoke-WebRequest -Uri $url -OutFile $destfile
} while (-not $?)
}
# External utilities like R.exe / Rscript.exe writing to stderr (even for harmless
# status information) can cause failures in GitHub Actions PowerShell jobs.
# See https://github.community/t/powershell-steps-fail-nondeterministically/115496
#
# Using standard PowerShell redirection does not work to avoid these errors.
# This function uses R's built-in redirection mechanism, sink(). Any place where
# this function is used is a command that writes harmless messages to stderr
function Invoke-R-Code-Redirect-Stderr {
param(
[Parameter(Mandatory = $true)][string]$rcode
)
$decorated_code = "out_file <- file(tempfile(), open = 'wt'); sink(out_file, type = 'message'); $rcode; sink()"
Rscript --vanilla -e $decorated_code
}
# Remove all items matching some pattern from PATH environment variable
function Remove-From-Path {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)][string]$pattern_to_remove
)
if ($PSCmdlet.ShouldProcess($env:PATH, "Removing ${pattern_to_remove}")) {
$env:PATH = ($env:PATH.Split(';') | Where-Object { $_ -notmatch "$pattern_to_remove" }) -join ';'
}
}
# remove some details that exist in the GitHub Actions images which might
# cause conflicts with R and other components installed by this script
$env:RTOOLS40_HOME = ""
Remove-From-Path ".*Amazon.*"
Remove-From-Path ".*Anaconda.*"
Remove-From-Path ".*android.*"
Remove-From-Path ".*Android.*"
Remove-From-Path ".*chocolatey.*"
Remove-From-Path ".*Chocolatey.*"
Remove-From-Path ".*cmake.*"
Remove-From-Path ".*CMake.*"
Remove-From-Path ".*\\Git\\.*"
Remove-From-Path "(?!.*pandoc.*).*hostedtoolcache.*"
Remove-From-Path ".*Microsoft SDKs.*"
Remove-From-Path ".*mingw.*"
Remove-From-Path ".*msys64.*"
Remove-From-Path ".*PostgreSQL.*"
Remove-From-Path ".*\\R\\.*"
Remove-From-Path ".*R Client.*"
Remove-From-Path ".*rtools40.*"
Remove-From-Path ".*rtools42.*"
Remove-From-Path ".*rtools43.*"
Remove-From-Path ".*rtools44.*"
Remove-From-Path ".*rtools45.*"
Remove-From-Path ".*shells.*"
Remove-From-Path ".*Strawberry.*"
Remove-From-Path ".*tools.*"
Remove-Item C:\rtools40 -Force -Recurse -ErrorAction Ignore
Remove-Item C:\rtools42 -Force -Recurse -ErrorAction Ignore
Remove-Item C:\rtools43 -Force -Recurse -ErrorAction Ignore
Remove-Item C:\rtools44 -Force -Recurse -ErrorAction Ignore
Remove-Item C:\rtools45 -Force -Recurse -ErrorAction Ignore
# Get details needed for installing R components
#
# NOTES:
# * some paths and file names are different on R4.0
$env:R_MAJOR_VERSION = $env:R_VERSION.split('.')[0]
if ($env:R_MAJOR_VERSION -eq "4") {
$RTOOLS_INSTALL_PATH = "C:\rtools43"
$env:RTOOLS_BIN = "$RTOOLS_INSTALL_PATH\usr\bin"
$env:RTOOLS_MINGW_BIN = "$RTOOLS_INSTALL_PATH\x86_64-w64-mingw32.static.posix\bin"
$env:RTOOLS_EXE_FILE = "rtools43-5550-5548.exe"
$env:R_WINDOWS_VERSION = "4.3.1"
} else {
Write-Output "[ERROR] Unrecognized R version: $env:R_VERSION"
Assert-Output $false
}
$env:CMAKE_VERSION = "3.30.0"
$env:R_LIB_PATH = "$env:BUILD_SOURCESDIRECTORY/RLibrary" -replace '[\\]', '/'
$env:R_LIBS = "$env:R_LIB_PATH"
$env:CMAKE_PATH = "$env:BUILD_SOURCESDIRECTORY/CMake_installation"
$env:PATH = @(
"$env:RTOOLS_BIN",
"$env:RTOOLS_MINGW_BIN",
"$env:R_LIB_PATH/R/bin/x64",
"$env:CMAKE_PATH/cmake-$env:CMAKE_VERSION-windows-x86_64/bin",
"$env:PATH"
) -join ";"
$env:CRAN_MIRROR = "https://cran.rstudio.com"
$env:MIKTEX_EXCEPTION_PATH = "$env:TEMP\miktex"
# don't fail builds for long-running examples unless they're very long.
# See https://github.com/lightgbm-org/LightGBM/issues/4049#issuecomment-793412254.
if ($env:R_BUILD_TYPE -ne "cran") {
$env:_R_CHECK_EXAMPLE_TIMING_THRESHOLD_ = 30
}
if (($env:COMPILER -eq "MINGW") -and ($env:R_BUILD_TYPE -eq "cmake")) {
$env:CXX = "$env:RTOOLS_MINGW_BIN/g++.exe"
$env:CC = "$env:RTOOLS_MINGW_BIN/gcc.exe"
}
Set-Location "$env:BUILD_SOURCESDIRECTORY"
tzutil /s "GMT Standard Time"
[Void][System.IO.Directory]::CreateDirectory("$env:R_LIB_PATH")
[Void][System.IO.Directory]::CreateDirectory("$env:CMAKE_PATH")
# download R, RTools and CMake
Write-Output "Downloading R, Rtools and CMake"
$params = @{
url = "$env:CRAN_MIRROR/bin/windows/base/old/$env:R_WINDOWS_VERSION/R-$env:R_WINDOWS_VERSION-win.exe"
destfile = "R-win.exe"
}
Get-File-With-Tenacity @params
$params = @{
url = "https://github.com/lightgbm-org/LightGBM/releases/download/v2.0.12/$env:RTOOLS_EXE_FILE"
destfile = "Rtools.exe"
}
Get-File-With-Tenacity @params
$params = @{
url = "https://github.com/Kitware/CMake/releases/download/v{0}/cmake-{0}-windows-x86_64.zip" -f $env:CMAKE_VERSION
destfile = "$env:CMAKE_PATH/cmake.zip"
}
Get-File-With-Tenacity @params
# Install R
Write-Output "Installing R"
$params = @{
FilePath = "R-win.exe"
NoNewWindow = $true
Wait = $true
ArgumentList = "/VERYSILENT /DIR=$env:R_LIB_PATH/R /COMPONENTS=main,x64,i386"
}
Start-Process @params ; Assert-Output $?
Write-Output "Done installing R"
Write-Output "Installing Rtools"
$params = @{
FilePath = "Rtools.exe"
NoNewWindow = $true
Wait = $true
ArgumentList = "/VERYSILENT /SUPPRESSMSGBOXES /DIR=$RTOOLS_INSTALL_PATH"
}
Start-Process @params; Assert-Output $?
Write-Output "Done installing Rtools"
Write-Output "Installing CMake"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("$env:CMAKE_PATH/cmake.zip", "$env:CMAKE_PATH") ; Assert-Output $?
# Remove old CMake shipped with RTools
Remove-Item "$env:RTOOLS_MINGW_BIN/cmake.exe" -Force -ErrorAction Ignore
Write-Output "Done installing CMake"
Write-Output "Installing dependencies"
Rscript.exe --vanilla ".ci/install-r-deps.R" --build --include=processx --test ; Assert-Output $?
Write-Output "Building R-package"
# R CMD check is not used for MSVC builds
if ($env:COMPILER -ne "MSVC") {
$PKG_FILE_NAME = "lightgbm_$env:LGB_VER.tar.gz"
$LOG_FILE_NAME = "lightgbm.Rcheck/00check.log"
if ($env:R_BUILD_TYPE -eq "cmake") {
if ($env:TOOLCHAIN -eq "MINGW") {
Write-Output "Telling R to use MinGW"
$env:BUILD_R_FLAGS = "c('--skip-install', '--use-mingw', '-j4')"
} elseif ($env:TOOLCHAIN -eq "MSYS") {
Write-Output "Telling R to use MSYS"
$env:BUILD_R_FLAGS = "c('--skip-install', '--use-msys2', '-j4')"
} elseif ($env:TOOLCHAIN -eq "MSVC") {
$env:BUILD_R_FLAGS = "'--skip-install'"
} else {
Write-Output "[ERROR] Unrecognized toolchain: $env:TOOLCHAIN"
Assert-Output $false
}
Invoke-R-Code-Redirect-Stderr "commandArgs <- function(...){$env:BUILD_R_FLAGS}; source('build_r.R')"
Assert-Output $?
} elseif ($env:R_BUILD_TYPE -eq "cran") {
$params = -join @(
"result <- processx::run(command = 'sh', args = 'build-cran-package.sh', ",
"echo = TRUE, windows_verbatim_args = FALSE, error_on_status = TRUE)"
)
Invoke-R-Code-Redirect-Stderr $params ; Assert-Output $?
Remove-From-Path ".*msys64.*"
# Test CRAN source .tar.gz in a directory that is not this repo or below it.
# When people install.packages('lightgbm'), they won't have the LightGBM
# git repo around. This is to protect against the use of relative paths
# like ../../CMakeLists.txt that would only work if you are in the repoo
$R_CMD_CHECK_DIR = "tmp-r-cmd-check"
New-Item -Path "C:\" -Name $R_CMD_CHECK_DIR -ItemType "directory" > $null
Move-Item -Path "$PKG_FILE_NAME" -Destination "C:\$R_CMD_CHECK_DIR\" > $null
Set-Location "C:\$R_CMD_CHECK_DIR\"
}
Write-Output "Running R CMD check"
if ($env:R_BUILD_TYPE -eq "cran") {
# CRAN packages must pass without --no-multiarch (build on 64-bit and 32-bit)
$check_args = "c('CMD', 'check', '--as-cran', '--run-donttest', '$PKG_FILE_NAME')"
} else {
$check_args = "c('CMD', 'check', '--no-multiarch', '--as-cran', '--run-donttest', '$PKG_FILE_NAME')"
}
$params = -join (
"result <- processx::run(command = 'R.exe', args = $check_args, ",
"echo = TRUE, windows_verbatim_args = FALSE, error_on_status = TRUE)"
)
Invoke-R-Code-Redirect-Stderr $params ; $check_succeeded = $?
Write-Output "R CMD check build logs:"
$INSTALL_LOG_FILE_NAME = "lightgbm.Rcheck\00install.out"
Get-Content -Path "$INSTALL_LOG_FILE_NAME"
Assert-Output $check_succeeded
Write-Output "Looking for issues with R CMD check results"
if (Get-Content "$LOG_FILE_NAME" | Select-String -Pattern "NOTE|WARNING|ERROR" -CaseSensitive -Quiet) {
Write-Output "NOTEs, WARNINGs, or ERRORs have been found by R CMD check"
Assert-Output $False
}
} else {
$INSTALL_LOG_FILE_NAME = "$env:BUILD_SOURCESDIRECTORY\00install_out.txt"
Invoke-R-Code-Redirect-Stderr "source('build_r.R')" 1> $INSTALL_LOG_FILE_NAME ; $install_succeeded = $?
Write-Output "----- build and install logs -----"
Get-Content -Path "$INSTALL_LOG_FILE_NAME"
Write-Output "----- end of build and install logs -----"
Assert-Output $install_succeeded
# some errors are not raised above, but can be found in the logs
if (Get-Content "$INSTALL_LOG_FILE_NAME" | Select-String -Pattern "ERROR" -CaseSensitive -Quiet) {
Write-Output "ERRORs have been found installing lightgbm"
Assert-Output $False
}
}
# Checking that the correct R version was used
if ($env:TOOLCHAIN -ne "MSVC") {
$checks = Select-String -Path "${LOG_FILE_NAME}" -Pattern "using R version $env:R_WINDOWS_VERSION"
$checks_cnt = $checks.Matches.length
} else {
$checksParams = @{
Path = "${INSTALL_LOG_FILE_NAME}"
Pattern = "R version passed into FindLibR.* $env:R_WINDOWS_VERSION"
}
$checks = Select-String @checksParams
$checks_cnt = $checks.Matches.length
}
if ($checks_cnt -eq 0) {
Write-Output "Wrong R version was found (expected '$env:R_WINDOWS_VERSION'). Check the build logs."
Assert-Output $False
}
# Checking that we actually got the expected compiler. The R-package has some logic
# to fail back to MinGW if MSVC fails, but for CI builds we need to check that the correct
# compiler was used.
if ($env:R_BUILD_TYPE -eq "cmake") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "Check for working CXX compiler.*$env:COMPILER"
if ($checks.Matches.length -eq 0) {
Write-Output "The wrong compiler was used. Check the build logs."
Assert-Output $False
}
}
# Checking that we got the right toolchain for MinGW. If using MinGW, both
# MinGW and MSYS toolchains are supported
if (($env:COMPILER -eq "MINGW") -and ($env:R_BUILD_TYPE -eq "cmake")) {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "Trying to build with.*$env:TOOLCHAIN"
if ($checks.Matches.length -eq 0) {
Write-Output "The wrong toolchain was used. Check the build logs."
Assert-Output $False
}
}
# Checking that MM_PREFETCH preprocessor definition is actually used in CI builds.
if ($env:R_BUILD_TYPE -eq "cran") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "checking whether MM_PREFETCH work.*yes"
$checks_cnt = $checks.Matches.length
} elseif ($env:TOOLCHAIN -ne "MSVC") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern ".*Performing Test MM_PREFETCH - Success"
$checks_cnt = $checks.Matches.length
} else {
$checks_cnt = 1
}
if ($checks_cnt -eq 0) {
Write-Output "MM_PREFETCH preprocessor definition wasn't used. Check the build logs."
Assert-Output $False
}
# Checking that MM_MALLOC preprocessor definition is actually used in CI builds.
if ($env:R_BUILD_TYPE -eq "cran") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "checking whether MM_MALLOC work.*yes"
$checks_cnt = $checks.Matches.length
} elseif ($env:TOOLCHAIN -ne "MSVC") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern ".*Performing Test MM_MALLOC - Success"
$checks_cnt = $checks.Matches.length
} else {
$checks_cnt = 1
}
if ($checks_cnt -eq 0) {
Write-Output "MM_MALLOC preprocessor definition wasn't used. Check the build logs."
Assert-Output $False
}
# Checking that OpenMP is actually used in CMake builds.
if ($env:R_BUILD_TYPE -eq "cmake") {
$checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern ".*Found OpenMP: TRUE.*"
if ($checks.Matches.length -eq 0) {
Write-Output "OpenMP wasn't found. Check the build logs."
Assert-Output $False
}
}
if ($env:COMPILER -eq "MSVC") {
Write-Output "Running tests with testthat.R"
Set-Location R-package/tests
# NOTE: using Rscript.exe intentionally here, instead of Invoke-R-Code-Redirect-Stderr,
# because something about the interaction between Invoke-R-Code-Redirect-Stderr
# and testthat results in failing tests not exiting with a non-0 exit code.
Rscript.exe --vanilla "testthat.R" ; Assert-Output $?
}
Write-Output "No issues were found checking the R-package"
+260
View File
@@ -0,0 +1,260 @@
#!/bin/bash
set -e -E -u -o pipefail
# defaults
ARCH=$(uname -m)
# set up R environment
export CRAN_MIRROR="https://cran.rstudio.com"
export R_LIB_PATH=~/Rlib
mkdir -p $R_LIB_PATH
export R_LIBS=$R_LIB_PATH
export PATH="$R_LIB_PATH/R/bin:$PATH"
# don't fail builds for long-running examples unless they're very long.
# See https://github.com/lightgbm-org/LightGBM/issues/4049#issuecomment-793412254.
if [[ $R_BUILD_TYPE != "cran" ]]; then
export _R_CHECK_EXAMPLE_TIMING_THRESHOLD_=30
fi
# Get details needed for installing R components
R_MAJOR_VERSION="${R_VERSION%.*}"
if [[ "${R_MAJOR_VERSION}" == "4" ]]; then
export R_MAC_VERSION=4.3.1
export R_MAC_PKG_URL=${CRAN_MIRROR}/bin/macosx/big-sur-${ARCH}/base/R-${R_MAC_VERSION}-${ARCH}.pkg
export R_LINUX_VERSION="4.3.1-1.2204.0"
export R_APT_REPO="jammy-cran40/"
else
echo "Unrecognized R version: ${R_VERSION}"
exit 1
fi
# installing precompiled R for Ubuntu
# https://cran.r-project.org/bin/linux/ubuntu/#installation
# adding steps from https://stackoverflow.com/a/56378217/3986677 to get latest version
#
# `devscripts` is required for 'checkbashisms' (https://github.com/r-lib/actions/issues/111)
if [[ $OS_NAME == "linux" ]]; then
mkdir -p ~/.gnupg
echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf
sudo apt-key adv \
--homedir ~/.gnupg \
--keyserver keyserver.ubuntu.com \
--recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 || exit 1
sudo add-apt-repository \
"deb ${CRAN_MIRROR}/bin/linux/ubuntu ${R_APT_REPO}" || exit 1
sudo apt-get update
sudo apt-get install \
--no-install-recommends \
-y \
devscripts \
libuv1-dev \
r-base-core=${R_LINUX_VERSION} \
r-base-dev=${R_LINUX_VERSION} \
texinfo \
texlive-latex-extra \
texlive-latex-recommended \
texlive-fonts-recommended \
texlive-fonts-extra \
tidy \
qpdf \
|| exit 1
if [[ $R_BUILD_TYPE == "cran" ]]; then
sudo apt-get install \
--no-install-recommends \
-y \
"autoconf=$(cat R-package/AUTOCONF_UBUNTU_VERSION)" \
automake \
|| exit 1
fi
fi
# Installing R precompiled for Mac OS 10.11 or higher
if [[ $OS_NAME == "macos" ]]; then
brew update-reset --auto-update
brew update --auto-update
if [[ $R_BUILD_TYPE == "cran" ]]; then
brew install automake || exit 1
fi
brew install \
checkbashisms \
qpdf || exit 1
brew install basictex || exit 1
export PATH="/Library/TeX/texbin:$PATH"
sudo tlmgr --verify-repo=none update --self || exit 1
sudo tlmgr --verify-repo=none install inconsolata helvetic rsfs || exit 1
curl -sL "${R_MAC_PKG_URL}" -o R.pkg || exit 1
sudo installer \
-pkg "$(pwd)/R.pkg" \
-target / || exit 1
# install tidy v5.8.0
# ref: https://groups.google.com/g/r-sig-mac/c/7u_ivEj4zhM
TIDY_URL=https://github.com/htacg/tidy-html5/releases/download/5.8.0/tidy-5.8.0-macos-x86_64+arm64.pkg
curl -sL ${TIDY_URL} -o tidy.pkg
sudo installer \
-pkg "$(pwd)/tidy.pkg" \
-target /
# ensure that this newer version of 'tidy' is used by 'R CMD check'
# ref: https://cran.r-project.org/doc/manuals/R-exts.html#Checking-packages
export R_TIDYCMD=/usr/local/bin/tidy
fi
# {Matrix} needs {lattice}, so this needs to run before manually installing {Matrix}.
# This should be unnecessary on R >=4.4.0
# ref: https://github.com/lightgbm-org/LightGBM/issues/6433
Rscript --vanilla -e "install.packages('lattice', repos = '${CRAN_MIRROR}', lib = '${R_LIB_PATH}')"
# manually install {Matrix}, as {Matrix}=1.7-0 raised its R floor all the way to R 4.4.0
# ref: https://github.com/lightgbm-org/LightGBM/issues/6433
Rscript --vanilla -e "install.packages('https://cran.r-project.org/src/contrib/Archive/Matrix/Matrix_1.6-5.tar.gz', repos = NULL, lib = '${R_LIB_PATH}')"
# Manually install dependencies to avoid a CI-time dependency on devtools (for devtools::install_deps())
Rscript --vanilla ./.ci/install-r-deps.R --build --test --exclude=Matrix || exit 1
cd "${BUILD_DIRECTORY}"
PKG_TARBALL="lightgbm_$(head -1 VERSION.txt).tar.gz"
BUILD_LOG_FILE="lightgbm.Rcheck/00install.out"
LOG_FILE_NAME="lightgbm.Rcheck/00check.log"
if [[ $R_BUILD_TYPE == "cmake" ]]; then
Rscript build_r.R -j4 --skip-install || exit 1
elif [[ $R_BUILD_TYPE == "cran" ]]; then
# on Linux, we recreate configure in CI to test if
# a change in a PR has changed configure.ac
if [[ $OS_NAME == "linux" ]]; then
./R-package/recreate-configure.sh
num_files_changed=$(
git diff --name-only | wc -l
)
if [[ ${num_files_changed} -gt 0 ]]; then
echo "'configure' in the R-package has changed. Please recreate it and commit the changes."
echo "Changed files:"
git diff --compact-summary
echo "See R-package/README.md for details on how to recreate this script."
echo ""
exit 1
fi
fi
./build-cran-package.sh || exit 1
# Test CRAN source .tar.gz in a directory that is not this repo or below it.
# When people install.packages('lightgbm'), they won't have the LightGBM
# git repo around. This is to protect against the use of relative paths
# like ../../CMakeLists.txt that would only work if you are in the repo
R_CMD_CHECK_DIR="${HOME}/tmp-r-cmd-check/"
mkdir -p "${R_CMD_CHECK_DIR}"
mv "${PKG_TARBALL}" "${R_CMD_CHECK_DIR}"
cd "${R_CMD_CHECK_DIR}"
fi
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
cp "${PKG_TARBALL}" "${BUILD_ARTIFACTSTAGINGDIRECTORY}/lightgbm-${LGB_VER}-r-cran.tar.gz"
fi
declare -i allowed_notes=0
bash "${BUILD_DIRECTORY}/.ci/run-r-cmd-check.sh" \
"${PKG_TARBALL}" \
"${allowed_notes}"
# ensure 'grep --count' doesn't cause failures
set +e
used_correct_r_version=$(
cat $LOG_FILE_NAME \
| grep --count "using R version ${R_VERSION}"
)
if [[ $used_correct_r_version -ne 1 ]]; then
echo "Unexpected R version was used. Expected '${R_VERSION}'."
exit 1
fi
if [[ $R_BUILD_TYPE == "cmake" ]]; then
passed_correct_r_version_to_cmake=$(
cat $BUILD_LOG_FILE \
| grep --count "R version passed into FindLibR.cmake: ${R_VERSION}"
)
if [[ $passed_correct_r_version_to_cmake -ne 1 ]]; then
echo "Unexpected R version was passed into cmake. Expected '${R_VERSION}'."
exit 1
fi
fi
# this check makes sure that CI builds of the package actually use OpenMP
if [[ $OS_NAME == "macos" ]] && [[ $R_BUILD_TYPE == "cran" ]]; then
omp_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E "checking whether OpenMP will work .*yes"
)
elif [[ $R_BUILD_TYPE == "cmake" ]]; then
omp_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E ".*Found OpenMP: TRUE.*"
)
else
omp_working=1
fi
if [[ $omp_working -ne 1 ]]; then
echo "OpenMP was not found"
exit 1
fi
# this check makes sure that CI builds of the package
# actually use MM_PREFETCH preprocessor definition
#
# _mm_prefetch will not work on arm64 architecture
# ref: https://github.com/lightgbm-org/LightGBM/issues/4124
if [[ $ARCH != "arm64" ]]; then
if [[ $R_BUILD_TYPE == "cran" ]]; then
mm_prefetch_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E "checking whether MM_PREFETCH work.*yes"
)
else
mm_prefetch_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E ".*Performing Test MM_PREFETCH - Success"
)
fi
if [[ $mm_prefetch_working -ne 1 ]]; then
echo "MM_PREFETCH test was not passed"
exit 1
fi
fi
# this check makes sure that CI builds of the package
# actually use MM_MALLOC preprocessor definition
if [[ $R_BUILD_TYPE == "cran" ]]; then
mm_malloc_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E "checking whether MM_MALLOC work.*yes"
)
else
mm_malloc_working=$(
cat $BUILD_LOG_FILE \
| grep --count -E ".*Performing Test MM_MALLOC - Success"
)
fi
if [[ $mm_malloc_working -ne 1 ]]; then
echo "MM_MALLOC test was not passed"
exit 1
fi
# this check makes sure that no "warning: unknown pragma ignored" logs
# reach the user leading them to believe that something went wrong
if [[ $R_BUILD_TYPE == "cran" ]]; then
pragma_warning_present=$(
cat $BUILD_LOG_FILE \
| grep --count -E "warning: unknown pragma ignored"
)
if [[ $pragma_warning_present -ne 0 ]]; then
echo "Unknown pragma warning is present, pragmas should have been removed before build"
exit 1
fi
fi
+190
View File
@@ -0,0 +1,190 @@
function Assert-Output {
param( [Parameter(Mandatory = $true)][bool]$success )
if (-not $success) {
$host.SetShouldExit(-1)
exit 1
}
}
$env:LGB_VER = (Get-Content $env:BUILD_SOURCESDIRECTORY\VERSION.txt).trim()
# Use custom temp directory to avoid
# > warning MSB8029: The Intermediate directory or Output directory cannot reside under the Temporary directory
# > as it could lead to issues with incremental build.
# And make sure this directory is always clean
$env:TMPDIR = "$env:USERPROFILE\tmp"
Remove-Item $env:TMPDIR -Force -Recurse -ErrorAction Ignore
[Void][System.IO.Directory]::CreateDirectory($env:TMPDIR)
# create the artifact upload directory if it doesn't exist yet
[Void][System.IO.Directory]::CreateDirectory($env:BUILD_ARTIFACTSTAGINGDIRECTORY)
if ($env:TASK -eq "r-package") {
& .\.ci\test-r-package-windows.ps1 ; Assert-Output $?
exit 0
}
if ($env:TASK -eq "cpp-tests") {
cmake -B build -S . -DBUILD_CPP_TEST=ON -DUSE_DEBUG=ON -A x64
cmake --build build --target testlightgbm --config Debug ; Assert-Output $?
.\Debug\testlightgbm.exe ; Assert-Output $?
exit 0
}
if ($env:TASK -eq "swig") {
$env:JAVA_HOME = $env:JAVA_HOME_8_X64 # there is pre-installed Eclipse Temurin 8 somewhere
$ProgressPreference = "SilentlyContinue" # progress bar bug extremely slows down download speed
$params = @{
Uri = "https://sourceforge.net/projects/swig/files/latest/download"
OutFile = "$env:BUILD_SOURCESDIRECTORY/swig/swigwin.zip"
UserAgent = "curl"
}
Invoke-WebRequest @params
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory(
"$env:BUILD_SOURCESDIRECTORY/swig/swigwin.zip",
"$env:BUILD_SOURCESDIRECTORY/swig"
) ; Assert-Output $?
$SwigFolder = Get-ChildItem -Name -Path "$env:BUILD_SOURCESDIRECTORY/swig" -Attributes Directory
$env:PATH = @("$env:BUILD_SOURCESDIRECTORY/swig/$SwigFolder", "$env:PATH") -join ";"
$BuildLogFileName = "$env:BUILD_SOURCESDIRECTORY\cmake_build.log"
cmake -B build -S . -A x64 -DUSE_SWIG=ON *> "$BuildLogFileName" ; $build_succeeded = $?
Write-Output "CMake build logs:"
Get-Content -Path "$BuildLogFileName"
Assert-Output $build_succeeded
$checks = Select-String -Path "${BuildLogFileName}" -Pattern "-- Found SWIG.*${SwigFolder}/swig.exe"
$checks_cnt = $checks.Matches.length
if ($checks_cnt -eq 0) {
Write-Output "Wrong SWIG version was found (expected '${SwigFolder}'). Check the build logs."
Assert-Output $False
}
cmake --build build --target ALL_BUILD --config Release ; Assert-Output $?
if ($env:PRODUCES_ARTIFACTS -eq "true") {
cp ./build/lightgbmlib.jar $env:BUILD_ARTIFACTSTAGINGDIRECTORY/lightgbmlib_win.jar ; Assert-Output $?
}
exit 0
}
# 'pixi' is used for end-of-life Python versions
if ($env:PYTHON_VERSION -eq "3.10") {
$activation = ((& pixi shell-hook --locked -e py310 --shell powershell) -join "`n")
Invoke-Expression $activation ; Assert-Output $?
} else {
# update conda env
$env:CONDA_ENV = "test-env"
conda activate ; Assert-Output $?
conda config --set always_yes yes --set changeps1 no ; Assert-Output $?
conda config --remove channels defaults ; Assert-Output $?
conda config --add channels nodefaults ; Assert-Output $?
conda config --add channels conda-forge ; Assert-Output $?
conda config --set channel_priority strict ; Assert-Output $?
conda install -q -y conda "python=$env:PYTHON_VERSION[build=*_cp*]" ; Assert-Output $?
# print output of 'conda info', to help in submitting bug reports
Write-Output "conda info:"
conda info
$env:CONDA_REQUIREMENT_FILE = "$env:BUILD_SOURCESDIRECTORY/.ci/conda-envs/ci-core.txt"
$condaParams = @(
"-y",
"-n", "$env:CONDA_ENV",
"--file", "$env:CONDA_REQUIREMENT_FILE",
"python=$env:PYTHON_VERSION[build=*_cp*]"
)
conda create @condaParams ; Assert-Output $?
# print output of 'conda list', to help in submitting bug reports
Write-Output "conda list:"
conda list -n $env:CONDA_ENV
# 'bdist' job invokes 'RefreshEnv' to update PATH from the registry (which may have been modified
# by building in OpenCL support), so defer activating the conda environment until later for those builds.
if ($env:TASK -ne "bdist") {
conda activate $env:CONDA_ENV
}
}
Set-Location "$env:BUILD_SOURCESDIRECTORY"
if ($env:TASK -eq "regular") {
cmake -B build -S . -A x64 ; Assert-Output $?
cmake --build build --target ALL_BUILD --config Release ; Assert-Output $?
sh ./build-python.sh install --precompile ; Assert-Output $?
cp ./Release/lib_lightgbm.dll "$env:BUILD_ARTIFACTSTAGINGDIRECTORY"
cp ./Release/lightgbm.exe "$env:BUILD_ARTIFACTSTAGINGDIRECTORY"
} elseif ($env:TASK -eq "sdist") {
sh ./build-python.sh sdist ; Assert-Output $?
sh ./.ci/check-python-dists.sh ./dist ; Assert-Output $?
Set-Location dist; pip install --no-deps @(Get-ChildItem *.gz) -v ; Assert-Output $?
} elseif ($env:TASK -eq "bdist") {
# Import the Chocolatey profile module so that the RefreshEnv command
# invoked below properly updates the current PowerShell session environment.
$module = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
Import-Module "$module" ; Assert-Output $?
RefreshEnv
Write-Output "Current OpenCL drivers:"
Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenCL\Vendors
# (re-) activate conda environment, in case any activation logic was overridden by that 'RefreshEnv' call above
conda activate $env:CONDA_ENV
# TODO: restore --integrated-opencl as part of https://github.com/lightgbm-org/LightGBM/issues/6968
sh "build-python.sh" bdist_wheel ; Assert-Output $?
sh ./.ci/check-python-dists.sh ./dist ; Assert-Output $?
Set-Location dist; pip install --no-deps @(Get-ChildItem *py3-none-win_amd64.whl) ; Assert-Output $?
cp @(Get-ChildItem *py3-none-win_amd64.whl) "$env:BUILD_ARTIFACTSTAGINGDIRECTORY"
} elseif (($env:APPVEYOR -eq "true") -and ($env:TASK -eq "python")) {
if ($env:COMPILER -eq "MINGW") {
sh ./build-python.sh install --mingw ; Assert-Output $?
} else {
sh ./build-python.sh install; Assert-Output $?
}
}
if (($env:TASK -eq "sdist") -or (($env:APPVEYOR -eq "true") -and ($env:TASK -eq "python"))) {
# cannot test C API with "sdist" task
$tests = "$env:BUILD_SOURCESDIRECTORY/tests/python_package_test"
} else {
$tests = "$env:BUILD_SOURCESDIRECTORY/tests"
}
if ($env:TASK -eq "bdist") {
# Make sure we can do both CPU and GPU; see tests/python_package_test/test_dual.py
# TODO: set LIGHTGBM_TEST_DUAL_CPU_GPU back to "1" as part of https://github.com/lightgbm-org/LightGBM/issues/6968
$env:LIGHTGBM_TEST_DUAL_CPU_GPU = "0"
}
pytest -ra $tests ; Assert-Output $?
if (($env:TASK -eq "regular") -or (($env:APPVEYOR -eq "true") -and ($env:TASK -eq "python"))) {
Set-Location "$env:BUILD_SOURCESDIRECTORY/examples/python-guide"
@("import matplotlib", "matplotlib.use('Agg')") + (Get-Content "plot_example.py") | Set-Content "plot_example.py"
# Prevent interactive window mode
(Get-Content "plot_example.py").replace(
'graph.render(view=True)',
'graph.render(view=False)'
) | Set-Content "plot_example.py"
# install optional plotting libraries
# (not necessary for pixi-managed environments, where they're just installed by default)
if ($env:PYTHON_VERSION -ne "3.10") {
conda install -y -n $env:CONDA_ENV "h5py>=3.10" "ipywidgets>=8.1.2" "notebook>=7.1.2"
}
# Run all examples
foreach ($file in @(Get-ChildItem *.py)) {
@(
"import sys, warnings",
-join @(
"warnings.showwarning = lambda message, category, filename, lineno, file=None, line=None: ",
"sys.stdout.write(warnings.formatwarning(message, category, filename, lineno, line))"
)
) + (Get-Content $file) | Set-Content $file
python $file ; Assert-Output $?
}
# Run all notebooks
Set-Location "$env:BUILD_SOURCESDIRECTORY/examples/python-guide/notebooks"
(Get-Content "interactive_plot_example.ipynb").replace(
'INTERACTIVE = False',
'assert False, \"Interactive mode disabled\"'
) | Set-Content "interactive_plot_example.ipynb"
jupyter nbconvert --ExecutePreprocessor.timeout=180 --to notebook --execute --inplace *.ipynb ; Assert-Output $?
}
Executable
+321
View File
@@ -0,0 +1,321 @@
#!/bin/bash
set -e -E -o -u pipefail
# defaults
CONDA_ENV="test-env"
IN_UBUNTU_BASE_CONTAINER=${IN_UBUNTU_BASE_CONTAINER:-"false"}
METHOD=${METHOD:-""}
PRODUCES_ARTIFACTS=${PRODUCES_ARTIFACTS:-"false"}
SANITIZERS=${SANITIZERS:-""}
ARCH=$(uname -m)
LGB_VER=$(head -n 1 "${BUILD_DIRECTORY}/VERSION.txt")
# create the artifact upload directory if it doesn't exist yet
mkdir -p "${BUILD_ARTIFACTSTAGINGDIRECTORY}"
if [[ $OS_NAME == "macos" ]] && [[ $COMPILER == "gcc" ]]; then
export CXX=g++-14
export CC=gcc-14
elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang" ]]; then
export CXX=clang++
export CC=clang
elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang-17" ]]; then
export CXX=clang++-17
export CC=clang-17
fi
if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then
export LANG="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
fi
# Setting MACOSX_DEPLOYMENT_TARGET prevents CMake from building against too-new
# macOS features, and helps tools like Python build tools determine the appropriate
# wheel compatibility tags.
#
# ref:
# * https://cmake.org/cmake/help/latest/envvar/MACOSX_DEPLOYMENT_TARGET.html
# * https://github.com/scikit-build/scikit-build-core/blob/acb7d0346e4a05bcb47a4ea3939c705ab71e3145/src/scikit_build_core/builder/macos.py#L36
if [[ $ARCH == "x86_64" ]]; then
export MACOSX_DEPLOYMENT_TARGET=10.15
else
export MACOSX_DEPLOYMENT_TARGET=12.0
fi
if [[ "${TASK}" == "r-package" ]]; then
bash "${BUILD_DIRECTORY}/.ci/test-r-package.sh" || exit 1
exit 0
fi
cd "${BUILD_DIRECTORY}"
if [[ $TASK == "swig" ]]; then
cmake -B build -S . -DUSE_SWIG=ON
cmake --build build -j4 || exit 1
if [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "gcc" ]]; then
objdump -T ./lib_lightgbm.so > ./objdump.log || exit 1
objdump -T ./lib_lightgbm_swig.so >> ./objdump.log || exit 1
./.ci/check-dynamic-dependencies.sh ./objdump.log || exit 1
fi
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
cp ./build/lightgbmlib.jar "${BUILD_ARTIFACTSTAGINGDIRECTORY}/lightgbmlib_${OS_NAME}.jar"
fi
exit 0
fi
if [[ "$TASK" == "cpp-tests" ]]; then
cmake_args=(
-DBUILD_CPP_TEST=ON
-DUSE_DEBUG=ON
)
if [[ $METHOD == "with-sanitizers" ]]; then
cmake_args+=("-DUSE_SANITIZER=ON")
if [[ -n $SANITIZERS ]]; then
cmake_args+=("-DENABLED_SANITIZERS=$SANITIZERS")
fi
fi
cmake -B build -S . "${cmake_args[@]}"
cmake --build build --target testlightgbm -j4 || exit 1
./testlightgbm || exit 1
exit 0
fi
# including python=version=[build=*_cp*] to ensure that conda prefers CPython and doesn't fall back to
# other implementations like pypy
CONDA_PYTHON_REQUIREMENT="python=${PYTHON_VERSION}[build=*_cp*]"
if [[ $TASK == "if-else" ]]; then
conda create -q -y -n "${CONDA_ENV}" "${CONDA_PYTHON_REQUIREMENT}" numpy
# shellcheck disable=SC1091
source activate "${CONDA_ENV}"
cmake -B build -S . || exit 1
cmake --build build --target lightgbm -j4 || exit 1
cd "$BUILD_DIRECTORY/tests/cpp_tests"
../../lightgbm config=train.conf convert_model_language=cpp convert_model=../../src/boosting/gbdt_prediction.cpp
../../lightgbm config=predict.conf output_result=origin.pred
../../lightgbm config=predict.conf output_result=ifelse.pred
python test.py
exit 0
fi
PYTHON_ENV_MANAGER="conda"
if [[ "${PYTHON_VERSION}" == "3.10" ]]; then
PYTHON_ENV_MANAGER="pixi"
CI_PIXI_ENV="py310"
fi
# 'pixi' is used for end-of-life Python versions
if [[ "${PYTHON_ENV_MANAGER}" == "pixi" ]]; then
eval "$(pixi shell-hook --locked -e "${CI_PIXI_ENV}")"
else
CONDA_REQUIREMENT_FILE="${BUILD_DIRECTORY}/.ci/conda-envs/ci-core.txt"
conda create \
-y \
-n "${CONDA_ENV}" \
--file "${CONDA_REQUIREMENT_FILE}" \
"${CONDA_PYTHON_REQUIREMENT}" \
|| exit 1
# print output of 'conda list', to help in submitting bug reports
echo "conda list:"
conda list -n ${CONDA_ENV}
# shellcheck disable=SC1091
source activate $CONDA_ENV
fi
cd "${BUILD_DIRECTORY}"
if [[ $TASK == "sdist" ]]; then
sh ./build-python.sh sdist || exit 1
sh .ci/check-python-dists.sh ./dist || exit 1
pip install -v --no-deps "./dist/lightgbm-${LGB_VER}.tar.gz" || exit 1
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
cp "./dist/lightgbm-${LGB_VER}.tar.gz" "${BUILD_ARTIFACTSTAGINGDIRECTORY}" || exit 1
fi
pytest -ra ./tests/python_package_test || exit 1
exit 0
elif [[ $TASK == "bdist" ]]; then
if [[ $OS_NAME == "macos" ]]; then
sh ./build-python.sh bdist_wheel || exit 1
sh .ci/check-python-dists.sh ./dist || exit 1
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
cp "$(echo "dist/lightgbm-${LGB_VER}-py3-none-macosx"*.whl)" "${BUILD_ARTIFACTSTAGINGDIRECTORY}" || exit 1
fi
else
sh ./build-python.sh bdist_wheel --integrated-opencl || exit 1
# print some debugging logs about the wheel's GLIBC version and dependencies on shared libraries
pip install 'auditwheel>=6.5.1'
auditwheel show ./dist/lightgbm*.whl
# pass through 'auditwheel repair' to set the appropriate wheel tags.
#
# intentionally avoid vendoring libgomp, to reduce the risk of multiple OpenMP libraries
# being loaded in the same process.
auditwheel repair \
--exclude 'libgomp.so*' \
--lib-sdir '' \
--wheel-dir dist-fixed/ \
./dist/lightgbm*.whl
# overwrite the original wheel with the new one
rm ./dist/lightgbm*.whl
mv ./dist-fixed/lightgbm*.whl ./dist
# check wheel properties
sh .ci/check-python-dists.sh ./dist || exit 1
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
# hard-code expected tag so CI will fail if 'auditwheel repair' has a surprising result (e.g. newer
# manylinux tag than we intended)
if [[ $ARCH == "x86_64" ]]; then
PLATFORM="manylinux_2_27_x86_64.manylinux_2_28_x86_64"
else
PLATFORM="manylinux2014_aarch64.manylinux_2_17_aarch64"
fi
cp "dist/lightgbm-${LGB_VER}-py3-none-${PLATFORM}.whl" "${BUILD_ARTIFACTSTAGINGDIRECTORY}" || exit 1
fi
# Make sure we can do both CPU and GPU; see tests/python_package_test/test_dual.py
export LIGHTGBM_TEST_DUAL_CPU_GPU=1
fi
pip install -v --no-deps ./dist/*.whl || exit 1
pytest -ra ./tests || exit 1
exit 0
fi
if [[ $TASK == "gpu" ]]; then
sed -i'.bak' 's/std::string device_type = "cpu";/std::string device_type = "gpu";/' ./include/LightGBM/config.h
grep -q 'std::string device_type = "gpu"' ./include/LightGBM/config.h || exit 1 # make sure that changes were really done
if [[ $METHOD == "pip" ]]; then
sh ./build-python.sh sdist || exit 1
sh .ci/check-python-dists.sh ./dist || exit 1
pip install \
-v \
--no-deps \
--config-settings=cmake.define.USE_GPU=ON \
"./dist/lightgbm-${LGB_VER}.tar.gz" \
|| exit 1
pytest -ra ./tests/python_package_test || exit 1
exit 0
elif [[ $METHOD == "wheel" ]]; then
sh ./build-python.sh bdist_wheel --gpu || exit 1
sh ./.ci/check-python-dists.sh ./dist || exit 1
pip install -v --no-deps "$(echo "./dist/lightgbm-${LGB_VER}"*.whl)" || exit 1
pytest -ra ./tests || exit 1
exit 0
elif [[ $METHOD == "source" ]]; then
cmake -B build -S . -DUSE_GPU=ON
fi
elif [[ $TASK == "cuda" ]]; then
sed -i'.bak' 's/std::string device_type = "cpu";/std::string device_type = "cuda";/' ./include/LightGBM/config.h
grep -q 'std::string device_type = "cuda"' ./include/LightGBM/config.h || exit 1 # make sure that changes were really done
# by default ``gpu_use_dp=false`` for efficiency. change to ``true`` here for exact results in ci tests
sed -i'.bak' 's/gpu_use_dp = false;/gpu_use_dp = true;/' ./include/LightGBM/config.h
grep -q 'gpu_use_dp = true' ./include/LightGBM/config.h || exit 1 # make sure that changes were really done
if [[ $METHOD == "pip" ]]; then
sh ./build-python.sh sdist || exit 1
sh ./.ci/check-python-dists.sh ./dist || exit 1
pip install \
-v \
--no-deps \
--config-settings=cmake.define.USE_CUDA=ON \
"./dist/lightgbm-${LGB_VER}.tar.gz" \
|| exit 1
pytest -ra ./tests/python_package_test || exit 1
exit 0
elif [[ $METHOD == "wheel" ]]; then
sh ./build-python.sh bdist_wheel --cuda || exit 1
sh ./.ci/check-python-dists.sh ./dist || exit 1
pip install -v --no-deps "$(echo "./dist/lightgbm-${LGB_VER}"*.whl)" || exit 1
pytest -ra ./tests || exit 1
exit 0
elif [[ $METHOD == "source" ]]; then
# we want at least 1 CI job testing that manual override of CMAKE_CUDA_ARCHITECTURES works
cmake \
-B build \
-S . \
-DUSE_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES="native"
fi
elif [[ $TASK == "mpi" ]]; then
if [[ $METHOD == "pip" ]]; then
sh ./build-python.sh sdist || exit 1
sh ./.ci/check-python-dists.sh ./dist || exit 1
pip install \
-v \
--no-deps \
--config-settings=cmake.define.USE_MPI=ON \
"./dist/lightgbm-${LGB_VER}.tar.gz" \
|| exit 1
pytest -ra ./tests/python_package_test || exit 1
exit 0
elif [[ $METHOD == "wheel" ]]; then
sh ./build-python.sh bdist_wheel --mpi || exit 1
sh ./.ci/check-python-dists.sh ./dist || exit 1
pip install -v --no-deps "$(echo "./dist/lightgbm-${LGB_VER}"*.whl)" || exit 1
pytest -ra ./tests || exit 1
exit 0
elif [[ $METHOD == "source" ]]; then
cmake -B build -S . -DUSE_MPI=ON -DUSE_DEBUG=ON
fi
else
cmake -B build -S .
fi
cmake --build build --target _lightgbm -j4 || exit 1
sh ./build-python.sh install --precompile || exit 1
pytest -ra ./tests || exit 1
if [[ $TASK == "regular" ]]; then
if [[ $PRODUCES_ARTIFACTS == "true" ]]; then
if [[ $OS_NAME == "macos" ]]; then
cp ./lib_lightgbm.dylib "${BUILD_ARTIFACTSTAGINGDIRECTORY}/lib_lightgbm.dylib"
else
if [[ $COMPILER == "gcc" ]]; then
objdump -T ./lib_lightgbm.so > ./objdump.log || exit 1
./.ci/check-dynamic-dependencies.sh ./objdump.log || exit 1
fi
cp ./lib_lightgbm.so "${BUILD_ARTIFACTSTAGINGDIRECTORY}/lib_lightgbm.so"
fi
fi
cd "$BUILD_DIRECTORY/examples/python-guide"
sed -i'.bak' '/import lightgbm as lgb/a\
import matplotlib\
matplotlib.use\(\"Agg\"\)\
' plot_example.py # prevent interactive window mode
sed -i'.bak' 's/graph.render(view=True)/graph.render(view=False)/' plot_example.py
# install optional plotting libraries
# (not necessary for pixi-managed environments, where they're just installed by default)
if [[ "${PYTHON_ENV_MANAGER}" != "pixi" ]]; then
conda install -y -n $CONDA_ENV \
'h5py>=3.10' \
'ipywidgets>=8.1.2' \
'notebook>=7.1.2'
fi
for f in *.py **/*.py; do python "${f}" || exit 1; done # run all examples
cd "$BUILD_DIRECTORY/examples/python-guide/notebooks"
sed -i'.bak' 's/INTERACTIVE = False/assert False, \\"Interactive mode disabled\\"/' interactive_plot_example.ipynb
jupyter nbconvert --ExecutePreprocessor.timeout=180 --to notebook --execute --inplace ./*.ipynb || exit 1 # run all notebooks
# importing the library should succeed even if all optional dependencies are not present
if [[ "${PYTHON_ENV_MANAGER}" != "pixi" ]]; then
conda uninstall -n $CONDA_ENV --force --yes \
cffi \
dask \
distributed \
joblib \
matplotlib-base \
pandas \
polars \
psutil \
pyarrow \
python-graphviz \
scikit-learn || exit 1
fi
python -c "import lightgbm" || exit 1
fi
+19
View File
@@ -0,0 +1,19 @@
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 2
[*.{py,sh,ps1,js,json}]
indent_size = 4
max_line_length = 120
skip = external_libs
known_first_party = lightgbm
# Tabs matter for Makefile and .gitmodules
[{makefile*,Makefile*,*.mk,*.mak,*.makefile,*.Makefile,GNUmakefile,BSDmakefile,make.bat,Makevars*,*.gitmodules}]
indent_style = tab
+8
View File
@@ -0,0 +1,8 @@
# introduce ruff-format (#6308)
6330d6269c81dfd4c96e664b99239b8ff39ccf91
# enable ruff format on tests and examples (#6317)
1b792e716682254c33ddb5eb845357e84018636d
# enable ruff-format on main library Python code (#6336)
dd31208ab7a7aea86762830697b00666f843ded9
# enable whitespace/indent_namespace rule from cpplint (#7056)
50f11a9f3c066eadee475ea567f9e9d49e6bb827
+34
View File
@@ -0,0 +1,34 @@
* text=auto eol=lf
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.cc text
*.cmake text
*.conf text
*.cpp text
*.cu text
*.cuh text
*.h text
*.hpp text
*.i text
*.js text
*.md text
*.py text
*.R text
*.rst text
*.sh text
*.svg text
*.toml text
*.tpp text
*.txt text
*.yaml text
*.yml text
# help git identify which files should be treated as binary
*.ico binary
*.png binary
# SCM syntax highlighting & preventing 3-way merges
# (added automatically by 'pixi init')
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
+10
View File
@@ -0,0 +1,10 @@
# This file controls default reviewers for LightGBM code.
# See https://help.github.com/en/articles/about-code-owners
# for details
#
# Maintainers are encouraged to use their best discretion in
# setting reviewers on PRs manually, but this file should
# offer a reasonable automatic best-guess
# catch-all rule (this only gets matched if no rules below match)
* @guolinke @jameslamb @shiyu1994 @jmoralez @borchero @StrikerRUS @mayer79
+26
View File
@@ -0,0 +1,26 @@
---
name: Bug Report 🐞
about: Something isn't working as expected? Here is the right place to report.
---
## Description
<!-- A clear description of the bug -->
## Reproducible example
<!-- Minimal code that exhibits this behavior -->
## Environment info
LightGBM version or commit hash:
Command(s) you used to install LightGBM
```shell
```
<!-- Put any additional environment information here -->
## Additional Comments
<!-- What else should we know? -->
+25
View File
@@ -0,0 +1,25 @@
---
name: Feature Request 💡
about: Suggest a new idea for the project.
labels: enhancement, feature-request
---
<!--
Please search your feature on previous issues and our feature requests consolidation hub (https://github.com/lightgbm-org/LightGBM/issues/2302) before you open a new one.
-->
## Summary
<!-- Briefly explain your feature proposal. -->
## Motivation
<!-- Why is it useful to have this feature in the LightGBM project? -->
## Description
<!-- Detailed description of the new feature. -->
## References
<!-- Any useful references, for instance, papers, implementations in other projects, draft code snippets, etc. -->
+17
View File
@@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
ci-dependencies:
patterns:
- "*"
cooldown:
# only accept releases that have been out for at least 15 days
default-days: 15
commit-message:
prefix: "[ci]"
labels:
- maintenance
+20
View File
@@ -0,0 +1,20 @@
name-template: 'v$NEXT_PATCH_VERSION'
tag-template: 'v$NEXT_PATCH_VERSION'
categories:
- title: '💡 New Features'
label: 'feature'
- title: '🔨 Breaking'
label: 'breaking'
- title: '🚀 Efficiency Improvement'
label: 'efficiency'
- title: '🐛 Bug Fixes'
label: 'fix'
- title: '📖 Documentation'
label: 'doc'
- title: '🧰 Maintenance'
label: 'maintenance'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
## Changes
$CHANGES
+81
View File
@@ -0,0 +1,81 @@
# builds core artifacts, intended to be attached to releases
# or used by other workflows
name: Build
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
# tell scripts where to put artifacts
BUILD_ARTIFACTSTAGINGDIRECTORY: '${{ github.workspace }}/artifacts'
jobs:
archive:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Create source archive
run: |
mkdir -p "${BUILD_ARTIFACTSTAGINGDIRECTORY}"
tar \
-czvf \
/tmp/LightGBM-complete_source_code_tar_gz.tar.gz \
.
mv \
/tmp/LightGBM-complete_source_code_tar_gz.tar.gz \
${BUILD_ARTIFACTSTAGINGDIRECTORY}/
- name: Create commit.txt
shell: bash
run: |
# for pull requests, github.sha refers to the merge commit from merging the PR and
# target branch... we want the actual commit that was pushed
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
else
COMMIT_SHA="${{ github.sha }}"
fi
echo "${COMMIT_SHA}" > "${BUILD_ARTIFACTSTAGINGDIRECTORY}/commit.txt"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: source-archive
path: |
${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}/commit.txt
${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}/LightGBM-complete_source_code_tar_gz.tar.gz
if-no-files-found: error
all-build-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- archive
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+158
View File
@@ -0,0 +1,158 @@
name: C++
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
# tell scripts where to put artifacts
# (this variable name is left over from when jobs ran on Azure DevOps)
BUILD_ARTIFACTSTAGINGDIRECTORY: '${{ github.workspace }}/artifacts'
# where repo sources are cloned to
BUILD_DIRECTORY: '${{ github.workspace }}'
# in CMake-driven builds, parallelize compilation
CMAKE_BUILD_PARALLEL_LEVEL: 4
jobs:
test:
name: ${{ matrix.task }} (${{ matrix.os-display-name || matrix.os }}, ${{ matrix.compiler }})
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
timeout-minutes: 60
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
#############
# C++ tests #
#############
- os: ubuntu-latest
task: cpp-tests
compiler: clang-17
method: with-sanitizers
container: 'ubuntu:22.04'
os-display-name: 'ubuntu22.04'
- os: macos-15-intel
task: cpp-tests
compiler: clang
method: with-sanitizers
sanitizers: "address;undefined"
container: null
- os: windows-2022
task: cpp-tests
compiler: msvc
container: null
###########
# if-else #
###########
# These test CLI-generated C++ inference code.
#
# They need Python because they're written with pytest, but they
# do not need the LightGBM Python package
- os: ubuntu-latest
task: if-else
compiler: clang-17
container: 'ubuntu:22.04'
os-display-name: 'ubuntu22.04'
python_version: '3.14'
setup-conda: 'true'
- os: macos-15-intel
task: if-else
compiler: clang
python_version: '3.10'
container: null
- os: ubuntu-latest
task: if-else
compiler: gcc
python_version: '3.11'
container: 'ghcr.io/lightgbm-org/lightgbm-vsts-agent:manylinux_2_28_x86_64'
os-display-name: 'manylinux_2_28'
steps:
- name: Install packages used by third-party actions
if: matrix.container == 'ubuntu:22.04'
shell: bash
run: |
apt-get update -y
apt-get install --no-install-recommends -y \
dirmngr \
gpg \
gpg-agent \
software-properties-common \
sudo
# install newest version of git
# ref:
# - https://unix.stackexchange.com/a/170831/550004
# - https://git-scm.com/download/linux
add-apt-repository ppa:git-core/ppa -y
apt-get update -y
apt-get install --no-install-recommends -y \
git
- name: Trust git cloning LightGBM
if: startsWith(matrix.os, 'ubuntu')
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Setup and run tests on Linux and macOS
if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu')
shell: bash
run: |
export COMPILER="${{ matrix.compiler }}"
export CONDA="${HOME}/miniforge"
export METHOD="${{ matrix.method }}"
export PATH=${CONDA}/bin:${PATH}
export PYTHON_VERSION="${{ matrix.python_version }}"
export SANITIZERS="${{ matrix.sanitizers }}"
export SETUP_CONDA="${{ matrix.setup-conda }}"
export TASK="${{ matrix.task }}"
if [[ "${{ matrix.os }}" =~ ^macos ]]; then
export OS_NAME="macos"
elif [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
export OS_NAME="linux"
fi
if [[ "${{ matrix.container }}" == "ubuntu:22.04" ]]; then
export DEBIAN_FRONTEND=noninteractive
export IN_UBUNTU_BASE_CONTAINER="true"
fi
$GITHUB_WORKSPACE/.ci/setup.sh
$GITHUB_WORKSPACE/.ci/test.sh
- name: Setup and run tests on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh -command ". {0}"
run: |
$env:BUILD_SOURCESDIRECTORY = $env:BUILD_DIRECTORY
$env:TASK = "${{ matrix.task }}"
& "$env:GITHUB_WORKSPACE/.ci/test-windows.ps1"
all-cpp-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- test
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+115
View File
@@ -0,0 +1,115 @@
name: CUDA Version
on:
push:
# run on pushes to LightGBM branches matching certain names
branches:
- cuda-*
# Run manually by clicking a button in the UI
workflow_dispatch:
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
test:
# yamllint disable-line rule:line-length
name: ${{ matrix.task }} ${{ matrix.cuda_version }} ${{ matrix.method }} (${{ matrix.linux_version }}, ${{ matrix.compiler }}, Python ${{ matrix.python_version }})
# yamllint disable-line rule:line-length
# ref: https://docs.github.com/en/actions/concepts/runners/about-larger-runners#specifications-for-gpu-larger-runners
runs-on: github-hosted-linux-gpu-amd64
container:
image: nvcr.io/nvidia/cuda:${{ matrix.cuda_version }}-devel-${{ matrix.linux_version }}
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
COMPILER: ${{ matrix.compiler }}
CONDA: /tmp/miniforge
DEBIAN_FRONTEND: noninteractive
METHOD: ${{ matrix.method }}
OS_NAME: linux
PYTHON_VERSION: ${{ matrix.python_version }}
TASK: ${{ matrix.task }}
SKBUILD_STRICT_CONFIG: true
options: --gpus all
timeout-minutes: 30
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- method: wheel
compiler: gcc
python_version: "3.11"
cuda_version: "13.2.1"
linux_version: "ubuntu24.04"
task: cuda
- method: wheel
compiler: gcc
python_version: "3.11"
cuda_version: "12.9.1"
linux_version: "ubuntu22.04"
task: cuda
- method: source
compiler: gcc
python_version: "3.14"
cuda_version: "12.2.2"
linux_version: "ubuntu22.04"
task: cuda
- method: pip
compiler: gcc
python_version: "3.12"
cuda_version: "11.8.0"
linux_version: "ubuntu20.04"
task: cuda
steps:
- name: Install latest git and sudo
run: |
apt-get update
apt-get install --no-install-recommends -y \
ca-certificates \
software-properties-common
add-apt-repository ppa:git-core/ppa -y
apt-get update
apt-get install --no-install-recommends -y \
git \
sudo
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Setup and run tests
env:
CI_CUDA_VERSION: ${{ matrix.cuda_version }}
run: |
export BUILD_ARTIFACTSTAGINGDIRECTORY="${{ github.workspace }}/artifacts"
export BUILD_DIRECTORY="$GITHUB_WORKSPACE"
export PATH=$CONDA/bin:$PATH
# check GPU usage
nvidia-smi
# build and test
$GITHUB_WORKSPACE/.ci/setup.sh
$GITHUB_WORKSPACE/.ci/test.sh
all-cuda-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- test
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+58
View File
@@ -0,0 +1,58 @@
name: 'Lock Inactive Threads'
on:
schedule:
# midnight UTC, every Wednesday, for Issues
- cron: '0 0 * * 3'
# midnight UTC, every Thursday, for PRs
- cron: '0 0 * * 4'
# allow manual triggering from GitHub UI
workflow_dispatch:
# only 1 job running in the repo at any time
concurrency:
group: lock
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
action:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
with:
github-token: ${{ github.token }}
# after how many days of inactivity should a closed issue/PR be locked?
issue-inactive-days: '365'
pr-inactive-days: '365'
# do not close feature request issues...
# we close those but track them in https://github.com/lightgbm-org/LightGBM/issues/2302
exclude-any-issue-labels: 'feature request'
# what labels should be removed prior to locking?
remove-issue-labels: 'awaiting response,awaiting review,blocking,in progress'
remove-pr-labels: 'awaiting response,awaiting review,blocking,in progress'
# what message should be posted prior to locking?
issue-comment: >
This issue has been automatically locked
since there has not been any recent activity since it was closed.
To start a new related discussion,
open a new issue at https://github.com/lightgbm-org/LightGBM/issues
including a reference to this.
pr-comment: >
This pull request has been automatically locked
since there has not been any recent activity since it was closed.
To start a new related discussion,
open a new issue at https://github.com/lightgbm-org/LightGBM/issues
including a reference to this.
# what should the locking status be?
issue-lock-reason: 'resolved'
pr-lock-reason: 'resolved'
process-only: ${{ github.event.schedule == '0 0 * * 3' && 'issues' || 'prs' }}
+54
View File
@@ -0,0 +1,54 @@
name: Link checks
on:
# Run manually by clicking a button in the UI
workflow_dispatch:
# Run once a day at 8:00am UTC
schedule:
- cron: '0 8 * * *'
# only 1 job running in the repo at any time
concurrency:
group: lock
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
COMPILER: gcc
OS_NAME: 'linux'
TASK: 'check-links'
jobs:
check-links:
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: false
- name: Build docs
run: |
export CONDA=${HOME}/miniforge
export PATH=${CONDA}/bin:${HOME}/.local/bin:${PATH}
$GITHUB_WORKSPACE/.ci/setup.sh || exit 1
$GITHUB_WORKSPACE/.ci/build-docs.sh || exit 1
- name: Check links
uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0
with:
args: >-
--config=./docs/.lychee.toml
--
"**/*.rst"
"**/*.md"
"./R-package/**/*.Rd"
"./docs/_build/html/*.html"
fail: true
failIfEmpty: true
+40
View File
@@ -0,0 +1,40 @@
name: No Response Bot
on:
issue_comment:
types: [created]
schedule:
# "every day at 04:00 UTC"
- cron: '0 4 * * *'
# only 1 job running in the repo at any time
concurrency:
group: lock
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
no-response:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: lee-dohm/no-response@9bb0a4b5e6a45046f00353d5de7d90fb8bd773bb # v0.5.0
with:
closeComment: >
This issue has been automatically closed
because it has been awaiting a response for too long.
When you have time to to work with the maintainers to resolve this issue,
please post a new comment and it will be re-opened.
If the issue has been locked for editing by the time you return to it,
please open a new issue and reference this one.
Thank you for taking the time to improve LightGBM!
daysUntilClose: 30
responseRequiredLabel: awaiting response
token: ${{ github.token }}
+44
View File
@@ -0,0 +1,44 @@
name: Optional checks
on:
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
all-optional-checks-successful:
timeout-minutes: 30
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ github.token }}
permissions:
contents: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: false
- name: Check valgrind workflow
shell: bash
run: |
# ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context
PR_BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
echo "checking status for branch '${PR_BRANCH}'"
./.ci/check-workflow-status.sh \
"${PR_BRANCH}" \
'r_valgrind.yml' \
${{ github.event.number }}
+534
View File
@@ -0,0 +1,534 @@
name: Python-package
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
# tell scripts where to put artifacts
# (this variable name is left over from when jobs ran on Azure DevOps)
BUILD_ARTIFACTSTAGINGDIRECTORY: '${{ github.workspace }}/artifacts'
# where repo sources are cloned to
BUILD_SOURCESDIRECTORY: '${{ github.workspace }}'
CMAKE_BUILD_PARALLEL_LEVEL: 4
# avoid interactive prompts in Debian-based distributions
DEBIAN_FRONTEND: noninteractive
# On runners using nvidia-container-runtime with Docker, ensure GPUs are available to running processes.
#
# ref: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html
NVIDIA_VISIBLE_DEVICES: 'all'
SKBUILD_STRICT_CONFIG: true
jobs:
test-linux-aarch64:
name: bdist wheel (manylinux2014-arm, gcc, Python 3.14)
runs-on: ubuntu-24.04-arm
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Setup and run tests
shell: bash
# this uses 'docker run' instead of just setting 'container:'
# because actions/checkout requires GLIBC 2.28 and that is too
# new for manylinux2014
env:
BUILD_DIRECTORY: /LightGBM
run: |
cat > ./docker-script.sh <<EOF
mkdir -p \$BUILD_ARTIFACTSTAGINGDIRECTORY
export CONDA=\$HOME/miniforge
export PATH=\$CONDA/bin:\$PATH
${{ env.BUILD_DIRECTORY }}/.ci/setup.sh || exit 1
${{ env.BUILD_DIRECTORY }}/.ci/test.sh || exit 1
EOF
IMAGE_URI="lightgbm/vsts-agent:manylinux2014_aarch64"
docker pull "${IMAGE_URI}" || exit 1
docker run \
--platform "${PLATFORM}" \
--rm \
--env BUILD_DIRECTORY=${{ env.BUILD_DIRECTORY }} \
--env BUILD_ARTIFACTSTAGINGDIRECTORY=${{ env.BUILD_DIRECTORY }}/artifacts/ \
--env COMPILER=gcc \
--env METHOD=wheel \
--env OS_NAME=linux \
--env PRODUCES_ARTIFACTS=true \
--env PYTHON_VERSION="3.14" \
--env TASK=bdist \
-v "${PWD}":"${{ env.BUILD_DIRECTORY }}" \
-w ${{ env.BUILD_DIRECTORY }} \
"${IMAGE_URI}" \
/bin/bash ./docker-script.sh
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-aarch64-wheel
path: artifacts/*.whl
if-no-files-found: error
test:
# yamllint disable-line rule:line-length
name: ${{ matrix.task }} ${{ matrix.method }} (${{ matrix.os-display-name || matrix.os }}, ${{ matrix.compiler }}, Python ${{ matrix.python_version }})
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- os: macos-15-intel
task: sdist
compiler: clang
python_version: '3.10'
pixi-env: 'py310'
- os: windows-2022
task: sdist
compiler: MSVC
python_version: '3.10'
pixi-env: 'py310'
- os: ubuntu-latest
container: &ubuntu_latest_image ubuntu:22.04
os-display-name: &ubuntu_latest_display_name 'ubuntu22.04'
task: regular
compiler: clang-17
python_version: '3.13'
in-ubuntu-base-container: 'true'
- os: ubuntu-latest
container: *ubuntu_latest_image
os-display-name: *ubuntu_latest_display_name
task: sdist
compiler: clang-17
python_version: '3.14'
in-ubuntu-base-container: 'true'
#########################
# OpenCL-based GPU jobs #
#########################
# clang-17 jobs are run on machines with actual GPUs because
# the package built with that compiler toolchain can't target the
# CPUs on GitHub's runners.
#
# ref: https://github.com/lightgbm-org/LightGBM/pull/7096/files#r2590879590
# TODO(jameslamb): restore these GPU jobs when we've clarified handling of GPU CI
# ref: https://github.com/lightgbm-org/LightGBM/issues/7187
# - os: github-hosted-linux-gpu-amd64
# container: nvcr.io/nvidia/cuda:12.9.1-devel-ubuntu22.04
# os-display-name: ubuntu22.04-with-gpu
# task: bdist
# compiler: clang-17
# method: wheel
# python_version: '3.11'
# in-ubuntu-base-container: 'true'
# - os: github-hosted-linux-gpu-amd64
# container: nvcr.io/nvidia/cuda:12.9.1-devel-ubuntu22.04
# os-display-name: ubuntu22.04-with-gpu
# task: gpu
# compiler: clang-17
# method: source
# python_version: '3.12'
# in-ubuntu-base-container: 'true'
- os: ubuntu-latest
container: &manylinux_amd64_image ghcr.io/lightgbm-org/lightgbm-vsts-agent:manylinux_2_28_x86_64
os-display-name: &manylinux_amd64_display_name 'manylinux_2_28'
task: gpu
method: source
compiler: gcc
python_version: '3.14'
in-ubuntu-base-container: 'false'
setup-conda: 'false'
############
# MPI jobs #
############
- os: macos-15-intel
task: mpi
compiler: gcc
method: source
python_version: '3.12'
- os: ubuntu-latest
container: *manylinux_amd64_image
os-display-name: *manylinux_amd64_display_name
task: mpi
compiler: gcc
method: source
python_version: '3.11'
in-ubuntu-base-container: 'false'
setup-conda: 'false'
- os: ubuntu-latest
container: *ubuntu_latest_image
os-display-name: *ubuntu_latest_display_name
task: mpi
compiler: clang-17
method: source
python_version: '3.13'
in-ubuntu-base-container: 'true'
- os: ubuntu-latest
container: *ubuntu_latest_image
os-display-name: *ubuntu_latest_display_name
task: mpi
compiler: clang-17
method: pip
python_version: '3.12'
in-ubuntu-base-container: 'true'
- os: ubuntu-latest
container: *ubuntu_latest_image
os-display-name: *ubuntu_latest_display_name
task: mpi
compiler: clang-17
method: wheel
python_version: '3.10'
pixi-env: 'py310'
in-ubuntu-base-container: 'true'
#####################
# jobs that create #
# release artifacts #
#####################
- os: macos-15-intel
task: bdist
compiler: clang
method: wheel
produces-artifacts: 'true'
artifact-name: 'macosx-amd64-wheel'
python_version: '3.13'
- os: macos-14
task: bdist
compiler: clang
method: wheel
produces-artifacts: 'true'
artifact-name: 'macosx-arm64-wheel'
python_version: '3.11'
- os: macos-15-intel
task: regular
compiler: clang
produces-artifacts: 'true'
artifact-name: 'macosx-amd64-liblightgbm'
python_version: '3.11'
- os: ubuntu-latest
container: *manylinux_amd64_image
os-display-name: *manylinux_amd64_display_name
task: regular
compiler: gcc
python_version: '3.11'
in-ubuntu-base-container: 'false'
setup-conda: 'false'
produces-artifacts: 'true'
artifact-name: 'linux-amd64-liblightgbm'
- os: ubuntu-latest
container: *manylinux_amd64_image
os-display-name: *manylinux_amd64_display_name
task: bdist
compiler: gcc
method: wheel
python_version: '3.10'
pixi-env: 'py310'
in-ubuntu-base-container: 'false'
setup-conda: 'false'
produces-artifacts: 'true'
artifact-name: 'linux-amd64-wheel'
- os: ubuntu-latest
container: *manylinux_amd64_image
os-display-name: *manylinux_amd64_display_name
task: sdist
compiler: gcc
python_version: '3.10'
pixi-env: 'py310'
in-ubuntu-base-container: 'false'
setup-conda: 'false'
produces-artifacts: 'true'
artifact-name: 'sdist'
- os: windows-2022
task: bdist
compiler: MSVC
method: wheel
produces-artifacts: 'true'
artifact-name: 'windows-amd64-wheel'
python_version: '3.13'
- os: windows-2022
task: regular
compiler: MSVC
produces-artifacts: 'true'
artifact-name: 'windows-amd64-cli-and-liblightgbm'
python_version: '3.11'
steps:
- name: Install packages used by third-party actions
if: ${{ matrix.in-ubuntu-base-container == 'true' }}
shell: bash
run: |
apt-get update -y
apt-get install --no-install-recommends -y \
ca-certificates \
dirmngr \
gpg \
gpg-agent \
software-properties-common \
sudo
# install newest version of git
# ref:
# - https://unix.stackexchange.com/a/170831/550004
# - https://git-scm.com/download/linux
add-apt-repository ppa:git-core/ppa -y
apt-get update -y
apt-get install --no-install-recommends -y \
git
- name: Trust git cloning LightGBM
if: startsWith(matrix.os, 'ubuntu')
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Set up 'pixi' for jobs that need it
if: matrix.pixi-env != ''
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
with:
environments: ${{ matrix.pixi-env }}
- name: Setup and run tests on Linux and macOS
if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') || endsWith(matrix.os, 'gpu-amd64')
shell: bash
run: |
export COMPILER="${{ matrix.compiler }}"
export IN_UBUNTU_BASE_CONTAINER="${{ matrix.in-ubuntu-base-container }}"
export SETUP_CONDA="${{ matrix.setup-conda }}"
export TASK="${{ matrix.task }}"
export METHOD="${{ matrix.method }}"
if [[ "${{ matrix.os }}" =~ ^macos ]]; then
export OS_NAME="macos"
elif \
[[ "${{ matrix.os }}" == "ubuntu-latest" ]] \
|| [[ "${{ matrix.os }}" == "github-hosted-linux-gpu-amd64" ]];
then
export OS_NAME="linux"
export PATH="/usr/lib64/openmpi/bin:${PATH}"
fi
export PYTHON_VERSION="${{ matrix.python_version }}"
export BUILD_DIRECTORY="$GITHUB_WORKSPACE"
# some pre-built images already have 'CONDA' set
if [[ -z "${CONDA:-}" ]]; then
export CONDA=${HOME}/miniforge
fi
export PATH=${CONDA}/bin:${PATH}
export PRODUCES_ARTIFACTS="${{ matrix.produces-artifacts }}"
$GITHUB_WORKSPACE/.ci/setup.sh || exit 1
$GITHUB_WORKSPACE/.ci/test.sh || exit 1
- name: Install OpenCL on Windows
if: startsWith(matrix.os, 'windows') && (matrix.task == 'bdist')
shell: pwsh -command ". {0}"
run: |
& "$env:GITHUB_WORKSPACE/.ci/install-opencl.ps1"
# 'conda init powershell' needs to be run in a separate process
# ref: https://docs.conda.io/projects/conda/en/stable/dev-guide/deep-dives/activation.html
- name: Initialize conda on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh -command ". {0}"
run: |
$env:PATH = "$env:CONDA/Scripts;$env:PATH"
conda init powershell
- name: Setup and run tests on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh -command ". {0}"
run: |
$env:COMPILER = "${{ matrix.compiler }}"
$env:METHOD = "${{ matrix.method }}"
$env:PRODUCES_ARTIFACTS = "${{ matrix.produces-artifacts }}"
$env:PYTHON_VERSION = "${{ matrix.python_version }}"
$env:TASK = "${{ matrix.task }}"
& "$env:GITHUB_WORKSPACE/.ci/test-windows.ps1"
- name: Upload artifacts
if: ${{ matrix.produces-artifacts == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact-name }}
path: |
artifacts/*.dll
artifacts/*.dylib
artifacts/*.exe
artifacts/*.so
artifacts/*.tar.gz
artifacts/*.whl
if-no-files-found: error
create-nuget:
name: NuGet package
runs-on: ubuntu-latest
timeout-minutes: 30
needs:
- test
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: false
- name: Download lib_lightgbm (all operating systems) and Windows CLI
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}
pattern: '*amd64*-liblightgbm'
run-id: ${{ github.run_id }}
- name: Setup NuGet CLI
uses: NuGet/setup-nuget@fd55a6f3b34392fa83fde1454582407d8c714123 # v4.0
# ref: https://github.com/NuGet/setup-nuget/issues/168
- name: Setup mono
run: |
sudo apt-get update -y
sudo apt-get install --no-install-recommends -y \
mono-devel
- name: Create NuGet package
run: |
python .ci/create-nuget.py "${BUILD_ARTIFACTSTAGINGDIRECTORY}"
nuget pack \
$(pwd)/.ci/nuget/LightGBM.nuspec \
-NonInteractive \
-Verbosity detailed \
-OutputDirectory "${BUILD_ARTIFACTSTAGINGDIRECTORY}"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: nuget-package
path: artifacts/*.nupkg
if-no-files-found: error
test-latest-versions:
name: Python - latest versions (manylinux_2_28)
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Create wheel
run: |
docker run \
--rm \
--env CMAKE_BUILD_PARALLEL_LEVEL=${{ env.CMAKE_BUILD_PARALLEL_LEVEL }} \
-v $(pwd):/opt/lgb-build \
-w /opt/lgb-build \
lightgbm/vsts-agent:manylinux_2_28_x86_64 \
/bin/bash -c 'PATH=/opt/miniforge/bin:$PATH sh ./build-python.sh bdist_wheel --nomp'
- name: Test compatibility
run: |
docker run \
--rm \
-v $(pwd):/opt/lgb-build \
-w /opt/lgb-build \
python:3.14 \
/bin/bash ./.ci/test-python-latest.sh
test-old-versions:
name: Python - oldest supported versions (manylinux_2_28, Python ${{ matrix.python_version }})
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
# This should always include at least the oldest
# not-yet-end-of-life Python version
python_version:
- '3.10'
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Create wheel
run: |
docker run \
--rm \
--env CMAKE_BUILD_PARALLEL_LEVEL=${{ env.CMAKE_BUILD_PARALLEL_LEVEL }} \
-v $(pwd):/opt/lgb-build \
-w /opt/lgb-build \
lightgbm/vsts-agent:manylinux_2_28_x86_64 \
/bin/bash -c 'PATH=/opt/miniforge/bin:$PATH sh ./build-python.sh bdist_wheel --nomp'
- name: Test compatibility
run: |
docker run \
--rm \
-v $(pwd):/opt/lgb-build \
-w /opt/lgb-build \
python:${{ matrix.python_version }} \
/bin/bash ./.ci/test-python-oldest.sh
upload-nightlies:
needs:
- test
- test-linux-aarch64
runs-on: ubuntu-latest
# publish whenever a new commit is pushed to 'main'
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: nightly-python-uploads
url: https://anaconda.org/lightgbm-packages/lightgbm
permissions:
contents: read
id-token: write
steps:
- name: setup
run: |
mkdir -p "${BUILD_ARTIFACTSTAGINGDIRECTORY}"
pip install --prefer-binary \
anaconda-auth \
anaconda-client
- name: download wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}
pattern: '*-wheel'
run-id: ${{ github.run_id }}
- name: download sdists
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdist
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}
run-id: ${{ github.run_id }}
- name: upload to anaconda.org
env:
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN_LIGHTGBM_PACKAGES }}
run: |
anaconda upload \
--package lightgbm \
--force \
-t pypi \
${BUILD_ARTIFACTSTAGINGDIRECTORY}/lightgbm-*.tar.gz \
${BUILD_ARTIFACTSTAGINGDIRECTORY}/*.whl
all-python-package-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- test-latest-versions
- test
- test-linux-aarch64
- test-old-versions
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+60
View File
@@ -0,0 +1,60 @@
name: R generate configure
on:
workflow_dispatch:
inputs:
pr-branch:
type: string
description: |
Branch in lightgbm-org/LightGBM to update.
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
r-configure:
name: r-configure
timeout-minutes: 60
runs-on: ubuntu-latest
container: "ubuntu:22.04"
permissions:
contents: write
id-token: write
pull-requests: read
steps:
- name: Install essential software before checkout
run: |
apt-get update
apt-get install --no-install-recommends -y \
ca-certificates \
git
- name: Trust git cloning LightGBM
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
submodules: false
repository: lightgbm-org/LightGBM
ref: "refs/heads/${{ inputs.pr-branch }}"
token: ${{ github.token }}
persist-credentials: true
- name: Update configure
shell: bash
run: ./R-package/recreate-configure.sh || exit 1
- name: Push changes
run: |
# source for this user and email: https://github.com/orgs/community/discussions/160496
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add "./R-package/configure"
git commit --allow-empty -m "Auto-update configure"
git push
+395
View File
@@ -0,0 +1,395 @@
name: R-package
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
# tell scripts where to put artifacts
# (this variable name is left over from when jobs ran on Azure DevOps)
BUILD_ARTIFACTSTAGINGDIRECTORY: '${{ github.workspace }}/artifacts'
# in CMake-driven builds, parallelize compilation
CMAKE_BUILD_PARALLEL_LEVEL: 4
# on Debian-based images, avoid interactive prompts
DEBIAN_FRONTEND: noninteractive
# Fix issues like the following that can show up running 'R CMD check' on
# specific clang versions:
#
# * checking for detritus in the temp directory ... NOTE
# Found the following files/directories:
# dsymutil-63923a dsymutil-9aa721 dsymutil-b7e1bb
#
# These are unlikely to show up in CRAN's checks. They come from
# 'dsymutil ---gen-reproducer' being run (not something LightGBM explicitly does).
#
# ref:
# - https://github.com/llvm/llvm-project/issues/61920
# - https://github.com/golang/go/issues/59026#issuecomment-1520487072
DSYMUTIL_REPRODUCER_PATH: /dev/null
# parallelize compilation (extra important for Linux, where CRAN doesn't supply pre-compiled binaries)
MAKEFLAGS: "-j4"
# hack to get around this:
# https://stat.ethz.ch/pipermail/r-package-devel/2020q3/005930.html
_R_CHECK_SYSTEM_CLOCK_: 0
# ignore R CMD CHECK NOTE checking how long it has
# been since the last submission
_R_CHECK_CRAN_INCOMING_REMOTE_: 0
# CRAN ignores the "installed size is too large" NOTE,
# so our CI can too. Setting to a large value here just
# to catch extreme problems
_R_CHECK_PKG_SIZES_THRESHOLD_: 100
jobs:
test:
# yamllint disable-line rule:line-length
name: ${{ matrix.task }} (${{ matrix.os-display-name || matrix.os }}, ${{ matrix.compiler }}, R ${{ matrix.r_version }}, ${{ matrix.build_type }})
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
timeout-minutes: 60
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
################
# CMake builds #
################
- os: ubuntu-latest
task: r-package
compiler: gcc
r_version: 4.3
build_type: cmake
container: 'ubuntu:22.04'
os-display-name: 'ubuntu22.04'
- os: ubuntu-latest
task: r-package
compiler: clang
r_version: 4.3
build_type: cmake
container: 'ubuntu:22.04'
os-display-name: 'ubuntu22.04'
- os: macos-15-intel
task: r-package
compiler: clang
r_version: 4.3
build_type: cmake
container: null
- os: windows-latest
task: r-package
compiler: MINGW
toolchain: MSYS
r_version: 4.3
build_type: cmake
container: null
# Visual Studio 2022
- os: windows-2022
task: r-package
compiler: MSVC
toolchain: MSVC
r_version: 4.3
build_type: cmake
container: null
###############
# CRAN builds #
###############
- os: windows-latest
task: r-package
compiler: MINGW
toolchain: MSYS
r_version: 4.3
build_type: cran
container: null
- os: ubuntu-latest
task: r-package
compiler: gcc
r_version: 4.3
build_type: cran
container: 'ubuntu:22.04'
os-display-name: 'ubuntu22.04'
produces-artifacts: 'true'
artifact-name: r-cran-package
- os: macos-15-intel
task: r-package
compiler: clang
r_version: 4.3
build_type: cran
container: null
# macos-14 = arm64
- os: macos-14
task: r-package
compiler: clang
r_version: 4.3
build_type: cran
container: null
steps:
- name: Prevent conversion of line endings on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh
run: git config --global core.autocrlf false
- name: Install packages used by third-party actions
if: startsWith(matrix.os, 'ubuntu')
shell: bash
run: |
apt-get update -y
apt-get install --no-install-recommends -y \
ca-certificates \
dirmngr \
gpg \
gpg-agent \
software-properties-common \
sudo
# install newest version of git
# ref:
# - https://unix.stackexchange.com/a/170831/550004
# - https://git-scm.com/download/linux
add-apt-repository ppa:git-core/ppa -y
apt-get update -y
apt-get install --no-install-recommends -y \
git
- name: Trust git cloning LightGBM
if: startsWith(matrix.os, 'ubuntu')
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Install pandoc
uses: r-lib/actions/setup-pandoc@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0
- name: Install tinytex
if: startsWith(matrix.os, 'windows')
uses: r-lib/actions/setup-tinytex@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0
env:
CTAN_MIRROR: https://ctan.math.illinois.edu/systems/win32/miktex
TINYTEX_INSTALLER: TinyTeX
- name: Setup and run tests on Linux and macOS
if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu')
shell: bash
run: |
export TASK="${{ matrix.task }}"
export COMPILER="${{ matrix.compiler }}"
if [[ "${{ matrix.os }}" =~ ^macos ]]; then
export OS_NAME="macos"
elif [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
export OS_NAME="linux"
export IN_UBUNTU_BASE_CONTAINER="true"
fi
export BUILD_DIRECTORY="$GITHUB_WORKSPACE"
export LGB_VER=$(head -n 1 "${BUILD_DIRECTORY}/VERSION.txt")
export PRODUCES_ARTIFACTS="${{ matrix.produces-artifacts }}"
export R_VERSION="${{ matrix.r_version }}"
export R_BUILD_TYPE="${{ matrix.build_type }}"
$GITHUB_WORKSPACE/.ci/setup.sh
$GITHUB_WORKSPACE/.ci/test.sh
- name: Setup and run tests on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh -command ". {0}"
run: |
$env:BUILD_SOURCESDIRECTORY = $env:GITHUB_WORKSPACE
$env:LGB_VER = (Get-Content -TotalCount 1 $env:BUILD_SOURCESDIRECTORY\VERSION.txt).trim().replace('rc', '-')
$env:TOOLCHAIN = "${{ matrix.toolchain }}"
$env:R_VERSION = "${{ matrix.r_version }}"
$env:R_BUILD_TYPE = "${{ matrix.build_type }}"
$env:COMPILER = "${{ matrix.compiler }}"
$env:TASK = "${{ matrix.task }}"
& "$env:GITHUB_WORKSPACE/.ci/test-windows.ps1"
- name: Upload artifacts
if: ${{ matrix.produces-artifacts == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact-name }}
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}/*.tar.gz
if-no-files-found: error
test-r-sanitizers:
name: r-sanitizers (ubuntu-latest, R-devel, ${{ matrix.compiler }} ASAN/UBSAN)
timeout-minutes: 60
runs-on: ubuntu-latest
container: wch1/r-debug # zizmor: ignore[unpinned-images]
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- r_customization: san
compiler: gcc
- r_customization: csan
compiler: clang
steps:
- name: Trust git cloning LightGBM
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Install packages
shell: bash
run: |
RDscript${{ matrix.r_customization }} ./.ci/install-r-deps.R --build --test
sh build-cran-package.sh --r-executable=RD${{ matrix.r_customization }}
RD${{ matrix.r_customization }} CMD INSTALL lightgbm_*.tar.gz || exit 1
- name: Run tests with sanitizers
shell: bash
run: |
cd R-package/tests
exit_code=0
RDscript${{ matrix.r_customization }} testthat.R >> tests.log 2>&1 || exit_code=-1
cat ./tests.log
exit ${exit_code}
test-r-extra-checks:
name: r-package (${{ matrix.image }}, R-devel)
timeout-minutes: 60
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# references:
# * CRAN "additional checks": https://cran.r-project.org/web/checks/check_issue_kinds.html
# * images: https://r-hub.github.io/containers/containers.html
#
# Generally, only images that are still supported, listed at
# https://r-hub.github.io/containers/#available-containers, should be used.
image:
- clang21
- clang22
- gcc16
- rchk
- ubuntu-clang
- ubuntu-gcc12
runs-on: ubuntu-latest
container: ghcr.io/r-hub/containers/${{ matrix.image }}:latest # zizmor: ignore[unpinned-images]
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Install pandoc
uses: r-lib/actions/setup-pandoc@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0
- name: Install system dependencies
shell: bash
run: |
# libuv is needed for {fs} (https://github.com/lightgbm-org/LightGBM/issues/7208)
if type -f apt 2>&1 > /dev/null; then
apt-get update
apt-get install --no-install-recommends -y \
cmake \
devscripts \
libcurl4-openssl-dev \
libuv1-dev \
texinfo \
texlive-latex-extra \
texlive-latex-recommended \
texlive-fonts-recommended \
texlive-fonts-extra \
tidy \
qpdf
else
yum update -y
yum install -y \
cmake \
devscripts \
libcurl-devel \
libuv-devel \
qpdf \
texinfo \
texinfo-tex \
texlive-amsmath \
texlive-amsfonts \
texlive-collection-fontsrecommended \
texlive-collection-latexrecommended \
texlive-latex \
tidy
fi
- name: Install packages and run tests
shell: bash
run: |
Rscript ./.ci/install-r-deps.R --build --test --exclude=testthat
sh build-cran-package.sh
# 'rchk' isn't run through 'R CMD check', use the approach documented at
# https://r-hub.github.io/containers/local.html
if [[ "${{ matrix.image }}" =~ "rchk" ]]; then
r-check "$(pwd)" \
| tee ./rchk-logs.txt 2>&1
# the '-v' exceptions below are from R/rchk itself and not LightGBM:
# https://github.com/kalibera/rchk/issues/22#issuecomment-656036156
if grep -E '\[PB\]|ERROR' ./rchk-logs.txt \
| grep -v 'too many states' \
> /dev/null; \
then
echo "rchk found issues"
exit 1
else
echo "rchk did not find any issues"
exit 0
fi
fi
# why these packages?
#
# - 'curl' is not a dependency of {lightgbm}, but it's needed for 'R CMD check' URL checks
# - 'testthat' is not needed by 'rchk', so avoid installing it until here
#
Rscript -e "
install.packages(c('curl', 'testthat'),
repos = 'https://cran.rstudio.com',
Ncpus = parallel::detectCores())
"
if [[ "${{ matrix.image }}" =~ "clang" ]]; then
# allowing the following NOTEs (produced by default in the clang images):
#
# * checking compilation flags used ... NOTE
# Compilation used the following non-portable flag(s):
# -Wp,-D_FORTIFY_SOURCE=3
#
# even though CRAN itself sets that:
# https://www.stats.ox.ac.uk/pub/bdr/Rconfig/r-devel-linux-x86_64-fedora-clang
#
declare -i allowed_notes=1
else
declare -i allowed_notes=0
fi
bash .ci/run-r-cmd-check.sh \
"$(echo lightgbm_$(head -1 VERSION.txt).tar.gz)" \
"${allowed_notes}"
all-r-package-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- test
- test-r-sanitizers
- test-r-extra-checks
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+106
View File
@@ -0,0 +1,106 @@
name: R valgrind tests
# 'run-name' is used here to distinguish in the API between different runs from forks.
#
# When this was added, it was the only way to feed workflow inputs into something that
# would show up in the output of 'gh run list'.
#
# See https://github.com/orgs/community/discussions/73223#discussioncomment-11862624
run-name: R valgrind tests (pr=${{ inputs.pr-number }})
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
on:
workflow_dispatch:
inputs:
pr-branch:
type: string
description: |
Branch the PR was submitted from.
Branches from forks should be prefixed with the user/org they originate from,
like '{user}:{branch}'.
pr-number:
type: string
description: Pull request ID, found in the PR URL.
jobs:
test-r-valgrind:
name: r-package (ubuntu-latest, R-devel, valgrind)
timeout-minutes: 360
runs-on: ubuntu-latest
container: wch1/r-debug # zizmor: ignore[unpinned-images]
env:
GITHUB_TOKEN: ${{ github.token }}
permissions:
actions: write
checks: write
contents: read
id-token: write
pull-requests: write
statuses: write
steps:
- name: Install essential software before checkout
shell: bash
run: |
apt-get update
apt-get install --no-install-recommends -y \
curl \
jq
- name: Install GitHub CLI
run: |
GH_CLI_VERSION="2.83.0"
curl \
--fail \
-O \
-L \
https://github.com/cli/cli/releases/download/v${GH_CLI_VERSION}/gh_${GH_CLI_VERSION}_linux_amd64.tar.gz
tar -xvf ./gh_${GH_CLI_VERSION}_linux_amd64.tar.gz
mv ./gh_${GH_CLI_VERSION}_linux_amd64/bin/gh /usr/local/bin/
- name: Trust git cloning LightGBM
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
submodules: true
persist-credentials: false
repository: lightgbm-org/LightGBM
ref: "refs/pull/${{ inputs.pr-number }}/merge"
- name: Run tests with valgrind
shell: bash
run: ./.ci/test-r-package-valgrind.sh
- name: Send final status
if: ${{ always() }}
env:
GITHUB__WORKFLOW: ${{ github.workflow }}
INPUTS__PR_NUMBER: ${{ inputs.pr-number }}
JOB__STATUS: ${{ job.status }}
run: |
$GITHUB_WORKSPACE/.ci/set-commit-status.sh \
"${GITHUB_WORKFLOW}" \
"${JOB__STATUS}" \
"${{ github.sha }}"
comment="Workflow **${GITHUB__WORKFLOW}** has been triggered! 🚀\r\n"
comment="${comment}\r\n${GITHUB_SERVER_URL}/lightgbm-org/LightGBM/actions/runs/${GITHUB_RUN_ID} \r\n"
comment="${comment}\r\nStatus: ${JOB__STATUS}"
$GITHUB_WORKSPACE/.ci/append-comment.sh \
"${INPUTS__PR_NUMBER}" \
"${comment}"
- name: Rerun workflow-indicator
if: ${{ always() }}
env:
INPUTS__PR_BRANCH: ${{ inputs.pr-branch }}
run: |
bash $GITHUB_WORKSPACE/.ci/rerun-workflow.sh \
"optional_checks.yml" \
"${INPUTS__PR_BRANCH}" \
|| true
+29
View File
@@ -0,0 +1,29 @@
name: Release Drafter
on:
push:
branches:
- main
# only 1 job running in the repo at any time
concurrency:
group: lock
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
jobs:
updateReleaseDraft:
permissions:
contents: write
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
with:
config-name: release-drafter.yml
disable-autolabeler: true
env:
GITHUB_TOKEN: ${{ github.token }}
+122
View File
@@ -0,0 +1,122 @@
# contains non-functional tests, like checks on docs
# and code style
name: Static Analysis
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
COMPILER: 'gcc'
MAKEFLAGS: '-j4'
OS_NAME: 'linux'
jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: false
- name: Setup and run tests
shell: bash
run: |
export TASK=lint
export CONDA=${HOME}/miniforge
export PATH=${CONDA}/bin:$HOME/.local/bin:${PATH}
$GITHUB_WORKSPACE/.ci/setup.sh || exit 1
$GITHUB_WORKSPACE/.ci/lint-all.sh || exit 1
check-docs:
name: check-docs
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Setup and run tests
shell: bash
run: |
export TASK=check-docs
export CONDA=${HOME}/miniforge
export PATH=${CONDA}/bin:$HOME/.local/bin:${PATH}
$GITHUB_WORKSPACE/.ci/setup.sh || exit 1
$GITHUB_WORKSPACE/.ci/build-docs.sh || exit 1
r-check-docs:
name: r-package-check-docs
timeout-minutes: 60
runs-on: ubuntu-latest
container: rocker/verse # zizmor: ignore[unpinned-images]
permissions:
contents: read
steps:
- name: Trust git cloning LightGBM
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Install packages
shell: bash
run: |
Rscript ./.ci/install-r-deps.R --build --include=roxygen2
sh build-cran-package.sh || exit 1
R CMD INSTALL --with-keep.source lightgbm_*.tar.gz || exit 1
- name: Test documentation
shell: bash --noprofile --norc {0}
run: |
Rscript .ci/build-docs.R || exit 1
num_doc_files_changed=$(
git diff --name-only | grep --count -E "\.Rd|NAMESPACE"
)
if [[ ${num_doc_files_changed} -gt 0 ]]; then
echo "Some R documentation files have changed. Please re-generate them and commit those changes."
echo ""
echo " sh build-cran-package.sh"
echo " R CMD INSTALL --with-keep.source lightgbm_*.tar.gz"
echo " Rscript -e \"roxygen2::roxygenize('R-package/', load = 'installed')\""
echo ""
exit 1
fi
all-static-analysis-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- lint
- check-docs
- r-check-docs
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+106
View File
@@ -0,0 +1,106 @@
name: SWIG
on:
push:
branches:
- main
pull_request:
branches:
- main
- master
# automatically cancel in-progress builds if another commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# default to 0 permissions
# (job-level overrides add the minimal permissions needed)
permissions:
contents: none
env:
# tell scripts where to put artifacts
# (this variable name is left over from when jobs ran on Azure DevOps)
BUILD_ARTIFACTSTAGINGDIRECTORY: '${{ github.workspace }}/artifacts'
# in CMake-driven builds, parallelize compilation
CMAKE_BUILD_PARALLEL_LEVEL: 4
# all SWIG jobs produce artifacts
PRODUCES_ARTIFACTS: 'true'
# all jobs here have the same 'TASK'
TASK: swig
jobs:
test-swig:
name: swig (${{ matrix.os-display-name || matrix.os }}, ${{ matrix.compiler }})
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
timeout-minutes: 60
permissions:
contents: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
compiler: gcc
container: 'ghcr.io/lightgbm-org/lightgbm-vsts-agent:manylinux_2_28_x86_64'
artifact-name: swig-linux-x86_64-jar
os-display-name: 'manylinux_2_28'
- os: macos-15-intel
compiler: clang
container: null
artifact-name: swig-macos-x86_64-jar
# Visual Studio 2022
- os: windows-2022
compiler: msvc
container: null
artifact-name: swig-windows-x86_64-jar
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 5
persist-credentials: false
submodules: true
- name: Setup and run tests on Linux and macOS
if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu')
shell: bash
run: |
export BUILD_DIRECTORY="${GITHUB_WORKSPACE}"
export COMPILER="${{ matrix.compiler }}"
export CONDA="${HOME}/miniforge"
export PATH=${CONDA}/bin:${PATH}
if [[ "${{ matrix.os }}" =~ ^macos ]]; then
export OS_NAME="macos"
elif [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
export OS_NAME="linux"
fi
$GITHUB_WORKSPACE/.ci/setup.sh
$GITHUB_WORKSPACE/.ci/test.sh
- name: Setup and run tests on Windows
if: startsWith(matrix.os, 'windows')
shell: pwsh -command ". {0}"
run: |
$env:BUILD_DIRECTORY = $env:GITHUB_WORKSPACE
$env:BUILD_SOURCESDIRECTORY = $env:BUILD_DIRECTORY
& "$env:GITHUB_WORKSPACE/.ci/test-windows.ps1"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact-name }}
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}/*.jar
if-no-files-found: error
all-swig-jobs-successful:
if: always()
runs-on: ubuntu-latest
needs:
- test-swig
permissions:
statuses: read
steps:
- name: Note that all tests succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+486
View File
@@ -0,0 +1,486 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Bb]uild/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
nuget/
# The packages folder can be ignored because of Package Restore
**/packages/
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
.*.swp
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
/windows/LightGBM.VC.db
/lightgbm
/testlightgbm
# Created by https://www.gitignore.io/api/python
### Python ###
!/python-package/lightgbm/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
prof/
*.prof
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
.ruff_cache/
cover/
**/coverage.html
**/coverage.html.zip
**/Rplots.pdf
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
docs/pythonapi/
*.flag
# Doxygen documentation
docs/doxyoutput/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
Untitled*.ipynb
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# R testing artefact
lightgbm.model
# saved or dumped model/data
*.model
*.pkl
*.bin
*.h5
# macOS
**/.DS_Store
# VSCode
.vscode
# IntelliJ/CLion
.idea
*.iml
/cmake-build-debug/
# Files from local Python install
lightgbm-python/
python-package/LICENSE
python-package/build_cpp/
python-package/compile/
python-package/lightgbm/VERSION.txt
# R build artefacts
**/autom4te.cache/
R-package/conftest*
R-package/config.status
!R-package/data/agaricus.test.rda
!R-package/data/agaricus.train.rda
!R-package/data/bank.rda
R-package/docs
R-package/src/CMakeLists.txt
R-package/src/Makevars
R-package/src/lib_lightgbm.so.dSYM/
R-package/src/src/
R-package/src-x64
R-package/src-i386
R-package/**/VERSION.txt
**/Makevars.win
lightgbm_r/
lightgbm*.tar.gz
lightgbm*.tgz
lightgbm.Rcheck/
miktex*.zip
*.def
# Files created by examples and tests
*.buffer
**/lgb-Dataset.data
**/lgb.Dataset.data
**/model.txt
**/lgb-model.txt
examples/**/*.txt
tests/distributed/mlist.txt
tests/distributed/train*
tests/distributed/model*
tests/distributed/predict*
# Files from interactive R sessions
.Rproj.user
**/.Rapp.history
**/.Rhistory
*.rda
*.RData
*.rds
# Files generated by aspell
**/*.bak
# GraphViz artifacts
*.gv
*.gv.*
# Files from local Dask work
dask-worker-space/
# credentials and key material
*.env
*.pem
*.pub
*.rdp
*_rsa
# hipify-perl -inplace leaves behind *.prehip files
*.prehip
# pixi environments
.pixi
!.pixi/config.toml
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# files created by release tasks
/release-artifacts
+15
View File
@@ -0,0 +1,15 @@
[submodule "include/boost/compute"]
path = external_libs/compute
url = https://github.com/boostorg/compute
[submodule "eigen"]
path = external_libs/eigen
url = https://gitlab.com/libeigen/eigen.git
[submodule "external_libs/fmt"]
path = external_libs/fmt
url = https://github.com/fmtlib/fmt.git
[submodule "external_libs/fast_double_parser"]
path = external_libs/fast_double_parser
url = https://github.com/lemire/fast_double_parser.git
[submodule "external_libs/nanoarrow"]
path = external_libs/nanoarrow
url = https://github.com/apache/arrow-nanoarrow.git
+115
View File
@@ -0,0 +1,115 @@
# exclude files which are auto-generated by build tools
exclude: |
(?x)^(
build|
external_libs|
lightgbm-python|
lightgbm_r|
\.pixi|
)$
|R-package/configure$
|R-package/inst/Makevars$
|R-package/inst/Makevars.win$
|R-package/man/.*Rd$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-toml
- id: check-xml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/cmake-lint/cmake-lint
rev: '1.4.3'
hooks:
- id: cmakelint
args: ["--linelength=120"]
- repo: https://github.com/cpplint/cpplint
rev: '2.0.2'
hooks:
- id: cpplint
args:
- --root=.. # workaround to get correct header guard pattern
- --recursive
- --filter=-build/include_subdir,-whitespace/line_length
- repo: local
hooks:
- id: check-omp-pragmas
name: check-omp-pragmas
entry: sh
args:
- .ci/check-omp-pragmas.sh
language: system
pass_filenames: false
- repo: https://github.com/adrienverge/yamllint
rev: v1.38.0
hooks:
- id: yamllint
args: ["--strict"]
- repo: local
hooks:
- id: regenerate-parameters
name: regenerate-parameters
entry: python
args:
- ./.ci/parameter-generator.py
language: python
pass_filenames: false
- repo: https://github.com/rstcheck/rstcheck
rev: v6.2.5
hooks:
- id: rstcheck
args: ["--config", "./python-package/pyproject.toml"]
additional_dependencies:
- breathe>=4.36.0
- sphinx>=8.1.3
- sphinx_rtd_theme>=3.0.1
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.20
hooks:
- id: ruff-check
args: ["--config", "python-package/pyproject.toml"]
types_or: [python, jupyter]
- id: ruff-format
args: ["--config", "python-package/pyproject.toml"]
types_or: [python, jupyter]
- repo: https://github.com/biomejs/pre-commit
rev: v2.5.2
hooks:
- id: biome-ci
args:
- --config-path=./biome.json
- --diagnostic-level=info
- --error-on-warnings
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
- repo: https://github.com/crate-ci/typos
rev: v1.48.0
hooks:
- id: typos
args: ["--force-exclude"]
exclude: (\.gitignore$)|(^\.editorconfig$)
- repo: https://github.com/henryiii/validate-pyproject-schema-store
rev: 2026.06.30
hooks:
- id: validate-pyproject
files: python-package/pyproject.toml$
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v2.1.0
hooks:
- id: mypy
args: ["--config-file", "python-package/pyproject.toml", "python-package/"]
pass_filenames: false
verbose: true
additional_dependencies:
- matplotlib>=3.9.1
- narwhals>=1.15
- pandas>=2.0
- scikit-learn>=1.5.2
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.26.1
hooks:
- id: zizmor
+16
View File
@@ -0,0 +1,16 @@
version: 2
build:
os: "ubuntu-24.04"
tools:
python: "miniforge3-latest"
conda:
environment: docs/env.yml
formats:
- pdf
sphinx:
builder: html
configuration: docs/conf.py
fail_on_warning: true
submodules:
include: all
recursive: true
+22
View File
@@ -0,0 +1,22 @@
default.extend-ignore-re = [
"/Ot",
"mis-alignment",
"mis-spelled",
"posix-seh-rt",
]
[default.extend-words]
MAPE = "MAPE"
datas = "datas"
indx = "indx"
interprete = "interprete"
mape = "mape"
splitted = "splitted"
[default.extend-identifiers]
ERRORs = "ERRORs"
GAM = "GAM"
ND24s = "ND24s"
WARNINGs = "WARNINGs"
fullset = "fullset"
thess = "thess"
+10
View File
@@ -0,0 +1,10 @@
# default config: https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
extends: default
rules:
document-start: disable
line-length:
max: 120
truthy:
# prevent treating GitHub Workflow "on" key as boolean value
check-keys: false
+866
View File
@@ -0,0 +1,866 @@
# Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
# Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root for license information.
cmake_minimum_required(VERSION 3.28)
option(USE_MPI "Enable MPI-based distributed learning" OFF)
option(USE_OPENMP "Enable OpenMP" ON)
option(USE_GPU "Enable GPU-accelerated training" OFF)
option(USE_SWIG "Enable SWIG to generate Java API" OFF)
option(USE_TIMETAG "Set to ON to output time costs" OFF)
option(USE_CUDA "Enable CUDA-accelerated training " OFF)
option(USE_ROCM "Enable ROCm-accelerated training " OFF)
option(USE_DEBUG "Set to ON for Debug mode" OFF)
option(USE_SANITIZER "Use sanitizer flags" OFF)
set(
ENABLED_SANITIZERS
"address" "leak" "undefined"
CACHE
STRING
"Semicolon separated list of sanitizer names, e.g., 'address;leak'. \
Supported sanitizers are address, leak, undefined and thread."
)
option(USE_HOMEBREW_FALLBACK "(macOS-only) also look in 'brew --prefix' for libraries (e.g. OpenMP)" ON)
option(BUILD_CLI "Build the 'lightgbm' command-line interface in addition to lib_lightgbm" ON)
option(BUILD_CPP_TEST "Build C++ tests with Google Test" OFF)
option(BUILD_STATIC_LIB "Build static library" OFF)
option(INSTALL_HEADERS "Install headers to CMAKE_INSTALL_PREFIX (e.g. '/usr/local/include')" ON)
option(__BUILD_FOR_PYTHON "Set to ON if building lib_lightgbm for use with the Python-package" OFF)
option(__BUILD_FOR_R "Set to ON if building lib_lightgbm for use with the R-package" OFF)
option(__INTEGRATE_OPENCL "Set to ON if building LightGBM with the OpenCL ICD Loader and its dependencies included" OFF)
# If using Visual Studio generators, always target v10.x of the Windows SDK.
# Doing this avoids lookups that could fall back to very old versions, e.g. by finding
# outdated registry entries.
# ref: https://cmake.org/cmake/help/latest/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.html
if(CMAKE_GENERATOR MATCHES "Visual Studio")
set(CMAKE_SYSTEM_VERSION 10.0 CACHE INTERNAL "target Windows SDK version" FORCE)
endif()
project(lightgbm LANGUAGES C CXX)
include(cmake/Utils.cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules")
#-- Sanitizer
if(USE_SANITIZER)
if(MSVC)
message(FATAL_ERROR "Sanitizers are not supported with MSVC.")
endif()
include(cmake/Sanitizer.cmake)
enable_sanitizers("${ENABLED_SANITIZERS}")
endif()
if(__INTEGRATE_OPENCL)
set(__INTEGRATE_OPENCL ON CACHE BOOL "" FORCE)
set(USE_GPU OFF CACHE BOOL "" FORCE)
message(STATUS "Building library with integrated OpenCL components")
endif()
if(__BUILD_FOR_PYTHON OR __BUILD_FOR_R OR USE_SWIG)
# the SWIG wrapper, the Python and R packages don't require the CLI
set(BUILD_CLI OFF)
# installing the SWIG wrapper, the R and Python packages shouldn't place LightGBM's headers
# outside of where the package is installed
set(INSTALL_HEADERS OFF)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.2")
message(FATAL_ERROR "Insufficient gcc version (${CMAKE_CXX_COMPILER_VERSION})")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "3.8")
message(FATAL_ERROR "Insufficient Clang version (${CMAKE_CXX_COMPILER_VERSION})")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.1.0")
message(FATAL_ERROR "Insufficient AppleClang version (${CMAKE_CXX_COMPILER_VERSION})")
endif()
elseif(MSVC)
if(MSVC_VERSION LESS 1900)
message(FATAL_ERROR "Insufficient MSVC version (${MSVC_VERSION})")
endif()
endif()
if(USE_SWIG)
find_package(SWIG REQUIRED)
find_package(Java REQUIRED)
find_package(JNI REQUIRED)
include(UseJava)
include(UseSWIG)
set(SWIG_CXX_EXTENSION "cxx")
set(SWIG_EXTRA_LIBRARIES "")
set(SWIG_JAVA_EXTRA_FILE_EXTENSIONS ".java" "JNI.java")
set(SWIG_MODULE_JAVA_LANGUAGE "JAVA")
set(SWIG_MODULE_JAVA_SWIG_LANGUAGE_FLAG "java")
set(CMAKE_SWIG_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/java")
include_directories(Java_INCLUDE_DIRS)
include_directories(JNI_INCLUDE_DIRS)
include_directories($ENV{JAVA_HOME}/include)
if(WIN32)
set(LGBM_SWIG_DESTINATION_DIR "${CMAKE_CURRENT_BINARY_DIR}/com/microsoft/ml/lightgbm/windows/x86_64")
include_directories($ENV{JAVA_HOME}/include/win32)
elseif(APPLE)
set(LGBM_SWIG_DESTINATION_DIR "${CMAKE_CURRENT_BINARY_DIR}/com/microsoft/ml/lightgbm/osx/x86_64")
include_directories($ENV{JAVA_HOME}/include/darwin)
else()
set(LGBM_SWIG_DESTINATION_DIR "${CMAKE_CURRENT_BINARY_DIR}/com/microsoft/ml/lightgbm/linux/x86_64")
include_directories($ENV{JAVA_HOME}/include/linux)
endif()
file(MAKE_DIRECTORY "${LGBM_SWIG_DESTINATION_DIR}")
endif()
set(EIGEN_DIR "${PROJECT_SOURCE_DIR}/external_libs/eigen")
include_directories(${EIGEN_DIR})
# See https://gitlab.com/libeigen/eigen/-/blob/master/COPYING.README
add_definitions(-DEIGEN_MPL2_ONLY)
add_definitions(-DEIGEN_DONT_PARALLELIZE)
set(FAST_DOUBLE_PARSER_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/external_libs/fast_double_parser/include")
include_directories(${FAST_DOUBLE_PARSER_INCLUDE_DIR})
set(FMT_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/external_libs/fmt/include")
include_directories(${FMT_INCLUDE_DIR})
if(BUILD_CPP_TEST AND MSVC)
# Use /MT flag to statically link the C runtime. Must be set before
# add_subdirectory(nanoarrow) so the bundled library uses the same runtime
# as the test executable; otherwise linking fails with unresolved __imp_*
# symbols (e.g. __imp_realloc).
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
# nanoarrow is only needed by the Arrow-based C API entry points, which are
# disabled for the R package (the R API never calls them). Skipping the
# subdirectory keeps the package tarball free of nanoarrow sources, which
# would otherwise trigger CRAN warnings about diagnostic-suppressing pragmas.
if(NOT __BUILD_FOR_R)
add_subdirectory("${PROJECT_SOURCE_DIR}/external_libs/nanoarrow" EXCLUDE_FROM_ALL)
if(NOT BUILD_STATIC_LIB)
set_target_properties(nanoarrow_static PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
endif()
if(__BUILD_FOR_R)
find_package(LibR REQUIRED)
message(STATUS "LIBR_EXECUTABLE: ${LIBR_EXECUTABLE}")
message(STATUS "LIBR_INCLUDE_DIRS: ${LIBR_INCLUDE_DIRS}")
message(STATUS "LIBR_LIBS_DIR: ${LIBR_LIBS_DIR}")
message(STATUS "LIBR_CORE_LIBRARY: ${LIBR_CORE_LIBRARY}")
include_directories(${LIBR_INCLUDE_DIRS})
add_definitions(-DLGB_R_BUILD)
endif()
if(USE_TIMETAG)
add_definitions(-DTIMETAG)
endif()
if(USE_DEBUG)
add_definitions(-DDEBUG)
endif()
if(USE_MPI)
find_package(MPI REQUIRED)
add_definitions(-DUSE_MPI)
else()
add_definitions(-DUSE_SOCKET)
endif()
if(USE_CUDA)
set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}")
find_package(CUDAToolkit 11.8 REQUIRED)
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
compute_cmake_cuda_archs("${CUDAToolkit_VERSION}")
endif()
enable_language(CUDA)
set(USE_OPENMP ON CACHE BOOL "CUDA requires OpenMP" FORCE)
endif()
if(USE_ROCM)
enable_language(HIP)
set(USE_OPENMP ON CACHE BOOL "ROCm requires OpenMP" FORCE)
endif()
if(USE_OPENMP)
if(APPLE)
find_package(OpenMP)
if(NOT OpenMP_FOUND)
if(USE_HOMEBREW_FALLBACK)
# libomp 15.0+ from brew is keg-only, so have to search in other locations.
# See https://github.com/Homebrew/homebrew-core/issues/112107#issuecomment-1278042927.
execute_process(COMMAND brew --prefix libomp
OUTPUT_VARIABLE HOMEBREW_LIBOMP_PREFIX
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include")
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include")
set(OpenMP_C_LIB_NAMES omp)
set(OpenMP_CXX_LIB_NAMES omp)
set(OpenMP_omp_LIBRARY ${HOMEBREW_LIBOMP_PREFIX}/lib/libomp.dylib)
endif()
find_package(OpenMP REQUIRED)
endif()
else()
find_package(OpenMP REQUIRED)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()
if(USE_GPU)
set(BOOST_COMPUTE_HEADER_DIR ${PROJECT_SOURCE_DIR}/external_libs/compute/include)
include_directories(${BOOST_COMPUTE_HEADER_DIR})
find_package(OpenCL REQUIRED)
include_directories(${OpenCL_INCLUDE_DIRS})
message(STATUS "OpenCL include directory: " ${OpenCL_INCLUDE_DIRS})
if(WIN32)
set(Boost_USE_STATIC_LIBS ON)
endif()
find_package(Boost 1.56.0 COMPONENTS filesystem system REQUIRED)
if(WIN32)
# disable autolinking in boost
add_definitions(-DBOOST_ALL_NO_LIB)
endif()
include_directories(${Boost_INCLUDE_DIRS})
add_definitions(-DUSE_GPU)
endif()
if(__INTEGRATE_OPENCL)
if(APPLE)
message(FATAL_ERROR "Integrated OpenCL build is not available on macOS")
else()
include(cmake/IntegratedOpenCL.cmake)
add_definitions(-DUSE_GPU)
endif()
endif()
if(USE_CUDA)
find_package(NCCL REQUIRED)
include_directories(${CUDAToolkit_INCLUDE_DIRS})
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=${OpenMP_CXX_FLAGS} -Xcompiler=-fPIC -Xcompiler=-Wall")
if(USE_DEBUG)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -g")
else()
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O3 -lineinfo")
endif()
message(STATUS "CMAKE_CUDA_FLAGS: ${CMAKE_CUDA_FLAGS}")
add_definitions(-DUSE_CUDA)
if(NOT DEFINED CMAKE_CUDA_STANDARD)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
endif()
endif()
if(USE_ROCM)
find_package(HIP)
include_directories(${HIP_INCLUDE_DIRS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__HIP_PLATFORM_AMD__")
set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} ${OpenMP_CXX_FLAGS} -fPIC -Wall")
# avoid warning: unused variable 'mask' due to __shfl_down_sync work-around
set(DISABLED_WARNINGS "${DISABLED_WARNINGS} -Wno-unused-variable")
# avoid warning: 'hipHostAlloc' is deprecated: use hipHostMalloc instead
set(DISABLED_WARNINGS "${DISABLED_WARNINGS} -Wno-deprecated-declarations")
# avoid many warnings about missing overrides
set(DISABLED_WARNINGS "${DISABLED_WARNINGS} -Wno-inconsistent-missing-override")
# avoid warning: shift count >= width of type in feature_histogram.hpp
set(DISABLED_WARNINGS "${DISABLED_WARNINGS} -Wno-shift-count-overflow")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DISABLED_WARNINGS}")
set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} ${DISABLED_WARNINGS}")
if(USE_DEBUG)
set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} -g -O0")
else()
set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} -O3")
endif()
message(STATUS "CMAKE_HIP_FLAGS: ${CMAKE_HIP_FLAGS}")
# Building for ROCm almost always means USE_CUDA.
# Exceptions to this will be guarded by USE_ROCM.
add_definitions(-DUSE_CUDA)
add_definitions(-DUSE_ROCM)
endif()
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
#include <xmmintrin.h>
int main() {
int a = 0;
_mm_prefetch(&a, _MM_HINT_NTA);
return 0;
}
" MM_PREFETCH)
if(${MM_PREFETCH})
message(STATUS "Using _mm_prefetch")
add_definitions(-DMM_PREFETCH)
endif()
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
#include <mm_malloc.h>
int main() {
char *a = (char*)_mm_malloc(8, 16);
_mm_free(a);
return 0;
}
" MM_MALLOC)
if(${MM_MALLOC})
message(STATUS "Using _mm_malloc")
add_definitions(-DMM_MALLOC)
endif()
if(UNIX OR MINGW OR CYGWIN)
set(
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -pthread -Wextra -Wall -Wno-ignored-attributes -Wno-unknown-pragmas -Wno-return-type"
)
if(MINGW)
# ignore this warning: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
set(
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -Wno-stringop-overflow"
)
endif()
if(USE_DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
if(USE_SWIG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing")
endif()
if(NOT USE_OPENMP)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas -Wno-unused-private-field")
endif()
if(__BUILD_FOR_R AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-cast-function-type")
endif()
endif()
if(WIN32 AND MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++")
endif()
# Check if inet_pton is already available, to avoid conflicts with the implementation in LightGBM.
# As of 2022, MinGW started including a definition of inet_pton.
if(WIN32)
include(CheckSymbolExists)
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32")
check_symbol_exists(inet_pton "ws2tcpip.h" WIN_INET_PTON_FOUND)
if(WIN_INET_PTON_FOUND)
add_definitions(-DWIN_HAS_INET_PTON)
endif()
list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "ws2_32")
endif()
if(MSVC)
# compiling 'fmt' on MSVC: "Unicode support requires compiling with /utf-8"
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP /utf-8")
if(__BUILD_FOR_R)
# MSVC does not like this commit:
# https://github.com/wch/r-source/commit/fb52ac1a610571fcb8ac92d886b9fefcffaa7d48
#
# and raises "error C3646: 'private_data_c': unknown override specifier"
add_definitions(-DR_LEGACY_RCOMPLEX)
endif()
if(USE_DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Od")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /Ob2 /Oi /Ot /Oy")
endif()
else()
if(NOT BUILD_STATIC_LIB)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()
if(NOT USE_DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funroll-loops")
endif()
endif()
set(LightGBM_HEADER_DIR ${PROJECT_SOURCE_DIR}/include)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
include_directories(${LightGBM_HEADER_DIR})
if(USE_MPI)
include_directories(${MPI_CXX_INCLUDE_PATH})
endif()
set(
LGBM_SOURCES
src/boosting/boosting.cpp
src/boosting/gbdt_model_text.cpp
src/boosting/gbdt_prediction.cpp
src/boosting/gbdt.cpp
src/boosting/prediction_early_stop.cpp
src/boosting/sample_strategy.cpp
src/io/bin.cpp
src/io/config_auto.cpp
src/io/config.cpp
src/io/dataset_loader.cpp
src/io/dataset.cpp
src/io/file_io.cpp
src/io/json11.cpp
src/io/metadata.cpp
src/io/parser.cpp
src/io/train_share_states.cpp
src/io/tree.cpp
src/metric/dcg_calculator.cpp
src/metric/metric.cpp
src/network/linker_topo.cpp
src/network/linkers_mpi.cpp
src/network/linkers_socket.cpp
src/network/network.cpp
src/objective/objective_function.cpp
src/treelearner/data_parallel_tree_learner.cpp
src/treelearner/feature_histogram.cpp
src/treelearner/feature_parallel_tree_learner.cpp
src/treelearner/gpu_tree_learner.cpp
src/treelearner/gradient_discretizer.cpp
src/treelearner/linear_tree_learner.cpp
src/treelearner/serial_tree_learner.cpp
src/treelearner/tree_learner.cpp
src/treelearner/voting_parallel_tree_learner.cpp
src/utils/openmp_wrapper.cpp
)
set(
LGBM_CUDA_SOURCES
src/boosting/cuda/cuda_score_updater.cpp
src/boosting/cuda/cuda_score_updater.cu
src/boosting/cuda/nccl_gbdt.cpp
src/metric/cuda/cuda_binary_metric.cpp
src/metric/cuda/cuda_pointwise_metric.cpp
src/metric/cuda/cuda_regression_metric.cpp
src/metric/cuda/cuda_pointwise_metric.cu
src/objective/cuda/cuda_binary_objective.cpp
src/objective/cuda/cuda_multiclass_objective.cpp
src/objective/cuda/cuda_rank_objective.cpp
src/objective/cuda/cuda_regression_objective.cpp
src/objective/cuda/cuda_binary_objective.cu
src/objective/cuda/cuda_multiclass_objective.cu
src/objective/cuda/cuda_rank_objective.cu
src/objective/cuda/cuda_regression_objective.cu
src/treelearner/cuda/cuda_best_split_finder.cpp
src/treelearner/cuda/cuda_data_partition.cpp
src/treelearner/cuda/cuda_histogram_constructor.cpp
src/treelearner/cuda/cuda_leaf_splits.cpp
src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp
src/treelearner/cuda/cuda_best_split_finder.cu
src/treelearner/cuda/cuda_data_partition.cu
src/treelearner/cuda/cuda_gradient_discretizer.cu
src/treelearner/cuda/cuda_histogram_constructor.cu
src/treelearner/cuda/cuda_leaf_splits.cu
src/treelearner/cuda/cuda_single_gpu_tree_learner.cu
src/io/cuda/cuda_column_data.cu
src/io/cuda/cuda_tree.cu
src/io/cuda/cuda_column_data.cpp
src/io/cuda/cuda_metadata.cpp
src/io/cuda/cuda_row_data.cpp
src/io/cuda/cuda_tree.cpp
src/cuda/cuda_utils.cpp
src/cuda/cuda_algorithms.cu
)
if(USE_CUDA OR USE_ROCM)
list(APPEND LGBM_SOURCES ${LGBM_CUDA_SOURCES})
endif()
if(USE_ROCM)
set(CU_FILES "")
foreach(file IN LISTS LGBM_CUDA_SOURCES)
string(REGEX MATCH "\\.cu$" is_cu_file "${file}")
if(is_cu_file)
list(APPEND CU_FILES "${file}")
endif()
endforeach()
set_source_files_properties(${CU_FILES} PROPERTIES LANGUAGE HIP)
endif()
add_library(lightgbm_objs OBJECT ${LGBM_SOURCES})
if(NOT __BUILD_FOR_R)
target_link_libraries(lightgbm_objs PRIVATE nanoarrow_static)
endif()
if(BUILD_CLI)
add_executable(lightgbm src/main.cpp src/application/application.cpp)
target_link_libraries(lightgbm PRIVATE lightgbm_objs)
endif()
set(API_SOURCES "src/c_api.cpp")
# Only build the R part of the library if building for
# use with the R-package
if(__BUILD_FOR_R)
list(APPEND API_SOURCES "src/lightgbm_R.cpp")
endif()
add_library(lightgbm_capi_objs OBJECT ${API_SOURCES})
if(NOT __BUILD_FOR_R)
target_link_libraries(lightgbm_capi_objs PRIVATE nanoarrow_static)
endif()
if(BUILD_STATIC_LIB)
add_library(_lightgbm STATIC)
else()
add_library(_lightgbm SHARED)
endif()
# R expects libraries of the form <project>.{dll,dylib,so}, not lib_<project>.{dll,dylib,so}
if(__BUILD_FOR_R)
set_target_properties(
_lightgbm
PROPERTIES
PREFIX ""
OUTPUT_NAME "lightgbm"
)
endif()
# LightGBM headers include openmp, cuda, R etc. headers,
# thus PUBLIC is required for building _lightgbm_swig target.
target_link_libraries(_lightgbm PUBLIC lightgbm_capi_objs lightgbm_objs)
if(MSVC AND NOT __BUILD_FOR_R)
set_target_properties(_lightgbm PROPERTIES OUTPUT_NAME "lib_lightgbm")
endif()
if(USE_SWIG)
set_property(SOURCE swig/lightgbmlib.i PROPERTY CPLUSPLUS ON)
list(APPEND swig_options -package com.microsoft.ml.lightgbm)
set_property(SOURCE swig/lightgbmlib.i PROPERTY SWIG_FLAGS "${swig_options}")
swig_add_library(_lightgbm_swig LANGUAGE java SOURCES swig/lightgbmlib.i)
swig_link_libraries(_lightgbm_swig _lightgbm)
set_target_properties(
_lightgbm_swig
PROPERTIES
# needed to ensure Linux build does not have lib prefix specified twice, e.g. liblib_lightgbm_swig
PREFIX ""
# needed in some versions of CMake for VS and MinGW builds to ensure output dll has lib prefix
OUTPUT_NAME "lib_lightgbm_swig"
)
if(WIN32)
set(LGBM_SWIG_LIB_DESTINATION_PATH "${LGBM_SWIG_DESTINATION_DIR}/lib_lightgbm_swig.dll")
if(MINGW OR CYGWIN)
set(LGBM_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm.dll")
set(LGBM_SWIG_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm_swig.dll")
else()
set(LGBM_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm.dll")
set(LGBM_SWIG_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm_swig.dll")
endif()
elseif(APPLE)
set(LGBM_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm.dylib")
set(LGBM_SWIG_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm_swig.jnilib")
set(LGBM_SWIG_LIB_DESTINATION_PATH "${LGBM_SWIG_DESTINATION_DIR}/lib_lightgbm_swig.dylib")
else()
set(LGBM_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm.so")
set(LGBM_SWIG_LIB_SOURCE_PATH "${PROJECT_SOURCE_DIR}/lib_lightgbm_swig.so")
set(LGBM_SWIG_LIB_DESTINATION_PATH "${LGBM_SWIG_DESTINATION_DIR}/lib_lightgbm_swig.so")
endif()
add_custom_command(
TARGET _lightgbm_swig
POST_BUILD
COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java
COMMAND
"${CMAKE_COMMAND}"
-E
copy_if_different
"${LGBM_LIB_SOURCE_PATH}"
"${LGBM_SWIG_DESTINATION_DIR}"
COMMAND
"${CMAKE_COMMAND}"
-E
copy_if_different
"${LGBM_SWIG_LIB_SOURCE_PATH}"
"${LGBM_SWIG_LIB_DESTINATION_PATH}"
COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com
)
endif()
if(USE_MPI)
target_link_libraries(lightgbm_objs PUBLIC ${MPI_CXX_LIBRARIES})
endif()
if(USE_OPENMP)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_link_libraries(lightgbm_objs PUBLIC OpenMP::OpenMP_CXX)
# c_api headers also includes OpenMP headers, thus compiling
# lightgbm_capi_objs needs include directory for OpenMP.
# Specifying OpenMP in target_link_libraries will get include directory
# requirements for compilation.
# This uses CMake's Transitive Usage Requirements. Refer to CMake doc:
# https://cmake.org/cmake/help/v3.16/manual/cmake-buildsystem.7.html#transitive-usage-requirements
target_link_libraries(lightgbm_capi_objs PUBLIC OpenMP::OpenMP_CXX)
endif()
endif()
if(USE_GPU)
target_link_libraries(lightgbm_objs PUBLIC ${OpenCL_LIBRARY} ${Boost_LIBRARIES})
endif()
if(USE_CUDA)
target_link_libraries(lightgbm_objs PUBLIC NCCL::NCCL)
endif()
if(USE_ROCM)
find_package(rccl)
target_link_libraries(lightgbm_objs PUBLIC ${RCCL_LIBRARY})
endif()
if(__INTEGRATE_OPENCL)
# targets OpenCL and Boost are added in IntegratedOpenCL.cmake
add_dependencies(lightgbm_objs OpenCL Boost)
# variables INTEGRATED_OPENCL_* are set in IntegratedOpenCL.cmake
target_include_directories(lightgbm_objs PRIVATE ${INTEGRATED_OPENCL_INCLUDES})
target_compile_definitions(lightgbm_objs PRIVATE ${INTEGRATED_OPENCL_DEFINITIONS})
target_link_libraries(lightgbm_objs PUBLIC ${INTEGRATED_OPENCL_LIBRARIES} ${CMAKE_DL_LIBS})
endif()
if(USE_CUDA)
set_target_properties(
lightgbm_objs
PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
)
set_target_properties(
_lightgbm
PROPERTIES
CUDA_RESOLVE_DEVICE_SYMBOLS ON
)
if(BUILD_CLI)
set_target_properties(
lightgbm
PROPERTIES
CUDA_RESOLVE_DEVICE_SYMBOLS ON
)
endif()
endif()
if(USE_ROCM)
target_link_libraries(lightgbm_objs PUBLIC hip::host)
endif()
if(WIN32)
if(MINGW OR CYGWIN)
target_link_libraries(lightgbm_objs PUBLIC ws2_32 iphlpapi)
endif()
endif()
if(__BUILD_FOR_R)
# utils/log.h and capi uses R headers, thus both object libraries need to link
# with R lib.
if(MSVC)
set(R_LIB ${LIBR_MSVC_CORE_LIBRARY})
else()
set(R_LIB ${LIBR_CORE_LIBRARY})
endif()
target_link_libraries(lightgbm_objs PUBLIC ${R_LIB})
target_link_libraries(lightgbm_capi_objs PUBLIC ${R_LIB})
endif()
#-- Google C++ tests
if(BUILD_CPP_TEST)
find_package(GTest CONFIG)
if(NOT GTEST_FOUND)
message(STATUS "Did not find Google Test in the system root. Fetching Google Test now...")
include(FetchContent)
# lint_cmake: -readability/wonkycase
FetchContent_Declare(
# lint_cmake: +readability/wonkycase
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# lint_cmake: -readability/wonkycase
FetchContent_MakeAvailable(googletest)
# lint_cmake: +readability/wonkycase
add_library(GTest::GTest ALIAS gtest)
endif()
set(LightGBM_TEST_HEADER_DIR ${PROJECT_SOURCE_DIR}/tests/cpp_tests)
include_directories(${LightGBM_TEST_HEADER_DIR})
set(
CPP_TEST_SOURCES
tests/cpp_tests/test_array_args.cpp
tests/cpp_tests/test_arrow.cpp
tests/cpp_tests/test_arrow_deprecated.cpp
tests/cpp_tests/test_byte_buffer.cpp
tests/cpp_tests/test_chunked_array.cpp
tests/cpp_tests/test_common.cpp
tests/cpp_tests/test_main.cpp
tests/cpp_tests/test_serialize.cpp
tests/cpp_tests/test_single_row.cpp
tests/cpp_tests/test_stream.cpp
tests/cpp_tests/testutils.cpp
)
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-")
endif()
add_executable(testlightgbm ${CPP_TEST_SOURCES})
target_link_libraries(testlightgbm PRIVATE lightgbm_objs lightgbm_capi_objs nanoarrow_static GTest::GTest)
endif()
if(BUILD_CLI)
install(
TARGETS lightgbm
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
)
endif()
if(__BUILD_FOR_PYTHON)
set(CMAKE_INSTALL_PREFIX "lightgbm")
endif()
# The macOS linker puts an absolute path to linked libraries in lib_lightgbm.dylib.
# This block overrides that information for LightGBM's OpenMP dependency, to allow
# finding that library in more places.
#
# This reduces the risk of runtime issues resulting from multiple {libgomp,libiomp,libomp}.dylib being loaded.
#
if(APPLE AND USE_OPENMP AND NOT BUILD_STATIC_LIB)
# store path to {libgomp,libiomp,libomp}.dylib found at build time in a variable
get_target_property(
OpenMP_LIBRARY_LOCATION
OpenMP::OpenMP_CXX
INTERFACE_LINK_LIBRARIES
)
# get just the filename of that path
# (to deal with the possibility that it might be 'libomp.dylib' or 'libgomp.dylib' or 'libiomp.dylib')
get_filename_component(
OpenMP_LIBRARY_NAME
${OpenMP_LIBRARY_LOCATION}
NAME
)
# get directory of that path
get_filename_component(
OpenMP_LIBRARY_DIR
${OpenMP_LIBRARY_LOCATION}
DIRECTORY
)
# get exact name of the library in a variable
get_target_property(
__LIB_LIGHTGBM_OUTPUT_NAME
_lightgbm
OUTPUT_NAME
)
if(NOT __LIB_LIGHTGBM_OUTPUT_NAME)
set(__LIB_LIGHTGBM_OUTPUT_NAME "lib_lightgbm")
endif()
if(CMAKE_SHARED_LIBRARY_SUFFIX_CXX)
set(
__LIB_LIGHTGBM_FILENAME "${__LIB_LIGHTGBM_OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX_CXX}"
CACHE INTERNAL "lightgbm shared library filename"
)
else()
set(
__LIB_LIGHTGBM_FILENAME "${__LIB_LIGHTGBM_OUTPUT_NAME}.dylib"
CACHE INTERNAL "lightgbm shared library filename"
)
endif()
# Override the absolute path to OpenMP with a relative one using @rpath.
#
# This also ensures that if a {libgomp,libiomp,libomp}.dylib has already been loaded, it'll just use that.
add_custom_command(
TARGET _lightgbm
POST_BUILD
COMMAND
install_name_tool
-change
${OpenMP_LIBRARY_LOCATION}
"@rpath/${OpenMP_LIBRARY_NAME}"
"${__LIB_LIGHTGBM_FILENAME}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Replacing hard-coded OpenMP install_name with '@rpath/${OpenMP_LIBRARY_NAME}'..."
)
# add RPATH entries to ensure the loader looks in the following, in the following order:
#
# - (R-only) ${LIBR_LIBS_DIR} (wherever R for macOS stores vendored third-party libraries)
# - ${OpenMP_LIBRARY_DIR} (wherever find_package(OpenMP) found OpenMP at build time)
# - /opt/homebrew/opt/libomp/lib (where 'brew install' / 'brew link' puts libomp.dylib)
# - /opt/local/lib/libomp (where 'port install' puts libomp.dylib)
#
# with some compilers, OpenMP ships with the compiler (e.g. libgomp with gcc)
list(APPEND __omp_install_rpaths "${OpenMP_LIBRARY_DIR}")
# with clang, libomp doesn't ship with the compiler and might be supplied separately
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(
APPEND __omp_install_rpaths
"/opt/homebrew/opt/libomp/lib"
"/opt/local/lib/libomp"
)
# It appears that CRAN's macOS binaries compiled with -fopenmp have install names
# of the form:
#
# /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libomp.dylib
#
# That corresponds to the libomp.dylib that ships with the R framework for macOS, available
# from https://cran.r-project.org/bin/macosx/.
#
# That absolute-path install name leads to that library being loaded unconditionally.
#
# That can result in e.g. 'library(data.table)' loading R's libomp.dylib and 'library(lightgbm)' loading
# Homebrew's. Having 2 loaded in the same process can lead to segfaults and unpredictable behavior.
#
# This can't be easily avoided by forcing R-package builds in LightGBM to use R's libomp.dylib
# at build time... LightGBM's CMake uses find_package(OpenMP), and R for macOS only provides the
# library, not CMake config files for it.
#
# Best we can do, to allow CMake-based builds of the R-package here to continue to work
# alongside CRAN-prepared binaries of other packages with OpenMP dependencies, is to
# ensure that R's library directory is the first place the loader searches for
# libomp.dylib when clang is used.
#
# ref: https://github.com/lightgbm-org/LightGBM/issues/6628
#
if(__BUILD_FOR_R)
list(PREPEND __omp_install_rpaths "${LIBR_LIBS_DIR}")
endif()
endif()
set_target_properties(
_lightgbm
PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "${__omp_install_rpaths}"
INSTALL_RPATH_USE_LINK_PATH FALSE
)
endif()
install(
TARGETS _lightgbm
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
)
if(INSTALL_HEADERS)
install(
DIRECTORY ${LightGBM_HEADER_DIR}/LightGBM
DESTINATION ${CMAKE_INSTALL_PREFIX}/include
)
install(
FILES ${FAST_DOUBLE_PARSER_INCLUDE_DIR}/fast_double_parser.h
DESTINATION ${CMAKE_INSTALL_PREFIX}/include/LightGBM/utils
)
install(
DIRECTORY ${FMT_INCLUDE_DIR}/
DESTINATION ${CMAKE_INSTALL_PREFIX}/include/LightGBM/utils
FILES_MATCHING PATTERN "*.h"
)
endif()
+41
View File
@@ -0,0 +1,41 @@
# Microsoft Open Source Code of Conduct
This code of conduct outlines expectations for participation in Microsoft-managed open source communities, as well as steps for reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all. People violating this code of conduct may be banned from the community.
## Our open source communities strive to:
* Be friendly and patient: Remember you might not be communicating in someone else's primary spoken or programming language, and others may not have your level of understanding.
* Be welcoming: Our communities welcome and support people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
* Be respectful: We are a world-wide community of professionals, and we conduct ourselves professionally. Disagreement is no excuse for poor behavior and poor manners. Disrespectful and unacceptable behavior includes, but is not limited to:
1. Violent threats or language.
2. Discriminatory or derogatory jokes and language.
3. Posting sexually explicit or violent material.
4. Posting, or threatening to post, people's personally identifying information ("doxing").
5. Insults, especially those using discriminatory terms or slurs.
Behavior that could be perceived as sexual attention.
Advocating for or encouraging any of the above behaviors.
* Understand disagreements: Disagreements, both social and technical, are useful learning opportunities. Seek to understand the other viewpoints and resolve differences constructively.
* This code is not exhaustive or complete. It serves to capture our common understanding of a productive, collaborative environment. We expect the code to be followed in spirit as much as in the letter.
## Scope
This code of conduct applies to all repos and communities for Microsoft-managed open source projects regardless of whether or not the repo explicitly calls out its use of this code. The code also applies in public spaces when an individual is representing a project or its community. Examples include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Note: Some Microsoft-managed communities have codes of conduct that pre-date this document and issue resolution process. While communities are not required to change their code, they are expected to use the resolution process outlined here. The review team will coordinate with the communities involved to address your concerns.
## Reporting Code of Conduct Issues
We encourage all communities to resolve issues on their own whenever possible. This builds a broader and deeper understanding and ultimately a healthier interaction. In the event that an issue cannot be resolved locally, please feel free to report your concerns by contacting opencode@microsoft.com. Your report will be handled in accordance with the issue resolution process described in the Code of Conduct FAQ.
In your report please include:
* Your contact information.
* Names (real, usernames or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well.
* Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public chat log), please include a link or attachment.
* Any additional information that may be helpful.
All reports will be reviewed by a multi-person team and will result in a response that is deemed necessary and appropriate to the circumstances. Where additional perspectives are needed, the team may seek insight from others with relevant expertise or experience. The confidentiality of the person reporting the incident will be kept at all times. Involved parties are never part of the review team.
Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the review team may take any action they deem appropriate, including a permanent ban from the community.
This code of conduct is based on the template established by the TODO Group and used by numerous other large communities (e.g., Facebook, Yahoo, Twitter, GitHub) and the Scope section from the Contributor Covenant version 1.4.
+28
View File
@@ -0,0 +1,28 @@
# contributing
LightGBM has been developed and used by many active community members.
Your help is very valuable to make it better for everyone.
## How to Contribute
- Check the [Feature Requests Hub](https://github.com/lightgbm-org/LightGBM/issues/2302), and submit pull requests to address chosen issue. If you need development guideline, you can check the [Development Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Development-Guide.rst) or directly ask us in Issues/Pull Requests.
- Contribute to the [tests](https://github.com/lightgbm-org/LightGBM/tree/main/tests) to make it more reliable.
- Contribute to the [documentation](https://github.com/lightgbm-org/LightGBM/tree/main/docs) to make it clearer for everyone.
- Contribute to the [examples](https://github.com/lightgbm-org/LightGBM/tree/main/examples) to share your experience with other users.
- Add your stories and experience to [Awesome LightGBM](https://github.com/lightgbm-org/LightGBM/blob/main/examples/README.md). If LightGBM helped you in a machine learning competition or some research application, we want to hear about it!
- [Open an issue](https://github.com/lightgbm-org/LightGBM/issues) to report problems or recommend new features.
## Development Guide
### Linting
Every commit in the repository is tested with multiple static analyzers.
When developing locally, run some of them using `pre-commit` ([pre-commit docs](https://pre-commit.com/)).
```shell
pre-commit run --all-files
```
That command will check for some issues and automatically reformat the code.
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) Microsoft Corporation
Copyright (c) The LightGBM developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+130
View File
@@ -0,0 +1,130 @@
# maintaining
This document is for LightGBM maintainers.
## Managing Dependencies
### Locked Environments with `pixi`
This project uses `pixi` for tasks and CI jobs that run with a locked set of dependencies.
In general, updating these environments looks like:
1. manually modify `pixi.toml`
2. run `pixi install`
And running inside one looks like:
```shell
# interactive shell
pixi shell -e py310
# run a task
pixi run -e py310 python -c "import pandas; print(pandas.__version__)"
```
See https://pixi.prefix.dev/latest/ for more details.
## Releasing
### Step 1: Put up a Release PR
Create a pull request into `main` which prepares the source code for release.
Copy the description and checklist from the previous release PR (for example: https://github.com/lightgbm-org/LightGBM/pull/6796).
This should usually also include a checklist of other issues and PRs that should be completed for the release,
and the PR should be used to discuss what makes it into the release.
### Step 2: Merge the Release PR
Once the PR is approved, merge it.
Do not merge any other PRs into `main` until the rest of the release is complete.
### Step 3: Wait for a New CI Run on `main`
Wait for all CI runs triggered by the merge to `main` to complete successfully.
These runs build and test the official artifacts that will be attached to the GitHub release and published to package managers.
### Step 4: Create a Release
Navigate to https://github.com/lightgbm-org/LightGBM/releases.
Click "edit" on the draft release that `release-drafter` has created there.
* update the tag and release title to match the version of LightGBM, in the format `v{major}.{minor}.{patch}`
* ensure that tag points at the commit on `main` created by merging the release PR
When you're satisfied with the state of the release, click "Publish release".
### Step 5: Upload Artifacts
After creating a release, run the following from the root of the repo to populate it with artifacts.
```shell
# download all artifacts to a local directory
./.ci/download-artifacts.sh ${COMMIT_ID}
# attach them to the GitHub release
gh release upload \
--repo lightgbm-org/LightGBM \
"${TAG}" \
./release-artifacts/*
```
Where:
* `COMMIT_ID` = full commit hash of the commit on `main` corresponding to the release
* `TAG` = the tag for the release (e.g. `v4.6.0`)
### Step 6: Complete All Other Post-merge Release Steps
These include things like publishing to package managers, updating build configs for repackagers like ``conda-forge``, and many other steps.
See the release checklist on the PR for details.
## Nightly Packages
Nightly packages for the `lightgbm` Python package are uploaded to https://anaconda.org/lightgbm-packages on every merge to `main`.
That's done using an upload token stored in a secret in CI.
Those tokens expire after 1 year.
To generate a new one, run the following.
```shell
# install Anaconda CLI
conda install -y -c conda-forge \
anaconda-auth \
anaconda-client
# authenticate locally
anaconda auth login
# create a token (this expires after 1 year)
TOKEN=$(
anaconda org auth \
--create \
--name nightly-uploads \
--org lightgbm-packages \
--scopes 'api:read api:write pypi:upload'
)
```
That token can be used by maintainers to manually upload packages as well.
For example:
```shell
./.ci/download-artifacts.sh $(git rev-parse HEAD)
# NOTE: set upload token in environment variable 'ANACONDA_API_TOKEN'
anaconda upload \
--package lightgbm \
--force-metadata-update \
-t pypi \
./release-artifacts/*.whl
```
+52
View File
@@ -0,0 +1,52 @@
\.appveyor\.yml
AUTOCONF_UBUNTU_VERSION
^autom4te.cache/.*$
^.*\.bin
^build_r.R$
\.clang-format
^.*\.clusterfuzzlite$
^cran-comments\.md$
^docs$
^.*\.dll
\.drone\.yml
^.*\.dylib
\.git
\.gitkeep$
^.*\.history
^Makefile$
^.*\.o
^.*\.out
^pkgdown$
^recreate-configure\.sh$
^.*\.so
^src/build/.*$
^src/CMakeLists.txt$
^src/external_libs/compute/.appveyor.yml$
^src/external_libs/compute/.coveralls.yml$
^src/external_libs/compute/.travis.yml$
^src/external_libs/compute/test/.*$
^src/external_libs/compute/index.html$
^src/external_libs/compute/.git$
^src/external_libs/compute/.gitignore$
^src/external_libs/compute/CONTRIBUTING.md$
^src/external_libs/compute/README.md$
src/external_libs/fast_double_parser/benchmarks
src/external_libs/fast_double_parser/Makefile
src/external_libs/fast_double_parser/.*\.md
src/external_libs/fast_double_parser/tests
src/external_libs/fast_double_parser/.*\.yaml
src/external_libs/fast_double_parser/.*\.yml
src/external_libs/fmt/.*\.md
src/external_libs/fmt/.travis.yml
src/external_libs/fmt/doc
src/external_libs/fmt/support/Android\.mk
src/external_libs/fmt/support/bazel/.bazel.*
src/external_libs/fmt/support/.*\.gradle
src/external_libs/fmt/support/.*\.pro
src/external_libs/fmt/support/.*\.py
src/external_libs/fmt/support/rtd
src/external_libs/fmt/support/.*sublime-syntax
src/external_libs/fmt/support/Vagrantfile
src/external_libs/fmt/support/.*\.xml
src/external_libs/fmt/support/.*\.yml
src/external_libs/fmt/test
+1
View File
@@ -0,0 +1 @@
2.71-2
+66
View File
@@ -0,0 +1,66 @@
Package: lightgbm
Type: Package
Title: Light Gradient Boosting Machine
Version: ~~VERSION~~
Date: ~~DATE~~
Authors@R: c(
person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut")),
person("Guolin", "Ke", email = "guolin.ke@outlook.com", role = c("aut")),
person("Damien", "Soukhavong", email = "damien.soukhavong@skema.edu", role = c("aut")),
person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut", "cre")),
person("Qi", "Meng", role = c("aut")),
person("Thomas", "Finley", role = c("aut")),
person("Taifeng", "Wang", role = c("aut")),
person("Wei", "Chen", role = c("aut")),
person("Weidong", "Ma", role = c("aut")),
person("Qiwei", "Ye", role = c("aut")),
person("Tie-Yan", "Liu", role = c("aut")),
person("Nikita", "Titov", role = c("aut")),
person("Yachen", "Yan", role = c("ctb")),
person("Microsoft Corporation", role = c("cph")),
person("Dropbox, Inc.", role = c("cph")),
person("Alberto", "Ferreira", role = c("ctb")),
person("Daniel", "Lemire", role = c("ctb")),
person("Victor", "Zverovich", role = c("cph")),
person("IBM Corporation", role = c("ctb")),
person("David", "Cortes", role = c("aut")),
person("Michael", "Mayer", role = c("ctb"))
)
Description: Tree based algorithms can be improved by introducing boosting frameworks.
'LightGBM' is one such framework, based on Ke, Guolin et al. (2017) <https://proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html>.
This package offers an R interface to work with it.
It is designed to be distributed and efficient with the following advantages:
1. Faster training speed and higher efficiency.
2. Lower memory usage.
3. Better accuracy.
4. Parallel learning supported.
5. Capable of handling large-scale data.
In recognition of these advantages, 'LightGBM' has been widely-used in many winning solutions of machine learning competitions.
Comparison experiments on public datasets suggest that 'LightGBM' can outperform existing boosting frameworks on both efficiency and accuracy, with significantly lower memory consumption. In addition, parallel experiments suggest that in certain circumstances, 'LightGBM' can achieve a linear speed-up in training time by using multiple machines.
Encoding: UTF-8
License: MIT + file LICENSE
URL: https://github.com/lightgbm-org/LightGBM
BugReports: https://github.com/lightgbm-org/LightGBM/issues
NeedsCompilation: yes
Biarch: true
VignetteBuilder: knitr
Suggests:
knitr,
markdown,
processx,
RhpcBLASctl,
testthat
Depends:
R (>= 4.0)
Imports:
R6 (>= 2.4.0),
data.table (>= 1.9.6),
graphics,
jsonlite (>= 1.0),
Matrix (>= 1.1-0),
methods,
parallel,
utils
SystemRequirements:
C++17
Config/roxygen2/version: 8.0.0
+2
View File
@@ -0,0 +1,2 @@
YEAR: 2016
COPYRIGHT HOLDER: Microsoft Corporation
+66
View File
@@ -0,0 +1,66 @@
# Generated by roxygen2: do not edit by hand
S3method("dimnames<-",lgb.Dataset)
S3method(dim,lgb.Dataset)
S3method(dimnames,lgb.Dataset)
S3method(get_field,lgb.Dataset)
S3method(predict,lgb.Booster)
S3method(print,lgb.Booster)
S3method(set_field,lgb.Dataset)
S3method(summary,lgb.Booster)
export(getLGBMthreads)
export(get_field)
export(lgb.Dataset)
export(lgb.Dataset.construct)
export(lgb.Dataset.create.valid)
export(lgb.Dataset.save)
export(lgb.Dataset.set.categorical)
export(lgb.Dataset.set.reference)
export(lgb.configure_fast_predict)
export(lgb.convert_with_rules)
export(lgb.cv)
export(lgb.drop_serialized)
export(lgb.dump)
export(lgb.get.eval.result)
export(lgb.importance)
export(lgb.interpret)
export(lgb.interprete)
export(lgb.load)
export(lgb.make_serializable)
export(lgb.model.dt.tree)
export(lgb.plot.importance)
export(lgb.plot.interpretation)
export(lgb.restore_handle)
export(lgb.save)
export(lgb.slice.Dataset)
export(lgb.train)
export(lightgbm)
export(setLGBMthreads)
export(set_field)
import(methods)
importClassesFrom(Matrix,CsparseMatrix)
importClassesFrom(Matrix,RsparseMatrix)
importClassesFrom(Matrix,dgCMatrix)
importClassesFrom(Matrix,dgRMatrix)
importClassesFrom(Matrix,dsparseMatrix)
importClassesFrom(Matrix,dsparseVector)
importFrom(Matrix,Matrix)
importFrom(R6,R6Class)
importFrom(data.table,":=")
importFrom(data.table,as.data.table)
importFrom(data.table,data.table)
importFrom(data.table,rbindlist)
importFrom(data.table,set)
importFrom(data.table,setnames)
importFrom(data.table,setorder)
importFrom(data.table,setorderv)
importFrom(graphics,barplot)
importFrom(graphics,par)
importFrom(jsonlite,fromJSON)
importFrom(methods,is)
importFrom(methods,new)
importFrom(parallel,detectCores)
importFrom(stats,quantile)
importFrom(utils,modifyList)
importFrom(utils,read.delim)
useDynLib(lightgbm , .registration = TRUE)
+104
View File
@@ -0,0 +1,104 @@
# Central location for parameter aliases.
# See https://lightgbm.readthedocs.io/en/latest/Parameters.html#core-parameters
# [description] List of respected parameter aliases specific to lgb.Dataset. Wrapped in a function to
# take advantage of lazy evaluation (so it doesn't matter what order
# R sources files during installation).
# [return] A named list, where each key is a parameter relevant to lgb.Dataset and each value is a character
# vector of corresponding aliases.
.DATASET_PARAMETERS <- function() {
all_aliases <- .PARAMETER_ALIASES()
return(all_aliases[c(
"bin_construct_sample_cnt"
, "categorical_feature"
, "data_random_seed"
, "enable_bundle"
, "feature_pre_filter"
, "forcedbins_filename"
, "group_column"
, "header"
, "ignore_column"
, "is_enable_sparse"
, "label_column"
, "linear_tree"
, "max_bin"
, "max_bin_by_feature"
, "min_data_in_bin"
, "pre_partition"
, "precise_float_parser"
, "two_round"
, "use_missing"
, "weight_column"
, "zero_as_missing"
)])
}
# [description] Non-exported environment, used for caching details that only need to be
# computed once per R session.
.lgb_session_cache_env <- new.env()
# [description] List of respected parameter aliases. Wrapped in a function to take advantage of
# lazy evaluation (so it doesn't matter what order R sources files during installation).
# [return] A named list, where each key is a main LightGBM parameter and each value is a character
# vector of corresponding aliases.
.PARAMETER_ALIASES <- function() {
if (exists("PARAMETER_ALIASES", where = .lgb_session_cache_env)) {
return(get("PARAMETER_ALIASES", envir = .lgb_session_cache_env))
}
params_to_aliases <- jsonlite::fromJSON(
.Call(
LGBM_DumpParamAliases_R
)
)
for (main_name in names(params_to_aliases)) {
aliases_with_main_name <- c(main_name, unlist(params_to_aliases[[main_name]]))
params_to_aliases[[main_name]] <- aliases_with_main_name
}
# store in cache so the next call to `.PARAMETER_ALIASES()` doesn't need to recompute this
assign(
x = "PARAMETER_ALIASES"
, value = params_to_aliases
, envir = .lgb_session_cache_env
)
return(params_to_aliases)
}
# [description]
# Per https://github.com/lightgbm-org/LightGBM/blob/main/docs/Parameters.rst#metric,
# a few different strings can be used to indicate "no metrics".
# [returns]
# A character vector
.NO_METRIC_STRINGS <- function() {
return(
c(
"na"
, "None"
, "null"
, "custom"
)
)
}
.MULTICLASS_OBJECTIVES <- function() {
return(
c(
"multi_logloss"
, "multiclass"
, "softmax"
, "multiclassova"
, "multiclass_ova"
, "ova"
, "ovr"
)
)
}
.BINARY_OBJECTIVES <- function() {
return(
c(
"binary_logloss"
, "binary"
, "binary_error"
)
)
}
+368
View File
@@ -0,0 +1,368 @@
# constants that control naming in lists
.EVAL_KEY <- function() {
return("eval")
}
.EVAL_ERR_KEY <- function() {
return("eval_err")
}
#' @importFrom R6 R6Class
CB_ENV <- R6::R6Class(
"lgb.cb_env",
cloneable = FALSE,
public = list(
model = NULL,
iteration = NULL,
begin_iteration = NULL,
end_iteration = NULL,
eval_list = list(),
eval_err_list = list(),
best_iter = -1L,
best_score = NA,
met_early_stop = FALSE
)
)
# Format the evaluation metric string
.format_eval_string <- function(eval_res, eval_err) {
# Check for empty evaluation string
if (is.null(eval_res) || length(eval_res) == 0L) {
stop("no evaluation results")
}
# Check for empty evaluation error
if (!is.null(eval_err)) {
return(sprintf("%s\'s %s:%g+%g", eval_res$data_name, eval_res$name, eval_res$value, eval_err))
} else {
return(sprintf("%s\'s %s:%g", eval_res$data_name, eval_res$name, eval_res$value))
}
}
.merge_eval_string <- function(env) {
# Check length of evaluation list
if (length(env$eval_list) <= 0L) {
return("")
}
# Get evaluation
msg <- list(sprintf("[%d]:", env$iteration))
# Set if evaluation error
is_eval_err <- length(env$eval_err_list) > 0L
# Loop through evaluation list
for (j in seq_along(env$eval_list)) {
# Store evaluation error
eval_err <- NULL
if (isTRUE(is_eval_err)) {
eval_err <- env$eval_err_list[[j]]
}
# Set error message
msg <- c(msg, .format_eval_string(eval_res = env$eval_list[[j]], eval_err = eval_err))
}
return(paste(msg, collapse = " "))
}
cb_print_evaluation <- function(period) {
# Create callback
callback <- function(env) {
# Check if period is at least 1 or more
if (period > 0L) {
# Store iteration
i <- env$iteration
# Check if iteration matches moduo
if ((i - 1L) %% period == 0L || is.element(i, c(env$begin_iteration, env$end_iteration))) {
# Merge evaluation string
msg <- .merge_eval_string(env = env)
# Check if message is existing
if (nchar(msg) > 0L) {
cat(.merge_eval_string(env = env), "\n")
}
}
}
return(invisible(NULL))
}
# Store attributes
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_print_evaluation"
return(callback)
}
cb_record_evaluation <- function() {
# Create callback
callback <- function(env) {
if (length(env$eval_list) <= 0L) {
return()
}
# Set if evaluation error
is_eval_err <- length(env$eval_err_list) > 0L
# Check length of recorded evaluation
if (length(env$model$record_evals) == 0L) {
# Loop through each evaluation list element
for (j in seq_along(env$eval_list)) {
# Store names
data_name <- env$eval_list[[j]]$data_name
name <- env$eval_list[[j]]$name
env$model$record_evals$start_iter <- env$begin_iteration
# Check if evaluation record exists
if (is.null(env$model$record_evals[[data_name]])) {
env$model$record_evals[[data_name]] <- list()
}
# Create dummy lists
env$model$record_evals[[data_name]][[name]] <- list()
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]] <- list()
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]] <- list()
}
}
# Loop through each evaluation list element
for (j in seq_along(env$eval_list)) {
# Get evaluation data
eval_res <- env$eval_list[[j]]
eval_err <- NULL
if (isTRUE(is_eval_err)) {
eval_err <- env$eval_err_list[[j]]
}
# Store names
data_name <- eval_res$data_name
name <- eval_res$name
# Store evaluation data
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]] <- c(
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]]
, eval_res$value
)
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]] <- c(
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]]
, eval_err
)
}
return(invisible(NULL))
}
# Store attributes
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_record_evaluation"
return(callback)
}
cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) {
factor_to_bigger_better <- NULL
best_iter <- NULL
best_score <- NULL
best_msg <- NULL
eval_len <- NULL
# Initialization function
init <- function(env) {
# Early stopping cannot work without metrics
if (length(env$eval_list) == 0L) {
stop("For early stopping, valids must have at least one element")
}
# Store evaluation length
eval_len <<- length(env$eval_list)
# Check if verbose or not
if (isTRUE(verbose)) {
msg <- paste0(
"Will train until there is no improvement in "
, stopping_rounds
, " rounds.\n"
)
cat(msg)
}
# Internally treat everything as a maximization task
factor_to_bigger_better <<- rep.int(1.0, eval_len)
best_iter <<- rep.int(-1L, eval_len)
best_score <<- rep.int(-Inf, eval_len)
best_msg <<- list()
# Loop through evaluation elements
for (i in seq_len(eval_len)) {
# Prepend message
best_msg <<- c(best_msg, "")
# Internally treat everything as a maximization task
if (!isTRUE(env$eval_list[[i]]$higher_better)) {
factor_to_bigger_better[i] <<- -1.0
}
}
return(invisible(NULL))
}
# Create callback
callback <- function(env) {
# Check for empty evaluation
if (is.null(eval_len)) {
init(env = env)
}
# Store iteration
cur_iter <- env$iteration
# By default, any metric can trigger early stopping. This can be disabled
# with 'first_metric_only = TRUE'
if (isTRUE(first_metric_only)) {
evals_to_check <- 1L
} else {
evals_to_check <- seq_len(eval_len)
}
# Loop through evaluation
for (i in evals_to_check) {
# Store score
score <- env$eval_list[[i]]$value * factor_to_bigger_better[i]
# Check if score is better
if (score > best_score[i]) {
# Store new scores
best_score[i] <<- score
best_iter[i] <<- cur_iter
# Prepare to print if verbose
if (verbose) {
best_msg[[i]] <<- as.character(.merge_eval_string(env = env))
}
} else {
# Check if early stopping is required
if (cur_iter - best_iter[i] >= stopping_rounds) {
if (!is.null(env$model)) {
env$model$best_score <- best_score[i]
env$model$best_iter <- best_iter[i]
}
if (isTRUE(verbose)) {
cat(paste0("Early stopping, best iteration is: ", best_msg[[i]], "\n"))
}
# Store best iteration and stop
env$best_iter <- best_iter[i]
env$met_early_stop <- TRUE
}
}
if (!isTRUE(env$met_early_stop) && cur_iter == env$end_iteration) {
if (!is.null(env$model)) {
env$model$best_score <- best_score[i]
env$model$best_iter <- best_iter[i]
}
if (isTRUE(verbose)) {
cat(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]], "\n"))
}
# Store best iteration and stop
env$best_iter <- best_iter[i]
env$met_early_stop <- TRUE
}
}
return(invisible(NULL))
}
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_early_stop"
return(callback)
}
# Extract callback names from the list of callbacks
.callback_names <- function(cb_list) {
return(unlist(lapply(cb_list, attr, "name")))
}
.add_cb <- function(cb_list, cb) {
# Combine two elements
cb_list <- c(cb_list, cb)
# Set names of elements
names(cb_list) <- .callback_names(cb_list = cb_list)
if ("cb_early_stop" %in% names(cb_list)) {
# Concatenate existing elements
cb_list <- c(cb_list, cb_list["cb_early_stop"])
# Remove only the first one
cb_list["cb_early_stop"] <- NULL
}
return(cb_list)
}
.categorize_callbacks <- function(cb_list) {
# Check for pre-iteration or post-iteration
return(
list(
pre_iter = Filter(function(x) {
pre <- attr(x, "is_pre_iteration")
!is.null(pre) && pre
}, cb_list),
post_iter = Filter(function(x) {
pre <- attr(x, "is_pre_iteration")
is.null(pre) || !pre
}, cb_list)
)
)
}
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
DataProcessor <- R6::R6Class(
classname = "lgb.DataProcessor",
public = list(
factor_levels = NULL,
process_label = function(label, objective, params) {
if (is.character(label)) {
label <- factor(label)
}
if (is.factor(label)) {
self$factor_levels <- levels(label)
if (length(self$factor_levels) <= 1L) {
stop("Labels to predict is a factor with <2 possible values.")
}
label <- as.numeric(label) - 1.0
out <- list(label = label)
if (length(self$factor_levels) == 2L) {
if (objective == "auto") {
objective <- "binary"
}
if (!(objective %in% .BINARY_OBJECTIVES())) {
stop("Two-level factors as labels only allowed for objective='binary' or objective='auto'.")
}
} else {
if (objective == "auto") {
objective <- "multiclass"
}
if (!(objective %in% .MULTICLASS_OBJECTIVES())) {
stop(
sprintf(
"Factors with >2 levels as labels only allowed for multi-class objectives. Got: %s (allowed: %s)"
, objective
, toString(.MULTICLASS_OBJECTIVES())
)
)
}
data_num_class <- length(self$factor_levels)
params <- .check_wrapper_param(
main_param_name = "num_class"
, params = params
, alternative_kwarg_value = data_num_class
)
if (params[["num_class"]] != data_num_class) {
warning(
sprintf(
"Found num_class=%d in params, but 'label' is a factor with %d levels. 'num_class' will be ignored."
, params[["num_class"]]
, data_num_class
)
)
params$num_class <- data_num_class
}
}
out$objective <- objective
out$params <- params
return(out)
} else {
label <- as.numeric(label)
if (objective == "auto") {
objective <- "regression"
}
out <- list(
label = label
, objective = objective
, params = params
)
return(out)
}
},
process_predictions = function(pred, type) {
if (NROW(self$factor_levels)) {
if (type == "class") {
pred <- as.integer(pred) + 1L
attributes(pred)$levels <- self$factor_levels
attributes(pred)$class <- "factor"
} else if (type %in% c("response", "raw")) {
if (is.matrix(pred) && ncol(pred) == length(self$factor_levels)) {
colnames(pred) <- self$factor_levels
}
}
}
return(pred)
}
)
)
File diff suppressed because it is too large Load Diff
+529
View File
@@ -0,0 +1,529 @@
#' @importFrom methods is new
#' @importFrom R6 R6Class
#' @importFrom utils read.delim
#' @importClassesFrom Matrix dsparseMatrix dsparseVector dgCMatrix dgRMatrix CsparseMatrix RsparseMatrix
Predictor <- R6::R6Class(
classname = "lgb.Predictor",
cloneable = FALSE,
public = list(
# Initialize will create a starter model
initialize = function(modelfile, params = list(), fast_predict_config = list()) {
private$params <- .params2str(params = params)
handle <- NULL
if (is.character(modelfile)) {
# Create handle on it
handle <- .Call(
LGBM_BoosterCreateFromModelfile_R
, path.expand(modelfile)
)
private$need_free_handle <- TRUE
} else if (methods::is(modelfile, "lgb.Booster.handle") || inherits(modelfile, "externalptr")) {
# Check if model file is a booster handle already
handle <- modelfile
private$need_free_handle <- FALSE
} else if (.is_Booster(modelfile)) {
handle <- modelfile$get_handle()
private$need_free_handle <- FALSE
} else {
stop("lgb.Predictor: modelfile must be either a character filename or an lgb.Booster.handle")
}
private$fast_predict_config <- fast_predict_config
# Override class and store it
class(handle) <- "lgb.Booster.handle"
private$handle <- handle
return(invisible(NULL))
},
# Get current iteration
current_iter = function() {
cur_iter <- 0L
.Call(
LGBM_BoosterGetCurrentIteration_R
, private$handle
, cur_iter
)
return(cur_iter)
},
# Predict from data
predict = function(data,
start_iteration = NULL,
num_iteration = NULL,
rawscore = FALSE,
predleaf = FALSE,
predcontrib = FALSE,
header = FALSE) {
# Check if number of iterations is existing - if not, then set it to -1 (use all)
if (is.null(num_iteration)) {
num_iteration <- -1L
}
# Check if start iterations is existing - if not, then set it to 0 (start from the first iteration)
if (is.null(start_iteration)) {
start_iteration <- 0L
}
# Check if data is a file name and not a matrix
if (identical(class(data), "character") && length(data) == 1L) {
data <- path.expand(data)
# Data is a filename, create a temporary file with a "lightgbm_" pattern in it
tmp_filename <- tempfile(pattern = "lightgbm_")
on.exit(unlink(tmp_filename), add = TRUE)
# Predict from temporary file
.Call(
LGBM_BoosterPredictForFile_R
, private$handle
, data
, as.integer(header)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, tmp_filename
)
# Get predictions from file
preds <- utils::read.delim(tmp_filename, header = FALSE, sep = "\t")
num_row <- nrow(preds)
preds <- as.vector(t(preds))
} else if (predcontrib && inherits(data, c("dsparseMatrix", "dsparseVector"))) {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
ncols_out <- integer(1L)
.Call(LGBM_BoosterGetNumClasses_R, private$handle, ncols_out)
ncols_out <- (ncols + 1L) * max(ncols_out, 1L)
if (is.na(ncols_out)) {
ncols_out <- as.numeric(ncols + 1L) * as.numeric(max(ncols_out, 1L))
}
if (!inherits(data, "dsparseVector") && ncols_out > .Machine$integer.max) {
stop("Resulting matrix of feature contributions is too large for R to handle.")
}
if (inherits(data, "dsparseVector")) {
if (length(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, length(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, c(0L, as.integer(length(data@x)))
, data@i - 1L
, data@x
, TRUE
, 1L
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dsparseVector")
out@i <- res$indices + 1L
out@x <- res$data
out@length <- ncols_out
return(out)
} else if (inherits(data, "dgRMatrix")) {
if (ncol(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, data@p
, data@j
, data@x
, TRUE
, nrow(data)
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dgRMatrix")
out@p <- res$indptr
out@j <- res$indices
out@x <- res$data
out@Dim <- as.integer(c(nrow(data), ncols_out))
} else if (inherits(data, "dgCMatrix")) {
if (ncol(data) != ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, data@p
, data@i
, data@x
, FALSE
, nrow(data)
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dgCMatrix")
out@p <- res$indptr
out@i <- res$indices
out@x <- res$data
out@Dim <- as.integer(c(nrow(data), length(res$indptr) - 1L))
} else {
stop(sprintf("Predictions on sparse inputs are only allowed for '%s', '%s', '%s' - got: %s"
, "dsparseVector"
, "dgRMatrix"
, "dgCMatrix"
, toString(class(data))))
}
if (NROW(row.names(data))) {
out@Dimnames[[1L]] <- row.names(data)
}
return(out)
} else {
# Not a file, we need to predict from R object
num_row <- nrow(data)
if (is.null(num_row)) {
num_row <- 1L
}
npred <- 0L
# Check number of predictions to do
.Call(
LGBM_BoosterCalcNumPredict_R
, private$handle
, as.integer(num_row)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, npred
)
# Pre-allocate empty vector
preds <- numeric(npred)
# Check if data is a matrix
if (is.matrix(data)) {
# this if() prevents the memory and computational costs
# of converting something that is already "double" to "double"
if (storage.mode(data) != "double") {
storage.mode(data) <- "double"
}
if (nrow(data) == 1L) {
use_fast_config <- private$check_can_use_fast_predict_config(
csr = FALSE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForMatSingleRowFast_R
, private$fast_predict_config$handle
, data
, preds
)
} else {
.Call(
LGBM_BoosterPredictForMatSingleRow_R
, private$handle
, data
, rawscore
, predleaf
, predcontrib
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else {
.Call(
LGBM_BoosterPredictForMat_R
, private$handle
, data
, as.integer(nrow(data))
, as.integer(ncol(data))
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, preds
)
}
} else if (inherits(data, "dsparseVector")) {
if (length(self$fast_predict_config)) {
ncols <- self$fast_predict_config$ncols
use_fast_config <- private$check_can_use_fast_predict_config(
csr = TRUE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
} else {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
use_fast_config <- FALSE
}
if (length(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, length(data)))
}
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForCSRSingleRowFast_R
, self$fast_predict_config$handle
, data@i - 1L
, data@x
, preds
)
} else {
.Call(
LGBM_BoosterPredictForCSRSingleRow_R
, private$handle
, data@i - 1L
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else if (inherits(data, "dgRMatrix")) {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
if (ncol(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
if (nrow(data) == 1L) {
if (length(self$fast_predict_config)) {
ncols <- self$fast_predict_config$ncols
use_fast_config <- private$check_can_use_fast_predict_config(
csr = TRUE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
} else {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
use_fast_config <- FALSE
}
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForCSRSingleRowFast_R
, self$fast_predict_config$handle
, data@j
, data@x
, preds
)
} else {
.Call(
LGBM_BoosterPredictForCSRSingleRow_R
, private$handle
, data@j
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else {
.Call(
LGBM_BoosterPredictForCSR_R
, private$handle
, data@p
, data@j
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else if (methods::is(data, "dgCMatrix")) {
if (length(data@p) > 2147483647L) {
stop("Cannot support large CSC matrix")
}
# Check if data is a dgCMatrix (sparse matrix, column compressed format)
.Call(
LGBM_BoosterPredictForCSC_R
, private$handle
, data@p
, data@i
, data@x
, length(data@p)
, length(data@x)
, nrow(data)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, preds
)
} else {
stop("predict: cannot predict on data of class ", sQuote(class(data), q = FALSE))
}
}
# Check if number of rows is strange (not a multiple of the dataset rows)
if (length(preds) %% num_row != 0L) {
stop(
"predict: prediction length "
, sQuote(length(preds), q = FALSE)
, " is not a multiple of nrows(data): "
, sQuote(num_row, q = FALSE)
)
}
# Get number of cases per row
npred_per_case <- length(preds) / num_row
# Data reshaping
if (npred_per_case > 1L || predleaf || predcontrib) {
preds <- matrix(preds, ncol = npred_per_case, byrow = TRUE)
}
# Keep row names if possible
if (NROW(row.names(data)) && NROW(data) == NROW(preds)) {
if (is.null(dim(preds))) {
names(preds) <- row.names(data)
} else {
row.names(preds) <- row.names(data)
}
}
return(preds)
}
),
private = list(
handle = NULL
, need_free_handle = FALSE
, params = ""
, fast_predict_config = list()
, check_can_use_fast_predict_config = function(csr,
rawscore,
predleaf,
predcontrib,
start_iteration,
num_iteration) {
if (!NROW(private$fast_predict_config)) {
return(FALSE)
}
if (.is_null_handle(private$fast_predict_config$handle)) {
warning(paste0("Model had fast CSR predict configuration, but it is inactive."
, " Try re-generating it through 'lgb.configure_fast_predict'."))
return(FALSE)
}
if (isTRUE(csr) != private$fast_predict_config$csr) {
return(FALSE)
}
return(
private$params == "" &&
private$fast_predict_config$rawscore == rawscore &&
private$fast_predict_config$predleaf == predleaf &&
private$fast_predict_config$predcontrib == predcontrib &&
.equal_or_both_null(private$fast_predict_config$start_iteration, start_iteration) &&
.equal_or_both_null(private$fast_predict_config$num_iteration, num_iteration)
)
}
# finalize() will free up the handles
, finalize = function() {
if (private$need_free_handle) {
.Call(
LGBM_BoosterFree_R
, private$handle
)
private$handle <- NULL
}
return(invisible(NULL))
}
)
)
+207
View File
@@ -0,0 +1,207 @@
# [description] get all column classes of a data.table or data.frame.
# This function collapses the result of class() into a single string
.get_column_classes <- function(df) {
return(
vapply(
X = df
, FUN = function(x) {
paste(class(x), collapse = ",")
}
, FUN.VALUE = character(1L)
)
)
}
# [description] check a data frame or data table for columns that are any
# type other than numeric and integer. This is used by lgb.convert_with_rules()
# to warn if more action is needed by users
# before a dataset can be converted to a lgb.Dataset.
.warn_for_unconverted_columns <- function(df, function_name) {
column_classes <- .get_column_classes(df = df)
unconverted_columns <- column_classes[!(column_classes %in% c("numeric", "integer"))]
if (length(unconverted_columns) > 0L) {
col_detail_string <- toString(
paste0(
names(unconverted_columns)
, " ("
, unconverted_columns
, ")"
)
)
msg <- paste0(
function_name
, ": "
, length(unconverted_columns)
, " columns are not numeric or integer. These need to be dropped or converted to "
, "be used in an lgb.Dataset object. "
, col_detail_string
)
warning(msg)
}
return(invisible(NULL))
}
.LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA <- function() {
return(-1L)
}
.LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA <- function() {
return(0L)
}
#' @name lgb.convert_with_rules
#' @title Data preparator for LightGBM datasets with rules (integer)
#' @description Attempts to prepare a clean dataset to prepare to put in a \code{lgb.Dataset}.
#' Factor, character, and logical columns are converted to integer. Missing values
#' in factors and characters will be filled with 0L. Missing values in logicals
#' will be filled with -1L.
#'
#' This function returns and optionally takes in "rules" the describe exactly
#' how to convert values in columns.
#'
#' Columns that contain only NA values will be converted by this function but will
#' not show up in the returned \code{rules}.
#'
#' NOTE: In previous releases of LightGBM, this function was called \code{lgb.prepare_rules2}.
#' @param data A data.frame or data.table to prepare.
#' @param rules A set of rules from the data preparator, if already used. This should be an R list,
#' where names are column names in \code{data} and values are named character
#' vectors whose names are column values and whose values are new values to
#' replace them with.
#' @return A list with the cleaned dataset (\code{data}) and the rules (\code{rules}).
#' Note that the data must be converted to a matrix format (\code{as.matrix}) for input in
#' \code{lgb.Dataset}.
#'
#' @examples
#' \donttest{
#' data(iris)
#'
#' str(iris)
#'
#' new_iris <- lgb.convert_with_rules(data = iris)
#' str(new_iris$data)
#'
#' data(iris) # Erase iris dataset
#' iris$Species[1L] <- "NEW FACTOR" # Introduce junk factor (NA)
#'
#' # Use conversion using known rules
#' # Unknown factors become 0, excellent for sparse datasets
#' newer_iris <- lgb.convert_with_rules(data = iris, rules = new_iris$rules)
#'
#' # Unknown factor is now zero, perfect for sparse datasets
#' newer_iris$data[1L, ] # Species became 0 as it is an unknown factor
#'
#' newer_iris$data[1L, 5L] <- 1.0 # Put back real initial value
#'
#' # Is the newly created dataset equal? YES!
#' all.equal(new_iris$data, newer_iris$data)
#'
#' # Can we test our own rules?
#' data(iris) # Erase iris dataset
#'
#' # We remapped values differently
#' personal_rules <- list(
#' Species = c(
#' "setosa" = 3L
#' , "versicolor" = 2L
#' , "virginica" = 1L
#' )
#' )
#' newest_iris <- lgb.convert_with_rules(data = iris, rules = personal_rules)
#' str(newest_iris$data) # SUCCESS!
#' }
#' @importFrom data.table set
#' @export
lgb.convert_with_rules <- function(data, rules = NULL) {
column_classes <- .get_column_classes(df = data)
is_data_table <- data.table::is.data.table(x = data)
is_data_frame <- is.data.frame(data)
if (!(is_data_table || is_data_frame)) {
stop(
"lgb.convert_with_rules: you provided "
, paste(class(data), collapse = " & ")
, " but data should have class data.frame or data.table"
)
}
# if user didn't provide rules, create them
if (is.null(rules)) {
rules <- list()
columns_to_fix <- which(column_classes %in% c("character", "factor", "logical"))
for (i in columns_to_fix) {
col_values <- data[[i]]
# Get unique values
if (is.factor(col_values)) {
unique_vals <- levels(col_values)
unique_vals <- unique_vals[!is.na(unique_vals)]
mini_numeric <- seq_along(unique_vals) # respect ordinal
} else if (is.character(col_values)) {
unique_vals <- as.factor(unique(col_values))
unique_vals <- unique_vals[!is.na(unique_vals)]
mini_numeric <- as.integer(unique_vals) # no respect for ordinal
} else if (is.logical(col_values)) {
unique_vals <- c(FALSE, TRUE)
mini_numeric <- c(0L, 1L)
}
# don't add rules for all-NA columns
if (length(unique_vals) > 0L) {
col_name <- names(data)[i]
rules[[col_name]] <- mini_numeric
names(rules[[col_name]]) <- unique_vals
}
}
}
for (col_name in names(rules)) {
if (column_classes[[col_name]] == "logical") {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA()
} else {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA()
}
if (is_data_table) {
data.table::set(
x = data
, j = col_name
, value = unname(rules[[col_name]][data[[col_name]]])
)
data[is.na(get(col_name)), (col_name) := default_value_for_na]
} else {
data[[col_name]] <- unname(rules[[col_name]][data[[col_name]]])
data[is.na(data[col_name]), col_name] <- default_value_for_na
}
}
# if any all-NA columns exist, they won't be in rules. Convert them
all_na_cols <- which(
sapply(
X = data
, FUN = function(x) {
(is.factor(x) || is.character(x) || is.logical(x)) && all(is.na(unique(x)))
}
)
)
for (col_name in all_na_cols) {
if (column_classes[[col_name]] == "logical") {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA()
} else {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA()
}
if (is_data_table) {
data[, (col_name) := rep(default_value_for_na, .N)]
} else {
data[[col_name]] <- default_value_for_na
}
}
.warn_for_unconverted_columns(df = data, function_name = "lgb.convert_with_rules")
return(list(data = data, rules = rules))
}
+617
View File
@@ -0,0 +1,617 @@
#' @importFrom R6 R6Class
CVBooster <- R6::R6Class(
classname = "lgb.CVBooster",
cloneable = FALSE,
public = list(
best_iter = -1L,
best_score = NA,
record_evals = list(),
boosters = list(),
initialize = function(x) {
self$boosters <- x
return(invisible(NULL))
},
reset_parameter = function(new_params) {
for (x in self$boosters) {
x[["booster"]]$reset_parameter(params = new_params)
}
return(invisible(self))
}
)
)
#' @name lgb.cv
#' @title Main CV logic for LightGBM
#' @description Cross validation logic used by LightGBM
#' @inheritParams lgb_shared_params
#' @param nfold the original dataset is randomly partitioned into \code{nfold} equal size subsamples.
#' @param record Boolean, TRUE will record iteration message to \code{booster$record_evals}
#' @param showsd \code{boolean}, whether to show standard deviation of cross validation.
#' This parameter defaults to \code{TRUE}. Setting it to \code{FALSE} can lead to a
#' slight speedup by avoiding unnecessary computation.
#' @param stratified a \code{boolean} indicating whether sampling of folds should be stratified
#' by the values of outcome labels.
#' @param folds \code{list} provides a possibility to use a list of pre-defined CV folds
#' (each element must be a vector of test fold's indices). When folds are supplied,
#' the \code{nfold} and \code{stratified} parameters are ignored.
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param reset_data Boolean, setting it to TRUE (not the default value) will transform the booster model
#' into a predictor model which frees up memory and the original datasets
#' @param eval_train_metric \code{boolean}, whether to add the cross validation results on the
#' training data. This parameter defaults to \code{FALSE}. Setting it to \code{TRUE}
#' will increase run time.
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained model \code{lgb.CVBooster}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' params <- list(
#' objective = "regression"
#' , metric = "l2"
#' , min_data = 1L
#' , learning_rate = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.cv(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' , nfold = 3L
#' )
#' }
#'
#' @importFrom data.table data.table setorderv
#' @export
lgb.cv <- function(params = list()
, data
, nrounds = 100L
, nfold = 3L
, obj = NULL
, eval = NULL
, verbose = 1L
, record = TRUE
, eval_freq = 1L
, showsd = TRUE
, stratified = TRUE
, folds = NULL
, init_model = NULL
, early_stopping_rounds = NULL
, callbacks = list()
, reset_data = FALSE
, serializable = TRUE
, eval_train_metric = FALSE
) {
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (!.is_Dataset(x = data)) {
stop("lgb.cv: data must be an lgb.Dataset instance")
}
# set some parameters, resolving the way they were passed in with other parameters
# in `params`.
# this ensures that the model stored with Booster$save() correctly represents
# what was passed in
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
params <- .check_wrapper_param(
main_param_name = "num_iterations"
, params = params
, alternative_kwarg_value = nrounds
)
params <- .check_wrapper_param(
main_param_name = "metric"
, params = params
, alternative_kwarg_value = NULL
)
params <- .check_wrapper_param(
main_param_name = "objective"
, params = params
, alternative_kwarg_value = obj
)
params <- .check_wrapper_param(
main_param_name = "early_stopping_round"
, params = params
, alternative_kwarg_value = early_stopping_rounds
)
early_stopping_rounds <- params[["early_stopping_round"]]
# extract any function objects passed for objective or metric
fobj <- NULL
if (is.function(params$objective)) {
fobj <- params$objective
params$objective <- "none"
}
# If eval is a single function, store it as a 1-element list
# (for backwards compatibility). If it is a list of functions, store
# all of them. This makes it possible to pass any mix of strings like "auc"
# and custom functions to eval
params <- .check_eval(params = params, eval = eval)
eval_functions <- list(NULL)
if (is.function(eval)) {
eval_functions <- list(eval)
}
if (methods::is(eval, "list")) {
eval_functions <- Filter(
f = is.function
, x = eval
)
}
# Init predictor to empty
predictor <- NULL
# Check for boosting from a trained model
if (is.character(init_model)) {
predictor <- Predictor$new(modelfile = init_model)
} else if (.is_Booster(x = init_model)) {
predictor <- init_model$to_predictor()
}
# Set the iteration to start from / end to (and check for boosting from a trained model, again)
begin_iteration <- 1L
if (!is.null(predictor)) {
begin_iteration <- predictor$current_iter() + 1L
}
end_iteration <- begin_iteration + params[["num_iterations"]] - 1L
# pop interaction_constraints off of params. It needs some preprocessing on the
# R side before being passed into the Dataset object
interaction_constraints <- params[["interaction_constraints"]]
params["interaction_constraints"] <- NULL
# Construct datasets, if needed
data$update_params(params = params)
data$construct()
# Check interaction constraints
params[["interaction_constraints"]] <- .check_interaction_constraints(
interaction_constraints = interaction_constraints
, column_names = data$get_colnames()
)
# Update parameters with parsed parameters
data$update_params(params = params)
# Create the predictor set
data$.__enclos_env__$private$set_predictor(predictor = predictor)
if (!is.null(folds)) {
# Check for list of folds or for single value
if (!identical(class(folds), "list") || length(folds) < 2L) {
stop(sQuote("folds"), " must be a list with 2 or more elements that are vectors of indices for each CV-fold")
}
} else {
if (nfold <= 1L) {
stop(sQuote("nfold"), " must be > 1")
}
# Create folds
folds <- .generate_cv_folds(
nfold = nfold
, nrows = nrow(data)
, stratified = stratified
, label = get_field(dataset = data, field_name = "label")
, group = get_field(dataset = data, field_name = "group")
, params = params
)
}
# Add printing log callback
if (params[["verbosity"]] > 0L && eval_freq > 0L) {
callbacks <- .add_cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq))
}
# Add evaluation log callback
if (record) {
callbacks <- .add_cb(cb_list = callbacks, cb = cb_record_evaluation())
}
# Did user pass parameters that indicate they want to use early stopping?
using_early_stopping <- !is.null(early_stopping_rounds) && early_stopping_rounds > 0L
boosting_param_names <- .PARAMETER_ALIASES()[["boosting"]]
using_dart <- any(
sapply(
X = boosting_param_names
, FUN = function(param) {
identical(params[[param]], "dart")
}
)
)
# Cannot use early stopping with 'dart' boosting
if (using_dart) {
if (using_early_stopping) {
warning("Early stopping is not available in 'dart' mode.")
}
using_early_stopping <- FALSE
# Remove the cb_early_stop() function if it was passed in to callbacks
callbacks <- Filter(
f = function(cb_func) {
!identical(attr(cb_func, "name"), "cb_early_stop")
}
, x = callbacks
)
}
# If user supplied early_stopping_rounds, add the early stopping callback
if (using_early_stopping) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_early_stop(
stopping_rounds = early_stopping_rounds
, first_metric_only = isTRUE(params[["first_metric_only"]])
, verbose = params[["verbosity"]] > 0L
)
)
}
cb <- .categorize_callbacks(cb_list = callbacks)
# Construct booster for each fold. The data.table() code below is used to
# guarantee that indices are sorted while keeping init_score and weight together
# with the correct indices. Note that it takes advantage of the fact that
# someDT$some_column returns NULL is 'some_column' does not exist in the data.table
bst_folds <- lapply(
X = seq_along(folds)
, FUN = function(k) {
# For learning-to-rank, each fold is a named list with two elements:
# * `fold` = an integer vector of row indices
# * `group` = an integer vector describing which groups are in the fold
# For classification or regression tasks, it will just be an integer
# vector of row indices
folds_have_group <- "group" %in% names(folds[[k]])
if (folds_have_group) {
test_indices <- folds[[k]]$fold
test_group_indices <- folds[[k]]$group
test_groups <- get_field(dataset = data, field_name = "group")[test_group_indices]
train_groups <- get_field(dataset = data, field_name = "group")[-test_group_indices]
} else {
test_indices <- folds[[k]]
}
train_indices <- seq_len(nrow(data))[-test_indices]
# set up test set
indexDT <- data.table::data.table(
indices = test_indices
, weight = get_field(dataset = data, field_name = "weight")[test_indices]
, init_score = get_field(dataset = data, field_name = "init_score")[test_indices]
)
data.table::setorderv(x = indexDT, cols = "indices", order = 1L)
dtest <- lgb.slice.Dataset(data, indexDT$indices)
set_field(dataset = dtest, field_name = "weight", data = indexDT$weight)
set_field(dataset = dtest, field_name = "init_score", data = indexDT$init_score)
# set up training set
indexDT <- data.table::data.table(
indices = train_indices
, weight = get_field(dataset = data, field_name = "weight")[train_indices]
, init_score = get_field(dataset = data, field_name = "init_score")[train_indices]
)
data.table::setorderv(x = indexDT, cols = "indices", order = 1L)
dtrain <- lgb.slice.Dataset(data, indexDT$indices)
set_field(dataset = dtrain, field_name = "weight", data = indexDT$weight)
set_field(dataset = dtrain, field_name = "init_score", data = indexDT$init_score)
if (folds_have_group) {
set_field(dataset = dtest, field_name = "group", data = test_groups)
set_field(dataset = dtrain, field_name = "group", data = train_groups)
}
booster <- Booster$new(params = params, train_set = dtrain)
if (isTRUE(eval_train_metric)) {
booster$add_valid(data = dtrain, name = "train")
}
booster$add_valid(data = dtest, name = "valid")
return(
list(booster = booster)
)
}
)
# Create new booster
cv_booster <- CVBooster$new(x = bst_folds)
# Callback env
env <- CB_ENV$new()
env$model <- cv_booster
env$begin_iteration <- begin_iteration
env$end_iteration <- end_iteration
# Start training model using number of iterations to start and end with
for (i in seq.int(from = begin_iteration, to = end_iteration)) {
# Overwrite iteration in environment
env$iteration <- i
env$eval_list <- list()
for (f in cb$pre_iter) {
f(env)
}
# Update one boosting iteration
msg <- lapply(cv_booster$boosters, function(fd) {
fd$booster$update(fobj = fobj)
out <- list()
for (eval_function in eval_functions) {
out <- append(out, fd$booster$eval_valid(feval = eval_function))
}
return(out)
})
# Prepare collection of evaluation results
merged_msg <- .merge_cv_result(
msg = msg
, showsd = showsd
)
# Write evaluation result in environment
env$eval_list <- merged_msg$eval_list
# Check for standard deviation requirement
if (showsd) {
env$eval_err_list <- merged_msg$eval_err_list
}
# Loop through env
for (f in cb$post_iter) {
f(env)
}
# Check for early stopping and break if needed
if (env$met_early_stop) break
}
# When early stopping is not activated, we compute the best iteration / score ourselves
# based on the first first metric
if (record && is.na(env$best_score)) {
# when using a custom eval function, the metric name is returned from the
# function, so figure it out from record_evals
if (!is.null(eval_functions[1L])) {
first_metric <- names(cv_booster$record_evals[["valid"]])[1L]
} else {
first_metric <- cv_booster$.__enclos_env__$private$eval_names[1L]
}
.find_best <- which.min
if (isTRUE(env$eval_list[[1L]]$higher_better[1L])) {
.find_best <- which.max
}
cv_booster$best_iter <- unname(
.find_best(
unlist(
cv_booster$record_evals[["valid"]][[first_metric]][[.EVAL_KEY()]]
)
)
)
cv_booster$best_score <- cv_booster$record_evals[["valid"]][[first_metric]][[.EVAL_KEY()]][[cv_booster$best_iter]]
}
# Propagate the best_iter attribute from the cv_booster to the individual boosters
for (bst in cv_booster$boosters) {
bst$booster$best_iter <- cv_booster$best_iter
}
if (reset_data) {
lapply(cv_booster$boosters, function(fd) {
# Store temporarily model data elsewhere
booster_old <- list(
best_iter = fd$booster$best_iter
, best_score = fd$booster$best_score
, record_evals = fd$booster$record_evals
)
# Reload model
fd$booster <- lgb.load(model_str = fd$booster$save_model_to_string())
fd$booster$best_iter <- booster_old$best_iter
fd$booster$best_score <- booster_old$best_score
fd$booster$record_evals <- booster_old$record_evals
})
}
if (serializable) {
lapply(cv_booster$boosters, function(model) model$booster$save_raw())
}
return(cv_booster)
}
# Generates random (stratified if needed) CV folds
.generate_cv_folds <- function(nfold, nrows, stratified, label, group, params) {
# Check for group existence
if (is.null(group)) {
# Shuffle
rnd_idx <- sample.int(nrows)
# Request stratified folds
if (isTRUE(stratified) && params$objective %in% c("binary", "multiclass") && length(label) == length(rnd_idx)) {
y <- label[rnd_idx]
y <- as.factor(y)
folds <- .stratified_folds(y = y, k = nfold)
} else {
# Make simple non-stratified folds
folds <- list()
# Loop through each fold
for (i in seq_len(nfold)) {
kstep <- length(rnd_idx) %/% (nfold - i + 1L)
folds[[i]] <- rnd_idx[seq_len(kstep)]
rnd_idx <- rnd_idx[-seq_len(kstep)]
}
}
} else {
# When doing group, stratified is not possible (only random selection)
if (nfold > length(group)) {
stop("\nYou requested too many folds for the number of available groups.\n")
}
# Degroup the groups
ungrouped <- inverse.rle(list(lengths = group, values = seq_along(group)))
# Can't stratify, shuffle
rnd_idx <- sample.int(length(group))
# Make simple non-stratified folds
folds <- list()
# Loop through each fold
for (i in seq_len(nfold)) {
kstep <- length(rnd_idx) %/% (nfold - i + 1L)
folds[[i]] <- list(
fold = which(ungrouped %in% rnd_idx[seq_len(kstep)])
, group = rnd_idx[seq_len(kstep)]
)
rnd_idx <- rnd_idx[-seq_len(kstep)]
}
}
return(folds)
}
# Creates CV folds stratified by the values of y.
# It was borrowed from caret::createFolds and simplified
# by always returning an unnamed list of fold indices.
#' @importFrom stats quantile
.stratified_folds <- function(y, k) {
# Group the numeric data based on their magnitudes
# and sample within those groups.
# When the number of samples is low, we may have
# issues further slicing the numeric data into
# groups. The number of groups will depend on the
# ratio of the number of folds to the sample size.
# At most, we will use quantiles. If the sample
# is too small, we just do regular unstratified CV
if (is.numeric(y)) {
cuts <- length(y) %/% k
if (cuts < 2L) {
cuts <- 2L
}
if (cuts > 5L) {
cuts <- 5L
}
y <- cut(
y
, unique(stats::quantile(y, probs = seq.int(0.0, 1.0, length.out = cuts)))
, include.lowest = TRUE
)
}
if (k < length(y)) {
# Reset levels so that the possible levels and
# the levels in the vector are the same
y <- as.factor(as.character(y))
numInClass <- table(y)
foldVector <- vector(mode = "integer", length(y))
# For each class, balance the fold allocation as far
# as possible, then resample the remainder.
# The final assignment of folds is also randomized.
for (i in seq_along(numInClass)) {
# Create a vector of integers from 1:k as many times as possible without
# going over the number of samples in the class. Note that if the number
# of samples in a class is less than k, nothing is produced here.
seqVector <- rep(seq_len(k), numInClass[i] %/% k)
# Add enough random integers to get length(seqVector) == numInClass[i]
if (numInClass[i] %% k > 0L) {
seqVector <- c(seqVector, sample.int(k, numInClass[i] %% k))
}
# Shuffle the integers for fold assignment and assign to this classes's data
foldVector[y == dimnames(numInClass)$y[i]] <- sample(seqVector)
}
} else {
foldVector <- seq(along = y)
}
out <- split(seq(along = y), foldVector)
names(out) <- NULL
return(out)
}
.merge_cv_result <- function(msg, showsd) {
if (length(msg) == 0L) {
stop("lgb.cv: size of cv result error")
}
eval_len <- length(msg[[1L]])
if (eval_len == 0L) {
stop("lgb.cv: should provide at least one metric for CV")
}
# Get evaluation results using a list apply
eval_result <- lapply(seq_len(eval_len), function(j) {
as.numeric(lapply(seq_along(msg), function(i) {
msg[[i]][[j]]$value }))
})
# Get evaluation. Just taking the first element here to
# get structure (name, higher_better, data_name)
ret_eval <- msg[[1L]]
for (j in seq_len(eval_len)) {
ret_eval[[j]]$value <- mean(eval_result[[j]])
}
ret_eval_err <- NULL
# Check for standard deviation
if (showsd) {
# Parse standard deviation
for (j in seq_len(eval_len)) {
ret_eval_err <- c(
ret_eval_err
, sqrt(mean(eval_result[[j]] ^ 2L) - mean(eval_result[[j]]) ^ 2L)
)
}
ret_eval_err <- as.list(ret_eval_err)
}
return(
list(
eval_list = ret_eval
, eval_err_list = ret_eval_err
)
)
}
+21
View File
@@ -0,0 +1,21 @@
#' @name lgb.drop_serialized
#' @title Drop serialized raw bytes in a LightGBM model object
#' @description If a LightGBM model object was produced with argument `serializable=TRUE`, the R object will keep
#' a copy of the underlying C++ object as raw bytes, which can be used to reconstruct such object after getting
#' serialized and de-serialized, but at the cost of extra memory usage. If these raw bytes are not needed anymore,
#' they can be dropped through this function in order to save memory. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @param model \code{lgb.Booster} object which was produced with `serializable=TRUE`.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
#' @seealso \link{lgb.restore_handle}, \link{lgb.make_serializable}.
#' @export
lgb.drop_serialized <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.drop_serialized: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$drop_raw()
return(invisible(model))
}
+83
View File
@@ -0,0 +1,83 @@
#' @name lgb.importance
#' @title Compute feature importance in a model
#' @description Creates a \code{data.table} of feature importances in a model.
#' @param model object of class \code{lgb.Booster}.
#' @param percentage whether to show importance in relative percentage.
#'
#' @return For a tree model, a \code{data.table} with the following columns:
#' \itemize{
#' \item{\code{Feature}: Feature names in the model.}
#' \item{\code{Gain}: The total gain of this feature's splits.}
#' \item{\code{Cover}: The number of observation related to this feature.}
#' \item{\code{Frequency}: The number of times a feature split in trees.}
#' }
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_imp1 <- lgb.importance(model, percentage = TRUE)
#' tree_imp2 <- lgb.importance(model, percentage = FALSE)
#' }
#' @importFrom data.table := setnames setorderv
#' @export
lgb.importance <- function(model, percentage = TRUE) {
if (!.is_Booster(x = model)) {
stop("'model' has to be an object of class lgb.Booster")
}
# Setup importance
tree_dt <- lgb.model.dt.tree(model = model)
# Extract elements
tree_imp_dt <- tree_dt[
!is.na(split_index)
, .(Gain = sum(split_gain), Cover = sum(internal_count), Frequency = .N)
, by = "split_feature"
]
data.table::setnames(
x = tree_imp_dt
, old = "split_feature"
, new = "Feature"
)
# Sort features by Gain
data.table::setorderv(
x = tree_imp_dt
, cols = "Gain"
, order = -1L
)
# Check if relative values are requested
if (percentage) {
tree_imp_dt[, `:=`(
Gain = Gain / sum(Gain)
, Cover = Cover / sum(Cover)
, Frequency = Frequency / sum(Frequency)
)]
}
# adding an empty [] to ensure the table is printed the first time print.data.table() is called
return(tree_imp_dt[])
}
+256
View File
@@ -0,0 +1,256 @@
#' @name lgb.interpret
#' @title Compute feature contribution of prediction
#' @description Computes feature contribution components of rawscore prediction.
#' @param model object of class \code{lgb.Booster}.
#' @param data a matrix object or a dgCMatrix object.
#' @param idxset an integer vector of indices of rows needed.
#' @param num_iteration number of iteration want to predict with, NULL or <= 0 means use best iteration.
#'
#' @return For regression, binary classification and lambdarank model, a \code{list} of \code{data.table}
#' with the following columns:
#' \itemize{
#' \item{\code{Feature}: Feature names in the model.}
#' \item{\code{Contribution}: The total contribution of this feature's splits.}
#' }
#' For multiclass classification, a \code{list} of \code{data.table} with the Feature column and
#' Contribution columns to each class.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' Logit <- function(x) log(x / (1.0 - x))
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' set_field(
#' dataset = dtrain
#' , field_name = "init_score"
#' , data = rep(Logit(mean(train$label)), length(train$label))
#' )
#' data(agaricus.test, package = "lightgbm")
#' test <- agaricus.test
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 3L
#' )
#'
#' tree_interpretation <- lgb.interpret(model, test$data, 1L:5L)
#' }
#' @importFrom data.table as.data.table
#' @export
lgb.interpret <- function(model,
data,
idxset,
num_iteration = NULL) {
# Get tree model
tree_dt <- lgb.model.dt.tree(model = model, num_iteration = num_iteration)
# Check number of classes
num_class <- model$.__enclos_env__$private$num_class
# Get vector list
tree_interpretation_dt_list <- vector(mode = "list", length = length(idxset))
# Get parsed predictions of data
pred_mat <- t(
model$predict(
data = data[idxset, , drop = FALSE]
, num_iteration = num_iteration
, predleaf = TRUE
)
)
leaf_index_dt <- data.table::as.data.table(x = pred_mat)
leaf_index_mat_list <- lapply(
X = leaf_index_dt
, FUN = matrix
, ncol = num_class
, byrow = TRUE
)
# Get list of trees
tree_index_mat_list <- lapply(
X = leaf_index_mat_list
, FUN = function(x) {
matrix(seq_along(x) - 1L, ncol = num_class, byrow = TRUE)
}
)
for (i in seq_along(idxset)) {
tree_interpretation_dt_list[[i]] <- .single_row_interpret(
tree_dt = tree_dt
, num_class = num_class
, tree_index_mat = tree_index_mat_list[[i]]
, leaf_index_mat = leaf_index_mat_list[[i]]
)
}
return(tree_interpretation_dt_list)
}
#' @name lgb.interprete
#' @title DEPRECATED - use lgb.interpret() instead
#' @description Alias for \code{lgb.interpret}.
#' @param ... Arguments passed through to \code{lgb.interpret}
#' @export
lgb.interprete <- function(...) {
warning("lgb.interprete() is deprecated and will be removed in a future release. Use lgb.interpret() instead.")
return(lgb.interpret(...))
}
#' @importFrom data.table data.table
single.tree.interpret <- function(tree_dt,
tree_id,
leaf_id) {
# Match tree id
single_tree_dt <- tree_dt[tree_index == tree_id, ]
# Get leaves
leaf_dt <- single_tree_dt[leaf_index == leaf_id, .(leaf_index, leaf_parent, leaf_value)]
# Get nodes
node_dt <- single_tree_dt[!is.na(split_index), .(split_index, split_feature, node_parent, internal_value)]
# Prepare sequences
feature_seq <- character(0L)
value_seq <- numeric(0L)
# Get to root from leaf
leaf_to_root <- function(parent_id, current_value) {
value_seq <<- c(current_value, value_seq)
if (!is.na(parent_id)) {
# Not null means existing node
this_node <- node_dt[split_index == parent_id, ]
feature_seq <<- c(this_node[["split_feature"]], feature_seq)
leaf_to_root(
parent_id = this_node[["node_parent"]]
, current_value = this_node[["internal_value"]]
)
}
}
# Perform leaf to root conversion
leaf_to_root(
parent_id = leaf_dt[["leaf_parent"]]
, current_value = leaf_dt[["leaf_value"]]
)
return(
data.table::data.table(
Feature = feature_seq
, Contribution = diff.default(value_seq)
)
)
}
#' @importFrom data.table := rbindlist setorder
.multiple_tree_interpret <- function(tree_dt,
tree_index,
leaf_index) {
interp_dt <- data.table::rbindlist(
l = mapply(
FUN = single.tree.interpret
, tree_id = tree_index
, leaf_id = leaf_index
, MoreArgs = list(
tree_dt = tree_dt
)
, SIMPLIFY = FALSE
, USE.NAMES = TRUE
)
, use.names = TRUE
)
interp_dt <- interp_dt[, .(Contribution = sum(Contribution)), by = "Feature"]
# Sort features in descending order by contribution
interp_dt[, abs_contribution := abs(Contribution)]
data.table::setorder(
x = interp_dt
, -abs_contribution
)
# Drop absolute value of contribution (only needed for sorting)
interp_dt[, abs_contribution := NULL]
return(interp_dt)
}
#' @importFrom data.table set setnames
.single_row_interpret <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) {
# Prepare vector list
tree_interpretation <- vector(mode = "list", length = num_class)
# Loop throughout each class
for (i in seq_len(num_class)) {
next_interp_dt <- .multiple_tree_interpret(
tree_dt = tree_dt
, tree_index = tree_index_mat[, i]
, leaf_index = leaf_index_mat[, i]
)
if (num_class > 1L) {
data.table::setnames(
x = next_interp_dt
, old = "Contribution"
, new = paste("Class", i - 1L)
)
}
tree_interpretation[[i]] <- next_interp_dt
}
if (num_class == 1L) {
tree_interpretation_dt <- tree_interpretation[[1L]]
} else {
# Full interpretation elements
tree_interpretation_dt <- Reduce(
f = function(x, y) {
merge(x, y, by = "Feature", all = TRUE)
}
, x = tree_interpretation
)
# Loop throughout each tree
for (j in 2L:ncol(tree_interpretation_dt)) {
data.table::set(
x = tree_interpretation_dt
, i = which(is.na(tree_interpretation_dt[[j]]))
, j = j
, value = 0.0
)
}
}
return(tree_interpretation_dt)
}
+21
View File
@@ -0,0 +1,21 @@
#' @name lgb.make_serializable
#' @title Make a LightGBM object serializable by keeping raw bytes
#' @description If a LightGBM model object was produced with argument `serializable=FALSE`, the R object will not
#' be serializable (e.g. cannot save and load with \code{saveRDS} and \code{readRDS}) as it will lack the raw bytes
#' needed to reconstruct its underlying C++ object. This function can be used to forcibly produce those serialized
#' raw bytes and make the object serializable. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @param model \code{lgb.Booster} object which was produced with `serializable=FALSE`.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
#' @seealso \link{lgb.restore_handle}, \link{lgb.drop_serialized}.
#' @export
lgb.make_serializable <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.make_serializable: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$save_raw()
return(invisible(model))
}
+192
View File
@@ -0,0 +1,192 @@
#' @name lgb.model.dt.tree
#' @title Parse a LightGBM model json dump
#' @description Parse a LightGBM model json dump into a \code{data.table} structure.
#' @param model object of class \code{lgb.Booster}.
#' @param num_iteration Number of iterations to include. NULL or <= 0 means use best iteration.
#' @param start_iteration Index (1-based) of the first boosting round to include in the output.
#' For example, passing \code{start_iteration=5, num_iteration=3} for a regression model
#' means "return information about the fifth, sixth, and seventh trees".
#'
#' \emph{New in version 4.4.0}
#'
#' @return
#' A \code{data.table} with detailed information about model trees' nodes and leaves.
#'
#' The columns of the \code{data.table} are:
#'
#' \itemize{
#' \item{\code{tree_index}: ID of a tree in a model (integer)}
#' \item{\code{split_index}: ID of a node in a tree (integer)}
#' \item{\code{split_feature}: for a node, it's a feature name (character);
#' for a leaf, it simply labels it as \code{"NA"}}
#' \item{\code{node_parent}: ID of the parent node for current node (integer)}
#' \item{\code{leaf_index}: ID of a leaf in a tree (integer)}
#' \item{\code{leaf_parent}: ID of the parent node for current leaf (integer)}
#' \item{\code{split_gain}: Split gain of a node}
#' \item{\code{threshold}: Splitting threshold value of a node}
#' \item{\code{decision_type}: Decision type of a node}
#' \item{\code{default_left}: Determine how to handle NA value, TRUE -> Left, FALSE -> Right}
#' \item{\code{internal_value}: Node value}
#' \item{\code{internal_count}: The number of observation collected by a node}
#' \item{\code{leaf_value}: Leaf value}
#' \item{\code{leaf_count}: The number of observation collected by a leaf}
#' }
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.01
#' , num_leaves = 63L
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(params, dtrain, 10L)
#'
#' tree_dt <- lgb.model.dt.tree(model)
#' }
#' @importFrom data.table := rbindlist
#' @importFrom jsonlite fromJSON
#' @export
lgb.model.dt.tree <- function(
model, num_iteration = NULL, start_iteration = 1L
) {
json_model <- lgb.dump(
booster = model
, num_iteration = num_iteration
, start_iteration = start_iteration
)
parsed_json_model <- jsonlite::fromJSON(
txt = json_model
, simplifyVector = TRUE
, simplifyDataFrame = FALSE
, simplifyMatrix = FALSE
, flatten = FALSE
)
# Parse tree model
tree_list <- lapply(
X = parsed_json_model$tree_info
, FUN = .single_tree_parse
)
# Combine into single data.table
tree_dt <- data.table::rbindlist(l = tree_list, use.names = TRUE)
# Substitute feature index with the actual feature name
# Since the index comes from C++ (which is 0-indexed), be sure
# to add 1 (e.g. index 28 means the 29th feature in feature_names)
split_feature_indx <- tree_dt[, split_feature] + 1L
# Get corresponding feature names. Positions in split_feature_indx
# which are NA will result in an NA feature name
feature_names <- parsed_json_model$feature_names[split_feature_indx]
tree_dt[, split_feature := feature_names]
return(tree_dt)
}
#' @importFrom data.table := data.table rbindlist
.single_tree_parse <- function(lgb_tree) {
tree_info_cols <- c(
"split_index"
, "split_feature"
, "split_gain"
, "threshold"
, "decision_type"
, "default_left"
, "internal_value"
, "internal_count"
)
# Traverse tree function
pre_order_traversal <- function(env = NULL, tree_node_leaf, current_depth = 0L, parent_index = NA_integer_) {
if (is.null(env)) {
# Setup initial default data.table with default types
env <- new.env(parent = emptyenv())
env$single_tree_dt <- list()
env$single_tree_dt[[1L]] <- data.table::data.table(
tree_index = integer(0L)
, depth = integer(0L)
, split_index = integer(0L)
, split_feature = integer(0L)
, node_parent = integer(0L)
, leaf_index = integer(0L)
, leaf_parent = integer(0L)
, split_gain = numeric(0L)
, threshold = numeric(0L)
, decision_type = character(0L)
, default_left = character(0L)
, internal_value = integer(0L)
, internal_count = integer(0L)
, leaf_value = integer(0L)
, leaf_count = integer(0L)
)
# start tree traversal
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf
, current_depth = current_depth
, parent_index = parent_index
)
} else {
# Check if split index is not null in leaf
if (!is.null(tree_node_leaf$split_index)) {
# update data.table
env$single_tree_dt[[length(env$single_tree_dt) + 1L]] <- c(
tree_node_leaf[tree_info_cols]
, list("depth" = current_depth, "node_parent" = parent_index)
)
# Traverse tree again both left and right
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf$left_child
, current_depth = current_depth + 1L
, parent_index = tree_node_leaf$split_index
)
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf$right_child
, current_depth = current_depth + 1L
, parent_index = tree_node_leaf$split_index
)
} else if (!is.null(tree_node_leaf$leaf_index)) {
# update list
env$single_tree_dt[[length(env$single_tree_dt) + 1L]] <- c(
tree_node_leaf[c("leaf_index", "leaf_value", "leaf_count")]
, list("depth" = current_depth, "leaf_parent" = parent_index)
)
}
}
return(env$single_tree_dt)
}
# Traverse structure and rowbind everything
single_tree_dt <- data.table::rbindlist(
pre_order_traversal(tree_node_leaf = lgb_tree$tree_structure)
, use.names = TRUE
, fill = TRUE
)
# Store index
single_tree_dt[, tree_index := lgb_tree$tree_index]
return(single_tree_dt)
}
+99
View File
@@ -0,0 +1,99 @@
#' @name lgb.plot.importance
#' @title Plot feature importance as a bar graph
#' @description Plot previously calculated feature importance: Gain, Cover and Frequency, as a bar graph.
#' @param tree_imp a \code{data.table} returned by \code{\link{lgb.importance}}.
#' @param top_n maximal number of top features to include into the plot.
#' @param measure the name of importance measure to plot, can be "Gain", "Cover" or "Frequency".
#' @param left_margin (base R barplot) allows to adjust the left margin size to fit feature names.
#' @param cex (base R barplot) passed as \code{cex.names} parameter to \code{\link[graphics]{barplot}}.
#' Set a number smaller than 1.0 to make the bar labels smaller than R's default and values
#' greater than 1.0 to make them larger.
#'
#' @details
#' The graph represents each feature as a horizontal bar of length proportional to the defined importance of a feature.
#' Features are shown ranked in a decreasing importance order.
#'
#' @return
#' The \code{lgb.plot.importance} function creates a \code{barplot}
#' and silently returns a processed data.table with \code{top_n} features sorted by defined importance.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#'
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_imp <- lgb.importance(model, percentage = TRUE)
#' lgb.plot.importance(tree_imp, top_n = 5L, measure = "Gain")
#' }
#' @importFrom graphics barplot par
#' @export
lgb.plot.importance <- function(tree_imp,
top_n = 10L,
measure = "Gain",
left_margin = 10L,
cex = NULL
) {
# Check for measurement (column names) correctness
measure <- match.arg(
measure
, choices = c("Gain", "Cover", "Frequency")
, several.ok = FALSE
)
# Get top N importance (defaults to 10)
top_n <- min(top_n, nrow(tree_imp))
# Parse importance
tree_imp <- tree_imp[order(abs(get(measure)), decreasing = TRUE), ][seq_len(top_n), ]
# Attempt to setup a correct cex
if (is.null(cex)) {
cex <- 2.5 / log2(1.0 + top_n)
}
# Refresh plot
op <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(op))
graphics::par(
mar = c(
op$mar[1L]
, left_margin
, op$mar[3L]
, op$mar[4L]
)
)
tree_imp[rev(seq_len(.N)),
graphics::barplot(
height = get(measure)
, names.arg = Feature
, horiz = TRUE
, border = NA
, main = "Feature Importance"
, xlab = measure
, cex.names = cex
, las = 1L
)]
return(invisible(tree_imp))
}
+166
View File
@@ -0,0 +1,166 @@
#' @name lgb.plot.interpretation
#' @title Plot feature contribution as a bar graph
#' @description Plot previously calculated feature contribution as a bar graph.
#' @param tree_interpretation_dt a \code{data.table} returned by \code{\link{lgb.interpret}}.
#' @param top_n maximal number of top features to include into the plot.
#' @param cols the column numbers of layout, will be used only for multiclass classification feature contribution.
#' @param left_margin (base R barplot) allows to adjust the left margin size to fit feature names.
#' @param cex (base R barplot) passed as \code{cex.names} parameter to \code{barplot}.
#'
#' @details
#' The graph represents each feature as a horizontal bar of length proportional to the defined
#' contribution of a feature. Features are shown ranked in a decreasing contribution order.
#'
#' @return
#' The \code{lgb.plot.interpretation} function creates a \code{barplot}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' Logit <- function(x) {
#' log(x / (1.0 - x))
#' }
#' data(agaricus.train, package = "lightgbm")
#' labels <- agaricus.train$label
#' dtrain <- lgb.Dataset(
#' agaricus.train$data
#' , label = labels
#' )
#' set_field(
#' dataset = dtrain
#' , field_name = "init_score"
#' , data = rep(Logit(mean(labels)), length(labels))
#' )
#'
#' data(agaricus.test, package = "lightgbm")
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_interpretation <- lgb.interpret(
#' model = model
#' , data = agaricus.test$data
#' , idxset = 1L:5L
#' )
#' lgb.plot.interpretation(
#' tree_interpretation_dt = tree_interpretation[[1L]]
#' , top_n = 3L
#' )
#' }
#' @importFrom data.table setnames
#' @importFrom graphics barplot par
#' @export
lgb.plot.interpretation <- function(tree_interpretation_dt,
top_n = 10L,
cols = 1L,
left_margin = 10L,
cex = NULL) {
num_class <- ncol(tree_interpretation_dt) - 1L
# Refresh plot
op <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(op))
# Do some magic plotting
bottom_margin <- 3.0
top_margin <- 2.0
right_margin <- op$mar[4L]
graphics::par(
mar = c(
bottom_margin
, left_margin
, top_margin
, right_margin
)
)
if (num_class == 1L) {
# Only one class, plot straight away
.multiple_tree_plot_interpretation(
tree_interpretation = tree_interpretation_dt
, top_n = top_n
, title = NULL
, cex = cex
)
} else {
# More than one class, shape data first
layout_mat <- matrix(
seq.int(to = cols * ceiling(num_class / cols))
, ncol = cols
, nrow = ceiling(num_class / cols)
)
# Shape output
graphics::par(mfcol = c(nrow(layout_mat), ncol(layout_mat)))
# Loop throughout all classes
for (i in seq_len(num_class)) {
# Prepare interpretation, perform T, get the names, and plot straight away
plot_dt <- tree_interpretation_dt[, c(1L, i + 1L), with = FALSE]
data.table::setnames(
x = plot_dt
, old = names(plot_dt)
, new = c("Feature", "Contribution")
)
.multiple_tree_plot_interpretation(
tree_interpretation = plot_dt
, top_n = top_n
, title = paste("Class", i - 1L)
, cex = cex
)
}
}
return(invisible(NULL))
}
#' @importFrom graphics barplot
.multiple_tree_plot_interpretation <- function(tree_interpretation,
top_n,
title,
cex) {
# Parse tree
tree_interpretation <- tree_interpretation[order(abs(Contribution), decreasing = TRUE), ][seq_len(min(top_n, .N)), ]
# Attempt to setup a correct cex
if (is.null(cex)) {
cex <- 2.5 / log2(1.0 + top_n)
}
# create plot
tree_interpretation[abs(Contribution) > 0.0, bar_color := "firebrick"]
tree_interpretation[Contribution == 0.0, bar_color := "steelblue"]
tree_interpretation[rev(seq_len(.N)),
graphics::barplot(
height = Contribution
, names.arg = Feature
, horiz = TRUE
, col = bar_color
, border = NA
, main = title
, cex.names = cex
, las = 1L
)]
return(invisible(NULL))
}
+47
View File
@@ -0,0 +1,47 @@
#' @name lgb.restore_handle
#' @title Restore the C++ component of a de-serialized LightGBM model
#' @description After a LightGBM model object is de-serialized through functions such as \code{save} or
#' \code{saveRDS}, its underlying C++ object will be blank and needs to be restored to able to use it. Such
#' object is restored automatically when calling functions such as \code{predict}, but this function can be
#' used to forcibly restore it beforehand. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @details Be aware that fast single-row prediction configurations are not restored through this
#' function. If you wish to make fast single-row predictions using a \code{lgb.Booster} loaded this way,
#' call \link{lgb.configure_fast_predict} on the loaded \code{lgb.Booster} object.
#' @param model \code{lgb.Booster} object which was de-serialized and whose underlying C++ object and R handle
#' need to be restored.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, invisibly).
#' @seealso \link{lgb.make_serializable}, \link{lgb.drop_serialized}.
#' @examples
#' \donttest{
#' library(lightgbm)
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data("agaricus.train")
#' model <- lightgbm(
#' agaricus.train$data
#' , agaricus.train$label
#' , params = list(objective = "binary")
#' , nrounds = 5L
#' , verbose = 0
#' , num_threads = 2L
#' )
#' fname <- tempfile(fileext="rds")
#' saveRDS(model, fname)
#'
#' model_new <- readRDS(fname)
#' model_new$check_null_handle()
#' lgb.restore_handle(model_new)
#' model_new$check_null_handle()
#' }
#' @export
lgb.restore_handle <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.restore_handle: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$restore_handle()
return(invisible(model))
}
+383
View File
@@ -0,0 +1,383 @@
#' @name lgb.train
#' @title Main training logic for LightGBM
#' @description Low-level R interface to train a LightGBM model. Unlike \code{\link{lightgbm}},
#' this function is focused on performance (e.g. speed, memory efficiency). It is also
#' less likely to have breaking API changes in new releases than \code{\link{lightgbm}}.
#' @inheritParams lgb_shared_params
#' @param valids a list of \code{lgb.Dataset} objects, used for validation
#' @param record Boolean, TRUE will record iteration message to \code{booster$record_evals}
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param reset_data Boolean, setting it to TRUE (not the default value) will transform the
#' booster model into a predictor model which frees up memory and the
#' original datasets
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained booster model \code{lgb.Booster}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' data(agaricus.test, package = "lightgbm")
#' test <- agaricus.test
#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
#' params <- list(
#' objective = "regression"
#' , metric = "l2"
#' , min_data = 1L
#' , learning_rate = 1.0
#' , num_threads = 2L
#' )
#' valids <- list(test = dtest)
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' , valids = valids
#' , early_stopping_rounds = 3L
#' )
#' }
#'
#' @export
lgb.train <- function(params = list(),
data,
nrounds = 100L,
valids = list(),
obj = NULL,
eval = NULL,
verbose = 1L,
record = TRUE,
eval_freq = 1L,
init_model = NULL,
early_stopping_rounds = NULL,
callbacks = list(),
reset_data = FALSE,
serializable = TRUE) {
# validate inputs early to avoid unnecessary computation
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (!.is_Dataset(x = data)) {
stop("lgb.train: data must be an lgb.Dataset instance")
}
if (length(valids) > 0L) {
if (!identical(class(valids), "list") || !all(vapply(valids, .is_Dataset, logical(1L)))) {
stop("lgb.train: valids must be a list of lgb.Dataset elements")
}
evnames <- names(valids)
if (is.null(evnames) || !all(nzchar(evnames))) {
stop("lgb.train: each element of valids must have a name")
}
}
# set some parameters, resolving the way they were passed in with other parameters
# in `params`.
# this ensures that the model stored with Booster$save() correctly represents
# what was passed in
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
params <- .check_wrapper_param(
main_param_name = "num_iterations"
, params = params
, alternative_kwarg_value = nrounds
)
params <- .check_wrapper_param(
main_param_name = "metric"
, params = params
, alternative_kwarg_value = NULL
)
params <- .check_wrapper_param(
main_param_name = "objective"
, params = params
, alternative_kwarg_value = obj
)
params <- .check_wrapper_param(
main_param_name = "early_stopping_round"
, params = params
, alternative_kwarg_value = early_stopping_rounds
)
early_stopping_rounds <- params[["early_stopping_round"]]
# extract any function objects passed for objective or metric
fobj <- NULL
if (is.function(params$objective)) {
fobj <- params$objective
params$objective <- "none"
}
# If eval is a single function, store it as a 1-element list
# (for backwards compatibility). If it is a list of functions, store
# all of them. This makes it possible to pass any mix of strings like "auc"
# and custom functions to eval
params <- .check_eval(params = params, eval = eval)
eval_functions <- list(NULL)
if (is.function(eval)) {
eval_functions <- list(eval)
}
if (methods::is(eval, "list")) {
eval_functions <- Filter(
f = is.function
, x = eval
)
}
# Init predictor to empty
predictor <- NULL
# Check for boosting from a trained model
if (is.character(init_model)) {
predictor <- Predictor$new(modelfile = init_model)
} else if (.is_Booster(x = init_model)) {
predictor <- init_model$to_predictor()
}
# Set the iteration to start from / end to (and check for boosting from a trained model, again)
begin_iteration <- 1L
if (!is.null(predictor)) {
begin_iteration <- predictor$current_iter() + 1L
}
end_iteration <- begin_iteration + params[["num_iterations"]] - 1L
# pop interaction_constraints off of params. It needs some preprocessing on the
# R side before being passed into the Dataset object
interaction_constraints <- params[["interaction_constraints"]]
params["interaction_constraints"] <- NULL
# Construct datasets, if needed
data$update_params(params = params)
data$construct()
# Check interaction constraints
params[["interaction_constraints"]] <- .check_interaction_constraints(
interaction_constraints = interaction_constraints
, column_names = data$get_colnames()
)
# Update parameters with parsed parameters
data$update_params(params)
# Create the predictor set
data$.__enclos_env__$private$set_predictor(predictor)
valid_contain_train <- FALSE
train_data_name <- "train"
reduced_valid_sets <- list()
# Parse validation datasets
if (length(valids) > 0L) {
for (key in names(valids)) {
# Use names to get validation datasets
valid_data <- valids[[key]]
# Check for duplicate train/validation dataset
if (identical(data, valid_data)) {
valid_contain_train <- TRUE
train_data_name <- key
next
}
# Update parameters, data
valid_data$update_params(params)
valid_data$set_reference(data)
reduced_valid_sets[[key]] <- valid_data
}
}
# Add printing log callback
if (params[["verbosity"]] > 0L && eval_freq > 0L) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_print_evaluation(period = eval_freq)
)
}
# Add evaluation log callback
if (record && length(valids) > 0L) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_record_evaluation()
)
}
# Did user pass parameters that indicate they want to use early stopping?
using_early_stopping <- !is.null(early_stopping_rounds) && early_stopping_rounds > 0L
boosting_param_names <- .PARAMETER_ALIASES()[["boosting"]]
using_dart <- any(
sapply(
X = boosting_param_names
, FUN = function(param) {
identical(params[[param]], "dart")
}
)
)
# Cannot use early stopping with 'dart' boosting
if (using_dart) {
if (using_early_stopping) {
warning("Early stopping is not available in 'dart' mode.")
}
using_early_stopping <- FALSE
# Remove the cb_early_stop() function if it was passed in to callbacks
callbacks <- Filter(
f = function(cb_func) {
!identical(attr(cb_func, "name"), "cb_early_stop")
}
, x = callbacks
)
}
# If user supplied early_stopping_rounds, add the early stopping callback
if (using_early_stopping) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_early_stop(
stopping_rounds = early_stopping_rounds
, first_metric_only = isTRUE(params[["first_metric_only"]])
, verbose = params[["verbosity"]] > 0L
)
)
}
cb <- .categorize_callbacks(cb_list = callbacks)
# Construct booster with datasets
booster <- Booster$new(params = params, train_set = data)
if (valid_contain_train) {
booster$set_train_data_name(name = train_data_name)
}
for (key in names(reduced_valid_sets)) {
booster$add_valid(data = reduced_valid_sets[[key]], name = key)
}
# Callback env
env <- CB_ENV$new()
env$model <- booster
env$begin_iteration <- begin_iteration
env$end_iteration <- end_iteration
# Start training model using number of iterations to start and end with
for (i in seq.int(from = begin_iteration, to = end_iteration)) {
# Overwrite iteration in environment
env$iteration <- i
env$eval_list <- list()
# Loop through "pre_iter" element
for (f in cb$pre_iter) {
f(env)
}
# Update one boosting iteration
booster$update(fobj = fobj)
# Prepare collection of evaluation results
eval_list <- list()
# Collection: Has validation dataset?
if (length(valids) > 0L) {
# Get evaluation results with passed-in functions
for (eval_function in eval_functions) {
# Validation has training dataset?
if (valid_contain_train) {
eval_list <- append(eval_list, booster$eval_train(feval = eval_function))
}
eval_list <- append(eval_list, booster$eval_valid(feval = eval_function))
}
# Calling booster$eval_valid() will get
# evaluation results with the metrics in params$metric by calling LGBM_BoosterGetEval_R",
# so need to be sure that gets called, which it wouldn't be above if no functions
# were passed in
if (length(eval_functions) == 0L) {
if (valid_contain_train) {
eval_list <- append(eval_list, booster$eval_train(feval = eval_function))
}
eval_list <- append(eval_list, booster$eval_valid(feval = eval_function))
}
}
# Write evaluation result in environment
env$eval_list <- eval_list
# Loop through env
for (f in cb$post_iter) {
f(env)
}
# Check for early stopping and break if needed
if (env$met_early_stop) break
}
# check if any valids were given other than the training data
non_train_valid_names <- names(valids)[!(names(valids) == train_data_name)]
first_valid_name <- non_train_valid_names[1L]
# When early stopping is not activated, we compute the best iteration / score ourselves by
# selecting the first metric and the first dataset
if (record && length(non_train_valid_names) > 0L && is.na(env$best_score)) {
# when using a custom eval function, the metric name is returned from the
# function, so figure it out from record_evals
if (!is.null(eval_functions[1L])) {
first_metric <- names(booster$record_evals[[first_valid_name]])[1L]
} else {
first_metric <- booster$.__enclos_env__$private$eval_names[1L]
}
.find_best <- which.min
if (isTRUE(env$eval_list[[1L]]$higher_better[1L])) {
.find_best <- which.max
}
booster$best_iter <- unname(
.find_best(
unlist(
booster$record_evals[[first_valid_name]][[first_metric]][[.EVAL_KEY()]]
)
)
)
booster$best_score <- booster$record_evals[[first_valid_name]][[first_metric]][[.EVAL_KEY()]][[booster$best_iter]]
}
# Check for booster model conversion to predictor model
if (reset_data) {
# Store temporarily model data elsewhere
booster_old <- list(
best_iter = booster$best_iter
, best_score = booster$best_score
, record_evals = booster$record_evals
)
# Reload model
booster <- lgb.load(model_str = booster$save_model_to_string())
booster$best_iter <- booster_old$best_iter
booster$best_score <- booster_old$best_score
booster$record_evals <- booster_old$record_evals
}
if (serializable) {
booster$save_raw()
}
return(booster)
}
+368
View File
@@ -0,0 +1,368 @@
#' @name lgb_shared_params
#' @title Shared parameter docs
#' @description Parameter docs shared by \code{lgb.train}, \code{lgb.cv}, and \code{lightgbm}
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param data a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
#' may allow you to pass other types of data like \code{matrix} and then separately supply
#' \code{label} as a keyword argument.
#' @param early_stopping_rounds int. Activates early stopping. When this parameter is non-null,
#' training will stop if the evaluation of any metric on any validation set
#' fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
#' If training stops early, the returned model will have attribute \code{best_iter}
#' set to the iteration number of the best iteration.
#' @param eval evaluation function(s). This can be a character vector, function, or list with a mixture of
#' strings and functions.
#'
#' \itemize{
#' \item{\bold{a. character vector}:
#' If you provide a character vector to this argument, it should contain strings with valid
#' evaluation metrics.
#' See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric}{
#' The "metric" section of the documentation}
#' for a list of valid metrics.
#' }
#' \item{\bold{b. function}:
#' You can provide a custom evaluation function. This
#' should accept the keyword arguments \code{preds} and \code{dtrain} and should return a named
#' list with three elements:
#' \itemize{
#' \item{\code{name}: A string with the name of the metric, used for printing
#' and storing results.
#' }
#' \item{\code{value}: A single number indicating the value of the metric for the
#' given predictions and true values
#' }
#' \item{
#' \code{higher_better}: A boolean indicating whether higher values indicate a better fit.
#' For example, this would be \code{FALSE} for metrics like MAE or RMSE.
#' }
#' }
#' }
#' \item{\bold{c. list}:
#' If a list is given, it should only contain character vectors and functions.
#' These should follow the requirements from the descriptions above.
#' }
#' }
#' @param eval_freq evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided
#' @param init_model path of model file or \code{lgb.Booster} object, will continue training from this model
#' @param nrounds number of training rounds
#' @param obj objective function, can be character or custom objective function. Examples include
#' \code{regression}, \code{regression_l1}, \code{huber},
#' \code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}
#' @param params a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
#' the "Parameters" section of the documentation} for a list of parameters and valid values.
#' @param verbose verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
#' printing of evaluation during training
#' @param serializable whether to make the resulting objects serializable through functions such as
#' \code{save} or \code{saveRDS} (see section "Model serialization").
#' @section Early Stopping:
#'
#' "early stopping" refers to stopping the training process if the model's performance on a given
#' validation set does not improve for several consecutive iterations.
#'
#' If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
#' early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
#' metrics will be considered for early stopping.
#'
#' If you want to only consider the first metric for early stopping, pass
#' \code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
#' in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
#' a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
#' or \code{objective} (passed into \code{params}).
#'
#' \bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
#' and early stopping will not be performed.
#' @section Model serialization:
#'
#' LightGBM model objects can be serialized and de-serialized through functions such as \code{save}
#' or \code{saveRDS}, but similarly to libraries such as 'xgboost', serialization works a bit differently
#' from typical R objects. In order to make models serializable in R, a copy of the underlying C++ object
#' as serialized raw bytes is produced and stored in the R model object, and when this R object is
#' de-serialized, the underlying C++ model object gets reconstructed from these raw bytes, but will only
#' do so once some function that uses it is called, such as \code{predict}. In order to forcibly
#' reconstruct the C++ object after deserialization (e.g. after calling \code{readRDS} or similar), one
#' can use the function \link{lgb.restore_handle} (for example, if one makes predictions in parallel or in
#' forked processes, it will be faster to restore the handle beforehand).
#'
#' Producing and keeping these raw bytes however uses extra memory, and if they are not required,
#' it is possible to avoid producing them by passing `serializable=FALSE`. In such cases, these raw
#' bytes can be added to the model on demand through function \link{lgb.make_serializable}.
#'
#' \emph{New in version 4.0.0}
#'
#' @keywords internal
NULL
#' @name lightgbm
#' @title Train a LightGBM model
#' @description High-level R interface to train a LightGBM model. Unlike \code{\link{lgb.train}}, this function
#' is focused on compatibility with other statistics and machine learning interfaces in R.
#' This focus on compatibility means that this interface may experience more frequent breaking API changes
#' than \code{\link{lgb.train}}.
#' For efficiency-sensitive applications, or for applications where breaking API changes across releases
#' is very expensive, use \code{\link{lgb.train}}.
#' @inheritParams lgb_shared_params
#' @param label Vector of labels, used if \code{data} is not an \code{\link{lgb.Dataset}}
#' @param weights Sample / observation weights for rows in the input data. If \code{NULL}, will assume that all
#' observations / rows have the same importance / weight.
#'
#' \emph{Changed from 'weight', in version 4.0.0}
#'
#' @param objective Optimization objective (e.g. `"regression"`, `"binary"`, etc.).
#' For a list of accepted objectives, see
#' \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#objective}{
#' the "objective" item of the "Parameters" section of the documentation}.
#'
#' If passing \code{"auto"} and \code{data} is not of type \code{lgb.Dataset}, the objective will
#' be determined according to what is passed for \code{label}:\itemize{
#' \item If passing a factor with two variables, will use objective \code{"binary"}.
#' \item If passing a factor with more than two variables, will use objective \code{"multiclass"}
#' (note that parameter \code{num_class} in this case will also be determined automatically from
#' \code{label}).
#' \item Otherwise (or if passing \code{lgb.Dataset} as input), will use objective \code{"regression"}.
#' }
#'
#' \emph{New in version 4.0.0}
#'
#' @param init_score initial score is the base prediction lightgbm will boost from
#'
#' \emph{New in version 4.0.0}
#'
#' @param num_threads Number of parallel threads to use. For best speed, this should be set to the number of
#' physical cores in the CPU - in a typical x86-64 machine, this corresponds to half the
#' number of maximum threads.
#'
#' Be aware that using too many threads can result in speed degradation in smaller datasets
#' (see the parameters documentation for more details).
#'
#' If passing zero, will use the default number of threads configured for OpenMP
#' (typically controlled through an environment variable \code{OMP_NUM_THREADS}).
#'
#' If passing \code{NULL} (the default), will try to use the number of physical cores in the
#' system, but be aware that getting the number of cores detected correctly requires package
#' \code{RhpcBLASctl} to be installed.
#'
#' This parameter gets overridden by \code{num_threads} and its aliases under \code{params}
#' if passed there.
#'
#' \emph{New in version 4.0.0}
#'
#' @param colnames Character vector of features. Only used if \code{data} is not an \code{\link{lgb.Dataset}}.
#' @param categorical_feature categorical features. This can either be a character vector of feature
#' names or an integer vector with the indices of the features (e.g.
#' \code{c(1L, 10L)} to say "the first and tenth columns").
#' Only used if \code{data} is not an \code{\link{lgb.Dataset}}.
#'
#' @param ... Additional arguments passed to \code{\link{lgb.train}}. For example
#' \itemize{
#' \item{\code{valids}: a list of \code{lgb.Dataset} objects, used for validation}
#' \item{\code{obj}: objective function, can be character or custom objective function. Examples include
#' \code{regression}, \code{regression_l1}, \code{huber},
#' \code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
#' \item{\code{eval}: evaluation function, can be (a list of) character or custom eval function}
#' \item{\code{record}: Boolean, TRUE will record iteration message to \code{booster$record_evals}}
#' \item{\code{reset_data}: Boolean, setting it to TRUE (not the default value) will transform the booster model
#' into a predictor model which frees up memory and the original datasets}
#' }
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained \code{lgb.Booster}
#' @export
lightgbm <- function(data,
label = NULL,
weights = NULL,
params = list(),
nrounds = 100L,
verbose = 1L,
eval_freq = 1L,
early_stopping_rounds = NULL,
init_model = NULL,
callbacks = list(),
serializable = TRUE,
objective = "auto",
init_score = NULL,
num_threads = NULL,
colnames = NULL,
categorical_feature = NULL,
...) {
# validate inputs early to avoid unnecessary computation
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (is.null(num_threads)) {
num_threads <- .get_default_num_threads()
}
params <- .check_wrapper_param(
main_param_name = "num_threads"
, params = params
, alternative_kwarg_value = num_threads
)
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
# Process factors as labels and auto-determine objective
if (!.is_Dataset(data)) {
data_processor <- DataProcessor$new()
temp <- data_processor$process_label(
label = label
, objective = objective
, params = params
)
label <- temp$label
objective <- temp$objective
params <- temp$params
rm(temp)
} else {
data_processor <- NULL
if (objective == "auto") {
objective <- "regression"
}
}
# Set data to a temporary variable
dtrain <- data
# Check whether data is lgb.Dataset, if not then create lgb.Dataset manually
if (!.is_Dataset(x = dtrain)) {
dtrain <- lgb.Dataset(
data = data
, label = label
, weight = weights
, init_score = init_score
, categorical_feature = categorical_feature
, colnames = colnames
)
}
train_args <- list(
"params" = params
, "data" = dtrain
, "nrounds" = nrounds
, "obj" = objective
, "verbose" = params[["verbosity"]]
, "eval_freq" = eval_freq
, "early_stopping_rounds" = early_stopping_rounds
, "init_model" = init_model
, "callbacks" = callbacks
, "serializable" = serializable
)
train_args <- append(train_args, list(...))
if (! "valids" %in% names(train_args)) {
train_args[["valids"]] <- list()
}
# Train a model using the regular way
bst <- do.call(
what = lgb.train
, args = train_args
)
bst$data_processor <- data_processor
return(bst)
}
#' @name agaricus.train
#' @title Training part from Mushroom Data Set
#' @description This data set is originally from the Mushroom data set,
#' UCI Machine Learning Repository.
#' This data set includes the following fields:
#'
#' \itemize{
#' \item{\code{label}: the label for each record}
#' \item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
#' }
#'
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Mushroom
#'
#' Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
#' [https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
#' School of Information and Computer Science.
#'
#' @docType data
#' @keywords datasets
#' @usage data(agaricus.train)
#' @format A list containing a label vector, and a dgCMatrix object with 6513
#' rows and 127 variables
NULL
#' @name agaricus.test
#' @title Test part from Mushroom Data Set
#' @description This data set is originally from the Mushroom data set,
#' UCI Machine Learning Repository.
#' This data set includes the following fields:
#'
#' \itemize{
#' \item{\code{label}: the label for each record}
#' \item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
#' }
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Mushroom
#'
#' Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
#' [https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
#' School of Information and Computer Science.
#'
#' @docType data
#' @keywords datasets
#' @usage data(agaricus.test)
#' @format A list containing a label vector, and a dgCMatrix object with 1611
#' rows and 126 variables
NULL
#' @name bank
#' @title Bank Marketing Data Set
#' @description This data set is originally from the Bank Marketing data set,
#' UCI Machine Learning Repository.
#'
#' It contains only the following: bank.csv with 10% of the examples and 17 inputs,
#' randomly selected from 3 (older version of this dataset with less inputs).
#'
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Bank+Marketing
#'
#' S. Moro, P. Cortez and P. Rita. (2014)
#' A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems
#'
#' @docType data
#' @keywords datasets
#' @usage data(bank)
#' @format A data.table with 4521 rows and 17 variables
NULL
# Various imports
#' @import methods
#' @importFrom Matrix Matrix
#' @importFrom R6 R6Class
#' @useDynLib lightgbm , .registration = TRUE
NULL
# Suppress false positive warnings from R CMD CHECK about
# "unrecognized global variable"
globalVariables(c(
"."
, ".N"
, ".SD"
, "abs_contribution"
, "bar_color"
, "Contribution"
, "Cover"
, "Feature"
, "Frequency"
, "Gain"
, "internal_count"
, "internal_value"
, "leaf_index"
, "leaf_parent"
, "leaf_value"
, "node_parent"
, "split_feature"
, "split_gain"
, "split_index"
, "tree_index"
))
+38
View File
@@ -0,0 +1,38 @@
# [description] List of metrics known to LightGBM. The most up to date list can be found
# at https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric-parameters
#
# [return] A named logical vector, where each key is a metric name and each value is a boolean.
# TRUE if higher values of the metric are desirable, FALSE if lower values are desirable.
# Note that only the 'main' metrics are stored here, not aliases, since only the 'main' metrics
# are returned from the C++ side. For example, if you use `metric = "mse"` in your code,
# the metric name `"l2"` will be returned.
.METRICS_HIGHER_BETTER <- function() {
return(
c(
"l1" = FALSE
, "l2" = FALSE
, "mape" = FALSE
, "rmse" = FALSE
, "quantile" = FALSE
, "huber" = FALSE
, "fair" = FALSE
, "poisson" = FALSE
, "gamma" = FALSE
, "gamma_deviance" = FALSE
, "tweedie" = FALSE
, "ndcg" = TRUE
, "map" = TRUE
, "auc" = TRUE
, "average_precision" = TRUE
, "r2" = TRUE
, "binary_logloss" = FALSE
, "binary_error" = FALSE
, "auc_mu" = TRUE
, "multi_logloss" = FALSE
, "multi_error" = FALSE
, "cross_entropy" = FALSE
, "cross_entropy_lambda" = FALSE
, "kullback_leibler" = FALSE
)
)
}
+51
View File
@@ -0,0 +1,51 @@
#' @name setLGBMThreads
#' @title Set maximum number of threads used by LightGBM
#' @description LightGBM attempts to speed up many operations by using multi-threading.
#' The number of threads used in those operations can be controlled via the
#' \code{num_threads} parameter passed through \code{params} to functions like
#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
#' a model from a text file) are done via code paths that don't explicitly accept thread-control
#' configuration.
#'
#' Use this function to set the maximum number of threads LightGBM will use for such operations.
#'
#' This function affects all LightGBM operations in the same process.
#'
#' So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM
#' operation in the same process will use more than 4 threads.
#'
#' Call \code{setLGBMthreads(-1)} to remove this limitation.
#' @param num_threads maximum number of threads to be used by LightGBM in multi-threaded operations
#' @return NULL
#' @seealso \link{getLGBMthreads}
#' @export
setLGBMthreads <- function(num_threads) {
.Call(
LGBM_SetMaxThreads_R,
num_threads
)
return(invisible(NULL))
}
#' @name getLGBMThreads
#' @title Get default number of threads used by LightGBM
#' @description LightGBM attempts to speed up many operations by using multi-threading.
#' The number of threads used in those operations can be controlled via the
#' \code{num_threads} parameter passed through \code{params} to functions like
#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
#' a model from a text file) are done via code paths that don't explicitly accept thread-control
#' configuration.
#'
#' Use this function to see the default number of threads LightGBM will use for such operations.
#' @return number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is
#' not explicitly supplied, LightGBM will choose a number of threads to use automatically.
#' @seealso \link{setLGBMthreads}
#' @export
getLGBMthreads <- function() {
out <- 0L
.Call(
LGBM_GetMaxThreads_R,
out
)
return(out)
}
+260
View File
@@ -0,0 +1,260 @@
.is_Booster <- function(x) {
return(all(c("R6", "lgb.Booster") %in% class(x))) # nolint: class_equals.
}
.is_Dataset <- function(x) {
return(all(c("R6", "lgb.Dataset") %in% class(x))) # nolint: class_equals.
}
.is_Predictor <- function(x) {
return(all(c("R6", "lgb.Predictor") %in% class(x))) # nolint: class_equals.
}
.is_null_handle <- function(x) {
if (is.null(x)) {
return(TRUE)
}
return(
isTRUE(.Call(LGBM_HandleIsNull_R, x))
)
}
.params2str <- function(params) {
if (!identical(class(params), "list")) {
stop("params must be a list")
}
names(params) <- gsub(".", "_", names(params), fixed = TRUE)
param_names <- names(params)
ret <- list()
# Perform key value join
for (i in seq_along(params)) {
# If a parameter has multiple values, join those values together with commas.
# trimws() is necessary because format() will pad to make strings the same width
val <- paste(
trimws(
format(
x = unname(params[[i]])
, scientific = FALSE
)
)
, collapse = ","
)
if (nchar(val) <= 0L) next # Skip join
# Join key value
pair <- paste(c(param_names[[i]], val), collapse = "=")
ret <- c(ret, pair)
}
if (length(ret) == 0L) {
return("")
}
return(paste(ret, collapse = " "))
}
# [description]
#
# Besides applying checks, this function
#
# 1. turns feature *names* into 1-based integer positions, then
# 2. adds an extra list element with skipped features, then
# 3. turns 1-based integer positions into 0-based positions, and finally
# 4. collapses the values of each list element into a string like "[0, 1]".
#
.check_interaction_constraints <- function(interaction_constraints, column_names) {
if (is.null(interaction_constraints)) {
return(list())
}
if (!identical(class(interaction_constraints), "list")) {
stop("interaction_constraints must be a list")
}
column_indices <- seq_along(column_names)
# Convert feature names to 1-based integer positions and apply checks
for (j in seq_along(interaction_constraints)) {
constraint <- interaction_constraints[[j]]
if (is.character(constraint)) {
constraint_indices <- match(constraint, column_names)
} else if (is.numeric(constraint)) {
constraint_indices <- as.integer(constraint)
} else {
stop("every element in interaction_constraints must be a character vector or numeric vector")
}
# Features outside range?
bad <- !(constraint_indices %in% column_indices)
if (any(bad)) {
stop(
"unknown feature(s) in interaction_constraints: "
, toString(sQuote(constraint[bad], q = FALSE))
)
}
interaction_constraints[[j]] <- constraint_indices
}
# Add missing features as new interaction set
remaining_indices <- setdiff(
column_indices, sort(unique(unlist(interaction_constraints)))
)
if (length(remaining_indices) > 0L) {
interaction_constraints <- c(
interaction_constraints, list(remaining_indices)
)
}
# Turn indices 0-based and convert to string
for (j in seq_along(interaction_constraints)) {
interaction_constraints[[j]] <- paste0(
"[", paste(interaction_constraints[[j]] - 1L, collapse = ","), "]"
)
}
return(interaction_constraints)
}
# [description]
# Take any character values from eval and store them in params$metric.
# This has to account for the fact that `eval` could be a character vector,
# a function, a list of functions, or a list with a mix of strings and
# functions
.check_eval <- function(params, eval) {
if (is.null(params$metric)) {
params$metric <- list()
} else if (is.character(params$metric)) {
params$metric <- as.list(params$metric)
}
# if 'eval' is a character vector or list, find the character
# elements and add them to 'metric'
if (!is.function(eval)) {
for (i in seq_along(eval)) {
element <- eval[[i]]
if (is.character(element)) {
params$metric <- append(params$metric, element)
}
}
}
# If more than one character metric was given, then "None" should
# not be included
if (length(params$metric) > 1L) {
params$metric <- Filter(
f = function(metric) {
!(metric %in% .NO_METRIC_STRINGS())
}
, x = params$metric
)
}
# duplicate metrics should be filtered out
params$metric <- as.list(unique(unlist(params$metric)))
return(params)
}
# [description]
#
# Resolve differences between passed-in keyword arguments, parameters,
# and parameter aliases. This function exists because some functions in the
# package take in parameters through their own keyword arguments other than
# the `params` list.
#
# If the same underlying parameter is provided multiple
# ways, the first item in this list is used:
#
# 1. the main (non-alias) parameter found in `params`
# 2. the alias with the highest priority found in `params`
# 3. the keyword argument passed in
#
# For example, "num_iterations" can also be provided to lgb.train()
# via keyword "nrounds". lgb.train() will choose one value for this parameter
# based on the first match in this list:
#
# 1. params[["num_iterations]]
# 2. the highest priority alias of "num_iterations" found in params
# 3. the nrounds keyword argument
#
# If multiple aliases are found in `params` for the same parameter, they are
# all removed before returning `params`.
#
# [return]
# params with num_iterations set to the chosen value, and other aliases
# of num_iterations removed
.check_wrapper_param <- function(main_param_name, params, alternative_kwarg_value) {
aliases <- .PARAMETER_ALIASES()[[main_param_name]]
aliases_provided <- aliases[aliases %in% names(params)]
aliases_provided <- aliases_provided[aliases_provided != main_param_name]
# prefer the main parameter
if (!is.null(params[[main_param_name]])) {
for (param in aliases_provided) {
params[[param]] <- NULL
}
return(params)
}
# if the main parameter wasn't provided, prefer the first alias
if (length(aliases_provided) > 0L) {
first_param <- aliases_provided[1L]
params[[main_param_name]] <- params[[first_param]]
for (param in aliases_provided) {
params[[param]] <- NULL
}
return(params)
}
# if not provided in params at all, use the alternative value provided
# through a keyword argument from lgb.train(), lgb.cv(), etc.
params[[main_param_name]] <- alternative_kwarg_value
return(params)
}
#' @importFrom parallel detectCores
.get_default_num_threads <- function() {
if (requireNamespace("RhpcBLASctl", quietly = TRUE)) { # nolint: undesirable_function.
return(RhpcBLASctl::get_num_cores())
} else {
msg <- "Optional package 'RhpcBLASctl' not found."
cores <- 0L
if (Sys.info()["sysname"] != "Linux") {
cores <- parallel::detectCores(logical = FALSE)
if (is.na(cores) || cores < 0L) {
cores <- 0L
}
}
if (cores == 0L) {
msg <- paste(msg, "Will use default number of OpenMP threads.", sep = " ")
} else {
msg <- paste(msg, "Detection of CPU cores might not be accurate.", sep = " ")
}
warning(msg)
return(cores)
}
}
.equal_or_both_null <- function(a, b) {
if (is.null(a)) {
if (!is.null(b)) {
return(FALSE)
}
return(TRUE)
} else {
if (is.null(b)) {
return(FALSE)
}
return(a == b)
}
}
+508
View File
@@ -0,0 +1,508 @@
# LightGBM R-package
[![CRAN Version](https://www.r-pkg.org/badges/version/lightgbm)](https://cran.r-project.org/package=lightgbm)
[![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/lightgbm)](https://cran.r-project.org/package=lightgbm)
[![API Docs](https://readthedocs.org/projects/lightgbm/badge/?version=latest)](https://lightgbm.readthedocs.io/en/latest/R/reference/)
<img src="man/figures/logo.svg" align="right" alt="" width="175" />
### Contents
* [Installation](#installation)
- [Installing the CRAN Package](#installing-the-cran-package)
- [Installing from Source with CMake](#install)
- [Installing a GPU-enabled Build](#installing-a-gpu-enabled-build)
- [Installing Precompiled Binaries](#installing-precompiled-binaries)
- [Installing from a Pre-compiled lib_lightgbm](#lib_lightgbm)
* [Examples](#examples)
* [Testing](#testing)
- [Running the Tests](#running-the-tests)
- [Code Coverage](#code-coverage)
* [Updating Documentation](#updating-documentation)
* [Preparing a CRAN Package](#preparing-a-cran-package)
* [Known Issues](#known-issues)
Installation
------------
For the easiest installation, go to ["Installing the CRAN package"](#installing-the-cran-package).
If you experience any issues with that, try ["Installing from Source with CMake"](#install). This can produce a more efficient version of the library on Windows systems with Visual Studio.
To build a GPU-enabled version of the package, follow the steps in ["Installing a GPU-enabled Build"](#installing-a-gpu-enabled-build).
If any of the above options do not work for you or do not meet your needs, please let the maintainers know by [opening an issue](https://github.com/lightgbm-org/LightGBM/issues).
When your package installation is done, you can check quickly if your LightGBM R-package is working by running the following:
```r
library(lightgbm)
data(agaricus.train, package='lightgbm')
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
model <- lgb.cv(
params = list(
objective = "regression"
, metric = "l2"
)
, data = dtrain
)
```
### Installing the CRAN package
`{lightgbm}` is [available on CRAN](https://cran.r-project.org/package=lightgbm), and can be installed with the following R code.
```r
install.packages("lightgbm", repos = "https://cran.r-project.org")
```
This is the easiest way to install `{lightgbm}`. It does not require `CMake` or `Visual Studio`, and should work well on many different operating systems and compilers.
Each CRAN package is also available on [LightGBM releases](https://github.com/lightgbm-org/LightGBM/releases), with a name like `lightgbm-{VERSION}-r-cran.tar.gz`.
#### Custom Installation (Linux, Mac)
The steps above should work on most systems, but users with highly-customized environments might want to change how R builds packages from source.
To change the compiler used when installing the CRAN package, you can create a file `~/.R/Makevars` which overrides `CC` (`C` compiler) and `CXX` (`C++` compiler).
For example, to use `gcc-14` instead of `clang` on macOS, you could use something like the following:
```make
# ~/.R/Makevars
CC=gcc-14
CC17=gcc-14
CXX=g++-14
CXX17=g++-14
```
To check the values R is using, run the following:
```shell
R CMD config --all
```
### Installing from Source with CMake <a id="install"></a>
You need to install git and [CMake](https://cmake.org/) first.
Note: this method is only supported on 64-bit systems. If you need to run LightGBM on 32-bit Windows (i386), follow the instructions in ["Installing the CRAN Package"](#installing-the-cran-package).
#### Windows Preparation
NOTE: Windows users may need to run with administrator rights (either R or the command prompt, depending on the way you are installing this package).
Installing a 64-bit version of [Rtools](https://cran.r-project.org/bin/windows/Rtools/) is mandatory.
After installing `Rtools` and `CMake`, be sure the following paths are added to the environment variable `PATH`. These may have been automatically added when installing other software.
* `Rtools`
- If you have `Rtools` 4.0, example:
- `C:\rtools40\mingw64\bin`
- `C:\rtools40\usr\bin`
- If you have `Rtools` 4.2+, example:
- `C:\rtools42\x86_64-w64-mingw32.static.posix\bin`
- `C:\rtools42\usr\bin`
- **NOTE**: this is e.g. `rtools43\` for R 4.3
* `CMake`
- example: `C:\Program Files\CMake\bin`
* `R`
- example: `C:\Program Files\R\R-4.5.1\bin`
NOTE: Two `Rtools` paths are required from `Rtools` 4.0 onwards because paths and the list of included software was changed in `Rtools` 4.0.
NOTE: `Rtools42` and later take a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html).
#### Windows Toolchain Options
A "toolchain" refers to the collection of software used to build the library. The R-package can be built with three different toolchains.
**Warning for Windows users**: it is recommended to use *Visual Studio* for its better multi-threading efficiency in Windows for many core systems. For very simple systems (dual core computers or worse), MinGW64 is recommended for maximum performance. If you do not know what to choose, it is recommended to use [Visual Studio](https://visualstudio.microsoft.com/downloads/), the default compiler. **Do not try using MinGW in Windows on many core systems. It may result in 10x slower results than Visual Studio.**
**Visual Studio (default)**
By default, the package will be built with [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/).
**MSYS2 (R 4.x)**
If you are using R 4.x and installation fails with Visual Studio, `LightGBM` will fall back to using [MSYS2](https://www.msys2.org/). This should work with the tools already bundled in `Rtools` 4.0.
If you want to force `LightGBM` to use MSYS2 (for any R version), pass `--use-msys2` to the installation script.
```shell
Rscript build_r.R --use-msys2
```
**MinGW**
If you want to force `LightGBM` to use [MinGW](https://www.mingw-w64.org/) (for any R version), pass `--use-mingw` to the installation script.
```shell
Rscript build_r.R --use-mingw
```
#### Mac OS Preparation
You can perform installation either with **Apple Clang** or **gcc**. In case you prefer **Apple Clang**, you should install **OpenMP** (details for installation can be found in [Installation Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#apple-clang)) first. In case you prefer **gcc**, you need to install it (details for installation can be found in [Installation Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#gcc)) and set some environment variables to tell R to use `gcc` and `g++`. If you install these from Homebrew, your versions of `g++` and `gcc` are most likely in `/usr/local/bin`, as shown below.
```
# replace 8 with version of gcc installed on your machine
export CXX=/usr/local/bin/g++-8 CC=/usr/local/bin/gcc-8
```
#### Install with CMake
After following the "preparation" steps above for your operating system, build and install the R-package with the following commands:
```sh
git clone --recursive https://github.com/lightgbm-org/LightGBM
cd LightGBM
Rscript build_r.R
```
The `build_r.R` script builds the package in a temporary directory called `lightgbm_r`. It will destroy and recreate that directory each time you run the script. That script supports the following command-line options:
- `--no-build-vignettes`: Skip building vignettes.
- `-j[jobs]`: Number of threads to use when compiling LightGBM. E.g., `-j4` will try to compile 4 objects at a time.
- by default, this script uses single-thread compilation
- for best results, set `-j` to the number of physical CPUs
- `--skip-install`: Build the package tarball, but do not install it.
- `--use-gpu`: Build a GPU-enabled version of the library.
- `--use-mingw`: Force the use of MinGW toolchain, regardless of R version.
- `--use-msys2`: Force the use of MSYS2 toolchain, regardless of R version.
Note: for the build with Visual Studio/VS Build Tools in Windows, you should use the Windows CMD or PowerShell.
### Installing a GPU-enabled Build
You will need to install Boost and OpenCL first: details for installation can be found in [Installation-Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#build-gpu-version).
After installing these other libraries, follow the steps in ["Installing from Source with CMake"](#install). When you reach the step that mentions `build_r.R`, pass the flag `--use-gpu`.
```shell
Rscript build_r.R --use-gpu
```
You may also need or want to provide additional configuration, depending on your setup. For example, you may need to provide locations for Boost and OpenCL.
```shell
Rscript build_r.R \
--use-gpu \
--opencl-library=/usr/lib/x86_64-linux-gnu/libOpenCL.so \
--boost-librarydir=/usr/lib/x86_64-linux-gnu
```
The following options correspond to the [CMake FindBoost options](https://cmake.org/cmake/help/latest/module/FindBoost.html) by the same names.
* `--boost-root`
* `--boost-dir`
* `--boost-include-dir`
* `--boost-librarydir`
The following options correspond to the [CMake FindOpenCL options](https://cmake.org/cmake/help/latest/module/FindOpenCL.html) by the same names.
* `--opencl-include-dir`
* `--opencl-library`
### Installing Precompiled Binaries
Precompiled binaries for Mac and Windows are prepared by CRAN a few days after each release to CRAN. They can be installed with the following R code.
```r
install.packages(
"lightgbm"
, type = "both"
, repos = "https://cran.r-project.org"
)
```
These packages do not require compilation, so they will be faster and easier to install than packages that are built from source.
CRAN does not prepare precompiled binaries for Linux, and as of this writing neither does this project.
### Installing from a Pre-compiled lib_lightgbm <a id="lib_lightgbm"></a>
Previous versions of LightGBM offered the ability to first compile the C++ library (`lib_lightgbm.{dll,dylib,so}`) and then build an R-package that wraps it.
As of version 3.0.0, this is no longer supported. If building from source is difficult for you, please [open an issue](https://github.com/lightgbm-org/LightGBM/issues).
Examples
--------
Please visit [demo](https://github.com/lightgbm-org/LightGBM/tree/main/R-package/demo):
* [Basic walkthrough of wrappers](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/basic_walkthrough.R)
* [Boosting from existing prediction](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/boost_from_prediction.R)
* [Early Stopping](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/early_stopping.R)
* [Cross Validation](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/cross_validation.R)
* [Multiclass Training/Prediction](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/multiclass.R)
* [Leaf (in)Stability](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/leaf_stability.R)
* [Weight-Parameter Adjustment Relationship](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/weight_param.R)
Testing
-------
The R-package's unit tests are run automatically on every commit, via integrations like [GitHub Actions](https://github.com/lightgbm-org/LightGBM/actions). Adding new tests in `R-package/tests/testthat` is a valuable way to improve the reliability of the R-package.
### Running the Tests
While developing the R-package, run the code below to run the unit tests.
```shell
sh build-cran-package.sh \
--no-build-vignettes
R CMD INSTALL --with-keep.source lightgbm*.tar.gz
cd R-package/tests
Rscript testthat.R
```
To run the tests with more verbose logs, set environment variable `LIGHTGBM_TEST_VERBOSITY` to a valid value for parameter [`verbosity`](https://lightgbm.readthedocs.io/en/latest/Parameters.html#verbosity).
```shell
export LIGHTGBM_TEST_VERBOSITY=1
cd R-package/tests
Rscript testthat.R
```
### Code Coverage
When adding tests, you may want to use test coverage to identify untested areas and to check if the tests you've added are covering all branches of the intended code.
The example below shows how to generate code coverage for the R-package on a macOS or Linux setup. To adjust for your environment, refer to [the customization step described above](#custom-installation-linux-mac).
```shell
# Install
sh build-cran-package.sh \
--no-build-vignettes
# Get coverage
Rscript -e " \
library(covr);
coverage <- covr::package_coverage('./lightgbm_r', type = 'tests', quiet = FALSE);
print(coverage);
covr::report(coverage, file = file.path(getwd(), 'coverage.html'), browse = TRUE);
"
```
Updating Documentation
----------------------
The R-package uses [`{roxygen2}`](https://CRAN.R-project.org/package=roxygen2) to generate its documentation.
The generated `DESCRIPTION`, `NAMESPACE`, and `man/` files are checked into source control.
To regenerate those files, run the following.
```shell
Rscript \
--vanilla \
-e "install.packages('roxygen2', repos = 'https://cran.rstudio.com')"
sh build-cran-package.sh --no-build-vignettes
R CMD INSTALL \
--with-keep.source \
./lightgbm_*.tar.gz
cd R-package
Rscript \
--vanilla \
-e "roxygen2::roxygenize(load = 'installed')"
```
Preparing a CRAN Package
------------------------
This section is primarily for maintainers, but may help users and contributors to understand the structure of the R-package.
Most of `LightGBM` uses `CMake` to handle tasks like setting compiler and linker flags, including header file locations, and linking to other libraries. Because CRAN packages typically do not assume the presence of `CMake`, the R-package uses an alternative method that is in the CRAN-supported toolchain for building R packages with C++ code: `Autoconf`.
For more information on this approach, see ["Writing R Extensions"](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Configure-and-cleanup).
### Build a CRAN Package
From the root of the repository, run the following.
```shell
git submodule update --init --recursive
sh build-cran-package.sh
```
This will create a file `lightgbm_${VERSION}.tar.gz`, where `VERSION` is the version of `LightGBM`.
That script supports the following command-line options:
- `--no-build-vignettes`: Skip building vignettes.
- `--r-executable=[path-to-executable]`: Use an alternative build of R.
### Standard Installation from CRAN Package
After building the package, install it with a command like the following:
```shell
R CMD install lightgbm_*.tar.gz
```
### Changing the CRAN Package
A lot of details are handled automatically by `R CMD build` and `R CMD install`, so it can be difficult to understand how the files in the R-package are related to each other. An extensive treatment of those details is available in ["Writing R Extensions"](https://cran.r-project.org/doc/manuals/r-release/R-exts.html).
This section briefly explains the key files for building a CRAN package. To update the package, edit the files relevant to your change and re-run the steps in [Build a CRAN Package](#build-a-cran-package).
**Linux or Mac**
At build time, `configure` will be run and used to create a file `Makevars`, using `Makevars.in` as a template.
1. Edit `configure.ac`.
2. Create `configure` with `autoconf`. Do not edit it by hand. This file must be generated on Ubuntu 22.04.
If you have an Ubuntu 22.04 environment available, run the provided script from the root of the `LightGBM` repository.
```shell
./R-package/recreate-configure.sh
```
If you do not have easy access to an Ubuntu 22.04 environment, the `configure` script can be generated using Docker by running the code below from the root of this repo.
```shell
docker run \
--rm \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
ubuntu:22.04 \
./R-package/recreate-configure.sh
```
The version of `autoconf` used by this project is stored in `R-package/AUTOCONF_UBUNTU_VERSION`. To update that version, update that file and run the commands above. To see available versions, see https://packages.ubuntu.com/search?keywords=autoconf.
3. Edit `src/Makevars.in`.
Alternatively, GitHub Actions can re-generate this file for you.
1. navigate to https://github.com/lightgbm-org/LightGBM/actions/workflows/r_configure.yml
2. click "Run workflow" (drop-down)
3. enter the branch from the pull request for the `pr-branch` input
4. click "Run workflow" (button)
**Configuring for Windows**
At build time, `configure.win` will be run and used to create a file `Makevars.win`, using `Makevars.win.in` as a template.
1. Edit `configure.win` directly.
2. Edit `src/Makevars.win.in`.
### Testing the CRAN Package
`{lightgbm}` is tested automatically on every commit, across many combinations of operating system, R version, and compiler. This section describes how to test the package locally while you are developing.
#### Windows, Mac, and Linux
```shell
sh build-cran-package.sh
R CMD check --as-cran lightgbm_*.tar.gz
```
#### <a id="UBSAN"></a>ASAN and UBSAN
All packages uploaded to CRAN must pass builds using `gcc` and `clang`, instrumented with two sanitizers: the Address Sanitizer (ASAN) and the Undefined Behavior Sanitizer (UBSAN).
For more background, see
* [this blog post](https://dirk.eddelbuettel.com/code/sanitizers.html)
* [top-level CRAN documentation on these checks](https://cran.r-project.org/web/checks/check_issue_kinds.html)
* [CRAN's configuration of these checks](https://www.stats.ox.ac.uk/pub/bdr/memtests/README.txt)
You can replicate these checks locally using Docker.
For more information on the image used for testing, see https://github.com/wch/r-debug.
In the code below, environment variable `R_CUSTOMIZATION` should be set to one of two values.
* `"san"` = replicates CRAN's `gcc-ASAN` and `gcc-UBSAN` checks
* `"csan"` = replicates CRAN's `clang-ASAN` and `clang-UBSAN` checks
```shell
docker run \
--rm \
-it \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
--env R_CUSTOMIZATION=san \
wch1/r-debug:latest \
/bin/bash
# install dependencies
RDscript${R_CUSTOMIZATION} \
-e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())"
# install lightgbm
sh build-cran-package.sh --r-executable=RD${R_CUSTOMIZATION}
RD${R_CUSTOMIZATION} \
CMD INSTALL lightgbm_*.tar.gz
# run tests
cd R-package/tests
rm -f ./tests.log
RDscript${R_CUSTOMIZATION} testthat.R >> tests.log 2>&1
# check that tests passed
echo "test exit code: $?"
tail -300 ./tests.log
```
#### Valgrind
All packages uploaded to CRAN must be built and tested without raising any issues from `valgrind`. `valgrind` is a profiler that can catch serious issues like memory leaks and illegal writes. For more information, see [this blog post](https://reside-ic.github.io/blog/debugging-and-fixing-crans-additional-checks-errors/).
You can replicate these checks locally using Docker. Note that instrumented versions of R built to use `valgrind` run much slower, and these tests may take as long as 20 minutes to run.
```shell
docker run \
--rm \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
-it \
wch1/r-debug
RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())"
sh build-cran-package.sh \
--r-executable=RDvalgrind
RDvalgrind CMD INSTALL \
--preclean \
--install-tests \
lightgbm_*.tar.gz
cd R-package/tests
RDvalgrind \
--no-readline \
--vanilla \
-d "valgrind --tool=memcheck --leak-check=full --track-origins=yes" \
-f testthat.R \
2>&1 \
| tee out.log \
| cat
```
These tests can also be triggered on a pull request branch, using GitHub Actions.
1. navigate to https://github.com/lightgbm-org/LightGBM/actions/workflows/r_valgrind.yml
2. click "Run workflow" (drop-down)
3. enter the branch from the pull request for the `pr-branch` input
4. enter the pull request ID for the `pr-number` input
5. click "Run workflow" (button)
Or by using the GitHub CLI, using a command similar to this:
```shell
gh workflow run \
--repo lightgbm-org/LightGBM \
r_valgrind.yml \
-f pr-branch=ci/fix-rerun-workflow \
-f pr-number=7072
```
Known Issues
------------
For information about known issues with the R-package, see the [R-package section of LightGBM's main FAQ page](https://lightgbm.readthedocs.io/en/latest/FAQ.html#r-package).
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
rm -f aclocal.m4
rm -rf ./autom4te.cache
rm -f config.log
rm -f config.status
rm -f src/Makevars
Vendored Executable
+3072
View File
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
### configure.ac -*- Autoconf -*-
# Template used by Autoconf to generate 'configure' script. For more see:
# * https://unconj.ca/blog/an-autoconf-primer-for-r-package-authors.html
# * https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Configure-and-cleanup
AC_PREREQ(2.69)
AC_INIT([lightgbm], [~~VERSION~~], [], [lightgbm], [])
###########################
# find compiler and flags #
###########################
AC_MSG_CHECKING([location of R])
AC_MSG_RESULT([${R_HOME}])
# set up CPP flags
# find the compiler and compiler flags used by R.
: ${R_HOME=`R HOME`}
if test -z "${R_HOME}"; then
echo "could not determine R_HOME"
exit 1
fi
CXX17=`"${R_HOME}/bin/R" CMD config CXX17`
CXX17STD=`"${R_HOME}/bin/R" CMD config CXX17STD`
CXX="${CXX17} ${CXX17STD}"
CPPFLAGS=`"${R_HOME}/bin/R" CMD config CPPFLAGS`
CXXFLAGS=`"${R_HOME}/bin/R" CMD config CXX17FLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
AC_LANG(C++)
# LightGBM-specific flags
LGB_CPPFLAGS=""
#########
# Eigen #
#########
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DEIGEN_MPL2_ONLY -DEIGEN_DONT_PARALLELIZE"
###############
# MM_PREFETCH #
###############
AC_MSG_CHECKING([whether MM_PREFETCH works])
ac_mmprefetch=no
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <xmmintrin.h>
]],
[[
int a = 0;
_mm_prefetch(&a, _MM_HINT_NTA);
return 0;
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mmprefetch=yes
AC_MSG_RESULT([${ac_mmprefetch}])
if test "${ac_mmprefetch}" = yes; then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_PREFETCH=1"
fi
############
# MM_ALLOC #
############
AC_MSG_CHECKING([whether MM_MALLOC works])
ac_mm_malloc=no
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <mm_malloc.h>
]],
[[
char *a = (char*)_mm_malloc(8, 16);
_mm_free(a);
return 0;
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_malloc=yes
AC_MSG_RESULT([${ac_mm_malloc}])
if test "${ac_mm_malloc}" = yes; then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_MALLOC=1"
fi
##########
# OpenMP #
##########
OPENMP_CXXFLAGS=""
if test `uname -s` = "Linux"
then
OPENMP_CXXFLAGS="\$(SHLIB_OPENMP_CXXFLAGS)"
fi
if test `uname -s` = "Darwin"
then
OPENMP_CXXFLAGS='-Xclang -fopenmp'
OPENMP_LIB='-lomp'
# libomp 15.0+ from brew is keg-only (i.e. not symlinked into the standard paths search by the linker),
# so need to search in other locations.
# See https://github.com/Homebrew/homebrew-core/issues/112107#issuecomment-1278042927.
#
# If Homebrew is found and libomp was installed with it, this code adds the necessary
# flags for the compiler to find libomp headers and for the linker to find libomp.dylib.
HOMEBREW_LIBOMP_PREFIX=""
if command -v brew >/dev/null 2>&1; then
ac_brew_openmp=no
AC_MSG_CHECKING([whether OpenMP was installed via Homebrew])
brew --prefix libomp >/dev/null 2>&1 && ac_brew_openmp=yes
AC_MSG_RESULT([${ac_brew_openmp}])
if test "${ac_brew_openmp}" = yes; then
HOMEBREW_LIBOMP_PREFIX=`brew --prefix libomp`
OPENMP_CXXFLAGS="${OPENMP_CXXFLAGS} -I${HOMEBREW_LIBOMP_PREFIX}/include"
OPENMP_LIB="${OPENMP_LIB} -L${HOMEBREW_LIBOMP_PREFIX}/lib"
fi
fi
ac_pkg_openmp=no
AC_MSG_CHECKING([whether OpenMP will work in a package])
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <omp.h>
]],
[[
return (omp_get_max_threads() <= 1);
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} ${LDFLAGS} ${OPENMP_CXXFLAGS} ${OPENMP_LIB} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_pkg_openmp=yes
# -Xclang is not portable (it is clang-specific)
# if compilation above failed, try without that flag
if test "${ac_pkg_openmp}" = no; then
if test -f "./conftest"; then
rm ./conftest
fi
OPENMP_CXXFLAGS="-fopenmp"
${CXX} ${CPPFLAGS} ${CXXFLAGS} ${LDFLAGS} ${OPENMP_CXXFLAGS} ${OPENMP_LIB} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_pkg_openmp=yes
fi
AC_MSG_RESULT([${ac_pkg_openmp}])
if test "${ac_pkg_openmp}" = no; then
OPENMP_CXXFLAGS=''
OPENMP_LIB=''
echo '***********************************************************************************************'
echo ' OpenMP is unavailable on this macOS system. LightGBM code will run single-threaded as a result.'
echo ' To use all CPU cores for training jobs, you should install OpenMP by running'
echo ''
echo ' brew install libomp'
echo '***********************************************************************************************'
fi
fi
# substitute variables from this script into Makevars.in
AC_SUBST(OPENMP_CXXFLAGS)
AC_SUBST(OPENMP_LIB)
AC_SUBST(LGB_CPPFLAGS)
AC_CONFIG_FILES([src/Makevars])
# write out Autoconf output
AC_OUTPUT
+102
View File
@@ -0,0 +1,102 @@
# Script used to generate `Makevars.win` from `Makevars.win.in`
# on Windows
###########################
# find compiler and flags #
###########################
R_EXE="${R_HOME}/bin${R_ARCH_BIN}/R"
CXX17=`"${R_EXE}" CMD config CXX17`
CXX17STD=`"${R_EXE}" CMD config CXX17STD`
CXX="${CXX17} ${CXX17STD}"
CXXFLAGS=`"${R_EXE}" CMD config CXX17FLAGS`
CPPFLAGS=`"${R_EXE}" CMD config CPPFLAGS`
# LightGBM-specific flags
LGB_CPPFLAGS=""
#########
# Eigen #
#########
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DEIGEN_MPL2_ONLY -DEIGEN_DONT_PARALLELIZE"
###############
# MM_PREFETCH #
###############
ac_mm_prefetch="no"
cat > conftest.cpp <<EOL
#include <xmmintrin.h>
int main() {
int a = 0;
_mm_prefetch(&a, _MM_HINT_NTA);
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_prefetch="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether MM_PREFETCH works...${ac_mm_prefetch}"
if test "${ac_mm_prefetch}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_PREFETCH=1"
fi
############
# MM_ALLOC #
############
ac_mm_malloc="no"
cat > conftest.cpp <<EOL
#include <mm_malloc.h>
int main() {
char *a = (char*)_mm_malloc(8, 16);
_mm_free(a);
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_malloc="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether MM_MALLOC works...${ac_mm_malloc}"
if test "${ac_mm_malloc}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_MALLOC=1"
fi
#############
# INET_PTON #
#############
ac_inet_pton="no"
cat > conftest.cpp <<EOL
#include <ws2tcpip.h>
int main() {
int (*fptr)(int, const char*, void*);
fptr = &inet_pton;
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_inet_pton="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether INET_PTON works...${ac_inet_pton}"
if test "${ac_inet_pton}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DWIN_HAS_INET_PTON=1"
fi
# Generate Makevars.win from Makevars.win.in
sed -e \
"s/@LGB_CPPFLAGS@/$LGB_CPPFLAGS/" \
< src/Makevars.win.in > src/Makevars.win
+962
View File
@@ -0,0 +1,962 @@
# CRAN Submission History
## v4.6.0 - Submission 1 - (February 13, 2025)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This release fixed several issues reported by CRAN.
Bashisms in `configure`
```text
possible bashism in configure.ac line 63 (should be VAR="${VAR}foo"):
LGB_CPPFLAGS+=" -DMM_PREFETCH=1"
possible bashism in configure.ac line 89 (should be VAR="${VAR}foo"):
LGB_CPPFLAGS+=" -DMM_MALLOC=1"
```
Compilation errors on GCC 15.
```text
io/json11.cpp:97:28: error: 'uint8_t' does not name a type
97 | } else if (static_cast<uint8_t>(ch) == 0xe2 &&
| ^~~~~~~
io/json11.cpp:97:28: note: 'uint8_t' is defined in header '<cstdint>'; this is probably fixable by adding '#include <cstdint>'
io/json11.cpp:98:28: error: 'uint8_t' does not name a type
98 | static_cast<uint8_t>(value[i + 1]) == 0x80 &&
```
This release contains fixes for those issues.
## v4.5.0 - Submission 1 - (July 25, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This release was a response to a request from CRAN.
On July 4, 2024, CRAN notified us that the following compiler warnings raised by `gcc` 14 needed to be fixed by August 3, 2024.
```text
Result: WARN
Found the following significant warnings:
io/dense_bin.hpp:617:27: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/multi_val_dense_bin.hpp:346:26: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/multi_val_sparse_bin.hpp:433:36: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/sparse_bin.hpp:785:19: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
See /data/gannet/ripley/R/packages/tests-devel/lightgbm.Rcheck/00install.out for details.
```
This release contains fixes for those issues.
## v4.4.0 - Submission 1 - (June 14, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This was a standard release of `{lightgbm}`, not intended to fix any particular R-specific issues.
## v4.3.0 - Submission 1 - (January 18, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This submission was put up in response to CRAN saying the package would be archived if the following
warning was not fixed within 14 days.
```text
/usr/local/clang-trunk/bin/../include/c++/v1/__fwd/string_view.h:22:41:
warning: 'char_traits<fmt::detail::char8_type>' is deprecated:
char_traits<T> for T not equal to char, wchar_t, char8_t, char16_t or char32_t is non-standard and is provided for a temporary period.
It will be removed in LLVM 19, so please migrate off of it. [-Wdeprecated-declarations]
```
See https://github.com/lightgbm-org/LightGBM/issues/6264.
## v4.2.0 - Submission 1 - (December 7, 2023)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This submission included many changes from the last 2 years, as well as fixes for a warning
CRAN said could cause the package to be archived: https://github.com/lightgbm-org/LightGBM/issues/6221.
## v4.1.0 - not submitted
v4.1.0 was not submitted to CRAN, because https://github.com/lightgbm-org/LightGBM/issues/5987 had not been resolved.
## v4.0.0 - Submission 2 - (July 19, 2023)
### CRAN response
> Dear maintainer,
> package lightgbm_4.0.0.tar.gz does not pass the incoming checks automatically.
The logs linked from those messages showed one issue remaining on Debian (0 on Windows).
```text
* checking examples ... [7s/4s] NOTE
Examples with CPU time > 2.5 times elapsed time
user system elapsed ratio
lgb.restore_handle 1.206 0.085 0.128 10.08
```
### Maintainer Notes
Chose to document the issue and need for a fix in https://github.com/lightgbm-org/LightGBM/issues/5987, but not resubmit,
to avoid annoying CRAN maintainers.
## v4.0.0 - Submission 1 - (July 16, 2023)
### CRAN response
> Dear maintainer,
> package lightgbm_4.0.0.tar.gz does not pass the incoming checks automatically.
The logs linked from those messages showed the following issues from `R CMD check`.
```text
* checking S3 generic/method consistency ... NOTE
Mismatches for apparent methods not registered:
merge:
function(x, y, ...)
merge.eval.string:
function(env)
format:
function(x, ...)
format.eval.string:
function(eval_res, eval_err)
See section 'Registering S3 methods' in the 'Writing R Extensions'
manual.
```
```text
* checking examples ... [8s/4s] NOTE
Examples with CPU time > 2.5 times elapsed time
user system elapsed ratio
lgb.restore_handle 1.819 0.128 0.165 11.8
```
### Maintainer Notes
Attempted to fix these with https://github.com/lightgbm-org/LightGBM/pull/5988 and resubmitted.
## v3.3.5 - Submission 2 - (January 16, 2023)
### CRAN response
> Reason was
>
> Flavor: r-devel-windows-x86_64
> Check: OOverall checktime, Result: NOTE
> Overall checktime 14 min > 10 min
>
> but the maintainer cannot do much to reduce this, so I triggered revdep checks now.
> Please reply to the archival message in case the issue is not fixable easily.
>
> Best,
> Uwe Ligges
### Maintainer Notes
This was technically not a "resubmission".
We asked CRAN why the first v3.3.5 submission had been archived, and they responded with the response above... and then v3.3.5 passed all checks with no further work from LightGBM maintainers.
## v3.3.5 - Submission 1 - (January 11, 2023)
### CRAN response
Archived without a response.
### Maintainer Notes
Submitted with the following comment.
> This submission contains {lightgbm} 3.3.5
> Per CRAN's policies, I am submitting it on behalf of the project's maintainer (Yu Shi), with his permission.
> This submission includes patches to address the following warnings observed on the fedora and debian CRAN checks.
> Found the following significant warnings:
> io/json11.cpp:207:47: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:216:51: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:225:53: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:268:60: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:272:36: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:276:37: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:381:41: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:150:39: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
Thank you very much for your time and consideration.
## v3.3.4 - Submission 1 - (December 15, 2022)
### CRAN response
Accepted to CRAN
### Maintainer Notes
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.4
> Per CRAN's policies, I am submitting it on behalf of the project's maintainer (Yu Shi), with his permission.
> This submission includes patches to address the following warnings observed on the fedora and debian CRAN checks.
>
> Compiled code should not call entry points which might terminate R nor write to stdout/stderr instead of to the console, nor use Fortran I/O nor system RNGs nor [v]sprintf.
> Thank you very much for your time and consideration.
## v3.3.3 - Submission 1 - (October 10, 2022)
### CRAN response
Accepted to CRAN
### Maintainer Notes
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.3.
> Per CRAN's policies, I am submitting on it on behalf of the project's maintainer (Yu Shi), with his permission (https://github.com/lightgbm-org/LightGBM/pull/5525).
> This submission includes two patches:
> * a change to testing to avoid a failed test related to non-ASCII strings on the `r-devel-linux-x86_64-debian-clang` check flavor (https://github.com/lightgbm-org/LightGBM/pull/5526)
> * modifications to allow compatibility with the RTools42 build toolchain (https://github.com/lightgbm-org/LightGBM/pull/5503)
> Thank you very much for your time and consideration.
## v3.3.2 - Submission 1 - (January 7, 2022)
### CRAN response
Accepted to CRAN on January 14, 2022.
### Maintainer Notes
In this submission, we uploaded a patch that CRAN stuff provided us via e-mail. The full text of the e-mail from CRAN:
```text
Dear maintainers,
This concerns the CRAN packages
Cairo cepreader gpboost httpuv ipaddress lightgbm proj4 prophet
RcppCWB RcppParallel RDieHarder re2 redux rgeolocate RGtk2 tth
udunits2 unrtf
maintained by one of you:
Andreas Blaette andreas.blaette@uni-due.de: RcppCWB
David Hall david.hall.physics@gmail.com: ipaddress
Dirk Eddelbuettel edd@debian.org: RDieHarder
Fabio Sigrist fabiosigrist@gmail.com: gpboost
Friedrich Leisch Friedrich.Leisch@R-project.org: tth
Girish Palya girishji@gmail.com: re2
James Hiebert hiebert@uvic.ca: udunits2
Jari Oksanen jhoksane@gmail.com: cepreader
Kevin Ushey kevin@rstudio.com: RcppParallel
ORPHANED: RGtk2
Os Keyes ironholds@gmail.com: rgeolocate
Rich FitzJohn rich.fitzjohn@gmail.com: redux
Sean Taylor sjtz@pm.me: prophet
Simon Urbanek simon.urbanek@r-project.org: proj4
Simon Urbanek Simon.Urbanek@r-project.org: Cairo
Winston Chang winston@rstudio.com: httpuv
Yu Shi yushi2@microsoft.com: lightgbm
your packages need to be updated for R-devel/R 4.2 to work on Windows,
following the recent switch to UCRT and Rtools42.
Sorry for the group message, please feel free to respond individually
regarding your package or ask specifically about what needs to be fixed.
I've created patches for you, so please review them and fix your packages:
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fsvn.r-project.org%2FR-dev-web%2Ftrunk%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fr_packages%2Fpatches%2FCRAN%2F&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=rFGf7Y4Dvo6g1kzV%2BeAJDLGm1TUtzQsLsavElTw6H1U%3D&amp;reserved=0
You can apply them as follows
tar xfz package_1.0.0.tar.gz
wget
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fsvn.r-project.org%2FR-dev-web%2Ftrunk%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fr_packages%2Fpatches%2FCRAN%2Fpackage.diff&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=iyTjhoqvzj3IbQ8HGCZeh1IQl34FAGpIdVyZWkzNvO0%3D&amp;reserved=0
patch --binary < package.diff
These patches are currently automatically applied by R-devel on Windows
at installation time, which makes most of your packages pass their
checks (as OK or NOTE), but please check your results carefully and
carefully review the patches. Usually these changes were because of
newer GCC or newer MinGW in the toolchain, but some for other reasons,
and some of them will definitely have to be improved so that the package
keeps building also for older versions of R using Rtools40. We have only
been testing the patches with UCRT (and Rtools42) on Windows.
For more information, please see
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FBlog%2Fpublic%2F2021%2F12%2F07%2Fupcoming-changes-in-r-4.2-on-windows%2F&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=SY77zgtbDbHvTxTgPLOoe%2Fw5OZDhXvJoxpVOoEaKoYo%3D&amp;reserved=0
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fhowto.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=dlVJ4nhQlmDPd56bHoVsWZuRfrUUorvOWxoUTmVDM%2Bg%3D&amp;reserved=0
Once you add your patches/fix the issues, your package will probably
show a warning during R CMD check (as patching would be attempted to be
applied again). That's ok, at that point please let me know and I will
remove my patch from the repository of automatically applied patches.
If you end up just applying the patch as is, there is probably no need
testing on your end, but you can do so using Winbuilder, r-hub, github
actions (e.g. https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fkalibera%2Fucrt3&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=msqoPzqDStlAUn%2Bb6gGevwFPD%2FaNL5dTxiNud2Sqzy8%3D&amp;reserved=0).
If you wanted to test locally on your Windows machine and do not have a
UCRT version of R-devel yet, please uninstall your old version of
R-devel, delete the old library used with that, install a new UCRT
version of R-devel , and install Rtools42. You can keep Rtools40
installed if you need it with R 4.1 or earlier.
Currently, the new R-devel can be downloaded from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.r-project.org%2Fnosvn%2Fwinutf8%2Fucrt3%2Fweb%2Frdevel.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=0hCwONzLmcW0GIXNqiOZQEIuhNA%2BjHhQvXsofs8J98o%3D&amp;reserved=0
And Rtools42 from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.r-project.org%2Fnosvn%2Fwinutf8%2Fucrt3%2Fweb%2Frtools.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=WLWLbOyQKbaYz8gkfKz2sqoGknjIOtl1aGAhUF%2Bpylg%3D&amp;reserved=0
If you end up testing locally, you can use R_INSTALL_TIME_PATCHES
environment variable to disable the automated patching, see the "howto"
document above. That way you could also see what the original issue was
causing.
If you wanted to find libraries to link for yourself, e.g. in a newer
version of your package, please look for "Using findLinkingOrder with
Rtools42 (tiff package example)" in the "howto" document above. I
created the patches for you manually before we finished this script, so
you may be able to create a shorter version using it, but - it's
probably not worth the effort.
If you wanted to try in a virtual machine, but did not have a license,
you can use also an automated setup of a free trial VM from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FBlog%2Fpublic%2F2021%2F03%2F18%2Fvirtual-windows-machine-for-checking-r-packages&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=aFFQYuC9CoBwBiLgZHi8N3yUnSiHu5Xtdqb2YBiMIHQ%3D&amp;reserved=0
(but that needs a very good and un-metered network connection to install)
Please let us know if you have any questions.
Thanks,
Tomas & Uwe
```
## v3.3.1 - Submission 1 - (October 27, 2021)
### CRAN response
Accepted to CRAN on October 30, 2021.
CRAN completed its checks and preparation of binaries on November 6, 2021.
### Maintainer Notes
Submitted v3.3.1 to CRAN, with the following fixes for the issues that caused CRAN to reject v3.3.0 and archive the package:
* https://github.com/lightgbm-org/LightGBM/pull/4673
* https://github.com/lightgbm-org/LightGBM/pull/4714
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.1.
> Per CRAN's policies, I am submitting on it on behalf of the project's maintainer (Yu Shi), with his permission (https://github.com/lightgbm-org/LightGBM/pull/4715#issuecomment-952537783).
> {lightgbm} was removed from CRAN on October 25, 2021 due to issues detected in the gcc-ASAN and clang-ASAN checks. To the best of our knowledge, we believe this release fixes those issues. We have introduced automated testing that we believe faithfully reproduces CRAN's tests with sanitizers (https://github.com/lightgbm-org/LightGBM/pull/4678).
> Thank you very much for your time and consideration.
Progress on the submission was tracked in https://github.com/lightgbm-org/LightGBM/issues/4713.
## v3.3.0 - Submission 1 - (October 8, 2021)
### CRAN response
`{lightgbm}` was removed from CRAN entirely on October 25, 2021.
On October 12, 2021, maintainers received the following message from CRAN (ripley@stats.ox.ac.uk):
> Dear maintainer,
> Please see the problems shown on https://cran.r-project.org/web/checks/check_results_lightgbm.html
> Please correct before 2021-10-25 to safely retain your package on CRAN.
> Do remember to look at the 'Additional issues'.
> The CRAN Team
We failed to produce a new submission prior to that date, so the package was removed entirely.
See https://github.com/lightgbm-org/LightGBM/issues/4713 for additional background and links explaining the specific failed CRAN checks.
### Maintainer Notes
In this submission, we attempted to switch the maintainer of the package (in the CRAN official sense) from Guolin Ke to Yu Shi.
Did this by adding a note in the CRAN submission web form explaining Guolin's departure from Microsoft.
## v3.2.1 - Submission 1 - (April 12, 2021)
### CRAN response
Accepted to CRAN.
### Maintainer Notes
## v3.2.0 - Submission 1 - (March 22, 2021)
### CRAN response
Package is failing checks in the `r-devel-linux-x86_64-debian-clang` environment (described [here](https://cran.r-project.org/web/checks/check_flavors.html#r-devel-linux-x86_64-debian-clang)). Specifically, one unit test on the use of non-ASCII feature names in `Booster$dump_model()` fails.
> Apparently your package fails its checks in a strict Latin-1* locale,
e.g. under Linux using LANG=en_US.iso88591 (see the debian-clang
results).
> Please correct before 2021-04-21 to safely retain your package on CRAN.
### Maintainer Notes
Submitted a version 3.2.1 to correct the errors noted.
## v3.1.1 - Submission 1 - (December 7, 2020)
### CRAN response
Accepted to CRAN, December 8.
### Maintainer Notes
Submitted a fix to 3.1.0 that skips some learning-to-rank tests on 32-bit Windows.
## v3.1.0 - Submission 1 - (November 15, 2020)
### CRAN response
Accepted to CRAN, November 18.
On November 21, found out that the CRAN's `r-oldrel-windows-ix86+x86_64` check was failing, with an issue similar to the one faced on Solaris and fixed in https://github.com/lightgbm-org/LightGBM/pull/3534.
CRAN did not ask for a re-submission, but this was fixed in 3.1.1.
### Maintainer Notes
This package was submitted with the following information in the "optional comments" box.
```text
Hello,
I'm submitting {lightgbm} 3.1.0 on behalf of the maintainer, Guolin Ke. I am a co-author on the package, and he has asked me to handle this submission. We saw in https://cran.r-project.org/web/packages/policies.html#Submission that this is permitted.
{lightgbm} was removed from CRAN in October for issues found by valgrind checks. We have invested significant effort in addressing those issues and creating an automatic test that tries to replicate CRAN's valgrind checks: https://github.com/lightgbm-org/LightGBM/blob/742d72f8bb051105484fd5cca11620493ffb0b2b/.github/workflows/r_valgrind.yml.
We see two warnings from valgrind that we believe are not problematic.
==2063== Conditional jump or move depends on uninitialised value(s)
==2063== at 0x49CF138: gregexpr_Regexc (grep.c:2439)
==2063== by 0x49D1F13: do_regexpr (grep.c:3100)
==2063== by 0x49A0058: bcEval (eval.c:7121)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498E414: R_execClosure (eval.c:1895)
==2063== by 0x498E0C7: Rf_applyClosure (eval.c:1821)
==2063== by 0x499FC8C: bcEval (eval.c:7089)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498B1CB: forcePromise (eval.c:555)
==2063== by 0x49963AB: FORCE_PROMISE (eval.c:5142)
==2063== by 0x4996566: getvar (eval.c:5183)
==2063== by 0x499D1A5: bcEval (eval.c:6873)
==2063== Uninitialised value was created by a stack allocation
==2063== at 0x49CEC37: gregexpr_Regexc (grep.c:2369)
This seems to be related to R itself and not any code in {lightgbm}.
==2063== 336 bytes in 1 blocks are possibly lost in loss record 153 of 2,709
==2063== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2063== by 0x40149CA: allocate_dtv (dl-tls.c:286)
==2063== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532)
==2063== by 0x5702322: allocate_stack (allocatestack.c:622)
==2063== by 0x5702322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660)
==2063== by 0x56D0DDA: ??? (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
==2063== by 0x56C88E0: GOMP_parallel (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
==2063== by 0x1544D29C: LGBM_DatasetCreateFromCSC (c_api.cpp:1286)
==2063== by 0x1546F980: LGBM_DatasetCreateFromCSC_R (lightgbm_R.cpp:91)
==2063== by 0x4941E2F: R_doDotCall (dotcode.c:634)
==2063== by 0x494CCC6: do_dotcall (dotcode.c:1281)
==2063== by 0x499FB01: bcEval (eval.c:7078)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498E414: R_execClosure (eval.c:1895)
We believe this is a false positive, and related to a misunderstanding between valgrind and openmp (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36298).
We have also added automated tests with ASAN/UBSAN to our testing setup, and have checked the package on Solaris 10 and found no issues.
Thanks for your time and consideration.
```
## v3.0.0.2 - Submission 1 - (September 29, 2020)
### CRAN response
First response was a message talking about failing checks on 3.0.0.
```text
package lightgbm_3.0.0.2.tar.gz has been auto-processed.
The auto-check found additional issues for the last version released on CRAN:
gcc-UBSAN <link>
valgrind <link>
CRAN incoming checks do not test for these additional issues and you will need an appropriately instrumented build of R to reproduce these.
Hence please reply-all and explain: Have these been fixed?
Please correct before 2020-10-05 to safely retain your package on CRAN.
There is still a valgrind error. This did not happen when tested on
submission, but the tests did run until timeout at 4 hours. When you
write illegally, corruption is common.
Illegal writes are serious errors.
```
Then in later responses to email correspondence with CRAN, CRAN expressed frustration with the number of failed submission and banned this package from new submissions for a month.
The content of that frustrated message was regrettable and it does not need to be preserved forever in this file.
### Maintainer Notes
The 3.0.0.x series is officially not making it to CRAN. We will wait until November, and try again.
Detailed plan about what will be tried before November 2020 to increase the likelihood of success for that package: https://github.com/lightgbm-org/LightGBM/pull/3338#issuecomment-702756840.
## v3.0.0.1 - Submission 1 - (September 24, 2020)
### CRAN response
```text
Thanks, we see:
Still lots of alignment errors, such as
lightgbm.Rcheck/tests/testthat.Rout:io/dataset_loader.cpp:340:59:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const value_type', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/stl_vector.h:1198:21:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const int', which requires 4 byte alignment lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/vector.tcc:449:28:runtime
error: reference binding to misaligned address 0x7f51fefad81e for type 'const type', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/move.h:77:36:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const int', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/alloc_traits.h:512:17:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const type', which requires 4 byte alignment
Please fix and resubmit.
```
### Maintainer Notes
Ok, these are the notes from the UBSAN tests. Was able to reproduce them with https://github.com/lightgbm-org/LightGBM/pull/3338#issuecomment-700399862, and they were fixed in https://github.com/lightgbm-org/LightGBM/pull/3415.
Struggling to replicate the valgrind result (running `R CMD check --use-valgrind` returns no issues), so trying submission again. Hoping that the fixes for mis-alignment fix the other errors too.
## v3.0.0 - Submission 6 - (September 24, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
```text
* checking CRAN incoming feasibility ... WARNING
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
Insufficient package version (submitted: 3.0.0, existing: 3.0.0)
Days since last update: 4
```
### Maintainer Notes
Did not think the version needed to be incremented if submitting a package in response to CRAN saying "you are failing checks and will be kicked off if you don't fix it", but I guess you do!
This can be fixed by just re-submitting but with the version changed from `3.0.0` to `3.0.0.1`.
## v3.0.0 - Submission 5 - (September 11, 2020)
### CRAN Response
Accepted to CRAN!
Please correct the problems below before 2020-10-05 to safely retain your package on CRAN:
```text
checking installed package size ... NOTE
installed size is 49.7Mb
sub-directories of 1Mb or more:
libs 49.1Mb
"network/socket_wrapper.hpp", line 30: Error: Could not open include file<ifaddrs.h>.
"network/socket_wrapper.hpp", line 216: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 217: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 220: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 222: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 214: Error: The function "getifaddrs" must have a prototype.
"network/socket_wrapper.hpp", line 228: Error: The function "freeifaddrs" must have a prototype.
"network/linkers_socket.cpp", line 76: Warning: A non-POD object of type "std::chrono::duration<double, std::ratio<1, 1000>>" passed as a variable argument to function "static LightGBM::Log::Info(const char*, ...)".
7 Error(s) and 1 Warning(s) detected.
*** Error code 2
make: Fatal error: Command failed for target `network/linkers_socket.o'
Current working directory /tmp/RtmpNfaavG/R.INSTALL40a84f70130a/lightgbm/src
ERROR: compilation failed for package lightgbm
* removing /home/ripley/R/Lib32/lightgbm
```
### Maintainer Notes
Added a patch that `psutil` has used to fix missing `ifaddrs.h` on Solaris 10: https://github.com/lightgbm-org/LightGBM/issues/629#issuecomment-665091451.
## v3.0.0 - Submission 4 - (September 4, 2020)
### CRAN Response
> Thanks, if the running time is the only reason to wrap the examples in
\donttest, please replace \donttest by \donttest (\donttest examples are
not executed in the CRAN checks).
> Please replace cat() by message() or warning() in your functions (except
for print() and summary() functions). Messages and warnings can be
suppressed if needed.
> Missing Rd-tags:
lightgbm/man/dimnames.lgb.Dataset.Rd: \value
lightgbm/man/lgb.Dataset.construct.Rd: \value
lightgbm/man/lgb.prepare.Rd: \value
...
> Please add the tag and explain in detail the returned objects.
### Maintainer Notes
Responded to CRAN with the following:
All examples have been wrapped with `\donttest` as requested. We have replied to Swetlana Herbrandt asking for clarification on the donttest news item in the R 4.0.2 changelog (https://cran.r-project.org/doc/manuals/r-devel/NEWS.html).
All uses of `cat()` have been replaced with `print()`. We chose `print()` over `message()` because it's important that they be written to stdout alongside all the other logs coming from the library's C++ code. `message()` and `warning()` write to stderr.
All exported objects now have `\value{}` statements in their documentation files in `man/`.
**We also replied directly to CRAN's feedback email**
> Swetlana,
> Thank you for your comments. I've just created a new submission that I believe addresses them.
> Can you help us understand something? In your message you said "\donttest examples are
not executed in the CRAN checks)", but in https://cran.r-project.org/doc/manuals/r-devel/NEWS.html we see the following:
> > "`R CMD check --as-cran` now runs \donttest examples (which are run by example()) instead of instructing the tester to do so. This can be temporarily circumvented during development by setting environment variable `_R_CHECK_DONTTEST_EXAMPLES_` to a false value."
> Could you help us understand how both of those statements can be true?
## v3.0.0 - Submission 3 - (August 29, 2020)
### CRAN response
* Please write references in the description of the DESCRIPTION file in
the form
- authors (year) doi:...
- authors (year) arXiv:...
- authors (year, ISBN:...)
* if those are not available: authors (year) https:... with no space after 'doi:', 'arXiv:', 'https:' and angle brackets for auto-linking.
* (If you want to add a title as well please put it in quotes: "Title")
* \donttest{} should only be used if the example really cannot be executed (e.g. because of missing additional software, missing API keys, ...) by the user. That's why wrapping examples in \donttest{} adds the comment ("# Not run:") as a warning for the user. Does not seem necessary. Please unwrap the examples if they are executable in < 5 sec, or replace
\donttest{} with \donttest{}.
* Please do not modify the global environment (e.g. by using <<-) in your
functions. This is not allowed by the CRAN policies.
* Please always add all authors, contributors and copyright holders in the Authors@R field with the appropriate roles. From CRAN policies you agreed to: "The ownership of copyright and intellectual property rights of all components of the package must be clear and unambiguous (including from the authors specification in the DESCRIPTION file). Where code is copied (or derived) from the work of others (including from R itself), care must be taken that any copyright/license statements are preserved and authorship is not misrepresented." e.g.: Microsoft Corporation, Dropbox Inc. Please explain in the submission comments what you did about this issue.
Please fix and resubmit
### Maintainer Notes
Responded to CRAN with the following:
The paper citation has been adjusted as requested. We were using 'glmnet' as a guide on how to include the URL but maybe they are no longer in compliance with CRAN policies: https://github.com/cran/glmnet/blob/b1a4b50de01e0cd24343959d7cf86452bac17b26/DESCRIPTION
All authors from the original LightGBM paper have been added to Authors@R as `"aut"`. We have also added Microsoft and DropBox, Inc. as `"cph"` (copyright holders). These roles were chosen based on the guidance in https://journal.r-project.org/archive/2012/RJ-2012-009/index.html.
lightgbm's code does use `<<-`, but it does not modify the global environment. The uses of `<<-` in R/lgb.interprete.R and R/callback.R are in functions which are called in an environment created by the lightgbm functions that call them, and this operator is used to reach one level up into the calling function's environment.
We chose to wrap our examples in `\donttest{}` because we found, through testing on https://r-hub.github.io/rhub/ and in our own continuous integration environments, that their run time varies a lot between platforms, and we cannot guarantee that all examples will run in under 5 seconds. We intentionally chose `\donttest{}` over `\donttest{}` because this item in the R 4.0.0 changelog (https://cran.r-project.org/doc/manuals/r-devel/NEWS.html) seems to indicate that \donttest will be ignored by CRAN's automated checks:
> "`R CMD check --as-cran` now runs \donttest examples (which are run by example()) instead of instructing the tester to do so. This can be temporarily circumvented during development by setting environment variable `_R_CHECK_DONTTEST_EXAMPLES_` to a false value."
We run all examples with `R CMD check --as-cran --run-dontrun` in our continuous integration tests on every commit to the package, so we have high confidence that they are working correctly.
## v3.0.0 - Submission 2 - (August 28, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
Possibly mis-spelled words in DESCRIPTION:
Guolin (13:52)
Ke (13:48)
LightGBM (14:20)
al (13:62)
et (13:59)
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'docs' 'lightgbm-hex-logo.png' 'lightgbm-hex-logo.svg'
```
* Windows: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
Possibly mis-spelled words in DESCRIPTION:
Guolin (13:52)
Ke (13:48)
LightGBM (14:20)
al (13:62)
et (13:59)
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'docs' 'lightgbm-hex-logo.png' 'lightgbm-hex-logo.svg'
```
### Maintainer Notes
We should tell them the misspellings note is a false positive.
For the note about included files, that is my fault. I had extra files laying around when I generated the package. I'm surprised to see `docs/` in that list, since it is ignored in `.Rbuildignore`. I even tested that with [the exact code Rbuildignore uses](https://github.com/wch/r-source/blob/9d13622f41cfa0f36db2595bd6a5bf93e2010e21/src/library/tools/R/build.R#L85). For now, I added `rm -r docs/` to `build-cran-package.sh`. We can figure out what is happening with `.Rbuildignore` in the future, but it shouldn't block a release.
## v3.0.0 - Submission 1 - (August 24, 2020)
NOTE: 3.0.0-1 was never released to CRAN. CRAN was on vacation August 14-24, 2020, and in that time version 3.0.0-1 (a release candidate) became 3.0.0.
### CRAN response
> Please only ship the CRAN template for the MIT license.
> Is there some reference about the method you can add in the Description field in the form Authors (year) doi:.....?
> Please fix and resubmit.
### `R CMD check` results
* Debian: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
* Windows: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
### Maintainer Notes
Tried updating `LICENSE` file to this template:
```yaml
YEAR: 2016
COPYRIGHT HOLDER: Microsoft Corporation
```
Added a citation and link for [the main paper](https://proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html) in `DESCRIPTION`.
## v3.0.0-1 - Submission 3 - (August 12, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
* Windows: 1 ERROR, 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
** running tests for arch 'i386' ... [9s] ERROR
Running 'testthat.R' [8s]
Running the tests in 'tests/testthat.R' failed.
Complete output:
> library(testthat)
> library(lightgbm)
Loading required package: R6
>
> test_check(
+ package = "lightgbm"
+ , stop_on_failure = TRUE
+ , stop_on_warning = FALSE
+ )
-- 1. Error: predictions do not fail for integer input (@test_Predictor.R#7) --
lgb.Dataset.construct: cannot create Dataset handle
Backtrace:
1. lightgbm::lgb.train(...)
2. data$construct()
```
### Maintainer Notes
The "checking CRAN incoming feasibility" NOTE can be safely ignored. It only shows up the first time you submit a package to CRAN.
So the only thing I see broken right now is the test error on 32-bit Windows. This is documented in https://github.com/lightgbm-org/LightGBM/issues/3187.
## v3.0.0-1 - Submission 2 - (August 10, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
Non-standard files/directories found at top level:
cran-comments.md docs
```
* Windows: 1 ERROR, 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'cran-comments.md' 'docs'
** checking whether the package can be loaded ... ERROR
Loading this package had a fatal error status code 1
Loading log:
Error: package 'lightgbm' is not installed for 'arch = i386'
Execution halted
```
### Maintainer Notes
Seems removing `Biarch` field didn't work. Noticed this in the install logs:
> Warning: this package has a non-empty 'configure.win' file, so building only the main architecture
Tried adding `Biarch: true` to `DESCRIPTION` to overcome this.
NOTE about non-standard files was the result of a mistake in `.Rbuildignore` syntax, and something strange with how `cran-comments.md` line in `.Rbuildignore` was treated. Updated `.Rbuildignore` and added an `rm cran-comments.md` to `build-cran-package.sh`.
## v3.0.0-1 - Submission 1 - (August 9, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 1 NOTE
```text
Possibly mis-spelled words in DESCRIPTION:
LightGBM (12:88, 19:41, 20:60, 20:264)
```
* Windows: 1 ERROR, 1 NOTE
```text
Possibly mis-spelled words in DESCRIPTION:
LightGBM (12:88, 19:41, 20:60, 20:264)
** checking whether the package can be loaded ... ERROR
Loading this package had a fatal error status code 1
Loading log:
Error: package 'lightgbm' is not installed for 'arch = i386'
Execution halted
```
### Maintainer Notes
Thought the issue on Windows was caused by `Biarch: false` in `DESCRIPTION`. Removed `Biarch` field.
Thought the "misspellings" issue could be resolved by adding single quotes around LightGBM, like `'LightGBM'`.
+10
View File
@@ -0,0 +1,10 @@
basic_walkthrough Basic feature walkthrough
boost_from_prediction Boosting from existing prediction
categorical_features_rules Categorical Feature Preparation with Rules
cross_validation Cross Validation
early_stopping Early Stop in training
efficient_many_training Efficiency for Many Model Trainings
multiclass Multiclass training/prediction
multiclass_custom_objective Multiclass with Custom Objective Function
leaf_stability Leaf (in)Stability example
weight_param Weight-Parameter adjustment relationship
+153
View File
@@ -0,0 +1,153 @@
library(lightgbm)
# We load in the agaricus dataset
# In this example, we are aiming to predict whether a mushroom is edible
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
train <- agaricus.train
test <- agaricus.test
# The loaded data is stored in sparseMatrix, and label is a numeric vector in {0,1}
class(train$label)
class(train$data)
# Set parameters for model training
train_params <- list(
num_leaves = 4L
, learning_rate = 1.0
, objective = "binary"
, nthread = 2L
)
#--------------------Basic Training using lightgbm----------------
# This is the basic usage of lightgbm you can put matrix in data field
# Note: we are putting in sparse matrix here, lightgbm naturally handles sparse input
# Use sparse matrix when your feature is sparse (e.g. when you are using one-hot encoding vector)
print("Training lightgbm with sparseMatrix")
bst <- lightgbm(
data = train$data
, params = train_params
, label = train$label
, nrounds = 2L
)
# Alternatively, you can put in dense matrix, i.e. basic R-matrix
print("Training lightgbm with Matrix")
bst <- lightgbm(
data = as.matrix(train$data)
, params = train_params
, label = train$label
, nrounds = 2L
)
# You can also put in lgb.Dataset object, which stores label, data and other meta datas needed for advanced features
print("Training lightgbm with lgb.Dataset")
dtrain <- lgb.Dataset(
data = train$data
, label = train$label
)
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
)
# Verbose = 0,1,2
print("Train lightgbm with verbose 0, no message")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 0L
)
print("Train lightgbm with verbose 1, print evaluation metric")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 1L
)
print("Train lightgbm with verbose 2, also print information about tree")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 2L
)
# You can also specify data as file path to a LibSVM/TCV/CSV format input
# Since we do not have this file with us, the following line is just for illustration
# bst <- lightgbm(
# data = "agaricus.train.svm"
# , num_leaves = 4L
# , learning_rate = 1.0
# , nrounds = 2L
# , objective = "binary"
# )
#--------------------Basic prediction using lightgbm--------------
# You can do prediction using the following line
# You can put in Matrix, sparseMatrix, or lgb.Dataset
pred <- predict(bst, test$data)
err <- mean(as.numeric(pred > 0.5) != test$label)
print(paste("test-error=", err))
#--------------------Save and load models-------------------------
# Save model to binary local file
lgb.save(bst, "lightgbm.model")
# Load binary model to R
bst2 <- lgb.load("lightgbm.model")
pred2 <- predict(bst2, test$data)
# pred2 should be identical to pred
print(paste("sum(abs(pred2-pred))=", sum(abs(pred2 - pred))))
#--------------------Advanced features ---------------------------
# To use advanced features, we need to put data in lgb.Dataset
dtrain <- lgb.Dataset(data = train$data, label = train$label, free_raw_data = FALSE)
dtest <- lgb.Dataset.create.valid(dtrain, data = test$data, label = test$label)
#--------------------Using validation set-------------------------
# valids is a list of lgb.Dataset, each of them is tagged with name
valids <- list(train = dtrain, test = dtest)
# To train with valids, use lgb.train, which contains more advanced features
# valids allows us to monitor the evaluation result on all data in the list
print("Train lightgbm using lgb.train with valids")
bst <- lgb.train(
data = dtrain
, params = train_params
, nrounds = 2L
, valids = valids
)
# We can change evaluation metrics, or use multiple evaluation metrics
print("Train lightgbm using lgb.train with valids, watch logloss and error")
bst <- lgb.train(
data = dtrain
, params = train_params
, nrounds = 2L
, valids = valids
, eval = c("binary_error", "binary_logloss")
)
# lgb.Dataset can also be saved using lgb.Dataset.save
lgb.Dataset.save(dtrain, "dtrain.buffer")
# To load it in, simply call lgb.Dataset
dtrain2 <- lgb.Dataset("dtrain.buffer")
bst <- lgb.train(
data = dtrain2
, params = train_params
, nrounds = 2L
, valids = valids
)
# information can be extracted from lgb.Dataset using get_field()
label <- get_field(dtest, "label")
pred <- predict(bst, test$data)
err <- as.numeric(sum(as.integer(pred > 0.5) != label)) / length(label)
print(paste("test-error=", err))
+38
View File
@@ -0,0 +1,38 @@
library(lightgbm)
# Load in the agaricus dataset
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
dtrain <- lgb.Dataset(agaricus.train$data, label = agaricus.train$label)
dtest <- lgb.Dataset.create.valid(dtrain, data = agaricus.test$data, label = agaricus.test$label)
valids <- list(eval = dtest, train = dtrain)
#--------------------Advanced features ---------------------------
# advanced: start from an initial base prediction
print("Start running example to start from an initial prediction")
# Train lightgbm for 1 round
param <- list(
num_leaves = 4L
, learning_rate = 1.0
, nthread = 2L
, objective = "binary"
)
bst <- lgb.train(param, dtrain, 1L, valids = valids)
# Note: we need the margin value instead of transformed prediction in set_init_score
ptrain <- predict(bst, agaricus.train$data, type = "raw")
ptest <- predict(bst, agaricus.test$data, type = "raw")
# set the init_score property of dtrain and dtest
# base margin is the base prediction we will boost from
set_field(dtrain, "init_score", ptrain)
set_field(dtest, "init_score", ptest)
print("This is result of boost from initial prediction")
bst <- lgb.train(
params = param
, data = dtrain
, nrounds = 5L
, valids = valids
)

Some files were not shown because too many files have changed in this diff Show More