Files
fewd/src/wfc_sftt.erl
T
2025-12-16 22:18:51 -08:00

175 lines
4.0 KiB
Erlang

% @doc
% sentence-fun <-> truth table logic
-module(wfc_sftt).
-vsn("0.2.0").
-export_type([
sf/0, tt/0
]).
-export([
ttfun_to_tt/1,
ttfun_to_sf/1,
arity/1,
tt/1, sf/1,
sf_to_tt/1, tt_to_sf/1,
appl_ttf/2, appl/2,
bfls/1,
bfl/2
]).
-type bit() :: 0 | 1.
-opaque sf() :: {sf, wfc_bm:bm()}.
-opaque tt() :: {tt, wfc_bm:bm()}.
-spec ttfun_to_tt(fun()) -> tt().
ttfun_to_tt(Fun) ->
{arity, Arity} = erlang:fun_info(Fun, arity),
Argses = bfls(Arity),
OutputCol = [erlang:apply(Fun, Args) || Args <- Argses],
{tt, wfc_bm:col(OutputCol)}.
-spec ttfun_to_sf(fun()) -> sf().
ttfun_to_sf(Fun) ->
tt_to_sf(ttfun_to_tt(Fun)).
-spec arity(sf() | tt()) -> pos_integer().
arity({sf, BM}) -> matrix_arity(BM);
arity({tt, BM}) -> matrix_arity(BM).
matrix_arity(Matrix) ->
{rc, Height, _} = wfc_bm:shape(Matrix),
log2(Height).
log2(N) when N > 1 ->
1 + log2(N div 2);
log2(1) ->
0.
-spec sf_to_tt(sf()) -> tt().
sf_to_tt({sf, SFBM}) ->
{tt, apply_bmt(SFBM)}.
-spec tt_to_sf(tt()) -> sf().
tt_to_sf({tt, SFBM}) ->
{sf, apply_bmt(SFBM)}.
apply_bmt(Matrix) ->
BMT = wfc_bm:bmt(matrix_arity(Matrix)),
wfc_bm:mul(BMT, Matrix).
tt(List) ->
{tt, wfc_bm:col(List)}.
sf(List) ->
{sf, wfc_bm:col(List)}.
-spec appl_ttf(fun(), [wfc:sentence()]) -> wfc:sentence().
appl_ttf(Fun, Sentences) ->
SF = ttfun_to_sf(Fun),
appl(SF, Sentences).
-spec appl(sf() | tt(), [wfc:sentence()]) -> wfc:sentence().
appl(TT = {tt, _}, Sentences) ->
appl(tt_to_sf(TT), Sentences);
appl(SF = {sf, SFBM}, Sentences) ->
Arity = arity(SF),
Arity = length(Sentences),
% [0, 0, 0], [1, 0, 0], ...
BitFlagLists = bfls(Arity),
io:format("bfls: ~p~n", [BitFlagLists]),
% an SFBM is a Nx1 matrix where N = two_to_the(Arity)
% hacky but works
[Row] = wfc_bm:to_list(wfc_bm:transpose(SFBM)),
io:format("row: ~p~n", [Row]),
% filter by whether or not we're including
Included = filter_included(BitFlagLists, Row),
io:format("included: ~p~n", [Included]),
AlmostSummands = inseminate(Included, Sentences),
io:format("Almost: ~p~n", [AlmostSummands]),
collapse(AlmostSummands).
filter_included([_ | Rest], [0 | Rest2]) ->
filter_included(Rest, Rest2);
filter_included([Item | Rest], [1 | Rest2]) ->
[Item | filter_included(Rest, Rest2)];
filter_included([], []) ->
[].
-spec collapse([[wfc:sentence()]]) -> wfc:sentence().
collapse(AlmostSummands) ->
Summands = lists:map(fun(Args) -> {ok,X} = wfc:mul(Args), X end, AlmostSummands),
{ok, Y} = wfc:add(Summands),
Y.
-spec inseminate(BFLs :: [[bit()]], Sentences :: [wfc:sentence()]) -> [[wfc:sentence()]].
inseminate(BFLs, Sentences) ->
lists:map(fun(BFL) -> inseminate2(BFL, Sentences) end, BFLs).
-spec inseminate2(BFL :: [bit()], Sentences :: [wfc:sentence()]) -> [wfc:sentence()].
inseminate2(BFL, Sentences) ->
lists:zipwith(fun pair/2, BFL, Sentences).
pair(0, _) -> wfc_sentence:one();
pair(1, S) -> S.
-spec bfls(Arity :: non_neg_integer()) -> [[bit()]].
% @doc get all bit-flag lists for 0..(2^Arity - 1)
bfls(Arity) when Arity >= 0 ->
[bfl(Arity, N) || N <- lists:seq(0, two_to_the(Arity) - 1)].
two_to_the(N) ->
1 bsl N.
-spec bfl(Length :: pos_integer(), N :: non_neg_integer()) -> [bit()].
% @doc
% get the little-endian representation of N as binary, as a
% list of bits.
bfl(Length, N) when Length > 0, N >= 0 ->
%% hacky but logically straightforward
bfl2(Length, lists:reverse(integer_to_list(N, 2))).
%% we're chopping digits off the end here
bfl2(Length, Bits_Str) when Length =< length(Bits_Str) ->
bfl3(take(Length, Bits_Str));
bfl2(Length, Bits_Str) when Length > length(Bits_Str) ->
% make more zeros than needed because about to circumcise
% them.
Zeros = [$0 || _ <- lists:seq(1, Length)],
bfl2(Length, Bits_Str ++ Zeros).
take(0, _) ->
[];
take(N, [Item | Rest]) when N >= 1 ->
[Item | take(N-1, Rest)].
bfl3(BitsText) ->
lists:map(fun unfuck_char/1, BitsText).
unfuck_char($0) -> 0;
unfuck_char($1) -> 1.