File Coverage

blib/lib/App/Math/Tutor/Cmd/Poly/Cmd/Solve.pm
Criterion Covered Total %
statement 30 47 63.8
branch 0 4 0.0
condition n/a
subroutine 10 12 83.3
pod n/a
total 40 63 63.4


line stmt bran cond sub pod time code
1             package App::Math::Tutor::Cmd::Poly::Cmd::Solve;
2              
3 1     1   4288 use warnings;
  1         4  
  1         36  
4 1     1   5 use strict;
  1         2  
  1         37  
5              
6 1     1   5 use vars qw(@ISA $VERSION);
  1         3  
  1         145  
7              
8             =head1 NAME
9              
10             App::Math::Tutor::Cmd::Poly::Cmd::Solve - Plugin for solving polynoms
11              
12             =cut
13              
14             our $VERSION = '0.004';
15              
16 1     1   7 use Moo;
  1         2  
  1         9  
17 1     1   296 use MooX::Cmd;
  1         1  
  1         6  
18 1     1   1668 use MooX::Options;
  1         2  
  1         7  
19              
20 1     1   1239 use Carp qw(croak);
  1         2  
  1         58  
21 1     1   5 use Scalar::Util qw/blessed dualvar/;
  1         2  
  1         44  
22 1     1   5 use Math::Complex;
  1         2  
  1         305  
23              
24             has template_filename => (
25             is => "ro",
26             default => "onecolmlsol"
27             );
28              
29             with "App::Math::Tutor::Role::PolyExercise";
30              
31 1     1   5902 use Math::Prime::Util qw(factor prime_precalc);
  1         14586  
  1         8  
