CHAOS Operators

teletype

#55

ok i’ll give it a shot…

logistic, cubic, and henon modes operate internally on bipolar floating point values; the first two use [-1, 1] and henon uses [-1.5, 1.5]. so their output needs to be rescaled to be useful in the fixed-point numerical universe of teletype.

the actual output range at any given time is dependent on the intitial state and the parameter settings. these are chaotic functions and so the whole point is that i can’t easily tell you what a given output range will be for a given configuration. but i can guarantee that as implemented, they will always be in [-10000, 10000].

for cubic/logistic i can prove that this range will never be exceeded, and that there are many orbits that closely approach those bounds. the henon is a tougher analytical nut to crack, and i am sort of eyeballing it and hard-clipping the output if it exceeds those bounds.

if you use low param values for logistic and cubic, you will see pretty steady output values for logistic, and steadily oscillating output values for cubic. cubic will change sign on each iteration; logistic will always have the sign of its initial value ( i think?) ed: no! i was wrong. i think logistic always blows up to -inf if initial state is negative. @sliderule this should be fixed in the implementation; clamp negative initial values to zero.

for henon it’s a bit more complicated (check out that crazy bifurcation map.)

why does only the cellular automata offer a 1:1 relationship between input values (0-255) and output values (0-255)?

because unlike the others, the CA algorithm doesn’t operate on floating point numbers. it actually operates on binary arrays for both the state and the parameter. in this implementation i’ve encoded these as bits in an integer (which is standard practice.) we could of course decide to scale them but i think it’s more useful not to. this way, if you understand how the structure works, you can get specific behaviors by setting known rule and state values.

and if we get bitwise operators, then you will be able to map individual states of the CA (bits in the output) to gates. which IMO is a really awesome musical application of this mathematical structure.

please see here for background on 1d binary CA (also called elementary CA):
http://mathworld.wolfram.com/ElementaryCellularAutomaton.html

It would be nice to have the range starting at 3.0 to include the first branch on the logistic and cubic maps

