diff --git a/lib/src/CMakeLists.txt b/lib/src/CMakeLists.txt index 746a438..1ea6646 100644 --- a/lib/src/CMakeLists.txt +++ b/lib/src/CMakeLists.txt @@ -3,8 +3,10 @@ ot_add_current_dir_to_include_dirs () ot_add_source_file (SlicedInverseRegression.cxx) +ot_add_source_file (SlicedInverseRegressionResult.cxx) ot_install_header_file (SlicedInverseRegression.hxx) +ot_install_header_file (SlicedInverseRegressionResult.hxx) include_directories (${INTERNAL_INCLUDE_DIRS}) diff --git a/lib/src/SlicedInverseRegression.cxx b/lib/src/SlicedInverseRegression.cxx index 773e54e..d63a3f7 100644 --- a/lib/src/SlicedInverseRegression.cxx +++ b/lib/src/SlicedInverseRegression.cxx @@ -20,6 +20,8 @@ */ #include "otsliced/SlicedInverseRegression.hxx" #include +#include +#include using namespace OT; @@ -38,22 +40,109 @@ SlicedInverseRegression::SlicedInverseRegression() // Nothing to do } +SlicedInverseRegression::SlicedInverseRegression(const Sample & inputSample, + const Sample & outputSample) + : PersistentObject() + , inputSample_(inputSample) + , outputSample_(outputSample) + , modesNumber_(inputSample.getDimension()) +{ + if (outputSample.getDimension() != 1) + throw InvalidArgumentException(HERE) << "Supervision variable must be of dimension 1"; +} + /* Virtual constructor method */ SlicedInverseRegression * SlicedInverseRegression::clone() const { return new SlicedInverseRegression(*this); } -/* example of a func that return a point squared. */ -Point SlicedInverseRegression::square(Point& p) const -{ +void SlicedInverseRegression::run() +{ + const UnsignedInteger size = inputSample_.getSize(); - Point p_out(p.getSize()); - for(UnsignedInteger i = 0; i < p.getSize(); ++ i) + const Indices supervisionIndices = outputSample_.argsort(); + Collection list_chunk; + UnsignedInteger offset = 0; + Indices chunk_population; + for (UnsignedInteger i = 0; i < sliceNumber_; ++ i) + { + const UnsignedInteger localSize = std::min(size / sliceNumber_, size - offset); + Indices chunk(localSize); + std::copy(supervisionIndices.begin() + offset, supervisionIndices.begin() + offset + localSize, chunk.begin()); + list_chunk.add(chunk); + chunk_population.add(localSize); + offset += localSize; + } + const Point center(inputSample_.computeMean()); + Sample X_centered(inputSample_); + X_centered -= center; + CovarianceMatrix input_covariance(X_centered.computeCovariance()); + input_covariance.getImplementation()->symmetrize(); + Matrix u; + Matrix vT; +#if OPENTURNS_VERSION >= 102300 + const Point singularValues = input_covariance.computeSVDInPlace(u, vT, false); // fullSVD +#else + const Point singularValues = input_covariance.computeSVD(u, vT, false, false); // fullSVD, keepIntact +#endif + Point s1(singularValues.getSize()); + for(UnsignedInteger i = 0; i < s1.getSize(); ++ i) + s1[i] = 1.0 / singularValues[i]; + Matrix us1(u); + for(UnsignedInteger j = 0; j < s1.getSize(); ++ j) + for(UnsignedInteger i = 0; i < s1.getSize(); ++ i) + us1(i, j) *= s1[j]; + Matrix inverseCovariance(us1 * vT); + const UnsignedInteger inputDimension = inputSample_.getDimension(); + Matrix weighted_covariance(inputDimension, inputDimension); + for(UnsignedInteger j = 0; j < sliceNumber_; ++ j) { - p_out[i] = p[i] * p[i]; + const Point meanSlice(inputSample_.select(list_chunk[j]).computeMean()); + Matrix slice_moment(inputDimension, 1); + for(UnsignedInteger i = 0; i < inputDimension; ++ i) + slice_moment(i, 0) = meanSlice[i]; + Matrix slice_covariance(slice_moment * slice_moment.transpose()); + const Scalar w = chunk_population[j] * 1.0 / size; + weighted_covariance = weighted_covariance + slice_covariance * w; } - return p_out; + SquareComplexMatrix eigen_vector; + SquareMatrix icwc((inverseCovariance * weighted_covariance).getImplementation()); +#if OPENTURNS_VERSION >= 102300 + const SquareMatrix::ComplexCollection eigen_value = icwc.computeEVInPlace(eigen_vector); +#else + const SquareMatrix::ComplexCollection eigen_value = icwc.computeEV(eigen_vector, false); // keepIntact +#endif + Sample evs(eigen_value.getSize(), 1); + Scalar max_imag = 0.0; + for(UnsignedInteger i = 0; i < eigen_value.getSize(); ++ i) + { + evs(i, 0) = eigen_value[i].real(); + max_imag = std::max(max_imag, std::abs(eigen_value[i].imag())); + } + + if (max_imag > std::sqrt(SpecFunc::Precision)) + throw NotDefinedException(HERE) << "complex eigen-values during SIR"; + else if (max_imag > 0.0) + LOGWARN("negligible complex eigen-values during SIR"); + + // Matrix eigen_vector2(eigen_vector.real()); + const Indices order(evs.argsort(false)); + + Point singular_values(modesNumber_); + Matrix basis(inputDimension, modesNumber_); + for(UnsignedInteger j = 0; j < modesNumber_; ++ j) + { + singular_values[j] = eigen_value[order[j]].real(); + for(UnsignedInteger i = 0; i < inputDimension; ++ i) + basis(i, j) = eigen_vector(i, order[j]).real(); + } + result_ = SlicedInverseRegressionResult(basis, center); +} + +SlicedInverseRegressionResult SlicedInverseRegression::getResult() const +{ + return result_; } /* String converter */ @@ -64,16 +153,51 @@ String SlicedInverseRegression::__repr__() const return oss; } + +/* Slice number accessor */ +UnsignedInteger SlicedInverseRegression::getSliceNumber() const +{ + return sliceNumber_; +} + +void SlicedInverseRegression::setSliceNumber(const UnsignedInteger sliceNumber) +{ + sliceNumber_ = sliceNumber; +} + +/* Modes number accessor */ +void SlicedInverseRegression::setModesNumber(const UnsignedInteger modesNumber) +{ + if (modesNumber_ > inputSample_.getDimension()) + throw InvalidArgumentException(HERE) << "Cannot use more than " <. + * + */ + +#include "otsliced/SlicedInverseRegressionResult.hxx" + +#include +#include + +using namespace OT; + +namespace OTSLICED +{ + +CLASSNAMEINIT(SlicedInverseRegressionResult); + +static Factory Factory_SlicedInverseRegressionResult; + + +/* Default constructor */ +SlicedInverseRegressionResult::SlicedInverseRegressionResult() + : PersistentObject() +{ + // Nothing to do +} + +SlicedInverseRegressionResult::SlicedInverseRegressionResult(const OT::Matrix & basis, + const OT::Point & center) + : PersistentObject() + , basis_(basis) + , center_(center) +{ +} + +/* Virtual constructor method */ +SlicedInverseRegressionResult * SlicedInverseRegressionResult::clone() const +{ + return new SlicedInverseRegressionResult(*this); +} + +/* String converter */ +String SlicedInverseRegressionResult::__repr__() const +{ + OSS oss; + oss << "class=" << SlicedInverseRegressionResult::GetClassName(); + return oss; +} + +Function SlicedInverseRegressionResult::getTransformation() const +{ + return LinearFunction(center_, Point(center_.getDimension()), basis_); +} + +Function SlicedInverseRegressionResult::getInverseTransformation() const +{ + Matrix inv(basis_.solveLinearSystem(IdentityMatrix(center_.getDimension()))); + return LinearFunction(-center_, Point(center_.getDimension()), inv); +} + +/* Method save() stores the object through the StorageManager */ +void SlicedInverseRegressionResult::save(Advocate & adv) const +{ + PersistentObject::save(adv); + adv.saveAttribute( "basis_", basis_ ); + adv.saveAttribute( "center_", center_ ); +} + +/* Method load() reloads the object from the StorageManager */ +void SlicedInverseRegressionResult::load(Advocate & adv) +{ + PersistentObject::load(adv); + adv.loadAttribute( "basis_", basis_ ); + adv.loadAttribute( "center_", center_ ); +} + + +} /* namespace OTSLICED */ diff --git a/lib/src/otsliced/SlicedInverseRegression.hxx b/lib/src/otsliced/SlicedInverseRegression.hxx index 39bdb4f..7bdfc23 100644 --- a/lib/src/otsliced/SlicedInverseRegression.hxx +++ b/lib/src/otsliced/SlicedInverseRegression.hxx @@ -21,10 +21,9 @@ #ifndef OTSLICED_SLICEDINVERSEREGRESSION_HXX #define OTSLICED_SLICEDINVERSEREGRESSION_HXX -#include -#include -#include -#include "otsliced/otslicedprivate.hxx" +#include "otsliced/SlicedInverseRegressionResult.hxx" + +#include namespace OTSLICED { @@ -32,7 +31,7 @@ namespace OTSLICED /** * @class SlicedInverseRegression * - * SlicedInverseRegression is some slicedinverseregression type to illustrate how to add some classes in OpenTURNS + * SIR dimension reduction */ class OTSLICED_API SlicedInverseRegression : public OT::PersistentObject @@ -43,11 +42,23 @@ public: /** Default constructor */ SlicedInverseRegression(); + SlicedInverseRegression(const OT::Sample & inputSample, + const OT::Sample & outputSample); + /** Virtual constructor method */ SlicedInverseRegression * clone() const override; - /** example of a func that return a point squared. **/ - OT::Point square(OT::Point& p) const; + /** Slice number accessor */ + void setSliceNumber(const OT::UnsignedInteger sliceNumber); + OT::UnsignedInteger getSliceNumber() const; + + /** Modes number accessor */ + void setModesNumber(const OT::UnsignedInteger modesNumber); + OT::UnsignedInteger getModesNumber() const; + + void run(); + + SlicedInverseRegressionResult getResult() const; /** String converter */ OT::String __repr__() const override; @@ -59,6 +70,13 @@ public: void load(OT::Advocate & adv) override; private: + OT::Sample inputSample_; + OT::Sample outputSample_; + + OT::UnsignedInteger modesNumber_ = 0; + OT::UnsignedInteger sliceNumber_ = 10; + + SlicedInverseRegressionResult result_; }; /* class SlicedInverseRegression */ diff --git a/lib/src/otsliced/SlicedInverseRegressionResult.hxx b/lib/src/otsliced/SlicedInverseRegressionResult.hxx new file mode 100644 index 0000000..f47337e --- /dev/null +++ b/lib/src/otsliced/SlicedInverseRegressionResult.hxx @@ -0,0 +1,73 @@ +// -*- C++ -*- +/** + * @brief SlicedInverseRegressionResult + * + * Copyright 2005-2024 Airbus-EDF-IMACS-ONERA-Phimeca + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ +#ifndef OTSLICED_SLICEDINVERSEREGRESSIONRESULT_HXX +#define OTSLICED_SLICEDINVERSEREGRESSIONRESULT_HXX + +#include "otsliced/otslicedprivate.hxx" + +#include +#include +#include + +namespace OTSLICED +{ + +/** + * @class SlicedInverseRegressionResult + * + * Result from SIR algorithm + */ +class OTSLICED_API SlicedInverseRegressionResult + : public OT::PersistentObject +{ + CLASSNAME + +public: + /** Default constructor */ + SlicedInverseRegressionResult(); + + SlicedInverseRegressionResult(const OT::Matrix & basis, + const OT::Point & center); + + /** Virtual constructor method */ + SlicedInverseRegressionResult * clone() const override; + + OT::Function getTransformation() const; + OT::Function getInverseTransformation() const; + + /** String converter */ + OT::String __repr__() const override; + + /** Method save() stores the object through the StorageManager */ + void save(OT::Advocate & adv) const override; + + /** Method load() reloads the object from the StorageManager */ + void load(OT::Advocate & adv) override; + +private: + OT::Matrix basis_; + OT::Point center_; + +}; /* class SlicedInverseRegressionResult */ + +} /* namespace OTSLICED */ + +#endif /* OTSLICED_SLICEDINVERSEREGRESSIONRESULT_HXX */ diff --git a/lib/test/t_SlicedInverseRegression_std.cxx b/lib/test/t_SlicedInverseRegression_std.cxx index bfd8d5e..c48219c 100644 --- a/lib/test/t_SlicedInverseRegression_std.cxx +++ b/lib/test/t_SlicedInverseRegression_std.cxx @@ -7,8 +7,8 @@ using namespace OTSLICED; int main() { - SlicedInverseRegression a; - std::cout << a << std::endl; + SlicedInverseRegression algo; + std::cout << algo << std::endl; return 0; } diff --git a/python/doc/examples/plot_example1.py b/python/doc/examples/plot_example1.py index 1f115c9..b3a580c 100755 --- a/python/doc/examples/plot_example1.py +++ b/python/doc/examples/plot_example1.py @@ -1,29 +1,22 @@ """ -Example 1: Axial stressed beam -============================== +Example on 2-d data +=================== """ # %% -# This example is a simple beam, restrained at one side and stressed by a traction load F at the other side. -# -# Inputs: -# -# - F, Traction load, Normal(75e3, 5e3) -# - :math:`sigma`, Axial stress, LogNormal(300, 30) -# - D, diameter, 20.0 -# -# Output: Primary energy savings :math:`G` -# -# .. math:: -# -# G = \sigma_e -\frac{F}{\pi \frac{D^2}{4} } -# +import openturns as ot +import otsliced # %% -import openturns as ot -import ottemplate +# Create 2-d data X and 1-d feature Y +N = 100 +X = ot.Normal([0.0] * 2, [0.1] * 2).getSample(N) +X += [[-i / (N - 1), 2 * i / (N - 1)] for i in range(N)] +X = X - X.computeMean() +f = ot.SymbolicFunction(["x1", "x2"], ["4*(x1+2*x2)+2"]) +Y = f(X) + ot.Normal(0.0, 0.2).getSample(N) -a = ottemplate.MyClass() -p = ot.Point([2, 3]) -squared_p = a.square(p) -print(squared_p) +# %% +# Run the SIR algorithm +algo = otsliced.SlicedInverseRegression(X, Y) +transformation = algo.getResult().getTransformation() diff --git a/python/doc/user_manual/user_manual.rst b/python/doc/user_manual/user_manual.rst index c19e5a9..20c7eed 100644 --- a/python/doc/user_manual/user_manual.rst +++ b/python/doc/user_manual/user_manual.rst @@ -8,3 +8,4 @@ API Reference :template: class.rst_t SlicedInverseRegression + SlicedInverseRegressionResult diff --git a/python/src/CMakeLists.txt b/python/src/CMakeLists.txt index 99a86e0..6c9e539 100644 --- a/python/src/CMakeLists.txt +++ b/python/src/CMakeLists.txt @@ -79,6 +79,7 @@ endmacro () ot_add_python_module( ${PACKAGE_NAME} ${PACKAGE_NAME}_module.i SlicedInverseRegression.i SlicedInverseRegression_doc.i.in + SlicedInverseRegressionResult.i SlicedInverseRegressionResult_doc.i.in ) diff --git a/python/src/METADATA.in b/python/src/METADATA.in index a26cd77..4cc03e6 100644 --- a/python/src/METADATA.in +++ b/python/src/METADATA.in @@ -1,4 +1,4 @@ -Metadata-Version: 2.0 +Metadata-Version: 1.2 Name: @PACKAGE_NAME@ Version: @PACKAGE_VERSION@ Summary: Template module diff --git a/python/src/SlicedInverseRegressionResult.i b/python/src/SlicedInverseRegressionResult.i new file mode 100644 index 0000000..be7a97a --- /dev/null +++ b/python/src/SlicedInverseRegressionResult.i @@ -0,0 +1,11 @@ +// SWIG file SlicedInverseRegressionResult.i + +%{ +#include "otsliced/SlicedInverseRegressionResult.hxx" +%} + +%include SlicedInverseRegressionResult_doc.i + +%copyctor OTSLICED::SlicedInverseRegressionResult; + +%include otsliced/SlicedInverseRegressionResult.hxx diff --git a/python/src/SlicedInverseRegressionResult_doc.i.in b/python/src/SlicedInverseRegressionResult_doc.i.in new file mode 100644 index 0000000..ead0352 --- /dev/null +++ b/python/src/SlicedInverseRegressionResult_doc.i.in @@ -0,0 +1,24 @@ +%feature("docstring") OTSLICED::SlicedInverseRegressionResult +"Result class for SIR method." + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegressionResult::getTransformation +"Transformation function accessor. + +Returns +------- +transformation : :py:class:`openturns.Function` + Transformation function +" + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegressionResult::getInverseTransformation +"Inverse transformation function accessor. + +Returns +------- +inverseTransformation : :py:class:`openturns.Function` + Inverse transformation function +" diff --git a/python/src/SlicedInverseRegression_doc.i.in b/python/src/SlicedInverseRegression_doc.i.in index f71f219..0455b4a 100644 --- a/python/src/SlicedInverseRegression_doc.i.in +++ b/python/src/SlicedInverseRegression_doc.i.in @@ -1,36 +1,77 @@ %feature("docstring") OTSLICED::SlicedInverseRegression -"SlicedInverseRegression class. +"SlicedInverseRegression algorithm. + +Parameters +---------- +X : :py:class:`openturns.Sample` + Input sample +y : :py:class:`openturns.Sample` + Feature sample Examples -------- >>> import otsliced ->>> a = otsliced.SlicedInverseRegression() +>>> X = ot.Normal(2).getSample(100) +>>> Y = ot.Normal(2).getSample(100) +>>> algo = otsliced.SlicedInverseRegression(X, Y) +" + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegression::run +"Run the algorithm." + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegression::getResult +"Accessor to the result. -Notes ------ -Template module class." +Returns +------- +result : :py:class:`otsliced.SlicedInverseRegressionResult` + Result class +" // --------------------------------------------------------------------- -%feature("docstring") OTSLICED::SlicedInverseRegression::square -"Square of a point. +%feature("docstring") OTSLICED::SlicedInverseRegression::setSliceNumber +"Accessor to the number of slices. Parameters ---------- -point : sequence of float - Input point +sliceNumber : int + Number of slices +" + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegression::getSliceNumber +"Accessor to the number of slices. Returns ------- -square : :py:class:`openturns.Point` - Square of point - -Examples --------- ->>> import openturns as ot ->>> import otsliced ->>> a = otsliced.SlicedInverseRegression() ->>> point = ot.Point([1.0, 2.0, 3.0]) ->>> a.square(point) -class=Point name=Unnamed dimension=3 values=[1,4,9]" +sliceNumber : int + Number of slices +" + +// --------------------------------------------------------------------- + +%feature("docstring") OTSLICED::SlicedInverseRegression::setModesNumber +"Accessor to the number of modes. + +Parameters +---------- +modesNumber : int + Number of modes retained. +" + +// --------------------------------------------------------------------- +%feature("docstring") OTSLICED::SlicedInverseRegression::getModesNumber +"Accessor to the number of modes. + +Returns +------- +modesNumber : int + Number of modes retained. +" diff --git a/python/src/otsliced_module.i b/python/src/otsliced_module.i index fab19e7..54f6551 100644 --- a/python/src/otsliced_module.i +++ b/python/src/otsliced_module.i @@ -18,6 +18,7 @@ // The new classes %include otsliced/otslicedprivate.hxx +%include SlicedInverseRegressionResult.i %include SlicedInverseRegression.i diff --git a/python/test/t_SlicedInverseRegression_std.py b/python/test/t_SlicedInverseRegression_std.py index 6a9d590..f1f50bb 100755 --- a/python/test/t_SlicedInverseRegression_std.py +++ b/python/test/t_SlicedInverseRegression_std.py @@ -1,14 +1,24 @@ #!/usr/bin/env python -from __future__ import print_function import openturns as ot +import openturns.testing as ott import otsliced -a = otsliced.SlicedInverseRegression() -print(a) +# Make 2D dataset +N = 100 +X = ot.Normal([0.0] * 2, [0.1] * 2).getSample(N) +X += [[-i / (N - 1), 2 * i / (N - 1)] for i in range(N)] +X = X - X.computeMean() +f = ot.SymbolicFunction(["x1", "x2"], ["4*(x1+2*x2)+2"]) +Y = f(X) + ot.Normal(0.0, 0.2).getSample(N) -p = ot.Point([2, 3]) -print(p) - -squared_p = a.square(p) -print(squared_p) +algo = otsliced.SlicedInverseRegression(X, Y) +algo.run() +result = algo.getResult() +x0 = X[0] +u0 = result.getTransformation()(x0) +print(x0, u0) +ott.assert_almost_equal(u0, [0.832816, -0.0498857]) +z0 = result.getInverseTransformation()(u0) +print(u0, z0) +ott.assert_almost_equal(z0, x0) diff --git a/python/test/t_docstring.expout b/python/test/t_docstring.expout deleted file mode 100644 index c094f3a..0000000 --- a/python/test/t_docstring.expout +++ /dev/null @@ -1,4 +0,0 @@ -__init__ ................................................... 0 tests failed -ottemplate ................................................. 0 tests failed -------------------------------------------------------------------------------- -TOTAL ...................................................... 0 tests failed