alice/code/ress.py

255 lines
6.9 KiB
Python

"""RESS.py
Random Equal Stimulus Sets
Originally coded in Object Pascal for Delphi by Wesley R. Elsberry
around 1999.
Translation to Python 3 by ChatGPT (GPT-4) 2023-06-01.
Random Equal Stimulus Sets are sequences of numbers indicating one of
a set of stimuli to be presented to a subject in a cognitive or
psychophysics task. The basic rules for generating these sequences is
derived from Gellermann 1925(?), but modified to permit the
specification of more than two stimuli in the set. The restriction on
a maximum of three sequential presentations of the same stimulus is
retained.
Issues:
The 'next_yield' method does not work.
Using 'next' for a sequence longer than the defined length of
sequence can cause there to be sequences that violate Gellermann's
assumptions, as the sequences composed together are not tested
across the joins.
"""
import sys
import os
import traceback
import random
MAXRESS = 120 # Arbitrary maximum
class RESS:
"""
RESS class represents the equivalent of the Pascal unit 'ress' in Python.
Random Equal Stimulus Sets are sequences of numbers indicating one of
a set of stimuli to be presented to a subject in a cognitive or
psychophysics task. The basic rules for generating these sequences is
derived from Gellermann 1925(?), but modified to permit the
specification of more than two stimuli in the set. The restriction on
a maximum of three sequential presentations of the same stimulus is
retained.
"""
def __init__(self):
self.classes = None
self.thelength = None
self.series = [0] * MAXRESS
self.lastseries = [0] * MAXRESS
self.cnt = None
self.seriesstr = ""
self.current = None
self.dummy = None
self.hist = [0] * 61
def init(self):
"""
Initializes the variables in TRESS.
"""
self.classes = 1
self.thelength = 0
self.series = [0] * MAXRESS
self.lastseries = [0] * MAXRESS
self.hist = [0] * 61
self.cnt = 0
self.seriesstr = ""
self.dummy = 0
def makestring(self):
"""
Creates a string representation of the series.
Returns:
The string representation of the series.
"""
tstr = ""
for val in self.series[1:self.thelength + 1]:
tstr += str(val)
self.seriesstr = tstr
return tstr
def generate(self, len, nclass):
"""
Generates a candidate series.
Args:
len: The length of the series.
nclass: The number of classes.
"""
self.cnt = 0
self.classes = nclass
# Constraint: sequence length less than maximum
if MAXRESS >= len:
self.thelength = len
else:
self.thelength = MAXRESS
# Constraint: Multiple of number of classes
if self.thelength % self.classes != 0:
self.thelength -= self.thelength % self.classes
for i in range(self.classes):
self.hist[i] = self.thelength // self.classes
self.series[0] = random.randint(0, self.classes - 1)
self.hist[self.series[0]] -= 1
run = 1
for i in range(1, self.thelength):
ctr = 0
while True:
ctr += 1
jj = random.randint(0, self.classes - 1)
if self.hist[jj] > 0:
shortrun = (self.series[i - 1] == jj and run < 3) or (self.series[i - 1] != jj)
break
if ctr > 100:
break
if self.series[i - 1] == jj:
run += 1
else:
run = 1
self.hist[jj] -= 1
self.series[i] = jj
def test(self):
"""
Tests candidates for criteria.
Returns:
True if the series is valid, False otherwise.
"""
ok = True
hist = [0] * 61
for val in self.series[:self.thelength]:
hist[val] += 1
for i in range(self.classes - 1):
if hist[i] != hist[i + 1]:
ok = False
if ok:
run = 1
for i in range(1, self.thelength):
if self.series[i - 1] == self.series[i]:
run += 1
if run > 3:
ok = False
else:
run = 1
return ok
def newress(self, nlen=24, nclass=2):
"""
Finds and saves a valid series using generate and test.
Args:
nlen: The length of the series.
nclass: The number of classes.
"""
print('nlen', nlen, 'nclass', nclass)
try:
random.seed()
self.lastseries = self.series
while True:
self.generate(nlen, nclass)
# print("gen", self.makestring())
if self.test():
break
return self.makestring()
except:
estr = f"Error: {traceback.format_exc()}"
print(estr)
return ''
def next(self):
"""
Returns the next value within a series.
Returns:
The next value in the series.
"""
if self.cnt >= self.thelength:
self.newress(self.thelength, self.classes)
self.cnt += 1
self.current = self.series[self.cnt]
return self.series[self.cnt]
def next_yield(self):
"""
Yields the next value within a series.
"""
print('start', self.series, self.cnt, self.series[self.cnt])
while True:
if self.cnt >= self.thelength:
print("calling newress")
self.newress(self.thelength, self.classes)
self.cnt = 0
print(self.cnt)
print(self.series, self.cnt, self.series[self.cnt])
self.current = self.series[self.cnt]
yield str(self.current)
self.cnt += 1
# Exercise the TRESS code
from random import seed
def main():
# Set the seed for random number generation
seed()
# Create an instance of the TRESS class
ress1 = RESS()
# Initialize the TRESS object
ress1.init()
# Generate and print a valid series
ress1.newress(24, 3)
series = ress1.makestring()
print("Generated Series:", series)
ress1.newress(24, 3)
series = ress1.makestring()
print("Generated Series:", series)
ress1.newress(24, 3)
series = ress1.makestring()
print("Generated Series:", series)
ress1.newress(24, 3)
series = ress1.makestring()
print("Generated Series:", series)
ress1.newress(24, 3)
series = ress1.makestring()
print("Generated Series:", series)
# Generate and print the next value in the series
for ii in range(26):
next_val = ress1.next()
print(ii, "Next Value:", str(next_val))
if __name__ == "__main__":
main()