def collect(value, sample_args, context=None):
    """Collect terms in an unsimplified polynomial."""
    is_question = context is None
    if context is None:
        context = composition.Context()

    entropy, sample_args = sample_args.peel()
    if value is None:
        entropy_value, entropy = entropy * np.random.dirichlet([2, 3])
        degrees = [random.randint(1, 3)]
        value = composition.Polynomial(
            polynomials.sample_coefficients(degrees, entropy_value))

    assert isinstance(value, composition.Polynomial)
    coefficients = value.coefficients

    all_coefficients_are_integer = True
    for coeff in coefficients.flat:
        if not number.is_integer(coeff):
            all_coefficients_are_integer = False
            break

    if all_coefficients_are_integer:
        coefficients = polynomials.expand_coefficients(coefficients, entropy)
    else:
        # put back the unused entropy
        sample_args = composition.SampleArgs(sample_args.num_modules,
                                             sample_args.entropy + entropy)

    num_variables = coefficients.ndim
    variables = [sympy.Symbol(context.pop()) for _ in range(num_variables)]
    unsimplified = polynomials.coefficients_to_polynomial(
        coefficients, variables)
    simplified = unsimplified.sympy().expand()

    # Bit of a hack: handle the very rare case where no number constants appearing
    if not ops.number_constants(unsimplified):
        unsimplified = ops.Add(unsimplified, ops.Constant(0))
    context.sample_by_replacing_constants(sample_args, unsimplified)

    if is_question:
        template = 'Sederhanakan {unsimplified}.'
        return example.Problem(question=example.question(
            context, template, unsimplified=unsimplified),
                               answer=simplified)
    else:
        function_symbol = context.pop()
        function = sympy.Function(function_symbol)(*variables)
        return composition.Entity(
            context=context,
            value=value,
            handle=composition.FunctionHandle(function_symbol),
            expression=unsimplified,
            polynomial_variables=variables,
            description='Misalkan {function} = {unsimplified}.',
            function=function,
            unsimplified=unsimplified)
def _value_sampler(value):
  """Returns sampler (e.g., number.integer) appropriate for `value`."""
  if value == _INT or number.is_integer(value):
    return functools.partial(number.integer, signed=True)
  if value == _INT_OR_RATIONAL or isinstance(value, sympy.Rational):
    return functools.partial(number.integer_or_rational, signed=True)
  if isinstance(value, display.Decimal):
    return functools.partial(number.integer_or_decimal, signed=True)
  raise ValueError('Unrecognized value {} of type {}'
                   .format(value, type(value)))
def integers_with_sum(value, count, entropy):
  """Returns list of integers with a given sum.

  Args:
    value: Target value.
    count: Integer >= 1; the number of integers to use.
    entropy: Entropy to use (in total).

  Returns:
    List of numbers summing to `value`.

  Raises:
    ValueError: If `value` is not an integer.
  """
  # Special cases.
  if count == 0:
    assert value == 0
    assert entropy == 0
    return []
  if count == 1:
    assert entropy == 0
    return [value]

  if not number.is_integer(value):
    raise ValueError('value={} (type={}) is not an integer'
                     .format(value, type(value)))

  # Because e.g., (1, 1) and (2, 2) will both map to the same set of integers
  # when we normalize to have sum equal to `value`.
  entropy *= count / (count - 1)

  min_term_entropy = max(
      1, number.entropy_of_value(int(math.ceil(value/count))))
  term_entropies = entropy * np.random.dirichlet(np.ones(count))
  term_entropies = np.maximum(min_term_entropy, term_entropies)

  terms = [number.integer(term_entropy, signed=True)
           for term_entropy in term_entropies]

  delta = value - sum(terms)
  deltas = _split_value_equally(delta, count)
  terms = [term + delta for term, delta in zip(terms, deltas)]
  random.shuffle(terms)
  return terms
