diff --git a/OpenProblemLibrary/macros/univ/Alfredmacros.pl b/OpenProblemLibrary/macros/univ/Alfredmacros.pl new file mode 100755 index 0000000000..de0871b50a --- /dev/null +++ b/OpenProblemLibrary/macros/univ/Alfredmacros.pl @@ -0,0 +1,369 @@ +# A group of macros used in the Alfred problem library. + +sub _Alfredmacros_init {} #don't reload these macros. + + +# Given a point (x,y) this macro computes the angle with respect to x-axis. The angle will be between 0 and 2pi. +sub inversetrig +{my $refangle = arctan(abs($_[1]/$_[0])); + if($_[0] == 0) + {0} + elsif ($_[0]>0) + {if($_[1] == 0) + {0} + elsif($_[1]>0) + {$refangle;} + else + {2*pi-$refangle;} + } + else + {if($_[1] == 0) + {pi} + elsif($_[1]>0) + {pi-$refangle;} + else + {pi+$refangle} + } +} + +## Compute the max and min of an array of numbers + + + +#This macro prevents students from double clicking in an answer box. This macro #is necessary for multiple integral problems where the answer box is typeset +#into the integration symbols. +sub doubleclickprevent +{TEXT(MODES( + TeX => "", + HTML => "" + )); +} + +#The problems that have the answer box in the limits must be displayed in JS +#math mode. This macro warns the user to use JS math mode if they are not. +sub jsMathwarn +{TEXT(MODES( + TeX => '', + HTML_jsMath => '', + HTML => $HR."Warning: to use this problem, you need to". + "select jsMath mode in the Display Options panel at the left".$HR, +)); +} + +sub jsmathmode +{ +TEXT(MODES( + TeX => '', + HTML_jsMath => '', + HTML => $HR."Warning: to use this problem, you need to ". + "select jsMath mode in the Display Options panel at the left".$HR, +)); +TEXT(MODES( + TeX => "", + HTML => "" + )); + +} + +sub mathjaxmode +{TEXT(MODES( + TeX => '', + HTML_MathJax => '', + HTML => $HR."Warning: to use this problem, you need to ". + "select MathJax mode in the Display Options panel at the left".$HR, +)); +} + +#This subroutine includes the Strang's textbook into a problem, you have to +#feed it the chapter and section. It is assumed that the book is in the course +#directory and is labeled strangtextbook. The parameters are +#strang(chapter,section,(optional) section title. +#example \{&strang(16,5,"surface integrals")\}. +$wwstrang = "http://webwork.alfred.edu/webwork2_course_files/strangcalculus"; +sub strang +{ +htmlLink(qq!$wwstrang/Strang-$_[0]-$_[1].pdf!,"$_[0].$_[1] $_[2] from Gilbert Strang's Calculus",q/TARGET="new_window"/) +} + +#Inserts a link to a trig table in the problem. +#example \{&trig_table()\} +sub trig_table +{ +htmlLink(qq!$wwstrang/trig_identities.pdf!,"Trig Identities",q/TARGET="new_window"/) +} + +sub strang_index +{ +htmlLink(qq!$wwstrang/Index.pdf!,"Index",q/TARGET="new_window"/) +} + +sub strang_TOC +{ +htmlLink(qq!$wwstrang/TOC.pdf!,"Table of Contents",q/TARGET="new_window"/) +} + +sub product_Rule_cmp { +my ( $correct, $student, $self ) = @_; + my ( $f1stu, $f2stu,$f3stu,$f4stu ) = @{$student}; + my ( $f1, $f2, $f3, $f4 ) = @{$correct}; + my @fgrade = (0,0,0,0); + my @fstu = ($f1stu,$f2stu,$f3stu,$f4stu); + my @fcorrect = ($f1,$f2,$f3,$f4); + #we will associate each student answer with a prime number, noting which student answer is in which blank. This allows us to make use of the fundamental theorem of arithmetic. + my @prime = (2,3,5,7); + my @answerblank = (0,0,0,0); + + for($i=0;$i<4;$i++){ + for($j=0;$j<4;$j++){ + if(($fcorrect[$i]==$fstu[$j])&&($answerblank[$j]==0)){ + {$answerblank[$j] = $prime[$i]; + $j = 4 # you have to terminate the inner loop in this case for the special case of f=e^x where f and f' are the same. + } + } + } + }; + for($i=0;$i<4;$i++){ + if(!$answerblank[$i]){ + $self->setMessage($i+1,"All of your answers should be $f, $g, or a derivative of one of these functions"); + } + } +#now we rely on the fact that products of primes are unique. First we check to see if all of the blanks are correct + if ((($answerblank[0]*$answerblank[1] == 6)&&($answerblank[2]*$answerblank[3] == 35))||(($answerblank[0]*$answerblank[1] == 35)&&($answerblank[2]*$answerblank[3] == 6))){ + @fgrade = (1,1,1,1); + } +#now check to see if the first pair of blanks is correct, knowing one pair is not + elsif ($answerblank[0]*$answerblank[1] == 6){ + if (($answerblank[2] == 5)||($answerblank[2] == 7)){ + @fgrade = (1,1,1,0); + } + elsif (($answerblank[3] == 5)||($answerblank[3] == 7)){ + @fgrade = (1,1,0,1); + } + else {@fgrade = (1,1,0,0);} + } + elsif ($answerblank[0]*$answerblank[1] == 35){ + if (($answerblank[2] == 2)||($answerblank[2] == 3)){ + @fgrade = (1,1,1,0); + } + elsif (($answerblank[3] == 2)||($answerblank[3] == 3)){ + @fgrade = (1,1,0,1); + } + else {@fgrade = (1,1,0,0);} + } +#if both sets are not correct, and the first set is not correct, check to see if the last pair are + elsif ($answerblank[2]*$answerblank[3] == 6){ + if (($answerblank[0] == 5)||($answerblank[0] == 7)){ + @fgrade = (1,0,1,1); + } + elsif (($answerblank[1] == 5)||($answerblank[1] == 7)){ + @fgrade = (0,1,1,1); + } + else {@fgrade = (0,0,1,1);} + } + elsif ($answerblank[2]*$answerblank[3] == 35){ + if (($answerblank[0] == 2)||($answerblank[0] == 3)){ + @fgrade = (1,0,1,1); + } + elsif (($answerblank[1] == 2)||($answerblank[1] == 3)){ + @fgrade = (0,1,1,1); + } + else {@fgrade = (0,0,1,1);} + } +#at this point they don't have a matched set of blanks correct. look for a single function in each pair that is right. You have to make sure you only get one for each pair of answer blanks. + else{ + if (($answerblank[0])&&($answerblank[2])&&($answerblank[0]*$answerblank[2] !=6)&&($answerblank[0]*$answerblank[2] !=35)&&($answerblank[0]!=$answerblank[2])){ + @fgrade = (1,0,1,0); + } + elsif (($answerblank[0])&&($answerblank[3])&&($answerblank[0]*$answerblank[3] !=6)&&($answerblank[0]*$answerblank[3] !=35)&&($answerblank[0]!=$answerblank[3])){ + @fgrade = (1,0,0,1); + } + elsif (($answerblank[1])&&($answerblank[2])&&($answerblank[1]*$answerblank[2] !=6)&&($answerblank[1]*$answerblank[2] !=35)&&($answerblank[1]!=$answerblank[2])){ + @fgrade = (0,1,1,0); + } + elsif (($answerblank[1])&&($answerblank[3])&&($answerblank[1]*$answerblank[3] !=6)&&($answerblank[1]*$answerblank[3] !=35)&&($answerblank[1]!=$answerblank[3])){ + @fgrade = (0,1,0,1); + } + elsif ($answerblank[0]){@fgrade = (1,0,0,0)} + elsif ($answerblank[1]){@fgrade = (0,1,0,0)} + elsif ($answerblank[2]){@fgrade = (0,0,1,0)} + elsif ($answerblank[3]){@fgrade = (0,0,0,1)} + }; + return [@fgrade]; +} + + + +sub check_boundary_conditions { +my ( $correct, $student, $self ) = @_; + return product_Rule_cmp(@_) ; + } + +sub Snxy(){ + my %args = @_; + my @x = @{$args{inputs}}; + my @y = @{$args{outputs}}; + my $m = $args{m}; + my $n = $args{n}; + my $i = 0; + my $sum = 0; + if ($#x == $#y){ + for ($i=0;$i <= $#x;$i++){ + $sum = $sum + ($x[$i])**($n)*($y[$i])**($m); + } + } + else {$sum = 0}; + return $sum; +} + +### To use the macros your problem must include unionTables.pl, and of course Alfredmacros.pl +### Table integral returns a string that can be included in a Table to output an integral whose upper and lower limits +### of integration can be answer blanks. There are several optional parameters: +### width - change the width of the answer blanks. defaults to 3. +### lowerwidth - change the width of the lower answer blank. defaults to width +### upperwidth - change the width of the upper answer blank. defaults to width. +### upper - the uppper limit of integration, does not have to be an answer blank, defaults to answer blank with width "width" +### lower - the lower limit of integration, does not have to be an answer blank, defaults to answer blank with width "width" +### limits - boolean, if 1 puts the limits of integration above and below the integral symbol, if 0 puts them after the integral symbol. +### default is 1. +### Your code must include unionTables.pl, and of course Alfredmacros.pl +### An example: +### \{BeginTable(center=>0). +### Row([tableintegral(), +### ],separation=>2). +### EndTable(); +### \} +### which will print an integral with answer blank on the upper and lower limits with the default length of 3 +### +### This example prints out a double integral, the first integral with answer blanks with width 10, the second integral +### has 0 for the lower limit of integration and an answer blank with width 5 for the upper limit of integration. +### The default limits of integratin are answer blanks with width 3, in this case the default width was overridden to 5 +### and the default lower limit was changed to a zero. +### \{BeginTable(center=>0). +### Row([tableintegral(width=>10,limits=>'\(0\)'),tableintegral(width=>5,lower=>'\(0\)',limits=>0), +### ],separation=>2). +### EndTable(); +### \} +### An example where the width of the upper and lower answer blanks have different widths. +### \{BeginTable(center=>0). +### Row([tableintegral(lowerwidth=>10,upperwidth=>1) +### ],separation=>2). +### EndTable(); +### \} + +sub tableintegral{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $lowerwidth = delete $arg{lowerwidth} // $width; + my $upperwidth = delete $arg{upperwidth} // $width; + my $lower = delete $arg{lower} // ans_rule($lowerwidth); + my $upper = delete $arg{upper} // ans_rule($upperwidth); + my $limits = delete $arg{limits} // 1; + if ($limits == 1){ + return $upper.$BR.'\(\displaystyle\int\)'.$BR.$lower + } + else { + return '\(\displaystyle\int\)',$upper.$BR.$BR.$lower + } +}; + +# a sum with answer blanks for the summation variable, lower limit, and upper limit +#\{ BeginTable(center=>0). +# Row([tablesum(width=>10), +# ],separation=>2). +# EndTable(); +#\} +# a sum with answer blanks for the upper and lower limits, and the summation variable is i +#\{ BeginTable(center=>0). +# Row([tablesum(width=>10,sumvariable=>'i'), +# ],separation=>2). +# EndTable(); +#\} +# a sum with answer blanks for the upper and lower limits, and summation variable is not used. +#\{ BeginTable(center=>0). +# Row([tablesum(width=>10,usesumvariable=>0), +# ],separation=>2). +# EndTable(); +#\} +# sum from n = 1 to infinity +#\{ BeginTable(center=>0). +# Row([tablesum(sumvariable=>'\(n\)',lower=>'\(1\)', upper=>'\(\hskip 3pt\infty\)') ],separation=>2). +# EndTable(); +#\} + +sub tablesum{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $lowerwidth = delete $arg{lowerwidth} // $width; + my $upperwidth = delete $arg{upperwidth} // $width; + my $lower = delete $arg{lower} // ans_rule($lowerwidth); + my $upper = delete $arg{upper} // ans_rule($upperwidth); + my $limits = delete $arg{limits} // 1; + my $sumvariable = delete $arg{sumvariable} // ans_rule($width); + my $usesumvariable = delete $arg{usesumvariable} // 1; + if ($usesumvariable == 0){ + if ($limits == 1){ + return $upper.$BR.'\(\displaystyle\sum\)'.$BR.$lower + } + else { + return '\(\displaystyle\int\)',$upper.$BR.$BR.$lower + } + } + else { + if ($limits == 1){ + return $upper.$BR.'\(\displaystyle\sum\)'.$BR.$sumvariable.'\( = \)'.$lower + } + else { + return '\(\displaystyle\int\)',$upper.$BR.$BR.$sumvariable.'\( = \)'.$lower + } + } +}; + + +### Create a vertical bar with an upper and lower limit. +sub tableevaluate{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $lowerwidth = delete $arg{lowerwidth} // $width; + my $upperwidth = delete $arg{upperwidth} // $width; + my $lower = delete $arg{lower} // ans_rule($lowerwidth); + my $upper = delete $arg{upper} // ans_rule($upperwidth); + return + '\(\Bigg\vert\)',$upper.$BR.$BR.$lower +}; + +### Create a subscripted character +sub tablesubscript{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $lower = delete $arg{lower} // ans_rule($width); + my $variable = delete $arg{variable} // 'c'; + return + $variable,$BR.$BR.$lower +}; + +### Create a superscripted character +sub tablesuperscript{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $upper = delete $arg{upper} // ans_rule($width); + my $variable = delete $arg{variable} // 'c'; + return + $variable,$upper.$BR.$BR.$BR +}; + + + +### A fraction +sub tablefrac{ + my %arg = @_; + my $width = delete $arg{width} // 3; + my $lower = delete $arg{lower} // ans_rule($width); + my $upper = delete $arg{upper} // ans_rule($width); + my $barwidth = delete $arg{barwidth} // 10+$width; + my $divisionbar = ""; + for ($count = 1;$count <= $barwidth; $count++){ + $divisionbar = $divisionbar."-"; + } + return $upper.$BR.$divisionbar.$BR.$lower +}; + diff --git a/OpenProblemLibrary/macros/univ/BrockPhysicsMacros.pl b/OpenProblemLibrary/macros/univ/BrockPhysicsMacros.pl new file mode 100644 index 0000000000..67405cbe6f --- /dev/null +++ b/OpenProblemLibrary/macros/univ/BrockPhysicsMacros.pl @@ -0,0 +1 @@ +# this file defines all Brock-Physics-specific macros. \ No newline at end of file diff --git a/OpenProblemLibrary/macros/univ/CofIdaho_macros.pl b/OpenProblemLibrary/macros/univ/CofIdaho_macros.pl new file mode 100755 index 0000000000..262ef22dc4 --- /dev/null +++ b/OpenProblemLibrary/macros/univ/CofIdaho_macros.pl @@ -0,0 +1,758 @@ +=pod + +=head1 NAME + + extra macros for Intermediate Algebra problems at The College of Idaho + +=head1 Synposis + macros by R Cruz -- The College of Idaho +=cut + +=head3 Format the instuctor answer + +=pod + +1) SimplifyExponents: Formats an expression without negative exponents + To use: SimplifyExponents(num, den,~~@var,@exp); + where num = numerator's constant + den = denominator's constant + ~~@var = pointer to an array with the list of variables + @exp = array with the exponents for the variables + +2) num_and_unit_checker: Checks for units like "apples" or "oranges" + To use: ANS (num_and_units_checker($correct_answer, "units"); + +3) strict_percent_cmp: Checks percentages have been rounded as required. + To use: ANS(strict_percent_cmp($answer,"%",roundto)); + where $answer is in percent form: xx.x + roundto = "tenths" or "hundredths" + +4) Picky_equation_cmp: Checks both sides of an equation model the word problem. + To use: Picky_equation_cmp("2x+1=5"); + Note: This one allows only the variables that have been used in the CofIdaho + problems. It should be changed so that it is more flexible, or better, + convert it to a MathObject. + +5) SlopeIntercept_equation_cmp: Checks both sides of an equation for the + slope-intercept form of a line. + To use: ANS(SlopeIntercept_equation_cmp($answer)); + where $answer = "y = $m x + $b"; (Use only y and x for variables.) + +6) functionNotation_cmp: Checks for "f(x)= ***" notation + To use: + ANS(functionNotation_cmp("f(x)=2x",["x","f"])); + +7) Checks factors of polynomials. + To use: The answer must be submitted in this form: + ANS(FactorEvaluator($answer,[variable array])); + If the polynomial is prime: + ANS(FactorEvaluator("Does not factor",polynomial,[variable array])); + + Note: Answers must be in the form: monomial(poly)...(poly) with + or without parentheses about the monomial. The polynomial factors must + not contain any other grouping symbols and, in any case, only parenthesis + may be used. This needs to be in the instructions for any set that uses + this macro (in the screenHeader.pg file). + + Note: "StrictFactoringEvaluator" requires leading negatives to be factored out. + This not may not work with all situations. BE SURE TO CHANGE THIS FILE IF THE + FactoringEvaluator IS CHANGED!! + +8) RationalExpEvaluator: Checks for simplified rational expressions. + To use: ANS(RationalExpEvaluation($answer,[variable array])); + or something like: ANS(RationalExpEvaluator($answer,["x","y"])); + + Note: Answers must be of the form: (poly)/(poly) + +9) ReduceFraction: Returns a string that represents a reduced fraction. + To use: $a = SimplifyFraction(numerator expression,denominator espression); +=cut + + + +################################################################### +# 1) Formats an expression without negative exponents +# Thanks to John Jones at ASU for this macro. + +sub SimplifyExponents { +loadMacros("contextLimitedPowers.pl"); + my $num = shift; + my $den = shift; + my $varref = shift; + my @expos = @_; + my @vars = @$varref; + + Context()->operators->set(@LimitedPowers::OnlyPositiveIntegers); + + my $answer_n = Formula("$num"); + my $answer_d = Formula("$den"); + + for $j (0..(scalar(@vars)-1)) { + $answer_n *= Formula("$vars[$j]^$expos[$j] ") if ($expos[$j] > 0); + $answer_d *= Formula("$vars[$j]^(-1*$expos[$j]) ") if ($expos[$j] < 0); + } + + $answer = $answer_n/$answer_d; + + return($answer->reduce()); +} + + +sub Simplified { + my ($correct, $student, $ah)=@_; + return unless $ah->{score} == 1; #This does not work??? + my $VariablesPlus1 = 2; #Put in the number of variables plus 1 + my $check = $ah->{student_ans}; + if(!( $ah->{isPreview}) && $correct == $student + && $check =~ /(([abxy].*){$VariablesPlus1,})|([\Q^].*[\Q^])/) + { + $ah->{ans_message} = "Simplify your answer"; + } + + return ($correct == $student && + ($ah->{student_ans} !~ /(([abxy].*){$VariablesPlus1,})|([\Q^].*[\Q^])/)); + } + + +################################################################### +# 2) Checks for units + +sub num_and_unit_checker{ + my $correct_ans = shift; #the answer + my $correct_units = shift; #the unit type + my $old_evaluator = num_cmp($correct_ans); + + my $new_evaluator = sub + { + my $student_ans = shift; + my $formatted_ans = $student_ans; + $formatted_ans =~ s{$correct_units}{}; + my $ans_hash = $old_evaluator->evaluate($formatted_ans); + if ( $student_ans !~ /$correct_units/) { + $ans_hash->{score} = 0; +$ans_hash->setKeys( 'ans_message' =>"Enter your answer with the correct units: $correct_units"); + } + $ans_hash->{original_student_ans}="$student_ans"; + $ans_hash->{preview_latex_string}="$student_ans"; + $ans_hash->{student_ans} = "$student_ans"; + $ans_hash->{correct_ans} = "$correct_ans"." $correct_units"; + + return $ans_hash; + }; + + return $new_evaluator; +} + + +################################################################### +# 3) Checks for rounded percents + +sub strict_percent_cmp{ + my $correct_ans = shift; #the answer + my $correct_units = shift; #the unit type which should be "%" + my $error = shift; #what to round to + my $errorAmount = .1; + if ($error eq "hundredth") {$errorAmount=.01;} + my $old_evaluator = num_cmp($correct_ans,tol=>0); + + my $new_evaluator = sub + { + my $student_ans = shift; + my $formatted_ans = $student_ans; + $formatted_ans =~ s{$correct_units}{}; + my $ans_hash = $old_evaluator->evaluate($formatted_ans); + if ( $student_ans !~ /$correct_units/) { + $ans_hash->{score} = 0; +$ans_hash->setKeys( 'ans_message' =>"Enter your answer with the correct units: $correct_units"); + } + else { + if ($ans_hash->{score}==0 && + abs($formatted_ans-$correct_ans)<$errorAmount) { +$ans_hash->setKeys( 'ans_message' =>"Round your answer to the nearest $error."); + } + } + $ans_hash->{original_student_ans}="$student_ans"; + $ans_hash->{preview_latex_string}="$student_ans"; + $ans_hash->{student_ans} = "$student_ans"; + $ans_hash->{correct_ans} = "$correct_ans"." $correct_units"; + + return $ans_hash; + }; + + return $new_evaluator; +} + + +################################################################### +# 4) Checks both sides of an equation model the word problem. + +sub Picky_equation_cmp { + +loadMacros( "unionUtils.pl", + "answerUtils.pl", + "listAnswer.pl", +"extraAnswerEvaluators.pl" +); + + my $ans = shift; + +my $new_evaluator = sub { + my $student = shift; + + $ans =~ tr/[=]/,/; + $student =~ tr/[=]/,/; + + my $old_evaluator = fun_list_cmp($ans,vars=>['a','b','c','d','h','l','n','v','w','x','y','s','t','A','L','R','C','P']); + my $ans_hash_old = $old_evaluator->evaluate($student); + + $ans =~ tr/,/=/; + $student =~ tr/,/=/; + $ans_hash_old->{correct_ans}="$ans"; + $ans_hash_old->{original_student_ans}="$student"; + $ans_hash_old->{student_ans}="$student"; + $ans_hash_old->{preview_latex_string}="$student"; + if ($ans_hash_old->{score}!=1) { + $ans_hash_old->{score}=0; +# $ans_hash_old->setKeys( 'ans_message' =>"Your equation must model the problem."); + $ans_hash_old->setKeys( 'ans_message'=>"At least one side is incorrect."); + } + if ($student !~ /[=]/) { + if ($ans =~/[RCP]/) { + @side = split(/[=]/,$ans); + $ans_hash_old->setKeys( 'ans_message'=>"Enter your answer in the form: $side[0] = expression.");} + else { $ans_hash_old->setKeys( 'ans_message' =>"You must enter an equation.");} + }; + +return $ans_hash_old; +}; +return $new_evaluator; +} + +################################################################### +# 5) Checks both sides of an equation for the slope-intercept form of a line. + +sub SlopeIntercept_equation_cmp { + +loadMacros( "unionUtils.pl", + "answerUtils.pl", + "contextLimitedPolynomial.pl" +); + + my $ans = shift; + my $formatAns = $ans; + $formatAns =~ tr/ //; + my @correctSides = split(/[=]/, $formatAns); + +my $new_evaluator = sub { + my $left_evaluator = fun_cmp($correctSides[0],vars=>['x','y']); + my $right_evaluator = fun_cmp($correctSides[1],vars=>['x','y']); + + my $student = shift; + my $formatStudent= $student; + $formatStudent =~ tr/ //; + if ($student !~ /[=]/) { + my $ans_hash = $right_evaluator->evaluate($formatStudent); + $ans_hash->{correct_ans}="$ans"; + $ans_hash->{original_student_ans}="$student"; + $ans_hash->{student_ans}="$student"; + $ans_hash->{preview_latex_string}="$student"; + $ans_hash->setKeys( 'ans_message' =>"You must enter an equation."); + $ans_hash->{score}=0; + return $ans_hash; + } + else { + my @studentSides = split(/[=]/, $formatStudent); + + my $left_ans_hash = $left_evaluator->evaluate($studentSides[0]); + my $right_ans_hash = $right_evaluator->evaluate($studentSides[1]); + + $left_ans_hash->{correct_ans}="$ans"; + $left_ans_hash->{original_student_ans}="$student"; + $left_ans_hash->{student_ans}="$student"; + $left_ans_hash->{preview_latex_string}="$student"; + + if ($right_ans_hash->{score}==0) {$left_ans_hash->{score}=0;} + + my $perlNumber = "[0-9]+[\.\/]?[0-9]*|\.[0-9]+"; + my $BadForm = ($studentSides[0]=~/\w.*\w/ + || $studentSides[1] =~ /[a-zA-Z].*[a-zA-Z]/ + || $studentSides[1] =~ /($perlNumber)[+-]+($perlNumber[a-zA-Z][+-])?($perlNumber)/); + if ($BadForm) { + $left_ans_hash->setKeys( + 'ans_message' =>"Enter your answer in the form: $BR y = mx+b, y = b or x = c"); + $left_ans_hash->{score}=0; + } + + return $left_ans_hash; + } +}; +return $new_evaluator; +} + + +################################################################### +# 6) Checks for function notation in an equality. + +sub functionNotation_cmp { + +loadMacros( "unionUtils.pl", + "answerUtils.pl" +); + + my $ans = shift; + my @variables = @_; + + my $formatAns = $ans; + $formatAns =~ tr/ //; + my @correctSides = split(/[=]/, $formatAns); + +my $new_evaluator = sub { +# my $notation_evaluator = fun_cmp($correctSides[0],vars=>@variables); + my $notation_evaluator = ordered_cs_str_cmp($correctSides[0]); + my $notation2_evaluator = unordered_str_cmp($correctSides[0]); + my $formula_evaluator = fun_cmp($correctSides[1],vars=>@variables,tol=>.001); + + my $student = shift; + my $formatStudent= $student; + $formatStudent =~ tr/ //; + + if ($student !~ /[=]/ || $student !~ /(\w\s*\(\s*\w\s*\))/) { + my $ans_hash = $formula_evaluator->evaluate($formatStudent); + $ans_hash->{correct_ans}="$ans"; + $ans_hash->{original_student_ans}="$student"; + $ans_hash->{student_ans}="$student"; + $ans_hash->{preview_latex_string}="$student"; + $ans_hash->setKeys( 'ans_message' =>"Your answer must be in function notation."); + return $ans_hash; + } + else { + my @studentSides = split(/[=]/, $formatStudent); + + my $notation_ans_hash = $notation_evaluator->evaluate($studentSides[0]); + my $notation2_ans_hash = $notation2_evaluator->evaluate($studentSides[0]); + my $formula_ans_hash = $formula_evaluator->evaluate($studentSides[1]); + + $formula_ans_hash->{correct_ans}="$ans"; + $formula_ans_hash->{original_student_ans}="$student"; + $formula_ans_hash->{student_ans}="$student"; + $formula_ans_hash->{preview_latex_string}="$student"; + + if ($notation_ans_hash->{score}!=1) { + $formula_ans_hash->{ans_message} = $notation_ans_hash->{ans_message}; + if ($notation2_ans_hash->{score}==1) + {$formula_ans_hash->setKeys( 'ans_message' =>"Your answer must be in correct function notation.");} + $formula_ans_hash->{score} = 0; + } + + return $formula_ans_hash; + } +}; +return $new_evaluator; +} + +################################################################### +# 7) Checks factors of polynomials. +# Note: Student's answers must be of the form: monomial(poly)...(poly) with +# or without parentheses about the monomial. The polynomial factors must +# not contain any other grouping symbols and, in any case, only parenthesis +# may be used. This needs to be in the instructions for any set that uses +# this macro (in the screenHeader.pg file). +# Note: "StrictFactoringEvaluator" requires leading negatives to be factored out. +# This not may not work with all situations. BE SURE TO CHANGE THIS FILE IF THE +# FactoringEvaluator IS CHANGED!! +#---------For diagnosing problems-------------------------------------- +# $ans_hash->setKeys( 'ans_message' =>"FYI: Not checking correctly. +# AnswerHashScore: $ans_hash->{score} : +# Student: $#student_factors, $student_ans, +# Formatted: $format_student_ans, +# Student Factors: $student_factors[0], $student_factors[1], +# : Correct: $#factors, $factors[0], $factors[1], +# Number matched: $CorrectFactors"); +#---------------------------------------------------------------------- + + +sub FactoringEvaluator { + + Context()->strings->add("Does not factor"=>()); + + my $ans = shift; + my $ans_text = $ans; #For string answers like "Does not factor" + if ($ans=~/factor/) + { + $ans = shift; + } + my @vars = @_; + + my $format_ans = $ans; + $format_ans =~ s/\*/ /g; #Remove any astrix + $format_ans =~ s/[\(]/,\(/g; #Put in the delimiter , + if ($format_ans=~/^,/) {$format_ans=~ s/,//;} #Remove any leading comma + my @factors = split(/[,]/, $format_ans); #Split off the terms + for $k (0..$#factors) {$factors[$k] = Formula($factors[$k])->reduce;} + $ans = Formula($ans)->reduce; + + my $old_evaluator = fun_cmp($ans,var=>@vars); + + my $new_evaluator = sub { + local($factor_eval,$negfactor_eval,$factor_hash,$negfactor_hash); + + my $student_ans = shift; + +#---------For string answers---------------------------------------------- + if ($student_ans =~ /factor/) + { + my $old_eval_string = str_cmp($ans_text); + my $ans_hash = $old_eval_string->evaluate($student_ans); + $ans_hash->{correct_ans} = $ans_text; #For better display (no caps) + $ans_hash->{student_ans} = $student_ans; + $ans_hash->{original_student_ans} = $student_ans; + return $ans_hash; + } +#--------------------------------------------------------------------------- + + my $ans_hash = $old_evaluator->evaluate($student_ans); + +#----------The only parentheses are allowed for grouping symbols------------ +#----------and only the minimum set needed to enter the answer correctly.--- + if ($student_ans =~ /[\{\}\[\]]/) + { + $ans_hash->{student_ans} = $student_ans; + $ans_hash->{original_student_ans} = $student_ans; + $ans_hash->{score}=0; + $ans_hash->setKeys( 'ans_message' =>"You may only use parentheses in your answer."); + return $ans_hash; + } + if ($student_ans =~ /[\(][^\)]*[\(]/) + { + $ans_hash->{student_ans} = $student_ans; + $ans_hash->{original_student_ans} = $student_ans; + $ans_hash->{score}=0; + $ans_hash->setKeys( 'ans_message' =>"Your answer has extraneous grouping symbols."); + return $ans_hash; + } + +#-----------Check the factors--------------------------------------------- + + if ($ans_hash->{score}==1) { + my $format_student_ans = $student_ans; + $format_student_ans =~ s/[\*]/ /g; #Remove any astrix + $format_student_ans =~ s/[\(]/,\(/g; #Put in the delimiter , + if ($format_student_ans=~/^,/) {$format_student_ans=~ s/,//;} #Remove leading commas + my @student_factors = split(/[,]/, $format_student_ans); #Split off the terms + for $k (0..$#student_factors) {$student_factors[$k] = Formula($student_factors[$k]);} + + #########Could this be done with a list?--would have to change the list's error msg. + + my $CorrectFactors = 0; + + foreach $i (0..$#factors) + { + $factor_eval = fun_cmp($factors[$i],var=>@vars); + $negfactor_eval = fun_cmp(-1*($factors[$i]),var=>@vars); + foreach $j (0..$#student_factors) + { + $factor_hash = $factor_eval->evaluate($student_factors[$j]); + $negfactor_hash = $negfactor_eval->evaluate($student_factors[$j]); + if ($factor_hash->{score}==1 || $negfactor_hash->{score}==1) + { + $CorrectFactors=$CorrectFactors+1; + +#---------This segment is for binomial factors raised to powers---------------- +# Example: (3x+1)^2 is correct, and (9x^2+6x+1) should not be accepted +# SHOULD MAKE THIS MORE GENERAL: TAKE OTHER EXPONENTS BESIDES INTEGERS, +# THE PATTERN SHOULD BE BETTER DEFINED. +#------------------------------------------------------------------------------ + $factor_string = $factors[$i]->string; + $st_factor_string = $student_factors[$j]->string; + if ($factor_string =~ /\(.*[+-]{1}.*\)\^\d+$/ && $st_factor_string !~ /\(.*[+-]{1}.*\)\^\d+$/) + { + $ans_hash->{score}=0; + $ans_hash->setKeys( 'ans_message' =>"Factor the expression completely."); + } +#------------------------------------------------------------------------------- + } + } + } + + if ($CorrectFactors!=$#factors+1) + { + $ans_hash->{score}=0; + $ans_hash->setKeys( 'ans_message' =>"Check that your answer is factored + completely and is entered in the + required format."); + } + } + + if ($ans_text=~/factor/) {$ans_hash->{correct_ans} = $ans_text;} #In case the answer is a string + + return $ans_hash; + }; #END of SUB + + Context()->strings->remove("Does not factor"=>()); + return $new_evaluator; +} + + +sub StrictFactoringEvaluator { + + my $ans = shift; + my @vars = @_; + + my $format_ans = $ans; + $format_ans =~ s/[\*]//g; #Remove any astrix + $format_ans =~ s/[\(]/,\(/g; #Put in the delimiter , + if ($format_ans=~/^,/) {$format_ans=~ s/,//;} #Remove any leading commas + my @factors = split(/[,]/, $format_ans); #Split off the terms + for $k (0..$#factors) {$factors[$k] = Formula($factors[$k]);} + $ans = Formula($ans)->reduce; + + my $old_evaluator = fun_cmp($ans,var=>@vars); + + my $new_evaluator = sub { + local($factor_eval,$factor_hash); + + my $student_ans = shift; + +#----This could be changed so that it would check prime polyomials here instead +#----of in the problem template. + if ($student_ans =~ /factor/) + { + my $string_ans = $ans->string; + my $old_eval_string = std_cs_str_cmp($string_ans); + my $ans_hash = $old_eval_string->evaluate($student_ans); + return $ans_hash; + } +#------------------------------------------------------ + + my $ans_hash = $old_evaluator->evaluate($student_ans); + + if ($ans_hash->{score} == 1) { #Check factors + my $format_student_ans = $student_ans; + $format_student_ans =~ s/[\*]//g; #Remove any astrix + $format_student_ans =~ s/[\(]/,\(/g; #Put in the delimiter , + if ($format_student_ans=~/^,/) {$format_student_ans=~ s/,//;} #Remove any leading commas + my @student_factors = split(/[,]/, $format_student_ans); #Split off the terms + for $k (0..$#student_factors) {$student_factors[$k] = Formula($student_factors[$k]);} + + #########Could this be done with a list?--would have to change the list's error msg. + + my $CorrectFactors = 0; + + foreach $i (0..$#factors) + { + $factor_eval = fun_cmp($factors[$i],var=>@vars); + foreach $j (0..$#student_factors) + { + $factor_hash = $factor_eval->evaluate($student_factors[$j]); + if ($factor_hash->{score}==1) + { + $CorrectFactors=$CorrectFactors+1; + } + } + } + + if ($CorrectFactors!=$#factors+1) + { + $ans_hash->{score}=0; + $ans_hash->setKeys( 'ans_message' =>"Check that your answer is factored completely and is entered in the required format."); + } + } + return $ans_hash; + }; + return $new_evaluator; +} + +################################################################### +# 8) Checks for simplified rational expressions. +# Note: Student's answers must be of the form: (poly)/(poly) +# +#---------For diagnosing problems-------------------------------------- +# $ans_hash->setKeys( 'ans_message' =>"FYI: Not checking correctly. +# Student: $student_num, $student_den,$student_ans; +# Answer: $num,$den,$ans"); +#---------------------------------------------------------------------- + +sub RationalExpEvaluator { + + Context()->strings->add( "Does not simplify" => () ); + + my $ans = shift; + my $ans_text = $ans; #Mainly for string answers like "Does not simplify" + if ( $ans =~ /not/ ) { + $ans = shift; + } + my @vars = @_; + + #---------Split off the numerator/denominator + my @factors = split( q[/], $ans ); + my $num = Formula( $factors[0] ); + my $den = ($#factors > 0) ? Formula( $factors[1] ) : Formula("1"); + + my $old_evaluator = fun_cmp( $ans, var => @vars ); + + my $new_evaluator = sub { + my $student_ans = shift; + my $student_ans_text = $student_ans; + + #---------For string answers---------------------------------------------- + if ( $student_ans_text =~ /not/ ) { + my $old_eval_string = str_cmp($ans_text); + my $ans_hash = $old_eval_string->evaluate($student_ans_text); + $ans_hash->{correct_ans} = $ans_text; + $ans_hash->{student_ans} = $student_ans_text; + $ans_hash->{original_student_ans} = $student_ans_text; + return $ans_hash; + } + #------------------------------------------------------------------------- + + my $ans_hash = $old_evaluator->evaluate($student_ans); + + if ( $ans_hash->{score} == 1 ) { + #--------Check for a scalar answer in case and the student entered a decimal + if ( $student_ans !~ /[a-zA-Z]/ && $ans !~ /[a-zA-Z]/ ) { + $ans_hash->{correct_ans} = $ans_text; + $ans_hash->{student_ans} = $student_ans_text; + $ans_hash->{original_student_ans} = $ans_hash->{student_ans}; + return $ans_hash; + } + #-------------------------------------------------------------------- + + #--------Split off the numerator/denominator------------------------- + my $student_ans_mo = Formula( $student_ans_text ); + my ( $student_num, $student_den ); + my @student_factors; + + # use MO parse tree rather than regex split because of surrounding parens from mathquill + if ( $student_ans_mo->{tree}->class eq 'BOP' && $student_ans_mo->{tree}{bop} =~ m!/! ) { + $student_num = $student_ans_mo->{tree}{lop}; + $student_den = $student_ans_mo->{tree}{rop}; + @student_factors = ( $student_num->string, $student_den->string ); + } else { + $student_num = $student_ans_mo; + $student_den = Formula('1'); + @student_factors = ( $student_num->string ); + } + #-------------------------------------------------------------------- + + if ( $#factors != $#student_factors ) { + $ans_hash->{score} = 0; + } else { + my $num_eval = fun_cmp( $num, var => @vars ); + my $num_hash = $num_eval->evaluate($student_num); + $ans_hash->{score} = $num_hash->{score}; + + if ( $#factors > 0 ) { + my $den_eval = fun_cmp( $den, var => @vars ); + my $den_hash = $den_eval->evaluate($student_den); + $ans_hash->{score} = $den_hash->{score}; + } + } + + $ans_hash->{ans_message} = "Simplify your answer." if ( $ans_hash->{score} == 0 ); + } + + return $ans_hash; + }; + + Context()->strings->remove( "Does not simplify" => () ); + return $new_evaluator; +} + +################################################################### +# 9) Writes a fraction in reduced form. +# + +sub ReduceFraction { + my $num = shift; + my $den = shift; + my $n = 1; + my $d = 1; + + ($n,$d) = reduce($num,$den); + my $result = "$n/$d"; + if ($d==1) {$result = "$n";} + + return($result); +} + + + +################################################################### +# 10) Checks a list of equations. +# Designed for checking a list of asymptotes + +sub equation_cmp_list { + +loadMacros( + "extraAnswerEvaluators.pl" +); + + my $ans = shift; + my @vars = @_; + + my $format_ans = $ans; + $format_ans =~ tr/ //; #Remove any whitespace + my @ans_list = split(/[,]/, $format_ans); #Split off the terms + +# +# my $old_evaluator2 = str_cmp($ans_list[1]); +# Would like to change this to an equation evaluator later +# my $old_evaluator1 = equation_cmp($ans[0],vars=>@vars); +# my $old_evaluator2 = equation_cmp($ans[1],vars=>@vars); + + my $new_evaluator = sub { + + local($eq_eval,$ans_hash); + + my $student_ans = shift; + chomp $student_ans; + my $format_st = $student_ans; + $format_st =~ tr/ //; #Remove any whitespace + + my @student_list = split(/,/,$format_st); + + my $Correct = -1; + foreach $i (0..$#ans_list) #Count the number of matches between the lists + { + my $eq_eval = str_cmp($ans_list[$i]); + foreach $j (0..$#student_list) + { + $ans_hash = $eq_eval->evaluate($student_list[$j]); + if ($ans_hash->{score}==1) {$Correct=$Correct+1;} + } + } + my $Duplicates = -1; + foreach $i (0..$#student_list) #Check for duplicates in the student's list + { + my $eq_eval = str_cmp($student_list[$i]); + foreach $j (0..$#student_list) + { + $ans_hash = $eq_eval->evaluate($student_list[$j]); + if ($ans_hash->{score}==1) {$Duplicates=$Duplicates+1;} + } + } + if ($Correct!=$#ans_list || $Duplicates!=$#student_list) + { + $ans_hash->{score}=0; + if ($Duplicates!=$#student_list) + { + $ans_hash->setKeys( 'ans_message' =>"You have listed an asymptote more than once."); + } + } + else + { + $ans_hash->{score}=1; + } + $ans_hash->{correct_ans} = $ans; + $ans_hash->{student_ans} = $student_ans; + $ans_hash->{original_student_ans} = $ans_hash->{student_ans}; +# $ans_hash->{type} = '', + $ans_hash->{preview_text_string} = $student_ans; + $ans_hash->{preview_latex_string} = $student_ans; + return $ans_hash; + }; + return $new_evaluator; +} + + +1; diff --git a/OpenProblemLibrary/macros/univ/Dartmouthmacros.pl b/OpenProblemLibrary/macros/univ/Dartmouthmacros.pl new file mode 100755 index 0000000000..a1a6ce63d2 --- /dev/null +++ b/OpenProblemLibrary/macros/univ/Dartmouthmacros.pl @@ -0,0 +1,313 @@ +#!/usr/bin/perl + +# this is equivalent to use strict, but can be used within the Safe compartment. +BEGIN{ + be_strict; +} + +## Some local macros + +sub trs_mod{ + my @inputs = @_; + my $a = $inputs[0]; + my $b = $inputs[1]; + my $zero = 1e-12; + if ($b < 0) {$b = - $b;} + +## Want mod(a,b) but perl and int are flawed + my $modvalue = $a; +# if ($a >= 0 && $a < $b) {$modvalue = $a;} + + while ($modvalue >= $b){$modvalue = $modvalue - $b;} + while ($modvalue < 0) {$modvalue = $modvalue + $b;} + if (abs($modvalue) <= $zero){$modvalue = 0;} + if (abs($modvalue - $b) <= $zero){$modvalue = 0;} +## Fudge for roundoff error + return $modvalue; +} + +## Compute the product of a scalar and a vector (scalar first) +sub scalar_mult_vector{ + ## Put the parameters passed into an array of values + my @vector = @_; + + ## Split off the first entry as the scalar, and the rest as the vector + my $scalar = $vector[0]; + my @vectorb = @vector[(1 .. $#vector )]; + + ## Initialize scalar multiple as empty vector + my @scalar_multiple=(); + + my $i; + for ($i=0; $i <= $#vectorb; $i++) + { + $scalar_multiple[$i] = $scalar * $vectorb[$i]; + } + return @scalar_multiple; +} + + + +## Compute the sum of two vectors +## Perl doesn't seem to have builtin array arithmetic +sub vector_sum { + ## Put the parameters passed into an array of values + my @vector = @_; + ## $#vector is the number of elements in vector minus 1 + my $halflength = ($#vector - 1)/2; + ## Slice the input into two equal length vectors + my @vectora = @vector[(0 .. $halflength)]; + my @vectorb = @vector[($halflength+1 .. $#vector )]; + + ## Initialize vector sum to empty array + my @vector_sum=(); + + my $i; + for ($i=0; $i <= $#vectora; $i++) + { + $vector_sum[$i] = $vectora[$i] + $vectorb[$i]; + } + return @vector_sum; +} + +## Compute the difference of two vectors +sub vector_diff{ + ## Put the parameters passed into an array of values + my @vector = @_; + ## $#vector is the number of elements in vector minus 1 + my $halflength = ($#vector - 1)/2; + ## Slice the input into two equal length vectors + my @vectora = @vector[(0 .. $halflength)]; + my @vectorb = @vector[($halflength+1 .. $#vector )]; + + return vector_sum(@vectora, scalar_mult_vector(-1, @vectorb)); +} + + +## Compute the length of a vector +sub vec_length { + ## Put the paramaters passed into an array of values + my @vector = @_; + + ## Initialize maximum value to first element + my $vector_length = 0; + + my $i; + for ($i=0; $i <= $#vector; $i++) + { + $vector_length = $vector_length + $vector[$i] * $vector[$i]; + } + $vector_length = sqrt($vector_length); + return $vector_length; +} + +sub vector_length { + my @vector = @_; + return vec_length(@vector); +} + +## Computes the dot product of two vectors (assumed of the same dimension) +sub dot_product { + ## Put the parameters passed into an array of values + my @vector = @_; + ## $#vector is the number of elements in vector minus 1 + my $halflength = ($#vector - 1)/2; + ## Split the input into two equal length vectors + my @vectora = @vector[(0 .. $halflength)]; + my @vectorb = @vector[($halflength+1 .. $#vector )]; + + ## Initialize dot product to zero + my $dot = 0; + + my $i; + for ($i=0; $i <= $#vectora; $i++) + { + $dot = $dot + $vectora[$i] * $vectorb[$i]; + } + return $dot; +} + +sub cross_product { + ## Put the parameters passed into an array of values + my @vector = @_; + + ## Slice the input into two equal length vectors + my @vectora = @vector[(0 .. 2)]; + my @vectorb = @vector[(3 .. 5)]; + + ## Initialize dot product to zero + my @cross = (); + + $cross[0] = $vectora[1]*$vectorb[2] - $vectora[2]*$vectorb[1]; + $cross[1] = $vectora[2]*$vectorb[0] - $vectora[0]*$vectorb[2]; + $cross[2] = $vectora[0]*$vectorb[1] - $vectora[1]*$vectorb[0]; + return @cross; +} + +## Compute the maximum value in a list +#sub max { +# ## Put the paramters passed into an array of values +# my @values = @_; +# +# ## Initialize maximum value to first element +# my $max = $values[0]; +# +# my $i; +# for ($i=1; $i <= $#values; $i++) +# { +# if ($values[$i] > $max) { +# $max = $values[$i]; +# } +# } +# return $max; +#} + +## Compute the minimum value in a list +#sub min { +# ## Put the paramters passed into an array of values +# my @values = @_; +# +# ## Initialize minimum value to first element +# my $min = $values[0]; +# +# my $i; +# for ($i=1; $i <= $#values; $i++) +# { +# if ($values[$i] < $min) { +# $min = $values[$i]; +# } +# } +# return $min +#} + + +## clean_scalar_string is invoked to make expressions like "$a x" look +## better when $a = 0, -1, 1 +## Usage: clean_scalar_string(scalar, "quoted string"); +## Example: clean_scalar_string(-1,"\pi") returns "-\pi" +sub clean_scalar_string{ + my $local_scalar = shift; + my $local_fixed_object = shift; + my $return_object; + + if ($local_scalar == 0) {$return_object = "0";} + elsif ($local_scalar == 1) {$return_object = "$local_fixed_object";} + elsif ($local_scalar == -1) {$return_object = "-$local_fixed_object";} + else {$return_object = "${local_scalar}${local_fixed_object}";} + return $return_object; +} + +## Computes the greatest common divisor of two integers +## Example: gcd(-300, 125) returns 25 +sub trs_gcd{ + my $a = shift; + my $b = shift; + my $c; + my $abs_a = abs($a); + my $abs_b = abs($b); + if ($abs_b == 0) {return $abs_a;} + else {$c = $abs_a % $abs_b; + return trs_gcd($abs_b, $c);} +} + +## reduced_fraction takes a pair of integers $numerator, $denominator +## ($denominator != 0) and returns an array of two elements @fraction +## $fraction[0] is the reduced numerator; $fraction[1] the reduced +## denominator +## Puts the sign of the fraction, if negative, in the numerator +## +## Usage: @fraction = reduced_fraction($numerator, $denominator) +## +sub reduced_fraction{ + my $local_numerator = shift; + my $local_denominator = shift; + my $sign_of_num = 1; + my $sign_of_denom = 1; + my $sign_of_quotient; + my @local_fraction = (); + + if ($local_numerator < 0) {$sign_of_num = -1;} + if ($local_denominator < 0) {$sign_of_denom = -1;} + $sign_of_quotient = $sign_of_num * $sign_of_denom; + + my $local_gcd = trs_gcd($local_numerator, $local_denominator); +## reduced numerator + $local_fraction[0] = ($sign_of_quotient * abs($local_numerator)) / $local_gcd; +## reduced denominator + $local_fraction[1] = abs($local_denominator) / $local_gcd; + return @local_fraction; +} + + +## Given Cartesian coordinates x and y returns an +## array the zeroth element which is the radius +## and the first element which is the argument +## 0 <= theta < 2*pi +## +## Returns r=0 theta=0 for the origin +## +sub coordinates_polar{ + my $x = shift; + my $y = shift; + my @polar=(); + my $radius = 0; + my $theta = 0; + my $pi = acos(-1); + + $radius = sqrt($x**2 + $y**2); + if ($radius != 0){ + if ($x == 0) {if ($y > 0) {$theta = $pi/2;} + else {$theta = 3*$pi/2;}} + else + { + if ($y > 0){if ($x > 0){$theta = atan($y/$x);} + else {$theta = $pi - atan(-$y/$x);}} + else {if ($x > 0){$theta = 2*$pi + atan($y/$x);} + else {$theta = $pi + atan($y/$x);}} + } + } + @polar=($radius, $theta); + return @polar; +} + + +## Cylindarical coordinates from polar routine +sub coordinates_cylindrical{ + my $x = shift; + my $y = shift; + my $z = shift; + my @cylindrical=(coordinates_polar($x, $y), $z); + return @cylindrical; +} + +## Spherical Coordinates from polar routine +## +sub coordinates_spherical{ + my $x = shift; + my $y = shift; + my $z = shift; + my $rho = 0; + my $phi = 0; + $rho = sqrt($x*$x + $y*$y + $z*$z); + my @npolar = (); + @npolar = coordinates_polar($x, $y); + my @spherical=(); + if ($x ==0 && $y == 0){if ($z >= 0) {$phi = 0;} + else {$phi = acos(-1);} + @spherical=($rho,0,$phi);} + else{ + $rho = sqrt($x*$x + $y*$y + $z*$z); + + @spherical=($rho, $npolar[1], acos($z/$rho)); + } + return @spherical; +} + + + + + +1; + + + diff --git a/OpenProblemLibrary/macros/univ/MUHelp.pl b/OpenProblemLibrary/macros/univ/MUHelp.pl new file mode 100644 index 0000000000..3f9e185e2a --- /dev/null +++ b/OpenProblemLibrary/macros/univ/MUHelp.pl @@ -0,0 +1,45 @@ +sub BBRED { + MODES(TeX => '{\\color{red} ', HTML => ''); +}; + +sub EBRED { + MODES( TeX => '}', HTML => ''); +}; + +$main::BBRED = BBRED(); +$main::EBRED = EBRED(); + +$badmessage = "$BBRED Something helpful should go here. Please inform your instructor that it is missing! $EBRED"; + +sub MUHelp { + my $type = shift; + return $badmessage unless defined($type); + + my $rString = ""; + my $typeOkay = 0; + + if ($type =~ m/sqrt/i) { + $rstring = "If necessary, type ${BBOLD}sqrt()${EBOLD} to enter a square root. For example, to enter \\(\\sqrt{7x}\\), you would type your answer as sqrt(7x). You must put parentheses around everything you want to take the square root of!!"; + $typeOkay = 1; + } elsif ($type =~ m/absvaleqn/i) { + $rstring = "Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution."; + $typeOkay = 1; + } elsif ($type =~ m/logans/i) { + $rstring = "Give an exact answer. If necessary, type ${BBOLD}log(b,x)${EBOLD} to enter \\(\\log_b(x)\\) as an answer. For example, type ${BBOLD}log(3,5)${EBOLD} to enter \\(\\log_3(5)\\). You may also use ${BBOLD}ln(15)${EBOLD} for \\(\\ln(15)\\), and ${BBOLD}log10(20)${EBOLD} for \\(\\log(20)\\). Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution."; + $typeOkay = 1; + } elsif ($type =~ m/lineareqns/i) { + $rstring = "If necessary, type ${BBOLD}no solutions${EBOLD} if the equation has no solution or type ${BBOLD}infinitely many${EBOLD} if there are infinitely many solutions."; + $typeOkay = 1; + } elsif ($type =~ m/quadeqns/i) { + $rstring = "Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution. If necessary, type ${BBOLD}sqrt()${EBOLD} to enter a square root. For example, to enter \\(\\sqrt{7x}\\), you would type your answer as sqrt(7x). You must put parenthesis are everything you want to take the square root of!!"; + $typeOkay = 1; + } + + if ($typeOkay == 1) {return $rstring;}; + return $badmessage; +} + +1; + + +## Append .helpLink("exponents","Click here for further help.") to a string to add a help link. \ No newline at end of file diff --git a/OpenProblemLibrary/macros/univ/littleneck.pl b/OpenProblemLibrary/macros/univ/littleneck.pl new file mode 100644 index 0000000000..8eb3cd8e46 --- /dev/null +++ b/OpenProblemLibrary/macros/univ/littleneck.pl @@ -0,0 +1,227 @@ +#***************************** +# Question mode variables +#***************************** +$PRACTICE_MODE = 0; +loadMacros("problemRandomize.pl"); +#******************************************** +# Set up a "generate new problem" button +#******************************************** +sub rand_button +{ + if ($PRACTICE_MODE == 1) + { + ProblemRandomize(when=>"Always",onlyAfterDue=>0,style=>"Input"); + } + elsif ($PRACTICE_MODE == 2) + { + ProblemRandomize(when=>"Always",onlyAfterDue=>0,label=>"Generate New Problem"); + } +} +#********************************************************** +# Subroutine to reduce fractions +# Input : Num, Denom +# Output: Num, Denom, Wholenum (if != 0, = $num/$denom) +# +# 22Jul09 - Returned Wholenum < 0 if num or denom < 0 +#********************************************************** +sub reduce_fraction +{ + $n = $_[0]; + $d = $_[1]; + $wholenum = 0; +# +# ********************** +# Remove negatives +# ********************** + if ($n < 0) + { + $num = -$n; + } + else + { + $num = $n; + } + if ($d < 0) + { + $denom = -$d; + } + else + { + $denom = $d; + } +# ****************************** +# Check for a whole number +# ****************************** + if ($num/$denom == int($num/$denom)) + { + $wholenum = $n/$d; + } +# *************** +# Find gcf +# *************** + else + { + if ($num > $denom) + { + $gcf = $denom; + } + else + { + $gcf = $num; + } + $found = 0; + while ( ($found == 0) && ($gcf > 1) ) + { + if ( ($num/$gcf == int($num/$gcf)) && ($denom/$gcf == int($denom/$gcf)) ) + { + $found = 1; + } + else + { + $gcf--; + } + } +# ******************************** +# If a GCF was found, reduce +# ******************************** + if ($found == 1) + { + $num = $num/$gcf; + $denom = $denom/$gcf; + } + } +# *********************** +# Restore negatives +# *********************** + if ($n < 0) + { + $num = -1*$num; + } + if ($d < 0) + { + $denom = -1*$denom; + } + return($num, $denom, $wholenum); +} +# +#********************************************************** +# Subroutine to produce reduced display fraction +# Input : Num($n), Denom($d) +# Output: Num($n), Denom($d), Wholenum($w),Display($display) +# calls reduce_fraction to get, +# Output: Num($n), Denom($d), Wholenum($w), +#(if Wholenum > 0,$display = $w +# else $Display=\(\frac{$n}{$d}\) +#********************************************************** +sub display_fraction_long +{ $n = $_[0]; + $d = $_[1]; + $w=0; + $display=0; +#call reduce_fraction +($n,$d,$w)=reduce_fraction($n,$d,$w); +#Decide on display format +if ($w>0){ + $display=$wholenumber; + } +else{ + $display="\\frac{$num}{$denom}"; + } +return($n, $d, $w,$display); +} +# +# +# +# +#********************************************************** +# Subroutine to produce reduced display fraction +# Input : Num($n), Denom($d) +# Output: Display($display) +# calls reduce_fraction to get, +# Output: Num($n), Denom($d), Wholenum($w), +#(if Wholenum > 0,$display = $w +# else $Display=\(\frac{$n}{$d}\) +#********************************************************** +sub display_fraction +{ $n = $_[0]; + $d = $_[1]; + $w=0; + $display=0; +#call reduce_fraction +($n,$d,$w)=reduce_fraction($n,$d,$w); +#Decide on display format +if ($w>0){ + $display=$wholenumber; + } + else{ + $display="\\frac{$num}{$denom}"; + } +return($display); +} +#******************************************************************************* +# sqrt_simplify(Num,Natnum,Radnum,Texstr,Error) +# +# Description: Given a natural number, will find the greatest perfect square +# that is a factor and use this to simplify the expression: +# sqrt(number). +# Input : +# Num = Number whose sqrt is being taken +# +# Output: +# Natnum => Natural number portion of solution (= 1 if no perfect square +# divides evenly) +# Radnum => The value still inside the radical +# Texstr => A LaTex string of the form "Natnum \sqrt{$Radnum}" to be used +# for displaying the solution. +# Error => If == 1, then the number entered was bad (ie, negative, nonint) +# (If necessary, this code can be made specific for the error) +#******************************************************************************* +sub sqrt_simplify +{ + $number = $_[0]; + ($Natnum,$Radnum) = (1,1); + $Error = 0; + + #*********************************** + # Check the number for validity + #*********************************** + if ( ($number == int($number)) && ($number > 0) ) + { + $perfsqr = 1; + #************************************************************************************* + # Check all perfect squares up to the max and store the hightest one that divides + #************************************************************************************* + for ($i = 2; $i*$i <= $number; $i++) + { + $sqr = $i*$i; + if ( int($number/$sqr) == $number/$sqr ) + { + $perfsqr = $i; + } + } + $Natnum = $perfsqr; + $Radnum = $number/($Natnum*$Natnum); + #************************************************************ + # If radnum = 1, the original number is a perfect square + # If natnum = 1, it cannot be simplified + #************************************************************ + if ($Radnum == 1) + { + $Texstr = "$Natnum"; + } + elsif ($Natnum == 1) + { + $Texstr = "\\sqrt{$number}"; + } + else + { + $Texstr = "$Natnum \\sqrt{$Radnum}"; + } + } + else + { + ($Natnum,$Radnum,$Error) = 1; + $Texstr = "1"; + } + return($Num,$Natnum,$Radnum,$Texstr,$Error); +}