From 0b3527c324082948d91f333f97b338299b38de5a Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:41:49 +0700 Subject: [PATCH 01/12] Replace synapse weights with per-neuron LUT. --- src/score_addition.h | 671 +++++++++---------------------------------- test/CMakeLists.txt | 2 +- tools/score_params.h | 8 +- 3 files changed, 140 insertions(+), 541 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index afdd11b..5b18021 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -6,6 +6,11 @@ #include #include +// LUT-based Addition. +// Every neuron holds a trit and computes its next value by looking up a per-neuron table indexed by the trits of +// a set of neighbours on the ring, only the LUT contents change under mutation. +// +// Trit values are {0, 1, 2} where 0 and 1 are the two decided states and 2 means UNKNOWN. namespace score_addition { @@ -13,18 +18,18 @@ static constexpr unsigned long long NUMBER_OF_INPUT_NEURONS = 2 * 7; // K static constexpr unsigned long long NUMBER_OF_OUTPUT_NEURONS = 8; // L static constexpr unsigned long long NUMBER_OF_TICKS = 120; static constexpr unsigned long long NUMBER_OF_MUTATIONS = 100; -// Fixed-topology detour: total neuron count is set directly. -static constexpr unsigned long long POPULATION_THRESHOLD = 32; // P -// Buffer is sized to N. Effective neighbor count clamps to N-1 (self excluded) -// inside getActualNeighborCount(), keeps maxNumberOfNeighbors even and the existing buffer-center math intact. -static constexpr unsigned long long MAX_NEIGHBOR_NEURONS = POPULATION_THRESHOLD; +static constexpr unsigned long long POPULATION_THRESHOLD = 32; // P +// The neighbour offsets that feed every neuron's LUT, in LUT-index order +static constexpr long long NEIGHBOR_OFFSETS[] = { 1, 4, 13 }; +static constexpr unsigned long long MAX_NEIGHBOR_NEURONS = + sizeof(NEIGHBOR_OFFSETS) / sizeof(NEIGHBOR_OFFSETS[0]); static constexpr unsigned int SOLUTION_THRESHOLD = ((1ULL << NUMBER_OF_INPUT_NEURONS) * NUMBER_OF_OUTPUT_NEURONS * 4 / 5); template < unsigned long long numberOfInputNeurons, // K unsigned long long numberOfOutputNeurons, // L unsigned long long numberOfTicks, // N - unsigned long long maxNumberOfNeighbors, // 2M + unsigned long long maxNumberOfNeighbors, // LUT fan-in (neighbours read per neuron) unsigned long long populationThreshold, // P unsigned long long numberOfMutations, // S unsigned int solutionThreshold> @@ -35,22 +40,32 @@ struct Miner static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; static constexpr unsigned long long numberOfEvolutionNeurons = populationThreshold - numberOfNeurons; // P - K - L - static constexpr unsigned long long maxNumberOfSynapses = - populationThreshold * maxNumberOfNeighbors; static constexpr unsigned long long trainingSetSize = 1ULL << numberOfInputNeurons; // 2^K - static constexpr unsigned long long paddingNumberOfSynapses = - (maxNumberOfSynapses + 31 ) / 32 * 32; // padding to multiple of 32 - // Packed 2-bit synapse storage: 4 weights per byte. Encoding: - // 00 -> 0, 01 -> +1, 10 -> -1, 11 -> 0 - static constexpr unsigned long long packedSynapsesBytes = (maxNumberOfSynapses + 3) / 4; - static_assert( - maxNumberOfSynapses <= (0xFFFFFFFFFFFFFFFF << 1ULL), - "maxNumberOfSynapses must less than or equal MAX_UINT64/2"); - static_assert(maxNumberOfNeighbors % 2 == 0, "maxNumberOfNeighbors must divided by 2"); + // Undecided trit (the third value); the two decided states are 0 and 1. + static constexpr unsigned char TRIT_UNKNOWN = 2; + + // 3^maxNumberOfNeighbors lines per LUT (one output trit per neighbour-trit combination). + static constexpr unsigned long long ipow(unsigned long long base, unsigned long long exp) + { + unsigned long long result = 1; + for (unsigned long long i = 0; i < exp; ++i) + { + result *= base; + } + return result; + } + static constexpr unsigned long long lutSize = ipow(3, maxNumberOfNeighbors); + static_assert( populationThreshold > numberOfNeurons, "populationThreshold must be greater than numberOfNeurons"); + static_assert( + (populationThreshold & (populationThreshold - 1)) == 0, + "populationThreshold must be a power of 2"); + static_assert( + maxNumberOfNeighbors == sizeof(NEIGHBOR_OFFSETS) / sizeof(NEIGHBOR_OFFSETS[0]), + "maxNumberOfNeighbors must equal the NEIGHBOR_OFFSETS table length"); std::vector poolVec; @@ -68,11 +83,6 @@ struct Miner char output[numberOfOutputNeurons]; // numberOfOutputNeurons bits of C (values: -1 or +1) } trainingSet[trainingSetSize]; // training set size: 2^K - struct Synapse - { - char weight; - }; - // Data for running the ANN struct Neuron { @@ -83,189 +93,61 @@ struct Miner kEvolution, }; Type type; - char value; - bool markForRemoval; + unsigned char value; // trit in {0, 1, 2} }; - // Data for roll back + // Data for roll back, mutation will change LUT output contents struct ANN { Neuron neurons[maxNumberOfNeurons]; - unsigned char synapsesPacked[packedSynapsesBytes]; + unsigned char lut[maxNumberOfNeurons][lutSize]; unsigned long long population; }; ANN bestANN; ANN currentANN; - // Decoded synapse buffer (derived from currentANN.synapsesPacked). - Synapse synapses[maxNumberOfSynapses]; - struct InitValue { unsigned long long outputNeuronPositions[numberOfOutputNeurons]; unsigned long long evolutionNeuronPositions[numberOfEvolutionNeurons]; - unsigned long long synapseWeight[paddingNumberOfSynapses / 32]; // each 64bits elements will - // decide value of 32 synapses - unsigned long long synapseMutation[numberOfMutations]; + unsigned char lutInit[maxNumberOfNeurons * lutSize]; // one byte per LUT line, taken mod 3 + unsigned long long mutationSeed[numberOfMutations]; } initValue; - // Get the pointer to all outgoing synapses of a neuron. - Synapse* getSynapses(unsigned long long neuronIndex) - { - return &synapses[neuronIndex * maxNumberOfNeighbors]; - } - - // Refresh the decoded synapses[] buffer from currentANN.synapsesPacked. - void decodeSynapses() - { - // Encoding: 00 -> 0, 01 -> +1, 10 -> -1, 11 -> 0 - static constexpr char weightFromBits[4] = { 0, +1, -1, 0 }; - for (unsigned long long i = 0; i < maxNumberOfSynapses; ++i) - { - // Byte location: 4 weights per byte -> currentANN.synapsesPacked[i / 4] - // Slot location in byte: (i % 4) * 2 - // Keep 2 bits: & 0x3U - unsigned char ev = (currentANN.synapsesPacked[i / 4] >> ((i % 4) * 2)) & 0x3U; - synapses[i].weight = weightFromBits[ev]; - } - } - unsigned long long neuronIndices[maxNumberOfNeurons]; - char previousNeuronValue[maxNumberOfNeurons]; + unsigned char previousNeuronValue[maxNumberOfNeurons]; + unsigned char nextNeuronValue[maxNumberOfNeurons]; unsigned long long outputNeuronIndices[numberOfOutputNeurons]; - char outputNeuronExpectedValue[numberOfOutputNeurons]; + unsigned char outputNeuronExpectedValue[numberOfOutputNeurons]; - long long neuronValueBuffer[maxNumberOfNeurons]; + // Indices of all non-input neurons (output + evolution), the only ones whose LUT is used + // and the only ones a mutation may touch. Filled in initializeANN(). + unsigned long long updatedNeuronIndices[maxNumberOfNeurons]; + unsigned long long numberOfUpdatedNeurons; - unsigned long long getActualNeighborCount() const + // Map a bipolar training bit to a trit. The two decided states map to 0 and, the neutral maps to UNKNOWN. + static unsigned char bipolarToTrit(char val) { - unsigned long long population = currentANN.population; - unsigned long long maxNeighbors = population - 1; // Exclude self - unsigned long long actual = std::min(maxNumberOfNeighbors, maxNeighbors); - - return actual; - } - - unsigned long long getLeftNeighborCount() const - { - unsigned long long actual = getActualNeighborCount(); - // For odd number, we add extra for the left - return (actual + 1) / 2; - } - - unsigned long long getRightNeighborCount() const - { - return getActualNeighborCount() - getLeftNeighborCount(); - } - - // Get the starting index in synapse buffer (left side start) - unsigned long long getSynapseStartIndex() const - { - constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; - return synapseBufferCenter - getLeftNeighborCount(); - } - - // Get the ending index in synapse buffer (exclusive) - unsigned long long getSynapseEndIndex() const - { - constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; - return synapseBufferCenter + getRightNeighborCount(); - } - - // Convert buffer index to neighbor offset - long long bufferIndexToOffset(unsigned long long bufferIdx) const - { - constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; - if (bufferIdx < synapseBufferCenter) - { - return (long long)bufferIdx - synapseBufferCenter; // Negative (left) - } - else + if (val < 0) { - return (long long)bufferIdx - synapseBufferCenter + 1; // Positive (right), skip 0 + return 0; } - } - - // Convert neighbor offset to buffer index - long long offsetToBufferIndex(long long offset) const - { - constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; - if (offset == 0) + if (val > 0) { - return -1; // Invalid, exclude self - } - else if (offset < 0) - { - return synapseBufferCenter + offset; - } - else - { - return synapseBufferCenter + offset - 1; + return 1; } + return TRIT_UNKNOWN; } - long long getIndexInSynapsesBuffer(long long neighborOffset) const - { - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - - if (neighborOffset == 0 || - neighborOffset < -leftCount || - neighborOffset > rightCount) - { - return -1; - } - - return offsetToBufferIndex(neighborOffset); - } - - - - // Bit-flip mutation on the 2-bit packed weight encoding. - // +1 (01) flipped on either bit -> 0 (00 or 11), never -1 - // -1 (10) flipped on either bit -> 0 (11 or 00), never +1 - // 0 (00) -> +1 or -1 depending on which bit is flipped - // 0 (11) -> +1 or -1 depending on which bit is flipped (opposite of 00) - void mutate(unsigned long long synapseMutation) - { - // Seed split: bit 0 -> which of the 2 bits to flip; bits 1..63 -> synapse pick - unsigned long long population = currentANN.population; - unsigned long long actualNeighbors = getActualNeighborCount(); - - unsigned long long totalValidSynapses = population * actualNeighbors; - unsigned long long flatIdx = (synapseMutation >> 1) % totalValidSynapses; - - unsigned long long neuronIdx = flatIdx / actualNeighbors; - unsigned long long localSynapseIdx = flatIdx % actualNeighbors; - - unsigned long long synapseIndex = localSynapseIdx + getSynapseStartIndex(); - unsigned long long synapseFullBufferIdx = neuronIdx * maxNumberOfNeighbors + synapseIndex; - - // which of the 2 bits will be flipped - unsigned long long bitOffset = synapseMutation & 1ULL; - // byte location: 4 weights per byte - unsigned long long byteIdx = synapseFullBufferIdx / 4; - // which 2-bit slot in that byte (0..3) - unsigned long long nibblePos = synapseFullBufferIdx % 4; - // get mask to the set bit - unsigned char mask = 1u << (nibblePos * 2 + bitOffset); - // flip the bit, others untouched - currentANN.synapsesPacked[byteIdx] ^= mask; - } - - // Calculate the new neuron index that is reached by moving from the given `neuronIdx` `value` - // neurons to the right or left. Negative `value` moves to the left, positive `value` moves to - // the right. The return value is clamped in a ring buffer fashion, i.e. moving right of the - // rightmost neuron continues at the leftmost neuron. + // Calculate the new neuron index reached by moving `value` neurons along the ring (wraps). unsigned long long clampNeuronIndex(long long neuronIdx, long long value) { unsigned long long population = currentANN.population; - assert(value > -(long long)population && value < (long long)population + assert(value > -(long long)population && value < (long long)population && "clampNeuronIndex: |value| must be less than population"); long long nnIndex = 0; - // Calculate the neuron index (ring structure) if (value >= 0) { nnIndex = neuronIdx + value; @@ -277,424 +159,115 @@ struct Miner nnIndex = nnIndex % population; return (unsigned long long)nnIndex; } - - - // Variable-topology helpers below are preserved (not compiled) for potential ant-colony - // reuse. Not used in fixed topology. -#if 0 - // Get the pointer to all outgoing synapse of a neurons - Synapse* getSynapses(unsigned long long neuronIndex) - { - return ¤tANN.synapses[neuronIndex * maxNumberOfNeighbors]; - } - - // Remove a neuron and all synapses relate to it - void removeNeuron(unsigned long long neuronIdx) - { - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - - // Scan all its neighbor to remove their outgoing synapse point to the neuron - for (long long neighborOffset = -leftCount; neighborOffset <= rightCount; neighborOffset++) - { - if (neighborOffset == 0) continue; - - unsigned long long nnIdx = clampNeuronIndex(neuronIdx, neighborOffset); - Synapse* pNNSynapses = getSynapses(nnIdx); - - long long synapseIndexOfNN = getIndexInSynapsesBuffer(-neighborOffset); - if (synapseIndexOfNN < 0) - { - continue; - } - - // The synapse array need to be shifted regard to the remove neuron - // Also neuron need to have 2M neighbors, the addtional synapse will be set as zero - // weight Case1 [S0 S1 S2 - SR S5 S6]. SR is removed, [S0 S1 S2 S5 S6 0] Case2 [S0 S1 SR - // - S3 S4 S5]. SR is removed, [0 S0 S1 S3 S4 S5] - constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; - if (synapseIndexOfNN >= (long long)halfMax) - { - for (long long k = synapseIndexOfNN; k < (long long)endSynapseBufferIdx - 1; ++k) - { - pNNSynapses[k] = pNNSynapses[k + 1]; - } - pNNSynapses[endSynapseBufferIdx - 1].weight = 0; - } - else - { - for (long long k = synapseIndexOfNN; k > (long long)startSynapseBufferIdx; --k) - { - pNNSynapses[k] = pNNSynapses[k - 1]; - } - pNNSynapses[startSynapseBufferIdx].weight = 0; - } - } - - // Shift the synapse array and the neuron array - for (unsigned long long shiftIdx = neuronIdx; shiftIdx < currentANN.population - 1; shiftIdx++) - { - currentANN.neurons[shiftIdx] = currentANN.neurons[shiftIdx + 1]; - - // Also shift the synapses - memcpy( - getSynapses(shiftIdx), - getSynapses(shiftIdx + 1), - maxNumberOfNeighbors * sizeof(Synapse)); - } - currentANN.population--; - } - - unsigned long long - getNeighborNeuronIndex(unsigned long long neuronIndex, unsigned long long neighborOffset) - { - const unsigned long long leftNeighbors = getLeftNeighborCount(); - unsigned long long nnIndex = 0; - if (neighborOffset < leftNeighbors) - { - nnIndex = clampNeuronIndex( - neuronIndex + neighborOffset, -(long long)leftNeighbors); - } - else - { - nnIndex = clampNeuronIndex( - neuronIndex + neighborOffset + 1, -(long long)leftNeighbors); - } - return nnIndex; - } - void insertNeuron(unsigned long long neuronIndex, unsigned long long synapseIndex) + // Get neighbor index + unsigned long long getSourceNeuron(unsigned long long neuronIdx, unsigned long long sourceSlot) { - unsigned long long synapseFullBufferIdx = neuronIndex * maxNumberOfNeighbors + synapseIndex; - // Old value before insert neuron - unsigned long long oldStartSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long oldEndSynapseBufferIdx = getSynapseEndIndex(); - unsigned long long oldActualNeighbors = getActualNeighborCount(); - long long oldLeftCount = (long long)getLeftNeighborCount(); - long long oldRightCount = (long long)getRightNeighborCount(); - - constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; - - // Validate synapse index is within valid range - assert(synapseIndex >= oldStartSynapseBufferIdx && synapseIndex < oldEndSynapseBufferIdx); - - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Copy original neuron to the inserted one and set it as Neuron::kEvolution type - Neuron insertNeuron; - insertNeuron = neurons[neuronIndex]; - insertNeuron.type = Neuron::kEvolution; - unsigned long long insertedNeuronIdx = neuronIndex + 1; - - char originalWeight = synapses[synapseFullBufferIdx].weight; - - // Insert the neuron into array, population increased one, all neurons next to original one - // need to shift right - for (unsigned long long i = population; i > neuronIndex; --i) - { - neurons[i] = neurons[i - 1]; - - // Also shift the synapses to the right - memcpy(getSynapses(i), getSynapses(i - 1), maxNumberOfNeighbors * sizeof(Synapse)); - } - neurons[insertedNeuronIdx] = insertNeuron; - population++; - - // Recalculate after population change - unsigned long long newActualNeighbors = getActualNeighborCount(); - unsigned long long newStartSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long newEndSynapseBufferIdx = getSynapseEndIndex(); - - // Try to update the synapse of inserted neuron. All outgoing synapse is init as zero weight - Synapse* pInsertNeuronSynapse = getSynapses(insertedNeuronIdx); - for (unsigned long long synIdx = 0; synIdx < maxNumberOfNeighbors; ++synIdx) - { - pInsertNeuronSynapse[synIdx].weight = 0; - } - - // Copy the outgoing synapse of original neuron - if (synapseIndex < halfMax) - { - // The synapse is going to a neuron to the left of the original neuron. - // Check if the incoming neuron is still contained in the neighbors of the inserted - // neuron. This is the case if the original `synapseIndex` is > 0, i.e. - // the original synapse if not going to the leftmost neighbor of the original neuron. - if (synapseIndex > newStartSynapseBufferIdx) - { - // Decrease idx by one because the new neuron is inserted directly to the right of - // the original one. - pInsertNeuronSynapse[synapseIndex - 1].weight = originalWeight; - } - // If the incoming neuron of the original synapse if not contained in the neighbors of - // the inserted neuron, don't add the synapse. - } - else - { - // The synapse is going to a neuron to the right of the original neuron. - // In this case, the incoming neuron of the synapse is for sure contained in the - // neighbors of the inserted neuron and has the same idx (right side neighbors of - // inserted neuron = right side neighbors of original neuron before insertion). - pInsertNeuronSynapse[synapseIndex].weight = originalWeight; - } - - // The change of synapse only impact neuron in [originalNeuronIdx - actualNeighbors / 2 - // + 1, originalNeuronIdx + actualNeighbors / 2] In the new index, it will be - // [originalNeuronIdx + 1 - actualNeighbors / 2, originalNeuronIdx + 1 + - // actualNeighbors / 2] [N0 N1 N2 original inserted N4 N5 N6], M = 2. - for (long long delta = -oldLeftCount; delta <= oldRightCount; ++delta) - { - // Only process the neighbors - if (delta == 0) - { - continue; - } - unsigned long long updatedNeuronIdx = clampNeuronIndex(insertedNeuronIdx, delta); - - // Generate a list of neighbor index of current updated neuron NN - // Find the location of the inserted neuron in the list of neighbors - long long insertedNeuronIdxInNeigborList = -1; - for (long long k = 0; k < newActualNeighbors; k++) - { - unsigned long long nnIndex = getNeighborNeuronIndex(updatedNeuronIdx, k); - if (nnIndex == insertedNeuronIdx) - { - insertedNeuronIdxInNeigborList = (long long)(newStartSynapseBufferIdx + k); - } - } - - assert(insertedNeuronIdxInNeigborList >= 0); - - Synapse* pUpdatedSynapses = getSynapses(updatedNeuronIdx); - // [N0 N1 N2 original inserted N4 N5 N6], M = 2. - // Case: neurons in range [N0 N1 N2 original], right synapses will be affected - if (delta < 0) - { - // Left side is kept as it is, only need to shift to the right side - for (long long k = (long long)newEndSynapseBufferIdx - 1; k >= insertedNeuronIdxInNeigborList; --k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k - 1]; - } - - // Incomming synapse from original neuron -> inserted neuron must be zero - if (delta == -1) - { - pUpdatedSynapses[insertedNeuronIdxInNeigborList].weight = 0; - } - } - else // Case: neurons in range [inserted N4 N5 N6], left synapses will be affected - { - // Right side is kept as it is, only need to shift to the left side - for (long long k = (long long)newStartSynapseBufferIdx; k < insertedNeuronIdxInNeigborList; ++k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k + 1]; - } - } - } + return clampNeuronIndex((long long)neuronIdx, NEIGHBOR_OFFSETS[sourceSlot]); } - - // Check which neurons/synapse need to be removed after mutation - unsigned long long scanRedundantNeurons() + // Inference step, every non-input neuron looks up its next trit from the trits of its neighbours + void processTick() { unsigned long long population = currentANN.population; - Synapse* synapses = currentANN.synapses; Neuron* neurons = currentANN.neurons; - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - - unsigned long long numberOfRedundantNeurons = 0; - // After each mutation, we must verify if there are neurons that do not affect the ANN - // output. These are neurons that either have all incoming synapse weights as 0, or all - // outgoing synapse weights as 0. Such neurons must be removed. - for (unsigned long long i = 0; i < population; i++) - { - neurons[i].markForRemoval = false; - if (neurons[i].type == Neuron::kEvolution) - { - bool allOutGoingZeros = true; - bool allIncommingZeros = true; - - // Loop though its synapses for checkout outgoing synapses - for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) - { - char synapseW = synapses[i * maxNumberOfNeighbors + m].weight; - if (synapseW != 0) - { - allOutGoingZeros = false; - break; - } - } - - // Loop through the neighbor neurons to check all incoming synapses - for (long long offset = -leftCount; offset <= rightCount; offset++) - { - if (offset == 0) continue; - - unsigned long long nnIdx = clampNeuronIndex(i, offset); - long long synapseIdx = getIndexInSynapsesBuffer(-offset); - if (synapseIdx < 0) - { - continue; - } - char synapseW = getSynapses(nnIdx)[synapseIdx].weight; - - if (synapseW != 0) - { - allIncommingZeros = false; - break; - } - } - if (allOutGoingZeros || allIncommingZeros) - { - neurons[i].markForRemoval = true; - numberOfRedundantNeurons++; - } - } - } - return numberOfRedundantNeurons; - } - - // Remove neurons and synapses that do not affect the ANN - void cleanANN() - { - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Scan and remove neurons/synapses - unsigned long long neuronIdx = 0; - while (neuronIdx < population) + for (unsigned long long n = 0; n < population; ++n) { - if (neurons[neuronIdx].markForRemoval) - { - // Remove it from the neuron list. Overwrite data - // Remove its synapses in the synapses array - removeNeuron(neuronIdx); - } - else + if (Neuron::kInput == neurons[n].type) { - neuronIdx++; + nextNeuronValue[n] = neurons[n].value; // inputs are held + continue; } - } - } -#endif // variable-topology helpers (kept for ant-colony reference) - - void processTick() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - // Memset value of current one - memset(neuronValueBuffer, 0, sizeof(neuronValueBuffer)); - - // Loop though all neurons - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - - for (long long n = 0; n < population; ++n) - { - const Synapse* kSynapses = getSynapses(n); - long long neuronValue = neurons[n].value; - // Scan through all neighbor neurons and sum all connected neurons. - for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) + // Base-3 index over the neighbours: index = sum(neighbourTrit_k * 3^k). + unsigned long long index = 0; + unsigned long long place = 1; + for (unsigned long long k = 0; k < maxNumberOfNeighbors; ++k) { - char synapseWeight = kSynapses[m].weight; - long long offset = bufferIndexToOffset(m); - unsigned long long nnIndex = clampNeuronIndex(n, offset); - - // Weight-sum - neuronValueBuffer[nnIndex] += synapseWeight * neuronValue; + unsigned long long nnIndex = getSourceNeuron(n, k); + index += (unsigned long long)neurons[nnIndex].value * place; + place *= 3; } + nextNeuronValue[n] = currentANN.lut[n][index]; } - // Clamp the neuron value - for (long long n = 0; n < population; ++n) + // Commit the new values + for (unsigned long long n = 0; n < population; ++n) { - // Only non input neurons are updated if (Neuron::kInput != neurons[n].type) { - long long neuronValue = clampNeuron(neuronValueBuffer[n]); - neurons[n].value = neuronValue; + neurons[n].value = nextNeuronValue[n]; } } } + void loadTrainingData(unsigned long long trainingIndex) { unsigned long long population = currentANN.population; Neuron* neurons = currentANN.neurons; const auto& data = trainingSet[trainingIndex]; - // Load the input neuron value unsigned long long inputIndex = 0; for (unsigned long long n = 0; n < population; ++n) { - // Init as zeros - neurons[n].value = 0; if (Neuron::kInput == neurons[n].type) { - neurons[n].value = data.input[inputIndex]; + neurons[n].value = bipolarToTrit(data.input[inputIndex]); inputIndex++; } + else + { + neurons[n].value = TRIT_UNKNOWN; // undecided before the dynamics run + } } - // Load the expected output value - memcpy(outputNeuronExpectedValue, data.output, sizeof(outputNeuronExpectedValue[0]) * numberOfOutputNeurons); + for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) + { + outputNeuronExpectedValue[i] = bipolarToTrit(data.output[i]); + } } + // Tick simulation only runs on one ANN void runTickSimulation(unsigned long long trainingIndex) { unsigned long long population = currentANN.population; Neuron* neurons = currentANN.neurons; - // Load the training set and fill ANN value loadTrainingData(trainingIndex); - // Save the neuron value for comparison for (unsigned long long i = 0; i < population; ++i) { - // Backup the neuron value previousNeuronValue[i] = neurons[i].value; } for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) { processTick(); - // Check exit conditions: + // Exit conditions: // - N ticks have passed (already in for loop) // - All neuron values are unchanged - // - All output neurons have non-zero values + // - All output neurons are decided (left the UNKNOWN trit) bool allNeuronsUnchanged = true; - bool allOutputNeuronsIsNonZeros = true; - for (long long n = 0; n < population; ++n) + bool allOutputsDecided = true; + for (unsigned long long n = 0; n < population; ++n) { - // Neuron unchanged check if (previousNeuronValue[n] != neurons[n].value) { allNeuronsUnchanged = false; } - - // Ouput neuron value check - if (neurons[n].type == Neuron::kOutput && neurons[n].value == 0) + if (neurons[n].type == Neuron::kOutput && neurons[n].value == TRIT_UNKNOWN) { - allOutputNeuronsIsNonZeros = false; + allOutputsDecided = false; } } - if (allOutputNeuronsIsNonZeros || allNeuronsUnchanged) + if (allOutputsDecided || allNeuronsUnchanged) { break; } - // Copy the neuron value - for (long long n = 0; n < population; ++n) + for (unsigned long long n = 0; n < population; ++n) { previousNeuronValue[n] = neurons[n].value; } @@ -706,8 +279,7 @@ struct Miner unsigned long long population = currentANN.population; Neuron* neurons = currentANN.neurons; - // Compute the non-matching value R between output neuron value and initial value - // Because the output neuron order never changes, the order is preserved + // Output neurons are matched in index-scan order against the expected trits. unsigned int R = 0; unsigned long long outputIdx = 0; for (unsigned long long i = 0; i < population; i++) @@ -746,10 +318,6 @@ struct Miner unsigned int inferANN() { - // Synapses live as packed 2-bit values in currentANN.synapsesPacked. - // Decoded char buffer once per inference. - decodeSynapses(); - unsigned int score = 0; for (unsigned long long i = 0; i < trainingSetSize; ++i) { @@ -763,6 +331,24 @@ struct Miner return score; } + // Rewrite a single LUT line of a single updated (non-input) neuron to a different trit. + // bit 0 selects the change, and the high bits select LUT-line to change + void mutate(unsigned long long mutationSeed) + { + // bit 0: which of the two other trits to move to (always a change) + const unsigned long long delta = mutationSeed & 1ULL; + + // bits 1..63: which LUT line + const unsigned long long totalLines = numberOfUpdatedNeurons * lutSize; + const unsigned long long flatIdx = (mutationSeed >> 1) % totalLines; + const unsigned long long neuronIdx = updatedNeuronIndices[flatIdx / lutSize]; + const unsigned long long line = flatIdx % lutSize; + + const unsigned char oldTrit = currentANN.lut[neuronIdx][line]; + const unsigned char newTrit = (unsigned char)((oldTrit + 1 + delta) % 3); + currentANN.lut[neuronIdx][line] = newTrit; + } + unsigned int initializeANN(unsigned char* publicKey, unsigned char* nonce) { unsigned char hash[32]; @@ -774,7 +360,7 @@ struct Miner unsigned long long& population = currentANN.population; Neuron* neurons = currentANN.neurons; - // Initialization -- fixed-topology: population is N total, set once. + // Initialization fixed-topology: population is N total, set once. population = populationThreshold; // Generate all 2^K possible (A, B, C) pairs @@ -783,25 +369,23 @@ struct Miner // Initalize with nonce and public key random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); - // Randomly choose the positions of neurons types. - // Default = Input. + // Randomly choose the positions of neurons types. Default = Input. for (unsigned long long i = 0; i < population; ++i) { neuronIndices[i] = i; neurons[i].type = Neuron::kInput; + neurons[i].value = TRIT_UNKNOWN; } unsigned long long neuronCount = population; + // Output positions from the remaining pool for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) { unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; - // Fill the neuron type neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; - // This index is used, copy the end of indices array to current position and decrease - // the number of picking neurons neuronCount = neuronCount - 1; neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; } @@ -817,10 +401,25 @@ struct Miner neuronIndices[evolutionNeuronIdx] = neuronIndices[neuronCount]; } - // Synapse weight initialization, already in the 2-bit packed, just copy them - memcpy(currentANN.synapsesPacked, - initValue.synapseWeight, - sizeof(currentANN.synapsesPacked)); + // Cache the indices of all updated (non-input) neurons for mutation. + numberOfUpdatedNeurons = 0; + for (unsigned long long i = 0; i < population; ++i) + { + if (neurons[i].type != Neuron::kInput) + { + updatedNeuronIndices[numberOfUpdatedNeurons] = i; + numberOfUpdatedNeurons++; + } + } + + // Seed every LUT line with a trit. + for (unsigned long long n = 0; n < population; ++n) + { + for (unsigned long long line = 0; line < lutSize; ++line) + { + currentANN.lut[n][line] = (unsigned char)(initValue.lutInit[n * lutSize + line] % 3); + } + } // Run the first inference to get starting point before mutation unsigned int score = inferANN(); @@ -837,7 +436,7 @@ struct Miner for (unsigned long long s = 0; s < numberOfMutations; ++s) { - mutate(initValue.synapseMutation[s]); + mutate(initValue.mutationSeed[s]); // Ticks simulation unsigned int R = inferANN(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e2fd89b..eac6529 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ project(qiner_test LANGUAGES C CXX) add_executable(${PROJECT_NAME} main.cpp - score_addition_test.cpp + # score_addition_test.cpp # obsolete: targets the removed synapse-buffer architecture; needs a LUT rewrite ) target_include_directories(${PROJECT_NAME} diff --git a/tools/score_params.h b/tools/score_params.h index e9b37b3..c5f256d 100644 --- a/tools/score_params.h +++ b/tools/score_params.h @@ -76,22 +76,22 @@ struct ConfigPair // All configurations using Config0 = ConfigPair< HyperIdentityParams<64, 64, 50, 64, 178, 50, 36>, - AdditionParams<2 * 2, 3, 200, 16, 16, 400, 36> + AdditionParams<2 * 2, 3, 200, 3, 16, 400, 36> >; using Config1 = ConfigPair< HyperIdentityParams<256, 256, 120, 256, 612, 100, 171>, - AdditionParams<4 * 2, 5, 200, 32, 32, 400, 171> + AdditionParams<4 * 2, 5, 200, 3, 32, 400, 171> >; using Config2 = ConfigPair< HyperIdentityParams<512, 512, 150, 512, 1174, 150, 300>, - AdditionParams<7 * 2, 8, 100, 32, 32, 200, 600> + AdditionParams<7 * 2, 8, 100, 3, 32, 200, 600> >; using Config3 = ConfigPair< HyperIdentityParams<1024, 1024, 200, 1024, 3000, 200, 600>, - AdditionParams<7 * 2, 8, 150, 64, 64, 500, 600> + AdditionParams<7 * 2, 8, 150, 3, 64, 500, 600> >; using ConfigList = std::tuple; From ba8d6445db763737bda67fb8dc8a517da0cebc17 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:36:35 +0700 Subject: [PATCH 02/12] Add K/L anti-attractor search. --- src/score_addition.h | 73 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 5b18021..3e9bc0d 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -24,6 +24,8 @@ static constexpr long long NEIGHBOR_OFFSETS[] = { 1, 4, 13 }; static constexpr unsigned long long MAX_NEIGHBOR_NEURONS = sizeof(NEIGHBOR_OFFSETS) / sizeof(NEIGHBOR_OFFSETS[0]); static constexpr unsigned int SOLUTION_THRESHOLD = ((1ULL << NUMBER_OF_INPUT_NEURONS) * NUMBER_OF_OUTPUT_NEURONS * 4 / 5); +// Max LUT entries one mutation step may use (miner-chosen L) +static constexpr unsigned int MAX_LUT_ENTRIES_PER_STEP = 10; template < unsigned long long numberOfInputNeurons, // K @@ -105,13 +107,15 @@ struct Miner }; ANN bestANN; ANN currentANN; + // Snapshot for the one-step rollback in the anti-attractor walk. + ANN prevANN; struct InitValue { unsigned long long outputNeuronPositions[numberOfOutputNeurons]; unsigned long long evolutionNeuronPositions[numberOfEvolutionNeurons]; unsigned char lutInit[maxNumberOfNeurons * lutSize]; // one byte per LUT line, taken mod 3 - unsigned long long mutationSeed[numberOfMutations]; + unsigned long long mutationSeed[numberOfMutations * MAX_LUT_ENTRIES_PER_STEP]; } initValue; unsigned long long neuronIndices[maxNumberOfNeurons]; @@ -355,6 +359,10 @@ struct Miner unsigned char combined[64]; memcpy(combined, publicKey, 32); memcpy(combined + 32, nonce, 32); + // K, L and the algo bit live in nonce[0..2], exclude them from the RNG + combined[32] = 0; + combined[33] = 0; + combined[34] = 0; KangarooTwelve(combined, 64, hash, 32); unsigned long long& population = currentANN.population; @@ -427,31 +435,68 @@ struct Miner return score; } - // Main function for mining + // Main mining function: N mutation steps with the anti-attractor split unsigned int computeScore(unsigned char* publicKey, unsigned char* nonce) { - // Initialize - unsigned int bestR = initializeANN(publicKey, nonce); + // Miner knobs from nonce[1..2], do not affect the RNG. + unsigned int L = nonce[1]; + if (L < 1) + { + L = 1; + } + if (L > MAX_LUT_ENTRIES_PER_STEP) + { + L = MAX_LUT_ENTRIES_PER_STEP; + } + unsigned long long K = nonce[2]; + if (K > numberOfMutations) + { + K = numberOfMutations; + } + + unsigned int curR = initializeANN(publicKey, nonce); memcpy(&bestANN, ¤tANN, sizeof(bestANN)); + unsigned int bestR = curR; for (unsigned long long s = 0; s < numberOfMutations; ++s) { - mutate(initValue.mutationSeed[s]); + // Snapshot for the one-step rollback. + memcpy(&prevANN, ¤tANN, sizeof(prevANN)); - // Ticks simulation - unsigned int R = inferANN(); + // Apply L LUT-entry mutations from this step's fixed seed slot. + for (unsigned int i = 0; i < L; ++i) + { + mutate(initValue.mutationSeed[s * MAX_LUT_ENTRIES_PER_STEP + i]); + } + + const unsigned int r = inferANN(); - // Roll back if neccessary - if (R >= bestR) + bool accept = false; + if (s < K) { - bestR = R; - // Better R. Save the state - memcpy(&bestANN, ¤tANN, sizeof(bestANN)); + // First K steps, keep the mutation if it made the score worse. + accept = (r <= curR); } else { - // Roll back - memcpy(¤tANN, &bestANN, sizeof(bestANN)); + // Then, keep the mutation if it made the score better. + accept = (r >= curR); + } + + if (accept) + { + curR = r; + } + else + { + // Roll back one step (to the previous position, NOT to the best). + memcpy(¤tANN, &prevANN, sizeof(currentANN)); + } + + if (curR > bestR) + { + bestR = curR; + memcpy(&bestANN, ¤tANN, sizeof(bestANN)); } } return bestR; From e6da0472cc2329826082eeb89d1381e3f3c2f187 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:53:13 +0700 Subject: [PATCH 03/12] Use error-based scoring with FALSE/UNKNOWN counts. --- src/score_addition.h | 102 +++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 29 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 3e9bc0d..884ac77 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -47,6 +47,48 @@ struct Miner // Undecided trit (the third value); the two decided states are 0 and 1. static constexpr unsigned char TRIT_UNKNOWN = 2; + // Error counts of one evaluation: FALSE = decided wrong, UNKNOWN = output left at trit 2. + struct Score + { + unsigned int numberOfFalses; + unsigned int numberOfUnknowns; + }; + + // Compare two error scores: returns 1 if (1) is worse, -1 if better, 0 if equal. + // Primary key: total errors (fewer better). Tie-break: fewer FALSEs better. + static int compare(unsigned int numberOfFalses1, unsigned int numberOfUnknowns1, unsigned int numberOfFalses2, unsigned int numberOfUnknowns2) + { + if (numberOfFalses1 + numberOfUnknowns1 == numberOfFalses2 + numberOfUnknowns2) + { + if (numberOfFalses1 == numberOfFalses2) + { + return 0; + } + else + { + if (numberOfFalses1 > numberOfFalses2) + { + return 1; + } + else + { + return -1; + } + } + } + else + { + if (numberOfFalses1 + numberOfUnknowns1 > numberOfFalses2 + numberOfUnknowns2) + { + return 1; + } + else + { + return -1; + } + } + } + // 3^maxNumberOfNeighbors lines per LUT (one output trit per neighbour-trit combination). static constexpr unsigned long long ipow(unsigned long long base, unsigned long long exp) { @@ -278,26 +320,30 @@ struct Miner } } - unsigned int computeMatchingOutput() + // Count the output errors of the current ANN, FALSE (decided wrong) and UNKNOWN (undecided). + void countOutputErrors(Score& score) { unsigned long long population = currentANN.population; Neuron* neurons = currentANN.neurons; - // Output neurons are matched in index-scan order against the expected trits. - unsigned int R = 0; unsigned long long outputIdx = 0; for (unsigned long long i = 0; i < population; i++) { if (neurons[i].type == Neuron::kOutput) { - if (neurons[i].value == outputNeuronExpectedValue[outputIdx]) + const unsigned char t = neurons[i].value; + const unsigned char e = outputNeuronExpectedValue[outputIdx]; + if (t == TRIT_UNKNOWN) + { + score.numberOfUnknowns++; + } + else if (t != e) { - R++; + score.numberOfFalses++; } outputIdx++; } } - return R; } // Generate all 2^K possible (A, B, C) pairs @@ -320,17 +366,16 @@ struct Miner } } - unsigned int inferANN() + // Run the ANN over the whole training set and return its error counts. + Score inferANN() { - unsigned int score = 0; + Score score; + score.numberOfFalses = 0; + score.numberOfUnknowns = 0; for (unsigned long long i = 0; i < trainingSetSize; ++i) { - // Ticks simulation runTickSimulation(i); - - // Compute R - unsigned int R = computeMatchingOutput(); - score += R; + countOutputErrors(score); } return score; } @@ -353,7 +398,7 @@ struct Miner currentANN.lut[neuronIdx][line] = newTrit; } - unsigned int initializeANN(unsigned char* publicKey, unsigned char* nonce) + Score initializeANN(unsigned char* publicKey, unsigned char* nonce) { unsigned char hash[32]; unsigned char combined[64]; @@ -429,10 +474,8 @@ struct Miner } } - // Run the first inference to get starting point before mutation - unsigned int score = inferANN(); - - return score; + // Error counts of the starting ANN. + return inferANN(); } // Main mining function: N mutation steps with the anti-attractor split @@ -454,9 +497,9 @@ struct Miner K = numberOfMutations; } - unsigned int curR = initializeANN(publicKey, nonce); + Score cur = initializeANN(publicKey, nonce); memcpy(&bestANN, ¤tANN, sizeof(bestANN)); - unsigned int bestR = curR; + Score best = cur; for (unsigned long long s = 0; s < numberOfMutations; ++s) { @@ -469,23 +512,24 @@ struct Miner mutate(initValue.mutationSeed[s * MAX_LUT_ENTRIES_PER_STEP + i]); } - const unsigned int r = inferANN(); + const Score r = inferANN(); + const int c = compare(r.numberOfFalses, r.numberOfUnknowns, cur.numberOfFalses, cur.numberOfUnknowns); bool accept = false; if (s < K) { // First K steps, keep the mutation if it made the score worse. - accept = (r <= curR); + accept = (c >= 0); } else { // Then, keep the mutation if it made the score better. - accept = (r >= curR); + accept = (c <= 0); } if (accept) { - curR = r; + cur = r; } else { @@ -493,19 +537,19 @@ struct Miner memcpy(¤tANN, &prevANN, sizeof(currentANN)); } - if (curR > bestR) + if (compare(cur.numberOfFalses, cur.numberOfUnknowns, best.numberOfFalses, best.numberOfUnknowns) < 0) { - bestR = curR; + best = cur; memcpy(&bestANN, ¤tANN, sizeof(bestANN)); } } - return bestR; + return best.numberOfFalses + best.numberOfUnknowns; } bool findSolution(unsigned char* publicKey, unsigned char* nonce) { - unsigned int score = computeScore(publicKey, nonce); - if (score >= solutionThreshold) + unsigned int totalErrors = computeScore(publicKey, nonce); + if (totalErrors <= solutionThreshold) { return true; } From 8f577d9bd8c41effb47f148dabf9fed686abb507 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:47:09 +0700 Subject: [PATCH 04/12] Hard code number of neighbor as 3. --- src/score_addition.h | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 884ac77..5ca1e53 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -89,18 +89,12 @@ struct Miner } } - // 3^maxNumberOfNeighbors lines per LUT (one output trit per neighbour-trit combination). - static constexpr unsigned long long ipow(unsigned long long base, unsigned long long exp) - { - unsigned long long result = 1; - for (unsigned long long i = 0; i < exp; ++i) - { - result *= base; - } - return result; - } - static constexpr unsigned long long lutSize = ipow(3, maxNumberOfNeighbors); + // 3 trit inputs, 3^3 = 27 lines per LUT (one output trit per neighbour-trit combination). + static constexpr unsigned long long lutSize = 27; + static_assert( + maxNumberOfNeighbors == 3, + "the LUT index is hardcoded for 3 neighbours"); static_assert( populationThreshold > numberOfNeurons, "populationThreshold must be greater than numberOfNeurons"); @@ -226,16 +220,11 @@ struct Miner continue; } - // Base-3 index over the neighbours: index = sum(neighbourTrit_k * 3^k). - unsigned long long index = 0; - unsigned long long place = 1; - for (unsigned long long k = 0; k < maxNumberOfNeighbors; ++k) - { - unsigned long long nnIndex = getSourceNeuron(n, k); - index += (unsigned long long)neurons[nnIndex].value * place; - place *= 3; - } - nextNeuronValue[n] = currentANN.lut[n][index]; + // Base-3 index over the three neighbour trits, index = t0 + 3*t1 + 9*t2. + const unsigned long long t0 = neurons[getSourceNeuron(n, 0)].value; + const unsigned long long t1 = neurons[getSourceNeuron(n, 1)].value; + const unsigned long long t2 = neurons[getSourceNeuron(n, 2)].value; + nextNeuronValue[n] = currentANN.lut[n][t0 + 3 * t1 + 9 * t2]; } // Commit the new values From 4080b94b9e3275297a8fc112e6783540b0b04d16 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:27:50 +0700 Subject: [PATCH 05/12] Simplify the score comparison. --- src/score_addition.h | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 5ca1e53..024bbd2 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -54,39 +54,21 @@ struct Miner unsigned int numberOfUnknowns; }; - // Compare two error scores: returns 1 if (1) is worse, -1 if better, 0 if equal. - // Primary key: total errors (fewer better). Tie-break: fewer FALSEs better. + // Compare two error scores by total error count (fewer is better). + // Returns 1 if (1) is worse, -1 if better, 0 if equal. static int compare(unsigned int numberOfFalses1, unsigned int numberOfUnknowns1, unsigned int numberOfFalses2, unsigned int numberOfUnknowns2) { - if (numberOfFalses1 + numberOfUnknowns1 == numberOfFalses2 + numberOfUnknowns2) + const unsigned int total1 = numberOfFalses1 + numberOfUnknowns1; + const unsigned int total2 = numberOfFalses2 + numberOfUnknowns2; + if (total1 > total2) { - if (numberOfFalses1 == numberOfFalses2) - { - return 0; - } - else - { - if (numberOfFalses1 > numberOfFalses2) - { - return 1; - } - else - { - return -1; - } - } + return 1; } - else + if (total1 < total2) { - if (numberOfFalses1 + numberOfUnknowns1 > numberOfFalses2 + numberOfUnknowns2) - { - return 1; - } - else - { - return -1; - } + return -1; } + return 0; } // 3 trit inputs, 3^3 = 27 lines per LUT (one output trit per neighbour-trit combination). From 1e5c4c1fd382e1605d08e8578eedababbca31486 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:31:24 +0700 Subject: [PATCH 06/12] Adjust the offset of neigbor and number of neurons. --- src/score_addition.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 024bbd2..0c33d03 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -18,9 +18,9 @@ static constexpr unsigned long long NUMBER_OF_INPUT_NEURONS = 2 * 7; // K static constexpr unsigned long long NUMBER_OF_OUTPUT_NEURONS = 8; // L static constexpr unsigned long long NUMBER_OF_TICKS = 120; static constexpr unsigned long long NUMBER_OF_MUTATIONS = 100; -static constexpr unsigned long long POPULATION_THRESHOLD = 32; // P +static constexpr unsigned long long POPULATION_THRESHOLD = 256; // P // The neighbour offsets that feed every neuron's LUT, in LUT-index order -static constexpr long long NEIGHBOR_OFFSETS[] = { 1, 4, 13 }; +static constexpr long long NEIGHBOR_OFFSETS[] = { 1, 5, 47 }; static constexpr unsigned long long MAX_NEIGHBOR_NEURONS = sizeof(NEIGHBOR_OFFSETS) / sizeof(NEIGHBOR_OFFSETS[0]); static constexpr unsigned int SOLUTION_THRESHOLD = ((1ULL << NUMBER_OF_INPUT_NEURONS) * NUMBER_OF_OUTPUT_NEURONS * 4 / 5); From efa1ab0360e4fdc1a90547546e93843906ada2d4 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:17:05 +0700 Subject: [PATCH 07/12] Spectrum Digest at the begining of epoch is used for generating training data. --- src/Qiner.cpp | 21 +++++++++++++--- src/score_addition.h | 59 +++++++++++++++++++++++++++++++++++--------- src/score_common.h | 27 ++++++++++++++++++++ 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/Qiner.cpp b/src/Qiner.cpp index 14f238a..e4bdafc 100644 --- a/src/Qiner.cpp +++ b/src/Qiner.cpp @@ -102,6 +102,8 @@ static std::atomic state(0); static unsigned char computorPublicKey[32]; static unsigned char randomSeed[32]; +// Epoch-start Spectrum Digest selects the Addition training subset; empty (all zero) when none is provided. +static unsigned char epochStartSpectrumDigest[32]; static std::atomic numberOfMiningIterations(0); static std::atomic numberOfFoundSolutions(0); static std::queue> foundNonce; @@ -188,7 +190,8 @@ using HyperIdentityMiner = score_hyberidentity::Miner< int miningThreadProc() { std::unique_ptr additionMiner(new AdditionMiner()); - additionMiner->initialize(randomSeed); + // Addition requires the epoch-start Spectrum Digest; it drives the training subset. + additionMiner->initialize(randomSeed, epochStartSpectrumDigest); std::unique_ptr hyperIdentityMiner(new HyperIdentityMiner()); hyperIdentityMiner->initialize(randomSeed); @@ -365,9 +368,9 @@ static void hexToByte(const char* hex, uint8_t* byte, const int sizeInByte) int main(int argc, char* argv[]) { std::vector miningThreads; - if (argc != 7) + if (argc != 7 && argc != 8) { - printf("Usage: Qiner [Node IP] [Node Port] [MiningID] [Signing Seed] [Mining Seed] [Number of threads]\n"); + printf("Usage: Qiner [Node IP] [Node Port] [MiningID] [Signing Seed] [Mining Seed] [Number of threads] [Epoch-start Spectrum Digest (optional)]\n"); } else { @@ -396,6 +399,18 @@ int main(int argc, char* argv[]) //getIdentityFromPublicKey(signingPublicKey, miningID, false); hexToByte(argv[5], randomSeed, 32); + + // Epoch-start Spectrum Digest is optional; without it the Addition training subset uses an empty digest. + if (argc == 8) + { + hexToByte(argv[7], epochStartSpectrumDigest, 32); + } + else + { + memset(epochStartSpectrumDigest, 0, sizeof(epochStartSpectrumDigest)); + printf("WARNING: no Epoch-start Spectrum Digest provided, using an empty one for the Addition training subset.\n"); + } + unsigned int numberOfThreads = atoi(argv[6]); printf("%d threads are used.\n", numberOfThreads); miningThreads.reserve(numberOfThreads); diff --git a/src/score_addition.h b/src/score_addition.h index 0c33d03..1cf68f8 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -42,7 +42,9 @@ struct Miner static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; static constexpr unsigned long long numberOfEvolutionNeurons = populationThreshold - numberOfNeurons; // P - K - L - static constexpr unsigned long long trainingSetSize = 1ULL << numberOfInputNeurons; // 2^K + // Half of the 2^K possible pairs are graded each epoch (chosen from the epoch-start spectrum digest). + static constexpr unsigned long long trainingSetSize = (1ULL << numberOfInputNeurons) / 2; // 2^(K-1) + static constexpr unsigned long long fullTrainingSetSize = trainingSetSize * 2; // 2^K total possible pairs // Undecided trit (the third value); the two decided states are 0 and 1. static constexpr unsigned char TRIT_UNKNOWN = 2; @@ -89,19 +91,55 @@ struct Miner std::vector poolVec; - void initialize(unsigned char miningSeed[32]) + // The epoch-start Spectrum Digest is required; it selects the graded training subset. + void initialize(unsigned char miningSeed[32], const unsigned char epochStartSpectrumDigest[32]) { // Init random2 pool with mining seed poolVec.resize(POOL_VEC_PADDING_SIZE); generateRandom2Pool(miningSeed, poolVec.data()); + + // Generate the full sample, then select the subset from the Spectrum Digest. + generateFullTrainingSet(); + setEpochStartSpectrumDigest(epochStartSpectrumDigest); + } + + // Select the graded subset deterministically from the epoch-start Spectrum Digest + void setEpochStartSpectrumDigest(const unsigned char epochStartSpectrumDigest[32]) + { + // One 32-bit random draw per pick, squeezed directly from the digest. + random(epochStartSpectrumDigest, 32, (unsigned char*)selectionRandoms, trainingSetSize * sizeof(unsigned int)); + + // Select trainingSetSize distinct samples from the full set. + for (unsigned long long i = 0; i < fullTrainingSetSize; ++i) + { + pairIndexPool[i] = (unsigned int)i; + } + for (unsigned long long k = 0; k < trainingSetSize; ++k) + { + const unsigned long long remaining = fullTrainingSetSize - k; + const unsigned long long j = selectionRandoms[k] % remaining; + + // Take pairIndexPool[j], remove it from the active range, copy that sample + const unsigned int pickedTrainingIndex = pairIndexPool[j]; + // Swap the already pick to the tail to avoid duplicated selection + pairIndexPool[j] = pairIndexPool[remaining - 1]; + + trainingSet[k] = fullTrainingSet[pickedTrainingIndex]; + } } - // Training set struct TraningPair { char input[numberOfInputNeurons]; // numberOfInputNeurons / 2 bits of A , and B (values: -1 or +1) char output[numberOfOutputNeurons]; // numberOfOutputNeurons bits of C (values: -1 or +1) - } trainingSet[trainingSetSize]; // training set size: 2^K + }; + // All 2^K possible samples (generated once); trainingSet is the 2^(K-1) graded subset chosen per epoch. + TraningPair fullTrainingSet[fullTrainingSetSize]; + TraningPair trainingSet[trainingSetSize]; + + // Scratch for the digest-driven selection: random draws and the pair-index pool. + unsigned int selectionRandoms[trainingSetSize]; + unsigned int pairIndexPool[fullTrainingSetSize]; // Data for running the ANN struct Neuron @@ -318,7 +356,7 @@ struct Miner } // Generate all 2^K possible (A, B, C) pairs - void generateTrainingSet() + void generateFullTrainingSet() { static constexpr long long boundValue = (1LL << (numberOfInputNeurons / 2)) / 2; unsigned long long index = 0; @@ -328,16 +366,16 @@ struct Miner { long long C = A + B; - toTenaryBits(A, trainingSet[index].input); + toTenaryBits(A, fullTrainingSet[index].input); toTenaryBits( - B, trainingSet[index].input + numberOfInputNeurons / 2); - toTenaryBits(C, trainingSet[index].output); + B, fullTrainingSet[index].input + numberOfInputNeurons / 2); + toTenaryBits(C, fullTrainingSet[index].output); index++; } } } - // Run the ANN over the whole training set and return its error counts. + // Run the ANN over the selected training subset and return its error counts. Score inferANN() { Score score; @@ -387,9 +425,6 @@ struct Miner // Initialization fixed-topology: population is N total, set once. population = populationThreshold; - // Generate all 2^K possible (A, B, C) pairs - generateTrainingSet(); - // Initalize with nonce and public key random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); diff --git a/src/score_common.h b/src/score_common.h index 4d0dfcf..702679f 100644 --- a/src/score_common.h +++ b/src/score_common.h @@ -8,6 +8,33 @@ constexpr unsigned long long POOL_VEC_SIZE = (((1ULL<<32) + 64)) >> 3; // 2^32+64 bits ~ 512MB constexpr unsigned long long POOL_VEC_PADDING_SIZE = (POOL_VEC_SIZE + 200 - 1) / 200 * 200; // padding for multiple of 200 +// Seed a Keccak state with up to 200 input bytes and squeeze outputSize bytes +void random(const unsigned char* input, unsigned long long inputSize, unsigned char* output, unsigned long long outputSize) +{ + unsigned char state[200]; + if (inputSize < sizeof(state)) + { + memcpy(state, input, inputSize); + memset(state + inputSize, 0, sizeof(state) - inputSize); + } + else + { + memcpy(state, input, sizeof(state)); + } + + for (unsigned long long i = 0; i < outputSize / sizeof(state); i++) + { + KeccakP1600_Permute_12rounds(state); + memcpy(output, state, sizeof(state)); + output += sizeof(state); + } + if (outputSize % sizeof(state)) + { + KeccakP1600_Permute_12rounds(state); + memcpy(output, state, outputSize % sizeof(state)); + } +} + void generateRandom2Pool(unsigned char miningSeed[32], unsigned char* pool) { unsigned char state[200]; From a7680320ec098a119a6a49ba11f1630f81bf730a Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:44:01 +0700 Subject: [PATCH 08/12] The neighbor neurons index is fixed. --- src/score_addition.h | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 1cf68f8..16a5581 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -98,6 +98,9 @@ struct Miner poolVec.resize(POOL_VEC_PADDING_SIZE); generateRandom2Pool(miningSeed, poolVec.data()); + // Fixed neighbour wiring, computed once. + computeSourceNeurons(); + // Generate the full sample, then select the subset from the Spectrum Digest. generateFullTrainingSet(); setEpochStartSpectrumDigest(epochStartSpectrumDigest); @@ -178,6 +181,9 @@ struct Miner unsigned char previousNeuronValue[maxNumberOfNeurons]; unsigned char nextNeuronValue[maxNumberOfNeurons]; + // Fixed neighbour, source neuron index for each (neuron, slot) + unsigned long long sourceNeuron[maxNumberOfNeurons][maxNumberOfNeighbors]; + unsigned long long outputNeuronIndices[numberOfOutputNeurons]; unsigned char outputNeuronExpectedValue[numberOfOutputNeurons]; @@ -186,7 +192,7 @@ struct Miner unsigned long long updatedNeuronIndices[maxNumberOfNeurons]; unsigned long long numberOfUpdatedNeurons; - // Map a bipolar training bit to a trit. The two decided states map to 0 and, the neutral maps to UNKNOWN. + // Map a bipolar training bit to a trit. The two decided states map to 0 and, the neutral maps to UNKNOWN. static unsigned char bipolarToTrit(char val) { if (val < 0) @@ -200,30 +206,26 @@ struct Miner return TRIT_UNKNOWN; } - // Calculate the new neuron index reached by moving `value` neurons along the ring (wraps). - unsigned long long clampNeuronIndex(long long neuronIdx, long long value) + // Precompute the fixed neighbour wiring: source neuron index for each (neuron, slot) + void computeSourceNeurons() { - unsigned long long population = currentANN.population; - assert(value > -(long long)population && value < (long long)population - && "clampNeuronIndex: |value| must be less than population"); - - long long nnIndex = 0; - if (value >= 0) + for (unsigned long long n = 0; n < populationThreshold; ++n) { - nnIndex = neuronIdx + value; - } - else - { - nnIndex = neuronIdx + population + value; + for (unsigned long long k = 0; k < maxNumberOfNeighbors; ++k) + { + const long long value = NEIGHBOR_OFFSETS[k]; + long long nnIndex = 0; + if (value >= 0) + { + nnIndex = (long long)n + value; + } + else + { + nnIndex = (long long)n + (long long)populationThreshold + value; + } + sourceNeuron[n][k] = (unsigned long long)(nnIndex % (long long)populationThreshold); + } } - nnIndex = nnIndex % population; - return (unsigned long long)nnIndex; - } - - // Get neighbor index - unsigned long long getSourceNeuron(unsigned long long neuronIdx, unsigned long long sourceSlot) - { - return clampNeuronIndex((long long)neuronIdx, NEIGHBOR_OFFSETS[sourceSlot]); } // Inference step, every non-input neuron looks up its next trit from the trits of its neighbours @@ -241,9 +243,9 @@ struct Miner } // Base-3 index over the three neighbour trits, index = t0 + 3*t1 + 9*t2. - const unsigned long long t0 = neurons[getSourceNeuron(n, 0)].value; - const unsigned long long t1 = neurons[getSourceNeuron(n, 1)].value; - const unsigned long long t2 = neurons[getSourceNeuron(n, 2)].value; + const unsigned long long t0 = neurons[sourceNeuron[n][0]].value; + const unsigned long long t1 = neurons[sourceNeuron[n][1]].value; + const unsigned long long t2 = neurons[sourceNeuron[n][2]].value; nextNeuronValue[n] = currentANN.lut[n][t0 + 3 * t1 + 9 * t2]; } From 00c43878ff44704dcfbee7400677d5704fae5b11 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:24:47 +0700 Subject: [PATCH 09/12] Remove exit condition that all neuron value unchanged. --- src/score_addition.h | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 16a5581..4efd26a 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -178,7 +178,6 @@ struct Miner } initValue; unsigned long long neuronIndices[maxNumberOfNeurons]; - unsigned char previousNeuronValue[maxNumberOfNeurons]; unsigned char nextNeuronValue[maxNumberOfNeurons]; // Fixed neighbour, source neuron index for each (neuron, slot) @@ -293,41 +292,23 @@ struct Miner loadTrainingData(trainingIndex); - for (unsigned long long i = 0; i < population; ++i) - { - previousNeuronValue[i] = neurons[i].value; - } - for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) { processTick(); - // Exit conditions: - // - N ticks have passed (already in for loop) - // - All neuron values are unchanged - // - All output neurons are decided (left the UNKNOWN trit) - bool allNeuronsUnchanged = true; + // Exit early once every output neuron is decided (left the UNKNOWN trit). bool allOutputsDecided = true; for (unsigned long long n = 0; n < population; ++n) { - if (previousNeuronValue[n] != neurons[n].value) - { - allNeuronsUnchanged = false; - } if (neurons[n].type == Neuron::kOutput && neurons[n].value == TRIT_UNKNOWN) { allOutputsDecided = false; } } - if (allOutputsDecided || allNeuronsUnchanged) + if (allOutputsDecided) { break; } - - for (unsigned long long n = 0; n < population; ++n) - { - previousNeuronValue[n] = neurons[n].value; - } } } From 06c52314c97cd085b14994d81298c07209efe8b5 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:20:54 +0700 Subject: [PATCH 10/12] Spectrum digest use for setting the neurons location. --- src/score_addition.h | 110 +++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index 4efd26a..b31324b 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -109,8 +109,8 @@ struct Miner // Select the graded subset deterministically from the epoch-start Spectrum Digest void setEpochStartSpectrumDigest(const unsigned char epochStartSpectrumDigest[32]) { - // One 32-bit random draw per pick, squeezed directly from the digest. - random(epochStartSpectrumDigest, 32, (unsigned char*)selectionRandoms, trainingSetSize * sizeof(unsigned int)); + // One random from the digest fills the subset selection draws and the neuron placement. + random(epochStartSpectrumDigest, 32, (unsigned char*)&epochRandoms, sizeof(epochRandoms)); // Select trainingSetSize distinct samples from the full set. for (unsigned long long i = 0; i < fullTrainingSetSize; ++i) @@ -120,7 +120,7 @@ struct Miner for (unsigned long long k = 0; k < trainingSetSize; ++k) { const unsigned long long remaining = fullTrainingSetSize - k; - const unsigned long long j = selectionRandoms[k] % remaining; + const unsigned long long j = epochRandoms.selectionRandoms[k] % remaining; // Take pairIndexPool[j], remove it from the active range, copy that sample const unsigned int pickedTrainingIndex = pairIndexPool[j]; @@ -129,6 +129,9 @@ struct Miner trainingSet[k] = fullTrainingSet[pickedTrainingIndex]; } + + // Neuron placement is fixed per epoch from the digest; compute it once here. + computeNeuronPlacement(); } struct TraningPair @@ -140,8 +143,13 @@ struct Miner TraningPair fullTrainingSet[fullTrainingSetSize]; TraningPair trainingSet[trainingSetSize]; - // Scratch for the digest-driven selection: random draws and the pair-index pool. - unsigned int selectionRandoms[trainingSetSize]; + // Per-epoch data from the spectrum digest + struct EpochRandoms + { + unsigned int selectionRandoms[trainingSetSize]; + unsigned long long inputNeuronPositions[numberOfInputNeurons]; + unsigned long long outputNeuronPositions[numberOfOutputNeurons]; + } epochRandoms; unsigned int pairIndexPool[fullTrainingSetSize]; // Data for running the ANN @@ -171,12 +179,11 @@ struct Miner struct InitValue { - unsigned long long outputNeuronPositions[numberOfOutputNeurons]; - unsigned long long evolutionNeuronPositions[numberOfEvolutionNeurons]; unsigned char lutInit[maxNumberOfNeurons * lutSize]; // one byte per LUT line, taken mod 3 unsigned long long mutationSeed[numberOfMutations * MAX_LUT_ENTRIES_PER_STEP]; } initValue; + unsigned long long neuronIndices[maxNumberOfNeurons]; unsigned char nextNeuronValue[maxNumberOfNeurons]; @@ -186,8 +193,11 @@ struct Miner unsigned long long outputNeuronIndices[numberOfOutputNeurons]; unsigned char outputNeuronExpectedValue[numberOfOutputNeurons]; + // Epoch-fixed neuron placement (input/output/evolution), computed once from the spectrum digest. + Neuron::Type neuronTypes[maxNumberOfNeurons]; + // Indices of all non-input neurons (output + evolution), the only ones whose LUT is used - // and the only ones a mutation may touch. Filled in initializeANN(). + // and the only ones a mutation may touch. Filled in computeNeuronPlacement(). unsigned long long updatedNeuronIndices[maxNumberOfNeurons]; unsigned long long numberOfUpdatedNeurons; @@ -227,6 +237,48 @@ struct Miner } } + // Neuron placement (input/output/evolution types) from the digest, computed once per epoch. + void computeNeuronPlacement() + { + for (unsigned long long i = 0; i < populationThreshold; ++i) + { + neuronIndices[i] = i; + neuronTypes[i] = Neuron::kEvolution; + } + unsigned long long neuronCount = populationThreshold; + + // Input positions from the remaining pool + for (unsigned long long i = 0; i < numberOfInputNeurons; ++i) + { + unsigned long long inputNeuronIdx = epochRandoms.inputNeuronPositions[i] % neuronCount; + neuronTypes[neuronIndices[inputNeuronIdx]] = Neuron::kInput; + neuronCount = neuronCount - 1; + neuronIndices[inputNeuronIdx] = neuronIndices[neuronCount]; + } + + // Output positions from the remaining pool + for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) + { + unsigned long long outputNeuronIdx = epochRandoms.outputNeuronPositions[i] % neuronCount; + neuronTypes[neuronIndices[outputNeuronIdx]] = Neuron::kOutput; + outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; + neuronCount = neuronCount - 1; + neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; + } + // The remaining neurons stay kEvolution. + + // Cache the indices of all updated (non-input) neurons for mutation. + numberOfUpdatedNeurons = 0; + for (unsigned long long i = 0; i < populationThreshold; ++i) + { + if (neuronTypes[i] != Neuron::kInput) + { + updatedNeuronIndices[numberOfUpdatedNeurons] = i; + numberOfUpdatedNeurons++; + } + } + } + // Inference step, every non-input neuron looks up its next trit from the trits of its neighbours void processTick() { @@ -408,51 +460,15 @@ struct Miner // Initialization fixed-topology: population is N total, set once. population = populationThreshold; - // Initalize with nonce and public key + // LUT init and the mutation come from the nonce random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); - // Randomly choose the positions of neurons types. Default = Input. + // Apply the epoch-fixed neuron placement for (unsigned long long i = 0; i < population; ++i) { - neuronIndices[i] = i; - neurons[i].type = Neuron::kInput; + neurons[i].type = neuronTypes[i]; neurons[i].value = TRIT_UNKNOWN; } - unsigned long long neuronCount = population; - - // Output positions from the remaining pool - for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) - { - unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; - - neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; - outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; - - neuronCount = neuronCount - 1; - neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; - } - - // Evolution positions from the remaining pool - for (unsigned long long i = 0; i < numberOfEvolutionNeurons; ++i) - { - unsigned long long evolutionNeuronIdx = initValue.evolutionNeuronPositions[i] % neuronCount; - - neurons[neuronIndices[evolutionNeuronIdx]].type = Neuron::kEvolution; - - neuronCount = neuronCount - 1; - neuronIndices[evolutionNeuronIdx] = neuronIndices[neuronCount]; - } - - // Cache the indices of all updated (non-input) neurons for mutation. - numberOfUpdatedNeurons = 0; - for (unsigned long long i = 0; i < population; ++i) - { - if (neurons[i].type != Neuron::kInput) - { - updatedNeuronIndices[numberOfUpdatedNeurons] = i; - numberOfUpdatedNeurons++; - } - } // Seed every LUT line with a trit. for (unsigned long long n = 0; n < population; ++n) From e52dee23851cf3ced5d86d2c6c731fd1a01eb279 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:31:06 +0700 Subject: [PATCH 11/12] Remove the variable population completely --- src/score_addition.h | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/score_addition.h b/src/score_addition.h index b31324b..9460f47 100644 --- a/src/score_addition.h +++ b/src/score_addition.h @@ -170,7 +170,6 @@ struct Miner { Neuron neurons[maxNumberOfNeurons]; unsigned char lut[maxNumberOfNeurons][lutSize]; - unsigned long long population; }; ANN bestANN; ANN currentANN; @@ -282,7 +281,7 @@ struct Miner // Inference step, every non-input neuron looks up its next trit from the trits of its neighbours void processTick() { - unsigned long long population = currentANN.population; + const unsigned long long population = populationThreshold; Neuron* neurons = currentANN.neurons; for (unsigned long long n = 0; n < population; ++n) @@ -312,7 +311,7 @@ struct Miner void loadTrainingData(unsigned long long trainingIndex) { - unsigned long long population = currentANN.population; + const unsigned long long population = populationThreshold; Neuron* neurons = currentANN.neurons; const auto& data = trainingSet[trainingIndex]; @@ -339,7 +338,7 @@ struct Miner // Tick simulation only runs on one ANN void runTickSimulation(unsigned long long trainingIndex) { - unsigned long long population = currentANN.population; + const unsigned long long population = populationThreshold; Neuron* neurons = currentANN.neurons; loadTrainingData(trainingIndex); @@ -367,7 +366,7 @@ struct Miner // Count the output errors of the current ANN, FALSE (decided wrong) and UNKNOWN (undecided). void countOutputErrors(Score& score) { - unsigned long long population = currentANN.population; + const unsigned long long population = populationThreshold; Neuron* neurons = currentANN.neurons; unsigned long long outputIdx = 0; @@ -454,12 +453,9 @@ struct Miner combined[34] = 0; KangarooTwelve(combined, 64, hash, 32); - unsigned long long& population = currentANN.population; + const unsigned long long population = populationThreshold; Neuron* neurons = currentANN.neurons; - // Initialization fixed-topology: population is N total, set once. - population = populationThreshold; - // LUT init and the mutation come from the nonce random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); From 5efc77f915248e9f3b0c4f3efb879af41f1bb219 Mon Sep 17 00:00:00 2001 From: cyber-pc <165458555+cyber-pc@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:32:40 +0700 Subject: [PATCH 12/12] Adapt the test generation --- tools/score_params.h | 8 +++---- tools/score_test_generator.cpp | 38 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/tools/score_params.h b/tools/score_params.h index c5f256d..7e8b07e 100644 --- a/tools/score_params.h +++ b/tools/score_params.h @@ -76,22 +76,22 @@ struct ConfigPair // All configurations using Config0 = ConfigPair< HyperIdentityParams<64, 64, 50, 64, 178, 50, 36>, - AdditionParams<2 * 2, 3, 200, 3, 16, 400, 36> + AdditionParams<2 * 2, 3, 120, 3, 64, 100, 19> >; using Config1 = ConfigPair< HyperIdentityParams<256, 256, 120, 256, 612, 100, 171>, - AdditionParams<4 * 2, 5, 200, 3, 32, 400, 171> + AdditionParams<4 * 2, 5, 120, 3, 128, 100, 512> >; using Config2 = ConfigPair< HyperIdentityParams<512, 512, 150, 512, 1174, 150, 300>, - AdditionParams<7 * 2, 8, 100, 3, 32, 200, 600> + AdditionParams<7 * 2, 8, 120, 3, 256, 100, 52428> >; using Config3 = ConfigPair< HyperIdentityParams<1024, 1024, 200, 1024, 3000, 200, 600>, - AdditionParams<7 * 2, 8, 150, 3, 64, 500, 600> + AdditionParams<7 * 2, 8, 120, 3, 512, 100, 52428> >; using ConfigList = std::tuple; diff --git a/tools/score_test_generator.cpp b/tools/score_test_generator.cpp index bed3144..72c64b8 100644 --- a/tools/score_test_generator.cpp +++ b/tools/score_test_generator.cpp @@ -101,6 +101,7 @@ constexpr unsigned int kDefaultTotalSamples = 32; std::vector miningSeeds; std::vector publicKeys; std::vector nonces; +std::vector spectrumDigests; std::vector> scoreResults; std::vector> scoreProcessingTimes; unsigned int processedSamplesCount = 0; @@ -164,7 +165,7 @@ void writeConfigs(std::ostream &oFile, std::index_sequence) // Recursive template to process each element in scoreSettings template -static void processElement(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, int threadId, bool writeFile) +static void processElement(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, unsigned char *spectrumDigest, int threadId, bool writeFile) { using CurrentConfig = std::tuple_element_t; auto t0 = std::chrono::high_resolution_clock::now(); @@ -197,7 +198,7 @@ static void processElement(unsigned char *miningSeed, unsigned char *publicKey, else if (gSelectedAlgorithm == AlgoType::Addition) { std::unique_ptr miner = std::make_unique(); - miner->initialize(miningSeed); + miner->initialize(miningSeed, spectrumDigest); score_value = miner->computeScore(publicKey, nonce); } @@ -210,16 +211,16 @@ static void processElement(unsigned char *miningSeed, unsigned char *publicKey, // Main processing function template -static void processHelper(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, int threadId, bool writeFile, std::index_sequence) +static void processHelper(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, unsigned char *spectrumDigest, int threadId, bool writeFile, std::index_sequence) { - (processElement(miningSeed, publicKey, nonce, threadId, writeFile), ...); + (processElement(miningSeed, publicKey, nonce, spectrumDigest, threadId, writeFile), ...); } // Recursive template to process each element in scoreSettings template -static void process(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, int threadId = 0, bool writeFile = true) +static void process(unsigned char *miningSeed, unsigned char *publicKey, unsigned char *nonce, unsigned char *spectrumDigest, int threadId = 0, bool writeFile = true) { - processHelper(miningSeed, publicKey, nonce, threadId, writeFile, std::make_index_sequence{}); + processHelper(miningSeed, publicKey, nonce, spectrumDigest, threadId, writeFile, std::make_index_sequence{}); } int generateSamples(std::string sampleFileName, unsigned int numberOfSamples, bool initMiningZeros = false) @@ -238,10 +239,12 @@ int generateSamples(std::string sampleFileName, unsigned int numberOfSamples, bo miningSeeds.resize(numberOfSamples); publicKeys.resize(numberOfSamples); nonces.resize(numberOfSamples); + spectrumDigests.resize(numberOfSamples); for (unsigned int i = 0; i < numberOfSamples; i++) { publicKeys[i].setRandomValue(); nonces[i].setRandomValue(); + spectrumDigests[i].setRandomValue(); if (initMiningZeros) { memset(miningSeeds[i].m256i_u8, 0, 32); @@ -261,16 +264,18 @@ int generateSamples(std::string sampleFileName, unsigned int numberOfSamples, bo } // Write the input to file - sampleFile << "seed, publickey, nonce" << std::endl; + sampleFile << "seed, publickey, nonce, spectrumdigest" << std::endl; for (unsigned int i = 0; i < numberOfSamples; i++) { auto miningSeedHexStr = byteToHex(miningSeeds[i].m256i_u8, 32); auto publicKeyHexStr = byteToHex(publicKeys[i].m256i_u8, 32); auto nonceHexStr = byteToHex(nonces[i].m256i_u8, 32); + auto spectrumDigestHexStr = byteToHex(spectrumDigests[i].m256i_u8, 32); sampleFile << miningSeedHexStr << ", " << publicKeyHexStr << ", " - << nonceHexStr << std::endl; + << nonceHexStr << ", " + << spectrumDigestHexStr << std::endl; } if (sampleFile.is_open()) { @@ -295,11 +300,12 @@ int generateSamples(std::string sampleFileName, unsigned int numberOfSamples, bo miningSeeds.resize(totalSamples); publicKeys.resize(totalSamples); nonces.resize(totalSamples); + spectrumDigests.resize(totalSamples); for (auto i = 0; i < totalSamples; i++) { - if (sampleString[i].size() != 3) + if (sampleString[i].size() != 3 && sampleString[i].size() != 4) { - std::cout << "Number of elements is mismatched. " << sampleString[i].size() << " vs 3" << " Exiting..." << std::endl; + std::cout << "Number of elements is mismatched. " << sampleString[i].size() << " vs 3 or 4" << " Exiting..." << std::endl; return 1; } if (initMiningZeros) @@ -313,6 +319,16 @@ int generateSamples(std::string sampleFileName, unsigned int numberOfSamples, bo hexToByte(sampleString[i][1], 32, publicKeys[i].m256i_u8); hexToByte(sampleString[i][2], 32, nonces[i].m256i_u8); + + // Spectrum Digest (Addition only). Legacy 3-column files default to a zero digest. + if (sampleString[i].size() == 4) + { + hexToByte(sampleString[i][3], 32, spectrumDigests[i].m256i_u8); + } + else + { + memset(spectrumDigests[i].m256i_u8, 0, 32); + } } std::cout << "Read sample file DONE " << std::endl; } @@ -386,7 +402,7 @@ void generateScore( output_file.close(); } } - process(miningSeeds[i].m256i_u8, publicKeys[i].m256i_u8, nonces[i].m256i_u8, i, writeFilePerSample); + process(miningSeeds[i].m256i_u8, publicKeys[i].m256i_u8, nonces[i].m256i_u8, spectrumDigests[i].m256i_u8, i, writeFilePerSample); { std::lock_guard lock(gMutex);