From 851d46f0161a3b382c0deb511355bbc56d0175a5 Mon Sep 17 00:00:00 2001 From: kevin1kevin1k Date: Wed, 10 Feb 2021 18:03:03 +0800 Subject: [PATCH] Enable installation with PyPI Co-authored-by: Yu-Sheng Li Co-authored-by: Jui-Nan Yen --- python/MANIFEST.in | 2 + python/README | 20 +++-- python/liblinear/__init__.py | 0 python/{ => liblinear}/commonutil.py | 0 python/{ => liblinear}/liblinear.py | 28 +++--- python/{ => liblinear}/liblinearutil.py | 10 +-- python/setup.py | 113 ++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 python/MANIFEST.in create mode 100644 python/liblinear/__init__.py rename python/{ => liblinear}/commonutil.py (100%) rename python/{ => liblinear}/liblinear.py (95%) rename python/{ => liblinear}/liblinearutil.py (98%) create mode 100644 python/setup.py diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 0000000..0746184 --- /dev/null +++ b/python/MANIFEST.in @@ -0,0 +1,2 @@ +include cpp-source/* +include cpp-source/*/* diff --git a/python/README b/python/README index 6449974..349d8a7 100644 --- a/python/README +++ b/python/README @@ -26,6 +26,12 @@ interface is developed with the built-in Python library "ctypes." Installation ============ +To install via pip or conda, run + +> pip install -U liblinear + +Alternatively, you may also directly use the Python interface without installing this package; see the following. + On Unix systems, type > make @@ -49,7 +55,7 @@ functions in liblinearutil.py and commonutil.py (shared with LIBSVM and imported by svmutil.py). The usage is the same as the LIBLINEAR MATLAB interface. ->>> from liblinearutil import * +>>> from liblinear.liblinearutil import * # Read data in LIBSVM format >>> y, x = svm_read_problem('../heart_scale') >>> m = train(y[:200], x[:200], '-c 4') @@ -77,7 +83,7 @@ The low-level use directly calls C interfaces imported by liblinear.py. Note tha all arguments and return values are in ctypes format. You need to handle them carefully. ->>> from liblinear import * +>>> from liblinear.liblinear import * >>> prob = problem([1,-1], [{1:1, 3:1}, {1:-1,3:-1}]) >>> param = parameter('-c 4') >>> m = liblinear.train(prob, param) # m is a ctype pointer to a model @@ -95,7 +101,7 @@ There are two levels of usage. The high-level one uses utility functions in liblinearutil.py and the usage is the same as the LIBLINEAR MATLAB interface. >>> import scipy ->>> from liblinearutil import * +>>> from liblinear.liblinearutil import * # Read data in LIBSVM format >>> y, x = svm_read_problem('../heart_scale', return_scipy = True) # y: ndarray, x: csr_matrix >>> m = train(y[:200], x[:200, :], '-c 4') @@ -128,7 +134,7 @@ The low-level use directly calls C interfaces imported by liblinear.py. Note tha all arguments and return values are in ctypes format. You need to handle them carefully. ->>> from liblinear import * +>>> from liblinear.liblinear import * >>> prob = problem(scipy.asarray([1,-1]), scipy.sparse.csr_matrix(([1, 1, -1, -1], ([0, 0, 1, 1], [0, 2, 0, 2])))) >>> param = parameter('-c 4') >>> m = liblinear.train(prob, param) # m is a ctype pointer to a model @@ -163,7 +169,7 @@ fields and methods. Before using the data structures, execute the following command to load the LIBLINEAR shared library: - >>> from liblinear import * + >>> from liblinear.liblinear import * - class feature_node: @@ -304,7 +310,7 @@ Utility Functions To use utility functions, type - >>> from liblinearutil import * + >>> from liblinear.liblinearutil import * The above command loads train() : train a linear model @@ -458,7 +464,7 @@ The above command loads Additional Information ====================== -This interface was written by Hsiang-Fu Yu from Department of Computer +This interface was originally written by Hsiang-Fu Yu from Department of Computer Science, National Taiwan University. If you find this tool useful, please cite LIBLINEAR as follows diff --git a/python/liblinear/__init__.py b/python/liblinear/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/commonutil.py b/python/liblinear/commonutil.py similarity index 100% rename from python/commonutil.py rename to python/liblinear/commonutil.py diff --git a/python/liblinear.py b/python/liblinear/liblinear.py similarity index 95% rename from python/liblinear.py rename to python/liblinear/liblinear.py index 64e21bb..c223129 100644 --- a/python/liblinear.py +++ b/python/liblinear/liblinear.py @@ -3,6 +3,7 @@ from ctypes import * from ctypes.util import find_library from os import path +from glob import glob import sys try: @@ -25,18 +26,23 @@ __all__ = ['liblinear', 'feature_node', 'gen_feature_nodearray', 'problem', try: dirname = path.dirname(path.abspath(__file__)) - if sys.platform == 'win32': - liblinear = CDLL(path.join(dirname, r'..\windows\liblinear.dll')) - else: - liblinear = CDLL(path.join(dirname, '../liblinear.so.4')) + dynamic_lib_name = 'clib.cp*' + path_to_so = glob(path.join(dirname, dynamic_lib_name))[0] + liblinear = CDLL(path_to_so) except: -# For unix the prefix 'lib' is not considered. - if find_library('linear'): - liblinear = CDLL(find_library('linear')) - elif find_library('liblinear'): - liblinear = CDLL(find_library('liblinear')) - else: - raise Exception('LIBLINEAR library not found.') + try : + if sys.platform == 'win32': + liblinear = CDLL(path.join(dirname, r'..\..\windows\liblinear.dll')) + else: + liblinear = CDLL(path.join(dirname, '../../liblinear.so.4')) + except: + # For unix the prefix 'lib' is not considered. + if find_library('linear'): + liblinear = CDLL(find_library('linear')) + elif find_library('liblinear'): + liblinear = CDLL(find_library('liblinear')) + else: + raise Exception('LIBLINEAR library not found.') L2R_LR = 0 L2R_L2LOSS_SVC_DUAL = 1 diff --git a/python/liblinearutil.py b/python/liblinear/liblinearutil.py similarity index 98% rename from python/liblinearutil.py rename to python/liblinear/liblinearutil.py index ce24bcf..073ca41 100644 --- a/python/liblinearutil.py +++ b/python/liblinear/liblinearutil.py @@ -2,11 +2,11 @@ import os, sys sys.path = [os.path.dirname(os.path.abspath(__file__))] + sys.path -from liblinear import * -from liblinear import __all__ as liblinear_all -from liblinear import scipy, sparse -from commonutil import * -from commonutil import __all__ as common_all +from .liblinear import * +from .liblinear import __all__ as liblinear_all +from .liblinear import scipy, sparse +from .commonutil import * +from .commonutil import __all__ as common_all from ctypes import c_double if sys.version_info[0] < 3: diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..7483925 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python + +import sys, os +from os import path +from shutil import copyfile, rmtree +from glob import glob + +from setuptools import setup, Extension +from distutils.command.clean import clean as clean_cmd + +# a technique to build a shared library on windows +from distutils.command.build_ext import build_ext + +build_ext.get_export_symbols = lambda x, y: [] + + +PACKAGE_NAME = "liblinear-official" +VERSION = "2.42.0" +cpp_dir = "cpp-source" +# should be consistent with dynamic_lib_name in liblinear/liblinear.py +dynamic_lib_name = "clib" + +# sources to be included to build the shared library +source_codes = [ + path.join("blas", "daxpy.c"), + path.join("blas", "ddot.c"), + path.join("blas", "dnrm2.c"), + path.join("blas", "dscal.c"), + "linear.cpp", + "newton.cpp", +] +headers = [ + path.join("blas", "blas.h"), + path.join("blas", "blasp.h"), + "newton.h", + "linear.h", + "linear.def", +] + +kwargs_for_extension = { + "sources": [path.join(cpp_dir, f) for f in source_codes], + "depends": [path.join(cpp_dir, f) for f in headers], + "include_dirs": [cpp_dir], + "language": "c++", +} + +# see ../Makefile.win +if sys.platform == "win32": + kwargs_for_extension.update( + { + "define_macros": [("_WIN64", ""), ("_CRT_SECURE_NO_DEPRECATE", "")], + "extra_link_args": ["-DEF:{}\linear.def".format(cpp_dir)], + } + ) + + +def create_cpp_source(): + for f in source_codes + headers: + src_file = path.join("..", f) + tgt_file = path.join(cpp_dir, f) + # ensure blas directory is created + os.makedirs(path.dirname(tgt_file), exist_ok=True) + copyfile(src_file, tgt_file) + + +class CleanCommand(clean_cmd): + def run(self): + clean_cmd.run(self) + to_be_removed = ["build/", "dist/", "MANIFEST", cpp_dir, "{}.egg-info".format(PACKAGE_NAME)] + to_be_removed += glob("./{}/{}.*".format(PACKAGE_NAME, dynamic_lib_name)) + for root, dirs, files in os.walk(os.curdir, topdown=False): + if "__pycache__" in dirs: + to_be_removed.append(path.join(root, "__pycache__")) + to_be_removed += [f for f in files if f.endswith(".pyc")] + + for f in to_be_removed: + print("remove {}".format(f)) + if f == ".": + continue + elif path.isfile(f): + os.remove(f) + elif path.isdir(f): + rmtree(f) + +def main(): + if not path.exists(cpp_dir): + create_cpp_source() + + with open("README") as f: + long_description = f.read() + + setup( + name=PACKAGE_NAME, + packages=[PACKAGE_NAME], + version=VERSION, + description="Python binding of LIBLINEAR", + long_description=long_description, + long_description_content_type="text/plain", + author="ML group @ National Taiwan University", + author_email="cjlin@csie.ntu.edu.tw", + url="https://www.csie.ntu.edu.tw/~cjlin/liblinear", + install_requires=["scipy"], + ext_modules=[ + Extension( + "{}.{}".format(PACKAGE_NAME, dynamic_lib_name), **kwargs_for_extension + ) + ], + cmdclass={"clean": CleanCommand}, + ) + + +main() + -- 2.50.1