ok, here’s the line to change:
[ https://github.com/burnsauce/teletype/blob/chaos/src/chaos.c#L50 ]

I find it a bit hard to guess where on the logistic map (i.e. the Feigenbaum digram) I am. Is there some easy maths to calculate this?

i don’t think so. that’s why determining the behaviors of iterative functions like this, directly led to the birth of numerical modelling. analytically, we can show things about the output bounds and the phase space (inductively), but it’s hard to predict a specific orbit without just calculating it.

the equations are simple, so it’s pretty easy to calculate where you end up after N iterations from any given starting configuration, with just a few lines of code in any programming environment. i guess that’s what i’d do.

so given that, i agree that it would be nice to always have a simple relationship between the fixed-point and internal representation of state and parameter.

i should point out that even small differences and errors in floating point implementation will lead to big differences in the orbit results. in numerical modelling work i’ve often seen significant discrepancies between different platforms. i think in this case it might be especially extreme if we are compiling with -mfast-float or such.

which is just to say, you can make a python one-liner to estimate the orbit for a given [state, param] value. but don’t be surprised if the estimate diverges from the TT implementation; it does not necessarily imply a programming error.

I see that the resolution is much higher now but I liked the 275 = 2.75 ratio you first suggested.

in my PR i took the liberty of applying an arbitrary scale to the parameters, as is done to the state. this is necessary if you want multiple algorithms, because they require diferent parameter ranges - setting alpha to 3.5 in a henon map will just blow it up.

and there are many other functions that could be added. a fun one i neglected to mention is the gingerbreadman. also a general LCG might be handy.

if you want the parameters to be literal then i think you have no choice but to split the algorithms into different operators.

i am not really a teletype user or even a eurorack user, so i bow out of the discussion when it gets to fine points of usability and syntactic preferences. that said:

  • i would maybe argue that CA should be a separate op since as pointed out, it’s very different structurally.
  • IMHO, a resolution of 0.01 on the parameter value for the maps, is insufficient to explore the coolness of closely-neihboring orbits.

#56

Could we add getters to the function that return the bounds?

CHAOS.RF | returns the range floor
CHAOS.RC | returns the range ceiling
// I originally thought CHAOS.RMAX CHAOS.RMIN but the line was super long

Which would let you write,

SCALE 0 100 CHAOS.RF CHAOS.RC X 
// still not certain this will fit, I'm not near my TT and don't remember the maximum characters off hand.
// but you get the idea, being able to scale without knowing the allowable ranges by heart or having to
// look them up.

Maybe we need a shorthand symbol for CHAOS?


#57

Actually, given the above we’d also need getters for the return value range. Maybe there’s a better way to do this?


#58
  • CHAOS is really just a 1-argument generator, it could be abstracted to mean any number of things, which is how CA was a natural fit, but I am for spinning it out to its own op given its obvious difference
  • scaling outputs 1.5 to 15000 is possible on 16 bits
  • the problem of parameter precision
    • 2 decimal arguments gets us 7 decimal places:
      • e.g.: CHAOS.R 3000 1001 is 3.0001001
  • currently, pressing the back tick key makes a pound symbol. I could replace that with any bitmap.

Exploring CHAOS

  • tracking min and max of generated values allows for dynamic scaling
  • you can get a windowed average using Q

#59

feel like i should just state this again as clearly as possible:

for LOGISTIC, the output range will always be in [0, 1], given initial state in [0, 1]. (i think negative initial state will blow it up.)

for CUBIC, the output will always be in [-1, 1], given initial state in [-1, 1].

for both of these, the state is trivially mapped to [-10k, 10k], and the parameter is arbitrarily mapped. (if you’d prefer it to be literal, i guess i’d recommand [3000, 3999] -> [3.0, 3.9]. but i don’t really see what you gain from this; you just lose resolution and require anyone using the operator to also have knowledge of the underlying algorithm.)

it is not easy to say for any given initial configuration, what range that specific sequence will cover. (that is kind of the point.) but generally, for these two maps: as you increase the parameter, it will be increasingly likely that any given initial state will eventually hit any given output value in the range.

this applies more or less to the henon map, and to other iterative maps that we haven’t added yet (gbman, twisted logistic, lcg, &c.) they also have provably bounded ranges, and likewise it is futile to ask the TT to calculate, in advance, the specific range for a given configuration.

so i basically don’t think a getter for the range seems very useful. unless you take this, as @sliderule does (?), to mean an analysis feature that dynamically tracks the actual output range.


i think having a windowed average sounds interesting. but with a long window the output would tend to be boring (0 or 1/2.) with a short window it could get pretty funky. from a design perspective i’d tend towards making “windowed average” its own operator (as we did in aleph BEES.)


#60

Actually, I was suggesting that doing things like tracking the minimum and maximum values could be a useful function of some patch, e.g.:

X CHAOS
Y MIN Y X
Z MAX Z X
X SCALE Y Z N 24 N 48 X
CV 1 X

#61

that sounds like a very useful thing to have


#62

Do you mean a useful thing to have built-in or a useful code snippet?


#63

i mean just to support the idea that analyzing the range / mean of an input is a generally useful functionality (not just for CHAOS.) but its possible that i don’t quite get what you’re doing in this snippet


#64

The script keeps track of the range of chaos values by storing the minimum in Y and the maximum in Z.

Then it scales the chaos value to a 2-octave voltage range mapped from Y to Z and sends it to a CV output.


#65

cool. though i don’t see where/how the min/max are initialized. with LOGISTIC, and Y initialized to zero, Y will remain zero forever. right?

(actually i don’t personally need any more detail on this, i think we’re on the same page)


#66

Is there a compelling reason why there is just one CHAOS OP with different algorithms instead of 4 different OPs that can be used simultaneously?

  • LOGSTC
  • CUBIC
  • HENON
  • CELATM

(or some other batch of names)

Do you know what a chaotic function is? I’m not talking about this CHAOS OP, but the mathematical definition of a chaotic function. Would you like me to have a go at explaining it?


#67

(sorry to impose) i think the only real reason is happenstance; there was a CHAOS that did the logistic map and then i added the others.

LOGISTIC and CUBIC are closely related though. they take the same range of parameters and only really differ in that the first is unipolar and the second is bipolar. so i can imagine them as one thing with a polarity switch.

(that said, i somehow did not realize before doing this that teletype operators appear to be static objects - yeah? you can only have one of each? interesting.)

and i absolutely never turn down an offer to explain chaos theory.


#68

Hm, both I think - my understanding of those functions is that they output somehow random values depending on their current state and then at some point might lock into a quasi stable state and output a repeating pattern.

More comprehensive explanations would be highly appreciated though. Reading through pages of mathematical formulas on the internet does not really help me. I think I have to be able to develop a somehow haptical or visual imagination connected to the diagrams to fully grasp it.

in the beginning I did not really understand what the actual operators do before being able to play with them now. I thought there would be just a set of values defined by the r parameter which then is randomly given out. But it seems that there also is an order specific and random at the same time.

After some experiments and with a slightly better practical understanding I would follow @wolfgangschaltung in that it is a little cumbersome to map the values to a useful range and try things out with different algorithms as also the useful range is a bit different on all of them. So I could think of individual OPs too - also seeing that this might limit the number of variations in the end. With a bit of practice the inclusive solution might work too - it’s just a feature/OP that is a bit harder to understand and has an unusual structure for tt code.

But maybe the only problem is that I still do not understand what is going on there?

What actually would be great would some practical advise for the cellular automate. I played with it and tried different parameter/value combinations but hardly get anything out of it in the sense of more than one ore two different values.

I m not sure how to understand this line. I thought we were discussing the implementation of CHAOS and what might be useful from a (in my case: quite simple) user perspective. I do not see any chance that I could change anything in the actual code and have no idea how this github thing works.

I like how the two alternating values develop with the lower parameter values increasing and would love to start from the common value at roughly 3.0 as a kind of a fix point.


#69

ok, no problem. :slight_smile: i am not requesting that you make any changes yourself. but in this thread we are discussing both the user and development perspectives. so i show where to make the requested change, for whoever makes it. (because despite appearances, i will not actually watch this thread forever and can’t commit to maintaining code that i won’t use.)

:peace_symbol:


#70

Path of least resistance plus the structure of @zebra’s code got me to the solution you see.

if there’s call for it, they can be separated. CA probably should be.


#71

I think there’s two problems we need to solve,

Problem: CHAOS algorithms are hard to understand
The only recent example I can find of a new tool that wasn’t immediately understandable by most of the community is Euclidean Rhythms ER. In the documentation, there’s a link to information on them conceptually. I think CHAOS algorithms need some documentation pointers to help people understand them enough to use them musically. In the (something) of Teletype thread, there’s a strong belief that operators in TT should be emphasize use in music.

Proposed solution - documentation. If anyone is interested in working on this, it would make sense to start a new thread to discuss it.

Problem: Useful ranges for parameters and return values aren’t uniform for each algorithm
The current implementation allows for programatic choice of algorithm, so CHAOS.ALG RRAND 1 4 is possible, which strikes me as being cool and useful. However, unless you then check the algorithm chosen, you can’t easily pass it appropriate parameters or predict how to use the returned values.

Proposed solution - There’s two possible approaches -

  1. Rename the algorithms explicitely (@sam suggested this above, I did a while back as well.) so that it’s always clear what the ranges because you’re only able to call the algorithm explicitly. It also allows the use of multiple algorithms at the same time, which the current implementation doesn’t (there’s one CHAOS only).
  2. Provide upper bound and lower bound getters on CHAOS once you’ve chosen the algorithm. @zebra points out that these bounds aren’t always obvious and can change, so any bounds we choose would be suggestions, not necessarily hard bounds. I suggested CHAOS.RC // range ceiling above but I think CHAOS.UB AND CHAOS.LB make more sense after sleeping on it.

Of the above two, my preference is the second - keep the current algorithm choice option but provide helpful bounds getters so that it’s easy to use CHAOS inside of TT.


Clearly the above is my interpretation of the conversation. I don’t have any special role in the discussion, and don’t meant to imply I do. Just wanted to try to clarify the problems we’ve identified and the solutions that have been proposed to make the discussion easier to resolve. If I missed something, or am totally wrong - please correct me.


#72

As noted above, the only outlier for the value is 10000 = 1.0 is CA when you consider that 15000 fits in a 16-bit integer.

I still think that the best compromise for the R parameter is:

CHAOS.R 3750 1234     | 3.7501234

One scale, easy to understand.

edit: although it means that the getter for R will need to be split into an upper and lower section, or eliminated entirely.

So really, there’s option 3:

  1. Values at fixed scale 10000 = 1.0, 2-parameter R, CA spun off to different operator.

#73

Yes, agreed - this is a good fix. I missed it when you suggested it. Unless anyone objects, I’d suggest making this the solution and update the top post in this thread to reflect the revised API and parameter values.

Does this mean the return values will also be scaled to a range?

How is the getter for R used? I know we allow getters on values as a standard rule, but - how would it be used in this case? If we can’t find a reasonable use for it, I’d suggest leaving it as is and allow the range of the getter to be dependent on the algorithm.


#74

Yes. 1.0 => 10000, 10000 => 1.0 (equal bidirectional)

I personally don’t see the need for a getter, to be honest, but general purpose variables are scarce, so somebody may want to increment it without using one of them up to keep track.

I think that it’s not very intuitive to ask users to work with different scales on 16-bit signed integers. A 2-parameter R is easy to read and understand, IMO.