These are a running webassembly demos (using cheerpj).

Note

This section is work in progress

Calculator

Tip
0
1
2
3
4
5
6
7
8
9
+

for addition

-

for subtraction

for multiplication

/

for division

\

for euclidean division

%

for modulo division

*

for the default 'group operation'

^

for exponentiation

-

negation

( )

brackets

""

quotes

java

This is the source code for the above.

@Getter
public  enum FieldInformation {
    rational(RationalNumbers.INSTANCE, "1 + 2", "1 + 3/5"),
    real(RealField.INSTANCE, "1 + 2", "1 + 3/5", "sin(𝜋/2)", "sqr(𝜑) - 𝜑"),
    bigdecimal(BigDecimalField.INSTANCE, "1 + 2", "1 + 3/5", "sin(𝜋/2)"),
    gaussian(GaussianRationals.INSTANCE, "1 + 2", "1 + 3/5", "\"1 + 2i\" ⋅ 8i"),
    complex(ComplexNumbers.INSTANCE, "1 + 2", "1 + 3/5", "sin(𝜋/2)", "exp(-i ⋅ 𝜋)", "\"2 + 3i\" ⋅ i"),
    bigcomplex(BigComplexNumbers.INSTANCE, "1 + 2", "1 + 3/5", "\"1 + 2i\" ⋅ 8i"),
    quaternions(Quaternions.of(RationalNumbers.INSTANCE),
        "1 + 2", "1 + 3/5", "\"1 + 2i + 3j + 4k\" ⋅ 8i"),
    quaternions_bigdecimal(Quaternions.of(BigDecimalField.INSTANCE),
        "1 + 2", "1 + 3/5", "\"1 + 2i + 3j + 4k\" ⋅ 8i"),
    integers(Integers.INSTANCE, "4 ⋅ 7", "9 - 3"),
    modulo10(ModuloRing.of(10), "4 ⋅ 7", "9 - 3"),
    modulo13(ModuloField.of(13), "10 ⋅ 7", "10 - 3", "12 ⋅ 6 / 4"),
    natural(NaturalNumbers.INSTANCE, "10 ⋅ 7", "10 - 3", "12 ⋅ 6 / 4"),
    even(EvenIntegers.INSTANCE, "10 ⋅ 8", "10 - 4"),
    squares(Squares.INSTANCE, "2 ⋅ 9"),

    klein(KleinGroup.INSTANCE,
        "a * b * c * e",
        "a * b"
    ),
    quaterniongroup(QuaternionGroup.INSTANCE, "i", "e" ),
    dihedral3(DihedralGroup.D3,
        "r1 * r2",
        "s0 * r1 * s0"
    ),
    dihedral4(DihedralGroup.of(4),
        "r1 * r2",
        "s0 * r1 * s0 * s3"
    )
    ;

    private final Magma<?> field;
    private final String[] examples;
    private final String[] elements;
    private final String[] binaryOperators;
    private final String[] unaryOperators;

    private final boolean finite;

    FieldInformation(Magma<?> field, String... examples) {
        this.field = field;
        this.finite = field.isFinite();
        this.examples = examples;
        this.elements = elements(field);
        this.binaryOperators = field.getSupportedOperators()
            .stream()
            .map(AlgebraicBinaryOperator::getSymbol)
            .toArray(String[]::new);

        this.unaryOperators = field.getSupportedUnaryOperators()
            .stream()
            .map(AlgebraicUnaryOperator::getSymbol)
            .toArray(String[]::new);

        log.fine("Created %s, operators: %s, unary: %s examples: %s, elements: %s".formatted(field,
            List.of(binaryOperators),
            List.of(unaryOperators),
            List.of(examples), List.of(elements)));
    }

    public static String[] elements(Magma<?> field) {
        Set<String> elements = new LinkedHashSet<>(field.getConstants().keySet());
        if (field.getCardinality().isCountable() && field instanceof  Streamable<?> streamable) {
            streamable.stream().limit(100).map(Object::toString).forEach(elements::add);
        }
        return elements.toArray(new String[0]);
    }

