def test_assoccomm(): from symbolic_pymc.relations import buildo x, a, b, c = tt.dvectors('xabc') test_expr = x + 1 q = var('q') res = run(1, q, buildo(tt.add, test_expr.owner.inputs, test_expr)) assert q == res[0] res = run(1, q, buildo(q, test_expr.owner.inputs, test_expr)) assert tt.add == res[0].reify() res = run(1, q, buildo(tt.add, q, test_expr)) assert mt(tuple(test_expr.owner.inputs)) == res[0] res = run(0, var('x'), eq_comm(mt.mul(a, b), mt.mul(b, var('x')))) assert (mt(a), ) == res res = run(0, var('x'), eq_comm(mt.add(a, b), mt.add(b, var('x')))) assert (mt(a), ) == res res = run(0, var('x'), (eq_assoc, mt.add(a, b, c), mt.add(a, var('x')))) # TODO: `res[0]` should return `etuple`s. Since `eq_assoc` effectively # picks apart the results of `arguments(...)`, I don't know if we can # keep the `etuple`s around. We might be able to convert the results # to `etuple`s automatically by wrapping `eq_assoc`, though. res_obj = etuple(*res[0]).eval_obj assert res_obj == mt(b + c) res = run(0, var('x'), (eq_assoc, mt.mul(a, b, c), mt.mul(a, var('x')))) res_obj = etuple(*res[0]).eval_obj assert res_obj == mt(b * c)
def test_assoccomm(): x, a, b, c = tt.dvectors("xabc") test_expr = x + 1 q = var() res = run(1, q, applyo(tt.add, etuple(*test_expr.owner.inputs), test_expr)) assert q == res[0] res = run(1, q, applyo(q, etuple(*test_expr.owner.inputs), test_expr)) assert tt.add == res[0].reify() res = run(1, q, applyo(tt.add, q, test_expr)) assert mt(tuple(test_expr.owner.inputs)) == res[0] x = var() res = run(0, x, eq_comm(mt.mul(a, b), mt.mul(b, x))) assert (mt(a), ) == res res = run(0, x, eq_comm(mt.add(a, b), mt.add(b, x))) assert (mt(a), ) == res (res, ) = run(0, x, eq_assoc(mt.add(a, b, c), mt.add(a, x))) assert res == mt(b + c) (res, ) = run(0, x, eq_assoc(mt.mul(a, b, c), mt.mul(a, x))) assert res == mt(b * c)
def test_distributions(): res = run(0, var('q'), eq(var('q'), mt(1)), constant_neq(var('q'), np.array(1.))) assert not res res = run(0, var('q'), eq(var('q'), mt(2)), constant_neq(var('q'), np.array(1.))) assert res
def test_meta_helpers(): zeros_mt = mt.zeros(2) assert np.array_equal(zeros_mt.reify().eval(), np.r_[0., 0.]) zeros_mt = mt.zeros(2, dtype=int) assert np.array_equal(zeros_mt.reify().eval(), np.r_[0, 0]) mat_mt = mt(np.eye(2)) diag_mt = mt.diag(mat_mt) assert np.array_equal(diag_mt.reify().eval(), np.r_[1., 1.]) diag_mt = mt.diag(mt(np.r_[1, 2, 3])) assert np.array_equal(diag_mt.reify().eval(), np.diag(np.r_[1, 2, 3]))
def test_pymc_broadcastable(): """Test PyMC3 to Theano conversion amid array broadcasting.""" tt.config.compute_test_value = 'ignore' mu_X = tt.vector('mu_X') sd_X = tt.vector('sd_X') mu_Y = tt.vector('mu_Y') sd_Y = tt.vector('sd_Y') mu_X.tag.test_value = np.array([0.], dtype=tt.config.floatX) sd_X.tag.test_value = np.array([1.], dtype=tt.config.floatX) mu_Y.tag.test_value = np.array([1.], dtype=tt.config.floatX) sd_Y.tag.test_value = np.array([0.5], dtype=tt.config.floatX) with pm.Model() as model: X_rv = pm.Normal('X_rv', mu_X, sd=sd_X, shape=(1, )) Y_rv = pm.Normal('Y_rv', mu_Y, sd=sd_Y, shape=(1, )) Z_rv = pm.Normal('Z_rv', X_rv + Y_rv, sd=sd_X + sd_Y, shape=(1, ), observed=[10.]) with pytest.warns(UserWarning): fgraph = model_graph(model) Z_rv_tt = canonicalize(fgraph, return_graph=False) # This will break comparison if we don't reuse it rng = Z_rv_tt.owner.inputs[1].owner.inputs[-1] mu_X_ = mt.vector('mu_X') sd_X_ = mt.vector('sd_X') mu_Y_ = mt.vector('mu_Y') sd_Y_ = mt.vector('sd_Y') tt.config.compute_test_value = 'ignore' X_rv_ = mt.NormalRV(mu_X_, sd_X_, (1, ), rng, name='X_rv') X_rv_ = mt.addbroadcast(X_rv_, 0) Y_rv_ = mt.NormalRV(mu_Y_, sd_Y_, (1, ), rng, name='Y_rv') Y_rv_ = mt.addbroadcast(Y_rv_, 0) Z_rv_ = mt.NormalRV(mt.add(X_rv_, Y_rv_), mt.add(sd_X_, sd_Y_), (1, ), rng, name='Z_rv') obs_ = mt(Z_rv.observations) Z_rv_obs_ = mt.observed(obs_, Z_rv_) Z_rv_meta = canonicalize(Z_rv_obs_.reify(), return_graph=False) assert mt(Z_rv_tt) == mt(Z_rv_meta)
def test_constant_neq(): q_lv = var() res = run(0, q_lv, eq(q_lv, mt(1)), constant_neq(q_lv, np.array(1.0))) assert not res # TODO: If `constant_neq` was a true constraint, this would work. # res = run(0, q_lv, constant_neq(q_lv, np.array(1.0)), eq(q_lv, mt(1))) # assert not res # TODO: If `constant_neq` was a true constraint, this would work. # res = run(0, q_lv, constant_neq(q_lv, np.array(1.0)), eq(q_lv, mt(2))) # assert res == (mt(2),) res = run(0, q_lv, eq(q_lv, mt(2)), constant_neq(q_lv, np.array(1.0))) assert res == (mt(2), )
def test_terms(): x, a, b = tt.dvectors("xab") test_expr = x + a * b assert mt(test_expr.owner.op) == operator(test_expr) assert mt(tuple(test_expr.owner.inputs)) == tuple(arguments(test_expr)) assert tuple(arguments(test_expr)) == mt(tuple(test_expr.owner.inputs)) # Implicit `etuple` conversion should retain the original object # (within the implicitly introduced meta object, of course). assert test_expr == arguments(test_expr)._parent._eval_obj.obj assert graph_equal(test_expr, term(operator(test_expr), arguments(test_expr))) assert mt(test_expr) == term(operator(test_expr), arguments(test_expr)) # Same here: should retain the original object. assert test_expr == term(operator(test_expr), arguments(test_expr)).reify()
def test_scan_op(): def f_pow2(x_tm1): return 2 * x_tm1 state = theano.tensor.scalar("state") n_steps = theano.tensor.iscalar("nsteps") output, updates = theano.scan( f_pow2, [], state, [], n_steps=n_steps, truncate_gradient=-1, go_backwards=False ) output_mt = mt(output) assert isinstance(output_mt.owner.inputs[0].owner.op, mt.Scan) assert output is output_mt.reify()
def test_unify_ops(): def f_pow2(x_tm1): return 2 * x_tm1 state = theano.tensor.scalar("state") n_steps = theano.tensor.iscalar("nsteps") output, updates = theano.scan(f_pow2, [], state, [], n_steps=n_steps, truncate_gradient=-1, go_backwards=False) assert np.array_equal(output.eval({ state: 1.0, n_steps: 4 }), np.r_[2.0, 4.0, 8.0, 16.0]) scan_tt = output.owner.inputs[0].owner.op inputs_lv, outputs_lv, info_lv = var(), var(), var() scan_lv = mt.Scan(inputs_lv, outputs_lv, info_lv) s = unify(scan_lv, scan_tt, {}) assert s is not False assert s[inputs_lv] is scan_tt.inputs s_new = s.copy() s_new[outputs_lv] = [5 * s_new[inputs_lv][0]] new_scan_mt = reify(scan_lv, s_new) output_mt = mt(output) output_mt.owner.inputs[0].owner.op = new_scan_mt output_mt.owner.inputs[0].reset() output_mt.owner.outputs[0].reset() output_mt.owner.reset() output_mt.reset() assert output_mt.obj is not output output_new = output_mt.reify() assert output_new != output assert np.array_equal(output_new.eval({ state: 1.0, n_steps: 4 }), np.r_[5.0, 25.0, 125.0, 625.0])
def test_etuple_term(): """Test `etuplize` and `etuple` interaction with `term` """ # Take apart an already constructed/evaluated meta # object. e2 = mt.add(mt.vector(), mt.vector()) e2_et = etuplize(e2) assert isinstance(e2_et, ExpressionTuple) # e2_et_expect = etuple( # mt.add, # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # ) e2_et_expect = etuple(mt.add, e2.base_arguments[0], e2.base_arguments[1]) assert e2_et == e2_et_expect assert e2_et.eval_obj is e2 # Make sure expression expansion works from Theano objects, too. # First, do it manually. tt_expr = tt.vector() + tt.vector() mt_expr = mt(tt_expr) assert mt_expr.obj is tt_expr assert mt_expr.reify() is tt_expr e3 = etuplize(mt_expr) assert e3 == e2_et assert e3.eval_obj is mt_expr assert e3.eval_obj.reify() is tt_expr # Now, through `etuplize` e2_et_2 = etuplize(tt_expr) assert e2_et_2 == e3 == e2_et assert isinstance(e2_et_2, ExpressionTuple) assert e2_et_2.eval_obj == tt_expr
def test_meta_classes(): vec_tt = tt.vector('vec') vec_m = metatize(vec_tt) assert vec_m.obj == vec_tt assert type(vec_m) == TheanoMetaTensorVariable # This should invalidate the underlying base object. vec_m.index = 0 assert vec_m.obj is None assert vec_m.reify().type == vec_tt.type assert vec_m.reify().name == vec_tt.name vec_type_m = vec_m.type assert type(vec_type_m) == TheanoMetaTensorType assert vec_type_m.dtype == vec_tt.dtype assert vec_type_m.broadcastable == vec_tt.type.broadcastable assert vec_type_m.name == vec_tt.type.name assert graph_equal(tt.add(1, 2), mt.add(1, 2).reify()) meta_var = mt.add(1, var()).reify() assert isinstance(meta_var, TheanoMetaTensorVariable) assert isinstance(meta_var.owner.op.obj, theano.Op) assert isinstance(meta_var.owner.inputs[0].obj, tt.TensorConstant) test_vals = [1, 2.4] meta_vars = metatize(test_vals) assert meta_vars == [metatize(x) for x in test_vals] # TODO: Do we really want meta variables to be equal to their # reified base objects? # assert meta_vars == [tt.as_tensor_variable(x) for x in test_vals] name_mt = var() add_tt = tt.add(0, 1) add_mt = mt.add(0, 1, name=name_mt) assert add_mt.name is name_mt assert add_tt.type == add_mt.type.reify() assert mt(add_tt.owner) == add_mt.owner # assert isvar(add_mt._obj) # Let's confirm that we can dynamically create a new meta `Op` type test_mat = np.c_[[2, 3], [4, 5]] svd_tt = tt.nlinalg.SVD()(test_mat) # First, can we create one from a new base `Op` instance? svd_op_mt = mt(tt.nlinalg.SVD()) svd_mt = svd_op_mt(test_mat) assert svd_mt[0].owner.nin == 1 assert svd_mt[0].owner.nout == 3 svd_outputs = svd_mt[0].owner.outputs assert svd_outputs[0] == svd_mt[0] assert svd_outputs[1] == svd_mt[1] assert svd_outputs[2] == svd_mt[2] assert mt(svd_tt) == svd_mt # Next, can we create one from a base `Op` type/class? svd_op_type_mt = mt.nlinalg.SVD assert isinstance(svd_op_type_mt, type) assert issubclass(svd_op_type_mt, TheanoMetaOp) # svd_op_inst_mt = svd_op_type_mt(tt.nlinalg.SVD()) # svd_op_inst_mt(test_mat) == svd_mt # Apply node with logic variable as outputs svd_apply_mt = TheanoMetaApply(svd_op_mt, [test_mat], outputs=var('out')) assert isinstance(svd_apply_mt.inputs, tuple) assert isinstance(svd_apply_mt.inputs[0], MetaSymbol) assert isvar(svd_apply_mt.outputs) assert svd_apply_mt.nin == 1 assert svd_apply_mt.nout is None # Apply node with logic variable as inputs svd_apply_mt = TheanoMetaApply(svd_op_mt, var('in'), outputs=var('out')) assert svd_apply_mt.nin is None # A meta variable with None index var_mt = TheanoMetaVariable(svd_mt[0].type, svd_mt[0].owner, None, None) assert var_mt.index is None reified_var_mt = var_mt.reify() assert isinstance(reified_var_mt, TheanoMetaTensorVariable) assert reified_var_mt.index == 0 assert var_mt.index == 0 assert reified_var_mt == svd_mt[0] # A meta variable with logic variable index var_mt = TheanoMetaVariable(svd_mt[0].type, svd_mt[0].owner, var('index'), None) assert isvar(var_mt.index) reified_var_mt = var_mt.reify() assert isvar(var_mt.index) assert reified_var_mt.index == 0 const_mt = mt(1) assert isinstance(const_mt, TheanoMetaTensorConstant) assert const_mt != mt(2)
def test_normal_normal_regression(): tt.config.compute_test_value = "ignore" theano.config.cxx = "" np.random.seed(9283) N = 10 M = 3 a_tt = tt.vector("a") R_tt = tt.vector("R") X_tt = tt.matrix("X") V_tt = tt.vector("V") a_tt.tag.test_value = np.random.normal(size=M) R_tt.tag.test_value = np.abs(np.random.normal(size=M)) X = np.random.normal(10, 1, size=N) X = np.c_[np.ones(10), X, X * X] X_tt.tag.test_value = X V_tt.tag.test_value = np.ones(N) beta_rv = NormalRV(a_tt, R_tt, name="\\beta") E_y_rv = X_tt.dot(beta_rv) E_y_rv.name = "E_y" Y_rv = NormalRV(E_y_rv, V_tt, name="Y") y_tt = tt.as_tensor_variable(Y_rv.tag.test_value) y_tt.name = "y" y_obs_rv = observed(y_tt, Y_rv) y_obs_rv.name = "y_obs" # # Use the relation with identify/match `Y`, `X` and `beta`. # y_args_tail_lv, b_args_tail_lv = var(), var() beta_lv = var() y_args_lv, y_lv, Y_lv, X_lv = var(), var(), var(), var() (res, ) = run( 1, (beta_lv, y_args_tail_lv, b_args_tail_lv), applyo(mt.observed, y_args_lv, y_obs_rv), eq(y_args_lv, (y_lv, Y_lv)), normal_normal_regression(Y_lv, X_lv, beta_lv, y_args_tail_lv, b_args_tail_lv), ) # TODO FIXME: This would work if non-op parameters (e.g. names) were covered by # `operator`/`car`. See `TheanoMetaOperator`. assert res[0].eval_obj.obj == beta_rv assert res[0] == etuplize(beta_rv) assert res[1] == etuplize(Y_rv)[2:] assert res[2] == etuplize(beta_rv)[1:] # # Use the relation with to produce `Y` from given `X` and `beta`. # X_new_mt = mt(tt.eye(N, M)) beta_new_mt = mt(NormalRV(0, 1, size=M)) Y_args_cdr_mt = etuplize(Y_rv)[2:] Y_lv = var() (res, ) = run( 1, Y_lv, normal_normal_regression(Y_lv, X_new_mt, beta_new_mt, Y_args_cdr_mt)) Y_out_mt = res.eval_obj Y_new_mt = etuple(mt.NormalRV, mt.dot(X_new_mt, beta_new_mt)) + Y_args_cdr_mt Y_new_mt = Y_new_mt.eval_obj assert Y_out_mt == Y_new_mt
def test_pymc_normal_model(): """Conduct a more in-depth test of PyMC3/Theano conversions for a specific model.""" tt.config.compute_test_value = 'ignore' mu_X = tt.dscalar('mu_X') sd_X = tt.dscalar('sd_X') mu_Y = tt.dscalar('mu_Y') mu_X.tag.test_value = np.array(0., dtype=tt.config.floatX) sd_X.tag.test_value = np.array(1., dtype=tt.config.floatX) mu_Y.tag.test_value = np.array(1., dtype=tt.config.floatX) # We need something that uses transforms... with pm.Model() as model: X_rv = pm.Normal('X_rv', mu_X, sd=sd_X) S_rv = pm.HalfCauchy('S_rv', beta=np.array(0.5, dtype=tt.config.floatX)) Y_rv = pm.Normal('Y_rv', X_rv * S_rv, sd=S_rv) Z_rv = pm.Normal('Z_rv', X_rv + Y_rv, sd=sd_X, observed=10.) fgraph = model_graph(model, output_vars=[Z_rv]) Z_rv_tt = canonicalize(fgraph, return_graph=False) # This will break comparison if we don't reuse it rng = Z_rv_tt.owner.inputs[1].owner.inputs[-1] mu_X_ = mt.dscalar('mu_X') sd_X_ = mt.dscalar('sd_X') tt.config.compute_test_value = 'ignore' X_rv_ = mt.NormalRV(mu_X_, sd_X_, None, rng, name='X_rv') S_rv_ = mt.HalfCauchyRV(np.array(0., dtype=tt.config.floatX), np.array(0.5, dtype=tt.config.floatX), None, rng, name='S_rv') Y_rv_ = mt.NormalRV(mt.mul(X_rv_, S_rv_), S_rv_, None, rng, name='Y_rv') Z_rv_ = mt.NormalRV(mt.add(X_rv_, Y_rv_), sd_X, None, rng, name='Z_rv') obs_ = mt(Z_rv.observations) Z_rv_obs_ = mt.observed(obs_, Z_rv_) Z_rv_meta = mt(canonicalize(Z_rv_obs_.reify(), return_graph=False)) assert mt(Z_rv_tt) == Z_rv_meta # Now, let's try that with multiple outputs. fgraph.disown() fgraph = model_graph(model, output_vars=[Y_rv, Z_rv]) assert len(fgraph.variables) == 25 Y_new_rv = walk(Y_rv, fgraph.memo) S_new_rv = walk(S_rv, fgraph.memo) X_new_rv = walk(X_rv, fgraph.memo) Z_new_rv = walk(Z_rv, fgraph.memo) # Make sure our new vars are actually in the graph and where # they should be. assert Y_new_rv == fgraph.outputs[0] assert Z_new_rv == fgraph.outputs[1] assert X_new_rv in fgraph.variables assert S_new_rv in fgraph.variables assert isinstance(Z_new_rv.owner.op, Observed) # Let's only look at the variables involved in the `Z_rv` subgraph. Z_vars = theano.gof.graph.variables(theano.gof.graph.inputs([Z_new_rv]), [Z_new_rv]) # Let's filter for only the `RandomVariables` with names. Z_vars_count = Counter([ n.name for n in Z_vars if n.name and n.owner and isinstance(n.owner.op, RandomVariable) ]) # Each new RV should be present and only occur once. assert Y_new_rv.name in Z_vars_count.keys() assert X_new_rv.name in Z_vars_count.keys() assert Z_new_rv.owner.inputs[1].name in Z_vars_count.keys() assert all(v == 1 for v in Z_vars_count.values())
def test_pymc_normals(): tt.config.compute_test_value = 'ignore' rand_state = theano.shared(np.random.RandomState()) mu_a = NormalRV(0., 100**2, name='mu_a', rng=rand_state) sigma_a = HalfCauchyRV(5, name='sigma_a', rng=rand_state) mu_b = NormalRV(0., 100**2, name='mu_b', rng=rand_state) sigma_b = HalfCauchyRV(5, name='sigma_b', rng=rand_state) county_idx = np.r_[1, 1, 2, 3] # We want the following for a, b: # N(m, S) -> m + N(0, 1) * S a = NormalRV(mu_a, sigma_a, size=(len(county_idx), ), name='a', rng=rand_state) b = NormalRV(mu_b, sigma_b, size=(len(county_idx), ), name='b', rng=rand_state) radon_est = a[county_idx] + b[county_idx] * 7 eps = HalfCauchyRV(5, name='eps', rng=rand_state) radon_like = NormalRV(radon_est, eps, name='radon_like', rng=rand_state) radon_like_rv = observed(tt.as_tensor_variable(np.r_[1., 2., 3., 4.]), radon_like) graph_mt = mt(radon_like_rv) expr_graph, = run( 1, var('q'), non_obs_fixedp_graph_applyo(scale_loc_transform, graph_mt, var('q'))) radon_like_rv_opt = expr_graph.reify() assert radon_like_rv_opt.owner.op == observed radon_like_opt = radon_like_rv_opt.owner.inputs[1] radon_est_opt = radon_like_opt.owner.inputs[0] # These should now be `tt.add(mu_*, ...)` outputs. a_opt = radon_est_opt.owner.inputs[0].owner.inputs[0] b_opt = radon_est_opt.owner.inputs[1].owner.inputs[0].owner.inputs[0] # Make sure NormalRV gets replaced with an addition assert a_opt.owner.op == tt.add assert b_opt.owner.op == tt.add # Make sure the first term in the addition is the old NormalRV mean mu_a_opt = a_opt.owner.inputs[0].owner.inputs[0] assert 'mu_a' == mu_a_opt.name == mu_a.name mu_b_opt = b_opt.owner.inputs[0].owner.inputs[0] assert 'mu_b' == mu_b_opt.name == mu_b.name # Make sure the second term in the addition is the standard NormalRV times # the old std. dev. assert a_opt.owner.inputs[1].owner.op == tt.mul assert b_opt.owner.inputs[1].owner.op == tt.mul sigma_a_opt = a_opt.owner.inputs[1].owner.inputs[0].owner.inputs[0] assert sigma_a_opt.owner.op == sigma_a.owner.op sigma_b_opt = b_opt.owner.inputs[1].owner.inputs[0].owner.inputs[0] assert sigma_b_opt.owner.op == sigma_b.owner.op a_std_norm_opt = a_opt.owner.inputs[1].owner.inputs[1] assert a_std_norm_opt.owner.op == NormalRV assert a_std_norm_opt.owner.inputs[0].data == 0.0 assert a_std_norm_opt.owner.inputs[1].data == 1.0 b_std_norm_opt = b_opt.owner.inputs[1].owner.inputs[1] assert b_std_norm_opt.owner.op == NormalRV assert b_std_norm_opt.owner.inputs[0].data == 0.0 assert b_std_norm_opt.owner.inputs[1].data == 1.0
def test_etuple_term(): """Test `etuplize` and `etuple` interaction with `term`.""" # Take apart an already constructed/evaluated meta # object. e2 = mt.add(mt.vector(), mt.vector()) e2_et = etuplize(e2) assert isinstance(e2_et, ExpressionTuple) # e2_et_expect = etuple( # mt.add, # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # ) e2_et_expect = etuple(mt.add, e2.base_arguments[0], e2.base_arguments[1]) assert e2_et == e2_et_expect assert e2_et.eval_obj is e2 # Make sure expression expansion works from Theano objects, too. # First, do it manually. tt_expr = tt.vector() + tt.vector() mt_expr = mt(tt_expr) assert mt_expr.obj is tt_expr assert mt_expr.reify() is tt_expr e3 = etuplize(mt_expr) assert e3 == e2_et assert e3.eval_obj is mt_expr assert e3.eval_obj.reify() is tt_expr # Now, through `etuplize` e2_et_2 = etuplize(tt_expr) assert e2_et_2 == e3 == e2_et assert isinstance(e2_et_2, ExpressionTuple) assert e2_et_2.eval_obj == tt_expr test_expr = mt(tt.vector("z") * 7) assert rator(test_expr) == mt.mul assert rands(test_expr)[0] == mt(tt.vector("z")) dim_shuffle_op = rator(rands(test_expr)[1]) assert isinstance(dim_shuffle_op, mt.DimShuffle) assert rands(rands(test_expr)[1]) == etuple(mt(7)) with pytest.raises(ConsError): rator(dim_shuffle_op) # assert rator(dim_shuffle_op) == mt.DimShuffle # assert rands(dim_shuffle_op) == etuple((), ("x",), True) const_tensor = rands(rands(test_expr)[1])[0] with pytest.raises(ConsError): rator(const_tensor) with pytest.raises(ConsError): rands(const_tensor) et_expr = etuplize(test_expr) exp_res = etuple(mt.mul, mt(tt.vector("z")), etuple(mt.DimShuffle((), ("x", ), True), mt(7)) # etuple(etuple(mt.DimShuffle, (), ("x",), True), mt(7)) ) assert et_expr == exp_res assert exp_res.eval_obj == test_expr