1import math
2
3import numpy
4import six
5
6import chainer
7from chainer import backend
8from chainer.backends import cuda
9from chainer.backends import intel64
10from chainer import function_node
11import chainer.functions
12from chainer.functions.math import floor as _floor
13from chainer import utils
14from chainer.utils import type_check
15from chainer import variable
16
17
18def _convert_value_to_string(value):
19    if isinstance(value, variable.Variable):
20        value = value.data
21
22    if numpy.isscalar(value):
23        if value < 0:
24            return '({})'.format(value)
25        else:
26            return str(value)
27
28    array_types = chainer.get_array_types()
29    if isinstance(value, array_types):
30        return 'constant array'
31    else:
32        raise ValueError(
33            'Value must be a Variable, scalar, {} or {}. Actual: {}'.format(
34                ', '.join([str(at) for at in array_types[:-1]]),
35                array_types[-1], type(value)))
36
37
38def _preprocess_const(x, value):
39    return x.dtype.type(value)
40
41
42def _chainerx_preprocess_const(x, value, label):
43    # Allow mixing of numpy/cupy array and chainerx array as long as
44    # conversion without copy is possible.
45    if isinstance(value, (numpy.ndarray, cuda.ndarray)):
46        # TODO(niboshi): force zero-copy
47        return backend.to_chx(value)
48
49    if isinstance(value, (six.integer_types, float)):
50        return value
51    if isinstance(value, numpy.generic):
52        return value.item()
53    if isinstance(value, variable.Variable):
54        value = variable.as_array(value)
55    utils._check_arrays_forward_compatible((x, value), label)
56    return value
57
58
59def _preprocess_rhs(x, value):
60    if isinstance(value, chainer.Variable):
61        return value
62
63    if not (numpy.isscalar(value)
64            or isinstance(value, chainer.get_array_types())):
65        raise TypeError(
66            'Value must be a scalar, `numpy.ndarray`, `cupy.ndarray` '
67            'or a `Variable`.\nActual: {}'.format(type(value)))
68
69    return value.astype(x.dtype, copy=False)
70
71
72class Neg(function_node.FunctionNode):
73
74    is_elementwise = True
75
76    @property
77    def label(self):
78        return '__neg__'
79
80    def check_type_forward(self, in_types):
81        type_check._argname(in_types, ('x',))
82
83    def forward_chainerx(self, x):
84        return -x[0],
85
86    def forward(self, x):
87        self.retain_inputs(())
88        return utils.force_array(-x[0]),
89
90    def backward(self, indexes, gy):
91        return -gy[0],
92
93
94def neg(self):  # -x
95    """Element-wise negation.
96
97    Returns:
98        ~chainer.Variable: Output variable.
99    """
100    return Neg().apply((self,))[0]
101
102
103class Absolute(function_node.FunctionNode):
104
105    is_elementwise = True
106
107    @property
108    def label(self):
109        return '|_|'
110
111    def check_type_forward(self, in_types):
112        type_check._argname(in_types, ('x',))
113        type_check.expect(in_types[0].dtype.kind == 'f')
114
115    def forward(self, x):
116        self.retain_inputs((0,))
117        return utils.force_array(abs(x[0])),
118
119    def backward(self, indexes, grad_outputs):
120        x = self.get_retained_inputs()[0]
121        return AbsoluteGrad(x.data).apply(grad_outputs)
122
123
124class AbsoluteGrad(function_node.FunctionNode):
125
126    is_elementwise = True
127
128    def __init__(self, x):
129        super(AbsoluteGrad, self).__init__()
130        self.x = x
131
132    def check_type_forward(self, in_types):
133        type_check._argname(in_types, ('gy',))
134        type_check.expect(in_types[0].dtype.kind == 'f')
135
136    def forward_cpu(self, inputs):
137        return utils.force_array(numpy.sign(self.x) * inputs[0]),
138
139    def forward_gpu(self, inputs):
140        gx0 = cuda.elementwise(
141            'T x0, T gy', 'T gx0',
142            'gx0 = ((x0 > 0) - (x0 < 0)) * gy',
143            'abs_bwd')(self.x, inputs[0])
144        return gx0,
145
146    def backward(self, indexes, grad_outputs):
147        return AbsoluteGrad(self.x).apply(grad_outputs)
148
149
150def absolute(self):
151    """Element-wise absolute.
152
153    Returns:
154        ~chainer.Variable: Output variable.
155    """
156    return Absolute().apply((self,))[0]
157
158
159class Add(function_node.FunctionNode):
160
161    is_elementwise = True
162
163    @property
164    def label(self):
165        return '_ + _'
166
167    def check_type_forward(self, in_types):
168        type_check._argname(in_types, ('lhs', 'rhs'))
169        type_check.expect(
170            in_types[0].dtype == in_types[1].dtype,
171        )
172        type_check.expect_broadcast_shapes(
173            in_types[0].shape, in_types[1].shape)
174
175    def forward_chainerx(self, x):
176        return x[0] + x[1],
177
178    def forward(self, x):
179        # may broadcast
180        y = utils.force_array(x[0] + x[1])
181        return y,
182
183    def backward(self, indexes, gy):
184        return tuple(chainer.functions.sum_to(gy[0], self.inputs[i].shape)
185                     for i in indexes)
186
187
188class AddConstant(function_node.FunctionNode):
189
190    is_elementwise = True
191
192    def __init__(self, value):
193        self.value = value
194
195    @property
196    def label(self):
197        return '_ + %s' % _convert_value_to_string(self.value)
198
199    def check_type_forward(self, in_types):
200        type_check._argname(in_types, ('x',))
201        type_check.expect(in_types.size() == 1)
202
203    def forward_chainerx(self, x):
204        value = _chainerx_preprocess_const(x[0], self.value, 'add')
205        return x[0] + value,
206
207    def forward(self, x):
208        value = _preprocess_const(x[0], self.value)
209        return utils.force_array(x[0] + value),
210
211    def backward(self, indexes, gy):
212        x_node, = self.inputs
213        return gy
214
215
216class MultiAdd(function_node.FunctionNode):
217
218    is_elementwise = True
219
220    def check_type_forward(self, in_types):
221        for i, in_type in enumerate(in_types):
222            type_check._argname((in_type,), ('x{}'.format(i),))
223            type_check.expect(in_types[0].dtype == in_type.dtype)
224
225    def forward(self, xs):
226        self.len = len(xs)
227        if len(xs) == 1:
228            return xs
229        if (intel64.should_use_ideep('>=auto')
230                and intel64.inputs_all_ready(xs)
231                and all(x.shape == xs[0].shape for x in xs[1:])):
232            y = intel64.ideep.multi_add(xs)
233        else:
234            # The output should be a new array. Add the first 2 arrays
235            # and get the result y. Then add the rest arrays to y.
236            y = xs[0] + xs[1]
237            for x in xs[2:]:
238                if x.shape == y.shape:
239                    y += x
240                else:
241                    y = x + y
242
243        return utils.force_array(y),
244
245    def backward(self, indexes, gy):
246        return tuple(chainer.functions.sum_to(gy[0], x_node.shape)
247                     for x_node in self.inputs)
248
249
250# TODO(hvy): Implement multi-add with chainerx.ndarrays.
251def add(*xs):  # lhs + rhs or add more than 2 variables
252    """Element-wise addition.
253
254    Returns:
255        ~chainer.Variable: Output variable.
256    """
257    if len(xs) == 2:
258        lhs, rhs = xs
259        if numpy.isscalar(rhs):
260            return AddConstant(rhs).apply((lhs,))[0]
261        rhs = _preprocess_rhs(lhs, rhs)
262        return Add().apply((lhs, rhs))[0]
263    else:
264        return MultiAdd().apply(xs)[0]
265
266
267class Sub(function_node.FunctionNode):
268
269    is_elementwise = True
270
271    @property
272    def label(self):
273        return '_ - _'
274
275    def check_type_forward(self, in_types):
276        type_check._argname(in_types, ('lhs', 'rhs'))
277        type_check.expect(in_types[0].dtype == in_types[1].dtype)
278        type_check.expect_broadcast_shapes(
279            in_types[0].shape, in_types[1].shape)
280
281    def forward_chainerx(self, x):
282        return x[0] - x[1],
283
284    def forward(self, x):
285        # may broadcast
286        return utils.force_array(x[0] - x[1]),
287
288    def backward(self, indexes, gy):
289        x1, x2 = self.inputs
290        g, = gy
291        return (
292            chainer.functions.sum_to(g, x1.shape) if 0 in indexes else None,
293            -chainer.functions.sum_to(g, x2.shape) if 1 in indexes else None,
294        )
295
296
297def sub(self, rhs):  # lhs - rhs
298    """Element-wise subtraction.
299
300    Returns:
301        ~chainer.Variable: Output variable.
302    """
303    if numpy.isscalar(rhs):
304        return AddConstant(-rhs).apply((self,))[0]
305    rhs = _preprocess_rhs(self, rhs)
306    return Sub().apply((self, rhs))[0]
307
308
309class SubFromConstant(function_node.FunctionNode):
310
311    is_elementwise = True
312
313    def __init__(self, value):
314        self.value = value
315
316    @property
317    def label(self):
318        return '%s - _' % _convert_value_to_string(self.value)
319
320    def check_type_forward(self, in_types):
321        type_check._argname(in_types, ('x',))
322
323    def forward(self, x):
324        value = _preprocess_const(x[0], self.value)
325        return utils.force_array(value - x[0]),
326
327    def backward(self, indexes, gy):
328        g, = gy
329        return -g,
330
331
332def rsub(self, rhs):  # rhs - lhs
333    """Element-wise subtraction.
334
335    Returns:
336        ~chainer.Variable: Output variable.
337    """
338    if numpy.isscalar(rhs):
339        return SubFromConstant(rhs).apply((self,))[0]
340    rhs = _preprocess_rhs(self, rhs)
341    return Sub().apply((rhs, self))[0]
342
343
344class Mul(function_node.FunctionNode):
345
346    is_elementwise = True
347
348    @property
349    def label(self):
350        return '_ * _'
351
352    def check_type_forward(self, in_types):
353        type_check._argname(in_types, ('lhs', 'rhs'))
354        type_check.expect(
355            in_types[0].dtype.kind == 'f',
356            in_types[0].dtype == in_types[1].dtype,
357        )
358        type_check.expect_broadcast_shapes(
359            in_types[0].shape, in_types[1].shape)
360
361    def forward_chainerx(self, x):
362        return x[0] * x[1],
363
364    def forward(self, x):
365        self.retain_inputs((0, 1))
366        # may broadcast
367        return utils.force_array(x[0] * x[1]),
368
369    def backward(self, indexes, gy):
370        xs = self.get_retained_inputs()
371        return tuple(
372            chainer.functions.sum_to(gy[0] * xs[1 - i], xs[i].shape)
373            for i in indexes
374        )
375
376
377class MulConstant(function_node.FunctionNode):
378
379    is_elementwise = True
380
381    def __init__(self, value):
382        self.value = value
383
384    @property
385    def label(self):
386        return '_ * %s' % _convert_value_to_string(self.value)
387
388    def check_type_forward(self, in_types):
389        type_check._argname(in_types, ('x',))
390
391    def forward_chainerx(self, x):
392        value = _chainerx_preprocess_const(x[0], self.value, 'mul')
393        return x[0] * value,
394
395    def forward(self, x):
396        value = _preprocess_const(x[0], self.value)
397        return utils.force_array(value * x[0]),
398
399    def backward(self, indexes, gy):
400        g, = gy
401        return self.value * g,
402
403
404def mul(self, rhs):  # lhs * rhs
405    """Element-wise multiplication.
406
407    Returns:
408        ~chainer.Variable: Output variable.
409    """
410    if numpy.isscalar(rhs):
411        return MulConstant(rhs).apply((self,))[0]
412    rhs = _preprocess_rhs(self, rhs)
413    return Mul().apply((self, rhs))[0]
414
415
416class Div(function_node.FunctionNode):
417
418    is_elementwise = True
419
420    @property
421    def label(self):
422        return '_ / _'
423
424    def check_type_forward(self, in_types):
425        type_check._argname(in_types, ('lhs', 'rhs'))
426        type_check.expect(
427            in_types[0].dtype.kind == 'f',
428            in_types[0].dtype == in_types[1].dtype,
429        )
430        type_check.expect_broadcast_shapes(
431            in_types[0].shape, in_types[1].shape)
432
433    def forward_chainerx(self, x):
434        return x[0] / x[1],
435
436    def forward(self, x):
437        self.retain_inputs((0, 1))
438        # may broadcast
439        return utils.force_array(x[0] / x[1]),
440
441    def backward(self, indexes, grad_outputs):
442        x0, x1 = self.get_retained_inputs()
443        is_grad_elementwise = x0.shape == x1.shape
444        divgrad = DivGrad(is_grad_elementwise)
445        return divgrad.apply((x0, x1, grad_outputs[0]))
446
447
448class DivGrad(function_node.FunctionNode):
449
450    def __init__(self, is_elementwise):
451        self.is_elementwise = is_elementwise
452
453    def forward_cpu(self, inputs):
454        self.retain_inputs((0, 1, 2))
455        x0, x1, gy = inputs
456        gx0 = utils.force_array(gy / x1)
457        gx1 = utils.force_array(-gx0 * x0 / x1)
458        return utils.sum_to(gx0, x0.shape), utils.sum_to(gx1, x1.shape)
459
460    def forward_gpu(self, inputs):
461        self.retain_inputs((0, 1, 2))
462        x0, x1, gy = inputs
463        gx0, gx1 = cuda.elementwise(
464            'T x0, T x1, T gy',
465            'T gx0, T gx1',
466            '''
467               gx0 = gy / x1;
468               gx1 = -gx0 * x0 / x1;
469            ''', 'div_bwd')(x0, x1, gy)
470        return utils.sum_to(gx0, x0.shape), utils.sum_to(gx1, x1.shape)
471
472    def backward(self, indexes, grad_outputs):
473        x0, x1, gy = self.get_retained_inputs()
474        ggx0, ggx1 = grad_outputs
475
476        ret = []
477        x1_square = x1 * x1
478
479        if 0 in indexes:
480            if ggx1 is None:
481                ret.append(None)
482            else:
483                gx0 = -ggx1 * gy / x1_square
484                ret.append(chainer.functions.sum_to(gx0, x0.shape))
485
486        if 1 in indexes:
487            gx1 = None if ggx0 is None else -ggx0 * gy / x1_square
488            gx1_1 = (None if ggx1 is None else
489                     ggx1 * 2 * gy * x0 / (x1_square * x1))
490            if gx1 is None:
491                gx1 = gx1_1
492            elif gx1_1 is not None:
493                gx1 += gx1_1
494            ret.append(None if gx1 is None else
495                       chainer.functions.sum_to(gx1, x1.shape))
496
497        if 2 in indexes:
498            ggy = None if ggx0 is None else ggx0 / x1
499            ggy_1 = None if ggx1 is None else ggx1 * x0 / x1_square
500            if ggy is None:
501                ggy = -ggy_1
502            elif ggy_1 is not None:
503                ggy -= ggy_1
504            ret.append(ggy)
505
506        return ret
507
508
509def div(self, rhs):  # lhs / rhs
510    """Element-wise division
511
512    Returns:
513        ~chainer.Variable: Output variable.
514    """
515    if numpy.isscalar(rhs):
516        return MulConstant(1. / rhs).apply((self,))[0]
517    rhs = _preprocess_rhs(self, rhs)
518    return Div().apply((self, rhs))[0]
519
520
521# TODO(sonots): Support chainerx
522class DivFromConstant(function_node.FunctionNode):
523
524    is_elementwise = True
525
526    def __init__(self, value):
527        self.value = value
528
529    @property
530    def label(self):
531        return '%s / _' % _convert_value_to_string(self.value)
532
533    def check_type_forward(self, in_types):
534        type_check._argname(in_types, ('x',))
535        type_check.expect(in_types[0].dtype.kind == 'f')
536
537    def forward(self, x):
538        self.retain_inputs((0,))
539        value = _preprocess_const(x[0], self.value)
540        return utils.force_array(value / x[0]),
541
542    def backward(self, indexes, grad_outputs):
543        x = self.get_retained_inputs()
544        return DivFromConstantGrad(self.value).apply((x[0], grad_outputs[0]))
545
546
547class DivFromConstantGrad(function_node.FunctionNode):
548
549    def __init__(self, value):
550        super(DivFromConstantGrad, self).__init__()
551        self.value = value
552
553    def forward_cpu(self, inputs):
554        self.retain_inputs((0, 1))
555        x, gy = inputs
556        value = _preprocess_const(x, self.value)
557        return utils.force_array(-value * gy / (x ** 2)),
558
559    def forward_gpu(self, inputs):
560        self.retain_inputs((0, 1))
561        x, gy = inputs
562        # TODO(beam2d): Make it not use the input
563        value = _preprocess_const(x, self.value)
564        return cuda.elementwise('T x, T gy, T value', 'T gx',
565                                'gx = -value * gy / (x * x)',
566                                'div_from_const_bwd')(x, gy, value),
567
568    def backward(self, indexes, grad_outputs):
569        x, gy = self.get_retained_inputs()
570        value = _preprocess_const(x.data, self.value)
571        ret = []
572        if 0 in indexes:
573            ret.append(grad_outputs[0] * 2 * value * gy / (x ** 3))
574        if 1 in indexes:
575            ret.append(grad_outputs[0] * -value / (x ** 2))
576        return ret
577
578
579def rdiv(self, rhs):  # rhs / lhs
580    """Element-wise division.
581
582    Returns:
583        ~chainer.Variable: Output variable.
584    """
585    if numpy.isscalar(rhs):
586        return DivFromConstant(rhs).apply((self,))[0]
587    rhs = _preprocess_rhs(self, rhs)
588    return Div().apply((rhs, self))[0]
589
590
591def floordiv(self, rhs):  # lhs // rhs
592    """Element-wise floor division.
593
594    Returns:
595        ~chainer.Variable: Output variable.
596    """
597
598    return _floor.floor(div(self, rhs))
599
600
601def rfloordiv(self, rhs):  # rhs // lhs
602    """Element-wise floor division.
603
604    Returns:
605        ~chainer.Variable: Output variable.
606    """
607
608    return _floor.floor(rdiv(self, rhs))
609
610
611class PowVarVar(function_node.FunctionNode):
612
613    is_elementwise = True
614
615    @property
616    def label(self):
617        return '_ ** _'
618
619    def check_type_forward(self, in_types):
620        type_check._argname(in_types, ('lhs', 'rhs'))
621        type_check.expect(
622            in_types[0].dtype.kind == 'f',
623            in_types[0].dtype == in_types[1].dtype,
624        )
625        type_check.expect_broadcast_shapes(
626            in_types[0].shape, in_types[1].shape)
627
628    def forward(self, x):
629        self.retain_inputs((0, 1))
630        # may broadcast
631        self.y = x[0] ** x[1]
632        return utils.force_array(self.y),
633
634    def backward(self, indexes, gy):
635        x0, x1 = self.get_retained_inputs()
636        is_grad_elementwise = x0.shape == x1.shape
637        return PowVarVarGrad(
638            is_grad_elementwise, self.y).apply((x0, x1, gy[0]))
639
640
641class PowVarVarGrad(function_node.FunctionNode):
642
643    def __init__(self, is_elementwise, y):
644        self.is_elementwise = is_elementwise
645        self.y = y
646
647    def check_type_forward(self, in_types):
648        type_check._argname(in_types, ('lhs', 'rhs', 'gy'))
649        type_check.expect(
650            in_types[0].dtype.kind == 'f',
651            in_types[0].dtype == in_types[1].dtype,
652            in_types[0].dtype == in_types[2].dtype,
653        )
654
655    def forward_cpu(self, inputs):
656        self.retain_inputs((0, 1, 2))
657        x0, x1, gy = inputs
658
659        one = x1.dtype.type(1)
660        gx0 = utils.sum_to(
661            utils.force_array(x1 * (x0 ** (x1 - one)) * gy), x0.shape)
662        gx1 = utils.sum_to(
663            utils.force_array(numpy.log(x0) * self.y * gy), x1.shape)
664        return gx0, gx1
665
666    def forward_gpu(self, inputs):
667        self.retain_inputs((0, 1, 2))
668        x0, x1, gy = inputs
669
670        gx0, gx1 = cuda.elementwise(
671            'T x0, T x1, T gy, T y', 'T gx0, T gx1',
672            '''
673            gx0 = x1 * pow(x0, x1 - 1) * gy;
674            gx1 = log(x0) * y * gy;
675            ''', 'pow_var_var_bwd')(x0, x1, gy, self.y)
676
677        gx0 = utils.sum_to(gx0, x0.shape)
678        gx1 = utils.sum_to(gx1, x1.shape)
679
680        return gx0, gx1
681
682    def backward(self, indexes, ggx):
683        x0, x1, gy = self.get_retained_inputs()
684        ggx0, ggx1 = ggx
685
686        log_x0 = chainer.functions.log(x0)
687        pow_x0_x1 = x0 ** x1
688        pow_x0_x1_1 = x0 ** (x1 - 1)
689        pow_x0_x1_2 = x0 ** (x1 - 2)
690
691        ret = []
692        if 0 in indexes:
693            gx0_0 = (0 if ggx0 is None else
694                     ggx0 * x1 * (x1 - 1) * pow_x0_x1_2)
695            gx0_1 = (0 if ggx1 is None else
696                     ggx1 * pow_x0_x1_1 * (log_x0 * x1 + 1))
697            gx0 = (gx0_0 + gx0_1) * gy
698            ret.append(chainer.functions.sum_to(gx0, x0.shape))
699        if 1 in indexes:
700            gx1_0 = (0 if ggx0 is None else
701                     ggx0 * pow_x0_x1_1 * (log_x0 * x1 + 1))
702            gx1_1 = (0 if ggx1 is None else
703                     ggx1 * log_x0 * log_x0 * pow_x0_x1)
704            gx1 = (gx1_0 + gx1_1) * gy
705            ret.append(chainer.functions.sum_to(gx1, x1.shape))
706        if 2 in indexes:
707            ggy_0 = 0 if ggx0 is None else ggx0 * x1 * pow_x0_x1_1
708            ggy_1 = 0 if ggx1 is None else ggx1 * log_x0 * pow_x0_x1
709            ggy = ggy_0 + ggy_1
710            ret.append(ggy)
711        return ret
712
713
714class PowVarConst(function_node.FunctionNode):
715
716    is_elementwise = True
717
718    def __init__(self, value):
719        self.value = value
720
721    @property
722    def label(self):
723        return '_ ** %s' % _convert_value_to_string(self.value)
724
725    def check_type_forward(self, in_types):
726        type_check._argname(in_types, ('x',))
727        type_check.expect(in_types[0].dtype.kind == 'f')
728
729    def forward(self, x):
730        self.retain_inputs((0,))
731        y = x[0] ** _preprocess_const(x[0], self.value)
732        return utils.force_array(y, x[0].dtype),
733
734    def backward(self, indexes, gy):
735        inputs = self.get_retained_inputs()
736        return PowVarConstGrad(self.value).apply((inputs[0], gy[0]))
737
738
739class PowVarConstGrad(function_node.FunctionNode):
740
741    is_elementwise = True
742
743    def __init__(self, value):
744        self.value = value
745        self.val = self.val_1 = None
746
747    def check_type_forward(self, in_types):
748        type_check._argname(in_types, ('x', 'gy'))
749        type_check.expect(
750            in_types[0].dtype.kind == 'f',
751            in_types[0].dtype == in_types[1].dtype,
752            in_types[0].shape == in_types[1].shape
753        )
754
755    def forward_cpu(self, inputs):
756        self.retain_inputs((0, 1))
757        x, gy = inputs
758
759        self.val_1 = _preprocess_const(x, self.value - 1)
760        gx = utils.force_type(x.dtype, self.value) * (x ** self.val_1) * gy
761        gx = utils.force_array(gx)
762        return gx,
763
764    def forward_gpu(self, inputs):
765        self.retain_inputs((0, 1))
766        x, gy = inputs
767
768        self.val = _preprocess_const(x, self.value)
769        gx = cuda.elementwise(
770            'T x, T gy, T value', 'T gx',
771            'gx = value * pow(x, value - 1) * gy',
772            'pow_var_const_bwd')(x, gy, self.val)
773        return gx,
774
775    def backward(self, indexes, ggx):
776        x, gy = self.get_retained_inputs()
777
778        if self.val is None:
779            self.val = _preprocess_const(x.data, self.value)
780        if self.val_1 is None:
781            self.val_1 = _preprocess_const(x.data, self.value - 1)
782        val_2 = _preprocess_const(x.data, self.value - 2)
783
784        ret = []
785        if 0 in indexes:
786            ret.append(ggx[0] * self.val * gy * self.val_1 * x ** val_2)
787        if 1 in indexes:
788            ret.append(ggx[0] * self.val * x ** self.val_1)
789        return ret
790
791
792def pow(self, rhs):  # lhs ** rhs
793    """Element-wise power function.
794
795    Returns:
796        ~chainer.Variable: Output variable.
797    """
798
799    if numpy.isscalar(rhs):
800        return PowVarConst(rhs).apply((self,))[0]
801    rhs = _preprocess_rhs(self, rhs)
802    return PowVarVar().apply((self, rhs))[0]
803
804
805class PowConstVar(function_node.FunctionNode):
806
807    is_elementwise = True
808
809    def __init__(self, value):
810        self.value = value
811
812    @property
813    def label(self):
814        return '%s ** _' % _convert_value_to_string(self.value)
815
816    def check_type_forward(self, in_types):
817        type_check._argname(in_types, ('x',))
818        type_check.expect(in_types[0].dtype.kind == 'f')
819
820    def forward(self, x):
821        self.retain_outputs((0,))
822        value = _preprocess_const(x[0], self.value)
823        y = value ** x[0]
824        return utils.force_array(y),
825
826    def backward(self, indexes, gy):
827        outputs = self.get_retained_outputs()
828        return PowConstVarGrad(self.value).apply((outputs[0], gy[0]))
829
830
831class PowConstVarGrad(function_node.FunctionNode):
832
833    is_elementwise = True
834
835    def __init__(self, value):
836        self.value = value
837        self.log_value = math.log(value)
838
839    def check_type_forward(self, in_types):
840        type_check._argname(in_types, ('y', 'gy'))
841        type_check.expect(
842            in_types[0].dtype.kind == 'f',
843            in_types[0].dtype == in_types[1].dtype,
844            in_types[0].shape == in_types[1].shape
845        )
846
847    def forward_cpu(self, inputs):
848        self.retain_inputs((0, 1))
849        y, gy = inputs
850
851        gx = utils.force_array(y.dtype.type(self.log_value) * y * gy)
852        return gx,
853
854    def forward_gpu(self, inputs):
855        self.retain_inputs((0, 1))
856        y, gy = inputs
857
858        value = _preprocess_const(y, self.value)
859        gx = cuda.elementwise(
860            'T y, T gy, T value', 'T gx',
861            'gx = log(value) * y * gy',
862            'pow_const_var_bwd')(y, gy, value)
863        return gx,
864
865    def backward(self, indexes, ggx):
866        y, gy = self.get_retained_inputs()
867
868        gygy = y.dtype.type(self.log_value) * ggx[0]
869
870        ret = []
871        if 0 in indexes:
872            ret.append(gygy * gy)
873        if 1 in indexes:
874            ret.append(gygy * y)
875        return ret
876
877
878def rpow(self, rhs):  # rhs ** lhs
879    """Element-wise power function.
880
881    Returns:
882        ~chainer.Variable: Output variable.
883    """
884
885    if numpy.isscalar(rhs):
886        return PowConstVar(rhs).apply((self,))[0]
887    rhs = _preprocess_rhs(self, rhs)
888    return PowVarVar().apply((rhs, self))[0]
889
890
891def matmul(self, rhs):  # lhs @ rhs
892    """Matrix multiplication.
893
894    Returns:
895        ~chainer.Variable: Output variable.
896    """
897
898    rhs = _preprocess_rhs(self, rhs)
899    return chainer.functions.matmul(self, rhs)
900
901
902def rmatmul(self, rhs):  # rhs @ lhs
903    """Matrix multiplication.
904
905    Returns:
906        ~chainer.Variable: Output variable.
907    """
908
909    rhs = _preprocess_rhs(self, rhs)
910    return chainer.functions.matmul(rhs, self)
911
912
913def install_variable_arithmetics():
914    variable.Variable.__neg__ = neg
915    variable.Variable.__abs__ = absolute
916    variable.Variable.__add__ = add
917    variable.Variable.__radd__ = add
918    variable.Variable.__sub__ = sub
919    variable.Variable.__rsub__ = rsub
920    variable.Variable.__mul__ = mul
921    variable.Variable.__rmul__ = mul
922    variable.Variable.__div__ = div
923    variable.Variable.__truediv__ = div
924    variable.Variable.__rdiv__ = rdiv
925    variable.Variable.__rtruediv__ = rdiv
926    variable.Variable.__floordiv__ = floordiv
927    variable.Variable.__rfloordiv__ = rfloordiv
928    variable.Variable.__pow__ = pow
929    variable.Variable.__rpow__ = rpow
930    variable.Variable.__matmul__ = matmul
931    variable.Variable.__rmatmul__ = rmatmul
932