    public String getDescription() {
        return field.getClass().getSimpleName() + " " + field;
    }

    public String getHelp() {
        return field.getDescription().orElse(null);
    }
}



public static String eval(final String expression, final String field) {
    try (var r = ConfigurationService.setConfiguration(cb -> cb
        .configure(UncertaintyConfiguration.class,
            (ub) -> ub.withNotation(ROUND_VALUE))
        .configure(MathContextConfiguration.class,
            (mc) -> mc.withContext(new MathContext(Utils.PI.length())))
    )) {
        var f = FieldInformation.valueOf(field).getField();

        log.fine(() -> "Evaluating expression in %s: %s. Binary: %s, Unary: %s".formatted(f, expression, f.getSupportedOperators(), f.getSupportedUnaryOperators()));
        if (f.getSupportedOperators().isEmpty()) {
            log.log(Level.SEVERE,  "Supported operators is empty for " + f);
        }
        var parsedExpression = AST.parse(expression, f);
        log.fine(() -> "Parsed expression: %s".formatted( parsedExpression));
        var result = parsedExpression.eval();
        var resultAsString = result.toString();
        log.info(() -> "Result: %s = %s".formatted(expression, resultAsString));
        return resultAsString;
    } catch (Throwable ex) {
        log.log(Level.SEVERE,  ex.getClass() + " " + ex.getMessage(), ex);
        throw ex;
    } finally {
        log.finer("Ready evaluation");
    }
}
javascript

