Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

t/02-accel-selection.t  view on Meta::CPAN

	my $f = $CLASS->new(
		n_trees     => 12,
		sample_size => 24,
		seed        => 5,
		use_c       => 0,
	);
	$f->fit( \@data );

	my $json     = $f->to_json;
	my $reloaded = $CLASS->from_json($json);
	is( $reloaded->{_use_c}, $HAS_C, 'from_json picks up the current $HAS_C, not the saved instance flag' );

	my $a = $f->score_samples( \@data );
	my $b = $reloaded->score_samples( \@data );
	is( scalar @$a, scalar @$b, 'same row count after reload' );
}; ## end 'use_c => 0 propagates through reload (from_json/load)' => sub

SKIP: {
	skip 'no Inline::C backend compiled in', 1 unless $HAS_C;

	subtest 'use_c => 1 honoured when $HAS_C is set' => sub {
		my $f = $CLASS->new(
			n_trees     => 20,
			sample_size => 32,
			seed        => 3,
			use_c       => 1,
		);
		is( $f->{_use_c}, 1, '_use_c is 1 after use_c => 1' );

		$f->fit( \@data );
		ok( ref $f->{_c_nodes} eq 'ARRAY' && @{ $f->{_c_nodes} }, 'fit() builds _c_nodes when use_c is on' );
	}; ## end 'use_c => 1 honoured when $HAS_C is set' => sub
} ## end SKIP:

SKIP: {
	skip 'use_c => 0 vs use_c => 1 comparison needs Inline::C', 1
		unless $HAS_C;

	subtest 'C-backed and Perl-fallback scores agree' => sub {
		# Identical seed + identical hyperparameters => identical trees =>
		# the two code paths should produce the same scores (up to the
		# tiny floating-point reordering inside score_all_xs vs the Perl
		# loop -- well under 1e-9 in practice for this scale).
		my $fc = $CLASS->new(
			n_trees     => 30,
			sample_size => 40,
			seed        => 17,
			use_c       => 1,
		);
		my $fp = $CLASS->new(
			n_trees     => 30,
			sample_size => 40,
			seed        => 17,
			use_c       => 0,
		);
		$fc->fit( \@data );
		$fp->fit( \@data );

		my $sc = $fc->score_samples( \@data );
		my $sp = $fp->score_samples( \@data );
		is( scalar @$sc, scalar @$sp, 'same length' );

		my $max_diff = 0;
		for my $i ( 0 .. $#$sc ) {
			my $d = abs( $sc->[$i] - $sp->[$i] );
			$max_diff = $d if $d > $max_diff;
		}
		cmp_ok( $max_diff, '<', 1e-9, "C and Perl scores agree (max diff $max_diff)" );

		# Labels at the default cutoff must match exactly.
		my $lc         = $fc->predict( \@data );
		my $lp         = $fp->predict( \@data );
		my $mismatches = grep { $lc->[$_] != $lp->[$_] } 0 .. $#$lc;
		is( $mismatches, 0, 'predict() labels agree across backends' );
	}; ## end 'C-backed and Perl-fallback scores agree' => sub
} ## end SKIP:

SKIP: {
	skip 'OpenMP not linked in', 1 unless $HAS_OPENMP;

	subtest 'use_openmp => 0 keeps C backend, disables parallel walk' => sub {
		my $f = $CLASS->new(
			n_trees     => 20,
			sample_size => 32,
			seed        => 3,
			use_openmp  => 0,
		);
		is( $f->{_use_c},      1, '_use_c stays on with use_openmp => 0' );
		is( $f->{_use_openmp}, 0, '_use_openmp is 0 after use_openmp => 0' );

		$f->fit( \@data );
		my $scores = $f->score_samples( \@data );
		is( scalar @$scores, scalar @data, 'serial C path still scores every sample' );
	}; ## end 'use_openmp => 0 keeps C backend, disables parallel walk' => sub

	subtest 'use_openmp => 1 honoured when $HAS_OPENMP is set' => sub {
		my $f = $CLASS->new(
			n_trees     => 20,
			sample_size => 32,
			seed        => 3,
			use_openmp  => 1,
		);
		is( $f->{_use_openmp}, 1, '_use_openmp is 1 after use_openmp => 1' );
	};
} ## end SKIP:

subtest 'use_openmp clamped to 0 when use_c is off' => sub {
	# OpenMP only matters with the C tree walk; if the C backend is off
	# the OpenMP flag is meaningless, so the constructor should clear it
	# rather than leaving it set to a value that never gets read.
	my $f = $CLASS->new(
		n_trees     => 10,
		sample_size => 16,
		seed        => 1,
		use_c       => 0,
		use_openmp  => 1,
	);
	is( $f->{_use_c},      0, '_use_c is 0' );
	is( $f->{_use_openmp}, 0, '_use_openmp clamped to 0 since C backend is off' );
}; ## end 'use_openmp clamped to 0 when use_c is off' => sub



( run in 1.272 second using v1.01-cache-2.11-cpan-995e09ba956 )