News:

SimuTranslator
Make Simutrans speak your language.

Simple integer sigmoid function?

Started by jamespetts, June 16, 2022, 12:04:45 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

jamespetts

For the purposes of the ex-15 branch, one of the things that I need is a sigmoid function for determining the maintenance increase over time. However, all of the reference material that I have been able to find is written requiring floating point arithmetic: this is the simplest example.

As we know, we cannot use floating point arithmetic in Simutrans for simulation functions as this is not deterministic among different platforms (e.g. Linux and Windows), so we need to use integer arithmetic.

Does anyone have any idea of how to write an integer only sigmoid function in C++? I am afraid that advanced mathematics are not my forte. It need not be very precise for these purposes, so a basic technique will suffice.

Thank you in advance.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

prissi

Do you want to add a neural network?

Since there are infinte sigmoid functions, you need to be more specific. If any is ok, you can use
x*/sqrt(1+x*x), since there is an integer square root function in Simutrans. If you can tolerate a less steep function, there is also x/(1+abs(x)) which is pure integer arithmetic ...

You can stretch these by using a larger number than 1.

jamespetts

Thank you very much, that is helpful. One thing that I am not clear about (and to those who understand advanced mathematics, I am sure that this is a silly question): how does one set the upper bound of this function? For a non-integral sigmoid function, I understand that the output range is between 0 and 1, but what is the output range of the two functions above?

Thank you again.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Mariculous

#3
x/sqrt(1+x*x) is ℝ -> (-1,1)
Same for x/(1+abs(x))

You can always scale and shift the output interval by scaling and shifting the output.
a * (x/sqrt(b+x*x) + c), respectively a * (x/(b+abs(x)) +c)
a will scale the output interval by the given factor.
c will shift the output interval by the given constant.
b will "stretch" in y direction.
Alternatively, you can always scale and shift the input to scale/move in x-direction

So if you are interessted in a sigmoid on the (0, 1) output range, you should define
a:=1/2
c:=1

Edit: An addition to the integer calculation fact, beware overflows if you choose the x/sqrt(b+x*x) sigmid.
If your input x is small enough, you are fine. "Small enough" practically means, if your input is sint16, use sint32 for calculations and you are safe.


If you really need floating point in simutrans, use a software implementation.
There already is one in simutrans-extended, used by the vehicle physics. Not sure what the exact requirements are for the sigmoid you need.

jamespetts

My apologies, I think that there may have been a misunderstanding: what I am after is an integer function, as we cannot use floating point for simulation code in Simutrans. So, a function whose output goes from 0 to 1 is not usable.

What I need is a function whose output goes from x to y (or, at last, from 0 to y), where y is an arbitrary integer.

In fact, ideally, what would be good is a function which takes four inputs: x, y, n and p. x would be the maximum on a scale (e.g., the maximum distance between overhauls) and y would be the current point on that scale (e.g., the current distance since last overhaul). n would be the minimum and p the maximum in the thing to be scaled (e.g., the base overhaul cost and the maximum overhaul cost). I know that this can be done using just x and y and then thereafter scaling with n and p, but if there is a cleaner, more efficient way of doing it, that would be even better.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

prissi

Do you mean a function that goes from 0 to 2*yt and has the turning point at xt with a given steepness?

(2*yt*(x-xt))/(steepness+abs(x-xt))+yt

does this, as long a 0 << xt << xmax, and steepness << xt

However, I really doubt that for cost scaling these are good functions unless you use very shallow steepness. I have no clear idea what you want to achieve, hence I cannot help you much.

Mariculous

#6
I am quite sure you mean
(yt*(x-xt))/(steepness+abs(x-xt))+yt
Which goes from 0 to 2*yt

Anyways, none of these are quite stable when calculated as integers, but might be good enough.

For example,
xt:=20
yt:=10
steepness:=1

jamespetts

Thank you both. For clarity, what I am looking to implement is something resembling the bathtub curve, but, for reasons of simplicity, without the initial period. Does that make things any clearer?
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Mariculous

I've never heard of that bathtub curve and I suspect I don't understand how it is related to a sigmoid curve.

PJMack

Quote from: jamespetts on June 16, 2022, 03:53:48 PMFor clarity, what I am looking to implement is something resembling the bathtub curve, but, for reasons of simplicity, without the initial period. Does that make things any clearer?
Based on the site given, the bathtub curve appears to be the sum of an inverse exponential curve for the initial period, a line for the random failures, and exponential curve for wear-out.  Although exponential for integers and fixed point values can be implemented, for this application it may be best to use a polynomial rather than exponential function.

If absolutely necessary, Simutrans has a small class called float32e8 (in the utils folder) which could be used to store a 1 bit sign, 32 bit mantissa, and 8 bit exponent.  It already has the proper operator overloads and utility functions for taking power, square root, base 2 logarithms and base 2 exponents.

prissi

Since sigmoid function is also obtained by integrating a Gaussion (or any peak function), one can indeed use them to model break downs. Assume a vehicle breaks down with a Gaussion/Lorentzian/etc. probability distribution around x0 with a certain FWHM, then the number broken down vehicles are given by a sigmoid function.

So one could argue to base maintenance on s sigmoid function, although after 2*x0 passed, there should be another step upwards.

(On a side note, since I saw sailships and horse quite outside their period in extended games, I am not sure if these will really alleviate these problems.)

jamespetts

