Here's the promised demo with Order 210 mixing with a NN:

http://freenet-homepage.de/toffer_86/cmmd.exe

It's pretty much the same like PAQ.

The results are a bit better than my 210 2d SSE map. I guess the NN does a fairly well job. I only selected one of 4 NNs dependent on coding order (orders 2-0, unseen symbol).

Code:

class mixer
{
protected:
static const uint N=3;
memory<sint32> weights;
sint32* w;
memory<int> inputs;
int p1;
public:
mixer( const uint m ):
weights(N*m),
w(&weights[0]),
inputs(N)
{ Clear(); }
void Clear()
{ weights.Zero(); w=&weights[0]; inputs.Zero(); }
inline int& Input( const int i ) { return inputs[i]; }
inline int P1() const { return p1<<bitmodel::P_BITS-math::P_BITS; }
inline int Mix( const uint ctx )
{
w = &weights[ctx*N];
p1 = 0;
for (uint i=0; i<N; ++i) {
inputs[i] = math::Stretch(inputs[i]>>bitmodel::P_BITS-math::P_BITS);
p1 += w[i]*inputs[i];
}
p1 = math::Squash(p1>>16);
return P1();
}
inline void Update( const int bit )
{
const int e = 7*((bit<<math::P_BITS)-p1);
for (uint i=0; i<N; ++i) {
w[i]+=inputs[i]*e + (1<<15)>>16;
}
}
};

And the math:

math.h

Code:

namespace math {
const int P_BITS=12;
const int SCALE=1<<P_BITS; // all values are scaled by this to create integers
class squash
{
protected:
static short table[SCALE];
public:
static const int X_MIN=-SCALE/2;
static const int X_MAX=SCALE/2-1;
static const int Y_MIN=0;
static const int Y_MAX=SCALE-1;
squash();
inline int operator()( const int i ) const
{
if (i>X_MAX) { return SCALE-1; }
if (i<X_MIN) { return 0; }
return table[i-X_MIN];
}
};
class stretch
{
protected:
static short table[SCALE];
public:
static const int X_MIN=0;
static const int X_MAX=SCALE-1;
static const int Y_MIN=-SCALE/2;
static const int Y_MAX=SCALE/2-1;
stretch();
inline int operator()( const int i ) const
{
assert( i>=0 && i<SCALE ); // should be true, since we input model probs
return table[i];
}
};
extern const squash Squash;
extern const stretch Stretch;
};

math.cpp

Code:

namespace math {
const int STEM_COUNT=33; // number of vertices (on the left side, x<0)
const int DELTA=SCALE/(2*STEM_COUNT-2); // width of a bin between two vertices
// Uncomment and call CalcStems to generate the stems[].
//const double X0=-8;
//const double X1= 0;
//
//double f( const double x )
//{ return 1.0/(1.0 + exp(-x)); }
//
//void CalcStems()
//{
// const double dx = (X1-X0)/(STEM_COUNT-1);
// double x = X0;
//
// for (int i=0; i<STEM_COUNT; ++i) {
// stems[i] = int(round(f(x)*SCALE));
// printf( "%4d, ", stems[i] ); //getchar();
// if (i%12==11) printf("
");
// x+=dx;
// }
//}
static short stems[STEM_COUNT] = { // left side of the sigmoid function, x<0
1, 2, 2, 3, 4, 5, 6, 8, 10, 13, 17, 21,
27, 35, 45, 58, 74, 94, 120, 153, 194, 246, 311, 391,
488, 606, 747, 912, 1102, 1314, 1546, 1793, 2048
};
////////////////////////////////////////////////////////////////////////////////
short squash::table[] = {squash::Y_MIN-1};
squash::squash()
{
if (table[0]>=squash::Y_MIN) { return; }
for (int i=0; i<=SCALE/2; ++i) {
short* t = &stems[i/DELTA];
const int r = i%DELTA;
table[i] = (t[0]*(DELTA-r) + t[1]*r)/DELTA;
int j = SCALE-i;
if (j>SCALE/2 && j<SCALE) { table[j] = SCALE-table[i]; }
}
table[0]=stretch::X_MIN;
}
short stretch::table[]={stretch::Y_MIN-1};
stretch::stretch()
{
if (table[0]>=stretch::Y_MIN);
const squash Sq;
for (int y=squash::X_MIN; y<squash::X_MAX; ++y) {
int x=Sq(y);
while (x!=Sq(y+1)) {
table[x++]=y;
}
}
table[SCALE-1]=squash::X_MAX;
}
const squash Squash;
const stretch Stretch;
};

I might write a cmm variant based on it. Don't use cmmd for benchmarking, it's just released for comparision with Shelwien's mixing. It lacks further SSE.