File Coverage

blib/lib/App/Math/Tutor/Cmd/Poly/Cmd/Solve.pm
Criterion Covered Total %
statement 30 142 21.1
branch 0 34 0.0
condition 0 15 0.0
subroutine 10 15 66.6
pod n/a
total 40 206 19.4


line stmt bran cond sub pod time code
1             package App::Math::Tutor::Cmd::Poly::Cmd::Solve;
2              
3 1     1   5376 use warnings;
  1         2  
  1         35  
4 1     1   4 use strict;
  1         1  
  1         29  
5              
6 1     1   3 use vars qw(@ISA $VERSION);
  1         1  
  1         72  
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.005';
15              
16 1     1   4 use Moo;
  1         1  
  1         6  
17 1     1   250 use MooX::Cmd;
  1         2  
  1         6  
18 1     1   2187 use MooX::Options;
  1         3  
  1         9  
19              
20 1     1   1724 use Carp qw(croak);
  1         2  
  1         91  
21 1     1   9 use Scalar::Util qw/blessed dualvar/;
  1         3  
  1         106  
22 1     1   8 use Math::Complex;
  1         2  
  1         278  
23              
24             has template_filename => (
25             is => "ro",
26             default => "onecolmlsol"
27             );
28              
29             with "App::Math::Tutor::Role::PolyExercise";
30              
31 1     1   1064 use Math::Prime::Util qw(factor prime_precalc);
  1         7724  
  1         6  
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 { 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           ( $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           $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 ) = @fac; # ( $values[0]->factor, $values[1]->factor || 0, $values[2]->factor || 0 );
87             $a == 0 and return;
88             my ( $p, $q ) = (
89             VulFrac->new(
90             num => $b,
91             denum => $a
92             )->_reduce,
93             VulFrac->new(
94             num => $c,
95             denum => $a
96             )->_reduce
97             );
98             my $p2 = VulFrac->new(
99             num => $p->num * $p->num,
100             denum => $p->denum * $p->denum * 4
101             )->_reduce;
102             my $gcd = VulFrac->new(
103             num => $p2->denum,
104             denum => $q->denum
105             )->_gcd;
106             my ( $fp, $fq ) = ( $q->{denum} / $gcd, $p2->{denum} / $gcd );
107             my $d = VulFrac->new(
108             num => $p2->num * $fp - $q->sign * $q->num * $fq,
109             denum => $p2->denum * $fp
110             );
111             $d->sign < 0 and !$self->complex_solution and return;
112             $d->{num} = abs( $d->{num} );
113             $d = $d->_reduce;
114             $self->_check_sqrt( $d->num, $values[0]->exponent ) and $self->_check_sqrt( $d->denum, $values[0]->exponent );
115             };
116              
117             my $a_plus_b = sub {
118             PolyNum->new(
119             operator => $_[0],
120             values => [ splice @_, 1 ]
121             );
122             };
123             my $a_mult_b = sub {
124             ProdNum->new(
125             operator => $_[0],
126             values => [ splice @_, 1 ]
127             );
128             };
129             my $sqr = sub {
130             Power->new(
131             basis => $_[0],
132             exponent => 2,
133             mode => defined $_[1] ? $_[1] : 0
134             );
135             };
136             my $sqrt = sub {
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 0     0     my ( $self, $poly ) = @_;
150 0           my ( @orig, @way, @solution );
151 0           push @orig, 0, "$poly";
152              
153 0           my @values = @{ $poly->values };
  0            
154 0           my @rvalues;
155             my @pqvalues;
156 0           my $reduced = 0;
157 0           my $a_f = $values[0]->factor;
158 0           my ( $p, $q );
159 0           foreach my $i ( 1 .. $#values )
160             {
161 0           my $exp = $values[$i]->exponent;
162 0           my $f = VulFrac->new(
163             num => $values[$i]->factor,
164             denum => $a_f
165             );
166 0           push(
167             @pqvalues,
168             PolyTerm->new(
169             factor => $f,
170             exponent => $exp
171             )
172             );
173 0 0 0       $f->_gcd > 1 and ++$reduced and $f = $f->_reduce;
174 0           push(
175             @rvalues,
176             PolyTerm->new(
177             factor => $f,
178             exponent => $exp
179             )
180             );
181 0 0         0 == $exp and $q = $f;
182 0 0         1 == $exp and $p = $f;
183             }
184 0 0         defined $p
185             or $p = VulFrac->new(
186             num => 0,
187             denum => $a_f
188             );
189 0 0         defined $q
190             or $q = VulFrac->new(
191             num => 0,
192             denum => $a_f
193             );
194 0           unshift( @pqvalues, PolyTerm->new( exponent => $values[0]->exponent ) );
195 0           unshift( @rvalues, PolyTerm->new( exponent => $values[0]->exponent ) );
196 0 0         $reduced and push @orig,
197             PolyNum->new(
198             values => \@pqvalues,
199             operator => "+"
200             );
201 0           push(
202             @orig,
203             PolyNum->new(
204             values => \@rvalues,
205             operator => "+"
206             )
207             );
208              
209 0           push( @solution, '$ ' . join( " = ", @orig ) . ' $' );
210              
211 0           push @way, "X_{1/2}";
212              
213 0           my $d = PolyNum->new(
214             values => [
215             $sqr->(
216             VulFrac->new(
217             num => $p,
218             denum => 2
219             )
220             ),
221             $q
222             ],
223             operator => "-",
224             );
225              
226 0           my $X12 = PolyNum->new(
227             operator => '\pm',
228             values => [
229             VulFrac->new(
230             num => $p,
231             denum => 2,
232             sign => -1
233             ),
234             $sqrt->($d),
235             ]
236             );
237 0           push @way, "$X12";
238              
239 0 0         if ($p)
    0          
240             {
241 0           $p = VulFrac->new(
242             num => $p->num,
243             denum => $a_mult_b->( '*', $p->denum, 2 ),
244             sign => $p->sign
245             );
246 0           $d = PolyNum->new(
247             values => [ $sqr->($p), $q ],
248             operator => "-",
249             );
250              
251 0           $X12 = PolyNum->new(
252             operator => '\pm',
253             values => [ $p->_neg, $sqrt->($d) ]
254             );
255 0           push @way, "$X12";
256              
257 0           $p = VulFrac->new(
258             num => $p->num,
259             denum => int( $p->denum ),
260             sign => $p->sign
261             )->_reduce;
262 0           $d = PolyNum->new(
263             values => [ $sqr->($p), $q ],
264             operator => "-",
265             );
266              
267 0           $X12 = PolyNum->new(
268             operator => '\pm',
269             values => [ $p->_neg, $sqrt->($d) ]
270             );
271 0           push @way, "$X12";
272              
273 0           my $p2 = VulFrac->new(
274             num => $p->num * $p->num,
275             denum => $p->denum * $p->denum,
276             sign => 1
277             )->_reduce;
278 0           $d = PolyNum->new(
279             values => [ $p2, $q ],
280             operator => "-",
281             );
282              
283 0           $X12 = PolyNum->new(
284             operator => '\pm',
285             values => [ $p->_neg, $sqrt->($d) ]
286             );
287 0           push @way, "$X12";
288              
289 0 0         if ($q)
290             {
291 0           my $gcd = VulFrac->new(
292             num => $p2->denum,
293             denum => $q->denum
294             )->_gcd;
295 0           my ( $fp, $fq ) = ( $q->{denum} / $gcd, $p2->{denum} / $gcd );
296 0           $d = PolyNum->new(
297             values => [
298             VulFrac->new(
299             num => $a_mult_b->( '*', $p2->num, $fp ),
300             denum => $a_mult_b->( '*', $p2->denum, $fp ),
301             sign => $p2->sign
302             ),
303             VulFrac->new(
304             num => $a_mult_b->( '*', $q->num, $fq ),
305             denum => $a_mult_b->( '*', $q->denum, $fq ),
306             sign => $q->sign
307             ),
308             ],
309             operator => "-",
310             );
311 0           $X12 = PolyNum->new(
312             operator => '\pm',
313             values => [ $p->_neg, $sqrt->($d) ]
314             );
315 0           push @way, "$X12";
316              
317 0           $d = VulFrac->new(
318             num => $p2->num * $fp - $q->sign * $q->num * $fq,
319             denum => $q->denum * $fq
320             );
321 0           $X12 = PolyNum->new(
322             operator => '\pm',
323             values => [ $p->_neg, $sqrt->($d) ]
324             );
325 0           push @way, "$X12";
326             }
327             }
328             elsif ($q)
329             {
330 0           $d = $q->_neg;
331 0           $X12 = PolyNum->new(
332             operator => '\pm',
333             values => [ $p->_neg, $sqrt->($d) ]
334             );
335             }
336              
337 0 0         if ( "VulFrac" eq ref($d) )
338             {
339 0           my ( $nbf, $nrm ) = $self->_extract_sqrt( $d->num, 2 );
340 0           my ( $dbf, $drm ) = $self->_extract_sqrt( $d->denum, 2 );
341              
342 0 0 0       if ( $nbf != 1 or $dbf != 1 )
343             {
344 0           $X12 = PolyNum->new(
345             operator => '\pm',
346             values => [
347             $p->_neg,
348             VulFrac->new(
349             num => Power->new(
350             basis => $d->sign * $nrm,
351             factor => $nbf,
352             exponent => VulFrac->new(
353             num => 1,
354             denum => 2
355             ),
356             mode => 1,
357             ),
358             denum => Power->new(
359             basis => $drm,
360             factor => $dbf,
361             exponent => VulFrac->new(
362             num => 1,
363             denum => 2
364             ),
365             mode => 1
366             ),
367             sign => dualvar( 1, "" ),
368             ),
369             ]
370             );
371 0           push @way, "$X12";
372             }
373             }
374              
375 0           push( @solution, '$ ' . join( " = ", @way ) . ' $' );
376              
377             #if ( $d >= 0 )
378             {
379 0           my $X1 = ref($X12)->new(
  0            
380             operator => "+",
381             values => $X12->values
382             );
383 0           my ( $digits, $x1, $x2 ) = (6); # $self->digits;
384 0           my $dplus = $digits + 1;
385             my $fmt = sub {
386 0     0     my $x = shift;
387 0           my ( $xs, $approx ) = ( "" . $x, 0 );
388 0 0 0       $x->display_format( 'format' => "%.${digits}f" ) and $xs = "" . $x and ++$approx
      0        
      0        
389             if ( $xs =~ m/e\d+/i or $xs =~ m/\.\d{${dplus}}/ );
390 0           push @way, $xs;
391 0 0         push @solution, '$ ' . join( $approx ? " \\approx " : " = ", @way ) . ' $';
392 0           };
393              
394 0           @way = "X_{1}";
395 0           $fmt->( $X1->_numify );
396              
397 0           my $X2 = ref($X12)->new(
398             operator => "-",
399             values => $X12->values
400             );
401              
402 0           @way = "X_{2}";
403 0           $fmt->( $X2->_numify );
404             }
405              
406 0           @solution;
407             }
408              
409             sub _build_exercises
410             {
411 0     0     my ($self) = @_;
412 0           my (@tasks);
413 0           my $mf = Math::Prime::Util::MemFree->new;
414              
415 0           foreach my $i ( 1 .. $self->quantity )
416             {
417 0           my @line;
418 0           push @line, $self->get_polynom(1);
419 0           push @tasks, \@line;
420             }
421              
422 0           my $exercises = {
423             section => "Polynom Solving",
424             caption => 'Polynoms',
425             label => 'polynom_solving',
426             header => [ ['Polynom Solve'] ],
427             solutions => [],
428             challenges => [],
429             };
430              
431 0           foreach my $line (@tasks)
432             {
433 0           my ( @solution, @challenge );
434              
435 0           my ($a) = @{$line};
  0            
436 0           push( @challenge, "\$ $a = 0 \$" );
437 0 0         $a->values->[0]->exponent > 2 and die "No way to solve polynoms of power 3 or higher";
438 0 0         $a->values->[0]->exponent == 2 and push @solution, $self->_get_quad_solution($a);
439              
440 0           push( @{ $exercises->{solutions} }, \@solution );
  0            
441 0           push( @{ $exercises->{challenges} }, \@challenge );
  0            
442             }
443              
444 0           $exercises;
445             }
446              
447             =head1 LICENSE AND COPYRIGHT
448              
449             Copyright 2010-2014 Jens Rehsack.
450              
451             This program is free software; you can redistribute it and/or modify it
452             under the terms of either: the GNU General Public License as published
453             by the Free Software Foundation; or the Artistic License.
454              
455             See http://dev.perl.org/licenses/ for more information.
456              
457             =cut
458              
459             1;