Skip to content

Commit

Permalink
Merge pull request ABRG-Models#147 from optseb/main
Browse files Browse the repository at this point in the history
Improved logistics
  • Loading branch information
sebjameswml authored Sep 27, 2023
2 parents a1a71aa + b462c2f commit 9450f1a
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 48 deletions.
45 changes: 31 additions & 14 deletions examples/graph_logist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,33 @@
#include <morph/Visual.h>
#include <morph/GraphVisual.h>
#include <morph/vvec.h>
#include <morph/unicode.h>

int main()
// Make an equation string for the legend
std::string make_legend_str (double k, double x0)
{
// We'll use morphologica's awesome unicode chars
// We'll use morphologica's awesome unicode chars for the subscript 0 on x0
using morph::unicode;
std::stringstream ktxt;
if (k != 1.0) { ktxt << k; }
std::stringstream brtxt;
std::stringstream ostxt;
if (x0 != 0.0) {
brtxt << "(";
if (x0 > 0.0) {
ostxt << " - " << x0 <<")";
} else {
ostxt << " + " << -x0 <<")";
}
}
std::stringstream eqn;
eqn << "k="<<k<<", x" << unicode::toUtf8(unicode::subs0) << "=" << x0;
eqn << ": f(x) = 1 / [1 + exp (-"<< ktxt.str() << brtxt.str() << "x" << ostxt.str() << ")]";
return eqn.str();
}