This is the source code for the above.

    constructor() {
        super('#calculator', 'org.meeuw.math.demo.Calculator');
        this.input = this.form.querySelector('input');
        this.field = this.form.querySelector('select');
        this.inputDataList= this.form.querySelector('datalist');
        this.information = null;
    }

    insert(string) {
        const input = this.input;
        const start = input.selectionStart;
        const end = input.selectionEnd;
        const value = input.value;
        input.value = value.slice(0, start) + string + value.slice(end);
        input.setSelectionRange(
            start + string.length,
            start + string.length
        );
        input.focus();
    }

    insertOperator(string) {
        const needsBrackets = string.length > 1;
        if (! needsBrackets) {
            return this.insert(string);
        }
        const input = this.input;
        const start = input.selectionStart;
        const end = input.selectionEnd;
        const value = input.value;
        if (start === end) {
            input.value = string + "(" + value + ")";
            input.setSelectionRange(
                start + string.length + 1,
                start + string.length + 1
            );
        } else {
            input.value = value.slice(0, start) + string + "(" + value.slice(start, end) + ")" + value.slice(end);
            input.setSelectionRange(
                start,
                end  + string.length + 2
            );
        }

        input.focus();
    }

    async setupForm() {
        await super.setupForm();
        this.form.addEventListener('beforeinput', async (e) => {
            this.form.querySelector("span.help").innerHTML = '';
            if (e.data === '=') {
                console.log(this.input.value);
                e.preventDefault();
                e.stopImmediatePropagation();
                await this.handleSubmit();
            }
             if (e.data === '*') {
                 this.form.querySelector("span.help").innerHTML = "to type * use ;";
                 e.preventDefault();
                 e.stopImmediatePropagation();
                 this.insert('')
             }
            if (e.data === ';') {
                 e.preventDefault();
                 e.stopImmediatePropagation();
                 this.insert('*')
             }
        });
    }

    async onInView(Calculator){

        await super.onInView(Calculator);
        // using the field information to update the example per field
        if (this.information === null) {
            this.information = {};
            const fi = await (await BaseClass.cj)['org.meeuw.math.demo.Calculator$FieldInformation'];
            const values = await fi.values();

            for (let i = 0; i < values.length; i++) {
                const value = await values[i];
                const elements = await BaseClass.awaitedArray(value.getElements());
                let elementSpans = null;
                if (elements) {
                    elementSpans = [];
                    for (let j = 0; j < elements.length; j++) {
                        const span = document.createElement('span');
                        span.classList.add('element');
                        span.textContent = elements[j];
                        span.onclick = async e => {
                            this.insert(e.target.textContent);
                        };
                        elementSpans[j] = span;
                    }
                }
                this.information[await values[i].name()] = {
                    examples: await BaseClass.awaitedArray(value.getExamples()),
                    elements: elements,
                    elementsSpans: elementSpans,
                    binaryOperators: await BaseClass.awaitedArray(value.getBinaryOperators()),
                    unaryOperators: await BaseClass.awaitedArray(value.getUnaryOperators()),
                    finite: await value.isFinite(),
                    description: await value.getDescription(),
                    help: await value.getHelp()
                };
            }
            console.log(JSON.stringify(this.information));
        }
        await this.updateFieldList();
        this.field.addEventListener('change', () => {
            this.updateDataList();
            this.updateHelp();
            this.updateOperators();
            this.updateDigits();

        });
        await this.updateDataList();
        await this.updateHelp();
        await this.updateOperators();
        await this.updateDigits();


    }

    updateFieldList() {
        for (const [key, value] of Object.entries(this.information)) {

            const option = document.createElement('option');
            option.value = key;
            option.text = value.description;
            this.field.appendChild(option);
        }
    }

    async updateDataList() {
        const selectedField = this.field.value;
        const information = this.information[selectedField];
        if (information) {
            this.inputDataList.innerHTML = '';
            for (const example of information.examples) {
                const option = document.createElement('option');
                option.value = example;
                this.inputDataList.appendChild(option);
            }
            console.log("Updated data list for", selectedField, information.examples);
        }
    }
    async updateHelp() {
        const fieldInformation =  this.information[this.field.value];
        const div = this.field.parentNode.querySelector("div.help");
        div.innerHTML = '';
        let help = fieldInformation.help;
        if (help) {
            div.appendChild(document.createTextNode(help));
        }
        const elements = this.information[this.field.value].elementSpans;
        if (elements) {
            div.appendChild(document.createElement("br"));
            div.appendChild(document.createTextNode("elements: "));
            elements.forEach(element => {
                div.appendChild(element);
            })
            if (!fieldInformation.finite) {
                div.appendChild(document.createTextNode("... infinitely many more"));
            }
        }
    }
    operatorDts(dl, operators) {
        this.dts(dl, operators, async e => {
            this.insertOperator(e.target.textContent);
        });
    }
    elementsDts(dl, operators) {
        this.dts(dl, operators, async e => {
            this.insert(e.target.textContent);
        });
    }

    dts(dl, operators, onclick) {
        const list = dl.querySelectorAll("dt");
        const symbolsInList = Array.from(list).map(e => e.textContent.trim());
        const unmatchedOperators = operators.filter(op => !symbolsInList.includes(op));
        unmatchedOperators.forEach(op => {
            const dt = document.createElement("dt");
            dt.classList.add('hdlist1');
            dt.textContent = op;
            dl.appendChild(dt);
            const dd = document.createElement("dd");
            dl.appendChild(dd);
        });
        for (const e of dl.querySelectorAll("dt")) {
            const symbol = e.textContent.trim();
            const title = e.nextElementSibling.textContent;
            if (!e.hasAttribute("original-display")) {
                e.setAttribute("original-display", window.getComputedStyle(e).display);
                e.onclick =onclick;
            }
            if (!operators.includes(symbol)) {
                e.style.display = 'none';
                e.nextElementSibling.hidden = true;
            } else {
                e.title = title;
                e.style.display = e.getAttribute("original-display");
                e.nextElementSibling.hidden = false;
            }
        }
    }

    async updateOperators() {
        const fieldInformation =  this.information[this.field.value];
        const operators = fieldInformation.binaryOperators;
        this.operatorDts(document.querySelector("#calculator_operators dl"), operators);
        const unaryOperators = fieldInformation.unaryOperators;
        this.operatorDts(document.querySelector("#calculator_unary_operator dl"), unaryOperators);
    }


    async updateDigits() {
        const fieldInformation =  this.information[this.field.value];
        const elements = fieldInformation.elements;
        this.elementsDts(document.querySelector("#calculator_digits dl"), elements);
    }

    async onSubmit(Calculator) {
        this.output.value = '';
        this.textContent = "executing..";
        //console.log("evaluating", this.input.value, "for", this.field.value);
        this.output.value = await Calculator.eval(
            this.input.value, this.field.value
        );
    }

}

