diff --git a/inference/dimmwitted/.clang-format b/inference/dimmwitted/.clang-format new file mode 100644 index 000000000..ec8234820 --- /dev/null +++ b/inference/dimmwitted/.clang-format @@ -0,0 +1,71 @@ +# A reasonable format for C/C++ +# See: https://google.github.io/styleguide/cppguide.html +# Got by: clang-format-3.7 -style=Google -dump-config +# See: http://clangformat.com +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... + diff --git a/inference/dimmwitted/.gitignore b/inference/dimmwitted/.gitignore new file mode 100644 index 000000000..ae74ebadb --- /dev/null +++ b/inference/dimmwitted/.gitignore @@ -0,0 +1,37 @@ +# Compiled Object files +*.slo +*.lo +*.o +dw +dw_test + +# Compiled Dependency files +*.d + +# Compiled Dynamic libraries +*.so +*.dylib + +# Compiled Static libraries +*.lai +*.la +*.a + +# build dependencies +lib/ + +# others +tags +.DS_Store +*.json +.color_coded +.ycm_extra_conf.py* + +# test artifacts +inference_result.out* +test/*/*.bin +test/*/graph.variables* +test/*/graph.weights* +test/*/graph.factors* +test/*/graph.domains* +test/biased_coin-performance/ diff --git a/inference/dimmwitted/.gitmodules b/inference/dimmwitted/.gitmodules new file mode 100644 index 000000000..7b3723e00 --- /dev/null +++ b/inference/dimmwitted/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test/bats"] + path = test/bats + url = https://github.com/sstephenson/bats diff --git a/inference/dimmwitted/.travis.yml b/inference/dimmwitted/.travis.yml new file mode 100644 index 000000000..6fe69ccbb --- /dev/null +++ b/inference/dimmwitted/.travis.yml @@ -0,0 +1,46 @@ +notifications: + email: + - deepdive-notifications@cs.stanford.edu + # XXX Routing notifications through cs.stanford.edu as Travis cannot directly send to googlegroups.com + # See: https://github.com/travis-ci/travis-ci/issues/2513 + #- deepdive-dev@googlegroups.com + +sudo: false # to use container-based infra, see: http://docs.travis-ci.com/user/migrating-from-legacy/ +addons: + apt: + sources: + # - llvm-toolchain-precise-3.7 # for clang-format-3.7 # XXX this APT repo is down since 2016-05-31, see: http://lists.llvm.org/pipermail/llvm-foundation/2016-May/000020.html + - ubuntu-toolchain-r-test # for gcc 4.8 + packages: + #- clang-format-3.7 + # for building sampler + #- libnuma-dev + - gcc-4.8 + - g++-4.8 +cache: + directories: + - $HOME/clang+llvm + # dependencies + - lib/gtest + - lib/gtest-1.7.0 + - lib/tclap + - lib/numactl + +language: cpp +compiler: + - gcc + +before_install: + # Workaround until clang/LLVM APT returns, see: https://github.com/travis-ci/travis-ci/issues/6120#issuecomment-224072540 + - test -x $HOME/clang+llvm/bin/clang-format || { LLVM_VERSION=3.7.0; wget http://llvm.org/releases/$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-linux-gnu-ubuntu-14.04.tar.xz -O $HOME/clang+llvm.tar.xz && tar xf $HOME/clang+llvm.tar.xz -C $HOME/clang+llvm --strip-components 1; } + # code should be already formatted + - PATH="$HOME/clang+llvm/bin:$PATH" make format + - test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; } +install: + - export CXX="g++-4.8" CC="gcc-4.8" + # skip preparing dependencies if they're cached + - lib/has-all-dep.sh || make dep + - make +script: + - export LD_LIBRARY_PATH="$PWD"/lib/numactl/lib + - make test diff --git a/inference/dimmwitted/LICENSE b/inference/dimmwitted/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/inference/dimmwitted/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/inference/dimmwitted/Makefile b/inference/dimmwitted/Makefile new file mode 100644 index 000000000..460e52122 --- /dev/null +++ b/inference/dimmwitted/Makefile @@ -0,0 +1,209 @@ +# Makefile for DimmWitted Gibbs Sampler + +.DEFAULT_GOAL := all + +################################################################################ +# common compiler flags +CXXFLAGS += -std=c++0x -Wall -Werror -fno-strict-aliasing +LDFLAGS += +LDLIBS += + +ifdef DEBUG +CXXFLAGS += -g -DDEBUG +endif + +# platform dependent compiler flags +UNAME := $(shell uname) + +ifeq ($(UNAME), Linux) +ifndef CXX +CXX = g++ +endif +ifndef DEBUG +CXXFLAGS += -Ofast +endif +endif + +ifeq ($(UNAME), Darwin) +ifndef CXX +CXX = clang++ +endif +ifndef DEBUG +CXXFLAGS += -O3 +endif +# optimization +CXXFLAGS += -stdlib=libc++ +CXXFLAGS += -flto +endif + +################################################################################ +# source files +SOURCES += src/dimmwitted.cc +SOURCES += src/cmd_parser.cc +SOURCES += src/binary_format.cc +SOURCES += src/bin2text.cc +SOURCES += src/text2bin.cc +SOURCES += src/main.cc +SOURCES += src/weight.cc +SOURCES += src/variable.cc +SOURCES += src/factor.cc +SOURCES += src/factor_graph.cc +SOURCES += src/inference_result.cc +SOURCES += src/gibbs_sampler.cc +SOURCES += src/timer.cc +SOURCES += src/numa_nodes.cc +OBJECTS = $(SOURCES:.cc=.o) +PROGRAM = dw + +# header files +HEADERS += $(wildcard src/*.h) + +# test files +TEST_SOURCES += test/test_main.cc +TEST_SOURCES += test/factor_test.cc +TEST_SOURCES += test/binary_format_test.cc +TEST_SOURCES += test/loading_test.cc +TEST_SOURCES += test/factor_graph_test.cc +TEST_SOURCES += test/sampler_test.cc +TEST_OBJECTS = $(TEST_SOURCES:.cc=.o) +TEST_PROGRAM = $(PROGRAM)_test +$(TEST_OBJECTS): CXXFLAGS += -I./src/ + +all: $(PROGRAM) +.PHONY: all + +################################################################################ +# how to link our sampler +$(PROGRAM): $(OBJECTS) + $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) + +# how to link our sampler unit tests +$(TEST_PROGRAM): $(TEST_OBJECTS) $(filter-out src/main.o,$(OBJECTS)) + $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) + +################################################################################ +# compiler generated dependency +# See: http://stackoverflow.com/a/16969086 +DEPENDENCIES = $(SOURCES:.cc=.d) $(TEST_SOURCES:.cc=.d) $(TEXT2BIN_SOURCES:.cc=.d) +-include $(DEPENDENCIES) +CXXFLAGS += -MMD + +# how to compile each source +%.o: %.cc + $(CXX) -o $@ $(CPPFLAGS) $(CXXFLAGS) -c $< + +################################################################################ +# how to get dependencies prepared +dep: +.PHONY: dep + +### Google Test for unit tests +# https://github.com/google/googletest +# test files need gtest +$(OBJECTS): CXXFLAGS += -I./lib/gtest-1.7.0/include/ +$(TEST_OBJECTS): CXXFLAGS += -I./lib/gtest-1.7.0/include/ +$(TEST_PROGRAM): LDFLAGS += -L./lib/gtest/ +$(TEST_PROGRAM): LDLIBS += -lgtest +dep: dep-gtest +dep-gtest: lib/gtest-1.7.0.zip + # gtest for tests + set -eu;\ + cd lib;\ + unzip -o $("$$expected"; \ + done +.PHONY: actual-expected + +################################################################################ +# how to format code +# XXX requiring a particular version since clang-format is not backward-compatible +CLANG_FORMAT_REQUIRED_VERSION := 3.7 +ifndef CLANG_FORMAT +ifneq ($(shell which clang-format-$(CLANG_FORMAT_REQUIRED_VERSION) 2>/dev/null),) + CLANG_FORMAT := clang-format-$(CLANG_FORMAT_REQUIRED_VERSION) +else + CLANG_FORMAT := clang-format +endif +endif +ifeq (0,$(shell $(CLANG_FORMAT) --version | grep -cF $(CLANG_FORMAT_REQUIRED_VERSION))) +format: + @echo '# ERROR: clang-format $(CLANG_FORMAT_REQUIRED_VERSION) required' + @echo '# On a Mac, try:' + @echo 'brew reinstall https://github.com/Homebrew/homebrew-core/raw/0c1a8721e1d2aeca63647f4f1b5f5a1dbe5d9a8b/Formula/clang-format.rb' + @echo '# Otherwise, install a release for your OS from http://llvm.org/releases/' + @false +else +format: + $(CLANG_FORMAT) -i $(SOURCES) $(TEST_SOURCES) $(TEXT2BIN_SOURCES) $(HEADERS) +endif +.PHONY: format diff --git a/inference/dimmwitted/README.md b/inference/dimmwitted/README.md new file mode 100644 index 000000000..5c987afc7 --- /dev/null +++ b/inference/dimmwitted/README.md @@ -0,0 +1,46 @@ +# DimmWitted: Fast Gibbs Sampler [![Build Status](https://travis-ci.org/HazyResearch/sampler.svg)](https://travis-ci.org/HazyResearch/sampler) + +## How fast is DimmWitted? + + - On Amazon EC2's FREE MACHINE (512M memory, 1 core). We can sample 3.6M variables/seconds. + - On a 2-node Amazon EC2 machine, sampling 7 billion random variables, each of which has 10 features, takes 3 minutes. This means we can run inference for all living human beings on this planet with $15 (100 samples!) + - On Macbook, DimmWitted runs 10x faster than DeepDive's default sampler. + +## Usage + +See: [DimmWitted sampler page in DeepDive's documentation](http://deepdive.stanford.edu/sampler). + +The binary format for DimmWitted's input is documented in [doc/binary_format.md](https://github.com/HazyResearch/sampler/blob/master/doc/binary_format.md). + +## Installation + +First, install build dependencies: + + make -j dep + +Then, build: + + make -j + +A modern C++ compiler is required: g++ >= 4.8 or clang++ >= 4.2. +To specify the compiler to use, set the `CXX` variable: + + CXX=/dfs/rulk/0/czhang/software/gcc/bin/g++ make + +To test, run: + + make -j test + + +## Development + +* Follow [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). +* Travis CI tests will error unless you run `make format` before git commits. +* Tests are written with [gtest](https://github.com/google/googletest) and [bats](https://github.com/sstephenson/bats). +* Command-line parsing is done with [TCLAP](http://tclap.sourceforge.net). +* NUMA control is done with [libnuma](http://oss.sgi.com/projects/libnuma/). + + +## Reference + +[C. Zhang and C. RĂ©. DimmWitted: A study of main-memory statistical analytics. PVLDB, 2014.](http://www.vldb.org/pvldb/vol7/p1283-zhang.pdf) diff --git a/inference/dimmwitted/doc/binary_format.md b/inference/dimmwitted/doc/binary_format.md new file mode 100644 index 000000000..9df25b502 --- /dev/null +++ b/inference/dimmwitted/doc/binary_format.md @@ -0,0 +1,72 @@ +# DimmWitted Factor Graph in Binary Format + +This documents the format of input data for DimmWitted Gibbs Sampler. + +DimmWitted uses a custom binary encoding that represents the input factor graph and its weights. +There are five input files in different format: metadata, weights, variables, domains for categorical variables, and factors. +Multiple bytes value must be in [*network byte order* or *Big-endian*](https://en.wikipedia.org/wiki/Endianness). + +## Metadata Text +This input is a single line in [CSV (comma-separated values)](https://en.wikipedia.org/wiki/Comma-separated_values) text format that describes the size of the factor graph with the following fields. + + numWeights,numVariables,numFactors,numEdges + + +## Weights Binary +This input enumerates all weights that can appear in the factor graph. +The following fields must appear in order for each weight. + + weightId uint64_t 8 // unique id for this weight, used by factors + isFixed uint8_t 1 // indicates whether this weight is: + // fixed (0x01) or to be learned (0x00) + initialValue double 8 // value to use when fixed, or + // initial value to start with when learning + +## Variables Binary +This input enumerates all variables present in the factor graph where the following fields are repeated for each variable. + + variableId uint64_t 8 // unique id for this variable, used by factors + isEvidence uint8_t 1 // indicates whether this is: + // a query variable (0x00), + // an evidence variable (0x01 and 0x02), or + // an observation variable (0x02) + initialValue uint64_t 8 // initial value to assign + dataType uint16_t 2 // indicates whether this is: + // a Boolean variable (0x0000), or + // a categorical variable (0x0001) + cardinality uint64_t 8 // cardinality of the variable: + // 2 for Boolean variables, or + // a number for categorical variables + +## Categorical Variable Domains Binary +This input enumerates the domain of categorical variables (i.e., the values each variable can take) +and the truthiness (soft evidence in data programming) for each value. +The following fields are repeated for each variable. + + variableId uint64_t 8 // the id of the variable this block describes + cardinality uint64_t 8 // cardinality of the variable + valueReferences // references to values (one block per cardinality) + +### Value Reference of a Variable + categoryValue uint64_t 8 // a value (id) this variable can take + truthiness double 8 // soft evidence weight; range in [0,1] + + + +## Factors Binary +This input enumerates all factors in the factor graph that refers to variables and weights by their id. +The following fields are repeated for each factor. + + factorFunction uint16_t 2 // type of factor function, see: FACTOR_FUNCTION_TYPE + arity uint64_t 8 // arity of the factor, i.e., how many variables it connects to + variableReferences // references to variables (one block per arity) + weightId uint64_t 8 // the weight id for this factor + featureValue double 8 // feature value for this factor + +For valid values for `factorFunction`, see [`FACTOR_FUNCTION_TYPE` enum](../src/common.h). + +### Variable References of a Factor +Each block of `variableReferences` consists of the following fields: + + variableId uint64_t 8 // the variable id for this factor + equalPredicate uint64_t 8 // value to check equality against the variable diff --git a/inference/dimmwitted/doc/text_format.md b/inference/dimmwitted/doc/text_format.md new file mode 100644 index 000000000..1a122c57b --- /dev/null +++ b/inference/dimmwitted/doc/text_format.md @@ -0,0 +1,41 @@ +# DimmWitted Factor Graph in Textual Format + +DimmWitted provides a handy way to generate the custom binary format from text (TSV; tab-separated values). +`dw text2bin` and `dw bin2text` speaks the textual format described below. + +TODO polish the following + +## Weights TSV +* `weights*.tsv` + 1. wid, e.g., `100` + 2. is fixed (`1`) or not (`0`) + 3. weight value, e.g., `0`, `2.5` + + +## Variables TSV +* `variables*.tsv` + 1. vid, e.g., `0` + 2. is evidence (`0` or `1`) + 3. initial value + 4. variable type (`0` for Boolean, `1` for categorical) + 5. cardinality + +## Categorical Domains TSV +* `domains*.tsv` + 1. vid, e.g., `0` + 2. cardinality, e.g., `3` + 3. array of domain values, e.g., `{2,4,8}` + 4. array of truthiness values, e.g., `{0.1,0,0.9}` + + +## Factors TSV +* `factors*.text2bin-args` + 1. factor function id (See: [`enum FACTOR_FUNCTION_TYPE`](https://github.com/HazyResearch/sampler/blob/master/src/common.h)) + 2. arity: how many variables are connected, e.g., `1` for unary and `2` for binary + 3. a variable value to compare against the variable in corresponding position (one per arity), e.g., negative (`0`) and positive (`1`) for Boolean variables + +* `factors*.tsv` + 1. vids: one or more depending on the given arity delimited by tabs, e.g., `0 1` for binary factors + 1b. value ids (present only for FUNC_AND_CATEGORICAL), e.g., `3 6` + 2. wid, e.g., `100` + 3. feature value, e.g., `3` diff --git a/inference/dimmwitted/lib/gtest-1.7.0.zip b/inference/dimmwitted/lib/gtest-1.7.0.zip new file mode 100644 index 000000000..86f5fd0ce Binary files /dev/null and b/inference/dimmwitted/lib/gtest-1.7.0.zip differ diff --git a/inference/dimmwitted/lib/has-all-dep.sh b/inference/dimmwitted/lib/has-all-dep.sh new file mode 100755 index 000000000..1d8c6fd3d --- /dev/null +++ b/inference/dimmwitted/lib/has-all-dep.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +test -e lib/gtest/libgtest.a +test -e lib/gtest-1.7.0/include/gtest/gtest.h + +test -d lib/tclap/include/tclap + +test -d lib/numactl/include + +test -d lib/zeromq/include +test -e lib/zmq/zmq.hpp + +test -d lib/msgpack/include diff --git a/inference/dimmwitted/lib/numactl-2.0.11.tar.gz b/inference/dimmwitted/lib/numactl-2.0.11.tar.gz new file mode 100644 index 000000000..b20a33596 Binary files /dev/null and b/inference/dimmwitted/lib/numactl-2.0.11.tar.gz differ diff --git a/inference/dimmwitted/lib/tclap-1.2.1.tar.gz b/inference/dimmwitted/lib/tclap-1.2.1.tar.gz new file mode 100644 index 000000000..32737480b Binary files /dev/null and b/inference/dimmwitted/lib/tclap-1.2.1.tar.gz differ diff --git a/inference/dimmwitted/src/bin2text.cc b/inference/dimmwitted/src/bin2text.cc new file mode 100644 index 000000000..1b8e6aee0 --- /dev/null +++ b/inference/dimmwitted/src/bin2text.cc @@ -0,0 +1,153 @@ +/* + * Transform a TSV format factor graph file and output corresponding binary + * format used in DeepDive + */ + +#include "bin2text.h" +#include "text2bin.h" +#include "binary_format.h" +#include "dimmwitted.h" +#include "factor_graph.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dd { + +void dump_variables(const FactorGraph &fg, const std::string &filename) { + std::ofstream fout(filename); + for (size_t i = 0; i < fg.size.num_variables; ++i) { + Variable &v = fg.variables[i]; + // variable id + fout << v.id; + // is_evidence + fout << text_field_delim << (v.is_evid ? "1" : "0"); + // value + fout << text_field_delim << v.assignment_dense; + // data type + std::string type_str; + switch (v.domain_type) { + case DTYPE_BOOLEAN: + type_str = "0"; + break; + case DTYPE_CATEGORICAL: + type_str = "1"; + break; + default: + std::abort(); + } + fout << text_field_delim << type_str; + // cardinality + fout << text_field_delim << v.cardinality; + fout << std::endl; + } +} + +void dump_domains(const FactorGraph &fg, const std::string &filename) { + std::ofstream fout(filename); + for (size_t i = 0; i < fg.size.num_variables; ++i) { + Variable &v = fg.variables[i]; + if (!v.is_boolean()) { + fout << v.id; + fout << text_field_delim << v.cardinality; + fout << text_field_delim; + fout << "{"; + for (size_t j = 0; j < v.cardinality; j++) { + if (j > 0) fout << ","; + fout << fg.get_var_value_at(v, j); + } + fout << "}"; + fout << std::endl; + } + } +} + +void dump_factors(const FactorGraph &fg, const std::string &filename) { + std::ofstream fout(filename); + for (size_t i = 0; i < fg.size.num_factors; ++i) { + const auto &f = fg.factors[i]; + + // var IDs + for (size_t j = 0; j < f.num_vars; ++j) { + const auto &vif = fg.get_factor_vif_at(f, j); + fout << vif.vid; + fout << text_field_delim; + } + + // value IDs (for categorical only) + if (f.is_categorical()) { + for (size_t j = 0; j < f.num_vars; ++j) { + const auto &vif = fg.get_factor_vif_at(f, j); + fout << fg.get_var_value_at(fg.variables[vif.vid], vif.dense_equal_to); + fout << text_field_delim; + } + } + + // weight ID and feature value + fout << f.weight_id; + fout << text_field_delim; + fout << f.feature_value; + fout << std::endl; + } +} + +void dump_weights(const FactorGraph &fg, const std::string &filename) { + std::ofstream fout(filename); + for (size_t i = 0; i < fg.size.num_weights; ++i) { + Weight &w = fg.weights[i]; + // weight id + fout << w.id; + // whether it's fixed + fout << text_field_delim << w.isfixed; + // weight value + fout << text_field_delim << w.weight; + fout << std::endl; + } +} + +void dump_meta(const FactorGraph &fg, const std::string &filename) { + std::ofstream fout(filename); + fout << fg.size.num_weights; + fout << "," << fg.size.num_variables; + fout << "," << fg.size.num_factors; + fout << "," << fg.size.num_edges; + // XXX dummy file names + fout << "," + << "graph.weights"; + fout << "," + << "graph.variables"; + fout << "," + << "graph.factors"; + fout << std::endl; +} + +void dump_factorgraph(const FactorGraph &fg, const std::string &output_dir) { + dump_variables(fg, output_dir + "/variables.tsv"); + dump_domains(fg, output_dir + "/domains.tsv"); + dump_factors(fg, output_dir + "/factors.tsv"); + dump_weights(fg, output_dir + "/weights.tsv"); + dump_meta(fg, output_dir + "/graph.meta"); +} + +int bin2text(const CmdParser &cmd_parser) { + FactorGraphDescriptor meta = read_meta(cmd_parser.fg_file); + // load factor graph + FactorGraph fg(meta); + fg.load_variables(cmd_parser.variable_file); + fg.load_weights(cmd_parser.weight_file); + fg.load_domains(cmd_parser.domain_file); + fg.load_factors(cmd_parser.factor_file); + fg.safety_check(); + + dump_factorgraph(fg, cmd_parser.output_folder); + + return 0; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/bin2text.h b/inference/dimmwitted/src/bin2text.h new file mode 100644 index 000000000..fc9c06a87 --- /dev/null +++ b/inference/dimmwitted/src/bin2text.h @@ -0,0 +1,22 @@ +#ifndef DIMMWITTED_BIN2TEXT_H_ +#define DIMMWITTED_BIN2TEXT_H_ + +#include "cmd_parser.h" +#include "factor_graph.h" + +#include + +namespace dd { + +void dump_factorgraph(const FactorGraph& fg, const std::string& output_dir); +void dump_meta(const FactorGraph& fg, const std::string& filename); +void dump_variables(const FactorGraph& fg, const std::string& filename); +void dump_domains(const FactorGraph& fg, const std::string& filename); +void dump_factors(const FactorGraph& fg, const std::string& filename); +void dump_weights(const FactorGraph& fg, const std::string& filename); + +int bin2text(const CmdParser& cmd_parser); + +} // namespace dd + +#endif // DIMMWITTED_BIN2TEXT_H_ diff --git a/inference/dimmwitted/src/binary_format.cc b/inference/dimmwitted/src/binary_format.cc new file mode 100644 index 000000000..821a66e6d --- /dev/null +++ b/inference/dimmwitted/src/binary_format.cc @@ -0,0 +1,228 @@ +/** + * This file contains binary formatting methods for FactorGraphs and + * CompactFactorGraphs. We think this file is a good place to put the + * definitions of these methods as it conveys the intentions quite clearly + * (i.e. for binary formatting of these objects). + */ +#include "binary_format.h" +#include "common.h" +#include "factor.h" +#include "factor_graph.h" +#include "variable.h" +#include +#include +#include +#include +#include +#include +#include + +namespace dd { + +// Read meta data file, return Meta struct +FactorGraphDescriptor read_meta(const std::string &meta_file) { + FactorGraphDescriptor meta; + std::ifstream file(meta_file); + std::string buf; + getline(file, buf, ','); + meta.num_weights = atoll(buf.c_str()); + getline(file, buf, ','); + meta.num_variables = atoll(buf.c_str()); + getline(file, buf, ','); + meta.num_factors = atoll(buf.c_str()); + getline(file, buf, ','); + meta.num_edges = atoll(buf.c_str()); + return meta; +} + +inline void parallel_load( + const std::vector &filenames, + std::function loader) { + std::vector threads; + for (const auto &filename : filenames) + threads.push_back(std::thread(loader, filename)); + for (auto &t : threads) t.join(); + threads.clear(); +} + +void FactorGraph::load_weights(const std::vector &filenames) { + std::mutex mtx; + parallel_load(filenames, [this, &mtx](const std::string &filename) { + std::ifstream file(filename, std::ios::binary); + size_t count = 0; + while (file && file.peek() != EOF) { + // read fields + size_t wid; + uint8_t isfixed; + double initial_value; + read_be_or_die(file, wid); + read_be_or_die(file, isfixed); + read_be_or_die(file, initial_value); + // load into factor graph + weights[wid] = Weight(wid, initial_value, isfixed); + ++count; + } + { // commit counts + std::lock_guard lck(mtx); + size.num_weights += count; + } + }); +} + +void FactorGraph::load_variables(const std::vector &filenames) { + std::mutex mtx; + parallel_load(filenames, [this, &mtx](const std::string &filename) { + std::ifstream file(filename, std::ios::binary); + size_t num_variables = 0; + size_t num_variables_evidence = 0; + size_t num_variables_query = 0; + while (file && file.peek() != EOF) { + size_t vid; + uint8_t role_serialized; + size_t initial_value; + uint16_t dtype_serialized; + size_t cardinality; + // read fields + read_be_or_die(file, vid); + read_be_or_die(file, role_serialized); + read_be_or_die(file, initial_value); + read_be_or_die(file, dtype_serialized); + read_be_or_die(file, cardinality); + // map serialized to internal values + DOMAIN_TYPE dtype; + switch (dtype_serialized) { + case 0: + dtype = DTYPE_BOOLEAN; + break; + case 1: + dtype = DTYPE_CATEGORICAL; + break; + default: + std::cerr << "[ERROR] Only Boolean and Categorical " + "variables are supported " + "now!" + << std::endl; + std::abort(); + } + bool is_evidence = role_serialized >= 1; + + size_t init_value = is_evidence ? initial_value : 0; + variables[vid] = + Variable(vid, dtype, is_evidence, cardinality, init_value); + ++num_variables; + if (is_evidence) { + ++num_variables_evidence; + } else { + ++num_variables_query; + } + } + { // commit counts + std::lock_guard lck(mtx); + size.num_variables += num_variables; + size.num_variables_evidence += num_variables_evidence; + size.num_variables_query += num_variables_query; + } + }); +} + +void FactorGraph::load_factors(const std::vector &filenames) { + std::mutex mtx; + // an array of mutexes for serializing accesses to the variables + std::unique_ptr mtx_variables( + new std::mutex[size.num_variables]); + std::atomic factor_cntr(0), edge_cntr(0); + parallel_load(filenames, [this, &mtx, &mtx_variables, &factor_cntr, + &edge_cntr](const std::string &filename) { + std::ifstream file(filename, std::ios::binary); + size_t num_factors = 0; + size_t num_edges = 0; + while (file && file.peek() != EOF) { + uint16_t type; + size_t arity; + // read fields + read_be_or_die(file, type); + read_be_or_die(file, arity); + // register the factor + size_t idx = factor_cntr.fetch_add(1, std::memory_order_relaxed); + ++num_factors; + factors[idx] = Factor(idx, DEFAULT_FEATURE_VALUE, Weight::INVALID_ID, + (FACTOR_FUNCTION_TYPE)type, arity); + + size_t edge_idx = edge_cntr.fetch_add(arity, std::memory_order_relaxed); + num_edges += arity; + factors[idx].vif_base = edge_idx; + for (size_t position = 0; position < arity; ++position) { + // read fields for each variable reference + size_t variable_id; + size_t should_equal_to; + read_be_or_die(file, variable_id); + read_be_or_die(file, should_equal_to); + assert(variable_id < capacity.num_variables && variable_id >= 0); + // convert original var value into dense value + size_t dense_val = + variables[variable_id].get_domain_index(should_equal_to); + vifs[edge_idx] = FactorToVariable(variable_id, dense_val); + // add to adjacency lists + if (variables[variable_id].is_boolean()) { + // normalize boolean var vals to 0 for indexing purpusoes + dense_val = Variable::BOOLEAN_DENSE_VALUE; + } + { + std::lock_guard lck(mtx_variables[variable_id]); + variables[variable_id].add_value_factor(dense_val, idx); + } + ++edge_idx; + } + + size_t wid; + read_be_or_die(file, wid); + factors[idx].weight_id = wid; + double val; + read_be_or_die(file, val); + factors[idx].feature_value = val; + } + { // commit counts + std::lock_guard lck(mtx); + size.num_factors += num_factors; + size.num_edges += num_edges; + } + }); +} + +void FactorGraph::load_domains(const std::vector &filenames) { + parallel_load(filenames, [this](const std::string &filename) { + std::ifstream file(filename, std::ios::binary); + + size_t value; + double truthiness; + while (file && file.peek() != EOF) { + // read field to find which categorical variable this block is + // for + size_t vid; + read_be_or_die(file, vid); + Variable &variable = variables[vid]; + // read all category values for this variables + size_t domain_size; + read_be_or_die(file, domain_size); + + assert(!variable.is_boolean()); + assert(variable.cardinality == domain_size); + + variable.domain_map.reset(new std::unordered_map()); + for (size_t i = 0; i < domain_size; ++i) { + read_be_or_die(file, value); + read_be_or_die(file, truthiness); + assert(truthiness >= 0 && truthiness <= 1); + (*variable.domain_map)[value] = {i, truthiness}; + } + + // convert original var value into dense value + if (variable.assignment_dense) { + variable.assignment_dense = + variable.get_domain_index(variable.assignment_dense); + } + } + }); +} + +} // namespace dd diff --git a/inference/dimmwitted/src/binary_format.h b/inference/dimmwitted/src/binary_format.h new file mode 100644 index 000000000..95382f984 --- /dev/null +++ b/inference/dimmwitted/src/binary_format.h @@ -0,0 +1,141 @@ +#ifndef DIMMWITTED_BINARY_FORMAT_H_ +#define DIMMWITTED_BINARY_FORMAT_H_ + +#include "factor_graph.h" + +#include +#include + +// Following gives be64toh() and htobe64() for 64-bit big <-> host endian +// conversions. +// See: http://stackoverflow.com/a/4410728/390044 +#if defined(__linux__) +#include +#elif defined(__APPLE__) +#include +#define be16toh(x) ntohs(x) +#define be32toh(x) ntohl(x) +#define be64toh(x) ntohll(x) +#define htobe16(x) htons(x) +#define htobe32(x) htonl(x) +#define htobe64(x) htonll(x) +#elif defined(__FreeBSD__) || defined(__NetBSD__) +#include +#elif defined(__OpenBSD__) +#include +#define be16toh(x) betoh16(x) +#define be32toh(x) betoh32(x) +#define be64toh(x) betoh64(x) +#define htobe16(x) htobe16(x) +#define htobe32(x) htobe32(x) +#define htobe64(x) htobe64(x) +#endif + +namespace dd { + +/** + * a few specialized functions for writing big endian values + */ +template +inline std::ostream &write_be(std::ostream &output, void *value); +template <> +inline std::ostream &write_be<1>(std::ostream &output, void *value) { + return output.write((char *)value, 1); +} +template <> +inline std::ostream &write_be<2>(std::ostream &output, void *value) { + uint16_t tmp = htobe16(*(uint16_t *)value); + return output.write((char *)&tmp, sizeof(tmp)); +} +template <> +inline std::ostream &write_be<4>(std::ostream &output, void *value) { + uint32_t tmp = htobe32(*(uint32_t *)value); + return output.write((char *)&tmp, sizeof(tmp)); +} +template <> +inline std::ostream &write_be<8>(std::ostream &output, void *value) { + uint64_t tmp = htobe64(*(uint64_t *)value); + return output.write((char *)&tmp, sizeof(tmp)); +} + +/** + * a handy way to serialize values of certain type in big endian + */ +template +inline std::ostream &write_be(std::ostream &output, T value) { + return write_be(output, &value); +} + +#define write_be_or_die(args...) assert(write_be(args)) + +/** + * a few specialized functions for reading big endian values + */ +template +inline std::istream &read_be(std::istream &input, char *dst); +template <> +inline std::istream &read_be<1>(std::istream &input, char *dst) { + return input.read(dst, 1); +} +template <> +inline std::istream &read_be<2>(std::istream &input, char *dst) { + std::istream &s = input.read(dst, 2); + *(uint16_t *)dst = be16toh(*(uint16_t *)dst); + return s; +} +template <> +inline std::istream &read_be<4>(std::istream &input, char *dst) { + std::istream &s = input.read(dst, 4); + *(uint32_t *)dst = be32toh(*(uint32_t *)dst); + return s; +} +template <> +inline std::istream &read_be<8>(std::istream &input, char *dst) { + std::istream &s = input.read(dst, 8); + *(uint64_t *)dst = be64toh(*(uint64_t *)dst); + return s; +} + +/** + * a handy way to deserialize big endian bytes into values of certain type + */ +template +inline std::istream &read_be(std::istream &input, T &value) { + char tmp[sizeof(T)]; + std::istream &s = read_be(input, tmp); + value = *(T *)(tmp); // interpret what's read into type T + return s; +} + +#define read_be_or_die(args...) assert(read_be(args)) + +/** + * Reads meta data from the given file. + * For reference of factor graph file formats, refer to + * deepdive.stanford.edu + */ +FactorGraphDescriptor read_meta(const std::string &meta_file); + +/** + * Loads weights from the given file into the given factor graph + */ +size_t read_weights(const std::string &filename, FactorGraph &); + +/** + * Loads variables from the given file into the given factor graph + */ +size_t read_variables(const std::string &filename, FactorGraph &); + +/** + * Loads factors from the given file into the given factor graph (original mode) + */ +size_t read_factors(const std::string &filename, FactorGraph &); + +/** + * Loads domains for categorical variables + */ +size_t read_domains(const std::string &filename, FactorGraph &fg); + +} // namespace dd + +#endif // DIMMWITTED_BINARY_FORMAT_H_ diff --git a/inference/dimmwitted/src/cmd_parser.cc b/inference/dimmwitted/src/cmd_parser.cc new file mode 100644 index 000000000..f6c9a75af --- /dev/null +++ b/inference/dimmwitted/src/cmd_parser.cc @@ -0,0 +1,294 @@ +#include +#include + +#include "cmd_parser.h" +#include "numa_nodes.h" + +#define HAVE_LONG_LONG // necessary for uint64_t arg parsing +#include + +#include + +namespace dd { + +constexpr char DimmWittedVersion[] = "0.01"; + +// a handy way to get value from MultiArg +template +static inline const T& getLastValueOrDefault(TCLAP::MultiArg& arg, + const T& defaultValue) { + return arg.getValue().empty() ? defaultValue : arg.getValue().back(); +} + +// a way to emulate a /dev/null buffer +class NullBuffer : public std::streambuf { + public: + int overflow(int c) { return c; } +}; +static NullBuffer nullbuf; +static std::ostream nullstream(&nullbuf); + +std::ostream& CmdParser::check(bool condition) { + if (condition) { + return nullstream; + } else { + ++num_errors_; + return std::cerr; + } +} + +std::ostream& CmdParser::recommend(bool condition) { + return condition ? nullstream : std::cerr; +} + +CmdParser::CmdParser( + int argc, const char* const argv[], + const std::map& modes) + : num_errors_(0) { + const char* arg0 = argv[0]; + app_name = argc > 1 ? argv[1] : ""; + ++argv; + --argc; + + // for TCLAP, see: + // http://tclap.sourceforge.net/manual.html#FUNDAMENTAL_CLASSES + + if (app_name == "gibbs") { + TCLAP::CmdLine cmd_("DimmWitted gibbs", ' ', DimmWittedVersion); + + TCLAP::ValueArg fg_file_("m", "fg_meta", + "factor graph metadata file", false, + "", "string", cmd_); + + TCLAP::MultiArg variable_file_( + "v", "variables", "variables file", false, "string", cmd_); + TCLAP::MultiArg domain_file_( + "", "domains", "categorical domains", false, "string", cmd_); + TCLAP::MultiArg factor_file_("f", "factors", "factors file", + false, "string", cmd_); + TCLAP::MultiArg weight_file_("w", "weights", "weights file", + false, "string", cmd_); + + TCLAP::ValueArg output_folder_( + "o", "outputFile", "Output Folder", false, "", "string", cmd_); + + TCLAP::MultiArg n_learning_epoch_("l", "n_learning_epoch", + "Number of Learning Epochs", true, + "int", cmd_); + TCLAP::MultiArg n_inference_epoch_( + "i", "n_inference_epoch", "Number of Samples for Inference", true, + "int", cmd_); + TCLAP::MultiArg burn_in_("", "burn_in", "Burn-in period", false, + "int", cmd_); + TCLAP::MultiArg n_datacopy_( + "c", "n_datacopy", + "Number of factor graph copies. Use 0 for all " + "available NUMA nodes (default)", + false, "int", cmd_); + TCLAP::MultiArg n_threads_( + "t", "n_threads", + "Number of threads to use. Use 0 for all available threads (default)", + false, "int", cmd_); + TCLAP::MultiArg stepsize_("a", "alpha", "Stepsize", false, "double", + cmd_); + TCLAP::MultiArg stepsize2_("p", "stepsize", "Stepsize", false, + "double", cmd_); + TCLAP::MultiArg decay_( + "d", "diminish", "Decay of stepsize per epoch", false, "double", cmd_); + TCLAP::MultiArg reg_param_( + "b", "reg_param", "l2 regularization parameter", false, "double", cmd_); + TCLAP::MultiArg regularization_("", "regularization", + "Regularization (l1 or l2)", + false, "string", cmd_); + + TCLAP::MultiSwitchArg quiet_("q", "quiet", "quiet output", cmd_); + TCLAP::MultiSwitchArg sample_evidence_( + "", "sample_evidence", "also sample evidence variables in inference", + cmd_); + TCLAP::MultiSwitchArg learn_non_evidence_( + "", "learn_non_evidence", "sample non-evidence variables in learning", + cmd_); + TCLAP::MultiSwitchArg noise_aware_( + "", "noise_aware", + "learn using noisy/soft evidence instead of hard evidence", cmd_); + + cmd_.parse(argc, argv); + + fg_file = fg_file_.getValue(); + variable_file = variable_file_.getValue(); + factor_file = factor_file_.getValue(); + weight_file = weight_file_.getValue(); + output_folder = output_folder_.getValue(); + domain_file = domain_file_.getValue(); + + n_learning_epoch = getLastValueOrDefault(n_learning_epoch_, (size_t)0); + n_inference_epoch = getLastValueOrDefault(n_inference_epoch_, (size_t)0); + + n_datacopy = getLastValueOrDefault(n_datacopy_, (size_t)0); + // ensure n_datacopy is a sane number + if (n_datacopy == 0) n_datacopy = NumaNodes::num_configured(); + check(0 < n_datacopy && n_datacopy <= NumaNodes::num_configured()) + << "n_datacopy (" << n_datacopy << ") must be in the range of [0, " + << NumaNodes::num_configured() << "]" << std::endl; + check(NumaNodes::num_configured() % n_datacopy == 0) + << "n_datacopy (" << n_datacopy + << ") must be a divisor of the number of NUMA nodes (" + << NumaNodes::num_configured() << ")" << std::endl; + + n_threads = getLastValueOrDefault(n_threads_, (size_t)0); + size_t NUM_AVAILABLE_THREADS = sysconf(_SC_NPROCESSORS_CONF); + if (n_threads == 0) n_threads = NUM_AVAILABLE_THREADS; + check(0 < n_threads && n_threads <= NUM_AVAILABLE_THREADS) + << "nthreads (" << n_threads << ") must be in the range of [0, " + << NUM_AVAILABLE_THREADS << "]" << std::endl; + // ensure each NUMA node partition gets at least one thread + recommend(n_threads >= n_datacopy) + << "n_threads (" << n_threads + << ") must be greater than or equal to n_datacopy (" << n_datacopy + << "), so each copy can have at least one thread" << std::endl; + // otherwise, just cap the n_datacopy to the n_threads + if (n_threads < n_datacopy) n_datacopy = n_threads; + recommend(n_threads % n_datacopy == 0) + << "n_threads (" << n_threads << ") should be multiples of n_datacopy (" + << n_datacopy << ") or some CPU cores will stay idle" << std::endl; + + burn_in = getLastValueOrDefault(burn_in_, (size_t)0); + stepsize = getLastValueOrDefault(stepsize_, 0.01); + stepsize2 = getLastValueOrDefault(stepsize2_, 0.01); + if (stepsize == 0.01) + stepsize = + stepsize2; // XXX hack to support two parameters to specify step size + decay = getLastValueOrDefault(decay_, 0.95); + reg_param = getLastValueOrDefault(reg_param_, 0.01); + regularization = + getLastValueOrDefault(regularization_, std::string("l2")) == "l1" + ? REG_L1 + : REG_L2; + + should_be_quiet = quiet_.getValue() > 0; + should_sample_evidence = sample_evidence_.getValue() > 0; + should_learn_non_evidence = learn_non_evidence_.getValue() > 0; + is_noise_aware = noise_aware_.getValue() > 0; + + } else if (app_name == "text2bin") { + TCLAP::CmdLine cmd_("DimmWitted text2bin", ' ', DimmWittedVersion); + TCLAP::UnlabeledValueArg text2bin_mode_( + "mode", "what to convert", true, "", + "variable | domain | factor | weight", cmd_); + TCLAP::UnlabeledValueArg text2bin_input_( + "input", "path to an input file formatted in TSV, tab-separated values", + true, "/dev/stdin", "input_file_path", cmd_); + TCLAP::UnlabeledValueArg text2bin_output_( + "output", "path to an output file", true, "/dev/stdout", + "output_file_path", cmd_); + TCLAP::UnlabeledValueArg text2bin_count_output_( + "count_output", "path to a count output file", true, "/dev/stderr", + "count_output_file_path", cmd_); + + // factor-specific arguments + // TODO turn these into labeled args + TCLAP::UnlabeledValueArg text2bin_factor_func_id_( + "func_id", + "factor function id (See: enum FACTOR_FUNCTION_TYPE in " + "https://github.com/HazyResearch/sampler/blob/master/src/common.h)", + true, 0, "func_id"); + TCLAP::UnlabeledValueArg text2bin_factor_arity_( + "arity", "arity of the factor, e.g., 1 | 2 | ...", true, 1, "arity"); + TCLAP::UnlabeledMultiArg text2bin_factor_variables_should_equal_to_( + "var_is_positive", + "whether each variable in position is positive or not, 1 or 0", 1, + "var_is_positive"); + if (argc > 0 && std::string(argv[1]) == "factor") { + cmd_.add(text2bin_factor_func_id_); + cmd_.add(text2bin_factor_arity_); + cmd_.add(text2bin_factor_variables_should_equal_to_); + } + + cmd_.parse(argc, argv); + + text2bin_mode = text2bin_mode_.getValue(); + text2bin_input = text2bin_input_.getValue(); + text2bin_output = text2bin_output_.getValue(); + text2bin_count_output = text2bin_count_output_.getValue(); + text2bin_factor_func_id = + static_cast(text2bin_factor_func_id_.getValue()); + text2bin_factor_arity = text2bin_factor_arity_.getValue(); + for (const auto& value : + text2bin_factor_variables_should_equal_to_.getValue()) { + text2bin_factor_variables_should_equal_to.push_back(value); + } + + } else if (app_name == "bin2text") { + TCLAP::CmdLine cmd_("DimmWitted bin2text", ' ', DimmWittedVersion); + + TCLAP::ValueArg fg_file_("m", "fg_meta", + "factor graph metadata file", false, + "", "string", cmd_); + + TCLAP::MultiArg variable_file_( + "v", "variables", "variables file", false, "string", cmd_); + TCLAP::MultiArg domain_file_( + "", "domains", "categorical domains", false, "string", cmd_); + TCLAP::MultiArg factor_file_("f", "factors", "factors file", + false, "string", cmd_); + TCLAP::MultiArg weight_file_("w", "weights", "weights file", + false, "string", cmd_); + + TCLAP::ValueArg output_folder_( + "o", "outputFile", "Output Folder", false, "", "string", cmd_); + + cmd_.parse(argc, argv); + + fg_file = fg_file_.getValue(); + variable_file = variable_file_.getValue(); + domain_file = domain_file_.getValue(); + factor_file = factor_file_.getValue(); + weight_file = weight_file_.getValue(); + output_folder = output_folder_.getValue(); + + } else if (argc == 0 || modes.find(app_name) == modes.cend()) { + ++num_errors_; + std::cout << "DimmWitted (https://github.com/HazyResearch/sampler)" + << std::endl; + std::cout << "Usage: " << arg0 << " MODE [ARG...]" << std::endl; + for (const auto mode : modes) { + std::cout << " " << arg0 << " " << mode.first << std::endl; + } + if (argc > 0) std::cerr << app_name << ": Unrecognized MODE" << std::endl; + } +} + +template +std::ostream& operator<<(std::ostream& out, const std::vector& v) { + if (!v.empty()) { + out << '['; + std::copy(v.begin(), v.end(), std::ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +std::ostream& operator<<(std::ostream& stream, const CmdParser& args) { + stream << "#################GIBBS SAMPLING#################" << std::endl; + stream << "# fg_file : " << args.fg_file << std::endl; + stream << "# variable_file : " << args.variable_file << std::endl; + stream << "# domain_file : " << args.domain_file << std::endl; + stream << "# weight_file : " << args.weight_file << std::endl; + stream << "# factor_file : " << args.factor_file << std::endl; + stream << "# output_folder : " << args.output_folder << std::endl; + stream << "# n_learning_epoch : " << args.n_learning_epoch << std::endl; + stream << "# n_inference_epoch : " << args.n_inference_epoch << std::endl; + stream << "# stepsize : " << args.stepsize << std::endl; + stream << "# decay : " << args.decay << std::endl; + stream << "# regularization : " << args.reg_param << std::endl; + stream << "# burn_in : " << args.burn_in << std::endl; + stream << "# n_datacopy : " << args.n_datacopy << std::endl; + stream << "# n_threads : " << args.n_threads << std::endl; + stream << "# learn_non_evidence : " << args.should_learn_non_evidence + << std::endl; + stream << "# is_noise_aware : " << args.is_noise_aware << std::endl; + stream << "################################################" << std::endl; + return stream; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/cmd_parser.h b/inference/dimmwitted/src/cmd_parser.h new file mode 100644 index 000000000..e270c8d67 --- /dev/null +++ b/inference/dimmwitted/src/cmd_parser.h @@ -0,0 +1,85 @@ +#ifndef DIMMWITTED_CMD_PARSER_H_ +#define DIMMWITTED_CMD_PARSER_H_ + +#include "common.h" + +#include +#include +#include +#include + +namespace dd { + +/** + * Command line argument parser + */ +class CmdParser { + private: + size_t num_errors_; + + /** + * A handy way to check conditions, record errors, and produce error messages. + */ + std::ostream &check(bool condition); + /** + * A handy way to generate warning messages but don't count them as errors. + */ + std::ostream &recommend(bool condition); + + public: + // all the arguments are defined in cmd_parser.cpp + std::string app_name; + + std::string fg_file; + std::vector variable_file; + std::vector domain_file; + std::vector factor_file; + std::vector weight_file; + std::string output_folder; + + size_t n_learning_epoch; + size_t n_inference_epoch; + size_t n_datacopy; + size_t n_threads; + size_t burn_in; + double stepsize; + double stepsize2; + double decay; + double reg_param; + regularization_t regularization; + + bool should_be_quiet; + bool should_sample_evidence; + bool should_learn_non_evidence; + + // when on, train with VariableToFactor.truthiness instead of + // Variable.assignment_dense + bool is_noise_aware; + + // text2bin specific members + std::string text2bin_mode; + std::string text2bin_input; + std::string text2bin_output; + std::string text2bin_count_output; + FACTOR_FUNCTION_TYPE text2bin_factor_func_id; + size_t text2bin_factor_arity; + std::vector text2bin_factor_variables_should_equal_to; + + size_t num_errors() { return num_errors_; } + + /** + * Constructs by parsing the given command line arguments + */ + CmdParser( + int argc, const char *const argv[], + const std::map &modes = {}); +}; + +template +std::ostream &operator<<(std::ostream &out, const std::vector &v); + +std::ostream &operator<<(std::ostream &stream, const CmdParser &cmd_parser); + +} // namespace dd + +#endif // DIMMWITTED_CMD_PARSER_H_ diff --git a/inference/dimmwitted/src/common.h b/inference/dimmwitted/src/common.h new file mode 100644 index 000000000..77fd459ec --- /dev/null +++ b/inference/dimmwitted/src/common.h @@ -0,0 +1,136 @@ +#ifndef DIMMWITTED_COMMON_H_ +#define DIMMWITTED_COMMON_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#define LOG_2 0.693147180559945 +#define MINUS_LOG_THRESHOLD -18.42 +#define LINEAR_ZERO_THRESHOLD 0.000001 + +/** + * To use, make with DEBUG flag turned on. + */ +#ifdef DEBUG +#define dprintf(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__); +#else +#define dprintf(fmt, ...) (void)(0) +#endif + +namespace dd { + +typedef enum { + DTYPE_BOOLEAN = 0, + DTYPE_CATEGORICAL = 1, +} DOMAIN_TYPE; + +typedef enum { + FUNC_IMPLY_NATURAL = 0, + FUNC_OR = 1, + FUNC_AND = 2, + FUNC_EQUAL = 3, + FUNC_ISTRUE = 4, + FUNC_LINEAR = 7, + FUNC_RATIO = 8, + FUNC_LOGICAL = 9, + FUNC_AND_CATEGORICAL = 12, + FUNC_IMPLY_MLN = 13, + + FUNC_UNDEFINED = -1, +} FACTOR_FUNCTION_TYPE; + +/** + * Handy way to copy arrays of objects correctly, pointed by unique_ptr + */ +#define COPY_ARRAY_UNIQUE_PTR_MEMBER(array_up, size) \ + COPY_ARRAY(other.array_up.get(), size, array_up.get()) +#define COPY_ARRAY(src, size, dst) std::copy(src, src + size, dst) +#define COPY_ARRAY_IF_POSSIBLE(src, size, dst) \ + if (src && dst) std::copy(src, src + size, dst) + +// Allocate an array T[num] without calling constructors. +// Use fast_alloc_free to deallocate such arrays. +template +T *fast_alloc_no_init(size_t num) { + void *raw_memory = operator new[](num * sizeof(T)); + return static_cast(raw_memory); +} + +// Free an array created by fast_alloc_no_init +template +void fast_alloc_free(T *ptr) { + operator delete[](static_cast(ptr)); +} + +// Copy an array using multiple threads +template +void parallel_copy(const std::unique_ptr &src, std::unique_ptr &dst, + size_t num) { + if (num <= 1000000) { + // small array, single thread + std::copy(src.get(), src.get() + num, dst.get()); + } else { + // big array, multithread + size_t cores = sysconf(_SC_NPROCESSORS_CONF); + std::vector threads; + size_t start = 0, increment = num / cores; + for (size_t i = 0; i < cores; ++i) { + start = i * increment; + size_t count = increment; + if (i == cores - 1) { + count = num - start; + } + T *part_src = src.get() + start; + T *part_dst = dst.get() + start; + threads.push_back(std::thread([part_src, part_dst, count]() { + std::copy(part_src, part_src + count, part_dst); + })); + } + for (auto &t : threads) t.join(); + threads.clear(); + } +} + +/** + * Explicitly say things are unused if they are actually unused. + */ +#define UNUSED(var) (void)(var) + +enum regularization_t { REG_L1, REG_L2 }; + +inline bool fast_exact_is_equal(double a, double b) { + return (a <= b && b <= a); +} + +inline bool is_linear_zero(double x) { + return x <= LINEAR_ZERO_THRESHOLD && x >= -LINEAR_ZERO_THRESHOLD; +} + +/** + * Calculates log(exp(log_a) + exp(log_b)) + */ +inline double logadd(double log_a, double log_b) { + if (log_a < log_b) { // swap them + double tmp = log_a; + log_a = log_b; + log_b = tmp; + } else if (fast_exact_is_equal(log_a, log_b)) { + // Special case when log_a == log_b. In particular this works when both + // log_a and log_b are (+-) INFINITY: it will return (+-) INFINITY + // instead of NaN. + return LOG_2 + log_a; + } + double negative_absolute_difference = log_b - log_a; + if (negative_absolute_difference < MINUS_LOG_THRESHOLD) return (log_a); + return (log_a + log1p(exp(negative_absolute_difference))); +} + +} // namespace dd + +#endif // DIMMWITTED_COMMON_H_ diff --git a/inference/dimmwitted/src/dimmwitted.cc b/inference/dimmwitted/src/dimmwitted.cc new file mode 100644 index 000000000..70e2b5705 --- /dev/null +++ b/inference/dimmwitted/src/dimmwitted.cc @@ -0,0 +1,284 @@ +#include "dimmwitted.h" +#include "assert.h" +#include "bin2text.h" +#include "binary_format.h" +#include "numa_nodes.h" +#include "common.h" +#include "factor_graph.h" +#include "gibbs_sampler.h" +#include "text2bin.h" + +#include +#include +#include +#include +#include + +namespace dd { + +// the command-line entry point +int dw(int argc, const char *const argv[]) { + // available modes + const std::map MODES = { + {"gibbs", gibbs}, // to do the learning and inference with Gibbs sampling + {"text2bin", text2bin}, // to generate binary factor graphs from TSV + {"bin2text", bin2text}, // to dump TSV of binary factor graphs + }; + + // parse command-line arguments + CmdParser cmd_parser(argc, argv, MODES); + if (cmd_parser.num_errors() > 0) return cmd_parser.num_errors(); + + // dispatch to the correct function + const auto &mode = MODES.find(cmd_parser.app_name); + return (mode != MODES.end()) ? mode->second(cmd_parser) : 1; +} + +int gibbs(const CmdParser &args) { + // number of NUMA nodes + size_t n_numa_node = NumaNodes::num_configured(); + // number of max threads per NUMA node + size_t n_thread_per_numa = (sysconf(_SC_NPROCESSORS_CONF)) / (n_numa_node); + + if (!args.should_be_quiet) { + std::cout << std::endl; + std::cout << "#################MACHINE CONFIG#################" + << std::endl; + std::cout << "# # NUMA Node : " << n_numa_node << std::endl; + std::cout << "# # Thread/NUMA Node : " << n_thread_per_numa << std::endl; + std::cout << "################################################" + << std::endl; + std::cout << std::endl; + std::cout << args << std::endl; + } + + FactorGraphDescriptor meta = read_meta(args.fg_file); + std::cout << "Factor graph to load:\t" << meta << std::endl; + + // Allocate the input on the first group of NUMA nodes + NumaNodes::partition(0, args.n_datacopy).bind(); + + // Load factor graph + std::cout << "\tinitializing factor graph..." << std::endl; + FactorGraph *fg = new FactorGraph(meta); + + std::cout << "\tloading factor graph..." << std::endl; + fg->load_variables(args.variable_file); + fg->load_weights(args.weight_file); + fg->load_domains(args.domain_file); + fg->load_factors(args.factor_file); + std::cout << "Factor graph loaded:\t" << fg->size << std::endl; + fg->safety_check(); + fg->construct_index(); + std::cout << "Factor graph indexed:\t" << fg->size << std::endl; + + if (!args.should_be_quiet) { + std::cout << "Printing FactorGraph statistics:" << std::endl; + std::cout << *fg << std::endl; + } + + // Initialize Gibbs sampling application. + DimmWitted dw(fg, fg->weights.get(), args); + + dw.learn(); + + dw.dump_weights(); + + dw.inference(); + + if (dw.opts.n_inference_epoch > 0) { + // dump only if we did any sampling at all + dw.aggregate_results_and_dump(); + } + + return 0; +} + +DimmWitted::DimmWitted(FactorGraph *p_cfg, const Weight weights[], + const CmdParser &opts) + : n_samplers_(opts.n_datacopy), weights(weights), opts(opts) { + size_t n_thread_per_numa = + std::max(size_t(1), opts.n_threads / opts.n_datacopy); + + // copy factor graphs and create samplers + size_t i = 0; + for (auto &numa_nodes : NumaNodes::partition(opts.n_datacopy)) { + numa_nodes.bind(); + std::cout << "CREATE CFG ON NODE ... " << numa_nodes << std::endl; + samplers.push_back(GibbsSampler( + std::unique_ptr( + i == 0 ? + // use the given factor graph for the first sampler + p_cfg + : + // then, make a copy for the rest + new FactorGraph(samplers[0].fg)), + weights, numa_nodes, n_thread_per_numa, i, opts)); + ++i; + } +} + +void DimmWitted::inference() { + const size_t n_epoch = compute_n_epochs(opts.n_inference_epoch); + const size_t nvar = samplers[0].fg.size.num_variables; + const bool should_show_progress = !opts.should_be_quiet; + Timer t_total, t; + + for (auto &sampler : samplers) sampler.infrs.clear_variabletally(); + + // inference epochs + for (size_t i_epoch = 0; i_epoch < n_epoch; ++i_epoch) { + if (should_show_progress) { + std::streamsize ss = std::cout.precision(); + std::cout << std::setprecision(3) << "INFERENCE EPOCH " + << i_epoch * n_samplers_ << "~" + << ((i_epoch + 1) * n_samplers_ - 1) << "...." << std::flush + << std::setprecision(ss); + } + + // restart timer + t.restart(); + + // sample + for (auto &sampler : samplers) sampler.sample(i_epoch); + + // wait for samplers to finish + for (auto &sampler : samplers) sampler.wait(); + + double elapsed = t.elapsed(); + if (should_show_progress) { + std::streamsize ss = std::cout.precision(); + std::cout << std::setprecision(3) << "" << elapsed << " sec." + << "," << (nvar * n_samplers_) / elapsed << " vars/sec" + << std::endl + << std::setprecision(ss); + } + } + + double elapsed = t_total.elapsed(); + std::cout << "TOTAL INFERENCE TIME: " << elapsed << " sec." << std::endl; +} + +void DimmWitted::learn() { + InferenceResult &infrs = samplers[0].infrs; + + const size_t n_epoch = compute_n_epochs(opts.n_learning_epoch); + const size_t nweight = infrs.nweights; + const double decay = opts.decay; + const bool should_show_progress = !opts.should_be_quiet; + Timer t_total, t; + + double current_stepsize = opts.stepsize; + const std::unique_ptr prev_weights(new double[nweight]); + COPY_ARRAY_IF_POSSIBLE(infrs.weight_values.get(), nweight, + prev_weights.get()); + + bool stop = false; + + // learning epochs + for (size_t i_epoch = 0; !stop && i_epoch < n_epoch; ++i_epoch) { + if (should_show_progress) { + std::streamsize ss = std::cout.precision(); + std::cout << std::setprecision(3) << "LEARNING EPOCH " + << i_epoch * n_samplers_ << "~" + << ((i_epoch + 1) * n_samplers_ - 1) << "...." << std::flush + << std::setprecision(ss); + } + + t.restart(); + + // performs stochastic gradient descent with sampling + for (auto &sampler : samplers) sampler.sample_sgd(current_stepsize); + + // wait the samplers to finish + for (auto &sampler : samplers) sampler.wait(); + + stop = update_weights(infrs, t.elapsed(), current_stepsize, prev_weights); + + // assigned weights to all factor graphs + for (size_t i = 1; i < n_samplers_; ++i) + infrs.copy_weights_to(samplers[i].infrs); + + current_stepsize *= decay; + } + + double elapsed = t_total.elapsed(); + std::cout << "TOTAL LEARNING TIME: " << elapsed << " sec." << std::endl; +} + +bool DimmWitted::update_weights(InferenceResult &infrs, double elapsed, + double stepsize, + const std::unique_ptr &prev_weights) { + // sum the weights and store in the first factor graph + // the average weights will be calculated + for (size_t i = 1; i < n_samplers_; ++i) + infrs.merge_weights_from(samplers[i].infrs); + if (n_samplers_ > 1) infrs.average_weights(n_samplers_); + + // calculate the norms of the difference of weights from the current epoch + // and last epoch + double lmax = -INFINITY; + double l2 = 0.0; + for (size_t j = 0; j < infrs.nweights; ++j) { + double diff = fabs(infrs.weight_values[j] - prev_weights[j]); + l2 += diff * diff; + if (lmax < diff) lmax = diff; + } + lmax /= stepsize; + + if (!opts.should_be_quiet) { + std::streamsize ss = std::cout.precision(); + std::cout << std::setprecision(3) << "" << elapsed << " sec." + << "," << (infrs.nvars * n_samplers_) / elapsed << " vars/sec." + << ",stepsize=" << stepsize << ",lmax=" << lmax + << ",l2=" << sqrt(l2) / stepsize << std::endl + << std::setprecision(ss); + } + + // update prev_weights + COPY_ARRAY(infrs.weight_values.get(), infrs.nweights, prev_weights.get()); + + // TODO: early stopping based on convergence + return false; +} + +void DimmWitted::dump_weights() { + // learning weights snippets + const InferenceResult &infrs = samplers[0].infrs; + + if (!opts.should_be_quiet) infrs.show_weights_snippet(std::cout); + + // dump learned weights + std::string filename_text(opts.output_folder + + "/inference_result.out.weights.text"); + std::cout << "DUMPING... TEXT : " << filename_text << std::endl; + std::ofstream fout_text(filename_text); + infrs.dump_weights_in_text(fout_text); + fout_text.close(); +} + +void DimmWitted::aggregate_results_and_dump() { + InferenceResult &infrs = samplers[0].infrs; + + // aggregate assignments across all possible worlds + for (size_t i = 1; i < n_samplers_; ++i) + infrs.aggregate_marginals_from(samplers[i].infrs); + + if (!opts.should_be_quiet) infrs.show_marginal_snippet(std::cout); + + // dump inference results + std::string filename_text(opts.output_folder + "/inference_result.out.text"); + std::cout << "DUMPING... TEXT : " << filename_text << std::endl; + std::ofstream fout_text(filename_text); + infrs.dump_marginals_in_text(fout_text); + fout_text.close(); + + if (!opts.should_be_quiet) infrs.show_marginal_histogram(std::cout); +} + +// compute number of NUMA-aware epochs for learning or inference +size_t DimmWitted::compute_n_epochs(size_t n_epoch) { + return std::ceil((double)n_epoch / n_samplers_); +} + +} // namespace dd diff --git a/inference/dimmwitted/src/dimmwitted.h b/inference/dimmwitted/src/dimmwitted.h new file mode 100644 index 000000000..3755dcff9 --- /dev/null +++ b/inference/dimmwitted/src/dimmwitted.h @@ -0,0 +1,89 @@ +#ifndef DIMMWITTED_DIMMWITTED_H_ +#define DIMMWITTED_DIMMWITTED_H_ + +#include "cmd_parser.h" +#include "factor_graph.h" +#include "gibbs_sampler.h" +#include + +namespace dd { + +/** + * Command-line interface. + */ +int dw(int argc, const char* const argv[]); + +/** + * Runs gibbs sampling using the given command line parser + */ +int gibbs(const CmdParser& cmd_parser); + +/** + * Class for (NUMA-aware) gibbs sampling + * + * This class encapsulates gibbs learning and inference, and dumping results. + * Note the factor graph is copied on each NUMA node. + */ +class DimmWitted { + private: + const size_t n_samplers_; + + public: + const Weight* const weights; // TODO clarify ownership + + // command line parser + const CmdParser& opts; // TODO clarify ownership + + // factor graph copies per NUMA node + std::vector samplers; + + /** + * Constructs DimmWitted class with given factor graph, command line + * parser, + * and number of data copies. Allocate factor graph to NUMA nodes. + * n_datacopy number of factor graph copies. n_datacopy = 1 means only + * keeping one factor graph. + */ + DimmWitted(FactorGraph* p_cfg, const Weight weights[], const CmdParser& opts); + + /** + * Performs learning + * n_epoch number of epochs. A epoch is one pass over data + * n_sample_per_epoch not used any more. + * stepsize starting step size for weight update (aka learning rate) + * decay after each epoch, the stepsize is updated as stepsize = stepsize * + * decay + * reg_param regularization parameter + * is_quiet whether to compress information display + */ + void learn(); + + /** + * Performs inference + * n_epoch number of epochs. A epoch is one pass over data + * is_quiet whether to compress information display + */ + void inference(); + + /** + * Aggregates results from different NUMA nodes + * Dumps the inference result for variables + * is_quiet whether to compress information display + */ + void aggregate_results_and_dump(); + + /** + * Dumps the learned weights + * is_quiet whether to compress information display + */ + void dump_weights(); + + private: + bool update_weights(InferenceResult& infrs, double elapsed, double stepsize, + const std::unique_ptr& prev_weights); + size_t compute_n_epochs(size_t n_epoch); +}; + +} // namespace dd + +#endif // DIMMWITTED_DIMMWITTED_H_ diff --git a/inference/dimmwitted/src/factor.cc b/inference/dimmwitted/src/factor.cc new file mode 100644 index 000000000..68d013c8a --- /dev/null +++ b/inference/dimmwitted/src/factor.cc @@ -0,0 +1,31 @@ +#include "factor.h" + +namespace dd { + +Factor::Factor() + : Factor(INVALID_ID, DEFAULT_FEATURE_VALUE, Weight::INVALID_ID, + FUNC_UNDEFINED, 0) {} + +Factor::Factor(size_t id, double feature_value, size_t weight_id, + FACTOR_FUNCTION_TYPE func_id, size_t num_vars) + : id(id), + feature_value(feature_value), + weight_id(weight_id), + func_id(func_id), + num_vars(num_vars), + vif_base(INVALID_ID) {} + +Factor::Factor(const Factor &other) { *this = other; } + +Factor &Factor::operator=(const Factor &other) { + id = other.id; + feature_value = other.feature_value; + weight_id = other.weight_id; + func_id = other.func_id; + num_vars = other.num_vars; + vif_base = other.vif_base; + + return *this; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/factor.h b/inference/dimmwitted/src/factor.h new file mode 100644 index 000000000..6b38dbd94 --- /dev/null +++ b/inference/dimmwitted/src/factor.h @@ -0,0 +1,306 @@ +#ifndef DIMMWITTED_FACTOR_H_ +#define DIMMWITTED_FACTOR_H_ + +#include "common.h" +#include "variable.h" +#include "weight.h" + +#include +#include +#include + +namespace dd { + +static constexpr double DEFAULT_FEATURE_VALUE = 1; + +/** + * Encapsulates a factor function in the factor graph. + */ +class Factor { + public: + size_t id; // factor id + double feature_value; // feature value + size_t weight_id; // weight id + FACTOR_FUNCTION_TYPE func_id; // factor function id + size_t num_vars; // number of variables + size_t vif_base; // start variable id in FactorGraph.vifs + + static constexpr size_t INVALID_ID = -1; + + /** + * Turns out the no-arg constructor is still required, since we're + * initializing arrays of these objects inside FactorGraph and + * FactorGraph. + */ + Factor(); + + Factor(size_t id, double value, size_t weight_id, + FACTOR_FUNCTION_TYPE func_id, size_t num_vars); + + Factor(const Factor &other); + + Factor &operator=(const Factor &other); + + inline bool is_categorical() const { return func_id == FUNC_AND_CATEGORICAL; } + +#define POTENTIAL_SIGN(func_id) _potential_sign_##func_id + + /** + * Returns potential of the factor. + * (potential is the value of the factor) + * The potential is calculated using the proposal value for variable with + * id vid, and assignments for other variables in the factor. + * + * vifs pointer to variables in the factor graph + * assignments pointer to variable values (array) + * vid variable id to be calculated with proposal + * proposal the proposed value. + */ + inline double potential( + const FactorToVariable vifs[], const size_t assignments[], + const size_t vid = Variable::INVALID_ID, + const size_t proposal = Variable::INVALID_VALUE) const { +#define RETURN_POTENTIAL_FOR(func_id) \ + case func_id: \ + return POTENTIAL_SIGN(func_id)(vifs, assignments, vid, proposal) * \ + this->feature_value +#define RETURN_POTENTIAL_FOR2(func_id, func_id2) \ + case func_id2: \ + RETURN_POTENTIAL_FOR(func_id) + switch (func_id) { + RETURN_POTENTIAL_FOR(FUNC_IMPLY_MLN); + RETURN_POTENTIAL_FOR(FUNC_IMPLY_NATURAL); + RETURN_POTENTIAL_FOR2(FUNC_AND, FUNC_ISTRUE); + RETURN_POTENTIAL_FOR(FUNC_OR); + RETURN_POTENTIAL_FOR(FUNC_EQUAL); + RETURN_POTENTIAL_FOR(FUNC_AND_CATEGORICAL); + RETURN_POTENTIAL_FOR(FUNC_LINEAR); + RETURN_POTENTIAL_FOR(FUNC_RATIO); + RETURN_POTENTIAL_FOR(FUNC_LOGICAL); +#undef RETURN_POTENTIAL_FOR + default: + std::cout << "Unsupported FACTOR_FUNCTION_TYPE = " << func_id + << std::endl; + std::abort(); + } + } + + private: + FRIEND_TEST(FactorTest, ONE_VAR_FACTORS); + FRIEND_TEST(FactorTest, TWO_VAR_FACTORS); + FRIEND_TEST(FactorTest, THREE_VAR_IMPLY); + + // whether a variable's value or proposal satisfies the is_equal condition + inline bool is_variable_satisfied(const FactorToVariable &vif, + const size_t &vid, + const size_t assignments[], + const size_t &proposal) const { + return (vif.vid == vid) ? vif.satisfiedUsing(proposal) + : vif.satisfiedUsing(assignments[vif.vid]); + } + +#define DEFINE_POTENTIAL_SIGN_FOR(func_id) \ + inline double POTENTIAL_SIGN(func_id)( \ + const FactorToVariable vifs[], const size_t assignments[], \ + const size_t vid, const size_t &proposal) const + + /** Return the value of the "equality test" of the variables in the factor, + * with the variable of index vid (wrt the factor) is set to the value of + * the 'proposal' argument. + * + */ + DEFINE_POTENTIAL_SIGN_FOR(FUNC_EQUAL) { + const FactorToVariable &vif = vifs[vif_base]; + /* We use the value of the first variable in the factor as the "gold" + * standard" */ + const bool firstsat = + is_variable_satisfied(vif, vid, assignments, proposal); + + /* Iterate over the factor variables */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + /* Early return as soon as we find a mismatch */ + if (satisfied != firstsat) return -1; + } + return 1; + } + + /** Return the value of the logical AND of the variables in the factor, with + * the variable of index vid (wrt the factor) is set to the value of the + * 'proposal' argument. + * + */ + DEFINE_POTENTIAL_SIGN_FOR(FUNC_AND) { + /* Iterate over the factor variables */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + /* Early return as soon as we find a variable that is not satisfied */ + if (!satisfied) return -1; + } + return 1; + } + + // potential for AND factor over categorical variables + DEFINE_POTENTIAL_SIGN_FOR(FUNC_AND_CATEGORICAL) { + /* Iterate over the factor variables */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + /* Early return as soon as we find a variable that is not satisfied */ + if (!satisfied) return 0; + } + return 1; + } + + /** Return the value of the logical OR of the variables in the factor, with + * the variable of index vid (wrt the factor) is set to the value of the + * 'proposal' argument. + * + */ + DEFINE_POTENTIAL_SIGN_FOR(FUNC_OR) { + /* Iterate over the factor variables */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + /* Early return as soon as we find a variable that is satisfied */ + if (satisfied) return 1; + } + return -1; + } + + /** Return the value of the 'imply (MLN version)' of the variables in the + * factor, with the variable of index vid (wrt the factor) is set to the + * value of the 'proposal' argument. + * + * The head of the 'imply' rule is stored as the *last* variable in the + * factor. + * + * The truth table of the 'imply (MLN version)' function requires to return + * 0.0 if the body of the imply is satisfied but the head is not, and to + * return 1.0 if the body is not satisfied. + * + */ + DEFINE_POTENTIAL_SIGN_FOR(FUNC_IMPLY_MLN) { + /* Compute the value of the body of the rule */ + bool bBody = true; + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars - 1; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + // If it is the proposal variable, we use the truth value of the proposal + bBody &= is_variable_satisfied(vif, vid, assignments, proposal); + } + + if (!bBody) { + // Early return if the body is not satisfied + return 1; + } else { + // Compute the value of the head of the rule + const FactorToVariable &vif = + vifs[vif_base + num_vars - 1]; // encoding of the head, should + // be more structured. + const bool bHead = is_variable_satisfied(vif, vid, assignments, proposal); + return bHead ? 1 : 0; + } + } + + /** Return the value of the 'imply' of the variables in the factor, with the + * variable of index vid (wrt the factor) is set to the value of the + * 'proposal' argument. + * + * The head of the 'imply' rule is stored as the *last* variable in the + * factor. + * + * The truth table of the 'imply' function requires to return + * -1.0 if the body of the rule is satisfied but the head is not, and to + * return 0.0 if the body is not satisfied. + * + */ + DEFINE_POTENTIAL_SIGN_FOR(FUNC_IMPLY_NATURAL) { + /* Compute the value of the body of the rule */ + bool bBody = true; + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars - 1; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + // If it is the proposal variable, we use the truth value of the proposal + bBody &= is_variable_satisfied(vif, vid, assignments, proposal); + } + + if (!bBody) { + // Early return if the body is not satisfied + return 0; + } else { + // Compute the value of the head of the rule */ + const FactorToVariable &vif = + vifs[vif_base + num_vars - 1]; // encoding of the head, should + // be more structured. + bool bHead = is_variable_satisfied(vif, vid, assignments, proposal); + return bHead ? 1 : -1; + } + } + + // potential for linear expression + DEFINE_POTENTIAL_SIGN_FOR(FUNC_LINEAR) { + double res = 0.0; + bool bHead = is_variable_satisfied(vifs[vif_base + num_vars - 1], vid, + assignments, proposal); + /* Compute the value of the body of the rule */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars - 1; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + res += ((1 - satisfied) || bHead); + } + if (num_vars == 1) + return double(bHead); + else + return res; + } + + // potential for linear expression + DEFINE_POTENTIAL_SIGN_FOR(FUNC_RATIO) { + double res = 1.0; + bool bHead = is_variable_satisfied(vifs[vif_base + num_vars - 1], vid, + assignments, proposal); + /* Compute the value of the body of the rule */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars - 1; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + res += ((1 - satisfied) || bHead); + } + if (num_vars == 1) return log2(res + double(bHead)); + return log2(res); + } + + // potential for linear expression + DEFINE_POTENTIAL_SIGN_FOR(FUNC_LOGICAL) { + double res = 0.0; + bool bHead = is_variable_satisfied(vifs[vif_base + num_vars - 1], vid, + assignments, proposal); + /* Compute the value of the body of the rule */ + for (size_t i_vif = vif_base; i_vif < vif_base + num_vars - 1; ++i_vif) { + const FactorToVariable &vif = vifs[i_vif]; + const bool satisfied = + is_variable_satisfied(vif, vid, assignments, proposal); + res += ((1 - satisfied) || bHead); + } + if (num_vars == 1) + return double(bHead); + else { + if (res > 0.0) + return 1.0; + else + return 0.0; + } + } + +#undef DEFINE_POTENTIAL_SIGN_FOR +}; + +} // namespace dd + +#endif // DIMMWITTED_FACTOR_H_ diff --git a/inference/dimmwitted/src/factor_graph.cc b/inference/dimmwitted/src/factor_graph.cc new file mode 100644 index 000000000..2b713d1da --- /dev/null +++ b/inference/dimmwitted/src/factor_graph.cc @@ -0,0 +1,316 @@ +#include "factor_graph.h" +#include "binary_format.h" +#include "factor.h" + +#include +#include +#include +#include + +namespace dd { + +FactorGraphDescriptor::FactorGraphDescriptor() + : FactorGraphDescriptor(0, 0, 0, 0) {} + +FactorGraphDescriptor::FactorGraphDescriptor(size_t n_var, size_t n_fac, + size_t n_wgt, size_t n_edg) + : num_variables(n_var), + num_factors(n_fac), + num_edges(n_edg), + num_weights(n_wgt), + num_values(0), + num_variables_evidence(0), + num_variables_query(0) {} + +std::ostream &operator<<(std::ostream &stream, + const FactorGraphDescriptor &size) { + stream << "#V=" << size.num_variables; + if (size.num_variables_query + size.num_variables_evidence > 0) { + stream << "("; + stream << "#Vqry=" << size.num_variables_query; + stream << " "; + stream << "#Vevd=" << size.num_variables_evidence; + stream << ")"; + } + stream << " " + << "#F=" << size.num_factors; + stream << " " + << "#W=" << size.num_weights; + stream << " " + << "#E=" << size.num_edges; + stream << " " + << "#Val=" << size.num_values; + return stream; +} + +FactorGraph::FactorGraph(const FactorGraphDescriptor &capacity) + : capacity(capacity), + size(), + // fast alloc: 0 sec for a 270M-factor graph + weights(fast_alloc_no_init(capacity.num_weights)), + factors(fast_alloc_no_init(capacity.num_factors)), + vifs(fast_alloc_no_init(capacity.num_edges)), + variables(fast_alloc_no_init(capacity.num_variables)), + factor_index(fast_alloc_no_init(capacity.num_edges)), + values(fast_alloc_no_init(capacity.num_values)) +// slow alloc: 55 sec for a 270M-factor graph +// weights(new Weight[capacity.num_weights]), +// factors(new Factor[capacity.num_factors]), +// vifs(new FactorToVariable[capacity.num_edges]), +// variables(new Variable[capacity.num_variables]), +// factor_index(new size_t[capacity.num_edges]), +// values(new VariableToFactor[capacity.num_values]) +{ + for (size_t i = 0; i < capacity.num_variables; ++i) { + // make sure the pointers are null after the fast mem allocation + // NOTE: if we introduce more pointers in the data structures, add here. + // see also ~FactorGraph() + variables[i].domain_map.release(); + variables[i].adjacent_factors.release(); + } +} + +FactorGraph::~FactorGraph() { + // manually free pointers in array elements before dirty-free the arrays + for (size_t i = 0; i < capacity.num_variables; ++i) { + variables[i].domain_map.reset(); + variables[i].adjacent_factors.reset(); + } + fast_alloc_free(weights.release()); + fast_alloc_free(factors.release()); + fast_alloc_free(vifs.release()); + fast_alloc_free(variables.release()); + fast_alloc_free(factor_index.release()); + fast_alloc_free(values.release()); +} + +// Construct index using multiple threads +// 6 sec instead of 30 sec for a 270M-factor graph +// std::vector> params; +void FactorGraph::construct_index() { + size_t total = size.num_variables; + size_t cores = sysconf(_SC_NPROCESSORS_CONF); + size_t increment = total / cores; + + // small graph, single thread + if (total < 1000) { + increment = total; + } + + size_t milestone = 0; + size_t num_values = 0, num_factors = 0; + + std::vector> tasks; + for (size_t i = 0; i < size.num_variables; ++i) { + if (i == milestone) { + milestone += increment; + if (milestone > total) { + milestone = total; + } + tasks.push_back([this, i, milestone, num_values, num_factors]() { + construct_index_part(i, milestone, num_values, num_factors); + }); + } + num_values += variables[i].internal_cardinality(); + if (variables[i].adjacent_factors) { + num_factors += variables[i].adjacent_factors->size(); + } + } + + size.num_values = capacity.num_values = num_values; + values.reset(fast_alloc_no_init(num_values)); + + std::vector threads; + for (const auto &task : tasks) { + threads.push_back(std::thread(task)); + } + for (auto &t : threads) t.join(); + threads.clear(); +} + +void FactorGraph::construct_index_part(size_t v_start, size_t v_end, + size_t val_base, size_t fac_base) { + size_t value_index_base = val_base, factor_index_base = fac_base; + + std::vector value_list; + std::vector truthiness_list; + + // For each variable, sort and uniq adjacent factors by value. + // We deallocate "domain_map" and "adjacent_factors". + for (size_t i = v_start; i < v_end; ++i) { + Variable &v = variables[i]; + v.var_val_base = value_index_base; + v.total_truthiness = 0; + + if (v.is_boolean()) { + // NOTE: we don't support truthiness for boolean vars + values[value_index_base] = + VariableToFactor(Variable::BOOLEAN_DENSE_VALUE, 0, 0, 0); + ++value_index_base; + } else { + if (v.domain_map) { + // explicitly listed domain values; recover the list from map. + value_list.assign(v.cardinality, Variable::INVALID_VALUE); + truthiness_list.assign(v.cardinality, 0); + for (const auto &item : *v.domain_map) { + value_list.at(item.second.index) = item.first; + truthiness_list.at(item.second.index) = item.second.truthiness; + v.total_truthiness += item.second.truthiness; + } + for (size_t j = 0; j < v.cardinality; ++j) { + values[value_index_base] = + VariableToFactor(value_list[j], truthiness_list[j], 0, 0); + ++value_index_base; + } + v.domain_map.reset(); // reclaim memory + } else { + // implicit [0...(cardinality-1)] domain values + // TODO: this branch should be deprecated; i.e., require domains for all + // categorical vars + for (size_t j = 0; j < v.cardinality; ++j) { + values[value_index_base] = VariableToFactor(j, 0, 0, 0); + ++value_index_base; + } + } + } + + if (v.adjacent_factors) { + // sort by + std::sort(v.adjacent_factors->begin(), v.adjacent_factors->end()); + size_t value_dense = Variable::INVALID_VALUE; + size_t last_factor_id = Factor::INVALID_ID; + for (const auto &item : *v.adjacent_factors) { + if (item.value_dense != value_dense) { + value_dense = item.value_dense; + assert(value_dense < v.cardinality); + values[v.var_val_base + value_dense].factor_index_base = + factor_index_base; + } else if (item.factor_id == last_factor_id) { + continue; // dedupe + } + factor_index[factor_index_base] = item.factor_id; + ++factor_index_base; + ++values[v.var_val_base + value_dense].factor_index_length; + last_factor_id = item.factor_id; + } + v.adjacent_factors.reset(); // reclaim memory + } + } +} + +void FactorGraph::safety_check() { + // check if any space is wasted + assert(capacity.num_variables == size.num_variables); + assert(capacity.num_factors == size.num_factors); + assert(capacity.num_edges == size.num_edges); + assert(capacity.num_weights == size.num_weights); + + // check whether variables, factors, and weights are stored + // in the order of their id + for (size_t i = 0; i < size.num_variables; ++i) { + assert(this->variables[i].id == i); + } + for (size_t i = 0; i < size.num_factors; ++i) { + assert(this->factors[i].id == i); + } + for (size_t i = 0; i < size.num_weights; ++i) { + assert(this->weights[i].id == i); + } +} + +FactorGraph::FactorGraph(const FactorGraph &other) + : FactorGraph(other.capacity) { + size = other.size; + + // fast copy: 3 sec for a 270M-factor graph + parallel_copy(other.weights, weights, size.num_weights); + parallel_copy(other.factors, factors, size.num_factors); + parallel_copy(other.vifs, vifs, size.num_edges); + parallel_copy(other.variables, variables, size.num_variables); + parallel_copy(other.factor_index, factor_index, size.num_edges); + parallel_copy(other.values, values, size.num_values); + + // slow copy: 18 sec for a 270M-factor graph + // COPY_ARRAY_UNIQUE_PTR_MEMBER(variables, size.num_variables); + // COPY_ARRAY_UNIQUE_PTR_MEMBER(factors, size.num_factors); + // COPY_ARRAY_UNIQUE_PTR_MEMBER(factor_index, size.num_edges); + // COPY_ARRAY_UNIQUE_PTR_MEMBER(vifs, size.num_edges); + // COPY_ARRAY_UNIQUE_PTR_MEMBER(weights, size.num_weights); + // COPY_ARRAY_UNIQUE_PTR_MEMBER(values, size.num_values); +} + +// Inline by defined here; accessible only from current file. +inline void FactorGraph::sgd_on_factor(size_t factor_id, double stepsize, + size_t vid, size_t evidence_value, + InferenceResult &infrs) { + const Factor &factor = factors[factor_id]; + if (infrs.weights_isfixed[factor.weight_id]) { + return; + } + // stochastic gradient ascent + // decrement weight with stepsize * gradient of weight + // gradient of weight = E[f] - E[f|D], where D is evidence variables, + // f is the factor function, E[] is expectation. Expectation is + // calculated using a sample of the variable. + double pot_evid = factor.potential(vifs.get(), infrs.assignments_evid.get(), + vid, evidence_value); + double pot_free = factor.potential(vifs.get(), infrs.assignments_free.get()); + double gradient = pot_free - pot_evid; + infrs.update_weight(factor.weight_id, stepsize, gradient); +} + +void FactorGraph::sgd_on_variable(const Variable &variable, + InferenceResult &infrs, double stepsize, + bool is_noise_aware) { + if (variable.is_boolean()) { + // boolean: for each factor {learn} + // NOTE: boolean vars do not support truthiness / noise-aware learning + const VariableToFactor &vv = values[variable.var_val_base]; + for (size_t j = 0; j < vv.factor_index_length; ++j) { + size_t factor_id = factor_index[vv.factor_index_base + j]; + sgd_on_factor(factor_id, stepsize, variable.id, variable.assignment_dense, + infrs); + } + } else { + // categorical: for each evidence value { for each factor {learn} } + size_t proposal = infrs.assignments_free[variable.id]; + for (size_t val = 0; val < variable.internal_cardinality(); ++val) { + // skip non-evidence values + if (!is_noise_aware && val != variable.assignment_dense) continue; + + const VariableToFactor &ev = values[variable.var_val_base + val]; + if (is_noise_aware && is_linear_zero(ev.truthiness)) continue; + + double truthiness = is_noise_aware ? ev.truthiness : 1; + + // run SGD on all factors "activated" by this evidence value + for (size_t j = 0; j < ev.factor_index_length; ++j) { + size_t factor_id = factor_index[ev.factor_index_base + j]; + sgd_on_factor(factor_id, stepsize * truthiness, variable.id, val, + infrs); + } + + // run SGD on all factors "activated" by proposal value + // NOTE: Current ddlog inference rule syntax implies that this list + // of factors would overlap the above list only if the factor + // connects tuple [var=val] and tuple [var=proposal], which sensible + // ddlog inference rules would never generate. + // Hence we assume that there is no overlap. + // This may change in the future as we introduce fancier factor + // types. + + // skip if we have just processed the same list of factors + // NOTE: not skipping before the first loop because ... assignments_evid! + if (val == proposal) continue; + + const VariableToFactor &pv = values[variable.var_val_base + proposal]; + for (size_t j = 0; j < pv.factor_index_length; ++j) { + size_t factor_id = factor_index[pv.factor_index_base + j]; + sgd_on_factor(factor_id, stepsize * truthiness, variable.id, val, + infrs); + } + } // end for + } +} + +} // namespace dd diff --git a/inference/dimmwitted/src/factor_graph.h b/inference/dimmwitted/src/factor_graph.h new file mode 100644 index 000000000..43dc55b7f --- /dev/null +++ b/inference/dimmwitted/src/factor_graph.h @@ -0,0 +1,156 @@ +#ifndef DIMMWITTED_FACTOR_GRAPH_H_ +#define DIMMWITTED_FACTOR_GRAPH_H_ + +#include "cmd_parser.h" +#include "common.h" + +#include "factor.h" +#include "inference_result.h" +#include "variable.h" +#include "weight.h" + +#include + +namespace dd { + +/** Meta data describing the dimension of a factor graph */ +class FactorGraphDescriptor { + public: + FactorGraphDescriptor(); + + FactorGraphDescriptor(size_t num_variables, size_t num_factors, + size_t num_weights, size_t num_edges); + + /** number of all variables */ + size_t num_variables; + + /** number of factors */ + size_t num_factors; + /** number of edges */ + size_t num_edges; + + /** number of all weights */ + size_t num_weights; + + /** number of all values; populated in FactorGraph.construct_index */ + size_t num_values; + + /** number of evidence variables */ + size_t num_variables_evidence; + /** number of query variables */ + size_t num_variables_query; +}; + +std::ostream& operator<<(std::ostream& stream, + const FactorGraphDescriptor& size); + +/** + * Class for a factor graph + */ +class FactorGraph { + public: + /** Capacity to allow multi-step loading */ + FactorGraphDescriptor capacity; + /** Actual count of things */ + FactorGraphDescriptor size; + + // distinct weights + std::unique_ptr weights; + + // factors and each factor's variables (with "equal_to" values) + std::unique_ptr factors; + std::unique_ptr vifs; + + // variables, each variable's values, and index into factor IDs + // factor_index follows sort order of adjacent + // |factor_index| may be smaller than |edges| because we deduplicate (see + // construct_index) + std::unique_ptr variables; + std::unique_ptr factor_index; + std::unique_ptr values; + + void load_weights(const std::vector& filenames); + void load_variables(const std::vector& filenames); + void load_factors(const std::vector& filenames); + void load_domains(const std::vector& filenames); + + // count data structures to ensure consistency with declared size + void safety_check(); + // construct "values" and "factor_index" for var-to-factor lookups + void construct_index(); + + void construct_index_part(size_t v_start, size_t v_end, size_t val_base, + size_t fac_base); + + inline size_t get_var_value_at(const Variable& var, size_t idx) const { + return values[var.var_val_base + idx].value; + } + + inline const FactorToVariable& get_factor_vif_at(const Factor& factor, + size_t idx) const { + return vifs[factor.vif_base + idx]; + } + + /** + * Constructs a new factor graph with given number number of variables, + * factors, weights, and edges + */ + FactorGraph(const FactorGraphDescriptor& capacity); + + // copy constructor + FactorGraph(const FactorGraph& other); + + ~FactorGraph(); + + /** + * Given a variable, updates the weights associated with the factors that + * connect to the variable. + * Used in learning phase, after sampling one variable, + * update corresponding weights (stochastic gradient descent). + */ + void sgd_on_variable(const Variable& variable, InferenceResult& infrs, + double stepsize, bool is_noise_aware); + + // perform SGD step for weight learning on one factor + inline void sgd_on_factor(size_t factor_id, double stepsize, size_t vid, + size_t evidence_value, InferenceResult& infrs); + + /** + * Returns log-linear weighted potential of the all factors for the given + * variable using the propsal value. + */ + inline double potential(const Variable& variable, const size_t proposal, + const size_t assignments[], + const double weight_values[]); +}; + +inline double FactorGraph::potential(const Variable& variable, size_t proposal, + const size_t assignments[], + const double weight_values[]) { + double pot = 0.0; + + VariableToFactor* const var_value_base = &values[variable.var_val_base]; + size_t offset = variable.var_value_offset(proposal); + const VariableToFactor& var_value = *(var_value_base + offset); + + // all adjacent factors in one chunk in factor_index + for (size_t i = 0; i < var_value.factor_index_length; ++i) { + size_t factor_id = factor_index[var_value.factor_index_base + i]; + const Factor& factor = factors[factor_id]; + double weight = weight_values[factor.weight_id]; + pot += weight * + factor.potential(vifs.get(), assignments, variable.id, proposal); + } + return pot; +} + +inline std::ostream& operator<<(std::ostream& out, FactorGraph const& fg) { + out << "FactorGraph("; + out << fg.size; + out << ")"; + return out; +} + +} // namespace dd + +#endif // DIMMWITTED_FACTOR_GRAPH_H_ diff --git a/inference/dimmwitted/src/gibbs_sampler.cc b/inference/dimmwitted/src/gibbs_sampler.cc new file mode 100644 index 000000000..ce3094572 --- /dev/null +++ b/inference/dimmwitted/src/gibbs_sampler.cc @@ -0,0 +1,77 @@ +#include "gibbs_sampler.h" + +namespace dd { + +GibbsSampler::GibbsSampler(std::unique_ptr _pfg, + const Weight weights[], const NumaNodes &numa_nodes, + size_t nthread, size_t nodeid, const CmdParser &opts) + : pfg(std::move(_pfg)), + pinfrs(new InferenceResult(*pfg, weights, opts)), + numa_nodes_(numa_nodes), + fg(*pfg), + infrs(*pinfrs), + nthread(nthread), + nodeid(nodeid) { + assert(nthread > 0); + for (size_t i = 0; i < nthread; ++i) + workers.push_back(GibbsSamplerThread(fg, infrs, i, nthread, opts)); +} + +void GibbsSampler::sample(size_t i_epoch) { + numa_nodes_.bind(); + for (auto &worker : workers) { + threads.push_back(std::thread([&worker]() { worker.sample(); })); + } +} + +void GibbsSampler::sample_sgd(double stepsize) { + numa_nodes_.bind(); + for (auto &worker : workers) { + threads.push_back( + std::thread([&worker, stepsize]() { worker.sample_sgd(stepsize); })); + } +} + +void GibbsSampler::wait() { + for (auto &t : threads) t.join(); + threads.clear(); +} + +GibbsSamplerThread::GibbsSamplerThread(FactorGraph &fg, InferenceResult &infrs, + size_t ith_shard, size_t n_shards, + const CmdParser &opts) + : varlen_potential_buffer_(0), + fg(fg), + infrs(infrs), + sample_evidence(opts.should_sample_evidence), + learn_non_evidence(opts.should_learn_non_evidence), + is_noise_aware(opts.is_noise_aware) { + set_random_seed(rand(), rand(), rand()); + size_t nvar = fg.size.num_variables; + // calculates the start and end id in this partition + start = ((size_t)(nvar / n_shards) + 1) * ith_shard; + end = ((size_t)(nvar / n_shards) + 1) * (ith_shard + 1); + end = end > nvar ? nvar : end; +} + +void GibbsSamplerThread::set_random_seed(unsigned short seed0, + unsigned short seed1, + unsigned short seed2) { + p_rand_seed[0] = seed0; + p_rand_seed[1] = seed1; + p_rand_seed[2] = seed2; +} + +void GibbsSamplerThread::sample() { + for (size_t vid = start; vid < end; ++vid) { + sample_single_variable(vid); + } +} + +void GibbsSamplerThread::sample_sgd(double stepsize) { + for (size_t vid = start; vid < end; ++vid) { + sample_sgd_single_variable(vid, stepsize); + } +} + +} // namespace dd diff --git a/inference/dimmwitted/src/gibbs_sampler.h b/inference/dimmwitted/src/gibbs_sampler.h new file mode 100644 index 000000000..393188294 --- /dev/null +++ b/inference/dimmwitted/src/gibbs_sampler.h @@ -0,0 +1,258 @@ +#ifndef DIMMWITTED_GIBBS_SAMPLER_H_ +#define DIMMWITTED_GIBBS_SAMPLER_H_ + +#include "common.h" +#include "factor_graph.h" +#include "numa_nodes.h" +#include "timer.h" +#include +#include + +namespace dd { + +class GibbsSamplerThread; + +/** + * Class for a single NUMA node sampler + */ +class GibbsSampler { + private: + std::unique_ptr pfg; + std::unique_ptr pinfrs; + NumaNodes numa_nodes_; + std::vector workers; + std::vector threads; + + public: + FactorGraph &fg; + InferenceResult &infrs; + + // number of threads + size_t nthread; + // node id + size_t nodeid; + + /** + * Constructs a GibbsSampler given factor graph, number of threads, and + * node id. + */ + GibbsSampler(std::unique_ptr pfg, const Weight weights[], + const NumaNodes &numa_nodes, size_t nthread, size_t nodeid, + const CmdParser &opts); + + /** + * Performs sample + */ + void sample(size_t i_epoch); + + /** + * Performs SGD + */ + void sample_sgd(double stepsize); + + /** + * Waits for sample worker to finish + */ + void wait(); +}; + +/** + * Class for single thread sampler + */ +class GibbsSamplerThread { + private: + // shard and variable id range assigned to this one + size_t start, end; + + // RNG seed + unsigned short p_rand_seed[3]; + + // potential for each proposals for categorical + std::vector varlen_potential_buffer_; + + // references and cached flags + FactorGraph &fg; + InferenceResult &infrs; + bool sample_evidence; + bool learn_non_evidence; + bool is_noise_aware; + + public: + /** + * Constructs a GibbsSamplerThread with given factor graph + */ + GibbsSamplerThread(FactorGraph &fg, InferenceResult &infrs, size_t i_sharding, + size_t n_sharding, const CmdParser &opts); + + /** + * Samples variables. The variables are divided into n_sharding equal + * partitions + * based on their ids. This function samples variables in the i_sharding-th + * partition. + */ + void sample(); + + /** + * Performs SGD with by sampling variables. The variables are divided into + * n_sharding equal partitions based on their ids. This function samples + * variables + * in the i_sharding-th partition. + */ + void sample_sgd(double stepsize); + + /** + * Performs SGD by sampling a single variable with id vid + */ + inline void sample_sgd_single_variable(size_t vid, double stepsize); + + /** + * Samples a single variable with id vid + */ + inline void sample_single_variable(size_t vid); + + // sample an "evidence" variable (parallel Gibbs conditioned on evidence) + inline size_t sample_evid(const Variable &variable); + + // sample a single variable (regular Gibbs) + inline size_t draw_sample(const Variable &variable, + const size_t assignments[], + const double weight_values[]); + + /** + * Resets RNG seed to given values + */ + void set_random_seed(unsigned short s0, unsigned short s1, unsigned short s2); +}; + +inline void GibbsSamplerThread::sample_sgd_single_variable(size_t vid, + double stepsize) { + // stochastic gradient ascent + // gradient of weight = E[f|D] - E[f], where D is evidence variables, + // f is the factor function, E[] is expectation. Expectation is calculated + // using a sample of the variable. + + const Variable &variable = fg.variables[vid]; + + // pick a value for the regular Gibbs chain + size_t proposal = draw_sample(variable, infrs.assignments_free.get(), + infrs.weight_values.get()); + infrs.assignments_free[variable.id] = proposal; + + // pick a value for the (parallel) evid Gibbs chain + infrs.assignments_evid[variable.id] = sample_evid(variable); + + if (!learn_non_evidence && ((!is_noise_aware && !variable.is_evid) || + (is_noise_aware && !variable.has_truthiness()))) + return; + + fg.sgd_on_variable(variable, infrs, stepsize, is_noise_aware); +} + +inline void GibbsSamplerThread::sample_single_variable(size_t vid) { + // this function uses the same sampling technique as in + // sample_sgd_single_variable + + const Variable &variable = fg.variables[vid]; + + if (!variable.is_evid || sample_evidence) { + size_t proposal = draw_sample(variable, infrs.assignments_evid.get(), + infrs.weight_values.get()); + infrs.assignments_evid[variable.id] = proposal; + + // bookkeep aggregates for computing marginals + ++infrs.agg_nsamples[variable.id]; + if (!variable.is_boolean() || proposal == 1) { + ++infrs.sample_tallies[variable.var_val_base + + variable.var_value_offset(proposal)]; + } + } +} + +inline size_t GibbsSamplerThread::sample_evid(const Variable &variable) { + if (!is_noise_aware && variable.is_evid) { + // direct assignment of hard "evidence" + return variable.assignment_dense; + } else if (is_noise_aware && variable.has_truthiness()) { + // truthiness-weighted sample of soft "evidence" values + double r = erand48(p_rand_seed); + double sum = 0; + for (size_t i = 0; i < variable.cardinality; ++i) { + double truthiness = fg.values[variable.var_val_base + i].truthiness; + sum += truthiness; + if (sum >= r) return i; + } + return 0; + } else { + // Gibbs sample on the assignments_evid chain + return draw_sample(variable, infrs.assignments_evid.get(), + infrs.weight_values.get()); + } +} + +inline size_t GibbsSamplerThread::draw_sample(const Variable &variable, + const size_t assignments[], + const double weight_values[]) { + size_t proposal = 0; + + switch (variable.domain_type) { + case DTYPE_BOOLEAN: { + double potential_pos; + double potential_neg; + potential_pos = fg.potential(variable, 1, assignments, weight_values); + potential_neg = fg.potential(variable, 0, assignments, weight_values); + + double r = erand48(p_rand_seed); + // sample the variable + // flip a coin with probability + // (exp(potential_pos) + exp(potential_neg)) / exp(potential_neg) + // = exp(potential_pos - potential_neg) + 1 + if (r * (1.0 + exp(potential_neg - potential_pos)) < 1.0) { + proposal = 1; + } else { + proposal = 0; + } + break; + } + + case DTYPE_CATEGORICAL: { + varlen_potential_buffer_.reserve(variable.cardinality); + double sum = -100000.0; + proposal = Variable::INVALID_VALUE; +// calculate potential for each proposal given a way to iterate the domain +#define COMPUTE_PROPOSAL(EACH_DOMAIN_VALUE, DOMAIN_VALUE, DOMAIN_INDEX) \ + do { \ + for \ + EACH_DOMAIN_VALUE { \ + varlen_potential_buffer_[DOMAIN_INDEX] = \ + fg.potential(variable, DOMAIN_VALUE, assignments, weight_values); \ + sum = logadd(sum, varlen_potential_buffer_[DOMAIN_INDEX]); \ + } \ + double r = erand48(p_rand_seed); \ + for \ + EACH_DOMAIN_VALUE { \ + r -= exp(varlen_potential_buffer_[DOMAIN_INDEX] - sum); \ + if (r <= 0) { \ + proposal = DOMAIN_VALUE; \ + break; \ + } \ + } \ + } while (0) + // All sparse values have been converted into dense values in + // FactorGraph.load_domains + COMPUTE_PROPOSAL((size_t i = 0; i < variable.cardinality; ++i), i, i); + + assert(proposal != Variable::INVALID_VALUE); + break; + } + + default: + // unsupported variable types + std::abort(); + } + + return proposal; +} + +} // namespace dd + +#endif // DIMMWITTED_GIBBS_SAMPLER_H_ diff --git a/inference/dimmwitted/src/inference_result.cc b/inference/dimmwitted/src/inference_result.cc new file mode 100644 index 000000000..603a60a83 --- /dev/null +++ b/inference/dimmwitted/src/inference_result.cc @@ -0,0 +1,245 @@ +#include "inference_result.h" +#include "factor_graph.h" +#include +#include +#include +#include + +namespace dd { + +InferenceResult::InferenceResult(const FactorGraph &fg, const CmdParser &opts) + : fg(fg), + opts(opts), + nvars(fg.size.num_variables), + nweights(fg.size.num_weights), + ntallies(fg.size.num_values), + sample_tallies(new size_t[fg.size.num_values]), + agg_nsamples(new size_t[nvars]), + assignments_free(new size_t[nvars]), + assignments_evid(new size_t[nvars]), + weight_values(new double[nweights]), + weight_grads(new float[nweights]), + weights_isfixed(new bool[nweights]) {} + +InferenceResult::InferenceResult(const FactorGraph &fg, const Weight weights[], + const CmdParser &opts) + : InferenceResult(fg, opts) { + for (size_t t = 0; t < nweights; ++t) { + const Weight &weight = weights[t]; + weight_values[weight.id] = weight.weight; + weight_grads[weight.id] = 0; + weights_isfixed[weight.id] = weight.isfixed; + } + + for (size_t t = 0; t < nvars; ++t) { + const Variable &variable = fg.variables[t]; + assignments_free[variable.id] = + variable.is_evid ? variable.assignment_dense : 0; + assignments_evid[variable.id] = assignments_free[variable.id]; + } + + clear_variabletally(); +} + +InferenceResult::InferenceResult(const InferenceResult &other) + : InferenceResult(other.fg, other.opts) { + COPY_ARRAY_UNIQUE_PTR_MEMBER(assignments_evid, nvars); + COPY_ARRAY_UNIQUE_PTR_MEMBER(agg_nsamples, nvars); + + COPY_ARRAY_UNIQUE_PTR_MEMBER(weight_values, nweights); + COPY_ARRAY_UNIQUE_PTR_MEMBER(weight_grads, nweights); + COPY_ARRAY_UNIQUE_PTR_MEMBER(weights_isfixed, nweights); + + ntallies = other.ntallies; + COPY_ARRAY_UNIQUE_PTR_MEMBER(sample_tallies, ntallies); +} + +void InferenceResult::merge_gradients_from(const InferenceResult &other) { + assert(nweights == other.nweights); + for (size_t j = 0; j < nweights; ++j) { + weight_grads[j] += other.weight_grads[j]; + } +} + +void InferenceResult::reset_gradients() { + std::memset(weight_grads.get(), 0, nweights * sizeof(float)); +} + +void InferenceResult::merge_weights_from(const InferenceResult &other) { + assert(nweights == other.nweights); + for (size_t j = 0; j < nweights; ++j) { + weight_values[j] += other.weight_values[j]; + } +} + +void InferenceResult::average_weights(size_t count) { + for (size_t j = 0; j < nweights; ++j) { + weight_values[j] /= count; + } +} + +void InferenceResult::copy_weights_to(InferenceResult &other) const { + assert(nweights == other.nweights); + for (size_t j = 0; j < nweights; ++j) { + if (!weights_isfixed[j]) other.weight_values[j] = weight_values[j]; + } +} + +void InferenceResult::show_weights_snippet(std::ostream &output) const { + output << "LEARNING SNIPPETS (QUERY WEIGHTS):" << std::endl; + size_t ct = 0; + for (size_t j = 0; j < nweights; ++j) { + ++ct; + output << " " << j << " " << weight_values[j] << std::endl; + if (ct % 10 == 0) { + break; + } + } + output << " ..." << std::endl; +} + +void InferenceResult::dump_weights_in_text(std::ostream &text_output) const { + for (size_t j = 0; j < nweights; ++j) { + text_output << j << " " << weight_values[j] << std::endl; + } +} + +void InferenceResult::clear_variabletally() { + for (size_t i = 0; i < nvars; ++i) { + agg_nsamples[i] = 0; + } + for (size_t i = 0; i < ntallies; ++i) { + sample_tallies[i] = 0; + } +} + +void InferenceResult::aggregate_marginals_from(const InferenceResult &other) { + // TODO maybe make this an operator+ after separating marginals from weights + assert(nvars == other.nvars); + assert(ntallies == other.ntallies); + for (size_t j = 0; j < other.nvars; ++j) { + const Variable &variable = other.fg.variables[j]; + agg_nsamples[variable.id] += other.agg_nsamples[variable.id]; + } + for (size_t j = 0; j < other.ntallies; ++j) { + sample_tallies[j] += other.sample_tallies[j]; + } +} + +void InferenceResult::show_marginal_snippet(std::ostream &output) const { + output << "INFERENCE SNIPPETS (QUERY VARIABLES):" << std::endl; + size_t ct = 0; + for (size_t j = 0; j < fg.size.num_variables; ++j) { + const Variable &variable = fg.variables[j]; + if (!variable.is_evid || opts.should_sample_evidence) { + ++ct; + output << " " << variable.id + << " NSAMPLE=" << agg_nsamples[variable.id] << std::endl; + + const auto &print_snippet = [this, &output, &variable]( + size_t domain_value, size_t domain_index) { + output << " @ " << domain_value << " -> EXP=" + << 1.0 * sample_tallies[variable.var_val_base + domain_index] / + agg_nsamples[variable.id] + << std::endl; + }; + + switch (variable.domain_type) { + case DTYPE_BOOLEAN: + print_snippet(1, 0); + break; + + case DTYPE_CATEGORICAL: { + for (size_t j = 0; j < variable.cardinality; ++j) { + print_snippet(fg.get_var_value_at(variable, j), j); + } + break; + } + + default: + std::abort(); + } + + if (ct % 10 == 0) { + break; + } + } + } + output << " ..." << std::endl; +} + +void InferenceResult::show_marginal_histogram(std::ostream &output, + const size_t bins) const { + // show a histogram of inference results + output << "INFERENCE CALIBRATION (QUERY BINS):" << std::endl; + std::vector abc(bins + 1, 0); + + size_t bad = 0; + for (size_t j = 0; j < nvars; ++j) { + const Variable &variable = fg.variables[j]; + if (!opts.should_sample_evidence && variable.is_evid) { + continue; + } + for (size_t k = 0; k < variable.internal_cardinality(); ++k) { + size_t bin = (size_t)((double)sample_tallies[variable.var_val_base + k] / + agg_nsamples[variable.id] * bins); + if (bin <= bins) { + ++abc[bin]; + } else { + ++bad; + } + } + } + abc[bins - 1] += abc[bins]; + + const std::ios::fmtflags flags(output.flags()); // save ostream settings + output << std::fixed; // specify number of decimals (rather than sig figs) + + // save precision and set new one + const size_t prec = output.precision(std::max((int)ceil(log10(bins)), 1)); + + for (size_t i = 0; i < bins; ++i) { + output << "PROB BIN " << (float)i / bins << "~" << (float)(i + 1) / bins + << " --> # " << abc[i] << std::endl; + } + + // restore ostream settings + output.flags(flags); + output << std::setprecision(prec); +} + +void InferenceResult::dump_marginals_in_text(std::ostream &text_output) const { + for (size_t j = 0; j < nvars; ++j) { + const Variable &variable = fg.variables[j]; + if (variable.is_evid && !opts.should_sample_evidence) { + continue; + } + + const auto &print_result = [this, &text_output, &variable]( + size_t domain_value, size_t domain_index) { + text_output << variable.id << " " << domain_value << " " + << 1.0 * + sample_tallies[variable.var_val_base + domain_index] / + agg_nsamples[variable.id] + << std::endl; + }; + + switch (variable.domain_type) { + case DTYPE_BOOLEAN: + print_result(1, 0); + break; + + case DTYPE_CATEGORICAL: { + for (size_t j = 0; j < variable.cardinality; ++j) { + print_result(fg.get_var_value_at(variable, j), j); + } + break; + } + + default: + std::abort(); + } + } +} + +} // namespace dd diff --git a/inference/dimmwitted/src/inference_result.h b/inference/dimmwitted/src/inference_result.h new file mode 100644 index 000000000..afe47c40e --- /dev/null +++ b/inference/dimmwitted/src/inference_result.h @@ -0,0 +1,93 @@ +#ifndef DIMMWITTED_INFERENCE_RESULT_H_ +#define DIMMWITTED_INFERENCE_RESULT_H_ + +#include "cmd_parser.h" +#include "variable.h" +#include "weight.h" +#include + +namespace dd { + +class FactorGraph; + +/** + * Encapsulates inference result statistics + */ +class InferenceResult { + private: + const FactorGraph &fg; + const CmdParser &opts; + + public: + size_t nvars; // number of variables + size_t nweights; // number of weights + size_t ntallies; // number of tallies + + // tallies for each var value (see Variable.var_val_base) + std::unique_ptr sample_tallies; + + // array of number of samples for each variable + std::unique_ptr agg_nsamples; + + // vanilla / traditional Gibbs chain + std::unique_ptr assignments_free; + + // separate Gibbs chain CONDITIONED ON EVIDENCE + std::unique_ptr assignments_evid; + + // array of weight values + std::unique_ptr weight_values; + // array of weight gradients, used in distributed learning + std::unique_ptr weight_grads; + // array of whether weight is fixed + std::unique_ptr weights_isfixed; + + InferenceResult(const FactorGraph &fg, const Weight weights[], + const CmdParser &opts); + + // copy constructor + InferenceResult(const InferenceResult &other); + + void merge_gradients_from(const InferenceResult &other); + void reset_gradients(); + void merge_weights_from(const InferenceResult &other); + void average_weights(size_t count); + void copy_weights_to(InferenceResult &other) const; + void show_weights_snippet(std::ostream &output) const; + void dump_weights_in_text(std::ostream &text_output) const; + + void clear_variabletally(); + void aggregate_marginals_from(const InferenceResult &other); + void show_marginal_snippet(std::ostream &output) const; + void show_marginal_histogram(std::ostream &output, + const size_t bins = 10) const; + void dump_marginals_in_text(std::ostream &text_output) const; + + inline void update_weight(size_t wid, double stepsize, double gradient) { + double diff = stepsize * gradient; + weight_grads[wid] += diff; + double weight = weight_values[wid]; + switch (opts.regularization) { + case REG_L2: { + // bounded approx of 1 - opts.reg_param * stepsize + weight *= (1.0 / (1.0 + opts.reg_param * stepsize)); + break; + } + case REG_L1: { + weight += opts.reg_param * (weight < 0); + break; + } + default: + std::abort(); + } + weight -= diff; + weight_values[wid] = weight; + } + + private: + InferenceResult(const FactorGraph &fg, const CmdParser &opts); +}; + +} // namespace dd + +#endif // DIMMWITTED_INFERENCE_RESULT_H_ diff --git a/inference/dimmwitted/src/main.cc b/inference/dimmwitted/src/main.cc new file mode 100644 index 000000000..67fcf71dc --- /dev/null +++ b/inference/dimmwitted/src/main.cc @@ -0,0 +1,4 @@ +#include "dimmwitted.h" + +// a simple main that delegates to dw() to ease test with gtest +int main(int argc, char *argv[]) { return dd::dw(argc, argv); } diff --git a/inference/dimmwitted/src/numa_nodes.cc b/inference/dimmwitted/src/numa_nodes.cc new file mode 100644 index 000000000..9ae4c7408 --- /dev/null +++ b/inference/dimmwitted/src/numa_nodes.cc @@ -0,0 +1,95 @@ +#include "numa_nodes.h" + +#include +#include + +#ifdef __linux__ + +// libnuma on Linux +#include +#include + +#else // __linux__ + +// portability shims for platforms without libnuma support, e.g., Mac +#define numa_available() -1 +#define numa_num_configured_nodes() 1 +#define numa_bind(bmp) +#define numa_parse_nodestring(str) nullptr +#define numa_bitmask_alloc(n) nullptr +#define numa_bitmask_free(bmp) + +void numa_warn(int number, char* where, ...) { + // ignore +} + +void numa_error(char* where) { + // ignore +} + +#endif // __linux__ + +namespace dd { + +NumaNodes::NumaNodes(const std::string& nodestring) + : nodestring_(nodestring), + numa_nodemask_(nullptr), + numa_available_(numa_available() != -1 && + numa_num_configured_nodes() > 0) {} + +NumaNodes::~NumaNodes() { + if (numa_nodemask_) numa_bitmask_free(numa_nodemask_); +} + +NumaNodes::NumaNodes(const NumaNodes& other) : NumaNodes(other.nodestring_) {} +NumaNodes& NumaNodes::operator=(NumaNodes other) { + swap(other); + return *this; +} +void NumaNodes::swap(NumaNodes& other) { + std::swap(nodestring_, other.nodestring_); + std::swap(numa_nodemask_, other.numa_nodemask_); +} + +struct bitmask* NumaNodes::numa_nodemask() { + if (!numa_nodemask_ && numa_available_) + numa_nodemask_ = numa_parse_nodestring(&nodestring_[0]); + return numa_nodemask_; +} +void NumaNodes::bind() { + if (numa_available_) numa_bind(numa_nodemask()); +} +void NumaNodes::unbind() { + if (numa_available_) numa_bind(numa_nodemask()); +} + +size_t NumaNodes::num_configured() { + return std::max(numa_available() != -1 ? numa_num_configured_nodes() : 1, 1); +} + +NumaNodes NumaNodes::partition(size_t ith_part, size_t num_parts) { + size_t num_numa_nodes = num_configured(); + assert(num_numa_nodes % num_parts == 0); + + size_t num_numa_nodes_per_part = num_numa_nodes / num_parts; + size_t begin = ith_part * num_numa_nodes_per_part; + size_t end = begin + num_numa_nodes_per_part - 1; + + std::ostringstream nodestringstream; + nodestringstream << begin << "-" << end; + return NumaNodes(nodestringstream.str()); +} + +std::vector NumaNodes::partition(size_t num_parts) { + std::vector parts; + for (size_t i = 0; i < num_parts; ++i) { + parts.push_back(partition(i, num_parts)); + } + return parts; +} + +std::ostream& operator<<(std::ostream& output, const NumaNodes& numa_nodes) { + return output << numa_nodes.nodestring_; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/numa_nodes.h b/inference/dimmwitted/src/numa_nodes.h new file mode 100644 index 000000000..dcf7a20d5 --- /dev/null +++ b/inference/dimmwitted/src/numa_nodes.h @@ -0,0 +1,81 @@ +#ifndef DIMMWITTED_NUMA_NODES_H_ +#define DIMMWITTED_NUMA_NODES_H_ + +#include +#include +#include + +struct bitmask; // just need a forward declaration since we only point to it + +namespace dd { + +/** + * A class for easily setting up NUMA/CPU constraints. + */ +class NumaNodes { + private: + /** + * A libnuma nodestring describing which NUMA nodes this instance covers. + * See: man numa(3) + */ + std::string nodestring_; + friend std::ostream& operator<<(std::ostream&, const NumaNodes&); + + /** + * The bitmask that corresponds to the NUMA nodestring. + */ + struct bitmask* numa_nodemask_; + struct bitmask* numa_nodemask(); + + /** + * Whether libnuma API is available is checked first and this flag is set. + * Other members will try to behave reasonably if this flag is false. + */ + bool numa_available_; + + public: + NumaNodes(const std::string& nodestring); + ~NumaNodes(); + + NumaNodes(const NumaNodes& other); + NumaNodes& operator=(NumaNodes other); + void swap(NumaNodes& other); + + /** + * Returns the nodestring used by libnuma that represents the NUMA nodes this + * instance covers. + */ + std::string nodestring() const; + + /** + * Binds memory allocation and CPU affinity of the calling task to the NUMA + * nodes of this instance. + */ + void bind(); + /** + * Unbinds any previously bound memory allocation and CPU affinity of the + * calling task. + */ + void unbind(); + + /** + * Returns how many NUMA nodes are configured on the system. + */ + static size_t num_configured(); + /** + * Returns the i-th partition when all NUMA nodes are grouped into a given + * number of partitions. + */ + static NumaNodes partition(size_t ith_part, size_t num_parts); + /** + * Returns a vector of instances that cover all NUMA nodes given a number of + * partitions. + */ + static std::vector partition(size_t num_parts); +}; + +std::ostream& operator<<(std::ostream&, const NumaNodes&); + +} // namespace dd + +#endif // DIMMWITTED_NUMA_NODES_H_ diff --git a/inference/dimmwitted/src/sinco/AUTHORS b/inference/dimmwitted/src/sinco/AUTHORS new file mode 100644 index 000000000..7f5201903 --- /dev/null +++ b/inference/dimmwitted/src/sinco/AUTHORS @@ -0,0 +1,6 @@ +The author: Katya Scheinberg, +address: IBM T.J. Watson Research Center, + 1101 Kitchawan Rd., Yorktown Heights, + 10598, NY +email: katyascheinberg@gmail.com + \ No newline at end of file diff --git a/inference/dimmwitted/src/sinco/COPYING b/inference/dimmwitted/src/sinco/COPYING new file mode 100644 index 000000000..c9990a7ea --- /dev/null +++ b/inference/dimmwitted/src/sinco/COPYING @@ -0,0 +1,213 @@ +Common Public License Version 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are not +derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and such +derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed +Patents to make, use, sell, offer to sell, import and otherwise transfer the +Contribution of such Contributor, if any, in source code and object code form. +This patent license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, such +addition of the Contribution causes such combination to be covered by the +Licensed Patents. The patent license shall not apply to any other combinations +which include the Contribution. No hardware per se is licensed hereunder. + + c) Recipient understands that although each Contributor grants the licenses +to its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other intellectual +property rights of any other entity. Each Contributor disclaims any liability to +Recipient for claims brought by any other entity based on infringement of +intellectual property rights or otherwise. As a condition to exercising the +rights and licenses granted hereunder, each Recipient hereby assumes sole +responsibility to secure any other intellectual property rights needed, if any. +For example, if a third party patent license is required to allow Recipient to +distribute the Program, it is Recipient's responsibility to acquire that license +before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license set +forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title and +non-infringement, and implied warranties or conditions of merchantability and +fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are offered +by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on or +through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement, including but not limited to the risks and costs of +program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with respect to +a patent applicable to software (including a cross-claim or counterclaim in a +lawsuit), then any patent licenses granted by that Contributor to such Recipient +under this Agreement shall terminate as of the date such litigation is filed. In +addition, if Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +IBM is the initial Agreement Steward. IBM may assign the responsibility to serve +as the Agreement Steward to a suitable separate entity. Each new version of the +Agreement will be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the Agreement +under which it was received. In addition, after a new version of the Agreement +is published, Contributor may elect to distribute the Program (including its +Contributions) under the new version. Except as expressly stated in Sections +2(a) and 2(b) above, Recipient receives no rights or licenses to the +intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/inference/dimmwitted/src/sinco/INSTALL b/inference/dimmwitted/src/sinco/INSTALL new file mode 100644 index 000000000..2596cc13c --- /dev/null +++ b/inference/dimmwitted/src/sinco/INSTALL @@ -0,0 +1,3 @@ +SINCO software comex with Matlab mex interface. To compile +run compile_sinco comand (file provided) in Matlab and then +use the appropriate calling line in your Matlab code. \ No newline at end of file diff --git a/inference/dimmwitted/src/sinco/a.out b/inference/dimmwitted/src/sinco/a.out new file mode 100755 index 000000000..cb84057d9 Binary files /dev/null and b/inference/dimmwitted/src/sinco/a.out differ diff --git a/inference/dimmwitted/src/sinco/compile_sinco.m b/inference/dimmwitted/src/sinco/compile_sinco.m new file mode 100644 index 000000000..963ad38e7 --- /dev/null +++ b/inference/dimmwitted/src/sinco/compile_sinco.m @@ -0,0 +1,27 @@ +% Script to compile mex files for SINCO using MATLAB interface +% +% +% Caution!!!! Only tested for Linux right now. + +% Test for mex extension to determine matlab version and platform +switch mexext + case {'mexw32'} % Win32 MATLAB after 7.1 + files='sinco.cpp sinco_mex.cpp'; +% libs=[matlabroot,'\extern\lib\win32\microsoft\libmwlapack.lib']; + switches='-v -O -DWIN32'; + eval(['mex ',switches,' ',files,' ','''']); + case {'dll'} % Win32 MATLAB before 7.1 + files='sinco.cpp sinco_mex.cpp'; +% libs=[matlabroot,'\extern\lib\win32\microsoft\msvc60\libmwlapack.lib']; + switches='-v -O -DWIN32'; + eval(['mex ',switches,' ',files,' ','''']); + case {'mexmac'}% Macintosh using the VecLib framework + files='cinco.cpp sinco_mex.cpp'; + switches='-v -O -Dmac'; + eval(['mex ',switches,' ',files,' ']); + otherwise % All other platforms + files='sinco.cpp sinco_mex.cpp'; + switches='-v -O -Dlinuxp'; + eval(['mex ',switches,' ',files,' ']); +end +disp(' ......................... Done Compiling Source .........................') diff --git a/inference/dimmwitted/src/sinco/sinco.cpp b/inference/dimmwitted/src/sinco/sinco.cpp new file mode 100644 index 000000000..4d1d3395d --- /dev/null +++ b/inference/dimmwitted/src/sinco/sinco.cpp @@ -0,0 +1,437 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sinco.hpp" + +/* This is the C++ implementation to solve the sparse inverse covariance + problem of the form + +max _C[ K*log(det(C)-tr(AC)-\lambda ||S.*C||_1] + +K and lambda are positive scalars, A is a given symmetric matrix, S is a given nonnegative matrix +(not necessarily symmetric), S.*C is a elemwhise product and ||*||_1 is the sum +of all absolute values. + + */ + +double funvalue_update(double alpha, double K, double Wij, double Wii, double Wjj, + double Aij, double Sij,double Sji, double update) { + double detratio, fchange; + /* This routine computes the change in the objective function value when a step of length\ alpha is made in the direction e_ie_j^T+e_je_i^T */ +if (update ==1) { + detratio=(1+alpha*Wij)*(1+alpha*Wij-alpha*alpha*Wii*Wjj/(1+alpha*Wij)); + fchange=K*(log(detratio))-2*alpha*Aij-alpha*(Sij+Sji); + } + else { + detratio=(1-alpha*Wij)*(1-alpha*Wij-alpha*alpha*Wii*Wjj/(1-alpha*Wij)); + fchange=K*(log(detratio))+2*alpha*Aij-alpha*(Sij+Sji); + } + return fchange; +} + + +void invupdate(Matrix& Gradp, Matrix& Gradpp, vector& UpInd, Matrix& W, const double theta, const double K, const int p, int i, int j, int update) +{ + /* This routine computes the change in the dual matrix W (inverse of C) when a step of length\ alpha is made in the direction e_ie_j^T+e_je_i^T */ +double a,b,c,d, wij, wjj, wii; + int ii, jj, upindind; +Matrix UpW(p,p); +wii=W(i,i); +wjj=W(j,j); +wij=W(i,j); +if (update==1){ + d=theta*theta*(wii*wjj-wij*wij)-1-2*theta*wij; + a=-(1+theta*wij)/d; + b=theta*wjj/d; + c=theta*wii/d; + for (int ii=p-1; ii>=0; ii--) { + for (int jj=ii; jj>=0; jj--){ + upindind=ii*p+jj; + UpW(ii,jj)=-theta*(a*W(i,ii)*W(j,jj)+ c*W(j,ii)*W(j,jj)+b*W(i,ii)*W(i,jj)+a*W(j,ii)*W(i,jj)); + if (fabs(UpW(ii,jj))>zerotol){UpInd[upindind]=1;} + else {UpInd[ii*p+jj]=0;} + UpW(jj,ii)=UpW(ii,jj); + UpInd[jj*p+ii]=UpInd[upindind]; + } + } +} + else{ + d=theta*theta*(-wii*wjj+wij*wij)+1-2*theta*wij; + a=(1-theta*wij)/d; + b=theta*wjj/d; + c=theta*wii/d; + + for (int ii=p-1; ii>=0; ii--) { + for (int jj=ii; jj>=0; jj--) { + upindind=ii*p+jj; + UpW(ii,jj)=theta*(a*W(i,ii)*W(j,jj)+ c*W(j,ii)*W(j,jj)+b*W(i,ii)*W(i,jj)+a*W(j,ii)*W(i,jj)); + if (fabs(UpW(ii,jj))>zerotol){UpInd[upindind]=1;} + else {UpInd[ii*p+jj]=0;} + UpW(jj,ii)=UpW(ii,jj); + UpInd[jj*p+ii]=UpInd[upindind]; + } + } +} +W.sum(UpW); +Gradp.sumwithscal(K,UpW); +Gradpp.sumwithscal(-K,UpW); + +} + + + +double findposstep(double K, double Wij, double Wii, double Wjj, + double Aij, double Sij, double Sji, int update) { + /* This routine computes the optimal length of a step of length in the direction e_ie_j^T+e_je_i^T for the positive component of C */ + double aux1, aux2, aux3, aux4, aux5; + double a, b, c, D, alpha, alpha1, alpha2; + + if ( update ==1 ) { + aux1=2*(K*Wij-Aij)-Sji-Sij; + aux2=(Wii*Wjj-Wij*Wij); + aux3=2*Wij; + aux4=-2*K*(Wii*Wjj+Wij*Wij); + aux5=2*K*Wij*aux2; + } + else { + aux1=2*(-K*Wij+Aij)-Sij-Sji; + aux2=(-Wii*Wjj+Wij*Wij); + aux3=2*Wij; + aux4=2*K*(Wii*Wjj+Wij*Wij); + aux5=-2*K*Wij*aux2; + } + a=aux2*aux1-aux5; + b=-aux3*aux1-aux4; + c=-update*aux1; + if (fabs(a)>zerotol) { + D=b*b-4*a*c; + if (D<0) { + printf ( "negative discriminant, \n"); + abort; + } + alpha1=fmin((-b-sqrt(D))/(2*a),(-b+sqrt(D))/(2*a)); + alpha2=fmax((-b-sqrt(D))/(2*a),(-b+sqrt(D))/(2*a)); + if (alpha1>=0){ alpha=alpha1;} + else { + if (alpha2>=0){ alpha=alpha2;} + else { + printf ( "unbounded direction!!!, \n"); + abort; + } + } + } + else if (-c/b>0) + { alpha=-c/b;} + else { + printf ( "unbounded direction!!!,\n"); + abort; + } + + /* Check correctness +double theta=alpha; + double fprime; + if ( update ==1 ) { + fprime=(K*Wij+K*Wij-Aij-Aij-Sij-Sji)- + K*theta/(theta*theta*(Wii*Wjj-Wij*Wij)-1-2*theta*Wij)*(-2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + else { + fprime=(-K*Wij-K*Wij+Aij+Aij-Sij-Sji)-K*theta/(theta*theta*(-Wii*Wjj+Wij*Wij) + +1-2*theta*Wij)*(2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + if (fabs(fprime) > 10e-6){ + printf("\n bad gradient"); + fflush(stdout); + abort; + } */ + return alpha; +} + + +double findnegstep(double K, bool diag, double Wij, double Wii, double Wjj, + double Aij, double Sij, double Sji, double Cpij, double Cppij, int update) { +/* This routine computes the optimal length of a step of length in the direction e_ie_j^T+e_je_i^T for the negative component of C */ + double aux1, aux2, aux3, aux4, aux5; + double a, b, c, D, maxstep, alpha, alpha1, alpha2; + + if (update ==1) { + aux1=2*(K*Wij-Aij)-Sji-Sij; + aux2=(Wii*Wjj-Wij*Wij); + aux3=2*Wij; + aux4=-2*K*(Wii*Wjj+Wij*Wij); + aux5=2*K*Wij*aux2; + maxstep=Cpij;} + else { + aux1=2*(-K*Wij+Aij)-Sij-Sji; + aux2=(-Wii*Wjj+Wij*Wij); + aux3=2*Wij; + aux4=2*K*(Wii*Wjj+Wij*Wij); + aux5=-2*K*Wij*aux2; + maxstep=Cppij; +} +if (diag) { + maxstep=maxstep/2; +} +a=aux2*aux1-aux5; +b=-aux3*aux1-aux4; +c=-update*aux1; + if (fabs(a)>zerotol) { + D=b*b-4*a*c; + if (D<0) { + printf ( "negative discriminant, \n"); + abort; + } + double sqrootD=sqrt(D); + alpha1=fmin(((-b-sqrootD)/(2*a)),((-b+sqrootD)/(2*a))); + alpha2=fmax(((-b-sqrootD)/(2*a)),((-b+sqrootD)/(2*a))); + if ( alpha2<0 ){ + alpha=fmax(alpha2, -maxstep);} + else if ( alpha1<0 ){ + alpha=fmax(alpha1, -maxstep);} + else { + alpha=-maxstep; + } + } + else if (-c/b<0){ + alpha=fmax(-c/b, -maxstep);} + else { + alpha=-maxstep; + } + /* Check correctness + +double theta=alpha; + double fprime; + if ( update ==1 ) { + fprime=(K*Wij+K*Wij-Aij-Aij-Sij-Sji)- + K*theta/(theta*theta*(Wii*Wjj-Wij*Wij)-1-2*theta*Wij)*(-2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + else { + fprime=(-K*Wij-K*Wij+Aij+Aij-Sij-Sji)-K*theta/(theta*theta*(-Wii*Wjj+Wij*Wij) + +1-2*theta*Wij)*(2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + if ((abs(theta) 10e-6)){ + printf("\n bad gradient"); + fflush(stdout); + abort; + } */ + return alpha; +} + + +void ICS::sinco(SOL& ICS_sol, const SOL& ICS_start, double tol ){ + /*This is the main routine for computing the sparse inverse covariance. + ICS class contains the problem data, that is A, K, p, lambda and S. + The input for this routine is a starting point ICS_start which contains + intial C, initial W=C^{-1} and initial objective function value. The output +of the routine is final C, W and function value. Tolerance is set in tol and +if set too high may significantly slow down the solver, since convergence + is slow in the end. */ + vector UpInd; + double alphamax, funmax, K, fnew; + int imax, jmax, updatemax, iter=0; + UpInd.resize(p*p, 1); + bool stop=false; + double f=ICS_start.f; + Steps AllSteps(p,p); + K=N/2; + ICS_sol.W=ICS_start.W; + ICS_sol.C=ICS_start.C; + Matrix Cp(p,p,0); + Matrix Cpp(p,p,0); + Matrix Gradp(p,p,0); + Matrix Gradpp(p,p,0); + for (int i=p-1; i>=0; i--) { + for (int j=i; j>=0; j--) { + if (ICS_start.C(i,j)>0) { + Cp(i,j)=ICS_start.C(i,j); + Cp(j,i)=ICS_start.C(j,i); } + else { + Cpp(i,j)=-ICS_start.C(i,j); + Cpp(j,i)=-ICS_start.C(j,i); + } + } + } + Gradp.sumwithscal(-1*lambda, S); + Gradp.sumwithscal(-1, A); + Gradp.sumwithscal(K,ICS_sol.W); + Gradpp.sumwithscal(-1*lambda, S); + Gradpp.sum(A); + Gradpp.sumwithscal(-1*K,ICS_sol.W); + + + while (!stop && (iter<100000)) { + // %find the largest positive derivative + alphamax=0; + funmax=f; + iter+=1; + // printf("\n iteration [%4d] [%6f]", iter, f); + // fflush(stdout); + + for (int i=p-1; i>=0; i--) { + for (int j=i; j>=0; j--) { + if (UpInd[i*p+j] || UpInd[i*p+i] || UpInd[j*p+j]){ + AllSteps(i,j).alpha=0; + if ((Gradp(i,j)+Gradp(j,i) > tol) && (Cpp(i,j)<=tol)){ + AllSteps(i,j).update=1; + AllSteps(i,j).alpha=findposstep(K, ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else if ((Gradpp(i,j)+Gradpp(j,i) > tol) && (Cp(i,j)<=tol)) { + AllSteps(i,j).update=-1; + AllSteps(i,j).alpha=findposstep(K, ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else if ((Gradp(i,j)+Gradp(j,i)<-tol) && (Cp(i,j)> tol )) { + AllSteps(i,j).update=1; + AllSteps(i,j).alpha=findnegstep(K, (i==j), ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), Cp(i,j), Cpp(i,j), + AllSteps(i,j).update); + } + else if ((Gradpp(i,j)+Gradpp(j,i)<-tol) && (Cpp(i,j)> tol)) { + AllSteps(i,j).update=-1; + AllSteps(i,j).alpha=findnegstep(K, (i==j), ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), Cp(i,j), Cpp(i,j), AllSteps(i,j).update); + } + if ( fabs(AllSteps(i,j).alpha)> tol) { + AllSteps(i,j).fchange=funvalue_update(AllSteps(i,j).alpha, + K,ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else { + AllSteps(i,j).fchange=0; + } + } + fnew=f+AllSteps(i,j).fchange; + if (fnew > funmax ) { + funmax=fnew; + imax=i; + jmax=j; + alphamax=AllSteps(i,j).alpha; + updatemax=AllSteps(i,j).update; + } + } + } + /* for (int i=0; i>test.A(i,j); + } + } + /* myfile.close(); */ + /* for (int i=0; i +#include +#include +#include +#include +#include +#include +#include + +#define zerotol 1e-10 +using namespace std; + + +class Matrix{ +public: + Matrix(){} + Matrix(int d1, int d2, double elm=0){ + M.resize(d1*d2, elm); + dim1=d1; + dim2=d2; + } + Matrix(const Matrix& A){ + M=A.M; + dim1=A.dim1; + dim2=A.dim2; + } + Matrix& operator=(const Matrix& A){ + if (this !=&A){ + M=A.M; + dim1=A.dim1; + dim2=A.dim2; + } + return *this; + } + + inline double operator()(int i, int j) const { + return M[i*dim2+j]; + } + inline double& operator()(int i, int j){ + return M[i*dim2+j]; + } + + double dotmultiply (Matrix A){ + double S=0; + for (int i=dim1*dim2-1; i>=0; i--) { + S+=A.M[i]*M[i]; + } + return S; + } + void importMat(double* A, int d1, int d2) { + M.resize(d1*d2); + for (int i=d1*d2-1; i>=0; i--) { + M[i]=A[i]; + } + dim1=d1; + dim2=d2; + } + + void exportMat(double* A) { + for (int i=dim1*dim2-1; i>=0; i--) { + A[i]=M[i]; + } + } + + + + /* double dotmultiply (Matrix& A){ + double S=0; + for (int i=0; i=0; i--) { + M[i]+=A.M[i]; + } + } + void sumwithscal(double N, Matrix& A){ + for (int i=dim1*dim2-1; i>=0; i--) { + M[i]+=N*A.M[i]; + } + } + void addidentity(double N){ + for (int i=0; i=0; i--) { + S+=fabs(M[i]*A.M[i]); + } + return S; + } + /* double normS (Matrix& A){ + double S=0; + for (int i=0; i M; + int dim1; + int dim2; +}; + +class Step{ +public: + double alpha; + double fchange; + int update; +}; + + +class Steps{ +public: + Steps(int d1, int d2){ + Stepmat.resize(d1*d2); + dim1=d1; + dim2=d2; + } + inline Step& operator()(int i, int j){ + return Stepmat[i*dim2+j]; + } + vector< Step > Stepmat; + int dim1; + int dim2; +}; + +class SOL{ +public: + Matrix C; + Matrix W; + double f; +}; + +/* Class ICS is a class of "Inverse Covariance Selection" problems, it has the optimization function, the data A, lambda, S, p and N. */ + +class ICS{ +public: + void sinco(SOL& ICS_sol, const SOL& ICS_start, double tol ); + Matrix A; + Matrix S; + double lambda; + int p; + int N; + +}; + + diff --git a/inference/dimmwitted/src/sinco/sinco_mex.cpp b/inference/dimmwitted/src/sinco/sinco_mex.cpp new file mode 100644 index 000000000..210ff35d1 --- /dev/null +++ b/inference/dimmwitted/src/sinco/sinco_mex.cpp @@ -0,0 +1,56 @@ +// MEX wrapper for the function computing sparse inv. covariance + +// function [Csol, Wsol, fsol]=sinco(Cstart, fstart, Wstart, A, S, lambda, p, K); + +// Calling sequence: void ICS::sinco(SOL& ICS_sol, const SOL& ICS_start, double tol ){ + +// Last Modified: Katya Scheinber Feb 2009 +// + + +#include "sinco.hpp" +#include "mex.h" + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + double *Cstart, *Wstart, *Csol, *Wsol, *A, *S, *fsol; + double fstart, K, lambda, tol; + int p; + ICS prob; + SOL Init_Sol; + SOL Opt_Sol; + + Cstart = mxGetPr(prhs[0]); + p = mxGetN(prhs[0]); + Wstart = mxGetPr(prhs[2]); + A = mxGetPr(prhs[3]); + S = mxGetPr(prhs[4]); + fstart = mxGetScalar(prhs[1]); + lambda = mxGetScalar(prhs[5]); + K = mxGetScalar(prhs[6]); + tol = mxGetScalar(prhs[7]); + + plhs[0] = mxCreateDoubleMatrix(p, p, mxREAL); + plhs[2] = mxCreateDoubleMatrix(1, 1, mxREAL); + plhs[1] = mxCreateDoubleMatrix(p, p, mxREAL); + Csol = mxGetPr(plhs[0]); + fsol = mxGetPr(plhs[2]); + Wsol = mxGetPr(plhs[1]); + + prob.p=p; + prob.S.importMat(S, p, p); + prob.A.importMat(A, p, p); + prob.lambda=lambda; + prob.N=K; + Init_Sol.C.importMat(Cstart, p, p); + Init_Sol.W.importMat(Wstart, p, p); + Init_Sol.f=fstart; + + prob.sinco(Opt_Sol, Init_Sol, tol ); + + Opt_Sol.C.exportMat(Csol); + Opt_Sol.W.exportMat(Wsol); + *fsol=Opt_Sol.f; + +} + diff --git a/inference/dimmwitted/src/text2bin.cc b/inference/dimmwitted/src/text2bin.cc new file mode 100644 index 000000000..89a6a8135 --- /dev/null +++ b/inference/dimmwitted/src/text2bin.cc @@ -0,0 +1,262 @@ +/* + * Transform a TSV format factor graph file and output corresponding binary + * format used in DeepDive + */ + +#include "text2bin.h" +#include "binary_format.h" +#include +#include +#include +#include +#include +#include +#include + +namespace dd { + +// read variables and convert to binary format +void text2binum_vars(std::string input_filename, std::string output_filename, + std::string count_filename) { + std::ifstream fin(input_filename); + std::ofstream fout(output_filename, std::ios::binary); + std::ofstream fout_count(count_filename); + size_t count = 0; + std::string line; + while (getline(fin, line)) { + std::istringstream ss(line); + size_t vid; + size_t var_role; + size_t initial_value; + size_t var_type; + size_t cardinality; + assert(ss >> vid); + assert(ss >> var_role); + assert(ss >> initial_value); + assert(ss >> var_type); + assert(ss >> cardinality); + uint8_t var_role_serialized = var_role; + uint16_t var_type_serialized = var_type; + write_be_or_die(fout, vid); + write_be_or_die(fout, var_role_serialized); + write_be_or_die(fout, initial_value); + write_be_or_die(fout, var_type_serialized); + write_be_or_die(fout, cardinality); + ++count; + } + fout_count << count << std::endl; +} + +// convert weights +void text2bin_weights(std::string input_filename, std::string output_filename, + std::string count_filename) { + std::ifstream fin(input_filename); + std::ofstream fout(output_filename, std::ios::binary); + std::ofstream fout_count(count_filename); + size_t count = 0; + std::string line; + while (getline(fin, line)) { + std::istringstream ss(line); + size_t wid; + size_t isfixed; + double initial_value; + assert(ss >> wid); + assert(ss >> isfixed); + assert(ss >> initial_value); + uint8_t isfixed_serialized = isfixed; + write_be_or_die(fout, wid); + write_be_or_die(fout, isfixed_serialized); + write_be_or_die(fout, initial_value); + ++count; + } + fout_count << count << std::endl; +} + +constexpr size_t UNDEFINED_COUNT = (size_t)-1; + +static inline size_t parse_pgarray( + std::istream &input, std::function parse_element, + size_t expected_count = UNDEFINED_COUNT) { + if (input.peek() == '{') { + input.get(); + std::string element; + bool ended = false; + size_t count = 0; + while (getline(input, element, ',')) { + if (element.at(element.length() - 1) == '}') { + ended = true; + element = element.substr(0, element.length() - 1); + } + parse_element(element); + count++; + if (ended) break; + } + assert(expected_count == UNDEFINED_COUNT || count == expected_count); + return count; + } else { + return UNDEFINED_COUNT; + } +} +static inline size_t parse_pgarray_or_die( + std::istream &input, std::function parse_element, + size_t expected_count = UNDEFINED_COUNT) { + size_t count = parse_pgarray(input, parse_element, expected_count); + if (count != UNDEFINED_COUNT) { + return count; + } else { + std::cerr << "Expected an array '{' but found: " << input.get() + << std::endl; + std::abort(); + } +} + +// load factors +// wid, vids +void text2bin_factors(std::string input_filename, std::string output_filename, + FACTOR_FUNCTION_TYPE funcid, size_t arity_expected, + const std::vector &variables_should_equal_to, + std::string count_filename) { + std::ifstream fin(input_filename); + std::ofstream fout(output_filename, std::ios::binary); + std::ofstream fout_count(count_filename); + size_t total_edges = 0; + std::vector vids; + std::vector values; + std::string line; + + while (getline(fin, line)) { + std::string field; + std::istringstream ss(line); + vids.clear(); + values.clear(); + + // parse factor type + uint16_t funcid_serialized = funcid; + + // parse variable ids + size_t arity = 0; + auto parse_variableid = [&vids, &arity, + &total_edges](const std::string &element) { + vids.push_back(atol(element.c_str())); + ++total_edges; + ++arity; + }; + for (size_t i = 0; i < arity_expected; ++i) { + assert(getline(ss, field, text_field_delim)); + parse_variableid(field); + } + + // parse variable values (categorical only) + if (funcid == FUNC_AND_CATEGORICAL) { + auto parse_value = [&values](const std::string &element) { + values.push_back(atol(element.c_str())); + }; + for (size_t i = 0; i < arity_expected; ++i) { + assert(getline(ss, field, text_field_delim)); + parse_value(field); + } + } + + // parse weight id + assert(getline(ss, field, text_field_delim)); + size_t wid = atol(field.c_str()); + + // parse feature value + assert(getline(ss, field, text_field_delim)); + double val = atof(field.c_str()); + + // write out record + write_be_or_die(fout, funcid_serialized); + write_be_or_die(fout, arity); + for (size_t i = 0; i < vids.size(); ++i) { + write_be_or_die(fout, vids[i]); + size_t should_equal_to = variables_should_equal_to.at(i); + if (funcid == FUNC_AND_CATEGORICAL) { + should_equal_to = values.at(i); + } + write_be_or_die(fout, should_equal_to); + } + write_be_or_die(fout, wid); + write_be_or_die(fout, val); + } + fout_count << total_edges << std::endl; +} + +// read categorical variable domains and convert to binary format +void text2bin_domains(std::string input_filename, std::string output_filename, + std::string count_filename) { + std::ifstream fin(input_filename); + std::ofstream fout(output_filename, std::ios::binary); + std::ofstream fout_count(count_filename); + size_t count = 0; + std::string line; + std::vector value_list; + std::vector truthiness_list; + while (getline(fin, line)) { + std::istringstream ss(line); + size_t vid; + size_t cardinality; + std::string domain_string, truthy_string; + assert(ss >> vid); + assert(ss >> cardinality); + assert(ss >> domain_string); + assert(ss >> truthy_string); + + value_list.reserve(cardinality); + truthiness_list.reserve(cardinality); + + // an array of domain values + std::istringstream domain_input(domain_string); + size_t i = 0; + parse_pgarray_or_die( + domain_input, [&i, &value_list](const std::string &subfield) { + size_t value = atol(subfield.c_str()); + value_list[i] = value; + ++i; + }, cardinality); + + // an array of truthiness + std::istringstream truthy_input(truthy_string); + i = 0; + parse_pgarray_or_die( + truthy_input, [&i, &truthiness_list](const std::string &subfield) { + double truthiness = atof(subfield.c_str()); + truthiness_list[i] = truthiness; + ++i; + }, cardinality); + + write_be_or_die(fout, vid); + write_be_or_die(fout, cardinality); + for (i = 0; i < cardinality; ++i) { + write_be_or_die(fout, value_list[i]); + write_be_or_die(fout, truthiness_list[i]); + } + ++count; + } + fout_count << count << std::endl; +} + +int text2bin(const CmdParser &args) { + // common arguments + if (args.text2bin_mode == "variable") { + text2binum_vars(args.text2bin_input, args.text2bin_output, + args.text2bin_count_output); + } else if (args.text2bin_mode == "weight") { + text2bin_weights(args.text2bin_input, args.text2bin_output, + args.text2bin_count_output); + } else if (args.text2bin_mode == "factor") { + text2bin_factors(args.text2bin_input, args.text2bin_output, + args.text2bin_factor_func_id, args.text2bin_factor_arity, + args.text2bin_factor_variables_should_equal_to, + args.text2bin_count_output); + } else if (args.text2bin_mode == "domain") { + text2bin_domains(args.text2bin_input, args.text2bin_output, + args.text2bin_count_output); + } else { + std::cerr << "Unsupported type" << std::endl; + return 1; + } + return 0; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/text2bin.h b/inference/dimmwitted/src/text2bin.h new file mode 100644 index 000000000..80b3b2bbe --- /dev/null +++ b/inference/dimmwitted/src/text2bin.h @@ -0,0 +1,14 @@ +#ifndef DIMMWITTED_TEXT2BIN_H_ +#define DIMMWITTED_TEXT2BIN_H_ + +#include "cmd_parser.h" + +namespace dd { + +constexpr char text_field_delim = '\t'; // tsv file delimiter + +int text2bin(const CmdParser &args); + +} // namespace dd + +#endif // DIMMWITTED_TEXT2BIN_H_ diff --git a/inference/dimmwitted/src/timer.cc b/inference/dimmwitted/src/timer.cc new file mode 100644 index 000000000..dae1ba24a --- /dev/null +++ b/inference/dimmwitted/src/timer.cc @@ -0,0 +1,31 @@ +#include "timer.h" + +#ifdef __MACH__ +int clock_gettime(int /*clk_id*/, struct timespec *t) { + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; +} + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 0 +#endif + +#endif + +namespace dd { + +Timer::Timer() { clock_gettime(CLOCK_MONOTONIC, &_start); } + +void Timer::restart() { clock_gettime(CLOCK_MONOTONIC, &_start); } + +float Timer::elapsed() { + clock_gettime(CLOCK_MONOTONIC, &_end); + return (_end.tv_sec - _start.tv_sec) + + (_end.tv_nsec - _start.tv_nsec) / 1000000000.0; +} + +} // namespace dd diff --git a/inference/dimmwitted/src/timer.h b/inference/dimmwitted/src/timer.h new file mode 100644 index 000000000..33548558b --- /dev/null +++ b/inference/dimmwitted/src/timer.h @@ -0,0 +1,50 @@ +#ifndef DIMMWITTED_TIMER_H_ +#define DIMMWITTED_TIMER_H_ + +#include +#include + +#ifdef __MACH__ +#include +#include +#endif + +#ifdef __MACH__ +#include +// clock_gettime is not implemented on OSX +int clock_gettime(int /*clk_id*/, struct timespec *t); + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 0 +#endif + +#endif + +#include + +namespace dd { + +/** + * Timer class that keeps track of time + */ +class Timer { + public: + struct timespec _start; + struct timespec _end; + + Timer(); + + /** + * Restart the timer + */ + void restart(); + + /** + * Returns time elapsed + */ + float elapsed(); +}; + +} // namespace dd + +#endif // DIMMWITTED_TIMER_H_ diff --git a/inference/dimmwitted/src/variable.cc b/inference/dimmwitted/src/variable.cc new file mode 100644 index 000000000..5568fdceb --- /dev/null +++ b/inference/dimmwitted/src/variable.cc @@ -0,0 +1,49 @@ +#include "variable.h" + +namespace dd { + +// To placate linker error "undefined reference" +// http://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char +constexpr size_t Variable::INVALID_ID; +constexpr size_t Variable::INVALID_VALUE; + +Variable::Variable() : Variable(INVALID_ID, DTYPE_BOOLEAN, false, 2, 0) {} + +Variable::Variable(size_t id, DOMAIN_TYPE domain_type, bool is_evidence, + size_t cardinality, size_t init_value) + : id(id), + domain_type(domain_type), + is_evid(is_evidence), + cardinality(cardinality), + assignment_dense(init_value), + total_truthiness(0), + var_val_base(-1) {} + +Variable::Variable(const Variable &other) { *this = other; } + +Variable &Variable::operator=(const Variable &other) { + id = other.id; + domain_type = other.domain_type; + is_evid = other.is_evid; + cardinality = other.cardinality; + assignment_dense = other.assignment_dense; + total_truthiness = other.total_truthiness; + var_val_base = other.var_val_base; + domain_map.reset( + other.domain_map + ? new std::unordered_map(*other.domain_map) + : nullptr); + adjacent_factors.reset( + other.adjacent_factors + ? new std::vector(*other.adjacent_factors) + : nullptr); + return *this; +} + +FactorToVariable::FactorToVariable() + : FactorToVariable(Variable::INVALID_ID, Variable::INVALID_VALUE) {} + +FactorToVariable::FactorToVariable(size_t vid, size_t dense_equal_to) + : vid(vid), dense_equal_to(dense_equal_to) {} + +} // namespace dd diff --git a/inference/dimmwitted/src/variable.h b/inference/dimmwitted/src/variable.h new file mode 100644 index 000000000..908e2a264 --- /dev/null +++ b/inference/dimmwitted/src/variable.h @@ -0,0 +1,171 @@ +#ifndef DIMMWITTED_VARIABLE_H_ +#define DIMMWITTED_VARIABLE_H_ + +#include "common.h" + +#include +#include +#include + +namespace dd { + +// Used to store back refs to factors in a Variable at loading time. +// Deallocated in FactorGraph.construct_index. +class TempValueFactor { + public: + size_t value_dense; + size_t factor_id; + + TempValueFactor(size_t value_dense_in, size_t factor_id_in) + : value_dense(value_dense_in), factor_id(factor_id_in){}; + + // Lexical sort order for indexing + bool operator<(const TempValueFactor& other) const { + return value_dense < other.value_dense || + (value_dense == other.value_dense && factor_id < other.factor_id); + } +}; + +// Used as value in Variable.domain_map +typedef struct { + size_t index; + double truthiness; +} TempVarValue; + +/** + * A variable in the factor graph. + */ +class Variable { + public: + // While a categorical var has `cardinality` VariableToFactor objects, + // a boolean var only has one VariableToFactor object in FactorGraph.values + // and one entry in InferenceResult.sample_tallies. + // BOOLEAN_DENSE_VALUE is used to index into those arrays. + static constexpr size_t BOOLEAN_DENSE_VALUE = 0; + static constexpr size_t INVALID_ID = (size_t)-1; + static constexpr size_t INVALID_VALUE = (size_t)-1; + + size_t id; // variable id + DOMAIN_TYPE domain_type; // can be DTYPE_BOOLEAN or DTYPE_CATEGORICAL + bool is_evid; // whether the variable is evidence + size_t cardinality; // cardinality; 2 for boolean + + // After loading (Factorgraph.construct_index), all assignment values + // and all proposal values take the "dense" form: + // - Boolean: {0, 1} + // - Categorical: [0..cardinality) + size_t assignment_dense; + + // Sum of all values' truthiness + double total_truthiness; + + // We concatenate the list of "possible values" for each variable to form + // FactorGraph.values (also InferenceResults.sample_tallies). + // - For boolean, there is just one value, namely BOOLEAN_DENSE_VALUE + // - For categorical, there are `cardinality` values + // - if domain is specified, it's the sorted list of domain values + // - otherwise, it's [0..cardinality) + // + // var_val_base is the start position into those lists. + size_t var_val_base; + + // Map from value to index in the domain vector. + // Populated at load time (FactorGraph.load_domains). + // Deallocated in FactorGraph.construct_index. + std::unique_ptr> domain_map; + + // Backrefs to factors (including factor's "equal_to" value). + // Populated at load time (FactorGraph.load_factors). + // Deallocated in FactorGraph.construct_index. + std::unique_ptr> + adjacent_factors; // factor ids the variable connects to + + Variable(); // default constructor, necessary for + // FactorGraph::variables + + Variable(size_t id, DOMAIN_TYPE domain_type, bool is_evidence, + size_t cardinality, size_t init_value); + + /** + * Constructs a variable with only the important information from a + * temporary variable + */ + Variable(const Variable& variable); + Variable& operator=(const Variable& variable); + + inline void add_value_factor(size_t value_dense, size_t factor_id) { + if (!adjacent_factors) { + adjacent_factors.reset(new std::vector()); + } + adjacent_factors->push_back(TempValueFactor(value_dense, factor_id)); + } + + inline bool is_boolean() const { return domain_type == DTYPE_BOOLEAN; } + + inline bool has_truthiness() const { + return !is_linear_zero(total_truthiness); + } + + inline size_t internal_cardinality() const { + return is_boolean() ? 1 : cardinality; + } + + inline size_t var_value_offset(size_t value_dense) const { + return is_boolean() ? BOOLEAN_DENSE_VALUE : value_dense; + } + + // get the index of the value + inline size_t get_domain_index(size_t v) const { + return domain_map ? domain_map->at(v).index : v; + } +}; + +class VariableToFactor { + public: + // original (sparse) value as assigned by DD grounding + size_t value; + // soft evidence weight. range: [0, 1]. + // NOTE: for categorical only + double truthiness; + // base offset into the factor index (FactorGraph.factor_index) + size_t factor_index_base; + // number of entries in the factor index + size_t factor_index_length; + + VariableToFactor() : VariableToFactor(Variable::INVALID_ID, 0, -1, 0) {} + + VariableToFactor(size_t v, double t, size_t base, size_t len) + : value(v), + truthiness(t), + factor_index_base(base), + factor_index_length(len) {} +}; + +/** + * Encapsulates a variable inside a factor + */ +class FactorToVariable { + public: + size_t vid; // variable id + // dense-form value binding to the var in the factor + // - Boolean: {0, 1} depending on wheter atom is negated in rule head + // - Categorical: [0..cardinality) depending on the var relation tuple + size_t dense_equal_to; + + /** + * Returns whether the variable's predicate is satisfied using the given + * value. + * Applies to both boolean and categorical. + */ + inline bool satisfiedUsing(size_t dense_value) const { + return dense_equal_to == dense_value; + } + + FactorToVariable(); + + FactorToVariable(size_t vid, size_t dense_equal_to); +}; + +} // namespace dd + +#endif // DIMMWITTED_VARIABLE_H_ diff --git a/inference/dimmwitted/src/variational.cc b/inference/dimmwitted/src/variational.cc new file mode 100644 index 000000000..01702505f --- /dev/null +++ b/inference/dimmwitted/src/variational.cc @@ -0,0 +1,489 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sinco/sinco.hpp" + + +double funvalue_update(double alpha, double K, double Wij, double Wii, double Wjj, + double Aij, double Sij,double Sji, double update) { + double detratio, fchange; + /* This routine computes the change in the objective function value when a step of length\ alpha is made in the direction e_ie_j^T+e_je_i^T */ +if (update ==1) { + detratio=(1+alpha*Wij)*(1+alpha*Wij-alpha*alpha*Wii*Wjj/(1+alpha*Wij)); + fchange=K*(log(detratio))-2*alpha*Aij-alpha*(Sij+Sji); + } + else { + detratio=(1-alpha*Wij)*(1-alpha*Wij-alpha*alpha*Wii*Wjj/(1-alpha*Wij)); + fchange=K*(log(detratio))+2*alpha*Aij-alpha*(Sij+Sji); + } + return fchange; +} + + +void invupdate(Matrix& Gradp, Matrix& Gradpp, vector& UpInd, Matrix& W, const double theta, const double K, const int p, int i, int j, int update) +{ + /* This routine computes the change in the dual matrix W (inverse of C) when a step of length\ alpha is made in the direction e_ie_j^T+e_je_i^T */ +double a,b,c,d, wij, wjj, wii; + int ii, jj, upindind; +Matrix UpW(p,p); +wii=W(i,i); +wjj=W(j,j); +wij=W(i,j); +if (update==1){ + d=theta*theta*(wii*wjj-wij*wij)-1-2*theta*wij; + a=-(1+theta*wij)/d; + b=theta*wjj/d; + c=theta*wii/d; + for (int ii=p-1; ii>=0; ii--) { + for (int jj=ii; jj>=0; jj--){ + upindind=ii*p+jj; + UpW(ii,jj)=-theta*(a*W(i,ii)*W(j,jj)+ c*W(j,ii)*W(j,jj)+b*W(i,ii)*W(i,jj)+a*W(j,ii)*W(i,jj)); + if (fabs(UpW(ii,jj))>zerotol){UpInd[upindind]=1;} + else {UpInd[ii*p+jj]=0;} + UpW(jj,ii)=UpW(ii,jj); + UpInd[jj*p+ii]=UpInd[upindind]; + } + } +} + else{ + d=theta*theta*(-wii*wjj+wij*wij)+1-2*theta*wij; + a=(1-theta*wij)/d; + b=theta*wjj/d; + c=theta*wii/d; + + for (int ii=p-1; ii>=0; ii--) { + for (int jj=ii; jj>=0; jj--) { + upindind=ii*p+jj; + UpW(ii,jj)=theta*(a*W(i,ii)*W(j,jj)+ c*W(j,ii)*W(j,jj)+b*W(i,ii)*W(i,jj)+a*W(j,ii)*W(i,jj)); + if (fabs(UpW(ii,jj))>zerotol){UpInd[upindind]=1;} + else {UpInd[ii*p+jj]=0;} + UpW(jj,ii)=UpW(ii,jj); + UpInd[jj*p+ii]=UpInd[upindind]; + } + } +} +W.sum(UpW); +Gradp.sumwithscal(K,UpW); +Gradpp.sumwithscal(-K,UpW); + +} + + + +double findposstep(double K, double Wij, double Wii, double Wjj, + double Aij, double Sij, double Sji, int update) { + /* This routine computes the optimal length of a step of length in the direction e_ie_j^T+e_je_i^T for the positive component of C */ + double aux1, aux2, aux3, aux4, aux5; + double a, b, c, D, alpha, alpha1, alpha2; + + if ( update ==1 ) { + aux1=2*(K*Wij-Aij)-Sji-Sij; + aux2=(Wii*Wjj-Wij*Wij); + aux3=2*Wij; + aux4=-2*K*(Wii*Wjj+Wij*Wij); + aux5=2*K*Wij*aux2; + } + else { + aux1=2*(-K*Wij+Aij)-Sij-Sji; + aux2=(-Wii*Wjj+Wij*Wij); + aux3=2*Wij; + aux4=2*K*(Wii*Wjj+Wij*Wij); + aux5=-2*K*Wij*aux2; + } + a=aux2*aux1-aux5; + b=-aux3*aux1-aux4; + c=-update*aux1; + if (fabs(a)>zerotol) { + D=b*b-4*a*c; + if (D<0) { + printf ( "negative discriminant, \n"); + abort; + } + alpha1=fmin((-b-sqrt(D))/(2*a),(-b+sqrt(D))/(2*a)); + alpha2=fmax((-b-sqrt(D))/(2*a),(-b+sqrt(D))/(2*a)); + if (alpha1>=0){ alpha=alpha1;} + else { + if (alpha2>=0){ alpha=alpha2;} + else { + printf ( "unbounded direction!!!, \n"); + abort; + } + } + } + else if (-c/b>0) + { alpha=-c/b;} + else { + printf ( "unbounded direction!!!,\n"); + abort; + } + + /* Check correctness +double theta=alpha; + double fprime; + if ( update ==1 ) { + fprime=(K*Wij+K*Wij-Aij-Aij-Sij-Sji)- + K*theta/(theta*theta*(Wii*Wjj-Wij*Wij)-1-2*theta*Wij)*(-2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + else { + fprime=(-K*Wij-K*Wij+Aij+Aij-Sij-Sji)-K*theta/(theta*theta*(-Wii*Wjj+Wij*Wij) + +1-2*theta*Wij)*(2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + if (fabs(fprime) > 10e-6){ + printf("\n bad gradient"); + fflush(stdout); + abort; + } */ + return alpha; +} + + +double findnegstep(double K, bool diag, double Wij, double Wii, double Wjj, + double Aij, double Sij, double Sji, double Cpij, double Cppij, int update) { +/* This routine computes the optimal length of a step of length in the direction e_ie_j^T+e_je_i^T for the negative component of C */ + double aux1, aux2, aux3, aux4, aux5; + double a, b, c, D, maxstep, alpha, alpha1, alpha2; + + if (update ==1) { + aux1=2*(K*Wij-Aij)-Sji-Sij; + aux2=(Wii*Wjj-Wij*Wij); + aux3=2*Wij; + aux4=-2*K*(Wii*Wjj+Wij*Wij); + aux5=2*K*Wij*aux2; + maxstep=Cpij;} + else { + aux1=2*(-K*Wij+Aij)-Sij-Sji; + aux2=(-Wii*Wjj+Wij*Wij); + aux3=2*Wij; + aux4=2*K*(Wii*Wjj+Wij*Wij); + aux5=-2*K*Wij*aux2; + maxstep=Cppij; +} +if (diag) { + maxstep=maxstep/2; +} +a=aux2*aux1-aux5; +b=-aux3*aux1-aux4; +c=-update*aux1; + if (fabs(a)>zerotol) { + D=b*b-4*a*c; + if (D<0) { + printf ( "negative discriminant, \n"); + abort; + } + double sqrootD=sqrt(D); + alpha1=fmin(((-b-sqrootD)/(2*a)),((-b+sqrootD)/(2*a))); + alpha2=fmax(((-b-sqrootD)/(2*a)),((-b+sqrootD)/(2*a))); + if ( alpha2<0 ){ + alpha=fmax(alpha2, -maxstep);} + else if ( alpha1<0 ){ + alpha=fmax(alpha1, -maxstep);} + else { + alpha=-maxstep; + } + } + else if (-c/b<0){ + alpha=fmax(-c/b, -maxstep);} + else { + alpha=-maxstep; + } + /* Check correctness + +double theta=alpha; + double fprime; + if ( update ==1 ) { + fprime=(K*Wij+K*Wij-Aij-Aij-Sij-Sji)- + K*theta/(theta*theta*(Wii*Wjj-Wij*Wij)-1-2*theta*Wij)*(-2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + else { + fprime=(-K*Wij-K*Wij+Aij+Aij-Sij-Sji)-K*theta/(theta*theta*(-Wii*Wjj+Wij*Wij) + +1-2*theta*Wij)*(2*(Wii*Wjj+Wij*Wij) + +2*theta*Wij*(Wii*Wjj-Wij*Wij)); + } + if ((abs(theta) 10e-6)){ + printf("\n bad gradient"); + fflush(stdout); + abort; + } */ + return alpha; +} + + +void ICS::sinco(SOL& ICS_sol, const SOL& ICS_start, double tol ){ + /*This is the main routine for computing the sparse inverse covariance. + ICS class contains the problem data, that is A, K, p, lambda and S. + The input for this routine is a starting point ICS_start which contains + intial C, initial W=C^{-1} and initial objective function value. The output +of the routine is final C, W and function value. Tolerance is set in tol and +if set too high may significantly slow down the solver, since convergence + is slow in the end. */ + vector UpInd; + double alphamax, funmax, K, fnew; + int imax, jmax, updatemax, iter=0; + UpInd.resize(p*p, 1); + bool stop=false; + double f=ICS_start.f; + Steps AllSteps(p,p); + K=N/2; + ICS_sol.W=ICS_start.W; + ICS_sol.C=ICS_start.C; + Matrix Cp(p,p,0); + Matrix Cpp(p,p,0); + Matrix Gradp(p,p,0); + Matrix Gradpp(p,p,0); + for (int i=p-1; i>=0; i--) { + for (int j=i; j>=0; j--) { + if (ICS_start.C(i,j)>0) { + Cp(i,j)=ICS_start.C(i,j); + Cp(j,i)=ICS_start.C(j,i); } + else { + Cpp(i,j)=-ICS_start.C(i,j); + Cpp(j,i)=-ICS_start.C(j,i); + } + } + } + Gradp.sumwithscal(-1*lambda, S); + Gradp.sumwithscal(-1, A); + Gradp.sumwithscal(K,ICS_sol.W); + Gradpp.sumwithscal(-1*lambda, S); + Gradpp.sum(A); + Gradpp.sumwithscal(-1*K,ICS_sol.W); + + + while (!stop && (iter<100000)) { + // %find the largest positive derivative + alphamax=0; + funmax=f; + iter+=1; + // printf("\n iteration [%4d] [%6f]", iter, f); + // fflush(stdout); + + for (int i=p-1; i>=0; i--) { + for (int j=i; j>=0; j--) { + if (UpInd[i*p+j] || UpInd[i*p+i] || UpInd[j*p+j]){ + AllSteps(i,j).alpha=0; + if ((Gradp(i,j)+Gradp(j,i) > tol) && (Cpp(i,j)<=tol)){ + AllSteps(i,j).update=1; + AllSteps(i,j).alpha=findposstep(K, ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else if ((Gradpp(i,j)+Gradpp(j,i) > tol) && (Cp(i,j)<=tol)) { + AllSteps(i,j).update=-1; + AllSteps(i,j).alpha=findposstep(K, ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else if ((Gradp(i,j)+Gradp(j,i)<-tol) && (Cp(i,j)> tol )) { + AllSteps(i,j).update=1; + AllSteps(i,j).alpha=findnegstep(K, (i==j), ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), Cp(i,j), Cpp(i,j), + AllSteps(i,j).update); + } + else if ((Gradpp(i,j)+Gradpp(j,i)<-tol) && (Cpp(i,j)> tol)) { + AllSteps(i,j).update=-1; + AllSteps(i,j).alpha=findnegstep(K, (i==j), ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), Cp(i,j), Cpp(i,j), AllSteps(i,j).update); + } + if ( fabs(AllSteps(i,j).alpha)> tol) { + AllSteps(i,j).fchange=funvalue_update(AllSteps(i,j).alpha, + K,ICS_sol.W(i,j),ICS_sol.W(i,i), + ICS_sol.W(j,j), A(i,j), lambda*S(i,j), + lambda*S(j,i), AllSteps(i,j).update); + } + else { + AllSteps(i,j).fchange=0; + } + } + fnew=f+AllSteps(i,j).fchange; + if (fnew > funmax ) { + funmax=fnew; + imax=i; + jmax=j; + alphamax=AllSteps(i,j).alpha; + updatemax=AllSteps(i,j).update; + } + } + } + /* for (int i=0; i vids; +}; + +std::vector components; + +int main(int argc, char** argv){ + + std::string dir(argv[1]); + int nsample = atoi(argv[2]); + + /** first, read all components **/ + std::string file_compoments = dir + "/mat_components_hasevids"; + std::ifstream fin(file_compoments.c_str()); + long vid, has_evid; + while(fin >> vid >> has_evid){ + components.push_back(Component()); + } + std::cout << "Created Components: " << components.size() << std::endl; + + /** second, read variables into components **/ + std::string file_var_comps = dir + "/mat_active_components"; + std::ifstream fin2(file_var_comps.c_str()); + long compid; + long nvars = 0; + while(fin2 >> vid >> compid){ + components[compid].vids.push_back(vid); + nvars ++; + } + std::cout << "Created Variables: " << nvars << std::endl; + + std::string file_output_rs = dir + "/mat_compact_factorgraphs"; + std::ofstream fout(file_output_rs.c_str()); + + float * tally_values = new float[nvars]; + for(long i=0;i> tmp){ + tally_values[ct++] += tmp; + } + } + float todivide = 1.0/nsample; + for(long i=0;i 0.99){ + fout << component.vids[i] << " " << component.vids[j] << " " << 10 << std::endl; + }else if(tally_values[component.vids[i]] < 0.01){ + fout << component.vids[i] << " " << component.vids[j] << " " << -10 << std::endl; + }else{ + float a = log(tally_values[component.vids[i]]/(1-tally_values[component.vids[i]])); + fout << component.vids[i] << " " << component.vids[j] << " " << a << std::endl; + } + }else if ( Opt_Sol.C(i,j) !=0) { + fout << component.vids[i] << " " << component.vids[j] << " " << Opt_Sol.C(i,j) << std::endl; + } + } + } + + } + + delete [] tally_values; + fout.close(); + + return 0; +} + + + + + diff --git a/inference/dimmwitted/src/weight.cc b/inference/dimmwitted/src/weight.cc new file mode 100644 index 000000000..e95d5b621 --- /dev/null +++ b/inference/dimmwitted/src/weight.cc @@ -0,0 +1,10 @@ +#include "weight.h" + +namespace dd { + +Weight::Weight(size_t id, double weight, bool isfixed) + : id(id), weight(weight), isfixed(isfixed) {} + +Weight::Weight() : Weight(INVALID_ID, 0.0, false) {} + +} // namespace dd diff --git a/inference/dimmwitted/src/weight.h b/inference/dimmwitted/src/weight.h new file mode 100644 index 000000000..c80f6e622 --- /dev/null +++ b/inference/dimmwitted/src/weight.h @@ -0,0 +1,28 @@ +#ifndef DIMMWITTED_WEIGHT_H_ +#define DIMMWITTED_WEIGHT_H_ + +#include "common.h" + +#include + +namespace dd { + +/** + * Encapsulates a weight for factors. + */ +class Weight { + public: + size_t id; // weight id + double weight; // weight value + bool isfixed; // whether the weight is fixed + + Weight(size_t id, double weight, bool isfixed); + + Weight(); + + static constexpr size_t INVALID_ID = (size_t)-1; +}; + +} // namespace dd + +#endif // DIMMWITTED_WEIGHT_H_ diff --git a/inference/dimmwitted/test/.end_to_end_test.bats.template b/inference/dimmwitted/test/.end_to_end_test.bats.template new file mode 100755 index 000000000..42745e869 --- /dev/null +++ b/inference/dimmwitted/test/.end_to_end_test.bats.template @@ -0,0 +1,19 @@ +#!/usr/bin/env bats +load helpers + +case $BATS_TEST_FILENAME in + *.bats) ;; + *) + echo >&2 "Cannot run the template directly. Must be run through a .bats symlink pointing to this file ($BATS_TEST_FILENAME)" + false +esac + +@test "end to end test: $(basename "$BATS_TEST_FILENAME" .bats)" { + cd "${BATS_TEST_FILENAME%.bats}" + + run_end_to_end.sh + + # check result + ! [[ -x ./check_result ]] || + ./check_result +} diff --git a/inference/dimmwitted/test/.gtest.bats.template b/inference/dimmwitted/test/.gtest.bats.template new file mode 100755 index 000000000..c9a151c39 --- /dev/null +++ b/inference/dimmwitted/test/.gtest.bats.template @@ -0,0 +1,23 @@ +#!/usr/bin/env bats +load helpers + +case $BATS_TEST_FILENAME in + *.bats) ;; + *) + echo >&2 "Cannot run the template directly. Must be run through a .bats symlink pointing to this file ($BATS_TEST_FILENAME)" + false +esac + +@test "gtest: $(basename "$BATS_TEST_FILENAME" .bats)" { + testFileBaseName=$(basename "$BATS_TEST_FILENAME" .bats) + # convert snake_case to CamelCase + testClassName=$(perl -ne 's/(?:^|_)(.)/{uc $1}/eg; print' <<<"$testFileBaseName") + + # setup + ! [[ -x "${BATS_TEST_FILENAME%.bats}".setup.sh ]] || "${BATS_TEST_FILENAME%.bats}".setup.sh + + dw_test --gtest_filter="$testClassName.*" + + # teardown + ! [[ -x "${BATS_TEST_FILENAME%.bats}".teardown.sh ]] || "${BATS_TEST_FILENAME%.bats}".teardown.sh +} diff --git a/inference/dimmwitted/test/README.md b/inference/dimmwitted/test/README.md new file mode 100644 index 000000000..3e147804e --- /dev/null +++ b/inference/dimmwitted/test/README.md @@ -0,0 +1,14 @@ +# DimmWitted tests + +## Running all tests + +```bash +bats *.bats +``` + +## End to end test directories + +### Factor graph TSV format + +See [text_format.md](../doc/text_format.md) + diff --git a/inference/dimmwitted/test/bats b/inference/dimmwitted/test/bats new file mode 160000 index 000000000..7b032e4b2 --- /dev/null +++ b/inference/dimmwitted/test/bats @@ -0,0 +1 @@ +Subproject commit 7b032e4b232666ee24f150338bad73de65c7b99d diff --git a/inference/dimmwitted/test/bats.mk b/inference/dimmwitted/test/bats.mk new file mode 100644 index 000000000..c96265c77 --- /dev/null +++ b/inference/dimmwitted/test/bats.mk @@ -0,0 +1,49 @@ +# A Makefile fragment for running tests with BATS +# +# Author: Jaeho Shin +# Created: 2015-07-09 + +# some default locations +TEST_ROOT = test +BATS_ROOT = $(TEST_ROOT)/bats +# a command to enumerate all .bats files to test +TEST_LIST_COMMAND = find $(TEST_ROOT) -path $(BATS_ROOT) -prune -false -o -name '*.bats' +export TEST_ROOT + +.PHONY: test test-build test-list +test-list: + @echo "make test \\" + @$(TEST_LIST_COMMAND) | sed 's/$$/ \\/; s/^/ ONLY+=/p; s/^ ONLY+=/ EXCEPT+=/' + @echo " #" + +test: test-build +test-build: + +test: $(BATS_ROOT)/bin/bats +$(BATS_ROOT)/bin/bats: + git submodule update --init $(BATS_ROOT) +# One can fine-tune the list of tests by setting environment variables +# TEST_ONLY and TEST_EXCEPT, or passing the same glob patterns as Make +# arguments ONLY and EXCEPT, e.g.: +# $ make test ONLY+=$(TEST_ROOT)/foo/*.bats EXCEPT+=$(TEST_ROOT)/*/unit_tests.bats +# $ TEST_ONLY=$(TEST_ROOT)/foo/*.bats make test EXCEPT+=$(TEST_ROOT)/*/unit_tests.bats +# $ TEST_ONLY=$(TEST_ROOT)/foo/*.bats TEST_EXCEPT=$(TEST_ROOT)/*/unit_tests.bats make test +TEST_ONLY ?= $(shell $(TEST_LIST_COMMAND)) +TEST_EXCEPT ?= +test: ONLY = $(TEST_ONLY) +test: EXCEPT = $(TEST_EXCEPT) +test: BATS_FILES = $(filter-out $(wildcard $(EXCEPT)),$(wildcard $(ONLY))) +.SECONDEXPANSION: +test: $$(BATS_FILES) +test: + # Running $(shell $(TEST_ROOT)/bats/bin/bats -c $(BATS_FILES)) tests defined in $(words $(BATS_FILES)) .bats files + # To test selectively, run: make test ONLY+=/path/to/bats/files + # To exclude certain tests: make test EXCEPT+=/path/to/bats/files + # For a list of tests, run: make test-list + @if [ $(words $(BATS_FILES)) -gt 0 ]; \ + then \ + echo "$(BATS_ROOT)/bin/bats \\"; \ + printf ' %s \\\n' $(BATS_FILES); \ + echo ' #'; \ + $(BATS_ROOT)/bin/bats $(BATS_FILES); \ + fi diff --git a/inference/dimmwitted/test/biased_coin-performance.sh b/inference/dimmwitted/test/biased_coin-performance.sh new file mode 100755 index 000000000..ecaff32d2 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin-performance.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" +. "$PWD"/helpers.bash + +cp -af biased_coin/. biased_coin-performance/ +cd biased_coin-performance +run_end_to_end.sh +echo '-l 20000 -i 20000 -s 1 --alpha 0.1 --diminish 0.995 --sample_evidence --reg_param 0' >dw-args +run_end_to_end.sh diff --git a/inference/dimmwitted/test/biased_coin.bats b/inference/dimmwitted/test/biased_coin.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/biased_coin.setup.sh b/inference/dimmwitted/test/biased_coin.setup.sh new file mode 100755 index 000000000..1ce1714a0 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin.setup.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" +dw text2bin variable biased_coin/variables.tsv biased_coin/graph.variables /dev/stderr +dw text2bin factor biased_coin/factors.tsv biased_coin/graph.factors /dev/stderr 4 1 1 +dw text2bin weight biased_coin/weights.tsv biased_coin/graph.weights /dev/stderr diff --git a/inference/dimmwitted/test/biased_coin/check_result b/inference/dimmwitted/test/biased_coin/check_result new file mode 100755 index 000000000..e73092417 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/check_result @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -eu + +# the factor graph used for test is from biased coin, which contains 18 +# variables, +# 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +# positive and id 8 negative. + +# check results + +# all weights should be around 1.0 +awk expected + eps) || (weight < expected - eps)) { + print "weight " id " value " weight " not around " expected + exit(1) + } +}' + +# all probabilities should be around 0.89 (e ^ w / (e ^ w + e ^ -w)) +awk expected + eps) || (prob < expected - eps)) { + print "var " id " prob " prob " not near " expected + exit(1) + } +}' diff --git a/inference/dimmwitted/test/biased_coin/dw-args b/inference/dimmwitted/test/biased_coin/dw-args new file mode 100644 index 000000000..b808ec077 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/dw-args @@ -0,0 +1 @@ +-l 2000 -i 2000 --alpha 0.1 --diminish 0.995 --sample_evidence --reg_param 0 diff --git a/inference/dimmwitted/test/biased_coin/factors.text2bin-args b/inference/dimmwitted/test/biased_coin/factors.text2bin-args new file mode 100644 index 000000000..fd4a5e215 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/factors.text2bin-args @@ -0,0 +1 @@ +4 1 1 diff --git a/inference/dimmwitted/test/biased_coin/factors.tsv b/inference/dimmwitted/test/biased_coin/factors.tsv new file mode 100644 index 000000000..85bd5fb16 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/factors.tsv @@ -0,0 +1,18 @@ +0 0 1 +1 0 1 +2 0 1 +3 0 1 +4 0 1 +5 0 1 +6 0 1 +7 0 1 +8 0 1 +9 0 1 +10 0 1 +11 0 1 +12 0 1 +13 0 1 +14 0 1 +15 0 1 +16 0 1 +17 0 1 diff --git a/inference/dimmwitted/test/biased_coin/graph.meta b/inference/dimmwitted/test/biased_coin/graph.meta new file mode 100644 index 000000000..72383605c --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/graph.meta @@ -0,0 +1 @@ +1,18,18,18,out/test_coin/graph.weights,out/test_coin/graph.variables,out/test_coin/graph.factors,out/test_coin/cfg_rules diff --git a/inference/dimmwitted/test/biased_coin/variables.tsv b/inference/dimmwitted/test/biased_coin/variables.tsv new file mode 100644 index 000000000..f1f628265 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/variables.tsv @@ -0,0 +1,18 @@ +0 1 1 0 2 +1 1 1 0 2 +2 1 1 0 2 +3 1 1 0 2 +4 1 1 0 2 +5 1 1 0 2 +6 1 1 0 2 +7 1 1 0 2 +8 1 0 0 2 +9 0 0 0 2 +10 0 0 0 2 +11 0 0 0 2 +12 0 0 0 2 +13 0 0 0 2 +14 0 0 0 2 +15 0 0 0 2 +16 0 0 0 2 +17 0 0 0 2 diff --git a/inference/dimmwitted/test/biased_coin/weights.tsv b/inference/dimmwitted/test/biased_coin/weights.tsv new file mode 100644 index 000000000..12f3b7225 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin/weights.tsv @@ -0,0 +1 @@ +0 0 0 diff --git a/inference/dimmwitted/test/biased_coin_continuous.bats b/inference/dimmwitted/test/biased_coin_continuous.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/biased_coin_continuous/check_result b/inference/dimmwitted/test/biased_coin_continuous/check_result new file mode 100755 index 000000000..ea39f777f --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/check_result @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -eu + +# the factor graph used for test is from biased coin, which contains 18 +# variables, +# 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +# positive and id 8 negative. However, the positive evidence has a total "value" +# of 6, same as the value for id 8. So roughly it's balanced... + +# check results + +# all weights should be around 0 +awk expected + eps) || (weight < expected - eps)) { + print "weight " id " not around " 0.0 + exit(1) + } +}' + +# all probabilities should be around 0.5 +awk expected + eps) || (prob < expected - eps)) { + print "var " id " prob not near " 0.5 + exit(1) + } +}' diff --git a/inference/dimmwitted/test/biased_coin_continuous/dw-args b/inference/dimmwitted/test/biased_coin_continuous/dw-args new file mode 100644 index 000000000..be8957c45 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/dw-args @@ -0,0 +1 @@ +-l 2000 -i 2000 --alpha 0.1 --diminish 0.995 --sample_evidence --reg_param 0 -c 1 diff --git a/inference/dimmwitted/test/biased_coin_continuous/factors.text2bin-args b/inference/dimmwitted/test/biased_coin_continuous/factors.text2bin-args new file mode 100644 index 000000000..fd4a5e215 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/factors.text2bin-args @@ -0,0 +1 @@ +4 1 1 diff --git a/inference/dimmwitted/test/biased_coin_continuous/factors.tsv b/inference/dimmwitted/test/biased_coin_continuous/factors.tsv new file mode 100644 index 000000000..0d92bba2d --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/factors.tsv @@ -0,0 +1,18 @@ +0 0 1 +1 0 1 +2 0 1 +3 0 1 +4 0 -1.5 +5 0 -0.5 +6 0 3.5 +7 0 0.5 +8 0 6 +9 0 1 +10 0 1 +11 0 1 +12 0 1 +13 0 1 +14 0 1 +15 0 1 +16 0 1 +17 0 1 diff --git a/inference/dimmwitted/test/biased_coin_continuous/graph.meta b/inference/dimmwitted/test/biased_coin_continuous/graph.meta new file mode 100644 index 000000000..72383605c --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/graph.meta @@ -0,0 +1 @@ +1,18,18,18,out/test_coin/graph.weights,out/test_coin/graph.variables,out/test_coin/graph.factors,out/test_coin/cfg_rules diff --git a/inference/dimmwitted/test/biased_coin_continuous/variables.tsv b/inference/dimmwitted/test/biased_coin_continuous/variables.tsv new file mode 100644 index 000000000..f1f628265 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/variables.tsv @@ -0,0 +1,18 @@ +0 1 1 0 2 +1 1 1 0 2 +2 1 1 0 2 +3 1 1 0 2 +4 1 1 0 2 +5 1 1 0 2 +6 1 1 0 2 +7 1 1 0 2 +8 1 0 0 2 +9 0 0 0 2 +10 0 0 0 2 +11 0 0 0 2 +12 0 0 0 2 +13 0 0 0 2 +14 0 0 0 2 +15 0 0 0 2 +16 0 0 0 2 +17 0 0 0 2 diff --git a/inference/dimmwitted/test/biased_coin_continuous/weights.tsv b/inference/dimmwitted/test/biased_coin_continuous/weights.tsv new file mode 100644 index 000000000..12f3b7225 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_continuous/weights.tsv @@ -0,0 +1 @@ +0 0 0 diff --git a/inference/dimmwitted/test/biased_coin_truthiness.bats b/inference/dimmwitted/test/biased_coin_truthiness.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/biased_coin_truthiness/check_result b/inference/dimmwitted/test/biased_coin_truthiness/check_result new file mode 100755 index 000000000..0e000b1a9 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/check_result @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -eu + +# the factor graph used for test is from biased coin, which contains 18 +# variables, +# 1 weight, 18 factors, and 18 edges. +# Variables of id 0-8 are soft evidence; +# truthiness total: 7.2 positve, 1.8 negative + +# check results + +# all weights should be around +/-0.7 +# e ^ 0.7 / e ^ -0.7 = 7.2 / 1.8 +awk expected + eps) || (weight < expected - eps)) { + print "weight " id " value " weight " not around " expected + exit(1) + } +}' + +# all probabilities should be around 0.89 +awk expected + eps) || (prob < expected - eps)) { + print "var " id " prob " prob " not near " expected + exit(1) + } + } +}' diff --git a/inference/dimmwitted/test/biased_coin_truthiness/domains.tsv b/inference/dimmwitted/test/biased_coin_truthiness/domains.tsv new file mode 100644 index 000000000..61371e232 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/domains.tsv @@ -0,0 +1,9 @@ +0 2 {0,1} {0.2,0.8} +1 2 {0,1} {0.2,0.8} +2 2 {0,1} {0.2,0.8} +3 2 {0,1} {0.2,0.8} +4 2 {0,1} {0.2,0.8} +5 2 {0,1} {0.2,0.8} +6 2 {0,1} {0.1,0.9} +7 2 {0,1} {0.45,0.55} +8 2 {0,1} {0.05,0.95} diff --git a/inference/dimmwitted/test/biased_coin_truthiness/dw-args b/inference/dimmwitted/test/biased_coin_truthiness/dw-args new file mode 100644 index 000000000..f4cc07786 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/dw-args @@ -0,0 +1 @@ +-l 2000 -i 2000 --alpha 0.01 --diminish 0.995 --sample_evidence --reg_param 0 --noise_aware diff --git a/inference/dimmwitted/test/biased_coin_truthiness/factors.text2bin-args b/inference/dimmwitted/test/biased_coin_truthiness/factors.text2bin-args new file mode 100644 index 000000000..1a38d4feb --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/factors.text2bin-args @@ -0,0 +1 @@ +12 1 1 diff --git a/inference/dimmwitted/test/biased_coin_truthiness/factors.tsv b/inference/dimmwitted/test/biased_coin_truthiness/factors.tsv new file mode 100644 index 000000000..5608cf712 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/factors.tsv @@ -0,0 +1,36 @@ +0 0 0 1 +1 0 0 1 +2 0 0 1 +3 0 0 1 +4 0 0 1 +5 0 0 1 +6 0 0 1 +7 0 0 1 +8 0 0 1 +9 0 0 1 +10 0 0 1 +11 0 0 1 +12 0 0 1 +13 0 0 1 +14 0 0 1 +15 0 0 1 +16 0 0 1 +17 0 0 1 +0 1 1 1 +1 1 1 1 +2 1 1 1 +3 1 1 1 +4 1 1 1 +5 1 1 1 +6 1 1 1 +7 1 1 1 +8 1 1 1 +9 1 1 1 +10 1 1 1 +11 1 1 1 +12 1 1 1 +13 1 1 1 +14 1 1 1 +15 1 1 1 +16 1 1 1 +17 1 1 1 diff --git a/inference/dimmwitted/test/biased_coin_truthiness/graph.meta b/inference/dimmwitted/test/biased_coin_truthiness/graph.meta new file mode 100644 index 000000000..324ab6796 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/graph.meta @@ -0,0 +1 @@ +2,18,36,36,out/test_coin/graph.weights,out/test_coin/graph.variables,out/test_coin/graph.factors,out/test_coin/cfg_rules diff --git a/inference/dimmwitted/test/biased_coin_truthiness/variables.tsv b/inference/dimmwitted/test/biased_coin_truthiness/variables.tsv new file mode 100644 index 000000000..be57b7b0e --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/variables.tsv @@ -0,0 +1,18 @@ +0 0 0 1 2 +1 0 0 1 2 +2 0 0 1 2 +3 0 0 1 2 +4 0 0 1 2 +5 0 0 1 2 +6 0 0 1 2 +7 0 0 1 2 +8 0 0 1 2 +9 0 0 1 2 +10 0 0 1 2 +11 0 0 1 2 +12 0 0 1 2 +13 0 0 1 2 +14 0 0 1 2 +15 0 0 1 2 +16 0 0 1 2 +17 0 0 1 2 diff --git a/inference/dimmwitted/test/biased_coin_truthiness/weights.tsv b/inference/dimmwitted/test/biased_coin_truthiness/weights.tsv new file mode 100644 index 000000000..1ba94e038 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_truthiness/weights.tsv @@ -0,0 +1,2 @@ +0 0 0 +1 0 0 diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial.bats b/inference/dimmwitted/test/biased_coin_with_multinomial.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/check_result b/inference/dimmwitted/test/biased_coin_with_multinomial/check_result new file mode 100755 index 000000000..c4f51d233 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/check_result @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eu + +# the factor graph used for test is from biased coin, which contains 18 +# variables, +# 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +# positive and id 8 negative. + +# check results + +# all weights should be around 2.1 +awk expected + eps) || (weight < expected - eps)) { + print "weight " id " value " weight " not around " expected + exit(1) + } +}' + +# all probabilities should be around 0.89 +awk expected + eps) || (prob < expected - eps)) { + print "var " id " prob " prob " not near " expected + exit(1) + } + } +}' diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/dw-args b/inference/dimmwitted/test/biased_coin_with_multinomial/dw-args new file mode 100644 index 000000000..19cfc0ced --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/dw-args @@ -0,0 +1 @@ +-l 2000 -i 2000 --alpha 0.01 --diminish 0.995 --sample_evidence --reg_param 0 diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/factors.text2bin-args b/inference/dimmwitted/test/biased_coin_with_multinomial/factors.text2bin-args new file mode 100644 index 000000000..1a38d4feb --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/factors.text2bin-args @@ -0,0 +1 @@ +12 1 1 diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/factors.tsv b/inference/dimmwitted/test/biased_coin_with_multinomial/factors.tsv new file mode 100644 index 000000000..5608cf712 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/factors.tsv @@ -0,0 +1,36 @@ +0 0 0 1 +1 0 0 1 +2 0 0 1 +3 0 0 1 +4 0 0 1 +5 0 0 1 +6 0 0 1 +7 0 0 1 +8 0 0 1 +9 0 0 1 +10 0 0 1 +11 0 0 1 +12 0 0 1 +13 0 0 1 +14 0 0 1 +15 0 0 1 +16 0 0 1 +17 0 0 1 +0 1 1 1 +1 1 1 1 +2 1 1 1 +3 1 1 1 +4 1 1 1 +5 1 1 1 +6 1 1 1 +7 1 1 1 +8 1 1 1 +9 1 1 1 +10 1 1 1 +11 1 1 1 +12 1 1 1 +13 1 1 1 +14 1 1 1 +15 1 1 1 +16 1 1 1 +17 1 1 1 diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/graph.meta b/inference/dimmwitted/test/biased_coin_with_multinomial/graph.meta new file mode 100644 index 000000000..324ab6796 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/graph.meta @@ -0,0 +1 @@ +2,18,36,36,out/test_coin/graph.weights,out/test_coin/graph.variables,out/test_coin/graph.factors,out/test_coin/cfg_rules diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/variables.tsv b/inference/dimmwitted/test/biased_coin_with_multinomial/variables.tsv new file mode 100644 index 000000000..b10831a04 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/variables.tsv @@ -0,0 +1,18 @@ +0 1 1 1 2 +1 1 1 1 2 +2 1 1 1 2 +3 1 1 1 2 +4 1 1 1 2 +5 1 1 1 2 +6 1 1 1 2 +7 1 1 1 2 +8 1 0 1 2 +9 0 0 1 2 +10 0 0 1 2 +11 0 0 1 2 +12 0 0 1 2 +13 0 0 1 2 +14 0 0 1 2 +15 0 0 1 2 +16 0 0 1 2 +17 0 0 1 2 diff --git a/inference/dimmwitted/test/biased_coin_with_multinomial/weights.tsv b/inference/dimmwitted/test/biased_coin_with_multinomial/weights.tsv new file mode 100644 index 000000000..1ba94e038 --- /dev/null +++ b/inference/dimmwitted/test/biased_coin_with_multinomial/weights.tsv @@ -0,0 +1,2 @@ +0 0 0 +1 0 0 diff --git a/inference/dimmwitted/test/binary_format_test.bats b/inference/dimmwitted/test/binary_format_test.bats new file mode 120000 index 000000000..b01bb55a0 --- /dev/null +++ b/inference/dimmwitted/test/binary_format_test.bats @@ -0,0 +1 @@ +.gtest.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/binary_format_test.cc b/inference/dimmwitted/test/binary_format_test.cc new file mode 100644 index 000000000..a1e87e600 --- /dev/null +++ b/inference/dimmwitted/test/binary_format_test.cc @@ -0,0 +1,79 @@ +/** + * Unit tests for binary format + * + * Author: Feiran Wang + */ + +#include "binary_format.h" +#include "dimmwitted.h" +#include "factor.h" +#include "factor_graph.h" +#include +#include + +namespace dd { + +// the factor graph used for test is from biased coin, which contains 18 +// variables, +// 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +// positive and id 8 negative. + +// test read_variables +TEST(BinaryFormatTest, read_variables) { + FactorGraph fg({18, 1, 1, 1}); + fg.load_variables({"./test/biased_coin/graph.variables"}); + EXPECT_EQ(fg.size.num_variables, 18U); + EXPECT_EQ(fg.size.num_variables_evidence, 9U); + EXPECT_EQ(fg.size.num_variables_query, 9U); + EXPECT_EQ(fg.variables[1].id, 1U); + EXPECT_EQ(fg.variables[1].domain_type, DTYPE_BOOLEAN); + EXPECT_EQ(fg.variables[1].is_evid, true); + EXPECT_EQ(fg.variables[1].cardinality, 2U); + EXPECT_EQ(fg.variables[1].assignment_dense, 1U); +} + +// test read_factors +TEST(BinaryFormatTest, read_factors) { + FactorGraph fg({18, 18, 1, 18}); + fg.load_variables({"./test/biased_coin/graph.variables"}); + fg.load_factors({"./test/biased_coin/graph.factors"}); + EXPECT_EQ(fg.size.num_factors, 18U); + EXPECT_EQ(fg.factors[0].id, 0U); + EXPECT_EQ(fg.factors[0].weight_id, 0U); + EXPECT_EQ(fg.factors[0].func_id, FUNC_ISTRUE); + EXPECT_EQ(fg.factors[0].num_vars, 1U); + EXPECT_EQ(fg.get_factor_vif_at(fg.factors[1], 0).vid, 1U); +} + +// test read_weights +TEST(BinaryFormatTest, read_weights) { + FactorGraph fg({1, 1, 1, 1}); + fg.load_weights({"./test/biased_coin/graph.weights"}); + EXPECT_EQ(fg.size.num_weights, 1U); + EXPECT_EQ(fg.weights[0].id, 0U); + EXPECT_EQ(fg.weights[0].isfixed, false); + EXPECT_EQ(fg.weights[0].weight, 0.0); +} + +// test read domains +TEST(BinaryFormatTest, read_domains) { + size_t num_variables = 3; + size_t domain_sizes[] = {1, 2, 3}; + FactorGraph fg({num_variables, 1, 1, 1}); + + // add variables + for (size_t i = 0; i < num_variables; ++i) { + fg.variables[i] = Variable(i, DTYPE_CATEGORICAL, false, domain_sizes[i], 0); + } + fg.load_domains({"./test/domains/graph.domains"}); + + for (size_t i = 0; i < num_variables; ++i) { + EXPECT_EQ(fg.variables[i].domain_map->size(), domain_sizes[i]); + } + + EXPECT_EQ(fg.variables[2].get_domain_index(1), 0U); + EXPECT_EQ(fg.variables[2].get_domain_index(3), 1U); + EXPECT_EQ(fg.variables[2].get_domain_index(5), 2U); +} + +} // namespace dd diff --git a/inference/dimmwitted/test/binary_format_test.setup.sh b/inference/dimmwitted/test/binary_format_test.setup.sh new file mode 100755 index 000000000..30a3023cb --- /dev/null +++ b/inference/dimmwitted/test/binary_format_test.setup.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" +dw text2bin variable biased_coin/variables.tsv biased_coin/graph.variables /dev/stderr +dw text2bin factor biased_coin/factors.tsv biased_coin/graph.factors /dev/stderr 4 1 1 +dw text2bin weight biased_coin/weights.tsv biased_coin/graph.weights /dev/stderr +dw text2bin domain domains/domains.tsv domains/graph.domains /dev/stderr diff --git a/inference/dimmwitted/test/domains/domains.tsv b/inference/dimmwitted/test/domains/domains.tsv new file mode 100644 index 000000000..8d6081bb4 --- /dev/null +++ b/inference/dimmwitted/test/domains/domains.tsv @@ -0,0 +1,3 @@ +0 1 {1} {0} +1 2 {0,3} {0,0} +2 3 {1,3,5} {0,0,0} diff --git a/inference/dimmwitted/test/factor_graph_test.bats b/inference/dimmwitted/test/factor_graph_test.bats new file mode 120000 index 000000000..b01bb55a0 --- /dev/null +++ b/inference/dimmwitted/test/factor_graph_test.bats @@ -0,0 +1 @@ +.gtest.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/factor_graph_test.cc b/inference/dimmwitted/test/factor_graph_test.cc new file mode 100644 index 000000000..216ce2306 --- /dev/null +++ b/inference/dimmwitted/test/factor_graph_test.cc @@ -0,0 +1,68 @@ +/** + * Unit tests for factor graph functions + * + * Author: Feiran Wang + */ + +#include "dimmwitted.h" +#include "factor_graph.h" +#include +#include + +namespace dd { + +// test fixture +// the factor graph used for test is from biased coin, which contains 18 +// variables, +// 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +// positive and id 8 negative. +class FactorGraphTest : public testing::Test { + protected: + std::unique_ptr cfg; + std::unique_ptr infrs; + std::unique_ptr cmd_parser; + + virtual void SetUp() { + const char* argv[] = { + "dw", "gibbs", + "-w", "./test/biased_coin/graph.weights", + "-v", "./test/biased_coin/graph.variables", + "-f", "./test/biased_coin/graph.factors", + "-m", "./test/biased_coin/graph.meta", + "-o", ".", + "-l", "100", + "-i", "100", + "--alpha", "0.1", + "--reg_param", "0", + }; + cmd_parser.reset(new CmdParser(sizeof(argv) / sizeof(*argv), argv)); + + FactorGraph fg({18, 18, 1, 18}); + fg.load_variables(cmd_parser->variable_file); + fg.load_weights(cmd_parser->weight_file); + fg.load_domains(cmd_parser->domain_file); + fg.load_factors(cmd_parser->factor_file); + fg.safety_check(); + fg.construct_index(); + + cfg.reset(new FactorGraph(fg)); + infrs.reset(new InferenceResult(*cfg, fg.weights.get(), *cmd_parser)); + } +}; + +// test sgd_on_variable function +TEST_F(FactorGraphTest, sgd_on_variable) { + infrs->assignments_free[cfg->variables[0].id] = 0; + + cfg->sgd_on_variable(cfg->variables[0], *infrs, 0.1, false); + std::cout << "The weight value is: " << infrs->weight_values[0] << std::endl; + EXPECT_EQ(infrs->weight_values[0], 0.2); + + cfg->sgd_on_variable(cfg->variables[10], *infrs, 0.1, false); + EXPECT_EQ(infrs->weight_values[0], 0.2); + + cfg->sgd_on_variable(cfg->variables[10], *infrs, 0.1, false); + EXPECT_EQ(infrs->weight_values[0], 0.2); +} + +} // namespace dd diff --git a/inference/dimmwitted/test/factor_graph_test.setup.sh b/inference/dimmwitted/test/factor_graph_test.setup.sh new file mode 120000 index 000000000..44af0475c --- /dev/null +++ b/inference/dimmwitted/test/factor_graph_test.setup.sh @@ -0,0 +1 @@ +biased_coin.setup.sh \ No newline at end of file diff --git a/inference/dimmwitted/test/factor_test.bats b/inference/dimmwitted/test/factor_test.bats new file mode 120000 index 000000000..b01bb55a0 --- /dev/null +++ b/inference/dimmwitted/test/factor_test.bats @@ -0,0 +1 @@ +.gtest.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/factor_test.cc b/inference/dimmwitted/test/factor_test.cc new file mode 100644 index 000000000..0bb93d3e4 --- /dev/null +++ b/inference/dimmwitted/test/factor_test.cc @@ -0,0 +1,239 @@ +#include "factor.h" +#include "factor_graph.h" +#include +#include +#include + +#define EQ_TOL 0.00001 + +namespace dd { + +TEST(FactorTest, ONE_VAR_FACTORS) { + FactorToVariable vifs[1]; + size_t values[1]; + size_t vid; + size_t propose; + + vifs[0].vid = 0; + vifs[0].dense_equal_to = 1; + + Factor f; + f.num_vars = 1; + f.vif_base = 0; + + values[0] = 0; + vid = 0; + + // CASE 1: True + propose = 1; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + + // CASE 2: False + propose = 0; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + -1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 0.0, + EQ_TOL); +} + +TEST(FactorTest, TWO_VAR_FACTORS) { + FactorToVariable vifs[2]; + size_t values[2]; + size_t vid; + size_t propose; + + vifs[0].vid = 0; + vifs[0].dense_equal_to = 1; + + vifs[1].vid = 1; + vifs[1].dense_equal_to = 1; + + Factor f; + f.num_vars = 2; + f.vif_base = 0; + + // CASE 1: True op True + values[0] = 1; + values[1] = 1; + vid = 1; + propose = 1; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + + // CASE 2: True op False + values[0] = 1; + values[1] = 1; + vid = 1; + propose = 0; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + -1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 0.0, + EQ_TOL); + + // CASE 3: False op True + values[0] = 0; + values[1] = 0; + vid = 1; + propose = 1; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 0.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + + // CASE 4: False op False + values[0] = 0; + values[1] = 0; + vid = 1; + propose = 0; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_AND)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_OR)(vifs, values, vid, propose), -1, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_EQUAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 0.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); +} + +TEST(FactorTest, THREE_VAR_IMPLY) { + FactorToVariable vifs[3]; + size_t values[3]; + size_t vid; + size_t propose; + + // first test case: True /\ x => True, x propose to False, Expect 0 + vifs[0].vid = 0; + vifs[0].dense_equal_to = 1; + + vifs[1].vid = 1; + vifs[1].dense_equal_to = 1; + + vifs[2].vid = 2; + vifs[2].dense_equal_to = 1; + + values[0] = 1; + values[1] = 1; + values[2] = 1; + + vid = 1; + propose = 0; + + Factor f; + f.num_vars = 3; + f.vif_base = 0; + + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 0.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_MLN)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 2.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), + log2(3.0), EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + + // second test case: True /\ x => True, x propose to True, Expect 1 + propose = 1; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_MLN)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 2.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), + log2(3.0), EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); + + // third test case: True /\ True => x, x propose to False, Expect -1 + vid = 2; + propose = 0; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + -1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_MLN)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), 0.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 0.0, + EQ_TOL); + + // forth test case: True /\ True => x, x propose to True, Expect 1 + vid = 2; + propose = 1; + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_NATURAL)(vifs, values, vid, propose), + 1.0, EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_IMPLY_MLN)(vifs, values, vid, propose), 1.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LINEAR)(vifs, values, vid, propose), 2.0, + EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_RATIO)(vifs, values, vid, propose), + log2(3.0), EQ_TOL); + EXPECT_NEAR(f.POTENTIAL_SIGN(FUNC_LOGICAL)(vifs, values, vid, propose), 1.0, + EQ_TOL); +} + +} // namespace dd diff --git a/inference/dimmwitted/test/helpers.bash b/inference/dimmwitted/test/helpers.bash new file mode 100644 index 000000000..52b48ec92 --- /dev/null +++ b/inference/dimmwitted/test/helpers.bash @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# put source tree root on PATH +PATH="${BASH_SOURCE%/test/helpers.bash}:${BASH_SOURCE%/helpers.bash}:$PATH" + +compare_binary_with_xxd_text() { + local expected_xxd_txt=$1 + local actual_bin=$2 + if ! cmp <(xxd -r "$expected_xxd_txt") "$actual_bin"; then + diff -u "$expected_xxd_txt" <(xxd "$actual_bin") + false + fi +} diff --git a/inference/dimmwitted/test/loading_test.bats b/inference/dimmwitted/test/loading_test.bats new file mode 120000 index 000000000..b01bb55a0 --- /dev/null +++ b/inference/dimmwitted/test/loading_test.bats @@ -0,0 +1 @@ +.gtest.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/loading_test.cc b/inference/dimmwitted/test/loading_test.cc new file mode 100644 index 000000000..02dc727bb --- /dev/null +++ b/inference/dimmwitted/test/loading_test.cc @@ -0,0 +1,65 @@ +/** + * Unit tests for loading factor graphs + * + * Author: Feiran Wang + */ + +#include "dimmwitted.h" +#include "factor_graph.h" +#include +#include + +namespace dd { + +// test fixture +// the factor graph used for test is from biased coin, which contains 18 +// variables, +// 1 weight, 18 factors, and 18 edges. Variables of id 0-8 are evidence: id 0-7 +// positive and id 8 negative. +class LoadingTest : public testing::Test { + protected: + FactorGraph fg; + + LoadingTest() : fg(FactorGraph({18, 18, 1, 18})) {} + + virtual void SetUp() { + const char *argv[] = { + "dw", "gibbs", + "-w", "./test/biased_coin/graph.weights", + "-v", "./test/biased_coin/graph.variables", + "-f", "./test/biased_coin/graph.factors", + "-m", "./test/biased_coin/graph.meta", + "-o", ".", + "-l", "100", + "-i", "100", + "--alpha", "0.1", + }; + CmdParser cmd_parser(sizeof(argv) / sizeof(*argv), argv); + fg.load_variables(cmd_parser.variable_file); + fg.load_weights(cmd_parser.weight_file); + fg.load_domains(cmd_parser.domain_file); + fg.load_factors(cmd_parser.factor_file); + fg.safety_check(); + fg.construct_index(); + } +}; + +// test for loading a factor graph +TEST_F(LoadingTest, load_factor_graph) { + EXPECT_EQ(fg.size.num_variables, 18U); + EXPECT_EQ(fg.size.num_variables_evidence, 9U); + EXPECT_EQ(fg.size.num_variables_query, 9U); + EXPECT_EQ(fg.size.num_factors, 18U); + EXPECT_EQ(fg.size.num_weights, 1U); +} + +// test for FactorGraph::copy_from function +TEST_F(LoadingTest, copy_from) { + FactorGraph cfg(fg); + + FactorGraph cfg2 = cfg; + + EXPECT_TRUE(memcmp(&cfg, &cfg2, sizeof(cfg))); +} + +} // namespace dd diff --git a/inference/dimmwitted/test/loading_test.setup.sh b/inference/dimmwitted/test/loading_test.setup.sh new file mode 120000 index 000000000..44af0475c --- /dev/null +++ b/inference/dimmwitted/test/loading_test.setup.sh @@ -0,0 +1 @@ +biased_coin.setup.sh \ No newline at end of file diff --git a/inference/dimmwitted/test/partial_observation.bats b/inference/dimmwitted/test/partial_observation.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/partial_observation/check_result b/inference/dimmwitted/test/partial_observation/check_result new file mode 100755 index 000000000..6fce6ad50 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/check_result @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -eu + +# The factor graph contains a chain A-B-C, where A, C are evidence, and B +# is partially observed. The factor between A, B and B, C is equal, +# respectively. There are 4 training examples with A = C = true, and only +# one B is observed to be true. We expect the weight to be positive, and +# probabilities to be > 0.9. + +# check results + +# all weights should be positive +awk 0)) { + print "weight " id " <= " 0 + exit(1) + } +}' + +# all probabilities should be > 0.9 +awk 0.9)) { + print "var " id " has prob <= " 0.9 + exit(1) + } +}' diff --git a/inference/dimmwitted/test/partial_observation/dw-args b/inference/dimmwitted/test/partial_observation/dw-args new file mode 100644 index 000000000..a6ec3b711 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/dw-args @@ -0,0 +1 @@ +-l 500 -i 500 --alpha 0.1 --reg_param 0 diff --git a/inference/dimmwitted/test/partial_observation/factors.text2bin-args b/inference/dimmwitted/test/partial_observation/factors.text2bin-args new file mode 100644 index 000000000..d97745db1 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/factors.text2bin-args @@ -0,0 +1 @@ +3 2 1 1 diff --git a/inference/dimmwitted/test/partial_observation/factors.tsv b/inference/dimmwitted/test/partial_observation/factors.tsv new file mode 100644 index 000000000..8b76d5d63 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/factors.tsv @@ -0,0 +1,8 @@ +0 4 0 1 +4 8 1 1 +1 5 0 1 +5 9 1 1 +2 6 0 1 +6 10 1 1 +3 7 0 1 +7 11 1 1 diff --git a/inference/dimmwitted/test/partial_observation/graph.meta b/inference/dimmwitted/test/partial_observation/graph.meta new file mode 100644 index 000000000..f0f3eb1f6 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/graph.meta @@ -0,0 +1 @@ +2,12,8,16,,, \ No newline at end of file diff --git a/inference/dimmwitted/test/partial_observation/variables.tsv b/inference/dimmwitted/test/partial_observation/variables.tsv new file mode 100644 index 000000000..38002acf8 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/variables.tsv @@ -0,0 +1,12 @@ +0 1 1 0 2 +1 1 1 0 2 +2 1 1 0 2 +3 1 1 0 2 +4 1 1 0 2 +5 0 0 0 2 +6 0 0 0 2 +7 0 0 0 2 +8 1 1 0 2 +9 1 1 0 2 +10 1 1 0 2 +11 1 1 0 2 diff --git a/inference/dimmwitted/test/partial_observation/weights.tsv b/inference/dimmwitted/test/partial_observation/weights.tsv new file mode 100644 index 000000000..1ba94e038 --- /dev/null +++ b/inference/dimmwitted/test/partial_observation/weights.tsv @@ -0,0 +1,2 @@ +0 0 0 +1 0 0 diff --git a/inference/dimmwitted/test/run_end_to_end.sh b/inference/dimmwitted/test/run_end_to_end.sh new file mode 100755 index 000000000..a1a43ad80 --- /dev/null +++ b/inference/dimmwitted/test/run_end_to_end.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# run_end_to_end.sh -- Runs dw on a factor graph specified as a set of TSV files +set -euo pipefail + +# convert the factor graph from tsv to binary format +for what in variable domain factor weight; do + for tsv in "$what"s*.tsv; do + [[ -e "$tsv" ]] || continue + dw text2bin "$what" "$tsv" graph."${tsv%.tsv}" /dev/stderr $( + # use extra text2bin args if specified + ! [[ -e "${tsv%.tsv}".text2bin-args ]] || cat "${tsv%.tsv}".text2bin-args + ) + done +done + +# run sampler +dw gibbs \ + -w <(cat 2>/dev/null graph.weights*) \ + -v <(cat 2>/dev/null graph.variables*) \ + -f <(cat 2>/dev/null graph.factors*) \ + -m graph.meta \ + -o . \ + --domains <(cat 2>/dev/null graph.domains*) \ + --quiet $(cat dw-args) diff --git a/inference/dimmwitted/test/sampler_test.bats b/inference/dimmwitted/test/sampler_test.bats new file mode 120000 index 000000000..b01bb55a0 --- /dev/null +++ b/inference/dimmwitted/test/sampler_test.bats @@ -0,0 +1 @@ +.gtest.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/sampler_test.cc b/inference/dimmwitted/test/sampler_test.cc new file mode 100644 index 000000000..b56fd10be --- /dev/null +++ b/inference/dimmwitted/test/sampler_test.cc @@ -0,0 +1,88 @@ +/** + * Unit tests for gibbs sampling + * + * Author: Feiran Wang + */ + +#include "dimmwitted.h" +#include "factor_graph.h" +#include "gibbs_sampler.h" +#include +#include + +namespace dd { + +// test fixture +class SamplerTest : public testing::Test { + protected: + std::unique_ptr cfg; + std::unique_ptr infrs; + std::unique_ptr sampler; + std::unique_ptr cmd_parser; + + virtual void SetUp() { + const char *argv[] = { + "dw", "gibbs", + "-w", "./test/biased_coin/graph.weights", + "-v", "./test/biased_coin/graph.variables", + "-f", "./test/biased_coin/graph.factors", + "-m", "./test/biased_coin/graph.meta", + "-o", ".", + "-l", "100", + "-i", "100", + "--alpha", "0.1", + "--reg_param", "0", + }; + cmd_parser.reset(new CmdParser(sizeof(argv) / sizeof(*argv), argv)); + + FactorGraph fg({18, 18, 1, 18}); + fg.load_variables(cmd_parser->variable_file); + fg.load_weights(cmd_parser->weight_file); + fg.load_domains(cmd_parser->domain_file); + fg.load_factors(cmd_parser->factor_file); + fg.safety_check(); + fg.construct_index(); + + cfg.reset(new FactorGraph(fg)); + infrs.reset(new InferenceResult(*cfg, fg.weights.get(), *cmd_parser)); + sampler.reset(new GibbsSamplerThread(*cfg, *infrs, 0, 1, *cmd_parser)); + } +}; + +// test for sample_sgd_single_variable +// the pseudo random number has been precalculated... +TEST_F(SamplerTest, sample_sgd_single_variable) { + infrs->assignments_free[cfg->variables[0].id] = 1; + sampler->set_random_seed(1, 1, 1); + + sampler->sample_sgd_single_variable(0, 0.1); + EXPECT_EQ(infrs->weight_values[0], 0.2); + + sampler->sample_sgd_single_variable(0, 0.1); + EXPECT_EQ(infrs->weight_values[0], 0.2); + + sampler->sample_sgd_single_variable(0, 0.1); + EXPECT_EQ(infrs->weight_values[0], 0.2); +} + +// test for sample_single_variable +TEST_F(SamplerTest, sample_single_variable) { + infrs->assignments_free[cfg->variables[0].id] = 1; + sampler->set_random_seed(1, 1, 1); + infrs->weight_values[0] = 2; + + sampler->sample_single_variable(0); + EXPECT_EQ(infrs->assignments_evid[0], 1U); + + sampler->sample_single_variable(10U); + EXPECT_EQ(infrs->assignments_evid[10], 1U); + + infrs->weight_values[0] = 20; + sampler->sample_single_variable(11U); + EXPECT_EQ(infrs->assignments_evid[11], 1U); + + sampler->sample_single_variable(12U); + EXPECT_EQ(infrs->assignments_evid[12], 1U); +} + +} // namespace dd diff --git a/inference/dimmwitted/test/sparse_domains.bats b/inference/dimmwitted/test/sparse_domains.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/sparse_domains/check_result b/inference/dimmwitted/test/sparse_domains/check_result new file mode 100755 index 000000000..874bca981 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/check_result @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -eu + +# the factor graph used for test is from biased categorical, which contains 20 +# variables, +# Variable has cardinality of 4. Evidence variables: 0: 1, 1: 2, 2: 3, 3: 4 +# where the first one is value, the second is count +# all variables and factors are in sparse domain format + +# check results + +# exponenents of weights should have ratio like 1:2:3:4 +# weight 0 is fixed at 0 +awk expected+eps) || (weight < expected-eps)) { + print "weight " id " not near " expected + exit(1) + } + } +}' + +awk expected + eps) || (prob < expected - eps)) { + print "var " id " category " e " prob is " prob ", not near " expected + exit(1) + } +}' diff --git a/inference/dimmwitted/test/sparse_domains/domains.tsv b/inference/dimmwitted/test/sparse_domains/domains.tsv new file mode 100644 index 000000000..b4b1b8b6c --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/domains.tsv @@ -0,0 +1,6 @@ +14 3 {0,1,3} {0,0,0} +15 1 {1} {0} +16 2 {1,3} {0.0,0} +17 1 {1} {0} +18 3 {0,1,3} {0,0,0} +19 2 {1,3} {0,0} diff --git a/inference/dimmwitted/test/sparse_domains/dw-args b/inference/dimmwitted/test/sparse_domains/dw-args new file mode 100644 index 000000000..66eb62abb --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/dw-args @@ -0,0 +1 @@ +-l 4000 -i 2000 --alpha 0.01 --diminish 0.999 --reg_param 0 diff --git a/inference/dimmwitted/test/sparse_domains/factors.text2bin-args b/inference/dimmwitted/test/sparse_domains/factors.text2bin-args new file mode 100644 index 000000000..11f2ce964 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/factors.text2bin-args @@ -0,0 +1 @@ +12 1 1 diff --git a/inference/dimmwitted/test/sparse_domains/factors.tsv b/inference/dimmwitted/test/sparse_domains/factors.tsv new file mode 100644 index 000000000..5a53e7d14 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/factors.tsv @@ -0,0 +1,62 @@ +0 0 0 1 +1 0 0 1 +2 0 0 1 +3 0 0 1 +4 0 0 1 +5 0 0 1 +6 0 0 1 +7 0 0 1 +8 0 0 1 +9 0 0 1 +10 0 0 1 +11 0 0 1 +12 0 0 1 +13 0 0 1 +0 1 1 1 +1 1 1 1 +2 1 1 1 +3 1 1 1 +4 1 1 1 +5 1 1 1 +6 1 1 1 +7 1 1 1 +8 1 1 1 +9 1 1 1 +10 1 1 1 +11 1 1 1 +12 1 1 1 +13 1 1 1 +0 2 2 1 +1 2 2 1 +2 2 2 1 +3 2 2 1 +4 2 2 1 +5 2 2 1 +6 2 2 1 +7 2 2 1 +8 2 2 1 +9 2 2 1 +10 2 2 1 +11 2 2 1 +12 2 2 1 +13 2 2 1 +0 3 3 1 +1 3 3 1 +2 3 3 1 +3 3 3 1 +4 3 3 1 +5 3 3 1 +6 3 3 1 +7 3 3 1 +8 3 3 1 +9 3 3 1 +10 3 3 1 +11 3 3 1 +12 3 3 1 +13 3 3 1 +17 1 1 1 +18 0 0 1 +18 1 1 1 +18 3 3 1 +19 1 1 1 +19 3 3 1 diff --git a/inference/dimmwitted/test/sparse_domains/factors2.text2bin-args b/inference/dimmwitted/test/sparse_domains/factors2.text2bin-args new file mode 100644 index 000000000..1b572dbbb --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/factors2.text2bin-args @@ -0,0 +1 @@ +12 2 1 1 diff --git a/inference/dimmwitted/test/sparse_domains/factors2.tsv b/inference/dimmwitted/test/sparse_domains/factors2.tsv new file mode 100644 index 000000000..9ac01d299 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/factors2.tsv @@ -0,0 +1,5 @@ +14 15 0 1 4 1 +14 15 1 1 5 1 +14 15 3 1 7 1 +15 16 1 1 5 1 +15 16 1 3 6 1 diff --git a/inference/dimmwitted/test/sparse_domains/graph.meta b/inference/dimmwitted/test/sparse_domains/graph.meta new file mode 100644 index 000000000..3467ec1ec --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/graph.meta @@ -0,0 +1 @@ +8,20,67,72,graph.weights,graph.variables,graph.factors diff --git a/inference/dimmwitted/test/sparse_domains/variables.tsv b/inference/dimmwitted/test/sparse_domains/variables.tsv new file mode 100644 index 000000000..c31df00cf --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/variables.tsv @@ -0,0 +1,20 @@ +0 1 0 1 4 +1 1 1 1 4 +2 1 1 1 4 +3 1 2 1 4 +4 1 2 1 4 +5 1 2 1 4 +6 1 3 1 4 +7 1 3 1 4 +8 1 3 1 4 +9 1 3 1 4 +10 0 0 1 4 +11 0 0 1 4 +12 0 0 1 4 +13 0 0 1 4 +14 0 0 1 3 +15 0 0 1 1 +16 0 0 1 2 +17 0 0 1 1 +18 0 0 1 3 +19 0 0 1 2 diff --git a/inference/dimmwitted/test/sparse_domains/weights.tsv b/inference/dimmwitted/test/sparse_domains/weights.tsv new file mode 100644 index 000000000..fe05de863 --- /dev/null +++ b/inference/dimmwitted/test/sparse_domains/weights.tsv @@ -0,0 +1,8 @@ +0 1 0 +1 0 0 +2 0 0 +3 0 0 +4 1 2.0 +5 1 3.0 +6 1 2.0 +7 1 0 diff --git a/inference/dimmwitted/test/sparse_multinomial2.bats b/inference/dimmwitted/test/sparse_multinomial2.bats new file mode 120000 index 000000000..cdcd927c6 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2.bats @@ -0,0 +1 @@ +.end_to_end_test.bats.template \ No newline at end of file diff --git a/inference/dimmwitted/test/sparse_multinomial2/check_result b/inference/dimmwitted/test/sparse_multinomial2/check_result new file mode 100755 index 000000000..3c561fbbf --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/check_result @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -eu + +awk expected + eps) || (prob < expected - eps)) { + print "var " vid " category " category " prob is " prob ", not near " expected + exit(1) + } +}' diff --git a/inference/dimmwitted/test/sparse_multinomial2/domains.tsv b/inference/dimmwitted/test/sparse_multinomial2/domains.tsv new file mode 100644 index 000000000..eba587aa0 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/domains.tsv @@ -0,0 +1,3 @@ +0 3 {0,1,2} {0,0,0} +1 3 {1,2,3} {0,0,0} +2 2 {1,3} {0,0} diff --git a/inference/dimmwitted/test/sparse_multinomial2/dw-args b/inference/dimmwitted/test/sparse_multinomial2/dw-args new file mode 100644 index 000000000..b7e578298 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/dw-args @@ -0,0 +1 @@ +-l 0 -i 1000 --alpha 0.1 --sample_evidence diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors1.text2bin-args b/inference/dimmwitted/test/sparse_multinomial2/factors1.text2bin-args new file mode 100644 index 000000000..11f2ce964 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors1.text2bin-args @@ -0,0 +1 @@ +12 1 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors1.tsv b/inference/dimmwitted/test/sparse_multinomial2/factors1.tsv new file mode 100644 index 000000000..3d6d3fa0f --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors1.tsv @@ -0,0 +1,8 @@ +0 0 18 1 +0 1 19 1 +0 2 20 1 +1 1 19 1 +1 2 20 1 +1 3 21 1 +2 1 22 1 +2 3 23 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors2.text2bin-args b/inference/dimmwitted/test/sparse_multinomial2/factors2.text2bin-args new file mode 100644 index 000000000..1b572dbbb --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors2.text2bin-args @@ -0,0 +1 @@ +12 2 1 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors2.tsv b/inference/dimmwitted/test/sparse_multinomial2/factors2.tsv new file mode 100644 index 000000000..10bd84f6c --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors2.tsv @@ -0,0 +1,9 @@ +0 1 0 1 9 1 +0 1 0 2 10 1 +0 1 0 3 11 1 +0 1 1 1 12 1 +0 1 1 2 13 1 +0 1 1 3 14 1 +0 1 2 1 15 1 +0 1 2 2 16 1 +0 1 2 3 17 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors2a.text2bin-args b/inference/dimmwitted/test/sparse_multinomial2/factors2a.text2bin-args new file mode 100644 index 000000000..1b572dbbb --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors2a.text2bin-args @@ -0,0 +1 @@ +12 2 1 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/factors2a.tsv b/inference/dimmwitted/test/sparse_multinomial2/factors2a.tsv new file mode 100644 index 000000000..02aa222ae --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/factors2a.tsv @@ -0,0 +1,15 @@ +0 1 0 1 0 1 +0 1 0 2 1 1 +0 1 0 3 2 1 +0 1 1 1 3 1 +0 1 1 2 4 1 +0 1 1 3 5 1 +0 1 2 1 6 1 +0 1 2 2 7 1 +0 1 2 3 8 1 +0 1 0 1 0 1 +0 1 1 1 2 1 +0 1 1 2 4 1 +0 1 2 1 6 1 +0 1 2 2 7 1 +0 1 2 3 8 1 diff --git a/inference/dimmwitted/test/sparse_multinomial2/graph.meta b/inference/dimmwitted/test/sparse_multinomial2/graph.meta new file mode 100644 index 000000000..0c5e17789 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/graph.meta @@ -0,0 +1 @@ +24,3,32,56,graph.weights,graph.variables,graph.factors diff --git a/inference/dimmwitted/test/sparse_multinomial2/variables.tsv b/inference/dimmwitted/test/sparse_multinomial2/variables.tsv new file mode 100644 index 000000000..1a8a3b8d0 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/variables.tsv @@ -0,0 +1,3 @@ +0 0 0 1 3 +1 0 0 1 3 +2 0 0 1 2 diff --git a/inference/dimmwitted/test/sparse_multinomial2/weights1.tsv b/inference/dimmwitted/test/sparse_multinomial2/weights1.tsv new file mode 100644 index 000000000..d4f2ac753 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/weights1.tsv @@ -0,0 +1,6 @@ +18 1 0 +19 1 0 +20 1 0 +21 1 0 +22 1 1 +23 1 4 diff --git a/inference/dimmwitted/test/sparse_multinomial2/weights2.tsv b/inference/dimmwitted/test/sparse_multinomial2/weights2.tsv new file mode 100644 index 000000000..e4d065063 --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/weights2.tsv @@ -0,0 +1,9 @@ +9 1 0 +10 1 0 +11 1 0 +12 1 10 +13 1 0 +14 1 0 +15 1 0 +16 1 0 +17 1 0 diff --git a/inference/dimmwitted/test/sparse_multinomial2/weights2a.tsv b/inference/dimmwitted/test/sparse_multinomial2/weights2a.tsv new file mode 100644 index 000000000..f8a9666da --- /dev/null +++ b/inference/dimmwitted/test/sparse_multinomial2/weights2a.tsv @@ -0,0 +1,9 @@ +0 1 0 +1 1 0 +2 1 0 +3 1 0 +4 1 0 +5 1 0 +6 1 0 +7 1 0 +8 1 0 diff --git a/inference/dimmwitted/test/test_main.cc b/inference/dimmwitted/test/test_main.cc new file mode 100644 index 000000000..d8579e20b --- /dev/null +++ b/inference/dimmwitted/test/test_main.cc @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/inference/dimmwitted/test/text2bin.bats b/inference/dimmwitted/test/text2bin.bats new file mode 100755 index 000000000..a226d721c --- /dev/null +++ b/inference/dimmwitted/test/text2bin.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +load helpers + +setup() { + cd "${BATS_TEST_FILENAME%.bats}" +} +teardown() { + : no-op +} + +@test "text2bin variable works" { + rm -f ./dd_variables.bin + dw text2bin variable ./dd_variables.txt ./dd_variables.bin /dev/stderr + compare_binary_with_xxd_text ./dd_variables.bin.txt ./dd_variables.bin +} + +@test "text2bin factor works" { + rm -f ./dd_factors.bin + dw text2bin factor ./dd_factors.txt ./dd_factors.bin /dev/stderr 2 1 1 + compare_binary_with_xxd_text ./dd_factors.bin.txt ./dd_factors.bin +} + +@test "text2bin weight works" { + rm -f ./dd_weights.bin + dw text2bin weight ./dd_weights.txt ./dd_weights.bin /dev/stderr + compare_binary_with_xxd_text ./dd_weights.bin.txt ./dd_weights.bin +} diff --git a/inference/dimmwitted/test/text2bin/dd_factors.bin.txt b/inference/dimmwitted/test/text2bin/dd_factors.bin.txt new file mode 100644 index 000000000..97e4e6f36 --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_factors.bin.txt @@ -0,0 +1,11 @@ +0000000: 0002 0000 0000 0000 0001 0000 0000 0000 ................ +0000010: 0000 0000 0000 0000 0001 0000 0000 0000 ................ +0000020: 0000 0000 0000 0000 0000 0002 0000 0000 ................ +0000030: 0000 0001 0000 0000 0000 0000 0000 0000 ................ +0000040: 0000 0001 0000 0000 0000 0000 3ff0 0000 ............?... +0000050: 0000 0000 0002 0000 0000 0000 0001 0000 ................ +0000060: 0000 0000 0000 0000 0000 0000 0001 0000 ................ +0000070: 0000 0000 0000 4000 0000 0000 0000 0002 ......@......... +0000080: 0000 0000 0000 0001 0000 0000 0000 0000 ................ +0000090: 0000 0000 0000 0001 0000 0000 0000 0000 ................ +00000a0: 4008 0000 0000 0000 @....... diff --git a/inference/dimmwitted/test/text2bin/dd_factors.txt b/inference/dimmwitted/test/text2bin/dd_factors.txt new file mode 100644 index 000000000..79a07c2d1 --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_factors.txt @@ -0,0 +1,4 @@ +0 {0} 0 +0 {1,2} 1 +0 {3,4,5} 2 +0 {6,7,8,9} 3 diff --git a/inference/dimmwitted/test/text2bin/dd_variables.bin.txt b/inference/dimmwitted/test/text2bin/dd_variables.bin.txt new file mode 100644 index 000000000..1dcd47759 --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_variables.bin.txt @@ -0,0 +1,17 @@ +0000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0000010: 0000 0000 0000 0000 0000 0200 0000 0000 ................ +0000020: 0000 0100 0000 0000 0000 0000 0000 0000 ................ +0000030: 0000 0000 0002 0000 0000 0000 0002 0000 ................ +0000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0000050: 0200 0000 0000 0000 0300 0000 0000 0000 ................ +0000060: 0000 0000 0000 0000 0000 0002 0000 0000 ................ +0000070: 0000 0004 0000 0000 0000 0000 0000 0000 ................ +0000080: 0000 0000 0000 0200 0000 0000 0000 0500 ................ +0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000a0: 0002 0000 0000 0000 0006 0000 0000 0000 ................ +00000b0: 0000 0000 0000 0000 0000 0000 0200 0000 ................ +00000c0: 0000 0000 0700 0000 0000 0000 0000 0000 ................ +00000d0: 0000 0000 0000 0002 0000 0000 0000 0008 ................ +00000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000f0: 0000 0200 0000 0000 0000 0900 0000 0000 ................ +0000100: 0000 0000 0000 0000 0000 0000 0002 .............. diff --git a/inference/dimmwitted/test/text2bin/dd_variables.txt b/inference/dimmwitted/test/text2bin/dd_variables.txt new file mode 100644 index 000000000..ddd09b04a --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_variables.txt @@ -0,0 +1,10 @@ +0 0 0 0 2 +1 0 0 0 2 +2 0 0 0 2 +3 0 0 0 2 +4 0 0 0 2 +5 0 0 0 2 +6 0 0 0 2 +7 0 0 0 2 +8 0 0 0 2 +9 0 0 0 2 diff --git a/inference/dimmwitted/test/text2bin/dd_weights.bin.txt b/inference/dimmwitted/test/text2bin/dd_weights.bin.txt new file mode 100644 index 000000000..170a4259e --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_weights.bin.txt @@ -0,0 +1,2 @@ +00000000: 0000 0000 0000 0000 0140 0800 0000 0000 .........@...... +00000010: 00 . diff --git a/inference/dimmwitted/test/text2bin/dd_weights.txt b/inference/dimmwitted/test/text2bin/dd_weights.txt new file mode 100644 index 000000000..40b2f3e45 --- /dev/null +++ b/inference/dimmwitted/test/text2bin/dd_weights.txt @@ -0,0 +1 @@ +0 1 3