Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

Makefile.PL  view on Meta::CPAN

}

1;
BUILDFLAGS
close $bf_fh or die "Makefile.PL: closing $bf_path failed: $!";

# Appended to the Makefile when the install-time build is on.  Modelled on
# what Inline::MakeMaker generates, with two differences: only the one
# module that actually embeds C gets a rule (Inline::MakeMaker emits one
# per .pm in lib/), and install mode is signalled to the module via
# IF_INSTALL_BUILD=1 in the rule's environment instead of a global
# -MInline=_INSTALL_ import, so the module can pass Inline's _INSTALL_
# config itself alongside NAME/VERSION.  Inline's install mode reads the
# version and blib/arch destination from @ARGV.  The trailing -e writes a
# stub .inl file satisfying the make dependency (also what
# Inline::MakeMaker does); the compiled object itself lands under
# blib/arch/auto/ and is picked up by `make install`.
sub MY::postamble {
	return '' unless $prebuilt;
	return <<'POSTAMBLE';
# --- Inline::C install-time build (generated by Makefile.PL):

examples/basic-anomaly-detection.pl  view on Meta::CPAN

#     perl -Ilib examples/basic-anomaly-detection.pl
# or, if the module is installed:
#     perl examples/basic-anomaly-detection.pl

use strict;
use warnings;
use Algorithm::Classifier::IsolationForest;

use constant PI => 3.14159265358979;

# Seed the global RNG so the *data* is reproducible from run to run. The forest
# gets its own seed below; fit() reseeds internally, which is fine because the
# data has already been generated by then.
srand(7);

sub gaussian {
	my ( $mu, $sigma ) = @_;
	my $u1 = rand() || 1e-12;
	my $u2 = rand();
	return $mu + $sigma * sqrt( -2 * log($u1) ) * cos( 2 * PI * $u2 );
}

lib/Algorithm/Classifier/IsolationForest.pm  view on Meta::CPAN

    double u1 = sm64_drand(s);
    double u2;
    if (u1 == 0.0) u1 = 1e-12;
    u2 = sm64_drand(s);
    return sqrt(-2.0 * log(u1)) * cos(6.283185307179586 * u2);
}

/* Thread-safe twin of _build_node_c: same split algorithm, but reads
 * randomness from a thread-private splitmix64 stream instead of
 * Drand01(), and writes into a TreeBuf instead of allocating Perl AVs
 * -- so it touches no interpreter-global state and is safe to call
 * concurrently from an OpenMP parallel region, one tree per thread. */
