<commit_before># Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for sequence data preprocessing utils."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from math import ceil

import numpy as np

from tensorflow.python import keras
from tensorflow.python.platform import test

class TestSequence(test.TestCase):

  def test_pad_sequences(self):
    a = [[1], [1, 2], [1, 2, 3]]

    # test padding
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='pre')
    self.assertAllClose(b, [[0, 0, 1], [0, 1, 2], [1, 2, 3]])
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='post')
    self.assertAllClose(b, [[1, 0, 0], [1, 2, 0], [1, 2, 3]])

    # test truncating
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='pre')
    self.assertAllClose(b, [[0, 1], [1, 2], [2, 3]])
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='post')
    self.assertAllClose(b, [[0, 1], [1, 2], [1, 2]])

    # test value
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, value=1)
    self.assertAllClose(b, [[1, 1, 1], [1, 1, 2], [1, 2, 3]])

  def test_pad_sequences_vector(self):
    a = [[[1, 1]], [[2, 1], [2, 2]], [[3, 1], [3, 2], [3, 3]]]

    # test padding
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='pre')
    self.assertAllClose(b, [[[0, 0], [0, 0], [1, 1]], [[0, 0], [2, 1], [2, 2]],
                            [[3, 1], [3, 2], [3, 3]]])
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='post')
    self.assertAllClose(b, [[[1, 1], [0, 0], [0, 0]], [[2, 1], [2, 2], [0, 0]],
                            [[3, 1], [3, 2], [3, 3]]])

    # test truncating
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='pre')
    self.assertAllClose(b, [[[0, 0], [1, 1]], [[2, 1], [2, 2]], [[3, 2], [3,
                                                                          3]]])

    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='post')
    self.assertAllClose(b, [[[0, 0], [1, 1]], [[2, 1], [2, 2]], [[3, 1], [3,
                                                                          2]]])

    # test value
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, value=1)
    self.assertAllClose(b, [[[1, 1], [1, 1], [1, 1]], [[1, 1], [2, 1], [2, 2]],
                            [[3, 1], [3, 2], [3, 3]]])

  def test_make_sampling_table(self):
    a = keras.preprocessing.sequence.make_sampling_table(3)
    self.assertAllClose(
        a, np.asarray([0.00315225, 0.00315225, 0.00547597]), rtol=.1)

  def test_skipgrams(self):
    # test with no window size and binary labels
    couples, labels = keras.preprocessing.sequence.skipgrams(
        np.arange(3), vocabulary_size=3)
    for couple in couples:
      self.assertIn(couple[0], [0, 1, 2])
      self.assertIn(couple[1], [0, 1, 2])

    # test window size and categorical labels
    couples, labels = keras.preprocessing.sequence.skipgrams(
        np.arange(5), vocabulary_size=5, window_size=1, categorical=True)
    for couple in couples:
      self.assertLessEqual(couple[0] - couple[1], 3)
    for l in labels:
      self.assertEqual(len(l), 2)

  def test_TimeseriesGenerator(self):
    data = np.array([[i] for i in range(50)])
    targets = np.array([[i] for i in range(50)])

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, batch_size=2)
    self.assertEqual(len(data_gen), 20)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[0], [2], [4], [6], [8]], [[1], [3], [5],
                                                              [7], [9]]]))
    self.assertAllClose(data_gen[0][1], np.array([[10], [11]]))
    self.assertAllClose(data_gen[1][0],
                        np.array([[[2], [4], [6], [8], [10]], [[3], [5], [7],
                                                               [9], [11]]]))
    self.assertAllClose(data_gen[1][1], np.array([[12], [13]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, reverse=True, batch_size=2)
    self.assertEqual(len(data_gen), 20)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[8], [6], [4], [2], [0]], [[9], [7], [5],
                                                              [3], [1]]]))
    self.assertAllClose(data_gen[0][1], np.array([[10], [11]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, shuffle=True, batch_size=1)
    batch = data_gen[0]
    r = batch[1][0][0]
    self.assertAllClose(batch[0],
                        np.array([[[r - 10], [r - 8], [r - 6], [r - 4],
                                   [r - 2]]]))
    self.assertAllClose(batch[1], np.array([
        [r],
    ]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, stride=2, batch_size=2)
    self.assertEqual(len(data_gen), 10)
    self.assertAllClose(data_gen[1][0],
                        np.array([[[4], [6], [8], [10], [12]], [[6], [8], [10],
                                                                [12], [14]]]))
    self.assertAllClose(data_gen[1][1], np.array([[14], [16]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data,
        targets,
        length=10,
        sampling_rate=2,
        start_index=10,
        end_index=30,
        batch_size=2)
    self.assertEqual(len(data_gen), 6)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[10], [12], [14], [16], [18]],
                                  [[11], [13], [15], [17], [19]]]))
    self.assertAllClose(data_gen[0][1], np.array([[20], [21]]))

    data = np.array([np.random.random_sample((1, 2, 3, 4)) for i in range(50)])
    targets = np.array([np.random.random_sample((3, 2, 1)) for i in range(50)])
    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data,
        targets,
        length=10,
        sampling_rate=2,
        start_index=10,
        end_index=30,
        batch_size=2)

    self.assertEqual(len(data_gen), 6)
    self.assertAllClose(data_gen[0][0],
                        np.array(
                            [np.array(data[10:19:2]),
                             np.array(data[11:20:2])]))
    self.assertAllClose(data_gen[0][1], np.array([targets[20], targets[21]]))

    with self.assertRaises(ValueError) as context:
      keras.preprocessing.sequence.TimeseriesGenerator(data, targets, length=50)
    error = str(context.exception)
    self.assertIn('`start_index+length=50 > end_index=49` is disallowed', error)

  def test_TimeSeriesGenerator_doesnt_miss_any_sample(self):
    x = np.array([[i] for i in range(10)])

    for length in range(3, 10):
      g = keras.preprocessing.sequence.TimeseriesGenerator(
          x, x, length=length, batch_size=1)
      expected = max(0, len(x) - length)
      actual = len(g)
      self.assertEqual(expected, actual)

      if actual > 0:
        # All elements in range(length, 10) should be used as current step
        expected = np.arange(length, 10).reshape(-1, 1)

        y = np.concatenate([g[ix][1] for ix in range(len(g))], axis=0)
        self.assertAllClose(y, expected)

    x = np.array([[i] for i in range(23)])

    strides = (1, 1, 5, 7, 3, 5, 3)
    lengths = (3, 3, 4, 3, 1, 3, 7)
    batch_sizes = (6, 6, 6, 5, 6, 6, 6)
    shuffles = (False, True, True, False, False, False, False)

    for stride, length, batch_size, shuffle in zip(strides, lengths,
                                                   batch_sizes, shuffles):
      g = keras.preprocessing.sequence.TimeseriesGenerator(
          x,
          x,
          length=length,
          sampling_rate=1,
          stride=stride,
          start_index=0,
          end_index=None,
          shuffle=shuffle,
          reverse=False,
          batch_size=batch_size)
      if shuffle:
        # all batches have the same size when shuffle is True.
        expected_sequences = ceil(
            (23 - length) / float(batch_size * stride)) * batch_size
      else:
        # last batch will be different if `(samples - length) / stride`
        # is not a multiple of `batch_size`.
        expected_sequences = ceil((23 - length) / float(stride))

      expected_batches = ceil(expected_sequences / float(batch_size))

      y = [g[ix][1] for ix in range(len(g))]

      actual_sequences = sum(len(iy) for iy in y)
      actual_batches = len(y)

      self.assertEqual(expected_sequences, actual_sequences)
      self.assertEqual(expected_batches, actual_batches)

if __name__ == '__main__':
  test.main()
<commit_msg>TF Keras sequence_test additional test cases covers 1- remove_long_seq api test case<commit_after># Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for sequence data preprocessing utils."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from math import ceil

import numpy as np

from tensorflow.python import keras
from tensorflow.python.platform import test

