migration

This commit is contained in:
root
2026-04-13 20:12:01 +03:00
commit 46ab1753a5
201 changed files with 16685 additions and 0 deletions

View File

@@ -0,0 +1,324 @@
package cljtest.linear;
import base.Asserts;
import base.ExtendedRandom;
import base.TestCounter;
import cljtest.ClojureScript;
import clojure.lang.IPersistentVector;
import common.Engine;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Clojure bridge.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Item {
Item ZERO = value(0);
Item ONE = value(1);
int dim();
boolean isValid();
Item refill(ExtendedRandom random);
Engine.Result<?> toClojure();
default Value mapValue(final DoubleUnaryOperator f) {
return value(f.applyAsDouble(value()));
}
default Vector map(final Function<Item, Item> f) {
throw new UnsupportedOperationException("map");
}
default int size() { throw new UnsupportedOperationException("size"); }
default Item get(final int index) { throw new UnsupportedOperationException("get"); }
default double value() {
throw new UnsupportedOperationException("getValue");
}
static Stream<Item> args(final int argc, final Item shape, final ExtendedRandom random) {
return Stream.generate(() -> shape.refill(random)).limit(argc);
}
static Item fromClojure(final Object value) {
if (value instanceof Number n) {
return value(n.doubleValue());
} else if (value instanceof IPersistentVector vector) {
return vector(IntStream.range(0, vector.length()).mapToObj(vector::nth).map(Item::fromClojure));
} else {
throw new AssertionError(value == null ? "null result" : "Unknown type " + value.getClass().getSimpleName());
}
}
static Vector vector(final Stream<? extends Item> items) {
return new Vector(items(items));
}
static Value value(final double value) {
return new Value(value);
}
static List<Item> items(final Stream<? extends Item> items) {
return items.collect(Collectors.toUnmodifiableList());
}
static Supplier<Item> generator(final int... dims) {
Supplier<Item> generator = () -> ZERO;
for (int i = dims.length - 1; i >= 0; i--) {
final int dim = dims[i];
final Supplier<Item> gen = generator;
generator = () -> vector(Stream.generate(gen).limit(dim));
}
return generator;
}
static IntFunction<List<Item>> same(final Supplier<Item> generator) {
return same(generator.get());
}
static IntFunction<List<Item>> same(final Item shape) {
return n -> Collections.nCopies(n, shape);
}
static Engine.Result<?>[] toClojure(final List<Item> args) {
return toArray(args.stream().map(Item::toClojure));
}
static Engine.Result<?>[] toArray(final Stream<? extends Engine.Result<?>> resultStream) {
return resultStream.toArray(Engine.Result<?>[]::new);
}
static List<Fun> functions(final String prefix) {
return functions(prefix, Operation.values());
}
static List<Fun> functions(final String prefix, final Operation... ops) {
return Arrays.stream(ops).map(op -> op.function(prefix)).toList();
}
record Value(double value) implements Item {
public boolean isValid() {
return Double.isFinite(value);
}
@Override
public int dim() {
return 0;
}
@Override
public Value refill(final ExtendedRandom random) {
return new Value(random.nextInt(1, 99) / 10.0);
}
@Override
public Engine.Result<?> toClojure() {
return LinearTester.number(value);
}
@Override
public boolean equals(final Object obj) {
return obj instanceof Value v && Asserts.isEqual(value, v.value, 1e-7);
}
@Override
public String toString() {
return Double.toString(value);
}
}
final class Vector implements Item {
private final List<Item> items;
private final int dim;
private Vector(final List<Item> items) {
this.items = items;
dim = items.stream().mapToInt(Item::dim).max().orElse(0) + 1;
}
@Override
public boolean isValid() {
return items.stream().allMatch(Item::isValid);
}
@Override
public int dim() {
return dim;
}
public int size() {
return items.size();
}
public Item get(final int index) {
return items.get(index);
}
@Override
public Vector refill(final ExtendedRandom random) {
return vector(items.stream().map(item -> item.refill(random)));
}
@Override
public Engine.Result<?> toClojure() {
return LinearTester.vector(toArray(items.stream().map(Item::toClojure)));
}
@Override
public boolean equals(final Object obj) {
return obj instanceof Vector v && items.equals(v.items);
}
@Override
public String toString() {
return items.stream().map(Item::toString).collect(Collectors.joining(", ", "[", "]"));
}
@Override
public Vector map(final Function<Item, Item> f) {
return vector(items.stream().map(f));
}
}
class Fun {
private final Function<List<Item>, Item> expected;
private final ClojureScript.F<?> actual;
public Fun(final String name, final Function<List<Item>, Item> implementation) {
expected = implementation;
actual = ClojureScript.function(name, Object.class);
}
public void test(final TestCounter counter, final Stream<Item> argStream) {
final List<Item> args = items(argStream);
test(counter, args, args);
}
public void test(final TestCounter counter, final List<Item> args, final List<Item> fakeArgs) {
final Item expected = this.expected.apply(fakeArgs);
// if (!expected.isValid()) {
// return;
// }
test(counter, () -> {
final Engine.Result<?> result;
try {
result = actual.call(toClojure(args));
} catch (final RuntimeException | AssertionError e) {
throw new AssertionError("No error expected for " + actual.callToString(toClojure(args)), e);
}
final Item actual = fromClojure(result.value());
if (!expected.equals(actual)) {
throw new AssertionError(result.context() + ": expected " + expected + ", found " + actual);
}
});
// System.err.println("Testing? " + result.context);
}
private static void test(final TestCounter counter, final Runnable action) {
counter.test(() -> {
if (counter.getTestNo() % 1000 == 0) {
counter.println("Test " + counter.getTestNo());
}
action.run();
});
}
public void test(final int args, final Item shape, final TestCounter counter, final ExtendedRandom random) {
test(args, Item.same(shape), counter, random);
}
public void test(final int args, final IntFunction<List<Item>> shapes, final TestCounter counter, final ExtendedRandom random) {
test(shapes.apply(args), counter, random);
}
public void test(final List<Item> shapes, final TestCounter counter, final ExtendedRandom random) {
test(counter, shapes.stream().map(shape -> shape.refill(random)));
}
public void expectException(final TestCounter counter, final Stream<Item> items) {
expectException(counter, toClojure(items.toList()));
}
protected void expectException(final TestCounter counter, final Engine.Result<?>... args) {
test(counter, () -> {
final Engine.Result<Throwable> result = actual.expectException(args);
final boolean ok = result.value() instanceof AssertionError;
if (!ok) {
result.value().printStackTrace();
}
Asserts.assertTrue(
"AssertionError expected instead of " + result.value() + " in " + result.context(),
ok
);
});
}
}
enum Operation {
ADD("+", (a, b) -> a + b, a -> a, ZERO),
SUB("-", (a, b) -> a - b, a -> -a, ZERO),
MUL("*", (a, b) -> a * b, a -> a, ONE),
DIV("d", (a, b) -> a / b, a -> 1 / a, ONE);
private final String suffix;
private final DoubleBinaryOperator binary;
private final DoubleUnaryOperator unary;
private final Item neutral;
Operation(final String suffix, final DoubleBinaryOperator binary, final DoubleUnaryOperator unary,
final Item neutral
) {
this.suffix = suffix;
this.binary = binary;
this.unary = unary;
this.neutral = neutral;
}
public String suffix() {
return suffix;
}
public DoubleBinaryOperator binary() {
return binary;
}
public DoubleUnaryOperator unary() {
return unary;
}
public Item neutral() {
return neutral;
}
public Item apply(final List<Item> args) {
final Item first = args.get(0);
if (first instanceof Value) {
return value(args.size() == 1
? unary.applyAsDouble(first.value())
: args.stream().map(Value.class::cast).mapToDouble(Value::value).reduce(binary).getAsDouble());
} else {
return vector(IntStream.range(0, first.size())
.mapToObj(i -> apply(items(args.stream().map(Vector.class::cast).map(arg -> arg.get(i))))));
}
}
private Fun function(final String prefix) {
return new Fun(prefix + suffix, this::apply);
}
}
}