static int _build_node_packed(const double* x, int nf, int* idxs, int size,
                               int depth, int limit, int mode_flag,
                               int ext_active, TreeBuf *buf, uint64_t *rng) {
    double *lo, *hi;
    int *varying, nv, f, my_idx;

    if (depth >= limit || size <= 1) {
        my_idx = tb_push_node(buf, 0.0, (double)size, 0.0, 0.0, 0.0, 0.0);
        free(idxs);

lib/Algorithm/Classifier/IsolationForest.pm  view on Meta::CPAN

#
# Example:
#   $self->_learn_contamination_threshold_streaming( 'train.csv', 1, 1_000_000, 1 );
sub _learn_contamination_threshold_streaming {
	my ( $self, $path, $skip_first, $n, $c_scan ) = @_;

	my $k = int( $self->{contamination} * $n + 0.5 );
	$k = 1  if $k < 1;
	$k = $n if $k > $n;

	# Whole set flagged: sit the cut just below the global minimum score.
	if ( $k >= $n ) {
		my $min;
		$self->_stream_scores( $path, $skip_first, sub { $min = $_[0] if !defined $min || $_[0] < $min }, $c_scan );
		$self->{threshold} = $min - 1e-9;
		return;
	}

	# One scoring pass keeps the k+1 largest scores (a min-heap rooted at the
	# smallest kept).  contamination <= 0.5 bounds k at n/2, so this tail is
	# always the smaller side of the split.

lib/Algorithm/Classifier/IsolationForest/App.pm  view on Meta::CPAN

package Algorithm::Classifier::IsolationForest::App;

use 5.006;
use strict;
use warnings;
use App::Cmd::Setup -app;

sub global_opt_spec {
	return ( [ 'help|h' => "This usage screen." ], [ 'version|v' => "This usage screen." ], );
}

=head1 NAME

Algorithm::Classifier::IsolationForest::App - the App::Cmd application behind the iforest command

=head1 DESCRIPTION

The L<App::Cmd> application class C<bin/iforest> runs.  Subcommands live
under C<Algorithm::Classifier::IsolationForest::App::Command::> and are
discovered by App::Cmd, so adding a module there adds a command -- there
is no registry to update.

They all inherit from
L<Algorithm::Classifier::IsolationForest::App::Command>, which App::Cmd
also finds by name alone.

=head1 METHODS

=head2 global_opt_spec

The options every subcommand accepts on top of its own, as the list of
arrayrefs L<Getopt::Long::Descriptive> expects.  App::Cmd calls this
while assembling the option spec for whichever command is about to run.

    iforest fit -h        # handled through this spec

=cut

1;

lib/Algorithm/Classifier/IsolationForest/App/Command.pm  view on Meta::CPAN

package Algorithm::Classifier::IsolationForest::App::Command;
use strict;
use warnings;
use App::Cmd::Setup -command;

sub global_opt_spec {
	my ( $class, $app ) = @_;
	return ( $class->options($app), );
}

sub validate_args {
	my ( $self, $opt, $args ) = @_;
	if ( $opt->{help} ) {
		my ($command) = $self->command_names;
		$self->app->execute_command( $self->app->prepare_command( "help", $command ) );
		exit;

lib/Algorithm/Classifier/IsolationForest/App/Command.pm  view on Meta::CPAN

Every C<iforest> subcommand inherits from this.  L<App::Cmd> finds it by
name alone -- L<Algorithm::Classifier::IsolationForest::App>'s C<-app>
setup looks for C<< <app class>::Command >> -- so the command modules
never mention it.

It earns its keep through L</validate_args>, which makes C<-h> print a
command's help instead of being validated like any other flag.

=head1 METHODS

=head2 global_opt_spec

Option-spec hook delegating to an C<options> method on the command,
taking the L<App::Cmd> application object and returning whatever that
method returns.

Nothing reaches this in practice: App::Cmd calls C<global_opt_spec> on
the application class rather than on the command base, and no command
here defines C<options> -- they all use App::Cmd's own C<opt_spec>.
Calling it would die on the missing method.

=head2 validate_args

App::Cmd's per-command validation hook, wrapped so C<-h> short-circuits
it.  Without this, C<-h> would fall through to the command's own
C<validate> and trip over whatever required options the user has not
typed yet -- which is exactly the moment they are reaching for the help.

t/91-streamd.t  view on Meta::CPAN

subtest 'saves: command, interval, symlink' => sub {
	my $r = rt( $c, { cmd => 'save', tag => 's1' } );
	like( $r->{ok}{saved}, qr/\Aoiforest-\d{8}-\d{6}(?:-\d+)?\.json\z/, 'save returns the file name' );
	is( $r->{tag}, 's1', 'save reply carries the tag' );
	ok( -f "$mdir/$r->{ok}{saved}", 'the timestamped file exists' );
	ok( -l $latest,                 'latest.json is a symlink' );
	is( readlink($latest), $r->{ok}{saved}, 'and points at the newest save (relative target)' );

	# Interval saves happen only after learning; learn then wait past the
	# 1s interval.
	my $count_before = () = glob("$mdir/oiforest-*.json");
	rt( $c, { row => [ 0.2, 0.8 ] } );
	select( undef, undef, undef, 2.5 );    ## no critic (ProhibitSleepViaSelect)
	rt( $c, { cmd => 'ping' } );           # tick the loop
	my $count_after = () = glob("$mdir/oiforest-*.json");
	cmp_ok( $count_after, '>', $count_before, 'a periodic save fired after learning' );
}; ## end 'saves: command, interval, symlink' => sub

my $seen_at_shutdown = rt( $c, { cmd => 'stats' } )->{ok}{seen};

subtest 'clean shutdown and resume' => sub {
	is( stop_daemon($daemon), 0, 'SIGTERM exits 0' );
	undef $daemon;
	ok( !-e $sock, 'socket removed' );
	ok( !-e $pidf, 'pid file removed' );



( run in 1.209 second using v1.01-cache-2.11-cpan-0fb53d1c279 )