Solver

The same idea as my very first applet. Solving the '24 flippo game'.

Give the desired outcome number, and a few input numbers, and it will find the possible ways to get it using those input numbers.

Tip

This combines several aspects of this project:

  • It uses 'rational numbers' to make all operations exact

  • It uses the permutation group to permute all combinations of values

  • It uses the Abstract Syntax Tree feature to combine values and operators

  • It uses some features of the Field, e.g. to perform operators generically.

  • It will currently use the field of RationalNumbers or GaussianRationals if the input contains a complex number. I might add also suport for RealField (and for example also support the POW operator)

Cheerpj still sometimes behaves a bit erraticly, I think something may be wrong with default methods?




java source code

This is the source code for the above.

package org.meeuw.math.demo;

import lombok.Getter;
import lombok.extern.java.Log;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import org.meeuw.math.abstractalgebra.Ring;
import org.meeuw.math.abstractalgebra.RingElement;
import org.meeuw.math.abstractalgebra.complex.GaussianRationals;
import org.meeuw.math.abstractalgebra.permutations.PermutationGroup;
import org.meeuw.math.abstractalgebra.quaternions.Quaternions;
import org.meeuw.math.abstractalgebra.rationalnumbers.RationalNumbers;
import org.meeuw.math.arithmetic.ast.*;
import org.meeuw.math.exceptions.MathException;
import org.meeuw.math.exceptions.NotParsable;
import org.meeuw.math.operators.AlgebraicBinaryOperator;

import static org.meeuw.math.CollectionUtils.navigableSet;
import static org.meeuw.math.operators.BasicAlgebraicBinaryOperator.*;

/**
 * A tool to evaluate all possible expressions (of a certain number of rational numbers) (and check if it equals a certain value)
 */
@Log
public  class Solver<E extends RingElement<E>> {
    private static final NavigableSet<AlgebraicBinaryOperator> OPERATORS = navigableSet(
        ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION
    );

    private final AtomicLong tries = new AtomicLong();

    @Getter
    private final Ring<E> structure;

    public Solver(Ring<E> structure) {
        this.structure = structure;
    }

    @SafeVarargs
    public final Stream<Expression<E>> stream(E... set) {
        PermutationGroup permutations = PermutationGroup.ofDegree(set.length);

        return permutations.stream()
            .map(permutation -> permutation.permute(set))
            .map(List::of)
            .distinct()
            .flatMap(permuted ->
                AST.stream(
                    permuted,
                    OPERATORS
                )
            )
            .map( e -> e.canonize(structure))
            .distinct()
            .peek(e -> tries.getAndIncrement());
    }



    public Stream<EvaluatedExpression<E>> evaledStream(E... set) {
        return stream(set)
            .map(e -> {
                try {
                    E evaled = e.eval();
                    return new EvaluatedExpression<>(e, evaled);
                } catch (MathException ex) {
                    return null;
                }
            })
            .filter(Objects::nonNull);
    }

    /**
     *
     */
    public  static <E extends RingElement<E>> SolverResult solve(Ring<E> structure, String outcomeString, String inputStrings) {

        ParseResult<E> outcome = parseOutcome(structure, outcomeString);
        ParseResult<E[]> input = parseInput(structure, inputStrings);
        if (outcome.success() && input.success()) {
            return solve(structure, outcome.result(), input.result());
        } else {
            throw new NotParsable(outcome.error() + "/" + input.error());
        }
    }