View File

@@ -0,0 +1,73 @@
package cljtest.linear;
import base.ExtendedRandom;
import base.Selector;
import base.TestCounter;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
* Tests for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class LinearTest {
// === Selector
public static final Selector SELECTOR = new Selector(LinearTester.class, "easy", "hard")
.variant("Base", v(LinearTester::new))
;
private LinearTest() {
}
/* package-private*/ static Consumer<TestCounter> v(final Function<TestCounter, LinearTester> variant) {
return counter -> variant.apply(counter).test();
}
/* package-private*/ static Consumer<TestCounter> variant(final List<Item.Fun> functions, final Consumer<Test> variant) {
return v(counter -> new LinearTester(counter) {
@Override
protected void test(final int args) {
variant.accept(new Test(this, functions, args));
}
});
}
/* package-private */ record Test(
LinearTester test,
List<Item.Fun> functions,
int args
) {
public void test(final Supplier<Item> generator) {
test.test(args, functions, Item.same(generator));
}
public void test(final IntFunction<List<Item>> generator) {
test.test(args, functions, generator);
}
public boolean isHard() {
return test.isHard();
}
public void expectException(final int[] okDims, final int[][] failDims) {
test.expectException(functions, okDims, failDims);
}
public ExtendedRandom random() {
return test.random();
}
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,231 @@
package cljtest.linear;
import base.Tester;
import base.TestCounter;
import cljtest.ClojureScript;
import clojure.lang.IPersistentVector;
import common.Engine;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Tester for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class LinearTester extends Tester {
public static final ClojureScript.F<IPersistentVector> VECTOR_C = ClojureScript.function("clojure.core/vector", IPersistentVector.class);
private static final List<Item.Fun> VECTOR = Item.functions("v");
private static final List<Item.Fun> MATRIX = Item.functions("m");
static {
ClojureScript.loadScript("linear.clj");
}
public static final Item.Fun SCALAR = new Item.Fun("scalar", args -> Item.value(
IntStream.range(0, args.get(0).size())
.mapToDouble(i -> product(args.stream().map(arg -> arg.get(i))))
.sum()
));
public static final Item.Fun V_BY_S = new Item.Fun("v*s", args -> {
final double q = product(args.stream().skip(1));
return args.get(0).map(v -> v.mapValue(a -> a * q));
});
public static final Item.Fun M_BY_S = new Item.Fun("m*s", args -> {
final double q = product(args.stream().skip(1));
return args.get(0).map(row -> row.map(v -> v.mapValue(a -> a * q)));
});
public static final Item.Fun M_BY_V = new Item.Fun("m*v", args -> {
final Item matrix = args.get(0);
final Item vector = args.get(1);
final double[] result = new double[matrix.size()];
for (int i = 0; i < matrix.size(); i++) {
for (int j = 0; j < vector.size(); j++) {
result[i] += matrix.get(i).get(j).value() * vector.get(j).value();
}
}
return Item.vector(Arrays.stream(result).mapToObj(Item::value));
});
public static final Item.Fun M_BY_M = new Item.Fun("m*m", args -> {
Item a = args.get(0);
for (final Item b : args.subList(1, args.size())) {
final double[][] result = new double[a.size()][b.get(0).size()];
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[i].length; j++) {
for (int k = 0; k < b.size(); k++) {
result[i][j] += a.get(i).get(k).value() * b.get(k).get(j).value();
}
}
}
a = Item.vector(Arrays.stream(result).map(row -> Item.vector(Arrays.stream(row).mapToObj(Item::value))));
}
return a;
});
public static final Item.Fun VECT = new Item.Fun("vect", args -> {
double[] a = IntStream.range(0, 3).mapToDouble(i -> args.get(0).get(i).value()).toArray();
for (final Item bb : args.subList(1, args.size())) {
double[] b = IntStream.range(0, 3).mapToDouble(i -> bb.get(i).value()).toArray();
a = new double[]{a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]};
}
return Item.vector(Arrays.stream(a).mapToObj(Item::value));
});
public static final Item.Fun TRANSPOSE = new Item.Fun("transpose", args -> {
final Item matrix = args.get(0);
return Item.vector(IntStream.range(0, matrix.get(0).size()).mapToObj(i -> matrix.map(row -> row.get(i))));
});
private static double product(final Stream<Item> items) {
return items.mapToDouble(Item::value).reduce(1, (a, b) -> a * b);
}
public LinearTester(final TestCounter counter) {
super(counter);
}
protected static Engine.Result<IPersistentVector> vector(final Number... xs) {
return wrap(LinearTester::number, xs);
}
protected static Engine.Result<IPersistentVector> matrix(final Number[]... m) {
return wrap(LinearTester::vector, m);
}
protected static <I, T> Engine.Result<IPersistentVector> wrap(final Function<I, Engine.Result<T>> wrapper, final I[] m) {
return vector(Arrays.stream(m).map(wrapper).toArray(Engine.Result[]::new));
}
protected static Engine.Result<Number> number(final Number x) {
return new Engine.Result<>(x.toString(), x);
}
protected static Engine.Result<IPersistentVector> vector(final Engine.Result<?>... xs) {
return VECTOR_C.call(xs);
}
protected static Number[] row(final Number... numbers) {
return numbers;
}
@Override
public void test() {
runTest(2);
if (isHard()) {
runTest(1);
for (int i = 3; i <= 5; i++) {
runTest(i);
}
expectException(VECTOR, new int[]{3}, new int[][]{{}, {3, 3}});
expectException(MATRIX, new int[]{3, 3}, new int[][]{{}, {3}, {3, 3, 3}});
expectException(List.of(VECT, SCALAR), new int[]{3}, new int[][]{{}, {3, 3}});
final Engine.Result<IPersistentVector> v123 = vector(1L, 2L, 3L);
final Engine.Result<IPersistentVector> v12 = vector(1.1, 2.1);
final Engine.Result<IPersistentVector> m123_456 = matrix(row(1.1, 2.1, 3.1), row(4.1, 5.1, 6.1));
M_BY_S.expectException(counter, m123_456, v123);
M_BY_V.expectException(counter, m123_456, v12);
M_BY_M.expectException(counter, m123_456, v123);
}
}
private void runTest(final int args) {
counter.scope(args + " arg(s)", () -> test(args));
}
protected boolean isHard() {
return counter.mode() > 0;
}
protected void expectException(final List<Item.Fun> funs, final int[] okDims, final int[][] failDims) {
final Supplier<Item> ok = Item.generator(okDims);
Stream.concat(Arrays.stream(failDims), corrupted(okDims)).map(Item::generator).forEach(fail -> {
expectException(funs, ok, fail);
expectException(funs, fail, ok);
});
}
private static Stream<int[]> corrupted(final int... dims) {
return IntStream.range(0, dims.length)
.boxed()
.flatMap(i -> Stream.of(corruptIndex(i, -1, dims), corruptIndex(i, +1, dims)));
}
private static int[] corruptIndex(final int i, final int delta, final int[] dims) {
final int[] nd = dims.clone();
nd[i] += delta;
return nd;
}
@SafeVarargs
protected final void expectException(final List<Item.Fun> funs, final Supplier<Item>... generators) {
for (final Item.Fun fun : funs) {
final Stream<Item> args = Arrays.stream(generators).map(Supplier::get);
fun.expectException(counter, args);
}
}
protected void test(final int args) {
for (int dim = 0; dim <= 10 / TestCounter.DENOMINATOR2; dim++) {
final Supplier<Item> generator = Item.generator(dim);
test(args, VECTOR, generator);
V_BY_S.test(counter, andScalars(args, generator));
SCALAR.test(args, generator.get(), counter, random());
}
for (int complexity = 1; complexity <= 20 / TestCounter.DENOMINATOR2; complexity++) {
for (int dim1 = 1; dim1 <= complexity; dim1++) {
final int dim2 = complexity - dim1;
if (dim2 > 0 || isHard()) {
final Supplier<Item> generator = Item.generator(dim1, dim2);
test(args, MATRIX, generator);
M_BY_S.test(counter, andScalars(args, generator));
M_BY_V.test(counter, Stream.of(generator.get().refill(random()), Item.generator(dim2).get().refill(
random())));
TRANSPOSE.test(counter, Stream.of(generator.get().refill(random())));
}
}
final int complex = complexity;
final int[] dims = IntStream.generate(() -> 1 + random().nextInt(complex)).limit(args + 1).toArray();
M_BY_M.test(counter, IntStream.range(0, args).mapToObj(i -> Item.generator(dims[i], dims[i + 1]).get().refill(
random())));
}
VECT.test(args, Item.generator(3).get(), counter, random());
}
private Stream<Item> andScalars(final int args, final Supplier<Item> generator) {
return Stream.concat(Stream.of(generator.get().refill(random())), Item.args(args - 1, Item.ZERO, random()));
}
protected void test(final int args, final List<Item.Fun> funs, final IntFunction<List<Item>> generator) {
for (final Item.Fun fun : funs) {
fun.test(args, generator, counter, random());
}
}
protected void test(final int args, final List<Item.Fun> funs, final Supplier<Item> generator) {
test(args, funs, Item.same(generator));
}
}