def is_integer_polynomial(value):
  if not is_polynomial(value):
    return False
  coefficients = np.reshape(value.coefficients, [-1])
  return all(number.is_integer(coeff) for coeff in coefficients)
  def sample(self, sample_args, values):
    """Sample multiple entities.

    Args:
      sample_args: Instance of `SampleArgs`. The min and max entropy
          and module count will be split up betwene the various entities
          sampled.
      values: List of values to sample.

    Returns:
      List of `Entity` of the same length as `types`.

    Raises:
      RuntimeError: If one of the modules generates a non-`Entity`.
    """
    # Can only sample children once.
    assert self._module_count == 1
    assert not self._child_symbols
    assert not self._child_entities

    if isinstance(sample_args, PreSampleArgs):
      sample_args = sample_args()
    sample_args_split = sample_args.split(len(values))

    def all_symbols():
      return (self._relation_symbols
              .union(self._self_symbols)
              .union(self._child_symbols))

    for value, child_sample_args in zip(values, sample_args_split):
      if number.is_integer(value):
        value = sympy.Integer(value)

      all_symbols_ = all_symbols()
      context = Context(all_symbols_)

      if child_sample_args.num_modules == 0:
        entity = self._value_entity(value, context)
      else:
        sampler = self._sampler(value, child_sample_args)
        entity = sampler(value, child_sample_args, context)
        if not isinstance(entity, Entity):
          raise RuntimeError(
              'Expected entity, but got {} instead'.format(entity))
        if (not number.is_integer_or_rational_or_decimal(entity.value)
            and not isinstance(entity.value, Polynomial)):
          raise RuntimeError('sampler {} returned invalid value of type {}'
                             .format(sampler, type(entity.value)))
        if ((number.is_integer_or_rational_or_decimal(value)
             and entity.value != value)
            or (isinstance(value, Polynomial) and not np.array_equal(
                entity.value.coefficients, value.coefficients))):
          raise RuntimeError(
              'entity values differ, sampler={} wanted={} got={}'
              .format(sampler, value, entity.value))
        if child_sample_args.num_modules != context.module_count:
          raise RuntimeError(
              'unused modules, value={} sample_args={} context.module_count={},'
              ' sampler={}'
              .format(value, child_sample_args, context.module_count, sampler))
        self._module_count += context.module_count

      self._child_entities.append(entity)
      for symbol in context.self_symbols.union(context.child_symbols):
        assert symbol not in all_symbols_
        self._child_symbols.add(symbol)

    return self._child_entities
Beispiel #6
0
def round_number(value, sample_args, context=None):
    """Question for rounding integers and decimals."""
    del value  # unused for now
    if context is None:
        context = composition.Context()

    entropy, sample_args = sample_args.peel()

    # This is the power of 10 to round to. E.g., power == 0 corresponds to
    # rounding to the nearest integer; power == -2 corresponds to rounding to two
    # decimal places, and power == 3 corresponds to rounding to the nearest 1000.
    power = random.randint(-7, 6)

    answer_entropy = 1 + random.uniform(0, entropy / 2)
    entropy = max(1, entropy - answer_entropy)
    value_integer = number.integer(answer_entropy, signed=True)

    remainder_divisor = 10**int(math.ceil(entropy))
    remainder_range_lower = -remainder_divisor / 2
    remainder_range_upper = remainder_divisor / 2

    if value_integer <= 0:
        remainder_range_lower += 1
    if value_integer >= 0:
        remainder_range_upper -= 1

    remainder = random.randint(remainder_range_lower, remainder_range_upper)
    input_ = value_integer + sympy.Rational(remainder, remainder_divisor)
    scale = 10**power if power >= 0 else sympy.Rational(1, 10**(-power))
    input_ = input_ * scale
    value = value_integer * scale
    if not number.is_integer(input_):
        input_ = display.Decimal(input_)
    if not number.is_integer(value):
        value = display.Decimal(value)

    (input_, ) = context.sample(sample_args, [input_])

    if power > 0:
        # Rounding to a power of ten.
        round_to = 10**power
        if random.choice([False, True]):
            # Write the rounding value as a word instead.
            round_to = display.StringNumber(
                round_to, join_number_words_with_hyphens=False)
        description = 'paling dekat {round_to}'.format(round_to=round_to)
    elif power == 0 and random.choice([False, True]):
        # Round to nearest integer.
        description = 'bilangan terdekat'
    else:
        # Round to decimal places.
        description = random.choice(['{dps} desimal'])
        if False:  # no plural indonesia
            # Plural
            description += 's'
        dps = -power
        if random.choice([False, True]):
            dps = display.StringNumber(dps)
        description = description.format(dps=dps)

    template = random.choice([
        'Bulatkan {input} ke {description}.',
        'Berapakah {input} dibulatkan menjadi {description}?',
    ])

    return example.Problem(question=example.question(context,
                                                     template,
                                                     input=input_,
                                                     description=description),
                           answer=value)