Thank you both for your replies. The intended relationship between the bathtub curve and sigmoid function is subtle and complex. As indicated above, for simplicity, I had not wanted to implement the initial part of the bathtub curve (the high and reducing failure rate/maintenance cost), not least because in the Simutrans context it would distort player incentives in an unrealistic way (this curve applies on a per type basis, not a per vehicle basis, whereas the wearing out applies on a per vehicle basis; but no player would then want to buy the vehicle until its initial high cost phase had ended, nor would a player easily understand that that cost would come down, and the complexities associated with accommodating these issues would be immense).

Thus, what we want to implement is the steady state part to the end. This looks a lot like a sigmoid curve in the initial part. However, the later part of the bathtub curve does not reach a new, higher steady state in the way that a sigmoid curve does. However, that new, higher steady state is, unless I have misunderstood something, necessary in a Simutrans context, as the cost of maintaining a vehicle does not carry on increasing to infinity; if somebody were still trying to run stagecoaches from the 1750s to-day, it would not cost twice the GDP of the USA every hour to maintain them. Thus, to implement a workable algorithm, we do need something resembling a sigmoid curve, although an exponential curve with a cutoff would also work (although the sudden cutoff from an exponential increase would seem very odd and probably be unrealistic - but this may be of limited importance in practice).

We do probably want a fairly gentle curve, as others have suggested, as a very steep curve would mean that the increase happens almost instantly, which is not how real vehicles are affected by maintenance. Nonetheless, it still has to be a curve of increasing steepness with some sort of exponential component. However, it need not be a very precise algorithm, so some relatively minor integer rounding errors may be acceptable.

I am afraid that I have very limited understanding of advanced mathematics and concepts such as Gaussion or Lorentzian functions. I did look up polynomial functions, but it seems that this is a very broad class of functions, so this does not tell me what sort of polynomial function that I should be implementing.

What I have done so far is implement a very basic attempt at a sigmoid curve, but this uses conditional logic to give a different function depending on whether the value is more than half of the upper bound, which gives an uneven distribution:

uint64 sigmoid(uint64 value, uint64 upper_bound)
{
// This is a very spiky S curve, more like:
/*
*   ___
* /
* /
*    |
*    |
*    |
*    |
    *    |
*    |
*    |
*    /
* ___/
*/

// TODO: Implement a proper function

// First, compute the first half of the S curve
const uint64 first_part = ((value * value) / (upper_bound * upper_bound));

// Next, compute the second half - only if the value is > half the upper bound
uint64 second_part = 0;
if (value > (upper_bound / 2))
{
second_part = sqrt_i64(value) / sqrt_i64(upper_bound);
}
else
{
return first_part;
}

// Average the two if in the third quartile of the range
return (first_part + second_part) / 2;
}

Can anyone suggest a suitable polynomial function that will give me a somewhat smoother output?

Thank you all for your assistance.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

prissi

(yt*(x-xt))/(steepness+abs(x-xt))+yt
can do this as long as you use large steepness (means very flat rising actually).

jamespetts

Thank you for that. Can I check what you intend by yt an xt here? I assume that x and y are the two variables that I need to pass to this function?

Thank you again.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Mariculous

xt and yt translate the function in a way that the turning point will be at T(xt,yt) <- Point

Further, the yt will scale the function so that the output range will be (0, 2*yt) <- Range

prissi

And the higher the steepness the more gradual the change.

jamespetts

Thank you for this - apologies for my very limited understanding of advanced mathematics. May I ask what sort of calibration that there is here for steepness - i.e., what is a high steepness value? 10? 100? 1000? 10000? Apologies if this is a silly question. Thank you again.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Matthew

I know even less maths than James but I had a thought. Would it help you, James, if Prissi or Sirius could enter this function in an online plotter so that you could play with the values?

I think I got Wolfram Alpha to produce this plot of the function, since it looks vaguely like a bathtub. I tried changing the values to get more of a bathtub shape, but hit the computation time barrier (you can only perform simple calculations, because they want you to buy their enormously expensive Mathematica product).

I also tried using the free Desmos plotter but I don't think I got anywhere near making it work because it never actually produces a curve. Here is my best effort, but it's clearly the wrong shape. And the variables all seem to be controlling the wrong parts of the function.

In both cases I had to negative the function to get the desired shape (i.e. y falls and then rises).

But maybe someone who actually understood the maths could get one of these plotters to p[oduce something useful?
(Signature being tested) If you enjoy playing Simutrans, then you might also enjoy watching Japan Railway Journal
Available in English and simplified Chinese
如果您喜欢玩Simutrans的话,那么说不定就想看《日本铁路之旅》(英语也有简体中文字幕)。

prissi

https://www.geogebra.org/graphing can plot this.

Use this
h(x)=((2200 (x-10000))/(100+abs(x-10000)))+2200
and choose duplicate to create copies. You can then alter parameters. Or use excel, see atached

Matthew

Quote from: prissi on June 22, 2022, 12:41:40 PMhttps://www.geogebra.org/graphing can plot this.

Use this
h(x)=((2200 (x-10000))/(100+abs(x-10000)))+2200
and choose duplicate to create copies. You can then alter parameters. Or use excel, see atached

Thank you! It may or may not help James, but I enjoyed plotting different values. Interestingly, it seems that setting inconsistent values for the y in yt gets different results too:



By the way, I found that I needed to zoom out a lot, as the 'Zoom to fit' tool did not guess correctly for this purpose.
(Signature being tested) If you enjoy playing Simutrans, then you might also enjoy watching Japan Railway Journal
Available in English and simplified Chinese
如果您喜欢玩Simutrans的话,那么说不定就想看《日本铁路之旅》(英语也有简体中文字幕)。