def testAdd(self): add = ops.Add() self.assertEqual(str(add), '0') self.assertEqual(add.sympy(), 0) add = ops.Add(2, 3) self.assertEqual(str(add), '2 + 3') self.assertEqual(add.sympy(), 5) add = ops.Add(ops.Add(1, 2), 3) self.assertEqual(str(add), '1 + 2 + 3') self.assertEqual(add.sympy(), 6)
def _make_equals_zero_split(monomials): """Returns an `ops.Eq` containing sum of monomials split on left and right.""" left = [] right = [] for monomial in monomials: if random.choice([False, True]): left.append(monomial) else: right.append(ops.Neg(monomial)) if not left: left = [0] if not right: right = [0] left = ops.Add(*left) right = ops.Add(*right) return ops.Eq(left, right)
def sample_with_small_evaluation(variable, degree, max_abs_input, entropy): """Generates a (canonically ordered) polynomial, with bounded evaluation. The coefficients are chosen to make use of the entropy, with the scaling adjusted so that all give roughly the same contribution to the output of the polynomial when the input is bounded in magnitude by `max_abs_input`. Args: variable: Variable to use in polynomial. degree: Degree of polynomial. max_abs_input: Number >= 1; max absolute value of input. entropy: Float; randomness for generating polynomial. Returns: Instance of `ops.Add`. """ assert max_abs_input >= 1 entropies = entropy * np.random.dirichlet(np.ones(degree + 1)) coeffs = [] for power in range(degree + 1): # This scaling guarantees that the terms give roughly equal contribution # to the typical magnitude of the polynomial when |input| <= max_abs_input. delta = 0.5 * (degree - 2 * power) * math.log10(max_abs_input) power_entropy = entropies[power] + delta min_abs = 1 if power == degree else 0 coeff = number.integer(power_entropy, signed=True, min_abs=min_abs) coeffs.append(coeff) terms = [monomial(coeff, variable, power) for power, coeff in enumerate(coeffs)] return ops.Add(*terms)
def testDescendants(self): constants = [ops.Constant(i) for i in range(6)] # (1 + 2*3**4) / 5 - 6 expression = ops.Sub( ops.Div( ops.Add( constants[0], ops.Mul( constants[1], ops.Pow( constants[2], constants[3]))), constants[4]), constants[5]) descendants = expression.descendants() descendants = ops._flatten(descendants) for constant in constants: self.assertIn(constant, descendants) self.assertEqual(descendants.count(constant), 1) # Also test top-level. self.assertEqual(constants[0].descendants(), [constants[0]]) # Also general structure. constant = ops.Constant(3) expression = ops.Neg(constant) self.assertEqual(set(expression.descendants()), set([constant, expression]))
def _sample_with_brackets(depth, variables, degrees, entropy, length, force_brackets=True): """Internal recursive function for: constructs a polynomial with brackets.""" # To generate arbitrary polynomial recursively, can do one of: # * add two polynomials, with at least one having brackets. # * multiply two polynomials. # * call `sample` (i.e., polynomial without brackets). if force_brackets: length = max(2, length) if not force_brackets and (random.choice([False, True]) or length < 2): return sample(variables, degrees, entropy, length) length_left = random.randint(1, length - 1) length_right = length - length_left entropy_left, entropy_right = entropy * np.random.dirichlet( [length_left, length_right]) if random.choice([False, True]): # Add two. Force brackets on at least one of the polynomials, and sample # repeatedly until we don't get cancellation. while True: left = _sample_with_brackets( depth + 1, variables, degrees, entropy_left, length_left, True) right = _sample_with_brackets( depth + 1, variables, degrees, entropy_right, length_right, False) if random.choice([False, True]): left, right = right, left result = ops.Add(left, right) all_ok = True for variable, degree in zip(variables, degrees): if _degree_of_variable(result, variable) != degree: all_ok = False break if all_ok: return result else: # Multiply two. def sample_with_zero_check(degrees_, entropy_, length_): while True: result = _sample_with_brackets( depth + 1, variables, degrees_, entropy_, length_, False) if degrees_.sum() > 0 or not result.sympy().is_zero: return result degrees = np.asarray(degrees) def sample_degree(max_degree): """Select in range [0, max_degree], biased away from ends.""" if max_degree <= 1 or random.choice([False, True]): return random.randint(0, max_degree) return random.randint(1, max_degree - 1) degrees_left = np.array([sample_degree(degree) for degree in degrees]) degrees_right = degrees - degrees_left left = sample_with_zero_check(degrees_left, entropy_left, length_left) right = sample_with_zero_check(degrees_right, entropy_right, length_right) return ops.Mul(left, right)
def surd_plus_integer(): """Do surd + integer.""" entropy_k = min(1, entropy) left = number.integer(entropy_k, signed=True) assert not multiples_only right = _sample_surd(base, entropy - entropy_k, max_power, False) if random.choice([True, False]): left, right = right, left return ops.Add(left, right)
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 coefficients_to_polynomial(coefficients, variables): """Converts array of lists of coefficients to a polynomial.""" coefficients = np.asarray(coefficients) shape = coefficients.shape indices = list(zip(*np.indices(shape).reshape([len(shape), -1]))) monomials = [] for power in indices: coeffs = coefficients.item(power) if (number.is_integer_or_rational(coeffs) or isinstance(coeffs, sympy.Symbol)): coeffs = [coeffs] elif not isinstance(coeffs, list): raise ValueError('Unrecognized coeffs={} type={}' .format(coeffs, type(coeffs))) for coeff in coeffs: monomials.append(monomial(coeff, variables, power)) random.shuffle(monomials) return ops.Add(*monomials)
def testMul(self): mul = ops.Mul() self.assertEqual(str(mul), '1') self.assertEqual(mul.sympy(), 1) mul = ops.Mul(2, 3) self.assertEqual(str(mul), '2*3') self.assertEqual(mul.sympy(), 6) mul = ops.Mul(ops.Identity(ops.Constant(-2)), 3) self.assertEqual(str(mul), '-2*3') self.assertEqual(mul.sympy(), -6) mul = ops.Mul(ops.Add(1, 2), 3) self.assertEqual(str(mul), '(1 + 2)*3') self.assertEqual(mul.sympy(), 9) mul = ops.Mul(ops.Mul(2, 3), 5) self.assertEqual(str(mul), '2*3*5') self.assertEqual(mul.sympy(), 30)
def testNeg(self): op = ops.Neg(2) self.assertEqual(str(op), '-2') self.assertEqual(op.sympy(), -2) op = ops.Add(ops.Neg(2), 3) self.assertEqual(str(op), '-2 + 3') self.assertEqual(op.sympy(), 1) op = ops.Add(3, ops.Neg(2)) self.assertEqual(str(op), '3 - 2') self.assertEqual(op.sympy(), 1) op = ops.Add(ops.Add(ops.Neg(2), 5), 3) self.assertEqual(str(op), '-2 + 5 + 3') self.assertEqual(op.sympy(), 6) op = ops.Add(3, ops.Add(ops.Identity(ops.Neg(2)), 5)) self.assertEqual(str(op), '3 - 2 + 5') self.assertEqual(op.sympy(), 6) op = ops.Add(3, ops.Add(2, ops.Neg(5))) self.assertEqual(str(op), '3 + 2 - 5') self.assertEqual(op.sympy(), 0)
def testEq(self): op = ops.Eq(ops.Add(2, 3), 4) self.assertEqual(str(op), '2 + 3 = 4') self.assertEqual(op.sympy(), False)