package Math::Bacovia::Power;
use 5.014;
use warnings;
use Class::Multimethods qw();
use parent qw(Math::Bacovia);
our $VERSION = $Math::Bacovia::VERSION;
sub new {
my ($class, $base, $power) = @_;
Math::Bacovia::_check_type(\$base);
if (defined($power)) {
Math::Bacovia::_check_type(\$power);
}
else {
$power = $Math::Bacovia::ONE;
}
bless {
base => $base,
power => $power,
}, $class;
}
sub get {
my ($x) = @_;
($x->{base}, $x->{power});
}
#
## Operations
#
#~ Class::Multimethods::multimethod pow => (__PACKAGE__, 'Math::Bacovia') => sub {
#~ my ($x, $y) = @_;
#~ __PACKAGE__->new($x->{base}, $x->{power} * $y);
#~ };
sub inv {
my ($x) = @_;
$x->{_inv} //= __PACKAGE__->new($x->{base}, $x->{power}->neg);
}
Class::Multimethods::multimethod mul => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
if (ref($x->{base}) eq ref($y->{base})
and $x->{base}->eq($y->{base})) {
__PACKAGE__->new($x->{base}, $x->{power} + $y->{power});
}
else {
'Math::Bacovia::Product'->new($x, $y);
}
};
Class::Multimethods::multimethod div => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
if (ref($x->{base}) eq ref($y->{base})
and $x->{base}->eq($y->{base})) {
__PACKAGE__->new($x->{base}, $x->{power} - $y->{power});
}
else {
'Math::Bacovia::Fraction'->new($x, $y);
}
};
#
## Equality
#
Class::Multimethods::multimethod eq => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
(ref($x->{base}) eq ref($y->{base}))
&& (ref($x->{power}) eq ref($y->{power}))
&& ($x->{base}->eq($y->{base}))
&& ($x->{power}->eq($y->{power}));
};
Class::Multimethods::multimethod eq => (__PACKAGE__, '*') => sub {
!1;
};
#
## Transformations
#
sub numeric {
my ($x) = @_;
($x->{base}->numeric)**($x->{power}->numeric);
}
sub pretty {
my ($x) = @_;
$x->{_pretty} //= do {
my $base = $x->{base}->pretty();
my $pow = $x->{power}->pretty();
if (substr($base, 0, 1) eq '-'
or ref($x->{base}) eq __PACKAGE__) {
"($base)^$pow";
}
else {
"$base^$pow";
}
}
}
sub stringify {
my ($x) = @_;
$x->{_str} //= "Power(" . $x->{base}->stringify() . ', ' . $x->{power}->stringify() . ")";
}
#
## Alternatives
#
sub alternatives {
my ($self, %opt) = @_;
$self->{_alt} //= do {
my @a1 = $self->{base}->alternatives(%opt);
my @a2 = $self->{power}->alternatives(%opt);
my @alt;
foreach my $x (@a1) {
foreach my $y (@a2) {
push @alt, $x**$y;
if ($opt{full}) {
push @alt, __PACKAGE__->new($x, $y);
}
if ($opt{full} or $opt{log}) {
push @alt, 'Math::Bacovia::Exp'->new('Math::Bacovia::Log'->new($x) * $y);
}
# Identity: x^0 = 1
if (ref($y) eq 'Math::Bacovia::Number' and $y->{value} == 0) {
push @alt, $Math::Bacovia::ONE;
}
# Identity: 1^x = 1
if (ref($x) eq 'Math::Bacovia::Number' and $x->{value} == 1) {
push @alt, $x;
}
# Identity: x^1 = x
if (ref($y) eq 'Math::Bacovia::Number' and $y->{value} == 1) {
push @alt, $x;
}
if ( ref($x) eq 'Math::Bacovia::Number'
and ref($y) eq 'Math::Bacovia::Number') {
# Integer or rational exponentation
if (ref(${$y->{value}}) eq 'Math::GMPz') {
my $ref_x = ref(${$x->{value}});
if ($ref_x eq 'Math::GMPz' or $ref_x eq 'Math::GMPq') {
push @alt, 'Math::Bacovia::Number'->new($x->{value}**$y->{value});
}
}
}
# Identity: (a/b)^x = a^x / b^x
#~ if (ref($x) eq 'Math::Bacovia::Fraction') {
#~ push @alt, $x->{num}**$y / $x->{den}**$y;
#~ }
# Identity: x^2 = x*x
#~ if ($y == 2) {
#~ push @alt, $x * $x;
#~ }
# Identity: x^log(y) = y^log(x)
if (ref($y) eq 'Math::Bacovia::Log') {
push @alt, $y->{value}**('Math::Bacovia::Log'->new($x));
}
# Identity: exp(x)^log(y) = y^x
if ( ref($x) eq 'Math::Bacovia::Exp'
and ref($y) eq 'Math::Bacovia::Exp') {
push @alt, ($y->{value}**$x->{value});
}
# Identity: exp(x)^y = exp(y*x)
#~ if (ref($x) eq 'Math::Bacovia::Exp') {
#~ push @alt, 'Math::Bacovia::Exp'->new($x->{value} * $y);
#~ }
# Identity: x^(y/log(x)) = exp(y)
if ( ref($y) eq 'Math::Bacovia::Fraction'
and ref($y->{den}) eq 'Math::Bacovia::Log'
and ref($x) eq ref($y->{den}{value})
and $x->eq($y->{den}{value})) {
if (ref($y->{num}) eq 'Math::Bacovia::Log') {
push @alt, $y->{num}{value};
}
else {
push @alt, 'Math::Bacovia::Exp'->new($y->{num})->alternatives(%opt);
}
}
}
}
[List::UtilsBy::uniq_by { $_->stringify } @alt];
};
@{$self->{_alt}};
}
1;