From 90c6a8fd34c96f88cbb470ecde336901cbe0c986 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 29 May 2025 02:18:59 +0200 Subject: [PATCH 1/5] Define defaults, bumping nextmv dependency --- nextmv-gurobipy/pyproject.toml | 2 +- nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py | 4 ++++ nextmv-scikit-learn/pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nextmv-gurobipy/pyproject.toml b/nextmv-gurobipy/pyproject.toml index c596855f..48e761cc 100644 --- a/nextmv-gurobipy/pyproject.toml +++ b/nextmv-gurobipy/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] dependencies = [ "gurobipy>=12.0.1", - "nextmv>=0.25.0" + "nextmv>=0.28.1" ] description = "An SDK for integrating Gurobi with the Nextmv platform" dynamic = [ diff --git a/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py b/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py index c3a59d50..06220c35 100644 --- a/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py +++ b/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py @@ -19,21 +19,25 @@ nextmv.Option( name="fit_intercept", option_type=bool, + default=True, description="Whether to calculate the intercept for this model.", ), nextmv.Option( name="copy_X", option_type=bool, + default=True, description="If True, X will be copied; else, it may be overwritten.", ), nextmv.Option( name="n_jobs", option_type=int, + default=None, description="The number of jobs to use for the computation.", ), nextmv.Option( name="positive", option_type=bool, + default=False, description="When set to True, forces the coefficients to be positive.", ), ] diff --git a/nextmv-scikit-learn/pyproject.toml b/nextmv-scikit-learn/pyproject.toml index dbdf3bba..591970af 100644 --- a/nextmv-scikit-learn/pyproject.toml +++ b/nextmv-scikit-learn/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] dependencies = [ "scikit-learn>=1.6.1", - "nextmv>=0.25.0" + "nextmv>=0.28.1" ] description = "An SDK for integrating scikit-learn with the Nextmv platform" dynamic = [ From b9f31f415304f3acdb6a1a708614a46763671e3b Mon Sep 17 00:00:00 2001 From: nextmv-bot Date: Thu, 29 May 2025 00:21:44 +0000 Subject: [PATCH 2/5] Bump nextmv-scikit-learn version to v0.3.1-dev.0 --- nextmv-scikit-learn/nextmv_sklearn/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextmv-scikit-learn/nextmv_sklearn/__about__.py b/nextmv-scikit-learn/nextmv_sklearn/__about__.py index 5938482b..36266248 100644 --- a/nextmv-scikit-learn/nextmv_sklearn/__about__.py +++ b/nextmv-scikit-learn/nextmv_sklearn/__about__.py @@ -1 +1 @@ -__version__ = "v0.3.0" +__version__ = "v0.3.1.dev0" From ccf539c0c89e4bbd00137a623f31362513cf7659 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 29 May 2025 15:07:42 +0200 Subject: [PATCH 3/5] Changing default to 1 --- nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py b/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py index 06220c35..77f368bc 100644 --- a/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py +++ b/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py @@ -31,7 +31,7 @@ nextmv.Option( name="n_jobs", option_type=int, - default=None, + default=1, description="The number of jobs to use for the computation.", ), nextmv.Option( From 3124162d1dc33ae414b140f01bd1ae9402aa1462 Mon Sep 17 00:00:00 2001 From: nextmv-bot Date: Thu, 29 May 2025 13:11:45 +0000 Subject: [PATCH 4/5] Bump nextmv-scikit-learn version to v0.3.1-dev.1 --- nextmv-scikit-learn/nextmv_sklearn/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextmv-scikit-learn/nextmv_sklearn/__about__.py b/nextmv-scikit-learn/nextmv_sklearn/__about__.py index 36266248..978d617c 100644 --- a/nextmv-scikit-learn/nextmv_sklearn/__about__.py +++ b/nextmv-scikit-learn/nextmv_sklearn/__about__.py @@ -1 +1 @@ -__version__ = "v0.3.1.dev0" +__version__ = "v0.3.1.dev1" From 5ab6e588b4fb6772fccc3a7e9bf47bca9eb401c0 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Wed, 4 Jun 2025 01:05:59 +0200 Subject: [PATCH 5/5] Adding test that pickles a model --- nextmv-scikit-learn/tests/test_pickle.py | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 nextmv-scikit-learn/tests/test_pickle.py diff --git a/nextmv-scikit-learn/tests/test_pickle.py b/nextmv-scikit-learn/tests/test_pickle.py new file mode 100644 index 00000000..03aaecc1 --- /dev/null +++ b/nextmv-scikit-learn/tests/test_pickle.py @@ -0,0 +1,60 @@ +import os +import unittest + +from nextmv_sklearn.linear_model import LinearRegression, LinearRegressionOptions + +import nextmv + + +class MLRegressorModel(nextmv.Model): + def solve(self, input: nextmv.Input) -> nextmv.Output: + if input.options.mode == "linear": + model = LinearRegression(input.options) + _ = model + return nextmv.Output(solution={}, options=input.options) + else: + raise ValueError(f"Unsupported mode: {input.options.mode}") + + +class TestPickle(unittest.TestCase): + def tearDown(self): + """Removes the mlflow elements created during the test.""" + model_configuration = nextmv.ModelConfiguration( + name="reg", + ) + nextmv.model._cleanup_python_model(model_dir="export", model_configuration=model_configuration) + + def test_options(self): + model = MLRegressorModel() + # Define options (custom and sklearn). + sklearn_opts = LinearRegressionOptions().to_nextmv() + custom_options = nextmv.Options( + nextmv.Option( + name="mode", + option_type=str, + default="linear", + description="ML mode (linear or xgboost).", + required=False, + ) + ) + options = custom_options.merge(sklearn_opts) + # Create a model configuration so that we can pickle the model. + model_configuration = nextmv.ModelConfiguration( + name="reg", + requirements=[ + "nextmv", + "nextmv-scikit-learn", + ], + options=options, + ) + + # Run the model with some input data. + input = nextmv.Input(data={}, options=options) + output = model.solve(input) + self.assertIsInstance(output, nextmv.Output) + + # Save (pickle) the model to a directory. + os.makedirs("export", exist_ok=True) + model.save("export", model_configuration) + # Assert that the "export" directory is not empty + self.assertTrue(len(os.listdir("export")) > 0)