    public  static <E extends RingElement<E>> SolverResult solve(Ring<E> structure, E outcome, E[] input) {

        Solver<E> solver = new Solver<>(structure);
        AtomicLong matches = new AtomicLong();
        log.info(() -> "Solving input " + List.of(input) + " for " + outcome + " ( in field " + structure + ")");

        return new SolverResult(solver.evaledStream(input)
            .filter(e ->
                e.result().eq(outcome)
            ).peek(e -> matches.getAndIncrement())
            .map(EvaluatedExpression::toString),
            solver.tries, matches, structure);
    }

    public static <F extends RingElement<F>> ParseResult<F> parseOutcome(Ring<F> field, String outcomeString) {
        log.info(() -> "Parsing input " + outcomeString + " in field " + field);

        String resultError = null;
        F result;
        try {
            result = field.fromString(outcomeString);
        } catch (NotParsable pe) {
            result = null;
            resultError = pe.getMessage();
        }
        return new ParseResult<F>(outcomeString, result, resultError);
    }
    public static <F extends RingElement<F>> ParseResult<F[]> parseInput(Ring<F> field, String inputStrings) {
        log.info(() -> "Parsing input " + inputStrings + " in field " + field);

        String inputError = null;

        String[] input = inputStrings.split("\\s+");
        F[] set = field.newArray(input.length);
        try {
            for (int i = 0; i < set.length; i++) {
                set[i] = field.fromString(input[i]);
            }
        } catch (NotParsable pe) {
            inputError = pe.getMessage();
        }
        return new ParseResult<>(inputStrings, set, inputError);
    }

    public static Ring<?> algebraicStructureFor(String outcomeString, String input) {
        log.info(() -> "Determining algebraic structure for outcome " + outcomeString + " and input " + input);
        if (outcomeString.matches(".*[jk].*") || input.matches(".*[jk].*")) {
            return Quaternions.of(RationalNumbers.INSTANCE);
        } else if (outcomeString.contains("i") || input.contains("i")) {
            return GaussianRationals.INSTANCE;
        } else {
            return RationalNumbers.INSTANCE;
        }
    }


    public record SolverResult(Stream<String> stream, AtomicLong tries, AtomicLong matches, Ring<?> field) {


    }

    public static void main(String[] integers) {
        if (integers.length < 3) {
            System.out.println();
            System.exit(1);
        }
        String resultString = integers[0];
        String inputStrings = String.join(" ", Arrays.copyOfRange(integers, 1, integers.length));

        Ring<?> field = algebraicStructureFor(resultString, inputStrings);
        SolverResult solverResult = Solver.solve(field, resultString, inputStrings);
        solverResult.stream().forEach(System.out::println);
        System.out.println("ready, found " + solverResult.matches().get() + ", tried " + solverResult.tries.get() + ", field " + solverResult.field().toString());
    }
}
javascript

This is the source code for the above.

    async onSubmit(Solver) {
        this.output.value += "using: " + await (this.model.field).toString();
        const solverResult = await Solver.solve(
            this.model.field, this.outcome.value, this.input.value
            );

        const stream = await solverResult.stream();
        const lines = await stream.toArray();
        for (let i = 0; i < lines.length; i++) {
            this.output.value += "\n" + await lines[i].toString();
        }
        const tries = await (await solverResult.tries()).get();
        const matches = await (await solverResult.matches()).get();
        this.output.value += `\nFound: ${matches}`;
        this.output.value += `\nTried: ${tries}`;
    }
}

Dynamic date parsing

The mihxil-time module contain a 'dynamic date parser. I dusted this of from old mmbase code.



javascript

This is the source code for the above.

async onSubmit(DynamicDateTime){
    try {
        const parser = await new DynamicDateTime();
        const parseResult = await parser.applyWithException(this.form.querySelector("#dynamicdate_toparse").value);
        this.output.value = await parseResult.toString();
    } catch (error) {
        console.log(error);
    }

}