32              
33             =head2 complex_solution
34              
35             Specifies whether solution can become complex or not
36              
37             =cut
38              
39             option complex_solution => (
40             is => "ro",
41             doc => "Specifies whether solution can become complex or not",
42             long_doc => "Hand's over control whether the solution has to be "
43             . "a 'real' number or can get complex, respectively.\n\n"
44             . "Default: no",
45             default => sub { return 0; },
46             short => "c",
47             negativable => 1,
48             );
49              
50             sub _extract_sqrt
51             {
52 0     0     my ( $self, $num, $exp ) = @_;
53 0           my @nf = factor( abs($num) );
54 0           my %nf;
55 0           ++$nf{$_} for (@nf);
56 0           my $bf = 1;
57 0           my $rm = 1;
58 0           foreach my $n ( sort keys %nf )
59             {
60 0           my $o = delete $nf{$n};
61 0           my $c = $o;
62 0           $c -= $exp while ( $c >= $exp );
63 0 0         $c and $rm *= $c * $n;
64 0 0         $o != $c and $bf *= $n**( ( $o - $c ) / $exp );
65             }
66 0           return ( $bf, $rm );
67             }
68              
69             sub _check_sqrt
70             {
71 0     0     my ( $self, $num, $exp ) = @_;
72 0           my ( $bf, $rm ) = $self->_extract_sqrt( $num, $exp );
73 0           my $format = $self->format;
74 0           return $rm <= $format;
75             }
76              
77             around _check_polynom => sub {
78             my $orig = shift;
79             my $self = shift;
80             $self->$orig(@_) or return;
81              
82             my @values = @{ $_[0]->values };
83             $values[0]->exponent == 2 or return; # XXX
84             my @fac = (0) x $values[0]->exponent;
85             $fac[ $_->exponent ] = $_->factor for (@values);
86             my ( $a, $b, $c ) =
87             @fac; # ( $values[0]->factor, $values[1]->factor || 0, $values[2]->factor || 0 );
88             $a == 0 and return;
89             my ( $p, $q ) = (
90             VulFrac->new(
91             num => $b,
92             denum => $a
93             )->_reduce,
94             VulFrac->new(
95             num => $c,
96             denum => $a
97             )->_reduce
98             );
99             my $p2 = VulFrac->new(
100             num => $p->num * $p->num,
101             denum => $p->denum * $p->denum * 4
102             )->_reduce;
103             my $gcd = VulFrac->new(
104             num => $p2->denum,
105             denum => $q->denum
106             )->_gcd;
107             my ( $fp, $fq ) = ( $q->{denum} / $gcd, $p2->{denum} / $gcd );
108             my $d = VulFrac->new( num => $p2->num * $fp - $q->sign * $q->num * $fq,
109             denum => $p2->denum * $fp );
110             $d->sign < 0 and !$self->complex_solution and return;
111             $d->{num} = abs( $d->{num} );
112             $d = $d->_reduce;
113             return $self->_check_sqrt( $d->num, $values[0]->exponent )
114             and $self->_check_sqrt( $d->denum, $values[0]->exponent );
115             };
116              
117             my $a_plus_b = sub {
118             return
119             PolyNum->new( operator => $_[0],
120             values => [ splice @_, 1 ] );
121             };
122             my $a_mult_b = sub {
123             return
124             ProdNum->new( operator => $_[0],
125             values => [ splice @_, 1 ] );
126             };
127             my $sqr = sub {
128             return
129             Power->new(
130             basis => $_[0],
131             exponent => 2,
132             mode => defined $_[1] ? $_[1] : 0
133             );
134             };
135             my $sqrt = sub {
136             return
137             Power->new(
138             basis => $_[0],
139             exponent => VulFrac->new(
140             num => 1,
141             denum => 2
142             ),
143             mode => defined $_[1] ? $_[1] : 1
144             );
145             };
146              
147             sub _get_quad_solution
148             {
149             my ( $self, $poly ) = @_;
150             my ( @orig, @way, @solution );
151             push @orig, 0, "$poly";
152              
153             my @values = @{ $poly->values };
154             my @rvalues;
155             my @pqvalues;
156             my $reduced = 0;
157             my $a_f = $values[0]->factor;
158             my ( $p, $q );
159             foreach my $i ( 1 .. $#values )
160             {
161             my $exp = $values[$i]->exponent;
162             my $f = VulFrac->new( num => $values[$i]->factor,
163             denum => $a_f );
164             push(
165             @pqvalues,
166             PolyTerm->new(
167             factor => $f,
168             exponent => $exp
169             )
170             );
171             $f->_gcd > 1 and ++$reduced and $f = $f->_reduce;
172             push(
173             @rvalues,
174             PolyTerm->new(
175             factor => $f,
176             exponent => $exp
177             )
178             );
179             0 == $exp and $q = $f;
180             1 == $exp and $p = $f;
181             }
182             defined $p
183             or $p = VulFrac->new( num => 0,
184             denum => $a_f );
185             defined $q
186             or $q = VulFrac->new( num => 0,
187             denum => $a_f );
188             unshift( @pqvalues, PolyTerm->new( exponent => $values[0]->exponent ) );
189             unshift( @rvalues, PolyTerm->new( exponent => $values[0]->exponent ) );
190             $reduced and push @orig,
191             PolyNum->new( values => \@pqvalues,
192             operator => "+" );
193             push(
194             @orig,
195             PolyNum->new(
196             values => \@rvalues,
197             operator => "+"
198             )
199             );
200              
201             push( @solution, '$ ' . join( " = ", @orig ) . ' $' );
202              
203             push @way, "X_{1/2}";
204              
205             my $d = PolyNum->new(
206             values => [
207             $sqr->(
208             VulFrac->new(
209             num => $p,
210             denum => 2
211             )
212             ),
213             $q
214             ],
215             operator => "-",
216             );
217              
218             my $X12 = PolyNum->new(
219             operator => '\pm',
220             values => [
221             VulFrac->new(
222             num => $p,
223             denum => 2,
224             sign => -1
225             ),
226             $sqrt->($d),
227             ]
228             );
229             push @way, "$X12";
230              
231             if ($p)
232             {
233             $p = VulFrac->new(
234             num => $p->num,
235             denum => $a_mult_b->( '*', $p->denum, 2 ),
236             sign => $p->sign
237             );
238             $d = PolyNum->new( values => [ $sqr->($p), $q ],
239             operator => "-", );
240              
241             $X12 = PolyNum->new( operator => '\pm',
242             values => [ $p->_neg, $sqrt->($d) ] );
243             push @way, "$X12";
244              
245             $p = VulFrac->new(
246             num => $p->num,
247             denum => int( $p->denum ),
248             sign => $p->sign
249             )->_reduce;
250             $d = PolyNum->new( values => [ $sqr->($p), $q ],
251             operator => "-", );
252              
253             $X12 = PolyNum->new( operator => '\pm',
254             values => [ $p->_neg, $sqrt->($d) ] );
255             push @way, "$X12";
256              
257             my $p2 = VulFrac->new(
258             num => $p->num * $p->num,
259             denum => $p->denum * $p->denum,
260             sign => 1
261             )->_reduce;
262             $d = PolyNum->new( values => [ $p2, $q ],
263             operator => "-", );
264              
265             $X12 = PolyNum->new( operator => '\pm',
266             values => [ $p->_neg, $sqrt->($d) ] );
267             push @way, "$X12";
268              
269             if ($q)
270             {
271             my $gcd = VulFrac->new(
272             num => $p2->denum,
273             denum => $q->denum
274             )->_gcd;
275             my ( $fp, $fq ) = ( $q->{denum} / $gcd, $p2->{denum} / $gcd );
276             $d = PolyNum->new(
277             values => [
278             VulFrac->new(
279             num => $a_mult_b->( '*', $p2->num, $fp ),
280             denum => $a_mult_b->( '*', $p2->denum, $fp ),
281             sign => $p2->sign
282             ),
283             VulFrac->new(
284             num => $a_mult_b->( '*', $q->num, $fq ),
285             denum => $a_mult_b->( '*', $q->denum, $fq ),
286             sign => $q->sign
287             ),
288             ],
289             operator => "-",
290             );
291             $X12 = PolyNum->new( operator => '\pm',
292             values => [ $p->_neg, $sqrt->($d) ] );
293             push @way, "$X12";
294              
295             $d = VulFrac->new( num => $p2->num * $fp - $q->sign * $q->num * $fq,
296             denum => $q->denum * $fq );
297             $X12 = PolyNum->new( operator => '\pm',
298             values => [ $p->_neg, $sqrt->($d) ] );
299             push @way, "$X12";
300             }
301             }
302             elsif ($q)
303             {
304             $d = $q->_neg;
305             $X12 = PolyNum->new( operator => '\pm',
306             values => [ $p->_neg, $sqrt->($d) ] );
307             }
308              
309             if ( "VulFrac" eq ref($d) )
310             {
311             my ( $nbf, $nrm ) = $self->_extract_sqrt( $d->num, 2 );
312             my ( $dbf, $drm ) = $self->_extract_sqrt( $d->denum, 2 );
313              
314             if ( $nbf != 1 or $dbf != 1 )
315             {
316             $X12 = PolyNum->new(
317             operator => '\pm',
318             values => [
319             $p->_neg,
320             VulFrac->new(
321             num =>
322             Power->new(
323             basis => $d->sign * $nrm,
324             factor => $nbf,
325             exponent =>
326             VulFrac->new(
327             num => 1,
328             denum => 2
329             ),
330             mode => 1,
331             ),
332             denum =>
333             Power->new(
334             basis => $drm,
335             factor => $dbf,
336             exponent =>
337             VulFrac->new(
338             num => 1,
339             denum => 2
340             ),
341             mode => 1
342             ),
343             sign => dualvar( 1, "" ),
344             ),
345             ]
346             );
347             push @way, "$X12";
348             }
349             }
350              
351             push( @solution, '$ ' . join( " = ", @way ) . ' $' );
352              
353             #if ( $d >= 0 )
354             {
355             my $X1 = ref($X12)->new( operator => "+",
356             values => $X12->values );
357             my ( $digits, $x1, $x2 ) = (6); # $self->digits;
358             my $dplus = $digits + 1;
359             my $fmt = sub {
360             my $x = shift;
361             my ( $xs, $approx ) = ( "" . $x, 0 );
362             $x->display_format( 'format' => "%.${digits}f" ) and $xs = "" . $x and ++$approx
363             if ( $xs =~ m/e\d+/i or $xs =~ m/\.\d{${dplus}}/ );
364             push @way, $xs;
365             push @solution, '$ ' . join( $approx ? " \\approx " : " = ", @way ) . ' $';
366             };
367              
368             @way = "X_{1}";
369             $fmt->( $X1->_numify );
370              
371             my $X2 = ref($X12)->new( operator => "-",
372             values => $X12->values );
373              
374             @way = "X_{2}";
375             $fmt->( $X2->_numify );
376             }
377              
378             return @solution;
379             }
380              
381             sub _build_exercises
382             {
383             my ($self) = @_;
384             my (@tasks);
385             my $mf = Math::Prime::Util::MemFree->new;
386              
387             foreach my $i ( 1 .. $self->quantity )
388             {
389             my @line;
390             push @line, $self->get_polynom(1);
391             push @tasks, \@line;
392             }
393              
394             my $exercises = {
395             section => "Polynom Solving",
396             caption => 'Polynoms',
397             label => 'polynom_solving',
398             header => [ ['Polynom Solve'] ],
399             solutions => [],
400             challenges => [],
401             };
402              
403             foreach my $line (@tasks)
404             {
405             my ( @solution, @challenge );
406              
407             my ($a) = @{$line};
408             push( @challenge, "\$ $a = 0 \$" );
409             $a->values->[0]->exponent > 2 and die "No way to solve polynoms of power 3 or higher";
410             $a->values->[0]->exponent == 2 and push @solution, $self->_get_quad_solution($a);
411              
412             push( @{ $exercises->{solutions} }, \@solution );
413             push( @{ $exercises->{challenges} }, \@challenge );
414             }
415              
416             return $exercises;
417             }
418              
419             =head1 LICENSE AND COPYRIGHT
420              
421             Copyright 2010-2014 Jens Rehsack.
422              
423             This program is free software; you can redistribute it and/or modify it
424             under the terms of either: the GNU General Public License as published
425             by the Free Software Foundation; or the Artistic License.
426              
427             See http://dev.perl.org/licenses/ for more information.
428              
429             =cut
430              
431             1;