123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326(*****************************************************************************)(* *)(* MIT License *)(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)moduleStringMap=structmoduleM=Map.Make(String)includeMletof_listl=of_seq(List.to_seql)lett(inner:'aRepr.t):'atRepr.t=letmoduleM=Repr.Of_map(structincludeMletkey_t=Repr.stringend)inM.tinnerletkeyst=bindingst|>List.mapfstletvaluest=bindingst|>List.mapsndletto_bytesprintermap=fold(funkeyeltstate->Bytes.cat(Bytes.of_stringkey)(Bytes.cat(printerelt)state))mapBytes.emptyletshow(show_inner:'a->string):'at->string=funm->"{\n"^String.concat"\n"(List.map(fun(k,v)->k^": "^show_innerv)(bindingsm))^"\n}"letto_pairm=(mapfstm,mapsndm)letadd_uniquekvm=ifmemkmthenraise(Invalid_argument(Printf.sprintf"key %s already present in map."k))elseaddkvm(* Return the union of two maps. The keys of the maps have to be disjoint unless
specifically stated in common_keys. In this case both key's values
are asserted to be equal, with a given equality function.
If no equal function is given the polymorphic euqality is used.*)letunion_disjoint?(common_keys_equal_elt=([],(=)))xy=letcommon_keys,equal_elt=common_keys_equal_eltinunion(funkeyelt_1elt_2->ifnot(List.memkeycommon_keys)thenraise(Invalid_argument(Printf.sprintf"the key %s appears in both union arguments and does not \
belong\n\
\ to common_keys."key))elseifnot(equal_eltelt_1elt_2)thenraise(Invalid_argument(Printf.sprintf"the key %s appears in both union argument with different \
values"key))elseSomeelt_1)xy(* applies union_disjoint on a list of map*)letunion_disjoint_list?(common_keys_equal_elt=([],(=)))map_list=List.fold_left(union_disjoint~common_keys_equal_elt)emptymap_list(* given a list of maps outputs a single map with the union of all keys and
containing lists which consist of the concatenation of the data elements
under the same key (order is preserved) *)letmap_list_to_list_mapmap_list=letjoin_keyxy=Some(x@y)inList.fold_left(funlist_mapm->unionjoinlist_map(map(funx->[x])m))emptymap_listletsub_mapsub_mapmap=letres=filter(funname_->memnamesub_map)mapinifcardinalres<>cardinalsub_mapthenfailwith"sub_map : first argument is not contained in the second.";resletupdate_keysfmap=fold(funkvacc->add(fk)vacc)mapemptymoduleAggregation=struct(* separator between prefixes & name ; must be only one character *)letsep="~"letupdate_key_namefstr=matchString.rindex_from_optstr(String.lengthstr-1)sep.[0]with|None->fstr|Somei->String.substr0(i+1)^f(String.substr(i+1)(String.lengthstr-i-1))letpadded~ni=letstr=string_of_intiinletlen=String.length(string_of_int(n-1))inString.(make(len-lengthstr)'0')^strletadd_prefix?(no_sep=false)?(n=0)?(i=0)?(shift=0)prefixstr=letprefix=ifprefix=""||no_septhenprefixelseprefix^sepinifn=0thenprefix^strelseprefix^padded~n(i+shift)^sep^strletbuild_all_namesprefixnname=List.initn(funi->add_prefix~n~iprefixname)letprefix_map?n?i?shiftprefixstr_map=fold(funk->add(add_prefix?n?i?shiftprefixk))str_mapemptyletof_list?n?shiftprefixnamel=of_list@@List.mapi(funix->(add_prefix?n~i?shiftprefixname,x))l(* This function will merge the maps of the list, by prefixing each key with it’s index in the list, optionnally with a shift, with the index prefix prefixed with zero to we able to handle n elements with the same prefix size (with n either given by [shift] or by the length of [list_map]) ; if a [prefix] is given, it will be put before the index.
*)letmap_of_list_map?(prefix="")?shiftlist_map=letshift,n=Option.value~default:(0,List.lengthlist_map)shiftinList.mapi(funim->prefix_map~n~i~shiftprefixm)list_map|>union_disjoint_listletsmap_of_smap_smapmapmap=fold(funprefixmapres->prefix_mapprefixmap::res)mapmap[]|>union_disjoint_listletgather_maps?(shifts_map=empty)map_list_map=mapi(funnamelist_map->map_of_list_map?shift:(find_optnameshifts_map)list_map)map_list_map|>smap_of_smap_smapletfilter_by_circuit_namecircuit_name=letsep_char=assert(String.lengthsep=1);String.getsep0infilter(funkey_->letname_parts=String.split_on_charsep_charkeyincircuit_name=""||List.exists(String.equalcircuit_name)(* we exclude the last element in [name_parts] *)(List.revname_parts|>List.tl))letselect_answers_by_circuitcircuit_name=map(filter_by_circuit_namecircuit_name)letadd_map_list_mapm1m2=mapi(funkl1->matchfind_optkm2with|Somel2->List.map2union_disjointl1l2|None->l1)m1endendmoduletypeS=sigincludeMap.Swithtypekey=stringandtype'at='aStringMap.tvalt:'aRepr.ty->'atRepr.tyvalof_list:(string*'a)list->'atvalkeys:'at->stringlistvalvalues:'at->'alist(* Splits a map of pairs into a pair of maps *)valto_pair:('a*'b)t->'at*'bt(* [add_unique k v map] adds [k -> v] to [map] & throw an error if [k] is
already in [map]
*)valadd_unique:key->'a->'at->'atvalunion_disjoint:?common_keys_equal_elt:stringlist*('a->'a->bool)->'at->'at->'atvalunion_disjoint_list:?common_keys_equal_elt:stringlist*('a->'a->bool)->'atlist->'atvalmap_list_to_list_map:'atlist->'alistt(* [sub_map m1 m2] returns m2 without the keys that do not appear in m1.
Raises failure if some key of m1 is not in m2
*)valsub_map:'at->'bt->'bt(* USE WITH CAUTION : be sure your update function won’t create duplications *)valupdate_keys:(key->key)->'at->'atmoduleAggregation:sig(* Separator for prefixing *)valsep:string(* applies the input function on the last part of the input string (parts
are delimited by sep).
[update_key_name f ("hello" ^ sep ^ "world" ^ sep ^ "!!")]
returns ["hello" ^ sep ^ "world" ^ sep ^ (f "!!")]
*)valupdate_key_name:(key->key)->key->key(* [add_prefix ~n ~i ~shift prefix str] return idx^prefix^sep^str
idx = [i] + [shift] as a string, eventually padded with '0' before to
allow a numbering until [n] with the same number of caracters
for instance, [prefix ~n:11 ~i:5 ~shift:1 "hello" "world"] will return
"06~hello~world"
[n] is zero by default, this means if no n is specified, no idx will be
added
[no_sep] is false by default ; if set to true, the separator before the
string to prefix will be ommitted :
[prefix ~no_sep:true ~n:11 ~i:5 ~shift:1 "hello" "world"] will return
"06~helloworld"
*)valadd_prefix:?no_sep:bool->?n:int->?i:int->?shift:int->string->string->string(* [build_all_names prefix n k] build the list of all prefixed [k] with
n proofs : [build_all_names "hello" 11 "world"] will return
["hello~00~world" ; "hello~01~world" ; … ; "hello~10~world"] *)valbuild_all_names:key->int->key->keylist(* adds prefix to each key of str_map ; [i] will be added as a string
before the prefix
For instance [prefix_map ~n:3000 ~i:5 ~shift:1 "hello" map] will prefix
all the keys of [map] with "0006~hello~"
*)valprefix_map:?n:int->?i:int->?shift:int->string->'at->'at(* [Aggregation.of_list ~n ~shift s name l] is the same as
[of_list @@ List.mapi (fun i x -> Aggregation.add_prefix ~n ~i ~shift s name, x) l] *)valof_list:?n:int->?shift:int->string->string->'alist->'at(* "c1" -> {"a" ; "b"} ; "c2" -> {"a" ; "c"} becomes
{"c1~a" ; "c1~b" ; "c2~a" ; "c2~c"} with the same values *)valsmap_of_smap_smap:'att->'at(* Converts a map of list of map in a map, by merging each list of map in
one map, prefixing all keys with their proof index, and then merging all
the new maps into one prefixing the keys with the outside map’s keys.
shifts_maps map outside key to pairs of integers.
'key1' -> (7, 20) means that 20 proofs will be produced for key1 in
total and we should start from the 8th one, assuming 7 of them were done
independently. (Note that we may not even finish the whole 20, this
depends on the map_list length).
For example, on input:
{ 'circuit_foo' -> [ {'a' -> fa0; 'b' -> fb0; 'c' -> fc0};
{'a' -> fa1; 'b' -> fb1; 'c' -> fc1} ];
'circuit_bar' -> [ {'a' -> ga0; 'b' -> gb0; 'c' -> gc0} ]; }
outputs
{ 'circuit_foo~0~a' -> fa0;
'circuit_foo~0~b' -> fb0
'circuit_foo~0~c' -> fc0
'circuit_foo~1~a' -> fa1
'circuit_foo~1~b' -> fb1
'circuit_foo~1~c' -> fc1
'circuit_bar~0~a' -> ga0
'circuit_bar~0~b' -> gb0
'circuit_bar~0~c' -> gc0
}
*)valgather_maps:?shifts_map:(int*int)t->'atlistt->'at(* Filter a map keeping the elements whose key corresponds to the given
circuit name *)valfilter_by_circuit_name:string->'at->'at(* [select_answers_by_circuit circuit_name s_map_map] takes a [circuit_name]
and a map with the structure:
{ 'x' -> { 'circuit_foo~0~a' -> [scalar] ;
'circuit_foo~0~b' -> [scalar] ;
...
}
}
and filters the keys of the inner map, keeping the elements whose key
corresponds to the given circuit name. *)valselect_answers_by_circuit:string->'att->'att(* [add_map_list_map m1 m2] will merge [m1] & [m2] ; the resulting map will contain the same keys as [m1] ; [m1] & [m2] can be disjoint, if a key is not found in [m2], the resulting map contains the same binding as [m1] for this key *)valadd_map_list_map:'atlistt->'atlistt->'atlisttendendinclude(StringMap:S)