int main()
{
// Set up a morph::Visual 'scene environment'.
morph::Visual v(1024, 768, "Logistic functions");
// Create a GraphVisual object (obtaining a unique_ptr to the object) with a spatial offset within the scene of 0,0,0
Expand All @@ -16,21 +38,16 @@ int main()
morph::vvec<double> x;
// This works like numpy's linspace() (the 3 args are "start", "end" and "num"):
x.linspace (-100, 30, 200);
// Logistic functions. Args are parameters to the function are (xoffset, alpha)
std::string lftag = std::string("ofst=-10, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.1"); // A dataset tag
// Logistic functions. Args are parameters to the function: (k, x0)
// vvec::logistic() returns a new vvec with the logistic function-transformed values:
gv->setdata (x, x.logistic(-10, 0.1), lftag);
gv->setdata (x, x.logistic(0.1, -10), make_legend_str (0.1, -10));
// For this one, demonstrate use of logistic_inplace():
lftag = std::string("ofst=-5, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.25");
morph::vvec<double> xlogistic = x;
xlogistic.logistic_inplace(-5, 0.25);
gv->setdata (x, xlogistic, lftag);
lftag = std::string("ofst=0, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.5");
gv->setdata (x, x.logistic(0, 0.5), lftag);
lftag = std::string("ofst=5, ") + unicode::toUtf8(unicode::alpha) + std::string("=1");
gv->setdata (x, x.logistic(5, 1), lftag);
lftag = std::string("ofst=10, ") + unicode::toUtf8(unicode::alpha) + std::string("=2");
gv->setdata (x, x.logistic(10, 2), lftag);
xlogistic.logistic_inplace(0.25, -5);
gv->setdata (x, xlogistic, make_legend_str (0.25, -5));
gv->setdata (x, x.logistic(0.5, 0), make_legend_str (0.5, 0));
gv->setdata (x, x.logistic(1, 5), make_legend_str (1, 5));
gv->setdata (x, x.logistic(2, 10), make_legend_str (2, 10));
// finalize() makes the GraphVisual compute the vertices of the OpenGL model
gv->finalize();
// Add the GraphVisual OpenGL model to the Visual scene, transferring ownership of the unique_ptr
Expand Down
63 changes: 43 additions & 20 deletions examples/graph_logist2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,24 @@ int main()
auto gv = std::make_unique<morph::GraphVisual<double>> (morph::vec<float>({-0.5f,-0.5f,0.0f}));
v.bindmodel (gv);
// Params are read from a JSON file
double ofst=0, alpha=0, x0=0, x1=0, m=0;
double x0=0, k=0, g1x0=0, g1x1=0;
{
morph::Config conf ("../examples/graph_logist2.json");
ofst = conf.get<double> ("ofst", 4.0);
alpha = conf.get<double> ("alpha", 10.0);
x0 = conf.get<double> ("x0", -10.0);
x1 = conf.get<double> ("x1", 10.0);
m = conf.get<double> ("m", 12.0);
k = conf.get<double> ("k", 10.0);
x0 = conf.get<double> ("x0", 4.0);
g1x0 = conf.get<double> ("g1x0", -10.0);
g1x1 = conf.get<double> ("g1x1", 10.0);
}
// Data for the x axis. A vvec is like std::vector, but with built-in maths methods
morph::vvec<double> x;
// This works like numpy's linspace() (the 3 args are "start", "end" and "num"):
x.linspace (x0, x1, 100);
x.linspace (g1x0, g1x1, 100);
// Logistic functions. Args are parameters to the function are (xoffset, alpha)
std::stringstream lftag;
lftag << "m=" << m << ", ofst=" << ofst << ", " << unicode::toUtf8(unicode::alpha) << "=" << alpha; // A dataset tag
lftag << "k=" << k << ", x" << unicode::toUtf8(unicode::subs0) << "=" << x0; // A dataset tag
// vvec::logistic() returns a new vvec with the logistic function-transformed values:
gv->setdata (x, (x*m).logistic(ofst, alpha), lftag.str());

gv->setdata (x, x.logistic (k, x0), lftag.str());
gv->ylabel = "f(x)";
// finalize() makes the GraphVisual compute the vertices of the OpenGL model
gv->finalize();
// Add the GraphVisual OpenGL model to the Visual scene, transferring ownership of the
Expand All @@ -47,7 +46,9 @@ int main()
morph::vvec<double> x2;
x2.linspace (0, 1, 100);
gv2->setlimits (0,1,0,1);
gv2->setdata (x2, (x2*m).logistic(ofst, alpha), lftag.str());
gv2->setdata (x2, x2.logistic(k, x0), lftag.str());
gv2->ylabel = "f(x)";

gv2->finalize();
morph::GraphVisual<double>* gv2ptr = v.addVisualModel (gv2);

Expand All @@ -60,19 +61,41 @@ int main()
// Update from config file with every render so that changes in the file are immediately reflected in the graph.
try {
morph::Config conf ("../examples/graph_logist2.json");
ofst = conf.get<double> ("ofst", 4.0);
alpha = conf.get<double> ("alpha", 10.0);
x0 = conf.get<double> ("x0", -10.0);
x1 = conf.get<double> ("x1", 10.0);
m = conf.get<double> ("m", 12.0);
k = conf.get<double> ("k", 10.0);
x0 = conf.get<double> ("x0", 4.0);

std::stringstream newtag;
newtag << "m=" << m << ", ofst=" << ofst << ", " << unicode::toUtf8(unicode::alpha) << "=" << alpha;
newtag << "k=" << k << ", x" << unicode::toUtf8(unicode::subs0) << "=" << x0;

// Remove label and existing legends with VisualModel::clearTexts()
gvptr->clearTexts();
// Update the graphs via their non-owning pointers
gvptr->update (x, (x*m).logistic(ofst, alpha), newtag.str(), 0);
gv2ptr->update (x2, (x2*m).logistic(ofst, alpha), newtag.str(), 0);
// Hmm - on update, the graph legends don't change. Can I fix?
gvptr->update (x, x.logistic(k, x0), newtag.str(), 0);
// Show the general eqn by adding a label below the first graph
std::stringstream eqngen;
eqngen << "f(x) = 1 / [1 + exp (-k(x - x"<< unicode::toUtf8(unicode::subs0) << ")]";
gvptr->addLabel (eqngen.str(), morph::vec<float>({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f));

gv2ptr->clearTexts();
// Update legend
gv2ptr->update (x2, x2.logistic(k, x0), newtag.str(), 0);
// Add a new eqn label
std::stringstream ktxt;
if (k != 1.0) { ktxt << k; }
std::stringstream brtxt;
std::stringstream ostxt;
if (x0 != 0.0) {
brtxt << "(";
if (x0 > 0.0) {
ostxt << " - " << x0 <<")";
} else {
ostxt << " + " << -x0 <<")";
}
}
// Show the specific equation on the second graph
std::stringstream eqn;
eqn << "f(x) = 1 / [1 + exp (-"<< ktxt.str() << brtxt.str() << "x" << ostxt.str() << ")]";
gv2ptr->addLabel (eqn.str(), morph::vec<float>({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f));

if (shown_error) {
std::cout << "JSON parsed successfully\n";
Expand Down
9 changes: 4 additions & 5 deletions examples/graph_logist2.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"x0" : -10,
"x1" : 10,
"m" : 12,
"ofst" : 4,
"alpha" : 10
"g1x0" : -10,
"g1x1" : 10,
"k" : 1,
"x0" : 0
}
18 changes: 9 additions & 9 deletions morph/vvec.h
Original file line number Diff line number Diff line change
Expand Up @@ -1148,20 +1148,20 @@ namespace morph {
for (auto& i : *this) { i = std::exp (i*i/(S{-2}*sigma*sigma)); }
}

//! Replace each element with the generalised logistic function of the element:
//! f(x) = [ 1 + exp(xoff - x) ]^-alpha
vvec<S> logistic (const S xoff = S{0}, const S alpha = S{1}) const
//! Return a vvec containing the generalised logistic function of this vvec:
//! f(x) = 1 / [ 1 + exp(-k*(x - x0)) ]
vvec<S> logistic (const S k = S{1}, const S x0 = S{0}) const
{
vvec<S> rtn(this->size());
auto _element = [alpha, xoff](S i) { return std::pow((S{1} + std::exp (xoff - i)), -alpha); };
std::transform (this->begin(), this->end(), rtn.begin(), _element);
auto _logisticfn = [k, x0](S _x) { return S{1} / (S{1} + std::exp (k*(x0 - _x))); };
std::transform (this->begin(), this->end(), rtn.begin(), _logisticfn);
return rtn;
}
void logistic_inplace (const S xoff = S{0}, const S alpha = S{1})
//! Replace each element x with the generalised logistic function of the element:
//! f(x) = 1 / [ 1 + exp(-k*(x - x0)) ]
void logistic_inplace (const S k = S{1}, const S x0 = S{0})
{
for (auto& i : *this) {
i = std::pow((S{1} + std::exp (xoff - i)), -alpha);
}
for (auto& _x : *this) { _x = S{1} / (S{1} + std::exp (k*(x0 - _x))); }
}

//! Smooth the vector by convolving with a gaussian filter with Gaussian width
Expand Down

0 comments on commit 9450f1a

Please sign in to comment.