=head1 NAME

Math::Volume::Rotational - Volume of rotational bodies 

=head1 SYNOPSIS

  use Math::Volume::Rotational qw/volume_x volume_y/;
  
  my $volume = volume_rot_x( '(2-x^2)^0.5', -2, 2 );
  
  # equivalent:
  use Math::Symbolic qw/parse_from_string/;
  my $formula = parse_from_string('(2-x^2)^0.5');
  $volume = volume_rot_x($formula, -2, 2);
  
  # The above calculates the volume of a sphere of radius 2 by rotating
  # the half-circle of radius 2 around the x-axis.
  # This is equivalent to the well-known formula "4/3*pi*radius^3".
  
  # volume_rot_y works similar by rotating around the y-axis.

=head1 DESCRIPTION

This module calculates the volume of rotational bodies. These are bodies
resulting from the rotation of a portion of a 2D function around an axis.

Please note that rotations around an axis other than either x- or y-axis
are considered highly experimental at this point.

=head2 EXPORT

None by default, but you may choose to have any of the following subroutines
exported to the calling namespace via standard Exporter semantics:

  volume_rot_x
  volume_rot_y
  volume_rot_arb

Additionally, you may use the export tag ':all' to export all of the above.

=head1 SUBROUTINES

=cut

package Math::Volume::Rotational;

use 5.006;
use strict;
use warnings;

use Carp;

use constant PI => 3.141592653589793238462643;
use Math::Integral::Romberg 'integral';
use Math::Symbolic qw/parse_from_string/;
use Math::Symbolic::Compiler;

require Exporter;

our @ISA = qw(Exporter);

our %EXPORT_TAGS = (
	'all' => [ qw(
		volume_rot_x
		volume_rot_y
		volume_rot_arb
	) ],
);

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT;

our $VERSION = '0.11';



=head2 volume_rot_x

Calculates the volume of a rotational body by rotatinf a the portion of
a function graph around the x-axis. The function graph is integrated from
a lower to an upper boundary.

Expects a Math::Symbolic tree or a string to be parsed as such as first
argument. Second argument must be the lower boundary, third must be the
upper parameter boundary.

=cut

sub volume_rot_x {
	my ($function, $lower, $upper) = @_;

	$function = parse_from_string($function)
	  if not ref($function) =~ /^Math::Symbolic/;

	my @sig = $function->signature();
	croak "Function has to be a scalar function" if @sig > 1;
	
	$function = $function ** 2;
	my($compiled) = $function->to_sub();

	return PI * integral($compiled, $lower, $upper);
}



=head2 volume_rot_y

Works the same as volume_rot_x. It calculates the volume of a rotational
body by rotating a the portion of
a function graph around the y-axis. The function graph is integrated from
a lower to an upper boundary.

Expects a Math::Symbolic tree or a string to be parsed as such as first
argument. Second argument must be the lower boundary, third must be the
upper parameter boundary.

=cut

sub volume_rot_y {
	my ($function, $lower, $upper) = @_;

	$function = Math::Symbolic::parse_from_string($function)
	  if not ref($function) =~ /^Math::Symbolic/;

	my @sig = $function->signature();
	croak "Function has to be a scalar function" if @sig > 1;
	my $var = (@sig ? $sig[0] : 'x');
	$function = Math::Symbolic::Operator->new(
		'partial_derivative',
		$function,
		Math::Symbolic::Variable->new($var)
	) * "$var^2";
	$function = $function->apply_derivatives()->simplify();
	my($compiled) = $function->to_sub();

	return PI * integral($compiled, $lower, $upper);
}



=head2 volume_rot_arb

Calculates the volume of a rotational
body by rotating a portion of
a function graph around an arbitrary axis in R^2.
The function graph is integrated from a lower to an upper boundary.
volume_rot_arb takes named arguments:

=over 4

=item function

This is the function to integrate over. It must be a Math::Symbolic tree
or a string to be parsed as such. Needs to be a scalar function.
This is a mandatory argument.

=item var

Indicates the name of the variable to use for integration. This is an
optional argument if the variable can be inferred from the function.

=item lower_boundary_function

This optional argument indicates a function to subtract from the integration
function before integration. Thus, you can calculate the volume of a hollow
sphere of a given thickness.

=item axis_y

Mandatory argument indicating the y value of the axis to rotate around at
x=0.

=item axis_slope

Indicates the slope of the axis in R^2. Mandatory argument.

=item lower

The lower boundary for integration. Mandatory argument.

=item upper

The upper boundary for integration. Mandatory argument.

=back

=cut

sub volume_rot_arb {
	my %args = @_;
	my $var = $args{var};
	my $f1 = $args{function};
	my $f2 = $args{lower_boundary_function};
	my $y = $args{axis_y};
	my $slope = $args{axis_slope};
	my $lower = $args{lower};
	my $upper = $args{upper};

	croak "'function' is a mandatory named argument to volume_rot_arb"
	  if not defined $f1;
	croak "'lower' is a mandatory named argument to volume_rot_arb"
	  if not defined $lower;
	croak "'upper' is a mandatory named argument to volume_rot_arb"
	  if not defined $upper;
	croak "'axis_y' is a mandatory named argument to volume_rot_arb"
	  if not defined $y;
	croak "'axis_slope' is a mandatory named argument to volume_rot_arb"
	  if not defined $slope;

	$f1 = parse_from_string($f1) if not ref($f1) =~ /^Math::Symbolic/;
	$f2 = parse_from_string($f2)
	  if defined $f2 and not ref($f2) =~ /^Math::Symbolic/;
	
	if (not defined $var) {
		my @sig = $f1->signature;
		if (@sig == 1) {
			$var = $sig[0];
		}
		else {
			croak "Could not infer integration variable";
		}
	}
	my $func = $f1;
	$func -= $f2 if defined $f2;
	my ($code) = $func->to_sub();
	my $area = integral($code, $lower, $upper);

	my $com = calculate_center_of_mass($func, $lower, $upper, $var, $area);

	my $dist = abs($com->[0] * (1-$slope) - $y) / sqrt(1 + $slope**2);

	my $volume = PI*$dist*$area;
}

sub calculate_center_of_mass {
	my $f = shift;
	my $l = shift;
	my $u = shift;
	my $v = shift;
	my $integral = shift;

	$f = parse_from_string($f) if not ref($f) =~ /^Math::Symbolic/;
	my ($c_f) = $f->to_sub();

	my $xf = $f * $v;
	my ($c_xf) = $xf->to_sub();

	my $fsq = $f ** 2;
	my ($c_fsq) = $fsq->to_sub();

	$integral = integral($c_f, $l, $u) if not defined $integral;
	
	my $s_x = integral($c_xf, $l, $u) / $integral;
	my $s_y = 0.5 * integral($c_fsq, $l, $u) / $integral;
	return [$s_x, $s_y];
}


1;
__END__

=head1 AUTHOR

Steffen Mueller, E<lt>volume-module at steffen-mueller dot netE<gt>

=head1 SEE ALSO

New versions of this module can be found on
http://steffen-mueller.net or CPAN. The module development takes place on
Sourceforge at http://sourceforge.net/projects/math-symbolic/

L<Math::Symbolic>.

L<Math::Integral::Romberg>.

=cut