class TestSequence(test.TestCase):

  def test_pad_sequences(self):
    a = [[1], [1, 2], [1, 2, 3]]

    # test padding
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='pre')
    self.assertAllClose(b, [[0, 0, 1], [0, 1, 2], [1, 2, 3]])
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='post')
    self.assertAllClose(b, [[1, 0, 0], [1, 2, 0], [1, 2, 3]])

    # test truncating
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='pre')
    self.assertAllClose(b, [[0, 1], [1, 2], [2, 3]])
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='post')
    self.assertAllClose(b, [[0, 1], [1, 2], [1, 2]])

    # test value
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, value=1)
    self.assertAllClose(b, [[1, 1, 1], [1, 1, 2], [1, 2, 3]])

  def test_pad_sequences_vector(self):
    a = [[[1, 1]], [[2, 1], [2, 2]], [[3, 1], [3, 2], [3, 3]]]

    # test padding
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='pre')
    self.assertAllClose(b, [[[0, 0], [0, 0], [1, 1]], [[0, 0], [2, 1], [2, 2]],
                            [[3, 1], [3, 2], [3, 3]]])
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, padding='post')
    self.assertAllClose(b, [[[1, 1], [0, 0], [0, 0]], [[2, 1], [2, 2], [0, 0]],
                            [[3, 1], [3, 2], [3, 3]]])

    # test truncating
    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='pre')
    self.assertAllClose(b, [[[0, 0], [1, 1]], [[2, 1], [2, 2]], [[3, 2], [3,
                                                                          3]]])

    b = keras.preprocessing.sequence.pad_sequences(
        a, maxlen=2, truncating='post')
    self.assertAllClose(b, [[[0, 0], [1, 1]], [[2, 1], [2, 2]], [[3, 1], [3,
                                                                          2]]])

    # test value
    b = keras.preprocessing.sequence.pad_sequences(a, maxlen=3, value=1)
    self.assertAllClose(b, [[[1, 1], [1, 1], [1, 1]], [[1, 1], [2, 1], [2, 2]],
                            [[3, 1], [3, 2], [3, 3]]])

  def test_make_sampling_table(self):
    a = keras.preprocessing.sequence.make_sampling_table(3)
    self.assertAllClose(
        a, np.asarray([0.00315225, 0.00315225, 0.00547597]), rtol=.1)

  def test_skipgrams(self):
    # test with no window size and binary labels
    couples, labels = keras.preprocessing.sequence.skipgrams(
        np.arange(3), vocabulary_size=3)
    for couple in couples:
      self.assertIn(couple[0], [0, 1, 2])
      self.assertIn(couple[1], [0, 1, 2])

    # test window size and categorical labels
    couples, labels = keras.preprocessing.sequence.skipgrams(
        np.arange(5), vocabulary_size=5, window_size=1, categorical=True)
    for couple in couples:
      self.assertLessEqual(couple[0] - couple[1], 3)
    for l in labels:
      self.assertEqual(len(l), 2)

  def test_remove_long_seq(self):
    a = [[[1, 1]], [[2, 1], [2, 2]], [[3, 1], [3, 2], [3, 3]]]

    new_seq, new_label = keras.preprocessing.sequence._remove_long_seq(
        maxlen=3, seq=a, label=['a', 'b', ['c', 'd']])
    self.assertEqual(new_seq, [[[1, 1]], [[2, 1], [2, 2]]])
    self.assertEqual(new_label, ['a', 'b'])

  def test_TimeseriesGenerator(self):
    data = np.array([[i] for i in range(50)])
    targets = np.array([[i] for i in range(50)])

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, batch_size=2)
    self.assertEqual(len(data_gen), 20)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[0], [2], [4], [6], [8]], [[1], [3], [5],
                                                              [7], [9]]]))
    self.assertAllClose(data_gen[0][1], np.array([[10], [11]]))
    self.assertAllClose(data_gen[1][0],
                        np.array([[[2], [4], [6], [8], [10]], [[3], [5], [7],
                                                               [9], [11]]]))
    self.assertAllClose(data_gen[1][1], np.array([[12], [13]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, reverse=True, batch_size=2)
    self.assertEqual(len(data_gen), 20)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[8], [6], [4], [2], [0]], [[9], [7], [5],
                                                              [3], [1]]]))
    self.assertAllClose(data_gen[0][1], np.array([[10], [11]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, shuffle=True, batch_size=1)
    batch = data_gen[0]
    r = batch[1][0][0]
    self.assertAllClose(batch[0],
                        np.array([[[r - 10], [r - 8], [r - 6], [r - 4],
                                   [r - 2]]]))
    self.assertAllClose(batch[1], np.array([
        [r],
    ]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data, targets, length=10, sampling_rate=2, stride=2, batch_size=2)
    self.assertEqual(len(data_gen), 10)
    self.assertAllClose(data_gen[1][0],
                        np.array([[[4], [6], [8], [10], [12]], [[6], [8], [10],
                                                                [12], [14]]]))
    self.assertAllClose(data_gen[1][1], np.array([[14], [16]]))

    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data,
        targets,
        length=10,
        sampling_rate=2,
        start_index=10,
        end_index=30,
        batch_size=2)
    self.assertEqual(len(data_gen), 6)
    self.assertAllClose(data_gen[0][0],
                        np.array([[[10], [12], [14], [16], [18]],
                                  [[11], [13], [15], [17], [19]]]))
    self.assertAllClose(data_gen[0][1], np.array([[20], [21]]))

    data = np.array([np.random.random_sample((1, 2, 3, 4)) for i in range(50)])
    targets = np.array([np.random.random_sample((3, 2, 1)) for i in range(50)])
    data_gen = keras.preprocessing.sequence.TimeseriesGenerator(
        data,
        targets,
        length=10,
        sampling_rate=2,
        start_index=10,
        end_index=30,
        batch_size=2)

    self.assertEqual(len(data_gen), 6)
    self.assertAllClose(data_gen[0][0],
                        np.array(
                            [np.array(data[10:19:2]),
                             np.array(data[11:20:2])]))
    self.assertAllClose(data_gen[0][1], np.array([targets[20], targets[21]]))

    with self.assertRaises(ValueError) as context:
      keras.preprocessing.sequence.TimeseriesGenerator(data, targets, length=50)
    error = str(context.exception)
    self.assertIn('`start_index+length=50 > end_index=49` is disallowed', error)

  def test_TimeSeriesGenerator_doesnt_miss_any_sample(self):
    x = np.array([[i] for i in range(10)])

    for length in range(3, 10):
      g = keras.preprocessing.sequence.TimeseriesGenerator(
          x, x, length=length, batch_size=1)
      expected = max(0, len(x) - length)
      actual = len(g)
      self.assertEqual(expected, actual)

      if actual > 0:
        # All elements in range(length, 10) should be used as current step
        expected = np.arange(length, 10).reshape(-1, 1)

        y = np.concatenate([g[ix][1] for ix in range(len(g))], axis=0)
        self.assertAllClose(y, expected)

    x = np.array([[i] for i in range(23)])

    strides = (1, 1, 5, 7, 3, 5, 3)
    lengths = (3, 3, 4, 3, 1, 3, 7)
    batch_sizes = (6, 6, 6, 5, 6, 6, 6)
    shuffles = (False, True, True, False, False, False, False)

    for stride, length, batch_size, shuffle in zip(strides, lengths,
                                                   batch_sizes, shuffles):
      g = keras.preprocessing.sequence.TimeseriesGenerator(
          x,
          x,
          length=length,
          sampling_rate=1,
          stride=stride,
          start_index=0,
          end_index=None,
          shuffle=shuffle,
          reverse=False,
          batch_size=batch_size)
      if shuffle:
        # all batches have the same size when shuffle is True.
        expected_sequences = ceil(
            (23 - length) / float(batch_size * stride)) * batch_size
      else:
        # last batch will be different if `(samples - length) / stride`
        # is not a multiple of `batch_size`.
        expected_sequences = ceil((23 - length) / float(stride))

      expected_batches = ceil(expected_sequences / float(batch_size))

      y = [g[ix][1] for ix in range(len(g))]

      actual_sequences = sum(len(iy) for iy in y)
      actual_batches = len(y)

      self.assertEqual(expected_sequences, actual_sequences)
      self.assertEqual(expected_batches, actual_batches)

if __name__ == '__main__':
  test.main()
<|endoftext|>


<commit_before>"""
Simple functions operating on a matrix, potentially modifying it.

These are useful for neuron output functions, normalization of
matrices, etc.

All of these function objects (callable objects) should work for
Numeric array arguments of arbitrary shape.  Some may also work for
scalars.

$Id$
"""
__version__='$Revision$'

import Numeric
import copy
from Numeric import dot, exp

from topo.base.arrayutils import clip_in_place,clip_lower
from topo.base.arrayutils import L2norm, norm
from topo.base.functionfamilies import OutputFn
from topo.base.parameterclasses import Number
from topo.base.parameterizedobject import ParameterizedObject

# Imported here so that all OutputFns will be in the same package
from topo.base.functionfamilies import IdentityOF

# CEBHACKALERT: these need to respect the mask - which will be passed in.

class PiecewiseLinear(OutputFn):
    """
    Piecewise-linear OutputFn with lower and upper thresholds.

    Values below the lower_threshold are set to zero, those above
    the upper threshold are set to 1.0, and those in between are
    scaled linearly.
    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))
    upper_bound = Number(default=1.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        fact = 1.0/(self.upper_bound-self.lower_bound)
        x -= self.lower_bound
        x *= fact
        clip_in_place(x,0.0,1.0)

class Sigmoid(OutputFn):
    """
    Sigmoidal output function - (From Mathworld A Wolfram Web Resource. http://mathworld.wolfram.com/SigmoidFunction.html)
    Parameters k and l control the gradient (k) and the x-position (l) of
    the exponential in the sigmoid function ie. 1/1+exp-(k*x+l)
    """
    k = Number(default=13,doc="Multiplicative parameter controlling the exponential.")
    l = Number(default=-4,doc="Additive parameter controlling the exponential.")

    def __call__(self,x):

        x_orig = copy.copy(x)
        x *= 0.0
	x += 1.0 / (1.0 + exp(-(self.k*x_orig + self.l)))

class DivisiveNormalizeL1(OutputFn):
    """
    OutputFn that divides an array by its L1 norm.

    This operation ensures that the sum of the absolute values of the
    array is equal to the specified norm_value, rescaling each value
    to make this true.  The array is unchanged if the sum of absolute
    values is zero.  For arrays of non-negative values where at least
    one is non-zero, this operation is equivalent to a divisive sum
    normalization.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        """L1-normalize the input array, if it has a nonzero sum."""
        current_sum = 1.0*Numeric.sum(abs(x.flat))
        if current_sum != 0:
            factor = (self.norm_value/current_sum)
            x *= factor

class DivisiveNormalizeL2(OutputFn):
    """
    OutputFn to divide an array by its Euclidean length (aka its L2 norm).

    For a given array interpreted as a flattened vector, keeps the
    Euclidean length of the vector at a specified norm_value.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*L2norm(x.flat)
        if tot != 0:
            factor = (self.norm_value/tot)
            x *= factor

class DivisiveNormalizeLinf(OutputFn):
    """
    OutputFn to divide an array by its L-infinity norm
    (i.e. the maximum absolute value of its elements).

    For a given array interpreted as a flattened vector, scales the
    elements divisively so that the maximum absolute value is the
    specified norm_value.

    The L-infinity norm is also known as the divisive infinity norm
    and Chebyshev norm.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*max(abs(x.flat))
        if tot != 0:
            factor = (self.norm_value/tot)
            x *= factor

class DivisiveNormalizeLp(OutputFn):
    """
    OutputFn to divide an array by its Lp-Norm, where p is specified.

    For a parameter p and a given array interpreted as a flattened
    vector, keeps the Lp-norm of the vector at a specified norm_value.
    Faster versions are provided separately for the typical L1-norm
    and L2-norm cases.  Defaults to be the same as an L2-norm, i.e.,
    DivisiveNormalizeL2.
    """
    p = Number(default=2)
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*norm(x.flat,self.p)
        if tot != 0:
            factor = (self.norm_value/tot)
            x *=factor

class HalfRectifyAndSquare(OutputFn):
    """
    Output function that applies a half-wave rectification (clips at zero)
    and then squares the values.
    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        clip_lower(x,self.lower_bound)
        x *= x

class HalfRectify(OutputFn):
    """
    Output function that applies a half-wave rectification (clips at zero)

    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        clip_lower(x,self.lower_bound)

class Square(OutputFn):
    """
    Output function that applies a squaring nonlinearity

    """

    def __call__(self,x):

        x *= x

class BinaryThreshold(OutputFn):
    """
    Forces all values below a threshold to zero, and above it to 1.0.
    """
    threshold = Number(default=0.25, doc="Decision point for determining binary value.")

    def __call__(self,x):
        above_threshold = x>=self.threshold
        x *= 0.0
        x += above_threshold

<commit_msg>Minor cleanup.<commit_after>"""
Simple functions operating on a matrix, potentially modifying it.

These are useful for neuron output functions, normalization of
matrices, etc.

All of these function objects (callable objects) should work for
Numeric array arguments of arbitrary shape.  Some may also work for
scalars.

$Id$
"""
__version__='$Revision$'

import Numeric
import copy
from Numeric import dot, exp

from topo.base.arrayutils import clip_in_place,clip_lower
from topo.base.arrayutils import L2norm, norm
from topo.base.functionfamilies import OutputFn
from topo.base.parameterclasses import Number
from topo.base.parameterizedobject import ParameterizedObject

# Imported here so that all OutputFns will be in the same package
from topo.base.functionfamilies import IdentityOF

# CEBHACKALERT: these need to respect the mask - which will be passed in.

class PiecewiseLinear(OutputFn):
    """
    Piecewise-linear OutputFn with lower and upper thresholds.

    Values below the lower_threshold are set to zero, those above
    the upper threshold are set to 1.0, and those in between are
    scaled linearly.
    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))
    upper_bound = Number(default=1.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        fact = 1.0/(self.upper_bound-self.lower_bound)
        x -= self.lower_bound
        x *= fact
        clip_in_place(x,0.0,1.0)

class Sigmoid(OutputFn):
    """
    Sigmoidal (logistic) output function 1/(1+exp-(k*x+l)).

    Definition taken from Mathworld, A Wolfram Web
    Resource, http://mathworld.wolfram.com/SigmoidFunction.html.
    Parameters k and l control the gradient (k) and the x-position (l)
    of the exponential.
    """
    k = Number(default=13,doc="Multiplicative parameter controlling the exponential.")
    l = Number(default=-4,doc="Additive parameter controlling the exponential.")

    def __call__(self,x):

        x_orig = copy.copy(x)
        x *= 0.0
	x += 1.0 / (1.0 + exp(-(self.k*x_orig + self.l)))

class DivisiveNormalizeL1(OutputFn):
    """
    OutputFn that divides an array by its L1 norm.

    This operation ensures that the sum of the absolute values of the
    array is equal to the specified norm_value, rescaling each value
    to make this true.  The array is unchanged if the sum of absolute
    values is zero.  For arrays of non-negative values where at least
    one is non-zero, this operation is equivalent to a divisive sum
    normalization.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        """L1-normalize the input array, if it has a nonzero sum."""
        current_sum = 1.0*Numeric.sum(abs(x.flat))
        if current_sum != 0:
            factor = (self.norm_value/current_sum)
            x *= factor

class DivisiveNormalizeL2(OutputFn):
    """
    OutputFn to divide an array by its Euclidean length (aka its L2 norm).

    For a given array interpreted as a flattened vector, keeps the
    Euclidean length of the vector at a specified norm_value.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*L2norm(x.flat)
        if tot != 0:
            factor = (self.norm_value/tot)
            x *= factor

class DivisiveNormalizeLinf(OutputFn):
    """
    OutputFn to divide an array by its L-infinity norm
    (i.e. the maximum absolute value of its elements).

    For a given array interpreted as a flattened vector, scales the
    elements divisively so that the maximum absolute value is the
    specified norm_value.

    The L-infinity norm is also known as the divisive infinity norm
    and Chebyshev norm.
    """
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*max(abs(x.flat))
        if tot != 0:
            factor = (self.norm_value/tot)
            x *= factor

class DivisiveNormalizeLp(OutputFn):
    """
    OutputFn to divide an array by its Lp-Norm, where p is specified.

    For a parameter p and a given array interpreted as a flattened
    vector, keeps the Lp-norm of the vector at a specified norm_value.
    Faster versions are provided separately for the typical L1-norm
    and L2-norm cases.  Defaults to be the same as an L2-norm, i.e.,
    DivisiveNormalizeL2.
    """
    p = Number(default=2)
    norm_value = Number(default=1.0)

    def __call__(self,x):
        tot = 1.0*norm(x.flat,self.p)
        if tot != 0:
            factor = (self.norm_value/tot)
            x *=factor

class HalfRectifyAndSquare(OutputFn):
    """
    Output function that applies a half-wave rectification (clips at zero)
    and then squares the values.
    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        clip_lower(x,self.lower_bound)
        x *= x

class HalfRectify(OutputFn):
    """
    Output function that applies a half-wave rectification (clips at zero)

    """
    lower_bound = Number(default=0.0,softbounds=(0.0,1.0))

    def __call__(self,x):
        clip_lower(x,self.lower_bound)

class Square(OutputFn):
    """
    Output function that applies a squaring nonlinearity

    """

    def __call__(self,x):

        x *= x

class BinaryThreshold(OutputFn):
    """
    Forces all values below a threshold to zero, and above it to 1.0.
    """
    threshold = Number(default=0.25, doc="Decision point for determining binary value.")

    def __call__(self,x):
        above_threshold = x>=self.threshold
        x *= 0.0
        x += above_threshold

<|endoftext|>


<commit_before>"""
Test breakpoint conditions with 'breakpoint modify -c <expr> id'.
"""

import os, time
import re
import unittest2
import lldb, lldbutil
from lldbtest import *

class BreakpointConditionsTestCase(TestBase):

    mydir = "breakpoint_conditions"

    @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
    def test_with_dsym_and_run_command(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        self.buildDsym()
        self.breakpoint_conditions()

    @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
    def test_with_dsym_and_python_api(self):
        """Use Python APIs to set breakpoint conditions."""
        self.buildDsym()
        self.breakpoint_conditions_python()

    def test_with_dwarf_and_run_command(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        self.buildDwarf()
        self.breakpoint_conditions()

    def test_with_dwarf_and_python_api(self):
        """Use Python APIs to set breakpoint conditions."""
        self.buildDwarf()
        self.breakpoint_conditions_python()

    def setUp(self):
        # Call super's setUp().
        TestBase.setUp(self)
        # Find the line number to of function 'c'.
        self.line1 = line_number('main.c', '// Find the line number of function "c" here.')
        self.line2 = line_number('main.c', "// Find the line number of c's parent call here.")

    def breakpoint_conditions(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        exe = os.path.join(os.getcwd(), "a.out")
        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)

        # Create a breakpoint by function name 'c'.
        self.expect("breakpoint set -n c", BREAKPOINT_CREATED,
            startstr = "Breakpoint created: 1: name = 'c', locations = 1")

        # And set a condition on the breakpoint to stop on when 'val == 3'.
        self.runCmd("breakpoint modify -c 'val == 3' 1")

        # Now run the program.
        self.runCmd("run", RUN_SUCCEEDED)

        # 'frame variable -t val' should return 3 due to breakpoint condition.
        self.expect("frame variable -t val", VARIABLES_DISPLAYED_CORRECTLY,
            startstr = '(int) val = 3')

        # Also check the hit count, which should be 3, by design.
        self.expect("breakpoint list", BREAKPOINT_HIT_THRICE,
            substrs = ["resolved = 1",
                       "Condition: val == 3",
                       "hit count = 3"])

        # The frame #0 should correspond to main.c:36, the executable statement
        # in function name 'c'.  And the parent frame should point to main.c:24.
        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT_CONDITION,
            #substrs = ["stop reason = breakpoint"],
            patterns = ["frame #0.*main.c:%d" % self.line1,
                        "frame #1.*main.c:%d" % self.line2])

    def breakpoint_conditions_python(self):
        """Use Python APIs to set breakpoint conditions."""
        exe = os.path.join(os.getcwd(), "a.out")

        # Create a target by the debugger.
        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target.IsValid(), VALID_TARGET)

        # Now create a breakpoint on main.c by name 'c'.
        breakpoint = target.BreakpointCreateByName('c', 'a.out')
        print "breakpoint:", breakpoint
        self.assertTrue(breakpoint.IsValid() and
                        breakpoint.GetNumLocations() == 1,
                        VALID_BREAKPOINT)

        # Get the breakpoint location from breakpoint after we verified that,
        # indeed, it has one location.
        location = breakpoint.GetLocationAtIndex(0)
        self.assertTrue(location.IsValid() and
                        location.IsEnabled(),
                        VALID_BREAKPOINT_LOCATION)

        # Set the condition on the breakpoint location.
        location.SetCondition('val == 3')
        self.expect(location.GetCondition(), exe=False,
            startstr = 'val == 3')

        # Now launch the process, and do not stop at entry point.
        self.process = target.LaunchProcess([''], [''], os.ctermid(), 0, False)

        self.process = target.GetProcess()
        self.assertTrue(self.process.IsValid(), PROCESS_IS_VALID)

        # Frame #0 should be on self.line1 and the break condition should hold.
        frame0 = self.process.GetThreadAtIndex(0).GetFrameAtIndex(0)
        var = frame0.LookupVarInScope('val', 'parameter')
        self.assertTrue(frame0.GetLineEntry().GetLine() == self.line1 and
                        var.GetValue(frame0) == '3')

        # The hit count for the breakpoint should be 3.
        self.assertTrue(breakpoint.GetHitCount() == 3)

        self.process.Continue()

if __name__ == '__main__':
    import atexit
    lldb.SBDebugger.Initialize()
    atexit.register(lambda: lldb.SBDebugger.Terminate())
    unittest2.main()
<commit_msg>Comment out the debug statement for printing the SBBreakpoint object.<commit_after>"""
Test breakpoint conditions with 'breakpoint modify -c <expr> id'.
"""

import os, time
import re
import unittest2
import lldb, lldbutil
from lldbtest import *

class BreakpointConditionsTestCase(TestBase):

    mydir = "breakpoint_conditions"

    @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
    def test_with_dsym_and_run_command(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        self.buildDsym()
        self.breakpoint_conditions()

    @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
    def test_with_dsym_and_python_api(self):
        """Use Python APIs to set breakpoint conditions."""
        self.buildDsym()
        self.breakpoint_conditions_python()

    def test_with_dwarf_and_run_command(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        self.buildDwarf()
        self.breakpoint_conditions()

    def test_with_dwarf_and_python_api(self):
        """Use Python APIs to set breakpoint conditions."""
        self.buildDwarf()
        self.breakpoint_conditions_python()

    def setUp(self):
        # Call super's setUp().
        TestBase.setUp(self)
        # Find the line number to of function 'c'.
        self.line1 = line_number('main.c', '// Find the line number of function "c" here.')
        self.line2 = line_number('main.c', "// Find the line number of c's parent call here.")

    def breakpoint_conditions(self):
        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
        exe = os.path.join(os.getcwd(), "a.out")
        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)

        # Create a breakpoint by function name 'c'.
        self.expect("breakpoint set -n c", BREAKPOINT_CREATED,
            startstr = "Breakpoint created: 1: name = 'c', locations = 1")

        # And set a condition on the breakpoint to stop on when 'val == 3'.
        self.runCmd("breakpoint modify -c 'val == 3' 1")

        # Now run the program.
        self.runCmd("run", RUN_SUCCEEDED)

        # 'frame variable -t val' should return 3 due to breakpoint condition.
        self.expect("frame variable -t val", VARIABLES_DISPLAYED_CORRECTLY,
            startstr = '(int) val = 3')

        # Also check the hit count, which should be 3, by design.
        self.expect("breakpoint list", BREAKPOINT_HIT_THRICE,
            substrs = ["resolved = 1",
                       "Condition: val == 3",
                       "hit count = 3"])

        # The frame #0 should correspond to main.c:36, the executable statement
        # in function name 'c'.  And the parent frame should point to main.c:24.
        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT_CONDITION,
            #substrs = ["stop reason = breakpoint"],
            patterns = ["frame #0.*main.c:%d" % self.line1,
                        "frame #1.*main.c:%d" % self.line2])

    def breakpoint_conditions_python(self):
        """Use Python APIs to set breakpoint conditions."""
        exe = os.path.join(os.getcwd(), "a.out")

        # Create a target by the debugger.
        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target.IsValid(), VALID_TARGET)

        # Now create a breakpoint on main.c by name 'c'.
        breakpoint = target.BreakpointCreateByName('c', 'a.out')
        #print "breakpoint:", breakpoint
        self.assertTrue(breakpoint.IsValid() and
                        breakpoint.GetNumLocations() == 1,
                        VALID_BREAKPOINT)

        # Get the breakpoint location from breakpoint after we verified that,
        # indeed, it has one location.
        location = breakpoint.GetLocationAtIndex(0)
        self.assertTrue(location.IsValid() and
                        location.IsEnabled(),
                        VALID_BREAKPOINT_LOCATION)

        # Set the condition on the breakpoint location.
        location.SetCondition('val == 3')
        self.expect(location.GetCondition(), exe=False,
            startstr = 'val == 3')

        # Now launch the process, and do not stop at entry point.
        self.process = target.LaunchProcess([''], [''], os.ctermid(), 0, False)

        self.process = target.GetProcess()
        self.assertTrue(self.process.IsValid(), PROCESS_IS_VALID)

        # Frame #0 should be on self.line1 and the break condition should hold.
        frame0 = self.process.GetThreadAtIndex(0).GetFrameAtIndex(0)
        var = frame0.LookupVarInScope('val', 'parameter')
        self.assertTrue(frame0.GetLineEntry().GetLine() == self.line1 and
                        var.GetValue(frame0) == '3')

        # The hit count for the breakpoint should be 3.
        self.assertTrue(breakpoint.GetHitCount() == 3)

        self.process.Continue()

if __name__ == '__main__':
    import atexit
    lldb.SBDebugger.Initialize()
    atexit.register(lambda: lldb.SBDebugger.Terminate())
    unittest2.main()
<|endoftext|>


<commit_before>"""
Plot panels to support GUI windows that display bitmap
plots. Related classes include Plot, PlotGroup, and PlotEngine.

BasicPlotPanel is the smallest class possible to create a new plot
window.  Look at it as an example of creating additional subclasses.

$Id$
"""
__version__='$Revision $'

import Pmw, re, os, sys
from Tkinter import Frame, TOP, YES, BOTH, BOTTOM, X, Button, LEFT, \
     RIGHT, DISABLED, Checkbutton, NORMAL, Canvas, Label, NSEW, IntVar, StringVar
import topo
import topo.base.topoobject
import topo.plotting.bitmap
import topo.base.simulator as simulator
import topo.plotting.plotengine as plotengine
import topo.base.registry as registry
import topo.plotting.plotgroup
import PIL
import Image
import ImageTk
import Numeric
import MLab

### JCALERT: the name has been changed from PlotPanel to PlotGroupPanel
### I think we might also want to change the name of the subclasses (e.g. UnitWeightPanel...)
### also, the file can be renamed plotgrouppanel.py (carefull the import statement in others files
### also have to be replaced

class PlotGroupPanel(Frame,topo.base.topoobject.TopoObject):
    """
    Abstract PlotGroupPanel class for displaying bitmapped images to a TK
    GUI window.  Must be subclassed to be usable.
    """

    @staticmethod
    def valid_context():
        """
        Test if this panel should be allowed to be opened by the TopoConsole.

        Given the existing Simulator definitions has no GeneratorSheets, or
        no ProjectionSheets, some panels should not be opened.  TopoConsole
        will call this function before creating an instance.
        """
        return True
#    valid_context = MakeStaticFunction(valid_context)

    def __init__(self,parent,pengine,console,plot_key,pgt_name=None,
                 plotgroup_type='BasicPlotGroup',**config):
        """
        parent:  it is the window (GUIToplevel()) that contains the panel.
        pengine: is the associated PlotEngine; it might end up to be unnecessary, needs fixing.
        console: is the associated console, (i.e. the TopoConsole that has this panel)
        plot_key: defines the title of the panel.
                  In the case of an activity plot or a feature map plot the title
                  is only the name of template (pgt_name)
                  In the case of projection and unit weights there is additional information
                  that are added by the method generate_plt_key (e.g. density, unit coordinates...)
        pgt_name: name of the PlotGroupTemplate associated with the panel
        plot_group_type: type of the PlotGroup associated with the panel

        """

        Frame.__init__(self,parent,config)
        topo.plotting.plot.TopoObject.__init__(self,**config)

        ### Usually the pgt_name is the plot_key by default,
        ### but for inputparamspanel pgt_name = None and the plot_key is 'Preview'
        ### passed by default when creating the class
        if pgt_name!=None:
            self.plot_key = pgt_name
        else:
            self.plot_key=plot_key

        self.plotgroup_type = plotgroup_type
        self.pgt_name = pgt_name  #Plot Group Template name

        ### JABHACKALERT!
        ###
        ### When the GeneratorSheet.density is changed to a relatively
        ### large but not unreasonable number, e.g. 512*512, the plot
        ### window is many times larger than the screen, and there's
        ### no way to reach the Reduce button.  In general, there need
        ### to be scrollbars for viewing plots too large for the
        ### screen.
        ###
        ### In this particular case, there appears to be a bug in how
        ### INITIAL_PLOT_WIDTH is handled, because with a size of 512
        ### a fixed INITIAL_PLOT_WIDTH of 60 shouldn't have any effect
        ### at all.  Yet changing INITIAL_PLOT_WIDTH to 1 fixes the
        ### size problem, and makes the images fit on screen with no
        ### problem.  Very strange.  My guess for the culprit is the line:
        ###
        ###   self.zoom_factor = int(self.INITIAL_PLOT_WIDTH/min_width) + 1
        ###
        ### (below), which doesn't seem at all a correct way to
        ### enforce a minimum width and an initial width.

        # Each plot can have a different minimum size.  If INITIAL_PLOT_WIDTH
        # is set to None, then no initial zooming is performed.  However,
        # MIN_PLOT_WIDTH may cause a zoom if the raw bitmap is still too
        # tiny.
        self.MIN_PLOT_WIDTH = 1
        self.INITIAL_PLOT_WIDTH = 60

        self.pe = pengine
        self.pe_group = None
        self.plots = []
        self.bitmaps = []
        self.labels = []
        self.__num_labels = 0

        self.console = console
        self.parent = parent
        self.balloon = Pmw.Balloon(parent)
        self.canvases = []

        # I think we can get rid of that
        #self.panel_num = self.console.num_activity_windows

        self.shared_control_frame = Frame(self)
        self.shared_control_frame.pack(side=TOP,expand=YES,fill=X)

        # JAB: Because these three buttons are present in nearly every
        # window, and aren't particularly important, we should
        # probably use small icons for them instead of text.  That way
        # they will form a visual group that users can typically
        # ignore.  Of course, the icons will need to announce their
        # names as help text if the mouse lingers over them, so that
        # the user can figure them out the first time.
        #
        # Refresh, Reduce, and Enlarge Buttons.
        Button(self.shared_control_frame,text="Refresh",
               command=self.refresh).pack(side=LEFT)
        self.reduce_button = Button(self.shared_control_frame,text="Reduce",
                                   state=DISABLED,
                                   command=self.reduce)
        self.reduce_button.pack(side=LEFT)
        Button(self.shared_control_frame,text="Enlarge",
               command=self.enlarge).pack(side=LEFT)

        # Default is to not have the window Auto-refresh, because some
        # plots are very slow to generate (e.g. some preference map
        # plots).  Call self.auto_refresh_checkbutton.invoke() to
        # enable autorefresh in a subclassed constructor function.
        self.auto_refresh = 0
        self.auto_refresh_checkbutton = Checkbutton(self.shared_control_frame,
                                                    text="Auto-refresh",
                                                    command=self.toggle_auto_refresh)
        self.auto_refresh_checkbutton.pack(side=LEFT)
        self.auto_refresh_checkbutton.invoke()

        # Normalization check button.
        pgt = registry.plotgroup_templates[self.pgt_name]
        if pgt:
            ### JABALERT! Why is it checking the first template for this?
            self.normalize = pgt.plot_templates[0].channels.get('Normalize',False)
            self.normalize_checkbutton = Checkbutton(self.shared_control_frame,
                                                     text="Normalize",
                                                     command=self.toggle_normalize)
            if self.normalize:
                self.normalize_checkbutton.select()
            self.normalize_checkbutton.pack(side=LEFT)

        # Main Plot group title can be changed from a subclass with the
        # command: self.plot_group.configure(tag_text='NewName')
        self.plot_group = Pmw.Group(self,tag_text=str(self.plot_key))
        self.plot_group.pack(side=TOP,expand=YES,fill=BOTH,padx=5,pady=5)
        self.plot_frame = self.plot_group.interior()

        # For the first plot, use the INITIAL_PLOT_WIDTH to calculate zoom.
        self.initial_plot = True
        self.zoom_factor = self.min_zoom_factor = 1

        self.control_frame = Frame(self)
        self.control_frame.pack(side=TOP,expand=YES,fill=X)

        # self.refresh()

    def toggle_normalize(self):
        """Function called by Widget when check-box clicked"""
        self.normalize = not self.normalize
        pgt = registry.plotgroup_templates[self.pgt_name]
        for (k,each) in pgt.plot_templates:
            each.channels['Normalize'] = self.normalize
        self.refresh()

    def refresh(self,extra=None):
        """
        Main steps for generating plots in the Frame.  These functions
        must either be implemented, or overwritten.
        """
        Pmw.showbusycursor()
        self.do_plot_cmd()                # Create plot tuples
        self.load_images()                # Scale bitmap images
        self.display_plots()              # Put images in GUI canvas
        self.display_labels()             # Match labels to grid
        self.refresh_title()              # Update Frame title.
        Pmw.hidebusycursor()

    def refresh_title(self):
        """
        Change the window title.  TopoConsole will call this on
        startup of window.
        """
        self.parent.title("%s time:%s" % (self.plot_key,self.pe.simulation.time()))

    def do_plot_cmd(self):
        """
        Subclasses of PlotGroupPanel will need to create this function to
        generate the plots.

        See UnitWeightsPanel and ProjectionPanel for
        examples.
        """
        self.pe_group = self.pe.get_plot_group(self.plot_key,
                                               self.plotgroup_type)
        self.pe_group.do_plot_cmd()
        self.plots = self.pe_group.plots()

    def load_images(self):
        """
        Pre:  self.pe_group contains a PlotGroup
              self.plots contains a list of plots following the
                  format provided by PlotGroup.plots()
        Post: self.bitmaps contains a list of Bitmap Images ready for display.

        No geometry or Sheet information is necessary to perform the
        operations in this function, so it is unlikely that load_images()
        will need to be redefined from a subclass.  It is assumed that
        the PlotGroup code has not scaled the bitmap to the size currently
        desired by the GUI.

        Image scaling is automatically done, as well as adjusted by the
        user.  self.MIN_PLOT_WIDTH is the number of pixels wide the
        smallest plot figure can be, and self.INITIAL_PLOT_WIDTH is the
        number of pixels wide the smallest plot figure will be on the
        first display.
        """
        # need to calculate the old min width, so we know if we need to reset
        # the zoom factors
        if self.bitmaps:
            old_min_width = reduce(min,[im.width() for im in self.bitmaps])
        else:
            old_min_width = -1

        if self.pe_group:
            self.bitmaps = self.pe_group.load_images()

        if self.bitmaps:
            min_width = reduce(min,[im.width() for im in self.bitmaps])
        else:
            min_width = 0

        # If the min width changed, then recalculate the zoom factor
        # If no plots, then no window.
        if old_min_width != min_width:
            if self.initial_plot and min_width != 0 and self.INITIAL_PLOT_WIDTH != None:
                self.zoom_factor = int(self.INITIAL_PLOT_WIDTH/min_width) + 1
                if self.zoom_factor < self.min_zoom_factor:
                    self.zoom_factor = self.min_zoom_factor
                self.initial_plot = False
            if self.MIN_PLOT_WIDTH > min_width and min_width != 0:
                self.min_zoom_factor = int(self.MIN_PLOT_WIDTH/min_width) + 1
                if self.zoom_factor < self.min_zoom_factor:
                    self.zoom_factor = self.min_zoom_factor
            else:
                self.min_zoom_factor = 1

            if self.zoom_factor > self.min_zoom_factor:
                self.reduce_button.config(state=NORMAL)
            else:
                self.reduce_button.config(state=DISABLED)

    def display_plots(self):
        """
        Pre:  self.bitmaps contains a list of topo.bitmap objects.

        Post: The bitmaps have been displayed to the screen in the active
              console window.  All images are displayed from left to right,
              in a single row.  If the number of images have changed since
              the last display, the grid size is increased, or decreased
              accordingly.

        This function shuld be redefined in subclasses for interesting
        things such as 2D grids.
        """
        self.zoomed_images = [ImageTk.PhotoImage(im.zoom(self.zoom_factor))
                              for im in self.bitmaps]

        # If the number of canvases or their sizes has changed, then
        # create a new set of canvases.  If the old canvases still can
        # work, then reuse them to prevent flicker.
        if self.canvases and len(self.zoomed_images) > 0:
            new_sizes = [(str(zi.width()),str(zi.height()))
                         for zi in self.zoomed_images]
            old_sizes = [(zi.config()['width'][-1],zi.config()['height'][-1])
                         for zi in self.canvases]
        else:
            new_sizes, old_sizes = 0, 0
        if len(self.zoomed_images) != len(self.canvases) or \
               new_sizes != old_sizes:
            old_canvases = self.canvases
            self.canvases = [Canvas(self.plot_frame,
                                    width=image.width(),
                                    height=image.height(),
                                    bd=0)
                             for image in self.zoomed_images]
            for i,image,canvas in zip(range(len(self.zoomed_images)),
                                      self.zoomed_images,self.canvases):
                canvas.create_image(image.width()/2,image.height()/2,image=image)
                canvas.grid(row=0,column=i,padx=5)
            for c in old_canvases:
                c.grid_forget()
        else:  # Width of first plot still same, and same number of images.
            for i,image,canvas in zip(range(len(self.zoomed_images)),
                                      self.zoomed_images,self.canvases):
                canvas.create_image(image.width()/2,image.height()/2,image=image)
                canvas.grid(row=0,column=i,padx=5)

    def display_labels(self):
        """
        This function should be redefined by subclasses to match any
        changes made to display__plots().  Depending on the situation,
        it may be useful to make this function a stub, and display the
        labels at the same time the images are displayed.
        """
        if self.__num_labels != len(self.canvases):
            old_labels = self.labels
            self.labels = [Label(self.plot_frame,text=each.view_info['src_name'])
                           for each in self.bitmaps]
            for i in range(len(self.labels)):
                self.labels[i].grid(row=1,column=i,sticky=NSEW)
            for l in old_labels:
                l.grid_forget()
            self.__num_labels = len(self.canvases)
        else:  # Same number of labels; reuse to avoid flickering.
            for i in range(len(self.labels)):
                self.labels[i].configure(text=self.bitmaps[i].view_info['src_name'])

    def reduce(self):
        """Function called by Widget to reduce the zoom factor"""
        if self.zoom_factor > self.min_zoom_factor:
            self.zoom_factor = self.zoom_factor - 1

        if self.zoom_factor == self.min_zoom_factor:
            self.reduce_button.config(state=DISABLED)

        self.display_plots()

    def enlarge(self):
        """Function called by Widget to increase the zoom factor"""
        self.reduce_button.config(state=NORMAL)
        self.zoom_factor = self.zoom_factor + 1
        self.display_plots()

    def toggle_auto_refresh(self):
        """Function called by Widget when check-box clicked"""
        self.auto_refresh = not self.auto_refresh

        self.debug("Auto-refresh = ", self.auto_refresh)
        topo.tkgui.show_cmd_prompt()

        if self.auto_refresh:
            self.console.add_auto_refresh_panel(self)
        else:
            self.console.del_auto_refresh_panel(self)

    def destroy(self):
        if self.auto_refresh:
            self.console.del_auto_refresh_panel(self)
        Frame.destroy(self)

<commit_msg>Minor change to the comments.<commit_after>"""
Plot panels to support GUI windows that display bitmap
plots. Related classes include Plot, PlotGroup, and PlotEngine.

BasicPlotPanel is the smallest class possible to create a new plot
window.  Look at it as an example of creating additional subclasses.

$Id$
"""
__version__='$Revision $'

import Pmw, re, os, sys
from Tkinter import Frame, TOP, YES, BOTH, BOTTOM, X, Button, LEFT, \
     RIGHT, DISABLED, Checkbutton, NORMAL, Canvas, Label, NSEW, IntVar, StringVar
import topo
import topo.base.topoobject
import topo.plotting.bitmap
import topo.base.simulator as simulator
import topo.plotting.plotengine as plotengine
import topo.base.registry as registry
import topo.plotting.plotgroup
import PIL
import Image
import ImageTk
import Numeric
import MLab

### JCALERT: the name has been changed from PlotPanel to PlotGroupPanel
### I think we might also want to change the name of the subclasses (e.g. UnitWeightPanel...)
### also, the file can be renamed plotgrouppanel.py (carefull the import statement in others files
### also have to be replaced...)

class PlotGroupPanel(Frame,topo.base.topoobject.TopoObject):
    """
    Abstract PlotGroupPanel class for displaying bitmapped images to a TK
    GUI window.  Must be subclassed to be usable.
    """
    ### JCALERT! What is the use of this method? Will it be used sometimes?
    @staticmethod
    def valid_context():
        """
        Test if this panel should be allowed to be opened by the TopoConsole.

        Given the existing Simulator definitions has no GeneratorSheets, or
        no ProjectionSheets, some panels should not be opened.  TopoConsole
        will call this function before creating an instance.
        """
        return True
#    valid_context = MakeStaticFunction(valid_context)

    def __init__(self,parent,pengine,console,plot_key,pgt_name=None,
                 plotgroup_type='BasicPlotGroup',**config):
        """
        parent:  it is the window (GUIToplevel()) that contains the panel.
        pengine: is the associated PlotEngine; it might end up to be unnecessary, needs fixing.
        console: is the associated console, (i.e. the TopoConsole that has this panel)
        plot_key: defines the title of the panel.
                  In the case of an activity plot or a feature map plot (BasicGroupPanel)
		  the title is only the name of the template (pgt_name)
                  In the case of projection and unit weights there is additional information
                  that are added by the method generate_plt_key (e.g. density, unit coordinates...)
        pgt_name: name of the PlotGroupTemplate associated with the panel
        plot_group_type: type of the PlotGroup associated with the panel

        """

        Frame.__init__(self,parent,config)
        topo.plotting.plot.TopoObject.__init__(self,**config)

        ### Usually the pgt_name is the plot_key by default,
        ### but for inputparamspanel pgt_name = None and the plot_key is 'Preview'
        ### passed by default when creating the class
        if pgt_name != None:
            self.plot_key = pgt_name
        else:
            self.plot_key = plot_key

        self.plotgroup_type = plotgroup_type
        self.pgt_name = pgt_name  #Plot Group Template name

        ### JABHACKALERT!
        ###
        ### When the GeneratorSheet.density is changed to a relatively
        ### large but not unreasonable number, e.g. 512*512, the plot
        ### window is many times larger than the screen, and there's
        ### no way to reach the Reduce button.  In general, there need
        ### to be scrollbars for viewing plots too large for the
        ### screen.
        ###
        ### In this particular case, there appears to be a bug in how
        ### INITIAL_PLOT_WIDTH is handled, because with a size of 512
        ### a fixed INITIAL_PLOT_WIDTH of 60 shouldn't have any effect
        ### at all.  Yet changing INITIAL_PLOT_WIDTH to 1 fixes the
        ### size problem, and makes the images fit on screen with no
        ### problem.  Very strange.  My guess for the culprit is the line:
        ###
        ###   self.zoom_factor = int(self.INITIAL_PLOT_WIDTH/min_width) + 1
        ###
        ### (below), which doesn't seem at all a correct way to
        ### enforce a minimum width and an initial width.

        # Each plot can have a different minimum size.  If INITIAL_PLOT_WIDTH
        # is set to None, then no initial zooming is performed.  However,
        # MIN_PLOT_WIDTH may cause a zoom if the raw bitmap is still too
        # tiny.
        self.MIN_PLOT_WIDTH = 1
        self.INITIAL_PLOT_WIDTH = 60

        self.pe = pengine
        self.pe_group = None
        self.plots = []
        self.bitmaps = []
        self.labels = []
        self.__num_labels = 0

        self.console = console
        self.parent = parent
        self.balloon = Pmw.Balloon(parent)
        self.canvases = []

	self.shared_control_frame = Frame(self)
        self.shared_control_frame.pack(side=TOP,expand=YES,fill=X)

        # JAB: Because these three buttons are present in nearly every
        # window, and aren't particularly important, we should
        # probably use small icons for them instead of text.  That way
        # they will form a visual group that users can typically
        # ignore.  Of course, the icons will need to announce their
        # names as help text if the mouse lingers over them, so that
        # the user can figure them out the first time.
        #
        # Refresh, Reduce, and Enlarge Buttons.
        Button(self.shared_control_frame,text="Refresh",
               command=self.refresh).pack(side=LEFT)
        self.reduce_button = Button(self.shared_control_frame,text="Reduce",
                                   state=DISABLED,
                                   command=self.reduce)
        self.reduce_button.pack(side=LEFT)
        Button(self.shared_control_frame,text="Enlarge",
               command=self.enlarge).pack(side=LEFT)

        # Default is to not have the window Auto-refresh, because some
        # plots are very slow to generate (e.g. some preference map
        # plots).  Call self.auto_refresh_checkbutton.invoke() to
        # enable autorefresh in a subclassed constructor function.
        self.auto_refresh = 0
        self.auto_refresh_checkbutton = Checkbutton(self.shared_control_frame,
                                                    text="Auto-refresh",
                                                    command=self.toggle_auto_refresh)
        self.auto_refresh_checkbutton.pack(side=LEFT)
        self.auto_refresh_checkbutton.invoke()

        # Normalization check button.
        pgt = registry.plotgroup_templates[self.pgt_name]
        if pgt:
            ### JABALERT! Why is it checking the first template for this?
            self.normalize = pgt.plot_templates[0].channels.get('Normalize',False)
            self.normalize_checkbutton = Checkbutton(self.shared_control_frame,
                                                     text="Normalize",
                                                     command=self.toggle_normalize)
            if self.normalize:
                self.normalize_checkbutton.select()
            self.normalize_checkbutton.pack(side=LEFT)

        # Main Plot group title can be changed from a subclass with the
        # command: self.plot_group.configure(tag_text='NewName')
        self.plot_group = Pmw.Group(self,tag_text=str(self.plot_key))
        self.plot_group.pack(side=TOP,expand=YES,fill=BOTH,padx=5,pady=5)
        self.plot_frame = self.plot_group.interior()

        # For the first plot, use the INITIAL_PLOT_WIDTH to calculate zoom.
        self.initial_plot = True
        self.zoom_factor = self.min_zoom_factor = 1

        self.control_frame = Frame(self)
        self.control_frame.pack(side=TOP,expand=YES,fill=X)

        # self.refresh()

    def toggle_normalize(self):
        """Function called by Widget when check-box clicked"""
        self.normalize = not self.normalize
        pgt = registry.plotgroup_templates[self.pgt_name]
        for (k,each) in pgt.plot_templates:
            each.channels['Normalize'] = self.normalize
        self.refresh()

    def refresh(self,extra=None):
        """
        Main steps for generating plots in the Frame.  These functions
        must either be implemented, or overwritten.
        """
        Pmw.showbusycursor()
        self.do_plot_cmd()                # Create plot tuples
        self.load_images()                # Scale bitmap images
        self.display_plots()              # Put images in GUI canvas
        self.display_labels()             # Match labels to grid
        self.refresh_title()              # Update Frame title.
        Pmw.hidebusycursor()

    def refresh_title(self):
        """
        Change the window title.  TopoConsole will call this on
        startup of window.
        """
        self.parent.title("%s time:%s" % (self.plot_key,self.pe.simulation.time()))

    def do_plot_cmd(self):
        """
        Subclasses of PlotGroupPanel will need to create this function to
        generate the plots.

        See UnitWeightsPanel and ProjectionPanel for
        examples.
        """
        self.pe_group = self.pe.get_plot_group(self.plot_key,
                                               self.plotgroup_type)
        self.pe_group.do_plot_cmd()
        self.plots = self.pe_group.plots()

    def load_images(self):
        """
        Pre:  self.pe_group contains a PlotGroup
              self.plots contains a list of plots following the
                  format provided by PlotGroup.plots()
        Post: self.bitmaps contains a list of Bitmap Images ready for display.

        No geometry or Sheet information is necessary to perform the
        operations in this function, so it is unlikely that load_images()
        will need to be redefined from a subclass.  It is assumed that
        the PlotGroup code has not scaled the bitmap to the size currently
        desired by the GUI.

        Image scaling is automatically done, as well as adjusted by the
        user.  self.MIN_PLOT_WIDTH is the number of pixels wide the
        smallest plot figure can be, and self.INITIAL_PLOT_WIDTH is the
        number of pixels wide the smallest plot figure will be on the
        first display.
        """
        # need to calculate the old min width, so we know if we need to reset
        # the zoom factors
        if self.bitmaps:
            old_min_width = reduce(min,[im.width() for im in self.bitmaps])
        else:
            old_min_width = -1

        if self.pe_group:
            self.bitmaps = self.pe_group.load_images()

        if self.bitmaps:
            min_width = reduce(min,[im.width() for im in self.bitmaps])
        else:
            min_width = 0

        # If the min width changed, then recalculate the zoom factor
        # If no plots, then no window.
        if old_min_width != min_width:
            if self.initial_plot and min_width != 0 and self.INITIAL_PLOT_WIDTH != None:
                self.zoom_factor = int(self.INITIAL_PLOT_WIDTH/min_width) + 1
                if self.zoom_factor < self.min_zoom_factor:
                    self.zoom_factor = self.min_zoom_factor
                self.initial_plot = False
            if self.MIN_PLOT_WIDTH > min_width and min_width != 0:
                self.min_zoom_factor = int(self.MIN_PLOT_WIDTH/min_width) + 1
                if self.zoom_factor < self.min_zoom_factor:
                    self.zoom_factor = self.min_zoom_factor
            else:
                self.min_zoom_factor = 1

            if self.zoom_factor > self.min_zoom_factor:
                self.reduce_button.config(state=NORMAL)
            else:
                self.reduce_button.config(state=DISABLED)

    def display_plots(self):
        """
        Pre:  self.bitmaps contains a list of topo.bitmap objects.

        Post: The bitmaps have been displayed to the screen in the active
              console window.  All images are displayed from left to right,
              in a single row.  If the number of images have changed since
              the last display, the grid size is increased, or decreased
              accordingly.

        This function shuld be redefined in subclasses for interesting
        things such as 2D grids.
        """
        self.zoomed_images = [ImageTk.PhotoImage(im.zoom(self.zoom_factor))
                              for im in self.bitmaps]

        # If the number of canvases or their sizes has changed, then
        # create a new set of canvases.  If the old canvases still can
        # work, then reuse them to prevent flicker.
        if self.canvases and len(self.zoomed_images) > 0:
            new_sizes = [(str(zi.width()),str(zi.height()))
                         for zi in self.zoomed_images]
            old_sizes = [(zi.config()['width'][-1],zi.config()['height'][-1])
                         for zi in self.canvases]
        else:
            new_sizes, old_sizes = 0, 0
        if len(self.zoomed_images) != len(self.canvases) or \
               new_sizes != old_sizes:
            old_canvases = self.canvases
            self.canvases = [Canvas(self.plot_frame,
                                    width=image.width(),
                                    height=image.height(),
                                    bd=0)
                             for image in self.zoomed_images]
            for i,image,canvas in zip(range(len(self.zoomed_images)),
                                      self.zoomed_images,self.canvases):
                canvas.create_image(image.width()/2,image.height()/2,image=image)
                canvas.grid(row=0,column=i,padx=5)
            for c in old_canvases:
                c.grid_forget()
        else:  # Width of first plot still same, and same number of images.
            for i,image,canvas in zip(range(len(self.zoomed_images)),
                                      self.zoomed_images,self.canvases):
                canvas.create_image(image.width()/2,image.height()/2,image=image)
                canvas.grid(row=0,column=i,padx=5)

    def display_labels(self):
        """
        This function should be redefined by subclasses to match any
        changes made to display__plots().  Depending on the situation,
        it may be useful to make this function a stub, and display the
        labels at the same time the images are displayed.
        """
        if self.__num_labels != len(self.canvases):
            old_labels = self.labels
            self.labels = [Label(self.plot_frame,text=each.view_info['src_name'])
                           for each in self.bitmaps]
            for i in range(len(self.labels)):
                self.labels[i].grid(row=1,column=i,sticky=NSEW)
            for l in old_labels:
                l.grid_forget()
            self.__num_labels = len(self.canvases)
        else:  # Same number of labels; reuse to avoid flickering.
            for i in range(len(self.labels)):
                self.labels[i].configure(text=self.bitmaps[i].view_info['src_name'])

    def reduce(self):
        """Function called by Widget to reduce the zoom factor"""
        if self.zoom_factor > self.min_zoom_factor:
            self.zoom_factor = self.zoom_factor - 1

        if self.zoom_factor == self.min_zoom_factor:
            self.reduce_button.config(state=DISABLED)

        self.display_plots()

    def enlarge(self):
        """Function called by Widget to increase the zoom factor"""
        self.reduce_button.config(state=NORMAL)
        self.zoom_factor = self.zoom_factor + 1
        self.display_plots()

    def toggle_auto_refresh(self):
        """Function called by Widget when check-box clicked"""
        self.auto_refresh = not self.auto_refresh

        self.debug("Auto-refresh = ", self.auto_refresh)
        topo.tkgui.show_cmd_prompt()

        if self.auto_refresh:
            self.console.add_auto_refresh_panel(self)
        else:
            self.console.del_auto_refresh_panel(self)

    def destroy(self):
        if self.auto_refresh:
            self.console.del_auto_refresh_panel(self)
        Frame.destroy(self)

<|endoftext|>


<commit_before>"""
Transfer functions with more complex dependencies.

$Id: basic.py 10790 2009-11-21 17:51:33Z antolikjan $
"""
__version__='$Revision: 10790 $'

import copy

import numpy, numpy.random
from numpy import ones

import param

import topo
import topo.base.functionfamily

from topo.base.arrayutil import clip_lower,array_argmax
from topo.base.patterngenerator import PatternGenerator,Constant
from topo.base.boundingregion import BoundingBox
from topo.base.sheetcoords import SheetCoordinateSystem

from topo.transferfn.basic import TransferFn,TransferFnWithState
from topo.pattern.basic import Gaussian

# Not suitable for basic.py due to its dependence on patterns.
class PatternCombine(TransferFn):
    """
    Combine the supplied pattern with one generated using a
    PatternGenerator.

    Useful for operations like adding noise or masking out lesioned
    items or around the edges of non-rectangular shapes.
    """
    generator = param.ClassSelector(PatternGenerator,
        default=Constant(), doc="""
        Pattern to combine with the supplied matrix.""")

    operator = param.Parameter(numpy.multiply,precedence=0.98,doc="""
        Binary Numeric function used to combine the two patterns.

        Any binary Numeric array "ufunc" returning the same type of
        array as the operands and supporting the reduce operator is
        allowed here.  See topo.pattern.basic.Composite.operator for
        more details.
        """)

    def __call__(self,x):
        ###JABHACKALERT: Need to set it up to be independent of
        #density; right now only things like random numbers work
        #reasonably
        rows,cols = x.shape
        bb = BoundingBox(points=((0,0), (rows,cols)))
        generated_pattern = self.generator(bounds=bb,xdensity=1,ydensity=1).transpose()
        new_pattern = self.operator(x, generated_pattern)
        x *= 0.0
        x += new_pattern

# Not suitable for basic.py due to its dependence on patterns.
class KernelMax(TransferFn):
    """
    Replaces the given matrix with a kernel function centered around the maximum value.

    This operation is usually part of the Kohonen SOM algorithm, and
    approximates a series of lateral interactions resulting in a
    single activity bubble.

    The radius of the kernel (i.e. the surround) is specified by the
    parameter 'radius', which should be set before using __call__.
    The shape of the surround is determined by the
    neighborhood_kernel_generator, and can be any PatternGenerator
    instance, or any function accepting bounds, density, radius, and
    height to return a kernel matrix.
    """
    kernel_radius = param.Number(default=0.0,bounds=(0,None),doc="""
        Kernel radius in Sheet coordinates.""")

    neighborhood_kernel_generator = param.ClassSelector(PatternGenerator,
        default=Gaussian(x=0.0,y=0.0,aspect_ratio=1.0),
        doc="Neighborhood function")

    crop_radius_multiplier = param.Number(default=3.0,doc="""
        Factor by which the radius should be multiplied, when deciding
        how far from the winner to keep evaluating the kernel.""")

    density=param.Number(1.0,bounds=(0,None),doc="""
        Density of the Sheet whose matrix we act on, for use
        in converting from matrix to Sheet coordinates.""")

    def __call__(self,x):
        rows,cols = x.shape
        radius = self.density*self.kernel_radius
        crop_radius = int(max(1.25,radius*self.crop_radius_multiplier))

        # find out the matrix coordinates of the winner
        wr,wc = array_argmax(x)

        # convert to sheet coordinates
        wy = rows-wr-1

        # Optimization: Calculate the bounding box around the winner
        # in which weights will be changed
        cmin = max(wc-crop_radius,  0)
        cmax = min(wc+crop_radius+1,cols)
        rmin = max(wr-crop_radius,  0)
        rmax = min(wr+crop_radius+1,rows)
        ymin = max(wy-crop_radius,  0)
        ymax = min(wy+crop_radius+1,rows)
        bb = BoundingBox(points=((cmin,ymin), (cmax,ymax)))

        # generate the kernel matrix and insert it into the correct
        # part of the output array
        kernel = self.neighborhood_kernel_generator(bounds=bb,xdensity=1,ydensity=1,
                                                    size=2*radius,x=wc+0.5,y=wy+0.5)
        x *= 0.0
        x[rmin:rmax,cmin:cmax] = kernel

class HalfRectify(TransferFn):
    """
    Transfer function that applies a half-wave rectification (clips at zero)
    """
    t_init = param.Number(default=0.0,doc="""The initial value of threshold at which output becomes non-zero..""")

    gain = param.Number(default=1.0,doc="""The neuronal gain""")

    randomized_init = param.Boolean(False,doc="""
        Whether to randomize the initial t parameter.""")

    noise_magnitude =  param.Number(default=0.1,doc="""
        The magnitude of the additive noise to apply to the t_init
        parameter at initialization.""")

    def __init__(self,**params):
        super(TransferFn,self).__init__(**params)
        self.first_call = True

    def __call__(self,x):
        if self.first_call:
            self.first_call = False
            if self.randomized_init:
                self.t = ones(x.shape, x.dtype.char) * self.t_init + \
		(topo.pattern.random.UniformRandom()(xdensity=x.shape[0],ydensity=x.shape[1])-0.5)*self.noise_magnitude*2
            else:
                self.t = ones(x.shape, x.dtype.char) * self.t_init

        x -= self.t
        clip_lower(x,0)
        x *= self.gain

class HomeostaticResponse(TransferFnWithState):
    """
    Adapts the parameters of a linear threshold function to maintain a
    constant desired average activity.
    """

    target_activity = param.Number(default=0.024,doc="""
        The target average activity.""")

    linear_slope = param.Number(default=1.0,doc="""
        Slope of the linear portion above threshold.""")

    t_init = param.Number(default=0.15,doc="""
        Initial value of the threshold.""")

    learning_rate = param.Number(default=0.001,doc="""
        Learning rate for homeostatic plasticity.""")

    smoothing = param.Number(default=0.999,doc="""
        Weighting of previous activity vs. current activity when
        calculating the average.""")

    randomized_init = param.Boolean(False,doc="""
        Whether to randomize the initial t parameter.""")

    noise_magnitude =  param.Number(default=0.1,doc="""
        The magnitude of the additive noise to apply to the t_init
        parameter at initialization.""")

    def __init__(self,**params):
        super(HomeostaticResponse,self).__init__(**params)
        self.first_call = True
        self.__current_state_stack=[]
        self.t=None     # To allow state_push at init
        self.y_avg=None # To allow state_push at init

    def __call__(self,x):
        if self.first_call:
            self.first_call = False
            if self.randomized_init:
                # CEBALERT: UniformRandom's seed should be available
                # as a parameter.
                self.t = ones(x.shape, x.dtype.char) * self.t_init + \
                         (topo.pattern.random.UniformRandom() \
                          (xdensity=x.shape[0],ydensity=x.shape[1]) \
                          -0.5)*self.noise_magnitude*2
            else:
                self.t = ones(x.shape, x.dtype.char) * self.t_init

            self.y_avg = ones(x.shape, x.dtype.char) * self.target_activity

        x_orig = copy.copy(x)
        x -= self.t
        clip_lower(x,0)
        x *= self.linear_slope

        if self.plastic & (float(topo.sim.time()) % 1.0 >= 0.54):
            self.y_avg = (1.0-self.smoothing)*x + self.smoothing*self.y_avg
            self.t += self.learning_rate * (self.y_avg - self.target_activity)

    def state_push(self):
        self.__current_state_stack.append((copy.copy(self.t),
                                           copy.copy(self.y_avg),
                                           copy.copy(self.first_call)))
        super(HomeostaticResponse, self).state_push()

    def state_pop(self):
        self.t, self.y_avg, self.first_call = self.__current_state_stack.pop()
        super(HomeostaticResponse, self).state_pop()

class AttributeTrackingTF(TransferFnWithState):
    """
    Keeps track of attributes of a specified Parameterized over time, for analysis or plotting.

    Useful objects to track include sheets (e.g. "topo.sim['V1']"),
    projections ("topo.sim['V1'].projections['LateralInhibitory']"),
    or an output_function.

    Any attribute whose value is a matrix the same size as the
    activity matrix can be tracked.  Only specified units within this
    matrix will be tracked.

    If no object is specified, this function will keep track of the
    incoming activity over time.

    The results are stored in a dictionary named 'values', as (time,
    value) pairs indexed by the parameter name and unit.  For
    instance, if the value of attribute 'x' is v for unit (0.0,0.0)
    at time t, values['x'][(0.0,0.0)]=(t,v).

    Updating of the tracked values can be disabled temporarily using
    the plastic parameter.
    """

    # ALERT: Need to make this read-only, because it can't be changed
    # after instantiation unless _object is also changed.  Or else
    # need to make _object update whenever object is changed and
    # _object has already been set.
    object = param.Parameter(default=None, doc="""
        Parameterized instance whose parameters will be tracked.

        If this parameter's value is a string, it will be evaluated first
        (by calling Python's eval() function).  This feature is designed to
        allow circular references, so that the OF can track the object that
        owns it, without causing problems for recursive traversal (as for
        script_repr()).""")
    # There may be some way to achieve the above without using eval(), which would be better.
    #JLALERT When using this function snapshots cannot be saved because of problem with eval()

    attrib_names = param.List(default=[], doc="""
        List of names of the function object's parameters that should be stored.""")

    units = param.List(default=[(0.0,0.0)], doc="""
        Sheet coordinates of the unit(s) for which parameter values will be stored.""")

    step = param.Number(default=1, doc="""
        How often to update the tracked values.

        For instance, step=1 means to update them every time this OF is
        called; step=2 means to update them every other time.""")

    coordframe = param.Parameter(default=None,doc="""
        The SheetCoordinateSystem to use to convert the position
        into matrix coordinates. If this parameter's value is a string,
        it will be evaluated first(by calling Python's eval() function).
        This feature is designed to allow circular references,
        so that the OF can track the object that
        owns it, without causing problems for recursive traversal (as for
        script_repr()).""")

    def __init__(self,**params):
        super(AttributeTrackingTF,self).__init__(**params)
        self.values={}
        self.n_step = 0
        self._object=None
        self._coordframe=None
        for p in self.attrib_names:
            self.values[p]={}
            for u in self.units:
                self.values[p][u]=[]

    def __call__(self,x):

        if self._object==None:
            if isinstance(self.object,str):
                self._object=eval(self.object)
            else:
                self._object=self.object

        if self._coordframe == None:
            if isinstance(self.coordframe,str) and isinstance(self._object,SheetCoordinateSystem):
                raise ValueError(str(self._object)+"is already a coordframe, no need to specify coordframe")
            elif isinstance(self._object,SheetCoordinateSystem):
                self._coordframe=self._object
            elif isinstance(self.coordframe,str):
                self._coordframe=eval(self.coordframe)
            else:
                raise ValueError("A coordinate frame (e.g. coordframe=topo.sim['V1']) must be specified in order to track"+str(self._object))

        #collect values on each appropriate step
        self.n_step += 1

        if self.n_step == self.step:
            self.n_step = 0
            if self.plastic:
                for p in self.attrib_names:
                    if p=="x":
                        value_matrix=x
                    else:
                        value_matrix= getattr(self._object, p)

                    for u in self.units:
                        mat_u=self._coordframe.sheet2matrixidx(u[0],u[1])
                        self.values[p][u].append((topo.sim.time(),value_matrix[mat_u]))

__all__ = list(set([k for k,v in locals().items() if isinstance(v,type) and issubclass(v,TransferFn)]))

<commit_msg>Added ALERT.<commit_after>"""
Transfer functions with more complex dependencies.

$Id: basic.py 10790 2009-11-21 17:51:33Z antolikjan $
"""
__version__='$Revision: 10790 $'

import copy

import numpy, numpy.random
from numpy import ones

import param

import topo
import topo.base.functionfamily

from topo.base.arrayutil import clip_lower,array_argmax
from topo.base.patterngenerator import PatternGenerator,Constant
from topo.base.boundingregion import BoundingBox
from topo.base.sheetcoords import SheetCoordinateSystem

from topo.transferfn.basic import TransferFn,TransferFnWithState
from topo.pattern.basic import Gaussian

# Not suitable for basic.py due to its dependence on patterns.
class PatternCombine(TransferFn):
    """
    Combine the supplied pattern with one generated using a
    PatternGenerator.

    Useful for operations like adding noise or masking out lesioned
    items or around the edges of non-rectangular shapes.
    """
    generator = param.ClassSelector(PatternGenerator,
        default=Constant(), doc="""
        Pattern to combine with the supplied matrix.""")

    operator = param.Parameter(numpy.multiply,precedence=0.98,doc="""
        Binary Numeric function used to combine the two patterns.

        Any binary Numeric array "ufunc" returning the same type of
        array as the operands and supporting the reduce operator is
        allowed here.  See topo.pattern.basic.Composite.operator for
        more details.
        """)

    def __call__(self,x):
        ###JABHACKALERT: Need to set it up to be independent of
        #density; right now only things like random numbers work
        #reasonably
        rows,cols = x.shape
        bb = BoundingBox(points=((0,0), (rows,cols)))
        generated_pattern = self.generator(bounds=bb,xdensity=1,ydensity=1).transpose()
        new_pattern = self.operator(x, generated_pattern)
        x *= 0.0
        x += new_pattern

# Not suitable for basic.py due to its dependence on patterns.
class KernelMax(TransferFn):
    """
    Replaces the given matrix with a kernel function centered around the maximum value.

    This operation is usually part of the Kohonen SOM algorithm, and
    approximates a series of lateral interactions resulting in a
    single activity bubble.

    The radius of the kernel (i.e. the surround) is specified by the
    parameter 'radius', which should be set before using __call__.
    The shape of the surround is determined by the
    neighborhood_kernel_generator, and can be any PatternGenerator
    instance, or any function accepting bounds, density, radius, and
    height to return a kernel matrix.
    """
    kernel_radius = param.Number(default=0.0,bounds=(0,None),doc="""
        Kernel radius in Sheet coordinates.""")

    neighborhood_kernel_generator = param.ClassSelector(PatternGenerator,
        default=Gaussian(x=0.0,y=0.0,aspect_ratio=1.0),
        doc="Neighborhood function")

    crop_radius_multiplier = param.Number(default=3.0,doc="""
        Factor by which the radius should be multiplied, when deciding
        how far from the winner to keep evaluating the kernel.""")

    density=param.Number(1.0,bounds=(0,None),doc="""
        Density of the Sheet whose matrix we act on, for use
        in converting from matrix to Sheet coordinates.""")

    def __call__(self,x):
        rows,cols = x.shape
        radius = self.density*self.kernel_radius
        crop_radius = int(max(1.25,radius*self.crop_radius_multiplier))

        # find out the matrix coordinates of the winner
        wr,wc = array_argmax(x)

        # convert to sheet coordinates
        wy = rows-wr-1

        # Optimization: Calculate the bounding box around the winner
        # in which weights will be changed
        cmin = max(wc-crop_radius,  0)
        cmax = min(wc+crop_radius+1,cols)
        rmin = max(wr-crop_radius,  0)
        rmax = min(wr+crop_radius+1,rows)
        ymin = max(wy-crop_radius,  0)
        ymax = min(wy+crop_radius+1,rows)
        bb = BoundingBox(points=((cmin,ymin), (cmax,ymax)))

        # generate the kernel matrix and insert it into the correct
        # part of the output array
        kernel = self.neighborhood_kernel_generator(bounds=bb,xdensity=1,ydensity=1,
                                                    size=2*radius,x=wc+0.5,y=wy+0.5)
        x *= 0.0
        x[rmin:rmax,cmin:cmax] = kernel

class HalfRectify(TransferFn):
    """
    Transfer function that applies a half-wave rectification (clips at zero)
    """
    t_init = param.Number(default=0.0,doc="""The initial value of threshold at which output becomes non-zero..""")

    gain = param.Number(default=1.0,doc="""The neuronal gain""")

    randomized_init = param.Boolean(False,doc="""
        Whether to randomize the initial t parameter.""")

    noise_magnitude =  param.Number(default=0.1,doc="""
        The magnitude of the additive noise to apply to the t_init
        parameter at initialization.""")

    def __init__(self,**params):
        super(TransferFn,self).__init__(**params)
        self.first_call = True

    def __call__(self,x):
        if self.first_call:
            self.first_call = False
            if self.randomized_init:
                self.t = ones(x.shape, x.dtype.char) * self.t_init + \
		(topo.pattern.random.UniformRandom()(xdensity=x.shape[0],ydensity=x.shape[1])-0.5)*self.noise_magnitude*2
            else:
                self.t = ones(x.shape, x.dtype.char) * self.t_init

        x -= self.t
        clip_lower(x,0)
        x *= self.gain

class HomeostaticResponse(TransferFnWithState):
    """
    Adapts the parameters of a linear threshold function to maintain a
    constant desired average activity.
    """

    target_activity = param.Number(default=0.024,doc="""
        The target average activity.""")

    linear_slope = param.Number(default=1.0,doc="""
        Slope of the linear portion above threshold.""")

    t_init = param.Number(default=0.15,doc="""
        Initial value of the threshold.""")

    learning_rate = param.Number(default=0.001,doc="""
        Learning rate for homeostatic plasticity.""")

    smoothing = param.Number(default=0.999,doc="""
        Weighting of previous activity vs. current activity when
        calculating the average.""")

    randomized_init = param.Boolean(False,doc="""
        Whether to randomize the initial t parameter.""")

    noise_magnitude =  param.Number(default=0.1,doc="""
        The magnitude of the additive noise to apply to the t_init
        parameter at initialization.""")

    def __init__(self,**params):
        super(HomeostaticResponse,self).__init__(**params)
        self.first_call = True
        self.__current_state_stack=[]
        self.t=None     # To allow state_push at init
        self.y_avg=None # To allow state_push at init

    def __call__(self,x):
        if self.first_call:
            self.first_call = False
            if self.randomized_init:
                # CEBALERT: UniformRandom's seed should be available
                # as a parameter.
                self.t = ones(x.shape, x.dtype.char) * self.t_init + \
                         (topo.pattern.random.UniformRandom() \
                          (xdensity=x.shape[0],ydensity=x.shape[1]) \
                          -0.5)*self.noise_magnitude*2
            else:
                self.t = ones(x.shape, x.dtype.char) * self.t_init

            self.y_avg = ones(x.shape, x.dtype.char) * self.target_activity

        x_orig = copy.copy(x)
        x -= self.t
        clip_lower(x,0)
        x *= self.linear_slope

        # CEBALERT: this line is at best confusing; needs to be
        # commented or simplified!
        if self.plastic & (float(topo.sim.time()) % 1.0 >= 0.54):
            self.y_avg = (1.0-self.smoothing)*x + self.smoothing*self.y_avg
            self.t += self.learning_rate * (self.y_avg - self.target_activity)

    def state_push(self):
        self.__current_state_stack.append((copy.copy(self.t),
                                           copy.copy(self.y_avg),
                                           copy.copy(self.first_call)))
        super(HomeostaticResponse, self).state_push()

    def state_pop(self):
        self.t, self.y_avg, self.first_call = self.__current_state_stack.pop()
        super(HomeostaticResponse, self).state_pop()

class AttributeTrackingTF(TransferFnWithState):
    """
    Keeps track of attributes of a specified Parameterized over time, for analysis or plotting.

    Useful objects to track include sheets (e.g. "topo.sim['V1']"),
    projections ("topo.sim['V1'].projections['LateralInhibitory']"),
    or an output_function.

    Any attribute whose value is a matrix the same size as the
    activity matrix can be tracked.  Only specified units within this
    matrix will be tracked.

    If no object is specified, this function will keep track of the
    incoming activity over time.

    The results are stored in a dictionary named 'values', as (time,
    value) pairs indexed by the parameter name and unit.  For
    instance, if the value of attribute 'x' is v for unit (0.0,0.0)
    at time t, values['x'][(0.0,0.0)]=(t,v).

    Updating of the tracked values can be disabled temporarily using
    the plastic parameter.
    """

    # ALERT: Need to make this read-only, because it can't be changed
    # after instantiation unless _object is also changed.  Or else
    # need to make _object update whenever object is changed and
    # _object has already been set.
    object = param.Parameter(default=None, doc="""
        Parameterized instance whose parameters will be tracked.

        If this parameter's value is a string, it will be evaluated first
        (by calling Python's eval() function).  This feature is designed to
        allow circular references, so that the OF can track the object that
        owns it, without causing problems for recursive traversal (as for
        script_repr()).""")
    # There may be some way to achieve the above without using eval(), which would be better.
    #JLALERT When using this function snapshots cannot be saved because of problem with eval()

    attrib_names = param.List(default=[], doc="""
        List of names of the function object's parameters that should be stored.""")

    units = param.List(default=[(0.0,0.0)], doc="""
        Sheet coordinates of the unit(s) for which parameter values will be stored.""")

    step = param.Number(default=1, doc="""
        How often to update the tracked values.

        For instance, step=1 means to update them every time this OF is
        called; step=2 means to update them every other time.""")

    coordframe = param.Parameter(default=None,doc="""
        The SheetCoordinateSystem to use to convert the position
        into matrix coordinates. If this parameter's value is a string,
        it will be evaluated first(by calling Python's eval() function).
        This feature is designed to allow circular references,
        so that the OF can track the object that
        owns it, without causing problems for recursive traversal (as for
        script_repr()).""")

    def __init__(self,**params):
        super(AttributeTrackingTF,self).__init__(**params)
        self.values={}
        self.n_step = 0
        self._object=None
        self._coordframe=None
        for p in self.attrib_names:
            self.values[p]={}
            for u in self.units:
                self.values[p][u]=[]

    def __call__(self,x):

        if self._object==None:
            if isinstance(self.object,str):
                self._object=eval(self.object)
            else:
                self._object=self.object

        if self._coordframe == None:
            if isinstance(self.coordframe,str) and isinstance(self._object,SheetCoordinateSystem):
                raise ValueError(str(self._object)+"is already a coordframe, no need to specify coordframe")
            elif isinstance(self._object,SheetCoordinateSystem):
                self._coordframe=self._object
            elif isinstance(self.coordframe,str):
                self._coordframe=eval(self.coordframe)
            else:
                raise ValueError("A coordinate frame (e.g. coordframe=topo.sim['V1']) must be specified in order to track"+str(self._object))

        #collect values on each appropriate step
        self.n_step += 1

        if self.n_step == self.step:
            self.n_step = 0
            if self.plastic:
                for p in self.attrib_names:
                    if p=="x":
                        value_matrix=x
                    else:
                        value_matrix= getattr(self._object, p)

                    for u in self.units:
                        mat_u=self._coordframe.sheet2matrixidx(u[0],u[1])
                        self.values[p][u].append((topo.sim.time(),value_matrix[mat_u]))

__all__ = list(set([k for k,v in locals().items() if isinstance(v,type) and issubclass(v,TransferFn)]))

<|endoftext|>