Initial commit of libDAI-0.2.1
authorJoris Mooij <jorism@marvin.jorismooij.nl>
Sat, 23 Aug 2008 10:52:23 +0000 (12:52 +0200)
committerJoris Mooij <jorism@marvin.jorismooij.nl>
Sat, 23 Aug 2008 10:52:23 +0000 (12:52 +0200)
76 files changed:
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
FILEFORMAT [new file with mode: 0644]
Makefile [new file with mode: 0644]
README
TODO [new file with mode: 0644]
alldai.cpp [new file with mode: 0644]
alldai.h [new file with mode: 0644]
bipgraph.h [new file with mode: 0644]
bp.cpp [new file with mode: 0644]
bp.h [new file with mode: 0644]
clustergraph.cpp [new file with mode: 0644]
clustergraph.h [new file with mode: 0644]
daialg.cpp [new file with mode: 0644]
daialg.h [new file with mode: 0644]
diffs.h [new file with mode: 0644]
doxygen.conf [new file with mode: 0644]
enum.h [new file with mode: 0644]
example.cpp [new file with mode: 0644]
factor.h [new file with mode: 0644]
factorgraph.cpp [new file with mode: 0644]
factorgraph.h [new file with mode: 0644]
hak.cpp [new file with mode: 0644]
hak.h [new file with mode: 0644]
index.h [new file with mode: 0644]
jtree.cpp [new file with mode: 0644]
jtree.h [new file with mode: 0644]
lc.cpp [new file with mode: 0644]
lc.h [new file with mode: 0644]
matlab/dai.cpp [new file with mode: 0644]
matlab/dai.m [new file with mode: 0644]
matlab/dai_potstrength.cpp [new file with mode: 0644]
matlab/dai_potstrength.m [new file with mode: 0644]
matlab/dai_readfg.cpp [new file with mode: 0644]
matlab/dai_readfg.m [new file with mode: 0644]
matlab/dai_removeshortloops.cpp [new file with mode: 0644]
matlab/dai_removeshortloops.m [new file with mode: 0644]
matlab/dai_writefg.cpp [new file with mode: 0644]
matlab/dai_writefg.m [new file with mode: 0644]
matlab/matlab.cpp [new file with mode: 0644]
matlab/matlab.h [new file with mode: 0644]
mf.cpp [new file with mode: 0644]
mf.h [new file with mode: 0644]
mr.cpp [new file with mode: 0644]
mr.h [new file with mode: 0644]
prob.h [new file with mode: 0644]
properties.cpp [new file with mode: 0644]
properties.h [new file with mode: 0644]
regiongraph.cpp [new file with mode: 0644]
regiongraph.h [new file with mode: 0644]
tests/alarm.fg [new file with mode: 0644]
tests/aliases.conf [new file with mode: 0644]
tests/hoi1.fg [new file with mode: 0644]
tests/hoi2.fg [new file with mode: 0644]
tests/hoi3.fg [new file with mode: 0644]
tests/hoi4.fg [new file with mode: 0644]
tests/test.cpp [new file with mode: 0644]
tests/testall [new file with mode: 0755]
tests/testfast.fg [new file with mode: 0644]
tests/testfast.out [new file with mode: 0644]
tests/testregression [new file with mode: 0755]
treeep.cpp [new file with mode: 0644]
treeep.h [new file with mode: 0644]
util.cpp [new file with mode: 0644]
util.h [new file with mode: 0644]
utils/createfg.cpp [new file with mode: 0644]
utils/fg2dot.cpp [new file with mode: 0644]
utils/fginfo.cpp [new file with mode: 0644]
utils/remove_short_loops.cpp [new file with mode: 0644]
utils/viewfg [new file with mode: 0755]
var.h [new file with mode: 0644]
varset.h [new file with mode: 0644]
weightedgraph.cpp [new file with mode: 0644]
weightedgraph.h [new file with mode: 0644]
x2x.cpp [new file with mode: 0644]
x2x.h [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..3912109
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..cade4da
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,14 @@
+libDAI-0.2.1 (2008-05-26)
+-------------------------
+
+Bugfix release.
+* added missing cstdio header in util.h
+* fixed Properties in MR_CLAMPING_* and MR_EXACT_*
+* added description of the factor graph fileformat
+* improved Makefile
+
+
+libDAI-0.2.0 (2006-11-30)
+-------------------------
+
+First public release.
diff --git a/FILEFORMAT b/FILEFORMAT
new file mode 100644 (file)
index 0000000..3703535
--- /dev/null
@@ -0,0 +1,88 @@
+This file describes the .fg fileformat used in libDAI to store factor graphs.
+
+Markov Random Fields are special cases of factor graphs, as are Bayesian
+networks. A factor graph can be specified as follows: for each factor, one has
+to specify which variables occur in the factor, what their respective
+cardinalities (i.e., number of possible values) are, and a table listing all
+the values of that factor for all possible configurations of these variables.
+A .fg file is not much more than that. It starts with a line containing the
+number of factors in that graph, followed by an empty line. Then all factors
+are specified, one block for each factor, where the blocks are seperated by
+empty lines. Each variable occurring in the factor graph has a unique
+identifier, its index (which should be a nonnegative integer). Comment lines
+start with #.
+
+Each block starts with a line containing the number of variables in that
+factor. The second line contains the indices of these variables, seperated by
+spaces (indices are nonnegative integers and to avoid confusion, it is
+suggested to start counting at 0). The third line contains the number of
+possible values of each of these variables, also seperated by spaces. Note that
+there is some redundancy here, since if a variable appears in more than one
+factor, the cardinality of that variable appears several times in the .fg file.
+The fourth line contains the number of nonzero entries in the factor table.
+The rest of the lines contain these nonzero entries; each entry consists of a
+table index, followed by white-space, followed by the value corresponding to
+that table index. The most difficult part is getting the indexing right. The
+convention that is used is that the left-most variables cycle through their
+values the fastest (similar to MATLAB indexing of multidimensional arrays). An
+example block describing one factor is:
+
+3
+4 8 7
+3 2 2
+11
+0 0.1
+1 3.5
+2 2.8
+3 6.3
+4 8.4
+6 7.4
+7 2.4
+8 8.9
+9 1.3
+10 1.6
+12 6.4
+11 2.6
+
+which corresponds to the following factor:
+
+x_4 x_8 x_7 | value
+ 0   0   0  |  0.1
+ 1   0   0  |  3.5
+ 2   0   0  |  2.8
+ 0   1   0  |  6.3
+ 1   1   0  |  8.4
+ 2   1   0  |  0.0
+ 0   0   1  |  7.4
+ 1   0   1  |  2.4
+ 2   0   1  |  8.9
+ 0   1   1  |  1.3
+ 1   1   1  |  1.6
+ 2   1   1  |  2.6
+
+Note that the value of x_4 changes fastest, followed by that of x_8, and x_7
+varies the slowest, corresponding to the second line of the block ("4 8 7").
+Further, x_4 can take on three values, and x_8 and x_7 each have two possible
+values, as described in the third line of the block ("3 2 2"). The table
+contains 11 non-zero entries (all except for the fifth entry). Note that the
+eleventh and twelveth entries are interchanged.
+
+A final note: the internal representation in libDAI of the factor above is
+different, because the variables are ordered according to their indices
+(i.e., the ordering would be x_4 x_7 x_8) and the values of the table are
+stored accordingly, with the variable having the smallest index changing
+fastest:
+
+x_4 x_7 x_8 | value
+ 0   0   0  |  0.1
+ 1   0   0  |  3.5
+ 2   0   0  |  2.8
+ 0   1   0  |  7.4
+ 1   1   0  |  2.4
+ 2   1   0  |  8.9
+ 0   0   1  |  6.3
+ 1   0   1  |  8.4
+ 2   0   1  |  0.0
+ 0   1   1  |  1.3
+ 1   1   1  |  1.6
+ 2   1   1  |  2.6
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..72fc05a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,169 @@
+#   Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+#   Radboud University Nijmegen, The Netherlands
+#   
+#   This file is part of libDAI.
+#
+#   libDAI is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   libDAI is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with libDAI; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+# We use the BOOST Program Options library
+BOOSTFLAGS = -lboost_program_options
+
+# Compile using GNU C++ Compiler
+CC = g++
+
+# Flags for the C++ compiler
+CCFLAGS = -Wall -fpic -g -DDEBUG -I. -I$(HOME)/include -O3 #-static #-pg #-DVERBOSE
+
+# To enable the Matlab interface, define WITH_MATLAB = yes
+WITH_MATLAB = 
+ifdef WITH_MATLAB
+# Replace the following by the directory where Matlab has been installed
+MATLABDIR = /opt/matlab/bin
+MEX = $(MATLABDIR)/mex
+MEXFLAGS = -g -I. -DDEBUG -largeArrayDims #-g means debugging
+endif
+
+# Replace the following with the extension of compiled MEX files on this platform, e.g. .mexglx for x86
+MEXEXT = .mexglx
+
+HEADERS = bipgraph.h diffs.h index.h var.h factor.h varset.h prob.h daialg.h properties.h alldai.h enum.h x2x.h
+
+# target matlabs is disabled by default since it only compiles with a very recent MatLab version
+TARGETS = tests utils libdai.a example testregression
+ifdef WITH_MATLAB
+TARGETS := $(TARGETS) matlabs
+endif
+all : $(TARGETS)
+       echo -e "\a"
+
+matlabs : matlab/dai.$(MEXEXT) matlab/dai_readfg.$(MEXEXT) matlab/dai_writefg.$(MEXEXT) matlab/dai_removeshortloops.$(MEXEXT) matlab/dai_potstrength.$(MEXEXT)
+
+libdai.a : daialg.o alldai.o bp.o clustergraph.o factorgraph.o hak.o jtree.o lc.o mf.o mr.o properties.o regiongraph.o util.o treeep.o weightedgraph.o x2x.o
+       ar rcs libdai.a daialg.o alldai.o bp.o clustergraph.o factorgraph.o hak.o jtree.o lc.o mf.o mr.o properties.o regiongraph.o util.o treeep.o weightedgraph.o x2x.o
+
+tests : tests/test
+
+utils : utils/createfg utils/fg2dot utils/remove_short_loops utils/fginfo
+
+testregression : tests/test
+       echo Testing...this can take a while...
+       cd tests; ./testregression; cd ..
+
+doc : *.h *.cpp doxygen.conf
+       doxygen doxygen.conf
+
+clean :
+       rm *.o *.$(MEXEXT) example matlab/*.$(MEXEXT) matlab/*.o tests/test utils/fg2dot utils/createfg utils/remove_short_loops utils/fginfo libdai.a; echo
+       rm -R doc; echo
+
+
+daialg.o : daialg.cpp $(HEADERS)
+       $(CC) $(CCFLAGS) -c daialg.cpp
+
+bp.o : bp.cpp bp.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c bp.cpp
+
+lc.o : lc.cpp lc.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c lc.cpp
+
+mf.o : mf.cpp mf.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c mf.cpp
+
+factorgraph.o : factorgraph.cpp factorgraph.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c factorgraph.cpp
+
+util.o : util.cpp util.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c util.cpp
+
+regiongraph.o : regiongraph.cpp regiongraph.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c regiongraph.cpp
+
+hak.o : hak.cpp hak.h $(HEADERS) regiongraph.h
+       $(CC) $(CCFLAGS) -c hak.cpp
+
+clustergraph.o : clustergraph.cpp clustergraph.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c clustergraph.cpp
+
+jtree.o : jtree.cpp jtree.h $(HEADERS) weightedgraph.h clustergraph.h regiongraph.h
+       $(CC) $(CCFLAGS) -c jtree.cpp
+
+treeep.o : treeep.cpp treeep.h $(HEADERS) weightedgraph.h clustergraph.h regiongraph.h jtree.h
+       $(CC) $(CCFLAGS) -c treeep.cpp
+
+weightedgraph.o : weightedgraph.cpp weightedgraph.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c weightedgraph.cpp
+
+mr.o : mr.cpp mr.h $(HEADERS)
+       $(CC) $(CCFLAGS) -c mr.cpp
+
+properties.o : properties.cpp $(HEADERS)
+       $(CC) $(CCFLAGS) -c properties.cpp
+
+alldai.o : alldai.cpp $(HEADERS)
+       $(CC) $(CCFLAGS) -c alldai.cpp
+
+x2x.o : x2x.cpp $(HEADERS)
+       $(CC) $(CCFLAGS) -c x2x.cpp
+
+# EXAMPLE
+##########
+
+example : example.cpp $(HEADERS) libdai.a
+       $(CC) $(CCFLAGS) -o example example.cpp -L. libdai.a
+
+# TESTS
+########
+
+tests/test : tests/test.cpp $(HEADERS) libdai.a
+       $(CC) $(CCFLAGS) -o tests/test tests/test.cpp -L. -ldai $(BOOSTFLAGS)
+
+
+# MATLAB INTERFACE
+###################
+
+matlab/dai.$(MEXEXT) : matlab/dai.cpp $(HEADERS) matlab/matlab.o daialg.o alldai.o bp.o clustergraph.o factorgraph.o hak.o jtree.o lc.o mf.o mr.o properties.o regiongraph.o util.o treeep.o weightedgraph.o x2x.o
+       $(MEX) $(MEXFLAGS) -o matlab/dai matlab/dai.cpp matlab/matlab.o daialg.o alldai.o bp.o clustergraph.o factorgraph.o hak.o jtree.o lc.o mf.o mr.o properties.o regiongraph.o util.o treeep.o weightedgraph.o x2x.o
+
+matlab/dai_readfg.$(MEXEXT) : matlab/dai_readfg.cpp $(HEADERS) factorgraph.o matlab/matlab.o
+       $(MEX) $(MEXFLAGS) -o matlab/dai_readfg matlab/dai_readfg.cpp factorgraph.o matlab/matlab.o
+
+matlab/dai_writefg.$(MEXEXT) : matlab/dai_writefg.cpp $(HEADERS) factorgraph.o matlab/matlab.o
+       $(MEX) $(MEXFLAGS) -o matlab/dai_writefg matlab/dai_writefg.cpp factorgraph.o matlab/matlab.o
+
+matlab/dai_removeshortloops.$(MEXEXT) : matlab/dai_removeshortloops.cpp $(HEADERS) factorgraph.o matlab/matlab.o
+       $(MEX) $(MEXFLAGS) -o matlab/dai_removeshortloops matlab/dai_removeshortloops.cpp factorgraph.o matlab/matlab.o
+
+matlab/dai_potstrength.$(MEXEXT) : matlab/dai_potstrength.cpp $(HEADERS) matlab/matlab.o
+       $(MEX) $(MEXFLAGS) -o matlab/dai_potstrength matlab/dai_potstrength.cpp matlab/matlab.o
+
+matlab/matlab.o : matlab/matlab.cpp matlab/matlab.h $(HEADERS)
+       $(MEX) $(MEXFLAGS) -outdir matlab -c matlab/matlab.cpp
+
+
+# UTILS
+########
+
+utils/createfg : utils/createfg.cpp $(HEADERS) factorgraph.o weightedgraph.o util.o
+       $(CC) $(CCFLAGS) -o utils/createfg utils/createfg.cpp factorgraph.o weightedgraph.o util.o $(BOOSTFLAGS)
+
+utils/fg2dot : utils/fg2dot.cpp $(HEADERS) factorgraph.o
+       $(CC) $(CCFLAGS) -o utils/fg2dot utils/fg2dot.cpp factorgraph.o
+
+utils/remove_short_loops : utils/remove_short_loops.cpp $(HEADERS) factorgraph.o
+       $(CC) $(CCFLAGS) -o utils/remove_short_loops utils/remove_short_loops.cpp factorgraph.o
+
+utils/fginfo : utils/fginfo.cpp $(HEADERS) factorgraph.o
+       $(CC) $(CCFLAGS) -o utils/fginfo utils/fginfo.cpp factorgraph.o
diff --git a/README b/README
index 97a2a48..1fe39c9 100644 (file)
--- a/README
+++ b/README
@@ -1,3 +1,172 @@
-Test. First commit...
-Test continues. Second commit.
-Test continues again. Third commit.
+libDAI - A free/open source C++ library for Discrete Approximate Inference methods
+==================================================================================
+
+v 0.2.1 - May 26, 2008
+
+
+Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+Radboud University Nijmegen, The Netherlands
+    
+
+----------------------------------------------------------------------------------
+This file is part of libDAI.
+
+libDAI is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+libDAI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with libDAI; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+----------------------------------------------------------------------------------
+
+
+
+What is libDAI?
+---------------
+libDAI is a free/open source C++ library (licensed under GPL, see the file
+COPYING for more details) that provides implementations of various
+(deterministic) approximate inference methods for discrete graphical models.
+libDAI supports arbitrary factor graphs with discrete variables (this includes
+discrete Markov Random Fields and Bayesian Networks).
+
+libDAI is not intended to be a complete package for approximate inference.
+Instead, it should be considered as an "inference engine", providing various
+inference methods. In particular, it contains no GUI, currently only supports
+its own file format for input and output (although support for standard file
+formats may be added), and provides no visualization.
+
+Because libDAI is implemented in C++, it is very fast compared with e.g. MatLab
+implementations. libDAI does provide a MatLab interface for easy integration
+with MatLab. Currently, libDAI supports the following deterministic approximate
+inference methods:
+
+    * Mean Field
+    * (Loopy) Belief Propagation
+    * Tree Expectation Propagation
+    * Generalized Belief Propagation
+    * Double-loop GBP
+    * Loop Corrected Approximate Inference
+
+Exact inference by JunctionTree is also provided.
+
+Many of these algorithms are not yet available in similar open source software,
+to the best of the author's knowledge (open source packages supporting both
+directed and undirected graphical models are Murphy's BNT, Intel's PNL and gR).
+
+The library is targeted at researchers; to be able to use the library, a good
+understanding of graphical models is needed. However, the code will hopefully
+find its way into real-world applications as well. 
+
+
+Rationale
+---------
+In my opinion, the lack of open source reference implementations hampers
+progress in research on approximate inference. Methods differ widely in terms
+of quality and performance characteristics, which also depend in different ways
+on various properties of the graphical models. Finding the best approximate
+inference method for a particular application therefore often requires
+empirical comparisons. However, implementing and debugging these methods takes
+a lot of time which could otherwise be spent on research. I hope that this code
+will aid researchers to be able to easily compare various (existing as well as
+new) approximate inference methods, in this way accelerating research and
+stimulating real-world applications of approximate inference.  
+
+
+Releases
+--------
+Releases can be obtained from http://www.mbfys.ru.nl/~jorism/libDAI.
+License: GNU Public License v2 (or higher).
+
+libDAI-0.2      December 1, 2006
+libDAI-0.2.1    May 26, 2008
+
+
+Acknowledgments
+---------------
+The development reported here is part of the Interactive Collaborative
+Information Systems (ICIS) project, supported by the Dutch Ministry of Economic
+Affairs, grant BSIK03024. I would like to thank Martijn Leisink for providing
+the basis on which libDAI has been built.
+
+
+Known issues
+------------
+Due to a bug in GCC 3.3.x and earlier (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=20358) 
+it doesn't compile with these versions (it does compile with GCC version 3.4 and higher).
+Workaround: replace the two NAN's in factor.h causing the error messages by -1.
+
+
+Documentation
+-------------
+Almost nonexistant. But I'm working on it. In the meantime, I'll provide limited support
+by email. The following gives an overview of different methods and their properties
+(can be slightly obsolete):
+
+BP
+    updates     UpdateType SEQFIX,SEQRND,SEQMAX,PARALL
+    tol         double
+    maxiter     size_t
+    verbose     size_t
+MF
+    tol         double
+    maxiter     size_t
+    verbose     size_t
+HAK
+    clusters    MIN,DELTA,LOOP
+    loopdepth
+    doubleloop  bool
+    tol         double
+    maxiter     size_t
+    verbose     size_t
+JTREE
+    updates     UpdateType  HUGIN,SHSH
+    verbose     size_t
+MR
+    updates     UpdateType  FULL,LINEAR
+    inits       InitType    RESPPROP,CLAMPING,EXACT
+    verbose     size_t
+TREEEP
+    type        TypeType    ORG,ALT
+    tol         double
+    maxiter     size_t
+    verbose     size_t
+LC
+    cavity      CavityType  FULL,PAIR,PAIR2,UNIFORM
+    updates     UpdateType  SEQFIX,SEQRND(,NONE)
+    reinit      bool
+    cavainame   string
+    cavaiopts   Properties
+    tol         double
+    maxiter     size_t
+    verbose     size_t
+
+
+
+Quick start
+-----------
+You need:
+- a recent version of gcc (version 3.4 at least)
+- the Boost C++ libraries (under Debian/Ubuntu you can install them using
+  "apt-get install libboost-dev libboost-program-options-dev")
+- GNU make
+
+To build the source, edit the Makefile and then run
+    
+    make
+
+If the build was successful, you can test the example program:
+
+    ./example tests/alarm.fg
+
+or the more elaborate test program:
+
+    tests/test --aliases tests/aliases.conf --filename tests/alarm.fg --methods JTREE_HUGIN BP_SEQMAX
+
+A description of the factor graph (.fg) file format can be found in the file FILEFORMAT.
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..a660621
--- /dev/null
+++ b/TODO
@@ -0,0 +1,96 @@
+IMPORTANT
+
+- Find out which methods need connected factor graphs (at least TreeEP and JunctionTree).
+
+- Forwardport the Bart patch
+
+- Get rid of multiple inheritance
+
+- Introduce a namespace libDAI
+
+- Investigate the performance penalty from the use of Properties map
+
+- Another design question that needs to be answered: should a BP object own a
+  FactorGraph, or only store a reference to a FactorGraph (which can optimize
+  memory), or should it be a FactorGraph with additional variables and functions?
+  Probably, the first or second option would be the best. Since FactorGraphs are
+  assumed to be rather static, it would make sense to store *references* to
+  FactorGraphs inside AIAlgs. Or maybe some smart_ptr? It would make sense to 
+  prevent unnecessary copying of FactorGraphs. Also, general marginalization 
+  functions now copy a complete object. Instead, it would make more sense that
+  they construct a new object without copying the FactorGraph. Or they can be made
+  simply methods of the general InfAlg class.
+
+
+DIFFICULT
+
+- Bug: TreeEP sometimes returns NANs.
+
+- Bug: TreeEP::logZ() seems to be wrong (?).
+
+- Bug: strange things happen for at least JTree and TreeEP if the factor graph is not connected.
+  Should be fixed by finding out which methods demand connected factor graphs and these
+  methods should check whether the supplied factor graph is connected.
+
+- Kees' trick for preventing NANs in GBP updates:  if quantity smaller than epsilon, replace by epsilon.
+
+- Bug: MF_SEQRANDOM does not work on alarm2.fg (normalization error: Z = 0)
+
+
+OPTIONAL
+
+- Restore brute force EXACT method.
+
+- Alternative implementation of undo factor changes: the only things that have to be
+undone are setting a factor to all ones and setting a factor to a Kronecker delta. This
+could also be implemented in the factor itself, which could maintain its state 
+(one/delta/full) and act accordingly.
+
+- Regression test could be enhanced by 
+  * reporting number of iterations
+  * reporting all single node marginal errors
+
+- Think about the _fac2ind variable: a better implementation of this feature is
+probably possible.
+
+- Changed() methods?
+
+- It might be an idea to forbid modification of the structure of a factor graph
+  (except in constructors). Then we could do away with Regenerate and similar methods.
+
+- Think about whether using the L-1 norm as a distance measure between new and
+  old messages/beliefs would be better, since || P(x_A, x_B) - Q(x_A, xB) ||_1 < epsilon
+  implies that || P(x_A) - Q(x_A) ||_1 < epsilon. However, for a product of messages
+  we have the weaker || P1(x_A) P2(x_A) - Q1(x_A) Q2(x_A) ||_1 < 2 epsilon if
+  || P1(x_A) - Q1(x_A) ||_1 < epsilon AND || P2(x_A) - Q1(x_A) ||_1 < epsilon...
+
+- Implement damping.
+
+- Optimize GBP with precalculated indices.
+
+- Variable elimination should be implemented generically using a function
+  object that tells you which variable to delete.
+
+- Improve general support for graphs and trees.
+
+- What to do in case of divide-by-zero? For HUGIN, the correct way to handle
+  this is to define x/0 = 0. For other methods, this may not be the case...
+  Maybe a good solution would be to make different flavours of Prob which differ
+  in the way that they handle zeroes (two issues here: normalization of an all-zero
+  factor, which appears to be a sign that something is wrong, and division-by-zero
+  in operator/, operator/= and inverse(), which is probably harmless and can be fixed
+  by defining x/0 = 0. I think it is harmless because usually it occurs by calculating
+  some marginal _in the absence of some factor_ as an intermediate step. Afterwards,
+  this factor is always multiplied in again, which sets any value on these positions
+  to zero. Another difference would be how to deal with underflows: GBP often has 
+  numbers that converge to zero, but instead this gives an underflow. A solution is
+  to set everything smaller than some epsilon to zero. Finally, one could decide to
+  add support for sparse potentials. Oh, and whether it can handle negative entries.
+
+- Optimize BP_SEQMAX (it should use a better data structure than a vector for the residuals).
+  Also it does strange things in case of negative potentials.
+
+- Investigate the possibility to calculate logZ *during* the inference algorithm
+  by keeping track of all normalizations.
+
+- Write documentation.
diff --git a/alldai.cpp b/alldai.cpp
new file mode 100644 (file)
index 0000000..70dcc05
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include "alldai.h"
+
+
+InfAlg *newInfAlg( const string &name, const FactorGraph &fg, const Properties &opts ) {
+    if( name == BP::Name ) 
+        return new BP (fg, opts);
+    else if( name == MF::Name ) 
+        return new MF (fg, opts);
+    else if( name == HAK::Name ) 
+        return new HAK (fg, opts);
+    else if( name == LC::Name )
+        return new LC (fg, opts);
+    else if( name == TreeEP::Name )
+        return new TreeEP (fg, opts);
+    else if( name == MR::Name )
+        return new MR (fg, opts);
+    else if( name == JTree::Name )
+        return new JTree (fg, opts);
+    else
+        throw "Unknown inference algorithm";
+}
diff --git a/alldai.h b/alldai.h
new file mode 100644 (file)
index 0000000..df6d6f8
--- /dev/null
+++ b/alldai.h
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __ALLDAI_H__
+#define __ALLDAI_H__
+
+
+#include "daialg.h"
+#include "bp.h"
+#include "lc.h"
+#include "hak.h"
+#include "mf.h"
+#include "jtree.h"
+#include "treeep.h"
+#include "mr.h"
+
+
+/// newInfAlg constructs a new approximate inference algorithm named name for the
+/// FactorGraph fg with optionts opts and returns a pointer to the new object.
+/// The caller needs to delete it (maybe some sort of smart_ptr might be useful here).
+InfAlg *newInfAlg( const string &name, const FactorGraph &fg, const Properties &opts );
+
+
+/// AINames contains the names of all approximate inference algorithms
+static const char* DAINames[] = {BP::Name, MF::Name, HAK::Name, LC::Name, TreeEP::Name, MR::Name, JTree::Name};
+
+
+#endif
diff --git a/bipgraph.h b/bipgraph.h
new file mode 100644 (file)
index 0000000..6abc133
--- /dev/null
@@ -0,0 +1,160 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __BIPGRAPH_H__
+#define __BIPGRAPH_H__
+
+
+#include <vector>
+#include <algorithm>
+
+
+/// A BipartiteGraph represents a graph with two types of nodes
+template <class T1, class T2> class BipartiteGraph {
+    public:
+        typedef std::pair<size_t,size_t>    _edge_t;
+        typedef std::vector<size_t>         _nb_t;
+        typedef _nb_t::const_iterator       _nb_cit;
+        
+
+    private:
+        /// Vertices of first type
+        std::vector<T1>                     _V1;
+
+        /// Vertices of second type
+        std::vector<T2>                     _V2;
+        
+        /// Edges, which are pairs (v1,v2) with v1 in _V1 and v2 in _V2
+        std::vector<_edge_t>                _E12;
+
+        /// Conversion matrix that computes which index of _E12 corresponds to a vertex index pair (v1,v2)
+        std::vector<std::vector<size_t> >   _E12ind;
+
+        /// Neighbour indices of vertices of first type
+        std::vector<_nb_t>                  _nb1;
+
+        /// Neighbour indices of vertices of second type
+        std::vector<_nb_t>                  _nb2;
+
+
+    public:
+        /// Default constructor
+        BipartiteGraph<T1,T2> () {};
+
+        /// Copy constructor
+        BipartiteGraph<T1,T2> ( const BipartiteGraph<T1,T2> & x ) : _V1(x._V1), _V2(x._V2), _E12(x._E12), _E12ind(x._E12ind), _nb1(x._nb1), _nb2(x._nb2) {};
+
+        /// Assignment operator
+        BipartiteGraph<T1,T2> & operator=(const BipartiteGraph<T1,T2> & x) {
+            if( this != &x ) {
+                _V1 =       x._V1;
+                _V2 =       x._V2;
+                _E12 =      x._E12;
+                _E12ind =   x._E12ind;
+                _nb1 =      x._nb1;
+                _nb2 =      x._nb2;
+            }
+            return *this;
+        }
+        
+        /// Provides read access to node of first type
+        const T1 & V1( size_t i1 ) const { return _V1[i1]; }
+        /// Provides full access to node of first type
+        T1 & V1( size_t i1 ) { return _V1[i1]; }
+        /// Provides read access to all nodes of first type
+        const std::vector<T1> & V1s() const { return _V1; }
+        /// Provides full access to all nodes of first type
+        std::vector<T1> & V1s() { return _V1; }
+
+        /// Provides read access to node of second type
+        const T2 & V2( size_t i2 ) const { return _V2[i2]; }
+        /// Provides full access to node of second type
+        T2 & V2( size_t i2 ) { return _V2[i2]; }
+        /// Provides read access to all nodes of second type
+        const std::vector<T2> & V2s() const { return _V2; }
+        /// Provides full access to all nodes of second type
+        std::vector<T2> & V2s() { return _V2; }
+
+        /// Provides read access to edge
+        const _edge_t & edge(size_t ind) const { return _E12[ind]; }
+        /// Provides full access to edge
+        _edge_t & edge(size_t ind) { return _E12[ind]; }
+        /// Provides read access to all edges
+        const std::vector<_edge_t> & edges() const { return _E12; }
+        /// Provides full access to all edges
+        std::vector<_edge_t> & edges() { return _E12; }
+        /// Returns number of edges
+        size_t nr_edges() const { return _E12.size(); }
+
+        /// Provides read access to neighbours of node of first type
+        const _nb_t & nb1( size_t i1 ) const { return _nb1[i1]; }
+        /// Provides full access to neighbours of node of first type
+        _nb_t & nb1( size_t i1 ) { return _nb1[i1]; }
+
+        /// Provides read access to neighbours of node of second type
+        const _nb_t & nb2( size_t i2 ) const { return _nb2[i2]; }
+        /// Provides full access to neighbours of node of second type
+        _nb_t & nb2( size_t i2 ) { return _nb2[i2]; }
+
+        /// Converts the pair of indices (i1,i2) to the corresponding edge index
+        size_t VV2E( const size_t i1, const size_t i2 ) const { return _E12ind[i1][i2]; }
+
+
+        /// Regenerates internal structures (_E12ind, _nb1 and _nb2) based upon _V1, _V2 and _E12
+        void Regenerate() {
+            // Calculate _nb1 and _nb2
+
+            // Start with empty vectors
+            _nb1.clear();
+            _nb1.resize(_V1.size());
+            // Start with empty vectors
+            _nb2.clear();
+            _nb2.resize(_V2.size());
+            // Each edge yields a neighbour pair
+            for( std::vector<_edge_t>::const_iterator e = _E12.begin(); e != _E12.end(); e++ ) {
+                _nb1[e->first].push_back(e->second);
+                _nb2[e->second].push_back(e->first);
+            }
+            // Remove duplicates from _nb1
+            for( size_t i1 = 0; i1 < _V1.size(); i1++ ) {
+                _nb_t::iterator new_end = unique(_nb1[i1].begin(), _nb1[i1].end());
+                _nb1[i1].erase( new_end, _nb1[i1].end() );
+            }
+            // Remove duplicates from _nb2
+            for( size_t i2 = 0; i2 < _V2.size(); i2++ ) {
+                _nb_t::iterator new_end = unique(_nb2[i2].begin(), _nb2[i2].end());
+                _nb2[i2].erase( new_end, _nb2[i2].end() );
+            }
+
+            // Calculate _E12ind
+            
+            // Allocate data structures
+            _E12ind.clear();
+            std::vector<size_t> col(_V2.size(),-1);
+            _E12ind.assign(_V1.size(), col);
+            // Assign elements
+            for( size_t k = 0; k < _E12.size(); k++ )
+                _E12ind[_E12[k].first][_E12[k].second] = k;
+        }
+};
+
+
+#endif
diff --git a/bp.cpp b/bp.cpp
new file mode 100644 (file)
index 0000000..e8ae3d4
--- /dev/null
+++ b/bp.cpp
@@ -0,0 +1,346 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include <sstream>
+#include <map>
+#include <set>
+#include <algorithm>
+#include "bp.h"
+#include "diffs.h"
+#include "util.h"
+#include "properties.h"
+
+
+using namespace std;
+
+
+const char *BP::Name = "BP";
+
+
+bool BP::checkProperties() {
+    if( !HasProperty("updates") )
+        return false;
+    if( !HasProperty("tol") )
+        return false;
+    if (!HasProperty("maxiter") )
+        return false;
+    if (!HasProperty("verbose") )
+        return false;
+    
+    ConvertPropertyTo<double>("tol");
+    ConvertPropertyTo<size_t>("maxiter");
+    ConvertPropertyTo<size_t>("verbose");
+    ConvertPropertyTo<UpdateType>("updates");
+
+    return true;
+}
+
+
+void BP::Regenerate() {
+    DAIAlgFG::Regenerate();
+    
+    // clear messages
+    _messages.clear();
+    _messages.reserve(nr_edges());
+
+    // clear indices
+    _indices.clear();
+    _indices.reserve(nr_edges());
+
+    // create messages and indices
+    for( vector<_edge_t>::const_iterator iI=edges().begin(); iI!=edges().end(); iI++ ) {
+        _messages.push_back( Prob( var(iI->first).states() ) );
+
+        vector<size_t> ind( factor(iI->second).stateSpace(), 0 );
+        Index i (var(iI->first), factor(iI->second).vars() );
+        for( size_t j = 0; i >= 0; ++i,++j )
+            ind[j] = i; 
+        _indices.push_back( ind );
+    }
+
+    // create new_messages
+    _newmessages = _messages;
+}
+
+
+void BP::init() {
+    assert( checkProperties() );
+    for( vector<Prob>::iterator mij = _messages.begin(); mij != _messages.end(); mij++ )
+        mij->fill(1.0 / mij->size());
+    _newmessages = _messages;
+}
+
+
+void BP::calcNewMessage (size_t iI) {
+    // calculate updated message I->i
+    size_t i = edge(iI).first;
+    size_t I = edge(iI).second;
+
+/*  UNOPTIMIZED (SIMPLE TO READ, BUT SLOW) VERSION
+
+    Factor prod( factor( I ) );
+    for( _nb_cit j = nb2(I).begin(); j != nb2(I).end(); j++ )
+        if( *j != i ) {     // for all j in I \ i
+            for( _nb_cit J = nb1(*j).begin(); J != nb1(*j).end(); J++ ) 
+                if( *J != I ) {     // for all J in nb(j) \ I 
+                    prod *= Factor( *j, message(*j,*J) );
+    Factor marg = prod.marginal(var(i));
+*/
+    
+    Prob prod( factor(I).p() );
+
+    // Calculate product of incoming messages and factor I
+    for( _nb_cit j = nb2(I).begin(); j != nb2(I).end(); j++ )
+        if( *j != i ) {     // for all j in I \ i
+            // ind is the precalculated Index(j,I) i.e. to x_I == k corresponds x_j == ind[k]
+            _ind_t* ind = &(index(*j,I));
+
+            // prod_j will be the product of messages coming into j
+            Prob prod_j( var(*j).states() ); 
+            for( _nb_cit J = nb1(*j).begin(); J != nb1(*j).end(); J++ ) 
+                if( *J != I )   // for all J in nb(j) \ I 
+                    prod_j *= message(*j,*J);
+
+            // multiply prod with prod_j
+            for( size_t r = 0; r < prod.size(); r++ )
+                prod[r] *= prod_j[(*ind)[r]];
+        }
+
+    // Marginalize onto i
+    Prob marg( var(i).states(), 0.0 );
+    // ind is the precalculated Index(i,I) i.e. to x_I == k corresponds x_i == ind[k]
+    _ind_t* ind = &(index(i,I));
+    for( size_t r = 0; r < prod.size(); r++ )
+        marg[(*ind)[r]] += prod[r];
+    marg.normalize( _normtype );
+    
+    // Store result
+    _newmessages[iI] = marg;
+}
+
+
+// BP::run does not check for NANs for performance reasons
+// Somehow NaNs do not often occur in BP...
+double BP::run() {
+    if( Verbose() >= 1 )
+        cout << "Starting " << identify() << "...";
+    if( Verbose() >= 3)
+       cout << endl; 
+
+    clock_t tic = toc();
+    Diffs diffs(nrVars(), 1.0);
+    
+    vector<size_t> edge_seq;
+    vector<double> residuals;
+
+    vector<Factor> old_beliefs;
+    old_beliefs.reserve( nrVars() );
+    for( size_t i = 0; i < nrVars(); i++ )
+        old_beliefs.push_back(belief1(i));
+
+    size_t iter = 0;
+
+    if( Updates() == UpdateType::SEQMAX ) {
+        // do the first pass
+        for(size_t iI = 0; iI < nr_edges(); iI++ ) 
+            calcNewMessage(iI);
+
+        // calculate initial residuals
+        residuals.reserve(nr_edges());
+        for( size_t iI = 0; iI < nr_edges(); iI++ )
+            residuals.push_back( dist( _newmessages[iI], _messages[iI], Prob::DISTLINF ) );
+    } else {
+        edge_seq.reserve( nr_edges() );
+        for( size_t i = 0; i < nr_edges(); i++ )
+            edge_seq.push_back( i );
+    }
+
+    // do several passes over the network until maximum number of iterations has
+    // been reached or until the maximum belief difference is smaller than tolerance
+    for( iter=0; iter < MaxIter() && diffs.max() > Tol(); iter++ ) {
+        if( Updates() == UpdateType::SEQMAX ) {
+            // Residuals-BP by Koller et al.
+            for( size_t t = 0; t < nr_edges(); t++ ) {
+                // update the message with the largest residual
+                size_t iI = max_element(residuals.begin(), residuals.end()) - residuals.begin();
+                _messages[iI] = _newmessages[iI];
+                residuals[iI] = 0;
+
+                // I->i has been updated, which means that residuals for all
+                // J->j with J in nb[i]\I and j in nb[J]\i have to be updated
+                size_t i = edge(iI).first;
+                size_t I = edge(iI).second;
+                for( _nb_cit J = nb1(i).begin(); J != nb1(i).end(); J++ ) 
+                    if( *J != I )
+                        for( _nb_cit j = nb2(*J).begin(); j != nb2(*J).end(); j++ )
+                            if( *j != i ) {
+                                size_t jJ = VV2E(*j,*J);
+                                calcNewMessage(jJ);
+                                residuals[jJ] = dist( _newmessages[jJ], _messages[jJ], Prob::DISTLINF );
+                            }
+            }
+        } else if( Updates() == UpdateType::PARALL ) {
+            // Parallel updates 
+            for( size_t t = 0; t < nr_edges(); t++ )
+                calcNewMessage(t);
+
+            for( size_t t = 0; t < nr_edges(); t++ )
+                _messages[t] = _newmessages[t];
+        } else {
+            // Sequential updates
+            if( Updates() == UpdateType::SEQRND )
+                random_shuffle( edge_seq.begin(), edge_seq.end() );
+            
+            for( size_t t = 0; t < nr_edges(); t++ ) {
+                size_t k = edge_seq[t];
+                calcNewMessage(k);
+                _messages[k] = _newmessages[k];
+            }
+        }
+
+        // calculate new beliefs and compare with old ones
+        for( size_t i = 0; i < nrVars(); i++ ) {
+            Factor nb( belief1(i) );
+            diffs.push( dist( nb, old_beliefs[i], Prob::DISTLINF ) );
+            old_beliefs[i] = nb;
+        }
+
+        if( Verbose() >= 3 )
+            cout << "BP::run:  maxdiff " << diffs.max() << " after " << iter+1 << " passes" << endl;
+    }
+
+    updateMaxDiff( diffs.max() );
+
+    if( Verbose() >= 1 ) {
+        if( diffs.max() > Tol() ) {
+            if( Verbose() == 1 )
+                cout << endl;
+                cout << "BP::run:  WARNING: not converged within " << MaxIter() << " passes (" << toc() - tic << " clocks)...final maxdiff:" << diffs.max() << endl;
+        } else {
+            if( Verbose() >= 3 )
+                cout << "BP::run:  ";
+                cout << "converged in " << iter << " passes (" << toc() - tic << " clocks)." << endl;
+        }
+    }
+
+    return diffs.max();
+}
+
+
+Factor BP::belief1( size_t i ) const {
+    Prob prod( var(i).states() ); 
+    for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ ) 
+        prod *= newMessage(i,*I);
+
+    prod.normalize( Prob::NORMPROB );
+    return( Factor( var(i), prod ) );
+}
+
+
+Factor BP::belief (const Var &n) const {
+    return( belief1( findVar( n ) ) );
+}
+
+
+vector<Factor> BP::beliefs() const {
+    vector<Factor> result;
+    for( size_t i = 0; i < nrVars(); i++ )
+        result.push_back( belief1(i) );
+    for( size_t I = 0; I < nrFactors(); I++ )
+        result.push_back( belief2(I) );
+    return result;
+}
+
+
+Factor BP::belief( const VarSet &ns ) const {
+    if( ns.size() == 1 )
+        return belief( *(ns.begin()) );
+    else {
+        size_t I;
+        for( I = 0; I < nrFactors(); I++ )
+            if( factor(I).vars() >> ns )
+                break;
+        assert( I != nrFactors() );
+        return belief2(I).marginal(ns);
+    }
+}
+
+
+Factor BP::belief2 (size_t I) const {
+    Prob prod( factor(I).p() );
+
+    for( _nb_cit j = nb2(I).begin(); j != nb2(I).end(); j++ ) {
+        // ind is the precalculated Index(j,I) i.e. to x_I == k corresponds x_j == ind[k]
+        const _ind_t *ind = &(index(*j, I));
+
+        // prod_j will be the product of messages coming into j
+        Prob prod_j( var(*j).states() ); 
+        for( _nb_cit J = nb1(*j).begin(); J != nb1(*j).end(); J++ ) 
+            if( *J != I )   // for all J in nb(j) \ I 
+                prod_j *= newMessage(*j,*J);
+
+        // multiply prod with prod_j
+        for( size_t r = 0; r < prod.size(); r++ )
+            prod[r] *= prod_j[(*ind)[r]];
+    }
+
+    Factor result( factor(I).vars(), prod );
+    result.normalize( Prob::NORMPROB );
+    
+    return( result );
+
+/*  UNOPTIMIZED VERSION
+    Factor prod( factor(I) );
+    for( _nb_cit i = nb2(I).begin(); i != nb2(I).end(); i++ ) {
+        for( _nb_cit J = nb1(*i).begin(); J != nb1(*i).end(); J++ )
+            if( *J != I )
+                prod *= Factor( var(*i), newMessage(*i,*J)) );
+    }
+    return prod.normalize( Prob::NORMPROB );*/
+}
+
+
+Complex BP::logZ() const {
+    Complex sum = 0.0;
+    for(size_t i = 0; i < nrVars(); i++ )
+        sum += Complex(1.0 - nb1(i).size()) * belief1(i).entropy();
+    for( size_t I = 0; I < nrFactors(); I++ )
+        sum -= KL_dist( belief2(I), factor(I) );
+    return sum;
+}
+
+
+string BP::identify() const { 
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+void BP::init( const VarSet &ns ) {
+    for( VarSet::const_iterator n = ns.begin(); n != ns.end(); n++ ) {
+        size_t ni = findVar( *n );
+        for( _nb_cit I = nb1(ni).begin(); I != nb1(ni).end(); I++ )
+            message(ni,*I).fill( 1.0 );
+    }
+}
diff --git a/bp.h b/bp.h
new file mode 100644 (file)
index 0000000..f7afa28
--- /dev/null
+++ b/bp.h
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __BP_H__
+#define __BP_H__
+
+
+#include "daialg.h"
+#include "factorgraph.h"
+#include "enum.h"
+
+
+using namespace std;
+
+
+class BP : public DAIAlgFG {
+    protected:
+        typedef vector<size_t>  _ind_t;
+
+        vector<_ind_t>          _indices;
+        vector<Prob>            _messages, _newmessages;    
+
+    public:
+        ENUM4(UpdateType,SEQFIX,SEQRND,SEQMAX,PARALL)
+        UpdateType Updates() const { return GetPropertyAs<UpdateType>("updates"); }
+
+        // default constructor
+        BP() : DAIAlgFG() {};
+        // copy constructor
+        BP(const BP & x) : DAIAlgFG(x), _indices(x._indices), _messages(x._messages), _newmessages(x._newmessages) {};
+        BP* clone() const { return new BP(*this); }
+        // construct BP object from FactorGraph
+        BP(const FactorGraph & fg, const Properties &opts) : DAIAlgFG(fg, opts) {
+            assert( checkProperties() );
+            Regenerate();
+        }
+        // assignment operator
+        BP & operator=(const BP & x) {
+            if(this!=&x) {
+                DAIAlgFG::operator=(x);
+                _messages = x._messages;
+                _newmessages = x._newmessages;
+                _indices = x._indices;
+            }
+            return *this;
+        }
+        
+        static const char *Name;
+
+        Prob & message(size_t i1, size_t i2) { return( _messages[VV2E(i1,i2)] ); }  
+        const Prob & message(size_t i1, size_t i2) const { return( _messages[VV2E(i1,i2)] ); }  
+        Prob & newMessage(size_t i1, size_t i2) { return( _newmessages[VV2E(i1,i2)] ); }    
+        const Prob & newMessage(size_t i1, size_t i2) const { return( _newmessages[VV2E(i1,i2)] ); }    
+        _ind_t & index(size_t i1, size_t i2) { return( _indices[VV2E(i1,i2)] ); }
+        const _ind_t & index(size_t i1, size_t i2) const { return( _indices[VV2E(i1,i2)] ); }
+
+        string identify() const;
+        void Regenerate();
+        void init();
+        void calcNewMessage(size_t iI);
+        double run();
+        Factor belief1 (size_t i) const;
+        Factor belief2 (size_t I) const;
+        Factor belief (const Var &n) const;
+        Factor belief (const VarSet &n) const;
+        vector<Factor> beliefs() const;
+        Complex logZ() const;
+
+        void init( const VarSet &ns );
+        void undoProbs( const VarSet &ns ) { FactorGraph::undoProbs(ns); init(ns); }
+        bool checkProperties();
+};
+
+#endif
diff --git a/clustergraph.cpp b/clustergraph.cpp
new file mode 100644 (file)
index 0000000..1c025fe
--- /dev/null
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <set>
+#include <vector>
+#include <iostream>
+#include "varset.h"
+#include "clustergraph.h"
+
+
+using namespace std;
+
+
+ClusterGraph ClusterGraph::VarElim( const vector<Var> & ElimSeq ) const {
+    const long verbose = 0;
+
+    // Make a copy
+    ClusterGraph _Cl(*this);
+
+    ClusterGraph result;
+    _Cl.eraseNonMaximal();
+    
+    // Do variable elimination
+    for( vector<Var>::const_iterator n = ElimSeq.begin(); n != ElimSeq.end(); n++ ) {
+        assert( _Cl.vars() && *n );
+
+        if( verbose >= 1 )
+            cout << "Cost of eliminating " << *n << ": " << _Cl.eliminationCost( *n ) << " new edges" << endl;
+        
+        result.insert( _Cl.Delta(*n) );
+
+        if( verbose >= 1 )
+            cout << "_Cl = " << _Cl << endl;
+
+        if( verbose >= 1 )
+            cout << "After inserting " << _Cl.delta(*n) << ", _Cl = ";
+        _Cl.insert( _Cl.delta(*n) );
+        if( verbose >= 1 )
+            cout << _Cl << endl;
+
+        if( verbose >= 1 )
+            cout << "After erasing clusters that contain " << *n <<  ", _Cl = ";
+        _Cl.eraseSubsuming( *n );
+        if( verbose >= 1 )
+            cout << _Cl << endl;
+
+        if( verbose >= 1 )
+            cout << "After erasing nonmaximal clusters, _Cl = ";
+        _Cl.eraseNonMaximal();
+        if( verbose >= 1 )
+            cout << _Cl << endl;
+    }
+
+    return result;
+}
+
+
+ClusterGraph ClusterGraph::VarElim_MinFill() const {
+    const long verbose = 0;
+
+    // Make a copy
+    ClusterGraph _Cl(*this);
+    VarSet _vars( vars() );
+
+    ClusterGraph result;
+    _Cl.eraseNonMaximal();
+    
+    // Do variable elimination
+    while( !_vars.empty() ) {
+        if( verbose >= 1 )
+            cout << "Var  Eliminiation cost" << endl;
+        VarSet::iterator lowest = _vars.end();
+        size_t lowest_cost = -1UL;
+        for( VarSet::const_iterator n = _vars.begin(); n != _vars.end(); n++ ) {
+            size_t cost = _Cl.eliminationCost( *n );
+            if( verbose >= 1 )
+                cout << *n << "  " << cost << endl;
+            if( lowest == _vars.end() || lowest_cost > cost ) {
+                lowest = n;
+                lowest_cost = cost;
+            }
+        }
+        Var n = *lowest;
+
+        if( verbose >= 1 )
+            cout << "Lowest: " << n << " (" << lowest_cost << ")" << endl;
+
+        result.insert( _Cl.Delta(n) );
+
+        _Cl.insert( _Cl.delta(n) );
+        _Cl.eraseSubsuming( n );
+        _Cl.eraseNonMaximal();
+        _vars /= n;
+
+    }
+
+    return result;
+}
diff --git a/clustergraph.h b/clustergraph.h
new file mode 100644 (file)
index 0000000..b838eea
--- /dev/null
@@ -0,0 +1,178 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __CLUSTERGRAPH_H__
+#define __CLUSTERGRAPH_H__
+
+
+#include <set>
+#include <vector>
+#include "varset.h"
+
+
+using namespace std;
+
+
+/// A ClusterGraph is a hypergraph with VarSets as nodes.
+/// It is implemented as a set<VarSet> in which the adjacency
+/// relationship is computed realtime. It may be better to
+/// implement it as a bipartitegraph, though. The additional
+/// functionality compared to a simple set<VarSet> is
+/// finding maximal clusters, finding cliques, etc...
+class ClusterGraph : public set<VarSet> {
+    public:
+        /// Default constructor
+        ClusterGraph() : set<VarSet>() {}
+
+        /// Construct from vector<VarSet>
+        ClusterGraph( const vector<VarSet> & cls ) {
+            insert( cls.begin(), cls.end() );
+        }
+        
+        /// Copy constructor
+        ClusterGraph(const ClusterGraph &x) : set<VarSet>(x) {}
+
+        /// Assignment operator
+        ClusterGraph& operator=( const ClusterGraph &x ) {
+            if( this != &x ) {
+                set<VarSet>::operator=( x );
+            }
+            return *this;
+        }
+
+        /// Returns true if ns is a maximal member of *this under inclusion (VarSet::operator<<)
+        bool isMaximal( const VarSet &ns ) const {
+            if( count( ns ) ) {
+                // ns is a member
+                bool maximal = true;
+                for( const_iterator x = begin(); x != end() && maximal; x++ )
+                    if( (ns << *x) && (ns != *x) )
+                        maximal = false;
+                return maximal;
+            } else
+                return false;
+        }
+
+        /// Erase all VarSets that are not maximal
+        ClusterGraph& eraseNonMaximal() {
+            for( iterator x = begin(); x != end(); )
+                if( !isMaximal(*x) )
+                    erase(x++);
+                else
+                    x++;
+            return *this;
+        }
+
+        /// Return union of all members
+        VarSet vars() const {
+            VarSet result;
+            for( iterator x = begin(); x != end(); x++ )
+                result |= *x;
+            return result;
+        }
+
+        /// Returns true if n1 and n2 are adjacent, i.e.\ by
+        /// definition, are both contained in some cluster in *this
+        bool adj( const Var& n1, const Var& n2 ) {
+            bool result = false;
+            for( iterator x = begin(); (x != end()) && (!result); x++ )
+                if( (*x && n1) && (*x && n2) )
+                    result = true;
+            return result;
+        }
+        
+        /// Returns union of clusters that contain n, minus n
+        VarSet Delta( const Var& n ) const {
+            VarSet result;
+            for( iterator x = begin(); x != end(); x++ )
+                if( (*x && n) )
+                    result |= *x;
+            return result;
+        }
+
+        /// Returns union of clusters that contain n, minus n
+        VarSet delta( const Var& n ) const {
+            return Delta( n ) / n;
+        }
+        
+        /// Erases all members that contain n
+        ClusterGraph& eraseSubsuming( const Var& n ) {
+            for( iterator x = begin(); x != end(); )
+                if( (*x && n) )
+                    erase(x++);
+                else
+                    x++;
+            return *this;
+        }
+        
+        /// Send to output stream
+        friend std::ostream & operator << ( std::ostream & os, const ClusterGraph & cl ) {
+            os << "{";
+            ClusterGraph::const_iterator x = cl.begin();
+            if( x != cl.end() )
+                os << *(x++);
+            for( ; x != cl.end(); x++ )
+                os << ", " << *x;
+            os << "}";
+            return os;
+        }
+
+        /// Convert to vector<VarSet>
+        vector<VarSet> toVector() const {
+            vector<VarSet> result;
+            result.reserve( size() );
+            for( const_iterator x = begin(); x != end(); x++ )
+                result.push_back( *x );
+            return result;
+        }
+
+        /// Calculate cost of eliminating variable n
+        /// using as a measure "number of added edges in the adjacency graph"
+        /// where the adjacency graph has the variables as its nodes and
+        /// connects nodes i1 and i2 iff i1 and i2 occur in some common factor I
+        size_t eliminationCost( const Var& n ) {
+            VarSet d_n = delta( n );
+            size_t cost = 0;
+
+            // for each unordered pair {i1,i2} adjacent to n
+            for( VarSet::const_iterator i1 = d_n.begin(); i1 != d_n.end(); i1++ ) {
+                VarSet d_i1 = delta( *i1 );
+                for( VarSet::const_iterator i2 = i1; (++i2) != d_n.end(); ) {
+                    // if i1 and i2 are not adjacent, eliminating n would make them adjacent
+                    if( !adj(*i1, *i2) )
+                        cost++;
+                }
+            }
+            return cost;
+        }
+
+        /// Perform Variable Elimination without Probs, i.e. only keeping track of
+        /// the interactions that are created along the way.
+        /// Input:  a set of outer clusters and an elimination sequence
+        /// Output: a set of elimination "cliques"
+        ClusterGraph VarElim( const vector<Var> &ElimSeq ) const;
+
+        /// As Taylan does it
+        ClusterGraph VarElim_MinFill() const;
+};
+
+
+#endif
diff --git a/daialg.cpp b/daialg.cpp
new file mode 100644 (file)
index 0000000..023a64a
--- /dev/null
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include "daialg.h"
+
+
+/// Calculate the marginal of obj on ns by clamping 
+/// all variables in ns and calculating logZ for each joined state
+Factor calcMarginal( const InfAlg & obj, const VarSet & ns, bool reInit ) {
+    Factor Pns (ns);
+    
+    multind mi( ns );
+
+    InfAlg *clamped = obj.clone();
+    if( !reInit )
+        clamped->init();
+
+    Complex logZ0;
+    for( size_t j = 0; j < mi.max(); j++ ) {
+        // save unclamped factors connected to ns
+        clamped->saveProbs( ns );
+
+        // set clamping Factors to delta functions
+        vector<size_t> vi = mi.vi( j );
+        size_t k = 0;
+        for( VarSet::const_iterator n = ns.begin(); n != ns.end(); n++, k++ )
+            clamped->clamp( *n, vi[k] );
+        
+        // run DAIAlg, calc logZ, store in Pns
+        if( clamped->Verbose() >= 2 )
+            cout << j << ": ";
+        if( reInit )
+            clamped->init();
+        clamped->run();
+
+        Complex Z;
+        if( j == 0 ) {
+            logZ0 = clamped->logZ();
+            Z = 1.0;
+        } else {
+            // subtract logZ0 to avoid very large numbers
+            Z = exp(clamped->logZ() - logZ0);
+            if( fabs(imag(Z)) > 1e-5 )
+                cout << "Marginal:: WARNING: complex Z (" << Z << ")" << endl;
+        }
+
+        Pns[j] = real(Z);
+        
+        // restore clamped factors
+        clamped->undoProbs( ns );
+    }
+
+    delete clamped;
+
+    return( Pns.normalized(Prob::NORMPROB) );
+}
+
+
+vector<Factor> calcPairBeliefs( const InfAlg & obj, const VarSet& ns, bool reInit ) {
+    // convert ns to vector<VarSet>
+    size_t N = ns.size();
+    vector<Var> vns;
+    vns.reserve( N );
+    for( VarSet::const_iterator n = ns.begin(); n != ns.end(); n++ )
+        vns.push_back( *n );
+
+    vector<Factor> pairbeliefs;
+    pairbeliefs.reserve( N * N );
+    for( size_t j = 0; j < N; j++ )
+        for( size_t k = 0; k < N; k++ )
+            if( j == k )
+                pairbeliefs.push_back(Factor());
+            else
+                pairbeliefs.push_back(Factor(vns[j] | vns[k]));
+
+    InfAlg *clamped = obj.clone();
+    if( !reInit )
+        clamped->init();
+
+    Complex logZ0;
+    for( size_t j = 0; j < N; j++ ) {
+        // clamp Var j to its possible values
+        for( size_t j_val = 0; j_val < vns[j].states(); j_val++ ) {
+            if( obj.Verbose() >= 2 )
+                cout << j << "/" << N-1 << " (" << j_val << "/" << vns[j].states() << "): ";
+
+            // save unclamped factors connected to ns
+            clamped->saveProbs( ns );
+            
+            clamped->clamp( vns[j], j_val );
+            if( reInit )
+                clamped->init();
+            clamped->run();
+
+            //if( j == 0 )
+            //  logZ0 = obj.logZ();
+            double Z_xj = 1.0;
+            if( j == 0 && j_val == 0 ) {
+                logZ0 = clamped->logZ();
+            } else {
+                // subtract logZ0 to avoid very large numbers
+                Complex Z = exp(clamped->logZ() - logZ0);
+                if( fabs(imag(Z)) > 1e-5 )
+                    cout << "calcPairBelief::  Warning: complex Z: " << Z << endl;
+                Z_xj = real(Z);
+            }
+
+            for( size_t k = 0; k < N; k++ ) 
+                if( k != j ) {
+                    Factor b_k = clamped->belief(vns[k]);
+                    for( size_t k_val = 0; k_val < vns[k].states(); k_val++ ) 
+                        if( vns[j].label() < vns[k].label() )
+                            pairbeliefs[j * N + k][j_val + (k_val * vns[j].states())] = Z_xj * b_k[k_val];
+                        else
+                            pairbeliefs[j * N + k][k_val + (j_val * vns[k].states())] = Z_xj * b_k[k_val];
+                }
+
+            // restore clamped factors
+            clamped->undoProbs( ns );
+        }
+    }
+    
+    delete clamped;
+
+    // Calculate result by taking the geometric average
+    vector<Factor> result;
+    result.reserve( N * (N - 1) / 2 );
+    for( size_t j = 0; j < N; j++ )
+        for( size_t k = j+1; k < N; k++ )
+            result.push_back( (pairbeliefs[j * N + k] * pairbeliefs[k * N + j]) ^ 0.5 );
+
+    return result;
+}
+
+
+Factor calcMarginal2ndO( const InfAlg & obj, const VarSet& ns, bool reInit ) {
+    // returns a a probability distribution whose 1st order interactions
+    // are unspecified, whose 2nd order interactions approximate those of 
+    // the marginal on ns, and whose higher order interactions are absent.
+
+    vector<Factor> pairbeliefs = calcPairBeliefs( obj, ns, reInit );
+
+    Factor Pns (ns);
+    for( size_t ij = 0; ij < pairbeliefs.size(); ij++ )
+        Pns *= pairbeliefs[ij];
+    
+    return( Pns.normalized(Prob::NORMPROB) );
+}
+
+
+vector<Factor> calcPairBeliefsNew( const InfAlg & obj, const VarSet& ns, bool reInit ) {
+    vector<Factor> result;
+    result.reserve( ns.size() * (ns.size() - 1) / 2 );
+
+    InfAlg *clamped = obj.clone();
+    if( !reInit )
+        clamped->init();
+
+    Complex logZ0;
+    VarSet::const_iterator nj = ns.begin();
+    for( long j = 0; j < (long)ns.size() - 1; j++, nj++ ) {
+        size_t k = 0;
+        for( VarSet::const_iterator nk = nj; (++nk) != ns.end(); k++ ) {
+            Factor pairbelief( *nj | *nk );
+
+            // clamp Vars j and k to their possible values
+            for( size_t j_val = 0; j_val < nj->states(); j_val++ ) 
+                for( size_t k_val = 0; k_val < nk->states(); k_val++ ) {
+                    // save unclamped factors connected to ns
+                    clamped->saveProbs( ns );
+            
+                    clamped->clamp( *nj, j_val );
+                    clamped->clamp( *nk, k_val );
+                    if( reInit )
+                        clamped->init();
+                    clamped->run();
+
+                    double Z_xj = 1.0;
+                    if( j_val == 0 && k_val == 0 ) {
+                        logZ0 = clamped->logZ();
+                    } else {
+                        // subtract logZ0 to avoid very large numbers
+                        Complex Z = exp(clamped->logZ() - logZ0);
+                        if( fabs(imag(Z)) > 1e-5 )
+                            cout << "calcPairBelief::  Warning: complex Z: " << Z << endl;
+                        Z_xj = real(Z);
+                    }
+
+                    // we assume that j.label() < k.label()
+                    // i.e. we make an assumption here about the indexing
+                    pairbelief[j_val + (k_val * nj->states())] = Z_xj;
+
+                    // restore clamped factors
+                    clamped->undoProbs( ns );
+                }
+        
+            result.push_back( pairbelief );
+        }
+    }
+    
+    delete clamped;
+
+    assert( result.size() == (ns.size() * (ns.size() - 1) / 2) );
+
+    return result;
+}
diff --git a/daialg.h b/daialg.h
new file mode 100644 (file)
index 0000000..a112796
--- /dev/null
+++ b/daialg.h
@@ -0,0 +1,285 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __DAIALG_H__
+#define __DAIALG_H__
+
+
+#include <string>
+#include <iostream>
+#include <vector>
+#include "factorgraph.h"
+#include "regiongraph.h"
+#include "properties.h"
+
+
+/// The InfAlg class is the common denominator of the various approximate inference algorithms.
+/// A InfAlg object represents a discrete factorized probability distribution over multiple variables 
+/// together with an inference algorithm.
+class InfAlg {
+    private:
+        /// Properties of the algorithm (replaces _tol, _maxiter, _verbose)
+        Properties              _properties;
+
+        /// Maximum difference encountered so far
+        double                  _maxdiff;
+
+
+    public:
+        /// Default constructor
+        InfAlg() : _properties(), _maxdiff(0.0) {}
+        
+        /// Constructor with options
+        InfAlg( const Properties &opts ) : _properties(opts), _maxdiff(0.0) {}
+        
+        /// Copy constructor
+        InfAlg( const InfAlg & x ) : _properties(x._properties), _maxdiff(x._maxdiff) {}
+
+        /// Clone (virtual copy constructor)
+        virtual InfAlg* clone() const = 0;
+
+        /// Assignment operator
+        InfAlg & operator=( const InfAlg & x ) {
+            if( this != &x ) {
+                _properties = x._properties;
+                _maxdiff    = x._maxdiff;
+            }
+            return *this;
+        }
+        
+        /// Virtual desctructor
+        // (this is needed because this class contains virtual functions)
+        virtual ~InfAlg() {}
+        
+        /// Returns true if a property with the given key is present
+        bool HasProperty(const PropertyKey &key) const { return _properties.hasKey(key); }
+
+        /// Gets a property
+        const PropertyValue & GetProperty(const PropertyKey &key) const { return _properties.Get(key); }
+        /// Gets a property, casted as ValueType
+        template<typename ValueType>
+        ValueType GetPropertyAs(const PropertyKey &key) const { return _properties.GetAs<ValueType>(key); }
+
+        /// Sets a property 
+        void SetProperty(const PropertyKey &key, const PropertyValue &val) { _properties[key] = val; }
+
+        /// Converts a property from string to ValueType, if necessary
+        template<typename ValueType>
+        void ConvertPropertyTo(const PropertyKey &key) { _properties.ConvertTo<ValueType>(key); }
+
+        /// Gets all properties
+        const Properties & GetProperties() const { return _properties; }
+
+        /// Sets properties
+        void SetProperties(const Properties &p) { _properties = p; }
+
+        /// Sets tolerance
+        void Tol( double tol ) { SetProperty("tol", tol); }
+        /// Gets tolerance
+        double Tol() const { return GetPropertyAs<double>("tol"); }
+
+        /// Sets maximum number of iterations
+        void MaxIter( size_t maxiter ) { SetProperty("maxiter", maxiter); }
+        /// Gets maximum number of iterations
+        size_t MaxIter() const { return GetPropertyAs<size_t>("maxiter"); }
+
+        /// Sets verbosity
+        void Verbose( size_t verbose ) { SetProperty("verbose", verbose); }
+        /// Gets verbosity
+        size_t Verbose() const { return GetPropertyAs<size_t>("verbose"); }
+
+        /// Sets maximum difference encountered so far
+        void MaxDiff( double maxdiff ) { _maxdiff = maxdiff; }
+        /// Gets maximum difference encountered so far
+        double MaxDiff() const { return _maxdiff; }
+        /// Updates maximum difference encountered so far
+        void updateMaxDiff( double maxdiff ) { if( maxdiff > _maxdiff ) _maxdiff = maxdiff; }
+        /// Sets maximum difference encountered so far to zero
+        void clearMaxDiff() { _maxdiff = 0.0; }
+
+        /// Identifies itself for logging purposes
+        virtual std::string identify() const = 0;
+
+        /// Get single node belief
+        virtual Factor belief( const Var &n ) const = 0;
+
+        /// Get general belief
+        virtual Factor belief( const VarSet &n ) const = 0;
+
+        /// Get all beliefs
+        virtual vector<Factor> beliefs() const = 0;
+
+        /// Get log partition sum
+        virtual Complex logZ() const = 0;
+
+        /// Clear messages and beliefs
+        virtual void init() = 0;
+
+        /// The actual approximate inference algorithm
+        virtual double run() = 0;
+
+        /// Save factor I
+        virtual void saveProb( size_t I ) = 0;
+        /// Save Factors involving ns
+        virtual void saveProbs( const VarSet &ns ) = 0;
+
+        /// Restore factor I
+        virtual void undoProb( size_t I ) = 0;
+        /// Restore Factors involving ns
+        virtual void undoProbs( const VarSet &ns ) = 0;
+
+        /// Clamp variable n to value i (i.e. multiply with a Kronecker delta \f$\delta_{x_n, i}\f$)
+        virtual void clamp( const Var & n, size_t i ) = 0;
+
+        /// Return all variables that interact with n
+        virtual VarSet delta( const Var & n ) const = 0;
+
+        /// Set all factors interacting with n to 1
+        virtual void makeCavity( const Var & n ) = 0;
+
+        /// Set factor I to 1
+        virtual void makeFactorCavity( size_t I ) = 0;
+
+        /// Get index of variable n
+        virtual size_t findVar( const Var & n ) const = 0;
+
+        /// Get index of first factor involving ns
+        virtual size_t findFactor( const VarSet &ns ) const = 0;
+
+        /// Get number of variables
+        virtual size_t nrVars() const = 0;
+
+        /// Get number of factors
+        virtual size_t nrFactors() const = 0;
+
+        /// Get const reference to variable i
+        virtual const Var & var(size_t i) const = 0;
+
+        /// Get reference to variable i
+        virtual Var & var(size_t i) = 0;
+
+        /// Get const reference to factor I
+        virtual const Factor & factor( size_t I ) const = 0;
+
+        /// Get reference to factor I
+        virtual Factor & factor( size_t I ) = 0;
+
+        /// Factor I has been updated
+        virtual void updatedFactor( size_t I ) = 0;
+
+        /// Checks whether all necessary properties have been set
+        /// and casts string-valued properties to other values if necessary
+        virtual bool checkProperties() = 0;
+};
+
+
+template <class T>
+class DAIAlg : public InfAlg, public T {
+    public:
+        /// Default constructor
+        DAIAlg() : InfAlg(), T() {}
+        
+        /// Construct DAIAlg with empty T but using the specified properties
+        DAIAlg( const Properties &opts ) : InfAlg( opts ), T() {}
+
+        /// Construct DAIAlg using the specified properties
+        DAIAlg( const T & t, const Properties &opts ) : InfAlg( opts ), T(t) {}
+        
+        /// Copy constructor
+        DAIAlg( const DAIAlg & x ) : InfAlg(x), T(x) {}
+
+        /// Save factor I (using T::saveProb)
+        void saveProb( size_t I ) { T::saveProb( I ); }
+        /// Save Factors involving ns (using T::saveProbs)
+        void saveProbs( const VarSet &ns ) { T::saveProbs( ns ); }
+
+        /// Restore factor I (using T::undoProb)
+        void undoProb( size_t I ) { T::undoProb( I ); }
+        /// Restore Factors involving ns (using T::undoProbs)
+        void undoProbs( const VarSet &ns ) { T::undoProbs( ns ); }
+
+        /// Clamp variable n to value i (i.e. multiply with a Kronecker delta \f$\delta_{x_n, i}\f$) (using T::clamp)
+        void clamp( const Var & n, size_t i ) { T::clamp( n, i ); }
+
+        /// Return all variables that interact with n (using T::delta)
+        VarSet delta( const Var & n ) const { return T::delta(n); }
+
+        /// Set all factors interacting with n to 1 (using T::makeCavity)
+        void makeCavity( const Var & n ) { T::makeCavity(n); }
+
+        /// Set factor I to 1 (using T::makeFactorCavity)
+        void makeFactorCavity( size_t I ) { T::makeFactorCavity(I); }
+
+        /// Get index of variable n (using T::findVar)
+        size_t findVar( const Var & n ) const { return T::findVar(n); }
+
+        /// Get index of first factor involving ns (using T::findFactor)
+        size_t findFactor( const VarSet &ns ) const { return T::findFactor(ns); }
+
+        /// Get number of variables (using T::nrFactors)
+        size_t nrVars() const { return T::nrVars(); }
+
+        /// Get number of factors (using T::nrFactors)
+        size_t nrFactors() const { return T::nrFactors(); }
+
+        /// Get const reference to variable i (using T::var)
+        const Var & var(size_t i) const { return T::var(i); }
+
+        /// Get reference to variable i (using T::var)
+        Var & var(size_t i) { return T::var(i); }
+
+        /// Get const reference to factor I (using T::factor)
+        const Factor & factor( size_t I ) const { return T::factor(I); }
+
+        /// Get reference to factor I (using T::factor)
+        Factor & factor( size_t I ) { return T::factor(I); }
+
+        /// Factor I has been updated (using T::updatedFactor)
+        void updatedFactor( size_t I ) { T::updatedFactor(I); }
+};
+
+
+typedef DAIAlg<FactorGraph> DAIAlgFG;
+typedef DAIAlg<RegionGraph> DAIAlgRG;
+
+
+/// Calculate the marginal of obj on ns by clamping 
+/// all variables in ns and calculating logZ for each joined state
+Factor calcMarginal( const InfAlg & obj, const VarSet & ns, bool reInit );
+
+
+/// Calculate beliefs of all pairs in ns (by clamping
+/// nodes in ns and calculating logZ and the beliefs for each state)
+vector<Factor> calcPairBeliefs( const InfAlg & obj, const VarSet& ns, bool reInit );
+
+
+/// Calculate beliefs of all pairs in ns (by clamping
+/// pairs in ns and calculating logZ for each joined state)
+vector<Factor> calcPairBeliefsNew( const InfAlg & obj, const VarSet& ns, bool reInit );
+
+
+/// Calculate 2nd order interactions of the marginal of obj on ns
+Factor calcMarginal2ndO( const InfAlg & obj, const VarSet& ns, bool reInit );
+
+
+
+#endif
diff --git a/diffs.h b/diffs.h
new file mode 100644 (file)
index 0000000..52fa781
--- /dev/null
+++ b/diffs.h
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __DIFFS_H__
+#define __DIFFS_H__
+
+
+#include <vector>
+
+using namespace std;
+
+
+class Diffs : public vector<double> {
+    private:
+        size_t _maxsize;
+        double _def;
+        vector<double>::iterator _pos;
+        vector<double>::iterator _maxpos;
+    public:
+        Diffs(long maxsize, double def) : vector<double>(), _maxsize(maxsize), _def(def) { 
+            this->reserve(_maxsize); 
+            _pos = begin(); 
+            _maxpos = begin(); 
+        };
+        double max() { 
+            if( size() < _maxsize )
+                return _def;
+            else
+                return( *_maxpos ); 
+        }
+        void push(double x) {
+            if( size() < _maxsize ) {
+                push_back(x);
+                _pos = end();
+                _maxpos = max_element(begin(),end());
+            }
+            else {
+                if( _pos == end() )
+                    _pos = begin();
+                if( _maxpos == _pos ) {
+                    *_pos++ = x; 
+                    _maxpos = max_element(begin(),end());
+                } else {
+                    if( x > *_maxpos )
+                        _maxpos = _pos;
+                    *_pos++ = x;
+                }
+            }
+        }
+};
+
+#endif
diff --git a/doxygen.conf b/doxygen.conf
new file mode 100644 (file)
index 0000000..a968bed
--- /dev/null
@@ -0,0 +1,1218 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libDAI
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = 
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = 
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = 
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/enum.h b/enum.h
new file mode 100644 (file)
index 0000000..649ae6c
--- /dev/null
+++ b/enum.h
@@ -0,0 +1,266 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __ENUM_H
+#define __ENUM_H
+
+
+#include <cstring>
+#include <iostream>
+
+
+// C++ enums are too limited for my purposes. This defines wrapper classes
+// that provide much more functionality than a simple enum. The only
+// disadvantage is that one wrapper class needs to be written for each
+// number of values an enum can take... a better solution is needed.
+
+
+#define ENUM2(x,a,b) class x {\
+    public:\
+        enum value {a, b};\
+\
+        x() : v(a) {}\
+\
+        x(value w) : v(w) {}\
+\
+        x(char const *w) {\
+           static char const* labels[] = {#a, #b};\
+           size_t i = 0;\
+           for( ; i < sizeof(labels) / sizeof(char const *); i++ )\
+               if( strcmp( w, labels[i] ) == 0 ) {\
+                   v = (value)i;\
+                   break;\
+               }\
+           if( i == sizeof(labels) / sizeof(char const *) )\
+               throw "Unknown " #x " value";\
+        }\
+\
+        operator value () const { return v; }\
+\
+        operator size_t () const { return (size_t)v; }\
+\
+        operator char const* () const {\
+           static char const* labels[] = {#a, #b};\
+           return labels[v];\
+        }\
+\
+        friend std::istream& operator >> (std::istream& is, x& y) {\
+            std::string s;\
+            is >> s;\
+            y = x(s.c_str());\
+            return is;\
+        }\
+\
+        friend std::ostream& operator << (std::ostream& os, const x& y) {\
+            os << (const char *)y;\
+            return os;\
+        }\
+\
+    private:\
+        value v;\
+};
+
+
+#define ENUM3(x,a,b,c) class x {\
+    public:\
+        enum value {a, b, c};\
+\
+        x() : v(a) {}\
+\
+        x(value w) : v(w) {}\
+\
+        x(char const *w) {\
+           static char const* labels[] = {#a, #b, #c};\
+           size_t i = 0;\
+           for( ; i < sizeof(labels) / sizeof(char const *); i++ )\
+               if( strcmp( w, labels[i] ) == 0 ) {\
+                   v = (value)i;\
+                   break;\
+               }\
+           if( i == sizeof(labels) / sizeof(char const *) )\
+               throw "Unknown " #x " value";\
+        }\
+\
+        operator value () const { return v; }\
+\
+        operator size_t () const { return (size_t)v; }\
+\
+        operator char const* () const {\
+           static char const* labels[] = {#a, #b, #c};\
+           return labels[v];\
+        }\
+\
+        friend std::istream& operator >> (std::istream& is, x& y) {\
+            std::string s;\
+            is >> s;\
+            y = x(s.c_str());\
+            return is;\
+        }\
+\
+        friend std::ostream& operator << (std::ostream& os, const x& y) {\
+            os << (const char *)y;\
+            return os;\
+        }\
+\
+    private:\
+        value v;\
+};
+
+
+#define ENUM4(x,a,b,c,d) class x {\
+    public:\
+        enum value {a, b, c, d};\
+\
+        x() : v(a) {}\
+\
+        x(value w) : v(w) {}\
+\
+        x(char const *w) {\
+           static char const* labels[] = {#a, #b, #c, #d};\
+           size_t i = 0;\
+           for( ; i < sizeof(labels) / sizeof(char const *); i++ )\
+               if( strcmp( w, labels[i] ) == 0 ) {\
+                   v = (value)i;\
+                   break;\
+               }\
+           if( i == sizeof(labels) / sizeof(char const *) )\
+               throw "Unknown " #x " value";\
+        }\
+\
+        operator value () const { return v; }\
+\
+        operator size_t () const { return (size_t)v; }\
+\
+        operator char const* () const {\
+           static char const* labels[] = {#a, #b, #c, #d};\
+           return labels[v];\
+        }\
+\
+        friend std::istream& operator >> (std::istream& is, x& y) {\
+            std::string s;\
+            is >> s;\
+            y = x(s.c_str());\
+            return is;\
+        }\
+\
+        friend std::ostream& operator << (std::ostream& os, const x& y) {\
+            os << (const char *)y;\
+            return os;\
+        }\
+\
+    private:\
+        value v;\
+};
+
+
+#define ENUM5(x,a,b,c,d,e) class x {\
+    public:\
+        enum value {a, b, c, d, e};\
+\
+        x() : v(a) {}\
+\
+        x(value w) : v(w) {}\
+\
+        x(char const *w) {\
+           static char const* labels[] = {#a, #b, #c, #d, #e};\
+           size_t i = 0;\
+           for( ; i < sizeof(labels) / sizeof(char const *); i++ )\
+               if( strcmp( w, labels[i] ) == 0 ) {\
+                   v = (value)i;\
+                   break;\
+               }\
+           if( i == sizeof(labels) / sizeof(char const *) )\
+               throw "Unknown " #x " value";\
+        }\
+\
+        operator value () const { return v; }\
+\
+        operator size_t () const { return (size_t)v; }\
+\
+        operator char const* () const {\
+           static char const* labels[] = {#a, #b, #c, #d, #e};\
+           return labels[v];\
+        }\
+\
+        friend std::istream& operator >> (std::istream& is, x& y) {\
+            std::string s;\
+            is >> s;\
+            y = x(s.c_str());\
+            return is;\
+        }\
+\
+        friend std::ostream& operator << (std::ostream& os, const x& y) {\
+            os << (const char *)y;\
+            return os;\
+        }\
+\
+    private:\
+        value v;\
+};
+
+
+#define ENUM6(x,a,b,c,d,e,f) class x {\
+    public:\
+        enum value {a, b, c, d, e, f};\
+\
+        x() : v(a) {}\
+\
+        x(value w) : v(w) {}\
+\
+        x(char const *w) {\
+           static char const* labels[] = {#a, #b, #c, #d, #e, #f};\
+           size_t i = 0;\
+           for( ; i < sizeof(labels) / sizeof(char const *); i++ )\
+               if( strcmp( w, labels[i] ) == 0 ) {\
+                   v = (value)i;\
+                   break;\
+               }\
+           if( i == sizeof(labels) / sizeof(char const *) )\
+               throw "Unknown " #x " value";\
+        }\
+\
+        operator value () const { return v; }\
+\
+        operator size_t () const { return (size_t)v; }\
+\
+        operator char const* () const {\
+           static char const* labels[] = {#a, #b, #c, #d, #e, #f};\
+           return labels[v];\
+        }\
+\
+        friend std::istream& operator >> (std::istream& is, x& y) {\
+            std::string s;\
+            is >> s;\
+            y = x(s.c_str());\
+            return is;\
+        }\
+\
+        friend std::ostream& operator << (std::ostream& os, const x& y) {\
+            os << (const char *)y;\
+            return os;\
+        }\
+\
+    private:\
+        value v;\
+};
+
+
+#endif
diff --git a/example.cpp b/example.cpp
new file mode 100644 (file)
index 0000000..5bada87
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright 2006-2008  Joris Mooij
+    jorism@jorismooij.nl
+    
+    This file is part of AI.
+
+    AI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    AI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with AI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include "alldai.h"
+
+
+using namespace std;
+
+
+int main( int argc, char *argv[] ) {
+    if ( argc != 2 ) {
+        cout << "Usage: " << argv[0] << " <filename.fg>" << endl << endl;
+        cout << "Reads factor graph <filename.fg> and runs" << endl;
+        cout << "Belief Propagation and JunctionTree on it." << endl << endl;
+        return 1;
+    } else {
+        FactorGraph fg;
+
+        if( fg.ReadFromFile(argv[1]) ) {
+            cout << "Error reading " << argv[1] << endl;
+            return 2;
+        } else {
+            size_t  maxiter = 1000;
+            double  tol = 1e-9;
+            size_t  verb = 1;
+
+            Properties opts;
+            opts.Set("maxiter",maxiter);
+            opts.Set("tol",tol);
+            opts.Set("verbose",verb);
+
+            JTree jt( fg, opts("updates",string("HUGIN")) );
+            jt.init();
+            jt.run();
+
+            cout << "Exact single node marginals:" << endl;
+            for( size_t i = 0; i < fg.nrVars(); i++ )
+                cout << jt.belief(fg.var(i)) << endl;
+
+            BP bp(fg, opts("updates",string("SEQMAX")));
+            bp.init();
+            bp.run();
+
+            cout << "Exact single node marginals:" << endl;
+            for( size_t i = 0; i < fg.nrVars(); i++ )
+                cout << bp.belief(fg.var(i)) << endl;
+        }
+    }
+
+    return 0;
+}
diff --git a/factor.h b/factor.h
new file mode 100644 (file)
index 0000000..2c01e17
--- /dev/null
+++ b/factor.h
@@ -0,0 +1,359 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __FACTOR_H__
+#define __FACTOR_H__
+
+
+#include <iostream>
+#include <cmath>
+#include "prob.h"
+#include "varset.h"
+#include "index.h"
+
+
+template<typename T> class      TFactor;
+typedef TFactor<Real>           Factor;
+typedef TFactor<Complex>        CFactor;
+
+
+// predefine friends
+template<typename T> Real           dist( const TFactor<T> & x, const TFactor<T> & y, Prob::DistType dt );
+template<typename T> Complex        KL_dist( const TFactor<T> & p, const TFactor<T> & q );
+template<typename T> std::ostream&  operator<< (std::ostream& os, const TFactor<T>& P);
+
+        
+// T should be castable from and to double and to complex
+template <typename T> class TFactor {
+    protected:
+        VarSet      _vs;
+        TProb<T>    _p;
+
+    public:
+        // Default constructor
+        TFactor () : _vs(), _p(1,1.0) {}
+        
+        // Construct Factor from VarSet
+        TFactor( const VarSet& ns ) : _vs(ns), _p(_vs.stateSpace()) {}
+        
+        // Construct Factor from VarSet and initial value
+        TFactor( const VarSet& ns, Real p ) : _vs(ns), _p(_vs.stateSpace(),p) {}
+        
+        // Construct Factor from VarSet and initial array
+        TFactor( const VarSet& ns, const Real* p ) : _vs(ns), _p(_vs.stateSpace(),p) {}
+
+        // Construct Factor from VarSet and TProb<T>
+        TFactor( const VarSet& ns, const TProb<T> p ) : _vs(ns), _p(p) {
+#ifdef DEBUG
+            assert( _vs.stateSpace() == _p.size() );
+#endif
+        }
+        
+        // Construct Factor from Var
+        TFactor( const Var& n ) : _vs(n), _p(n.states()) {}
+
+        // Copy constructor
+        TFactor( const TFactor<T> &x ) : _vs(x._vs), _p(x._p) {}
+        
+        // Assignment operator
+        TFactor<T> & operator= (const TFactor<T> &x) {
+            if( this != &x ) {
+                _vs = x._vs;
+                _p  = x._p;
+            }
+            return *this;
+        }
+
+        const TProb<T> & p() const { return _p; }
+        TProb<T> & p() { return _p; }
+        const VarSet & vars() const { return _vs; }
+        size_t stateSpace() const { 
+#ifdef DEBUG
+            assert( _vs.stateSpace() == _p.size() );
+#endif
+            return _p.size();
+        }
+
+        T operator[] (size_t i) const { return _p[i]; }
+        T& operator[] (size_t i) { return _p[i]; }
+        TFactor<T> & fill (T p)
+            { _p.fill( p ); return(*this); }
+        TFactor<T> & randomize ()
+            { _p.randomize(); return(*this); }
+        TFactor<T> operator* (T x) const {
+            Factor result = *this;
+            result.p() *= x;
+            return result;
+        }
+        TFactor<T>& operator*= (T x) {
+            _p *= x;
+            return *this;
+        }
+        TFactor<T> operator/ (T x) const {
+            Factor result = *this;
+            result.p() /= x;
+            return result;
+        }
+        TFactor<T>& operator/= (T x) {
+            _p /= x;
+            return *this;
+        }
+        TFactor<T> operator* (const TFactor<T>& Q) const;
+        TFactor<T>& operator*= (const TFactor<T>& Q) { return( *this = (*this * Q) ); }
+        TFactor<T> operator+ (const TFactor<T>& Q) const {
+#ifdef DEBUG
+            assert( Q._vs == _vs );
+#endif
+            TFactor<T> sum(*this); 
+            sum._p += Q._p; 
+            return sum; 
+        }
+        TFactor<T> operator- (const TFactor<T>& Q) const {
+#ifdef DEBUG
+            assert( Q._vs == _vs );
+#endif
+            TFactor<T> sum(*this); 
+            sum._p -= Q._p; 
+            return sum; 
+        }
+        TFactor<T>& operator+= (const TFactor<T>& Q) { 
+#ifdef DEBUG
+            assert( Q._vs == _vs );
+#endif
+            _p += Q._p;
+            return *this;
+        }
+        TFactor<T>& operator-= (const TFactor<T>& Q) { 
+#ifdef DEBUG
+            assert( Q._vs == _vs );
+#endif
+            _p -= Q._p;
+            return *this;
+        }
+
+        TFactor<T> operator^ (Real a) const { TFactor<T> x; x._vs = _vs; x._p = _p^a; return x; }
+        TFactor<T>& operator^= (Real a) { _p ^= a; return *this; }
+
+        TFactor<T>& makeZero( Real epsilon ) {
+            _p.makeZero( epsilon );
+            return *this;
+        }
+            
+        TFactor<T> inverse() const { 
+            TFactor<T> inv; 
+            inv._vs = _vs; 
+            inv._p = _p.inverse(true);  // FIXME
+            return inv; 
+        }
+
+        TFactor<T> divided_by( const TFactor<T>& denom ) const { 
+#ifdef DEBUG
+            assert( denom._vs == _vs );
+#endif
+            TFactor<T> quot(*this); 
+            quot._p /= denom._p; 
+            return quot; 
+        }
+
+        TFactor<T>& divide( const TFactor<T>& denom ) {
+#ifdef DEBUG
+            assert( denom._vs == _vs );
+#endif
+            _p /= denom._p;
+            return *this;
+        }
+
+        TFactor<T> exp() const { 
+            TFactor<T> e; 
+            e._vs = _vs; 
+            e._p = _p.exp(); 
+            return e; 
+        }
+
+        TFactor<T> log() const {
+            TFactor<T> l; 
+            l._vs = _vs; 
+            l._p = _p.log(); 
+            return l; 
+        }
+
+        TFactor<T> log0() const {
+            TFactor<T> l0; 
+            l0._vs = _vs; 
+            l0._p = _p.log0(); 
+            return l0; 
+        }
+
+        CFactor clog0() const {
+            CFactor l0; 
+            l0._vs = _vs; 
+            l0._p = _p.clog0(); 
+            return l0; 
+        }
+
+        T normalize( typename Prob::NormType norm ) { return _p.normalize( norm ); }
+        TFactor<T> normalized( typename Prob::NormType norm ) const { 
+            TFactor<T> result;
+            result._vs = _vs;
+            result._p = _p.normalized( norm );
+            return result;
+        }
+
+        // returns slice of this factor where the subset ns is in state ns_state
+        Factor slice( const VarSet & ns, size_t ns_state ) const {
+            assert( ns << _vs );
+            VarSet nsrem = _vs / ns;
+            Factor result( nsrem, 0.0 );
+            
+            // OPTIMIZE ME
+            Index i_ns (ns, _vs);
+            Index i_nsrem (nsrem, _vs);
+            for( size_t i = 0; i < stateSpace(); i++, ++i_ns, ++i_nsrem )
+                if( (size_t)i_ns == ns_state )
+                    result._p[i_nsrem] = _p[i];
+
+            return result;
+        }
+
+        // returns unnormalized marginal
+        TFactor<T> part_sum(const VarSet & ns) const;
+        // returns normalized marginal
+        TFactor<T> marginal(const VarSet & ns) const { return part_sum(ns).normalized( Prob::NORMPROB ); }
+
+        bool hasNaNs() const { return _p.hasNaNs(); }
+        bool hasNegatives() const { return _p.hasNegatives(); }
+        T totalSum() const { return _p.totalSum(); }
+        T maxAbs() const { return _p.maxAbs(); }
+        T max() const { return _p.max(); }
+        Complex entropy() const { return _p.entropy(); }
+        T strength( const Var &i, const Var &j ) const;
+
+        friend Real dist( const TFactor<T> & x, const TFactor<T> & y, Prob::DistType dt ) {
+            if( x._vs.empty() || y._vs.empty() )
+                return -1;
+            else {
+#ifdef DEBUG
+                assert( x._vs == y._vs );
+#endif
+                return dist( x._p, y._p, dt );
+            }
+        }
+        friend Complex KL_dist <> (const TFactor<T> & p, const TFactor<T> & q);
+        template<class U> friend std::ostream& operator<< (std::ostream& os, const TFactor<U>& P);
+};
+
+
+template<typename T> TFactor<T> TFactor<T>::part_sum(const VarSet & ns) const {
+#ifdef DEBUG
+    assert( ns << _vs );
+#endif
+
+    TFactor<T> res( ns, 0.0 );
+
+    Index i_res( ns, _vs );
+    for( size_t i = 0; i < _p.size(); i++, ++i_res )
+        res._p[i_res] += _p[i];
+
+    return res;
+}
+
+
+template<typename T> std::ostream& operator<< (std::ostream& os, const TFactor<T>& P) {
+    os << "(" << P.vars() << " <";
+    for( size_t i = 0; i < P._p.size(); i++ )
+        os << P._p[i] << " ";
+    os << ">)";
+    return os;
+}
+
+
+template<typename T> TFactor<T> TFactor<T>::operator* (const TFactor<T>& Q) const {
+    TFactor<T> prod( _vs | Q._vs, 0.0 );
+
+    Index i1(_vs, prod._vs);
+    Index i2(Q._vs, prod._vs);
+
+    for( size_t i = 0; i < prod._p.size(); i++, ++i1, ++i2 )
+        prod._p[i] += _p[i1] * Q._p[i2];
+
+    return prod;
+}
+
+
+template<typename T> Complex KL_dist(const TFactor<T> & P, const TFactor<T> & Q) {
+    if( P._vs.empty() || Q._vs.empty() )
+        return -1;
+    else {
+#ifdef DEBUG
+        assert( P._vs == Q._vs );
+#endif
+        return KL_dist( P._p, Q._p );
+    }
+}
+
+
+// calculate N(psi, i, j)
+template<typename T> T TFactor<T>::strength( const Var &i, const Var &j ) const {
+#ifdef DEBUG
+    assert( _vs && i );
+    assert( _vs && j );
+    assert( i != j );
+#endif
+    VarSet ij = i | j;
+
+    T max = 0.0;
+    for( size_t alpha1 = 0; alpha1 < i.states(); alpha1++ )
+        for( size_t alpha2 = 0; alpha2 < i.states(); alpha2++ )
+            if( alpha2 != alpha1 )
+                for( size_t beta1 = 0; beta1 < j.states(); beta1++ ) 
+                    for( size_t beta2 = 0; beta2 < j.states(); beta2++ )
+                        if( beta2 != beta1 ) {
+                            size_t as = 1, bs = 1;
+                            if( i < j )
+                                bs = i.states();
+                            else
+                                as = j.states();
+                            T f1 = slice( ij, alpha1 * as + beta1 * bs ).p().divide( slice( ij, alpha2 * as + beta1 * bs ).p() ).max();
+                            T f2 = slice( ij, alpha2 * as + beta2 * bs ).p().divide( slice( ij, alpha1 * as + beta2 * bs ).p() ).max();
+                            T f = f1 * f2;
+                            if( f > max )
+                                max = f;
+                        }
+    
+    return std::tanh( 0.25 * std::log( max ) );
+}
+
+
+template<typename T> TFactor<T> RemoveFirstOrderInteractions( const TFactor<T> & psi ) {
+    TFactor<T> result = psi;
+
+    VarSet vars = psi.vars();
+    for( size_t iter = 0; iter < 100; iter++ ) {
+        for( VarSet::const_iterator n = vars.begin(); n != vars.end(); n++ )
+            result = result * result.part_sum(*n).inverse();
+        result.normalize( Prob::NORMPROB );
+    }
+
+    return result;
+}
+
+
+#endif
diff --git a/factorgraph.cpp b/factorgraph.cpp
new file mode 100644 (file)
index 0000000..bd15554
--- /dev/null
@@ -0,0 +1,619 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <set>
+#include <fstream>
+#include <string>
+#include <algorithm>
+#include <functional>
+#include "factorgraph.h"
+
+
+using namespace std;
+
+
+FactorGraph::FactorGraph( const vector<Factor> &P ) : BipartiteGraph<Var,Factor>(), _undoProbs(), _hasNegatives(false), _normtype(Prob::NORMPROB) {
+    // add Factors
+    set<Var> _vars;
+    for(vector<Factor>::const_iterator p2 = P.begin(); p2 != P.end(); p2++ ) {
+        V2s().push_back(*p2);
+        if( !_hasNegatives && p2->hasNegatives() )
+            _hasNegatives = true;
+        for( set<Var>::const_iterator i = p2->vars().begin(); i != p2->vars().end(); i++ )
+            _vars.insert(*i);
+    }
+
+    // if negative factors are present, use LINF norm
+    if( _hasNegatives )
+        _normtype = Prob::NORMLINF;
+    
+    // add _vars
+    for(VarSet::const_iterator p1 = _vars.begin(); p1 != _vars.end(); p1++ )
+        vars().push_back(*p1);
+
+    // create edges
+    for(size_t i2 = 0; i2 < nrFactors(); i2++ ) {
+        VarSet ns = factor(i2).vars();
+        for(VarSet::const_iterator q = ns.begin(); q != ns.end(); q++ ) {
+            for(size_t i1=0; i1 < nrVars(); i1++ ) {
+                if (var(i1) == *q) {
+                    edges().push_back(_edge_t(i1,i2));
+                    break;
+                }
+            }
+        }
+    }
+
+    // calc neighbours and adjacency matrix
+    Regenerate();
+
+    // Check for short loops
+    if( hasShortLoops(P) )
+        cerr << "FactorGraph::FactorGraph():  WARNING: short loops are present" << endl;
+}
+
+
+/*FactorGraph& FactorGraph::addFactor( const Factor &I ) {
+    // add Factor
+    _V2.push_back( I );
+    if( !_hasNegatives && I.hasNegatives() )
+        _hasNegatives = true;
+
+    // if negative factors are present, use LINF norm
+    if( _hasNegatives )
+        _normtype = Prob::NORMLINF;
+
+    // add new vars in Factor
+    for( VarSet::const_iterator i = I.vars().begin(); i != I.vars().end(); i++ ) {
+        size_t i_ind = find(vars().begin(), vars().end(), *i) - vars().begin();
+        if( i_ind == vars().size() )
+            _V1.push_back( *i );
+        _E12.push_back( _edge_t( i_ind, nrFactors() - 1 ) );
+    }
+
+    Regenerate();
+    return(*this);
+}*/
+
+
+ostream& operator << (ostream& os, const FactorGraph& fg) {
+    os << fg.nrFactors() << endl;
+
+    for( size_t I = 0; I < fg.nrFactors(); I++ ) {
+        os << endl;
+        os << fg.factor(I).vars().size() << endl;
+        for( VarSet::const_iterator i = fg.factor(I).vars().begin(); i != fg.factor(I).vars().end(); i++ )
+            os << i->label() << " ";
+        os << endl;
+        for( VarSet::const_iterator i = fg.factor(I).vars().begin(); i != fg.factor(I).vars().end(); i++ )
+            os << i->states() << " ";
+        os << endl;
+        size_t nr_nonzeros = 0;
+        for( size_t k = 0; k < fg.factor(I).stateSpace(); k++ )
+            if( fg.factor(I)[k] != 0.0 )
+                nr_nonzeros++;
+        os << nr_nonzeros << endl;
+        for( size_t k = 0; k < fg.factor(I).stateSpace(); k++ )
+            if( fg.factor(I)[k] != 0.0 ) {
+                char buf[20];
+                sprintf(buf,"%18.14g", fg.factor(I)[k]);
+                os << k << " " << buf << endl;
+            }
+    }
+
+    return(os);
+}
+
+
+istream& operator >> (istream& is, FactorGraph& fg) {
+    long verbose = 0;
+
+    try {
+        vector<Factor> factors;
+        size_t nr_f;
+        string line;
+        
+        while( (is.peek()) == '#' )
+            getline(is,line);
+        is >> nr_f;
+        if( is.fail() )
+            throw "ReadFromFile: unable to read number of Factors";
+        if( verbose >= 2 )
+            cout << "Reading " << nr_f << " factors..." << endl;
+
+        getline (is,line);
+        if( is.fail() )
+            throw "ReadFromFile: empty line expected";
+
+        for( size_t I = 0; I < nr_f; I++ ) {
+            if( verbose >= 3 )
+                cout << "Reading factor " << I << "..." << endl;
+            size_t nr_members;
+            while( (is.peek()) == '#' )
+                getline(is,line);
+            is >> nr_members;
+            if( verbose >= 3 )
+                cout << "  nr_members: " << nr_members << endl;
+
+            vector<long> labels;
+            for( size_t mi = 0; mi < nr_members; mi++ ) {
+                long mi_label;
+                while( (is.peek()) == '#' )
+                    getline(is,line);
+                is >> mi_label;
+                labels.push_back(mi_label);
+            }
+            if( verbose >= 3 ) {
+                cout << "  labels: ";
+                copy (labels.begin(), labels.end(), ostream_iterator<int>(cout, " "));
+                cout << endl;
+            }
+
+            vector<size_t> dims;
+            for( size_t mi = 0; mi < nr_members; mi++ ) {
+                size_t mi_dim;
+                while( (is.peek()) == '#' )
+                    getline(is,line);
+                is >> mi_dim;
+                dims.push_back(mi_dim);
+            }
+            if( verbose >= 3 ) {
+                cout << "  dimensions: ";
+                copy (dims.begin(), dims.end(), ostream_iterator<int>(cout, " "));
+                cout << endl;
+            }
+
+            // add the Factor
+            VarSet I_vars;
+            for( size_t mi = 0; mi < nr_members; mi++ )
+                I_vars.insert( Var(labels[mi], dims[mi]) );
+            factors.push_back(Factor(I_vars,0.0));
+            
+            // calculate permutation sigma (internally, members are sorted)
+            vector<long> sigma(nr_members,0);
+            VarSet::iterator j = I_vars.begin();
+            for( size_t mi = 0; mi < nr_members; mi++,j++ ) {
+                long search_for = j->label();
+                vector<long>::iterator j_loc = find(labels.begin(),labels.end(),search_for);
+                sigma[mi] = j_loc - labels.begin();
+            }
+            if( verbose >= 3 ) {
+                cout << "  sigma: ";
+                copy( sigma.begin(), sigma.end(), ostream_iterator<int>(cout," "));
+                cout << endl;
+            }
+
+            // calculate multindices
+            vector<size_t> sdims(nr_members,0);
+            for( size_t k = 0; k < nr_members; k++ ) {
+                sdims[k] = dims[sigma[k]];
+            }
+            multind mi(dims);
+            multind smi(sdims);
+            if( verbose >= 3 ) {
+                cout << "  mi.max(): " << mi.max() << endl;
+                cout << "       ";
+                for( size_t k=0; k < nr_members; k++ ) 
+                    cout << labels[k] << " ";
+                cout << "   ";
+                for( size_t k=0; k < nr_members; k++ ) 
+                    cout << labels[sigma[k]] << " ";
+                cout << endl;
+            }
+            
+            // read values
+            size_t nr_nonzeros;
+            while( (is.peek()) == '#' )
+                getline(is,line);
+            is >> nr_nonzeros;
+            if( verbose >= 3 ) 
+                cout << "  nonzeroes: " << nr_nonzeros << endl;
+            for( size_t k = 0; k < nr_nonzeros; k++ ) {
+                size_t li;
+                double val;
+                while( (is.peek()) == '#' )
+                    getline(is,line);
+                is >> li;
+                while( (is.peek()) == '#' )
+                    getline(is,line);
+                is >> val;
+
+                vector<size_t> vi = mi.vi(li);
+                vector<size_t> svi(vi.size(),0);
+                for( size_t k = 0; k < vi.size(); k++ )
+                    svi[k] = vi[sigma[k]];
+                size_t sli = smi.li(svi);
+                if( verbose >= 3 ) {
+                    cout << "    " << li << ": ";
+                    copy( vi.begin(), vi.end(), ostream_iterator<size_t>(cout," "));
+                    cout << "-> ";
+                    copy( svi.begin(), svi.end(), ostream_iterator<size_t>(cout," "));
+                    cout << ": " << sli << endl;
+                }
+                factors.back()[sli] = val;
+            }
+        }
+
+        if( verbose >= 3 ) {
+            cout << "factors:" << endl;
+            copy(factors.begin(), factors.end(), ostream_iterator<Factor>(cout,"\n"));
+        }
+
+        fg = FactorGraph(factors);
+    } catch (char *e) {
+        cout << e << endl;
+    }
+
+    return is;
+}
+
+
+VarSet FactorGraph::delta(const Var & n) const {
+    // calculate Markov Blanket
+    size_t i = findVar( n );
+
+    VarSet del;
+    for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ )
+        for( _nb_cit j = nb2(*I).begin(); j != nb2(*I).end(); j++ )
+            if( *j != i )
+                del |= var(*j);
+
+    return del;
+}
+
+
+VarSet FactorGraph::Delta(const Var & n) const {
+    return( delta(n) | n );
+}
+
+
+void FactorGraph::makeFactorCavity(size_t I) {
+    // fill Factor I with ones
+    factor(I).fill(1.0);
+}
+
+
+void FactorGraph::makeCavity(const Var & n) {
+    // fills all Factors that include Var n with ones
+    size_t i = findVar( n );
+
+    for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ )
+        factor(*I).fill(1.0);
+}
+
+
+/*FactorGraph & FactorGraph::DeleteFactor(size_t I) {
+    // Go through all edges
+    for( vector<_edge_t>::iterator edge = _E12.begin(); edge != _E12.end(); edge++ )
+        if( edge->second >= I ) {
+            if( edge->second == I )
+                edge->second = -1UL;
+            else 
+                (edge->second)--;
+        }
+    // Remove all edges containing I
+    for( vector<_edge_t>::iterator edge = _E12.begin(); edge != _E12.end(); edge++ )
+        if( edge->second == -1UL )
+            edge = _E12.erase( edge );
+//  vector<_edge_t>::iterator new_end = _E12.remove_if( _E12.begin(), _E12.end(), compose1( bind2nd(equal_to<size_t>(), -1), select2nd<_edge_t>() ) );
+//  _E12.erase( new_end, _E12.end() );
+
+    // Erase the factor
+    _V2.erase( _V2.begin() + I );
+    
+    Regenerate();
+
+    return *this;
+}
+
+
+FactorGraph & FactorGraph::DeleteVar(size_t i) {
+    // Go through all edges
+    for( vector<_edge_t>::iterator edge = _E12.begin(); edge != _E12.end(); edge++ )
+        if( edge->first >= i ) {
+            if( edge->first == i )
+                edge->first = -1UL;
+            else 
+                (edge->first)--;
+        }
+    // Remove all edges containing i
+    for( vector<_edge_t>::iterator edge = _E12.begin(); edge != _E12.end(); edge++ )
+        if( edge->first == -1UL )
+            edge = _E12.erase( edge );
+                
+//  vector<_edge_t>::iterator new_end = _E12.remove_if( _E12.begin(), _E12.end(), compose1( bind2nd(equal_to<size_t>(), -1), select1st<_edge_t>() ) );
+//  _E12.erase( new_end, _E12.end() );
+
+    // Erase the variable
+    _V1.erase( _V1.begin() + i );
+    
+    Regenerate();
+
+    return *this;
+}*/
+
+
+long FactorGraph::ReadFromFile(const char *filename) {
+    ifstream infile;
+    infile.open (filename);
+    if (infile.is_open()) {
+        infile >> *this;
+        infile.close();
+        return 0;
+    } else {
+        cout << "ERROR OPENING FILE" << endl;
+        return 1;
+    }
+}
+
+
+long FactorGraph::WriteToFile(const char *filename) const {
+    ofstream outfile;
+    outfile.open (filename);
+    if (outfile.is_open()) {
+        try {
+            outfile << *this;
+        } catch (char *e) {
+            cout << e << endl;
+            return 1;
+        }
+        outfile.close();
+        return 0;
+    } else {
+        cout << "ERROR OPENING FILE" << endl;
+        return 1;
+    }
+}
+
+
+long FactorGraph::WriteToDotFile(const char *filename) const {
+    ofstream outfile;
+    outfile.open (filename);
+    if (outfile.is_open()) {
+        try {
+            outfile << "graph G {" << endl;
+            outfile << "graph[size=\"9,9\"];" << endl;
+            outfile << "node[shape=circle,width=0.4,fixedsize=true];" << endl;
+            for( size_t i = 0; i < nrVars(); i++ )
+                outfile << "\tx" << var(i).label() << ";" << endl;
+            outfile << "node[shape=box,style=filled,color=lightgrey,width=0.3,height=0.3,fixedsize=true];" << endl;
+            for( size_t I = 0; I < nrFactors(); I++ )
+                outfile << "\tp" << I << ";" << endl;
+            for( size_t iI = 0; iI < nr_edges(); iI++ )
+                outfile << "\tx" << var(edge(iI).first).label() << " -- p" << edge(iI).second << ";" << endl;
+            outfile << "}" << endl;
+        } catch (char *e) {
+            cout << e << endl;
+            return 1;
+        }
+        outfile.close();
+        return 0;
+    } else {
+        cout << "ERROR OPENING FILE" << endl;
+        return 1;
+    }
+}
+
+
+bool hasShortLoops( const vector<Factor> &P ) {
+    bool found = false;
+    vector<Factor>::const_iterator I, J;
+    for( I = P.begin(); I != P.end(); I++ ) {
+        J = I;
+        J++;
+        for( ; J != P.end(); J++ )
+            if( (I->vars() & J->vars()).size() >= 2 ) {
+                found = true;
+                break;
+            }
+        if( found )
+            break;
+    }
+    return found;
+}
+
+
+void RemoveShortLoops(vector<Factor> &P) {
+    bool found = true;
+    while( found ) {
+        found = false;
+        vector<Factor>::iterator I, J;
+        for( I = P.begin(); I != P.end(); I++ ) {
+            J = I;
+            J++;
+            for( ; J != P.end(); J++ )
+                if( (I->vars() & J->vars()).size() >= 2 ) {
+                    found = true;
+                    break;
+                }
+            if( found )
+                break;
+        }
+        if( found ) {
+            cout << "Merging factors " << I->vars() << " and " << J->vars() << endl;
+            *I *= *J;
+            P.erase(J);
+        }
+    }
+}
+
+
+Factor FactorGraph::ExactMarginal(const VarSet & x) const {
+    Factor P;
+    for( size_t I = 0; I < nrFactors(); I++ )
+        P *= factor(I);
+    return P.marginal(x);
+}
+
+
+Real FactorGraph::ExactlogZ() const {
+    Factor P;
+    for( size_t I = 0; I < nrFactors(); I++ )
+        P *= factor(I);
+    return std::log(P.totalSum());
+}
+
+
+vector<VarSet> FactorGraph::Cliques() const {
+    vector<VarSet> result;
+    
+    for( size_t I = 0; I < nrFactors(); I++ ) {
+        bool maximal = true;
+        for( size_t J = 0; (J < nrFactors()) && maximal; J++ )
+            if( (factor(J).vars() >> factor(I).vars()) && !(factor(J).vars() == factor(I).vars()) )
+                maximal = false;
+        
+        if( maximal )
+            result.push_back( factor(I).vars() );
+    }
+
+    return result;
+}
+
+
+void FactorGraph::clamp( const Var & n, size_t i ) {
+    assert( i <= n.states() );
+
+/*  if( do_surgery ) {
+        size_t ni = find( vars().begin(), vars().end(), n) - vars().begin();
+
+        if( ni != nrVars() ) {
+            for( _nb_cit I = nb1(ni).begin(); I != nb1(ni).end(); I++ ) {
+                if( factor(*I).size() == 1 )
+                    // Remove this single-variable factor
+    //              I = (_V2.erase(I))--;
+                    _E12.erase( _E12.begin() + VV2E(ni, *I) );
+                else {
+                    // Replace it by the slice
+                    Index ind_I_min_n( factor(*I), factor(*I) / n );
+                    Index ind_n( factor(*I), n );
+                    Factor slice_I( factor(*I) / n );
+                    for( size_t ind_I = 0; ind_I < factor(*I).stateSpace(); ++ind_I, ++ind_I_min_n, ++ind_n )
+                        if( ind_n == i )
+                            slice_I[ind_I_min_n] = factor(*I)[ind_I];
+                    factor(*I) = slice_I;
+
+                    // Remove the edge between n and I
+                    _E12.erase( _E12.begin() + VV2E(ni, *I) );
+                }
+            }
+
+            Regenerate();
+            
+            // remove all unconnected factors
+            for( size_t I = 0; I < nrFactors(); I++ )
+                if( nb2(I).size() == 0 )
+                    DeleteFactor(I--);
+
+            DeleteVar( ni );
+
+            // FIXME
+        }
+    } */
+
+    // The cheap solution (at least in terms of coding time) is to multiply every factor
+    // that contains the variable with a delta function
+
+    Factor delta_n_i(n,0.0);
+    delta_n_i[i] = 1.0;
+
+    // For all factors that contain n
+    for( size_t I = 0; I < nrFactors(); I++ ) 
+        if( factor(I).vars() && n )
+            // Multiply it with a delta function
+            factor(I) *= delta_n_i;
+
+    return;
+}
+
+
+void FactorGraph::saveProb( size_t I ) {
+    map<size_t,Prob>::iterator it = _undoProbs.find( I );
+    if( it != _undoProbs.end() )
+        cout << "FactorGraph::saveProb:  WARNING: _undoProbs[I] already defined!" << endl;
+    _undoProbs[I] = factor(I).p();
+}
+
+
+void FactorGraph::undoProb( size_t I ) {
+    map<size_t,Prob>::iterator it = _undoProbs.find( I );
+    if( it != _undoProbs.end() ) {
+        factor(I).p() = (*it).second;
+        _undoProbs.erase(it);
+    }
+}
+
+
+void FactorGraph::saveProbs( const VarSet &ns ) {
+    if( !_undoProbs.empty() )
+        cout << "FactorGraph::saveProbs:  WARNING: _undoProbs not empy!" << endl;
+    for( size_t I = 0; I < nrFactors(); I++ )
+        if( factor(I).vars() && ns )
+            _undoProbs[I] = factor(I).p();
+}
+
+
+void FactorGraph::undoProbs( const VarSet &ns ) {
+    for( map<size_t,Prob>::iterator uI = _undoProbs.begin(); uI != _undoProbs.end(); ) {
+        if( factor((*uI).first).vars() && ns ) {
+//          cout << "undoing " << factor((*uI).first).vars() << endl;
+//          cout << "from " << factor((*uI).first).p() << " to " << (*uI).second << endl;
+            factor((*uI).first).p() = (*uI).second;
+            _undoProbs.erase(uI++);
+        } else
+            uI++;
+    }
+}
+
+
+bool FactorGraph::isConnected() const {
+    if( nrVars() == 0 )
+        return false;
+    else {
+        Var n = var( 0 );
+
+        VarSet component = n;
+
+        VarSet remaining;
+        for( size_t i = 1; i < nrVars(); i++ )
+            remaining |= var(i);
+
+        bool found_new_vars = true;
+        while( found_new_vars ) {
+            VarSet new_vars;
+            for( VarSet::const_iterator m = remaining.begin(); m != remaining.end(); m++ )
+                if( delta(*m) && component )
+                    new_vars |= *m;
+
+            if( new_vars.empty() )
+                found_new_vars = false;
+            else 
+                found_new_vars = true;
+
+            component |= new_vars;
+            remaining /= new_vars;
+        };
+        return remaining.empty();
+    }
+}
diff --git a/factorgraph.h b/factorgraph.h
new file mode 100644 (file)
index 0000000..9723a96
--- /dev/null
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __FACTORGRAPH_H__
+#define __FACTORGRAPH_H__
+
+
+#include <iostream>
+#include <map>
+#include "bipgraph.h"
+#include "factor.h"
+
+
+using namespace std;
+
+
+class FactorGraph : public BipartiteGraph<Var,Factor> {
+    protected:
+        map<size_t,Prob>    _undoProbs;
+        bool                _hasNegatives;
+        Prob::NormType      _normtype;
+
+    public:
+        /// Default constructor
+        FactorGraph() : BipartiteGraph<Var,Factor>(), _undoProbs(), _hasNegatives(false), _normtype(Prob::NORMPROB) {};
+        /// Copy constructor
+        FactorGraph(const FactorGraph & x) : BipartiteGraph<Var,Factor>(x), _undoProbs(), _hasNegatives(x._hasNegatives), _normtype(x._normtype) {};
+        /// Construct FactorGraph from vector of Factors
+        FactorGraph(const vector<Factor> &P);
+        /// Assignment operator
+        FactorGraph & operator=(const FactorGraph & x) {
+            if(this!=&x) {
+                BipartiteGraph<Var,Factor>::operator=(x);
+                _undoProbs      = x._undoProbs;
+                _hasNegatives   = x._hasNegatives;
+                _normtype       = x._normtype;
+            }
+            return *this;
+        }
+        virtual ~FactorGraph() {}
+
+        // aliases
+        Var & var(size_t i) { return V1(i); }
+        const Var & var(size_t i) const { return V1(i); }
+        const vector<Var> & vars() const { return V1s(); }
+        vector<Var> & vars() { return V1s(); }
+        size_t nrVars() const { return V1s().size(); }
+        Factor & factor(size_t I) { return V2(I); }
+        const Factor & factor(size_t I) const { return V2(I); }
+        const vector<Factor> & factors() const { return V2s(); }
+        vector<Factor> & factors() { return V2s(); }
+        size_t nrFactors() const { return V2s().size(); }
+
+        /// Provides read access to neighbours of variable
+        const _nb_t & nbV( size_t i1 ) const { return nb1(i1); }
+        /// Provides full access to neighbours of variable
+        _nb_t & nbV( size_t i1 ) { return nb1(i1); }
+        /// Provides read access to neighbours of factor
+        const _nb_t & nbF( size_t i2 ) const { return nb2(i2); }
+        /// Provides full access to neighbours of factor
+        _nb_t & nbF( size_t i2 ) { return nb2(i2); }
+
+        size_t findVar(const Var & n) const {
+            size_t i = find( vars().begin(), vars().end(), n ) - vars().begin();
+            assert( i != nrVars() );
+            return i;
+        }
+        size_t findFactor(const VarSet &ns) const {
+            size_t I;
+            for( I = 0; I < nrFactors(); I++ )
+                if( factor(I).vars() == ns )
+                    break;
+            assert( I != nrFactors() );
+            return I;
+        }
+
+        friend ostream& operator << (ostream& os, const FactorGraph& fg);
+        friend istream& operator >> (istream& is, FactorGraph& fg);
+
+        VarSet delta(const Var & n) const;
+        VarSet Delta(const Var & n) const;
+        virtual void makeFactorCavity(size_t I);
+        virtual void makeCavity(const Var & n);
+
+        long ReadFromFile(const char *filename);
+        long WriteToFile(const char *filename) const;
+        long WriteToDotFile(const char *filename) const;
+
+        Factor ExactMarginal(const VarSet & x) const;
+        Real ExactlogZ() const;
+
+        virtual void clamp( const Var & n, size_t i );
+        
+        bool hasNegatives() const { return _hasNegatives; }
+        Prob::NormType NormType() const { return _normtype; }
+        
+        vector<VarSet> Cliques() const;
+
+        virtual void undoProbs( const VarSet &ns );
+        void saveProbs( const VarSet &ns );
+        virtual void undoProb( size_t I );
+        void saveProb( size_t I );
+
+        bool isConnected() const;
+
+        virtual void updatedFactor( size_t I ) {};
+};
+
+
+bool hasShortLoops(const vector<Factor> &P);
+void RemoveShortLoops(vector<Factor> &P);
+
+
+#endif
diff --git a/hak.cpp b/hak.cpp
new file mode 100644 (file)
index 0000000..453ec54
--- /dev/null
+++ b/hak.cpp
@@ -0,0 +1,404 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <map>
+#include "hak.h"
+#include "util.h"
+#include "diffs.h"
+
+
+const char *HAK::Name = "HAK";
+
+
+bool HAK::checkProperties() {
+    if( !HasProperty("tol") )
+        return false;
+    if (!HasProperty("maxiter") )
+        return false;
+    if (!HasProperty("verbose") )
+        return false;
+    if( !HasProperty("doubleloop") )
+        return false;
+    if( !HasProperty("clusters") )
+        return false;
+    
+    ConvertPropertyTo<double>("tol");
+    ConvertPropertyTo<size_t>("maxiter");
+    ConvertPropertyTo<size_t>("verbose");
+    ConvertPropertyTo<bool>("doubleloop");
+    ConvertPropertyTo<ClustersType>("clusters");
+
+    if( HasProperty("loopdepth") )
+        ConvertPropertyTo<size_t>("loopdepth");
+    else if( Clusters() == ClustersType::LOOP )
+        return false;
+
+    return true;
+}
+
+
+void HAK::constructMessages() {
+    // Create outer beliefs
+    _Qa.clear();
+    _Qa.reserve(nr_ORs());
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        _Qa.push_back( Factor( OR(alpha).vars() ) );
+
+    // Create inner beliefs
+    _Qb.clear();
+    _Qb.reserve(nr_IRs());
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        _Qb.push_back( Factor( IR(beta) ) );
+    
+    // Create messages
+    _muab.clear();
+    _muab.reserve(nr_Redges());
+    _muba.clear();
+    _muba.reserve(nr_Redges());
+    for( vector<R_edge_t>::const_iterator ab = Redges().begin(); ab != Redges().end(); ab++ ) {
+        _muab.push_back( Factor( IR(ab->second) ) );
+        _muba.push_back( Factor( IR(ab->second) ) );
+    }
+}
+
+
+HAK::HAK(const RegionGraph & rg, const Properties &opts) : DAIAlgRG(rg, opts) {
+    assert( checkProperties() );
+
+    constructMessages();
+}
+
+
+void HAK::findLoopClusters( const FactorGraph & fg, set<VarSet> &allcl, VarSet newcl, const Var & root, size_t length, VarSet vars ) {
+    for( VarSet::const_iterator in = vars.begin(); in != vars.end(); in++ ) {
+        VarSet ind = fg.delta( *in );
+        if( (newcl.size()) >= 2 && (ind >> root) ) {
+            allcl.insert( newcl | *in );
+        }
+        else if( length > 1 )
+            findLoopClusters( fg, allcl, newcl | *in, root, length - 1, ind / newcl );
+    }
+}
+
+
+HAK::HAK(const FactorGraph & fg, const Properties &opts) : DAIAlgRG(opts) {
+    assert( checkProperties() );
+
+    vector<VarSet> cl;
+    if( Clusters() == ClustersType::MIN ) {
+        cl = fg.Cliques();
+    } else if( Clusters() == ClustersType::DELTA ) {
+        for( size_t i = 0; i < fg.nrVars(); i++ )
+            cl.push_back(fg.Delta(fg.var(i))); 
+    } else if( Clusters() == ClustersType::LOOP ) {
+        cl = fg.Cliques();
+        set<VarSet> scl;
+        for( vector<Var>::const_iterator i0 = fg.vars().begin(); i0 != fg.vars().end(); i0++ ) {
+            VarSet i0d = fg.delta(*i0);
+            if( LoopDepth() > 1 )
+                findLoopClusters( fg, scl, *i0, *i0, LoopDepth() - 1, fg.delta(*i0) );
+        }
+        for( set<VarSet>::const_iterator c = scl.begin(); c != scl.end(); c++ )
+            cl.push_back(*c);
+        if( Verbose() >= 3 ) {
+            cout << "HAK uses the following clusters: " << endl;
+            for( vector<VarSet>::const_iterator cli = cl.begin(); cli != cl.end(); cli++ )
+                cout << *cli << endl;
+        }
+    } else
+        throw "Invalid Clusters type";
+
+    RegionGraph rg(fg,cl);
+    RegionGraph::operator=(rg);
+    constructMessages();
+
+    if( Verbose() >= 3 )
+        cout << "HAK regiongraph: " << *this << endl;
+}
+
+
+string HAK::identify() const { 
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+void HAK::init( const VarSet &ns ) {
+    for( vector<Factor>::iterator alpha = _Qa.begin(); alpha != _Qa.end(); alpha++ )
+        if( alpha->vars() && ns )
+            alpha->fill( 1.0 / alpha->stateSpace() );
+
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        if( IR(beta) && ns ) {
+            _Qb[beta].fill( 1.0 / IR(beta).stateSpace() );
+            for( R_nb_cit alpha = nbIR(beta).begin(); alpha != nbIR(beta).end(); alpha++ ) {
+                muab(*alpha,beta).fill( 1.0 / IR(beta).stateSpace() );
+                muba(beta,*alpha).fill( 1.0 / IR(beta).stateSpace() );
+            }
+        }
+}
+
+
+void HAK::init() {
+    assert( checkProperties() );
+
+    for( vector<Factor>::iterator alpha = _Qa.begin(); alpha != _Qa.end(); alpha++ )
+        alpha->fill( 1.0 / alpha->stateSpace() );
+
+    for( vector<Factor>::iterator beta = _Qb.begin(); beta != _Qb.end(); beta++ )
+        beta->fill( 1.0 / beta->stateSpace() );
+
+    for( size_t ab = 0; ab < nr_Redges(); ab++ ) {
+        _muab[ab].fill( 1.0 / _muab[ab].stateSpace() );
+        _muba[ab].fill( 1.0 / _muba[ab].stateSpace() );
+    }
+}
+
+
+double HAK::doGBP() {
+    if( Verbose() >= 1 )
+        cout << "Starting " << identify() << "...";
+    if( Verbose() >= 3)
+        cout << endl;
+
+    clock_t tic = toc();
+
+    // Check whether counting numbers won't lead to problems
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        assert( nbIR(beta).size() + IR(beta).c() != 0.0 );
+
+    // Keep old beliefs to check convergence
+    vector<Factor> old_beliefs;
+    old_beliefs.reserve( nrVars() );
+    for( size_t i = 0; i < nrVars(); i++ )
+        old_beliefs.push_back( belief( var(i) ) );
+
+    // Differences in single node beliefs
+    Diffs diffs(nrVars(), 1.0);
+
+    size_t iter = 0;
+    // do several passes over the network until maximum number of iterations has
+    // been reached or until the maximum belief difference is smaller than tolerance
+    for( iter = 0; iter < MaxIter() && diffs.max() > Tol(); iter++ ) {
+        for( size_t beta = 0; beta < nr_IRs(); beta++ ) {
+            for( R_nb_cit alpha = nbIR(beta).begin(); alpha != nbIR(beta).end(); alpha++ )
+                muab(*alpha,beta) = _Qa[*alpha].marginal(IR(beta)).divided_by( muba(beta,*alpha) );
+
+            Factor Qb_new;
+            for( R_nb_cit alpha = nbIR(beta).begin(); alpha != nbIR(beta).end(); alpha++ )
+                Qb_new *= muab(*alpha,beta) ^ (1 / (nbIR(beta).size() + IR(beta).c()));
+            Qb_new.normalize( _normtype );
+            if( Qb_new.hasNaNs() ) {
+                cout << "HAK::doGBP:  Qb_new has NaNs!" << endl;
+                return NAN;
+            }
+//          _Qb[beta] = Qb_new.makeZero(1e-100);    // damping?
+            _Qb[beta] = Qb_new;
+
+            for( R_nb_cit alpha = nbIR(beta).begin(); alpha != nbIR(beta).end(); alpha++ ) {
+                muba(beta,*alpha) = _Qb[beta].divided_by( muab(*alpha,beta) );
+
+                Factor Qa_new = OR(*alpha);
+                for( R_nb_cit gamma = nbOR(*alpha).begin(); gamma != nbOR(*alpha).end(); gamma++ )
+                    Qa_new *= muba(*gamma,*alpha);
+                Qa_new ^= (1.0 / OR(*alpha).c());
+                Qa_new.normalize( _normtype );
+                if( Qa_new.hasNaNs() ) {
+                    cout << "HAK::doGBP:  Qa_new has NaNs!" << endl;
+                    return NAN;
+                }
+//              _Qa[*alpha] = Qa_new.makeZero(1e-100); // damping?
+                _Qa[*alpha] = Qa_new;
+            }
+        }
+
+        // Calculate new single variable beliefs and compare with old ones
+        for( size_t i = 0; i < nrVars(); i++ ) {
+            Factor new_belief = belief( var( i ) );
+            diffs.push( dist( new_belief, old_beliefs[i], Prob::DISTLINF ) );
+            old_beliefs[i] = new_belief;
+        }
+
+        if( Verbose() >= 3 )
+            cout << "HAK::doGBP:  maxdiff " << diffs.max() << " after " << iter+1 << " passes" << endl;
+    }
+
+    updateMaxDiff( diffs.max() );
+
+    if( Verbose() >= 1 ) {
+        if( diffs.max() > Tol() ) {
+            if( Verbose() == 1 )
+                cout << endl;
+            cout << "HAK::doGBP:  WARNING: not converged within " << MaxIter() << " passes (" << toc() - tic << " clocks)...final maxdiff:" << diffs.max() << endl;
+        } else {
+            if( Verbose() >= 2 )
+                cout << "HAK::doGBP:  ";
+            cout << "converged in " << iter << " passes (" << toc() - tic << " clocks)." << endl;
+        }
+    }
+
+    return diffs.max();
+}
+
+
+double HAK::doDoubleLoop() {
+    if( Verbose() >= 1 )
+        cout << "Starting " << identify() << "...";
+    if( Verbose() >= 3)
+        cout << endl;
+
+    clock_t tic = toc();
+
+    // Save original outer regions
+    vector<FRegion> org_ORs = ORs();
+
+    // Save original inner counting numbers and set negative counting numbers to zero
+    vector<double> org_IR_cs( nr_IRs(), 0.0 );
+    for( size_t beta = 0; beta < nr_IRs(); beta++ ) {
+        org_IR_cs[beta] = IR(beta).c();
+        if( IR(beta).c() < 0.0 )
+            IR(beta).c() = 0.0;
+    }
+
+    // Keep old beliefs to check convergence
+    vector<Factor> old_beliefs;
+    old_beliefs.reserve( nrVars() );
+    for( size_t i = 0; i < nrVars(); i++ )
+        old_beliefs.push_back( belief( var(i) ) );
+
+    // Differences in single node beliefs
+    Diffs diffs(nrVars(), 1.0);
+
+    size_t   outer_maxiter   = MaxIter();
+    double  outer_tol       = Tol();
+    size_t   outer_verbose   = Verbose();
+    double  org_maxdiff     = MaxDiff();
+
+    // Set parameters for inner loop
+    MaxIter( 5 );
+    Verbose( outer_verbose ? outer_verbose - 1 : 0 );
+
+    size_t outer_iter = 0;
+    for( outer_iter = 0; outer_iter < outer_maxiter && diffs.max() > outer_tol; outer_iter++ ) {
+        // Calculate new outer regions
+        for( size_t alpha = 0; alpha < nr_ORs(); alpha++ ) {
+            OR(alpha) = org_ORs[alpha];
+            for( R_nb_cit beta = nbOR(alpha).begin(); beta != nbOR(alpha).end(); beta++ )
+                OR(alpha) *= _Qb[*beta] ^ ((IR(*beta).c() - org_IR_cs[*beta]) / nbIR(*beta).size());
+        }
+
+        // Inner loop
+        if( isnan( doGBP() ) )
+            return NAN;
+
+        // Calculate new single variable beliefs and compare with old ones
+        for( size_t i = 0; i < nrVars(); i++ ) {
+            Factor new_belief = belief( var( i ) );
+            diffs.push( dist( new_belief, old_beliefs[i], Prob::DISTLINF ) );
+            old_beliefs[i] = new_belief;
+        }
+
+        if( Verbose() >= 3 )
+            cout << "HAK::doDoubleLoop:  maxdiff " << diffs.max() << " after " << outer_iter+1 << " passes" << endl;
+    }
+
+    // restore _maxiter, _verbose and _maxdiff
+    MaxIter( outer_maxiter );
+    Verbose( outer_verbose );
+    MaxDiff( org_maxdiff );
+
+    updateMaxDiff( diffs.max() );
+
+    // Restore original outer regions
+    ORs() = org_ORs;
+
+    // Restore original inner counting numbers
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        IR(beta).c() = org_IR_cs[beta];
+
+    if( Verbose() >= 1 ) {
+        if( diffs.max() > Tol() ) {
+            if( Verbose() == 1 )
+                cout << endl;
+                cout << "HAK::doDoubleLoop:  WARNING: not converged within " << outer_maxiter << " passes (" << toc() - tic << " clocks)...final maxdiff:" << diffs.max() << endl;
+            } else {
+                if( Verbose() >= 3 )
+                    cout << "HAK::doDoubleLoop:  ";
+                cout << "converged in " << outer_iter << " passes (" << toc() - tic << " clocks)." << endl;
+            }
+        }
+
+    return diffs.max();
+}
+
+
+double HAK::run() {
+    if( DoubleLoop() )
+        return doDoubleLoop();
+    else
+        return doGBP();
+}
+
+
+Factor HAK::belief( const VarSet &ns ) const {
+    vector<Factor>::const_iterator beta;
+    for( beta = _Qb.begin(); beta != _Qb.end(); beta++ )
+        if( beta->vars() >> ns )
+            break;
+    if( beta != _Qb.end() )
+        return( beta->marginal(ns) );
+    else {
+        vector<Factor>::const_iterator alpha;
+        for( alpha = _Qa.begin(); alpha != _Qa.end(); alpha++ )
+            if( alpha->vars() >> ns )
+                break;
+        assert( alpha != _Qa.end() );
+        return( alpha->marginal(ns) );
+    }
+}
+
+
+Factor HAK::belief( const Var &n ) const {
+    return belief( (VarSet)n );
+}
+
+
+vector<Factor> HAK::beliefs() const {
+    vector<Factor> result;
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        result.push_back( Qb(beta) );
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        result.push_back( Qa(alpha) );
+    return result;
+}
+
+
+Complex HAK::logZ() const {
+    Complex sum = 0.0;
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        sum += Complex(IR(beta).c()) * Qb(beta).entropy();
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ ) {
+        sum += Complex(OR(alpha).c()) * Qa(alpha).entropy();
+        sum += (OR(alpha).log0() * Qa(alpha)).totalSum();
+    }
+    return sum;
+}
diff --git a/hak.h b/hak.h
new file mode 100644 (file)
index 0000000..9867f2d
--- /dev/null
+++ b/hak.h
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __HAK_H__
+#define __HAK_H__
+
+
+#include "daialg.h"
+#include "regiongraph.h"
+#include "enum.h"
+
+
+using namespace std;
+
+
+/// HAK provides an implementation of the single and double-loop algorithms by Heskes, Albers and Kappen
+class HAK : public DAIAlgRG {
+    protected:
+        vector<Factor>          _Qa;
+        vector<Factor>          _Qb;
+        vector<Factor>          _muab;
+        vector<Factor>          _muba;
+        
+    public:
+        /// Default constructor
+        HAK() : DAIAlgRG() {};
+
+        /// Copy constructor
+        HAK(const HAK & x) : DAIAlgRG(x), _Qa(x._Qa), _Qb(x._Qb), _muab(x._muab), _muba(x._muba) {};
+
+        /// Clone function
+        HAK* clone() const { return new HAK(*this); }
+        
+        /// Construct from RegionGraph
+        HAK(const RegionGraph & rg, const Properties &opts);
+
+        /// Construct from RactorGraph using "clusters" option
+        HAK(const FactorGraph & fg, const Properties &opts);
+
+        /// Assignment operator
+        HAK & operator=(const HAK & x) {
+            if( this != &x ) {
+                DAIAlgRG::operator=(x);
+                _Qa         = x._Qa;
+                _Qb         = x._Qb;
+                _muab       = x._muab;
+                _muba       = x._muba;
+            }
+            return *this;
+        }
+        
+        static const char *Name;
+
+        ENUM3(ClustersType,MIN,DELTA,LOOP)
+        ClustersType Clusters() const { return GetPropertyAs<ClustersType>("clusters"); }
+        bool DoubleLoop() { return GetPropertyAs<bool>("doubleloop"); }
+        size_t LoopDepth() { return GetPropertyAs<size_t>("loopdepth"); }
+
+        Factor & muab( size_t alpha, size_t beta ) { return _muab[ORIR2E(alpha,beta)]; }
+        Factor & muba( size_t beta, size_t alpha ) { return _muba[ORIR2E(alpha,beta)]; }
+        const Factor& Qa( size_t alpha ) const { return _Qa[alpha]; };
+        const Factor& Qb( size_t beta ) const { return _Qb[beta]; };
+
+//      void Regenerate();
+        double doGBP();
+        double doDoubleLoop();
+        double run();
+        void init();
+        string identify() const;
+        Factor belief( const Var &n ) const;
+        Factor belief( const VarSet &ns ) const;
+        vector<Factor> beliefs() const;
+        Complex logZ () const;
+
+        void init( const VarSet &ns );
+        void undoProbs( const VarSet &ns ) { RegionGraph::undoProbs( ns ); init( ns ); }
+        bool checkProperties();
+
+    private:
+        void constructMessages();
+        void findLoopClusters( const FactorGraph &fg, set<VarSet> &allcl, VarSet newcl, const Var & root, size_t length, VarSet vars );
+};
+
+
+#endif
diff --git a/index.h b/index.h
new file mode 100644 (file)
index 0000000..ee7443c
--- /dev/null
+++ b/index.h
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __INDEX_H__
+#define __INDEX_H__
+
+#include <vector>
+#include "varset.h"
+
+/* Example:
+ *
+ * Index i ({s_j_1,s_j_2,...,s_j_m}, {s_1,...,s_N});    // j_k in {1,...,N}
+ * for( ; i>=0; ++i ) {
+ *      // loops over all states of (s_1,...,s_N)
+ *      // i is linear index of corresponding state of (s_j_1, ..., s_j_m)
+ * }
+ */
+
+class Index
+{
+private:
+    long _index;
+    std::vector<int> _count,_max,_sum;
+public:
+    Index () { _index=-1; };
+    Index (const VarSet& P, const VarSet& ns)
+    {
+        long sum=1;
+        VarSet::const_iterator j=ns.begin();
+        for(VarSet::const_iterator i=P.begin();i!=P.end();++i)
+        {
+            for(;j!=ns.end()&&j->label()<=i->label();++j)
+            {
+                _count.push_back(0);
+                _max.push_back(j->states());
+                _sum.push_back((i->label()==j->label())?sum:0);
+            };
+            sum*=i->states();
+        };
+        for(;j!=ns.end();++j)
+        {
+            _count.push_back(0);
+            _max.push_back(j->states());
+            _sum.push_back(0);
+        };
+        _index=0;
+    };
+    Index (const Index & ind) : _index(ind._index), _count(ind._count), _max(ind._max), _sum(ind._sum) {};
+    Index & operator=(const Index & ind) {
+        if(this!=&ind) {
+            _index = ind._index;
+            _count = ind._count;
+            _max = ind._max;
+            _sum = ind._sum;
+        }
+        return *this;
+    }
+    Index& clear ()
+    {
+        for(unsigned i=0;i!=_count.size();++i) _count[i]=0;
+        _index=0;
+        return(*this);
+    };
+    operator long () const { return(_index); };
+    Index& operator ++ ()
+    {
+        if(_index>=0)
+        {
+            unsigned i;
+            for(i=0;(i<_count.size())
+                    &&(_index+=_sum[i],++_count[i]==_max[i]);++i)
+            {
+                _index-=_sum[i]*_max[i];
+                _count[i]=0;
+            };
+            if(i==_count.size()) _index=-1;
+        };
+        return(*this);
+    };
+};
+
+class multind {
+    private:
+        std::vector<size_t> _dims;  // dimensions
+        std::vector<size_t> _pdims; // products of dimensions
+
+    public:
+        multind(const std::vector<size_t> di) {
+            _dims = di;
+            size_t prod = 1;
+            for( std::vector<size_t>::const_iterator i=di.begin(); i!=di.end(); i++ ) {
+                _pdims.push_back(prod);
+                prod = prod * (*i);
+            }
+            _pdims.push_back(prod);
+        }
+        multind(const VarSet& ns) {
+            _dims.reserve( ns.size() ); 
+            _pdims.reserve( ns.size() + 1 ); 
+            size_t prod = 1;
+            for( VarSet::const_iterator n = ns.begin(); n != ns.end(); n++ ) {
+                _pdims.push_back( prod );
+                prod *= n->states();
+                _dims.push_back( n->states() );
+            }
+            _pdims.push_back( prod );
+        }
+        std::vector<size_t> vi(size_t li) const {   // linear index to vector index
+            std::vector<size_t> v(_dims.size(),0);
+            assert(li >= 0 && li < _pdims.back());
+            for( long j = v.size()-1; j >= 0; j-- ) {
+                size_t q = li / _pdims[j];
+                v[j] = q;
+                li = li - q * _pdims[j];
+            }
+            return v;
+        }
+        size_t li(const std::vector<size_t> vi) const { // linear index
+            size_t s = 0;
+            assert(vi.size() == _dims.size());
+            for( size_t j = 0; j < vi.size(); j++ ) 
+                s += vi[j] * _pdims[j];
+            return s;
+        }
+        size_t max() const { return( _pdims.back() ); };
+
+        // FIXME add an iterator, which increases a vector index just using addition
+};
+
+#endif
diff --git a/jtree.cpp b/jtree.cpp
new file mode 100644 (file)
index 0000000..51018fb
--- /dev/null
+++ b/jtree.cpp
@@ -0,0 +1,506 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include "jtree.h"
+
+
+using namespace std;
+
+
+const char *JTree::Name = "JTREE";
+
+
+bool JTree::checkProperties() {
+    if (!HasProperty("verbose") )
+        return false;
+    if( !HasProperty("updates") )
+        return false;
+    
+    ConvertPropertyTo<size_t>("verbose");
+    ConvertPropertyTo<UpdateType>("updates");
+
+    return true;
+}
+
+
+JTree::JTree( const FactorGraph &fg, const Properties &opts, bool automatic) : DAIAlgRG(fg, opts), _RTree(), _Qa(), _Qb(), _mes(), _logZ() {
+    assert( checkProperties() );
+
+    if( automatic ) {
+        ClusterGraph _cg;
+
+        // Copy factors
+        for( size_t I = 0; I < nrFactors(); I++ )
+            _cg.insert( factor(I).vars() );
+        if( Verbose() >= 3 )
+            cout << "Initial clusters: " << _cg << endl;
+
+        // Retain only maximal clusters
+        _cg.eraseNonMaximal();
+        if( Verbose() >= 3 )
+            cout << "Maximal clusters: " << _cg << endl;
+
+        vector<VarSet> ElimVec = _cg.VarElim_MinFill().eraseNonMaximal().toVector();
+        if( Verbose() >= 3 ) {
+            cout << "VarElim_MinFill result: {" << endl;
+            for( size_t i = 0; i < ElimVec.size(); i++ ) {
+                if( i != 0 )
+                    cout << ", ";
+                cout << ElimVec[i];
+            }
+            cout << "}" << endl;
+        }
+
+        GenerateJT( ElimVec );
+    }
+}
+
+
+void JTree::GenerateJT( const vector<VarSet> &Cliques ) {
+    // Construct a weighted graph (each edge is weighted with the cardinality 
+    // of the intersection of the nodes, where the nodes are the elements of
+    // Cliques).
+    WeightedGraph<int> JuncGraph;
+    for( size_t i = 0; i < Cliques.size(); i++ )
+        for( size_t j = i+1; j < Cliques.size(); j++ ) {
+            size_t w = (Cliques[i] & Cliques[j]).size();
+            JuncGraph[UEdge(i,j)] = w;
+        }
+    
+    // Construct maximal spanning tree using Prim's algorithm
+    _RTree = MaxSpanningTreePrim( JuncGraph );
+
+    // Construct corresponding region graph
+
+    // Create outer regions
+    ORs().reserve( Cliques.size() );
+    for( size_t i = 0; i < Cliques.size(); i++ )
+        ORs().push_back( FRegion( Factor(Cliques[i], 1.0), 1.0 ) );
+
+    // For each factor, find an outer region that subsumes that factor.
+    // Then, multiply the outer region with that factor.
+    for( size_t I = 0; I < nrFactors(); I++ ) {
+        size_t alpha;
+        for( alpha = 0; alpha < nr_ORs(); alpha++ )
+            if( OR(alpha).vars() >> factor(I).vars() ) {
+//              OR(alpha) *= factor(I);
+                _fac2OR[I] = alpha;
+                break;
+            }
+        assert( alpha != nr_ORs() );
+    }
+    RecomputeORs();
+
+    // Create inner regions and edges
+    IRs().reserve( _RTree.size() );
+    Redges().reserve( 2 * _RTree.size() );
+    for( size_t i = 0; i < _RTree.size(); i++ ) {
+        Redges().push_back( R_edge_t( _RTree[i].n1, IRs().size() ) );
+        Redges().push_back( R_edge_t( _RTree[i].n2, IRs().size() ) );
+        // inner clusters have counting number -1
+        IRs().push_back( Region( Cliques[_RTree[i].n1] & Cliques[_RTree[i].n2], -1.0 ) );
+    }
+
+    // Regenerate BipartiteGraph internals
+    Regenerate();
+
+    // Create messages and beliefs
+    _Qa.clear();
+    _Qa.reserve( nr_ORs() );
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        _Qa.push_back( OR(alpha) );
+
+    _Qb.clear();
+    _Qb.reserve( nr_IRs() );
+    for( size_t beta = 0; beta < nr_IRs(); beta++ ) 
+        _Qb.push_back( Factor( IR(beta), 1.0 ) );
+
+    _mes.clear();
+    _mes.reserve( nr_Redges() );
+    for( size_t e = 0; e < nr_Redges(); e++ )
+        _mes.push_back( Factor( IR(Redge(e).second), 1.0 ) );
+
+    // Check counting numbers
+    Check_Counting_Numbers();
+
+    if( Verbose() >= 3 ) {
+        cout << "Resulting regiongraph: " << *this << endl;
+    }
+}
+
+
+string JTree::identify() const {
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+Factor JTree::belief( const VarSet &ns ) const {
+    vector<Factor>::const_iterator beta;
+    for( beta = _Qb.begin(); beta != _Qb.end(); beta++ )
+        if( beta->vars() >> ns )
+            break;
+    if( beta != _Qb.end() )
+        return( beta->marginal(ns) );
+    else {
+        vector<Factor>::const_iterator alpha;
+        for( alpha = _Qa.begin(); alpha != _Qa.end(); alpha++ )
+            if( alpha->vars() >> ns )
+                break;
+        assert( alpha != _Qa.end() );
+        return( alpha->marginal(ns) );
+    }
+}
+
+
+vector<Factor> JTree::beliefs() const {
+    vector<Factor> result;
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        result.push_back( _Qb[beta] );
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        result.push_back( _Qa[alpha] );
+    return result;
+}
+
+
+Factor JTree::belief( const Var &n ) const {
+    return belief( (VarSet)n );
+}
+
+
+// Needs no init
+void JTree::runHUGIN() {
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        _Qa[alpha] = OR(alpha);
+
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        _Qb[beta].fill( 1.0 );
+
+    // CollectEvidence
+    _logZ = 0.0;
+    for( size_t i = _RTree.size(); (i--) != 0; ) {
+//      Make outer region _RTree[i].n1 consistent with outer region _RTree[i].n2
+//      IR(i) = seperator OR(_RTree[i].n1) && OR(_RTree[i].n2)
+        Factor new_Qb = _Qa[_RTree[i].n2].part_sum( IR( i ) );
+        _logZ += log(new_Qb.normalize( Prob::NORMPROB ));
+        _Qa[_RTree[i].n1] *= new_Qb.divided_by( _Qb[i] ); 
+        _Qb[i] = new_Qb;
+    }
+    if( _RTree.empty() )
+        _logZ += log(_Qa[0].normalize( Prob::NORMPROB ) );
+    else
+        _logZ += log(_Qa[_RTree[0].n1].normalize( Prob::NORMPROB ));
+
+    // DistributeEvidence
+    for( size_t i = 0; i < _RTree.size(); i++ ) {
+//      Make outer region _RTree[i].n2 consistent with outer region _RTree[i].n1
+//      IR(i) = seperator OR(_RTree[i].n1) && OR(_RTree[i].n2)
+        Factor new_Qb = _Qa[_RTree[i].n1].marginal( IR( i ) );
+        _Qa[_RTree[i].n2] *= new_Qb.divided_by( _Qb[i] ); 
+        _Qb[i] = new_Qb;
+    }
+
+    // Normalize
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ )
+        _Qa[alpha].normalize( Prob::NORMPROB );
+}
+
+
+// Really needs no init! Initial messages can be anything.
+void JTree::runShaferShenoy() {
+    // First pass
+    _logZ = 0.0;
+    for( size_t e = _RTree.size(); (e--) != 0; ) {
+        // send a message from _RTree[e].n2 to _RTree[e].n1
+        // or, actually, from the seperator IR(e) to _RTree[e].n1
+
+        size_t i = _RTree[e].n2;
+        size_t j = _RTree[e].n1;
+        
+        Factor piet = OR(i);
+        for( R_nb_cit k = nbOR(i).begin(); k != nbOR(i).end(); k++ )
+            if( *k != e )
+                piet *= message( i, *k );
+        message( j, e ) = piet.part_sum( IR(e) );
+        _logZ += log( message(j,e).normalize( Prob::NORMPROB ) );
+    }
+
+    // Second pass
+    for( size_t e = 0; e < _RTree.size(); e++ ) {
+        size_t i = _RTree[e].n1;
+        size_t j = _RTree[e].n2;
+        
+        Factor piet = OR(i);
+        for( R_nb_cit k = nbOR(i).begin(); k != nbOR(i).end(); k++ )
+            if( *k != e )
+                piet *= message( i, *k );
+        message( j, e ) = piet.marginal( IR(e) );
+    }
+
+    // Calculate beliefs
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ ) {
+        Factor piet = OR(alpha);
+        for( R_nb_cit k = nbOR(alpha).begin(); k != nbOR(alpha).end(); k++ )
+            piet *= message( alpha, *k );
+        if( _RTree.empty() ) {
+            _logZ += log( piet.normalize( Prob::NORMPROB ) );
+            _Qa[alpha] = piet;
+        } else if( alpha == _RTree[0].n1 ) {
+            _logZ += log( piet.normalize( Prob::NORMPROB ) );
+            _Qa[alpha] = piet;
+        } else
+            _Qa[alpha] = piet.normalized( Prob::NORMPROB );
+    }
+
+    // Only for logZ (and for belief)...
+    for( size_t beta = 0; beta < nr_IRs(); beta++ ) 
+        _Qb[beta] = _Qa[nbIR(beta)[0]].marginal( IR(beta) );
+}
+
+
+double JTree::run() {
+    if( Updates() == UpdateType::HUGIN )
+        runHUGIN();
+    else if( Updates() == UpdateType::SHSH )
+        runShaferShenoy();
+    return 0.0;
+}
+
+
+Complex JTree::logZ() const {
+    Complex sum = 0.0;
+    for( size_t beta = 0; beta < nr_IRs(); beta++ )
+        sum += Complex(IR(beta).c()) * _Qb[beta].entropy();
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ ) {
+        sum += Complex(OR(alpha).c()) * _Qa[alpha].entropy();
+        sum += (OR(alpha).log0() * _Qa[alpha]).totalSum();
+    }
+    return sum;
+}
+
+
+
+size_t JTree::findEfficientTree( const VarSet& ns, DEdgeVec &Tree, size_t PreviousRoot ) const {
+    // find new root clique (the one with maximal statespace overlap with ns)
+    size_t maxval = 0, maxalpha = 0;
+    for( size_t alpha = 0; alpha < nr_ORs(); alpha++ ) {
+        size_t val = (ns & OR(alpha).vars()).stateSpace();
+        if( val > maxval ) {
+            maxval = val;
+            maxalpha = alpha;
+        }
+    }
+
+//  for( size_t e = 0; e < _RTree.size(); e++ )
+//      cout << OR(_RTree[e].n1).vars() << "->" << OR(_RTree[e].n2).vars() << ",  ";
+//  cout << endl;
+    // grow new tree
+    Graph oldTree;
+    for( DEdgeVec::const_iterator e = _RTree.begin(); e != _RTree.end(); e++ )
+        oldTree.insert( UEdge(e->n1, e->n2) );
+    DEdgeVec newTree = GrowRootedTree( oldTree, maxalpha );
+//  cout << ns << ": ";
+//  for( size_t e = 0; e < newTree.size(); e++ )
+//      cout << OR(newTree[e].n1).vars() << "->" << OR(newTree[e].n2).vars() << ",  ";
+//  cout << endl;
+    
+    // identify subtree that contains variables of ns which are not in the new root
+    VarSet nsrem = ns / OR(maxalpha).vars();
+//  cout << "nsrem:" << nsrem << endl;
+    set<DEdge> subTree;
+    // for each variable in ns that is not in the root clique
+    for( VarSet::const_iterator n = nsrem.begin(); n != nsrem.end(); n++ ) {
+        // find first occurence of *n in the tree, which is closest to the root
+        size_t e = 0;
+        for( ; e != newTree.size(); e++ ) {
+            if( OR(newTree[e].n2).vars() && *n )
+                break;
+        }
+        assert( e != newTree.size() );
+
+        // track-back path to root and add edges to subTree
+        subTree.insert( newTree[e] );
+        size_t pos = newTree[e].n1;
+        for( ; e > 0; e-- )
+            if( newTree[e-1].n2 == pos ) {
+                subTree.insert( newTree[e-1] );
+                pos = newTree[e-1].n1;
+            }
+    }
+    if( PreviousRoot != (size_t)-1 && PreviousRoot != maxalpha) {
+        // find first occurence of PreviousRoot in the tree, which is closest to the new root
+        size_t e = 0;
+        for( ; e != newTree.size(); e++ ) {
+            if( newTree[e].n2 == PreviousRoot )
+                break;
+        }
+        assert( e != newTree.size() );
+
+        // track-back path to root and add edges to subTree
+        subTree.insert( newTree[e] );
+        size_t pos = newTree[e].n1;
+        for( ; e > 0; e-- )
+            if( newTree[e-1].n2 == pos ) {
+                subTree.insert( newTree[e-1] );
+                pos = newTree[e-1].n1;
+            }
+    }
+//  cout << "subTree: " << endl;
+//  for( set<DEdge>::const_iterator sTi = subTree.begin(); sTi != subTree.end(); sTi++ )
+//      cout << OR(sTi->n1).vars() << "->" << OR(sTi->n2).vars() << ",  ";
+//  cout << endl;
+
+    // Resulting Tree is a reordered copy of newTree
+    // First add edges in subTree to Tree
+    Tree.clear();
+    for( DEdgeVec::const_iterator e = newTree.begin(); e != newTree.end(); e++ )
+        if( subTree.count( *e ) ) {
+            Tree.push_back( *e );
+//          cout << OR(e->n1).vars() << "->" << OR(e->n2).vars() << ",  ";
+        }
+//  cout << endl;
+    // Then add edges pointing away from nsrem
+    // FIXME
+/*  for( DEdgeVec::const_iterator e = newTree.begin(); e != newTree.end(); e++ )
+        for( set<DEdge>::const_iterator sTi = subTree.begin(); sTi != subTree.end(); sTi++ )
+            if( *e != *sTi ) {
+                if( e->n1 == sTi->n1 || e->n1 == sTi->n2 ||
+                    e->n2 == sTi->n1 || e->n2 == sTi->n2 ) {
+                    Tree.push_back( *e );
+//                  cout << OR(e->n1).vars() << "->" << OR(e->n2).vars() << ",  ";
+                }
+            }*/
+    // FIXME
+/*  for( DEdgeVec::const_iterator e = newTree.begin(); e != newTree.end(); e++ )
+        if( find( Tree.begin(), Tree.end(), *e) == Tree.end() ) {
+            bool found = false;
+            for( VarSet::const_iterator n = nsrem.begin(); n != nsrem.end(); n++ )
+                if( (OR(e->n1).vars() && *n) ) {
+                    found = true;
+                    break;
+                }
+            if( found ) {
+                Tree.push_back( *e );
+                cout << OR(e->n1).vars() << "->" << OR(e->n2).vars() << ",  ";
+            }
+        }
+    cout << endl;*/
+    size_t subTreeSize = Tree.size();
+    // Then add remaining edges
+    for( DEdgeVec::const_iterator e = newTree.begin(); e != newTree.end(); e++ )
+        if( find( Tree.begin(), Tree.end(), *e ) == Tree.end() )
+            Tree.push_back( *e );
+
+    return subTreeSize;
+}
+
+
+// Cutset conditioning
+// assumes that run() has been called already
+Factor JTree::calcMarginal( const VarSet& ns ) {
+    vector<Factor>::const_iterator beta;
+    for( beta = _Qb.begin(); beta != _Qb.end(); beta++ )
+        if( beta->vars() >> ns )
+            break;
+    if( beta != _Qb.end() )
+        return( beta->marginal(ns) );
+    else {
+        vector<Factor>::const_iterator alpha;
+        for( alpha = _Qa.begin(); alpha != _Qa.end(); alpha++ )
+            if( alpha->vars() >> ns )
+                break;
+        if( alpha != _Qa.end() )
+            return( alpha->marginal(ns) );
+        else {
+            // Find subtree to do efficient inference
+            DEdgeVec T;
+            size_t Tsize = findEfficientTree( ns, T );
+
+            // Find remaining variables (which are not in the new root)
+            VarSet nsrem = ns / OR(T.front().n1).vars();
+            Factor Pns (ns, 0.0);
+            
+            multind mi( nsrem );
+
+            // Save _Qa and _Qb on the subtree
+            map<size_t,Factor> _Qa_old;
+            map<size_t,Factor> _Qb_old;
+            vector<size_t> b(Tsize, 0);
+            for( size_t i = Tsize; (i--) != 0; ) {
+                size_t alpha1 = T[i].n1;
+                size_t alpha2 = T[i].n2;
+                size_t beta;
+                for( beta = 0; beta < nr_IRs(); beta++ )
+                    if( UEdge( _RTree[beta].n1, _RTree[beta].n2 ) == UEdge( alpha1, alpha2 ) )
+                        break;
+                assert( beta != nr_IRs() );
+                b[i] = beta;
+
+                if( !_Qa_old.count( alpha1 ) )
+                    _Qa_old[alpha1] = _Qa[alpha1];
+                if( !_Qa_old.count( alpha2 ) )
+                    _Qa_old[alpha2] = _Qa[alpha2];
+                if( !_Qb_old.count( beta ) )
+                    _Qb_old[beta] = _Qb[beta];
+            }
+                
+            // For all states of nsrem
+            for( size_t j = 0; j < mi.max(); j++ ) {
+                vector<size_t> vi = mi.vi( j );
+                
+                // CollectEvidence
+                double logZ = 0.0;
+                for( size_t i = Tsize; (i--) != 0; ) {
+            //      Make outer region T[i].n1 consistent with outer region T[i].n2
+            //      IR(i) = seperator OR(T[i].n1) && OR(T[i].n2)
+
+                    size_t k = 0;
+                    for( VarSet::const_iterator n = nsrem.begin(); n != nsrem.end(); n++, k++ )
+                        if( _Qa[T[i].n2].vars() >> *n ) {
+                            Factor piet( *n, 0.0 );
+                            piet[vi[k]] = 1.0;
+                            _Qa[T[i].n2] *= piet; 
+                        }
+
+                    Factor new_Qb = _Qa[T[i].n2].part_sum( IR( b[i] ) );
+                    logZ += log(new_Qb.normalize( Prob::NORMPROB ));
+                    _Qa[T[i].n1] *= new_Qb.divided_by( _Qb[b[i]] ); 
+                    _Qb[b[i]] = new_Qb;
+                }
+                logZ += log(_Qa[T[0].n1].normalize( Prob::NORMPROB ));
+
+                Factor piet( nsrem, 0.0 );
+                piet[j] = exp(logZ);
+                Pns += piet * _Qa[T[0].n1].part_sum( ns / nsrem );      // OPTIMIZE ME
+
+                // Restore clamped beliefs
+                for( map<size_t,Factor>::const_iterator alpha = _Qa_old.begin(); alpha != _Qa_old.end(); alpha++ )
+                    _Qa[alpha->first] = alpha->second;
+                for( map<size_t,Factor>::const_iterator beta = _Qb_old.begin(); beta != _Qb_old.end(); beta++ )
+                    _Qb[beta->first] = beta->second;
+            }
+
+            return( Pns.normalized(Prob::NORMPROB) );
+        }
+    }
+}
diff --git a/jtree.h b/jtree.h
new file mode 100644 (file)
index 0000000..9cc0217
--- /dev/null
+++ b/jtree.h
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __JTREE_H__
+#define __JTREE_H__
+
+
+#include <vector>
+#include "daialg.h"
+#include "varset.h"
+#include "regiongraph.h"
+#include "factorgraph.h"
+#include "clustergraph.h"
+#include "weightedgraph.h"
+#include "enum.h"
+
+
+using namespace std;
+
+
+class JTree : public DAIAlgRG {
+    protected:
+        DEdgeVec        _RTree;     // rooted tree
+        vector<Factor>  _Qa;
+        vector<Factor>  _Qb;
+        vector<Factor>  _mes;
+        double          _logZ;
+
+
+    public:
+        ENUM2(UpdateType,HUGIN,SHSH)
+        UpdateType Updates() const { return GetPropertyAs<UpdateType>("updates"); }
+
+        JTree() : DAIAlgRG(), _RTree(), _Qa(), _Qb(), _mes(), _logZ() {};
+        JTree( const JTree& x ) : DAIAlgRG(x), _RTree(x._RTree), _Qa(x._Qa), _Qb(x._Qb), _mes(x._mes), _logZ(x._logZ) {};
+        JTree* clone() const { return new JTree(*this); }
+        JTree & operator=( const JTree& x ) {
+            if( this != &x ) {
+                DAIAlgRG::operator=(x);
+                _RTree  = x._RTree;
+                _Qa     = x._Qa;
+                _Qb     = x._Qb;
+                _mes    = x._mes;
+                _logZ   = x._logZ;
+            }
+            return *this;
+        }
+        JTree( const FactorGraph &fg, const Properties &opts, bool automatic=true );
+        void GenerateJT( const vector<VarSet> &Cliques );
+
+        Factor & message(size_t i1, size_t i2) { return( _mes[ORIR2E(i1,i2)] ); }   
+        const Factor & message(size_t i1, size_t i2) const { return( _mes[ORIR2E(i1,i2)] ); }   
+
+        static const char *Name;
+        string identify() const;
+//      void Regenerate();
+        void init() {
+            assert( checkProperties() );
+        }
+        void runHUGIN();
+        void runShaferShenoy();
+        double run();
+        Factor belief( const Var &n ) const;
+        Factor belief( const VarSet &ns ) const;
+        vector<Factor> beliefs() const;
+        Complex logZ() const;
+
+        void init( const VarSet &ns ) {}
+        void undoProbs( const VarSet &ns ) { RegionGraph::undoProbs( ns ); init( ns ); }
+
+        size_t findEfficientTree( const VarSet& ns, DEdgeVec &Tree, size_t PreviousRoot=(size_t)-1 ) const;
+        Factor calcMarginal( const VarSet& ns );
+        bool checkProperties();
+};
+
+
+#endif
diff --git a/lc.cpp b/lc.cpp
new file mode 100644 (file)
index 0000000..314d8b9
--- /dev/null
+++ b/lc.cpp
@@ -0,0 +1,345 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include <algorithm>
+#include <map>
+#include <set>
+#include "lc.h"
+#include "diffs.h"
+#include "util.h"
+#include "alldai.h"
+#include "x2x.h"
+
+
+using namespace std;
+
+
+const char *LC::Name = "LC";
+
+
+bool LC::checkProperties() {
+    if( !HasProperty("cavity") )
+        return false;
+    if( !HasProperty("updates") )
+        return false;
+    if( !HasProperty("tol") )
+        return false;
+    if (!HasProperty("maxiter") )
+        return false;
+    if (!HasProperty("verbose") )
+        return false;
+
+    ConvertPropertyTo<CavityType>("cavity");
+    ConvertPropertyTo<UpdateType>("updates");
+    ConvertPropertyTo<double>("tol");
+    ConvertPropertyTo<size_t>("maxiter");
+    ConvertPropertyTo<size_t>("verbose");
+
+    if (HasProperty("cavainame") )
+        ConvertPropertyTo<string>("cavainame");
+    if (HasProperty("cavaiopts") )
+        ConvertPropertyTo<Properties>("cavaiopts");
+    if( HasProperty("reinit") )
+        ConvertPropertyTo<bool>("reinit");
+    
+    return true;
+}
+
+
+LC::LC(const FactorGraph & fg, const Properties &opts) : DAIAlgFG(fg, opts) {
+    assert( checkProperties() );
+
+    // calc iI
+    for( size_t i=0; i < nrVars(); i++ ) {
+        for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ ) {
+                _iI_type _iI_entry;
+                _iI_entry.i = i;
+                _iI_entry.I = *I;
+
+                _iI.push_back(_iI_entry);
+            }
+    }
+
+    // create pancakes
+    _pancakes.resize(nrVars());
+   
+    // create cavitydists
+    for( size_t i=0; i < nrVars(); i++ )
+        _cavitydists.push_back(Factor(delta(var(i))));
+
+    // create phis
+    _phis.reserve(nr_edges());
+    for( size_t iI = 0; iI < nr_edges(); iI++ ) {
+        size_t i = edge(iI).first;
+        size_t I = edge(iI).second;
+        _phis.push_back( Factor( factor(I).vars() / var(i) ) );
+    }
+
+    // create beliefs
+    for( size_t i=0; i < nrVars(); i++ )
+        _beliefs.push_back(Factor(var(i)));
+}
+
+
+string LC::identify() const { 
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+void LC::CalcBelief (size_t i) {
+    _beliefs[i] = _pancakes[i].marginal(var(i));
+}
+
+
+double LC::CalcCavityDist (size_t i, const string &name, const Properties &opts) {
+    Factor Bi;
+    double maxdiff = 0;
+
+    if( Verbose() >= 2 )
+        cout << "Initing cavity " << var(i) << "(" << delta(var(i)).size() << " vars, " << delta(var(i)).stateSpace() << " states)" << endl;
+
+    if( Cavity() == CavityType::UNIFORM )
+        Bi = Factor(delta(var(i)));
+    else {
+        InfAlg *cav = newInfAlg( name, *this, opts );
+        cav->makeCavity( var(i) );
+
+        if( Cavity() == CavityType::FULL )
+            Bi = calcMarginal( *cav, cav->delta(var(i)), reInit() );
+        else if( Cavity() == CavityType::PAIR )
+            Bi = calcMarginal2ndO( *cav, cav->delta(var(i)), reInit() );
+        else if( Cavity() == CavityType::PAIR2 ) {
+            vector<Factor> pairbeliefs = calcPairBeliefsNew( *cav, cav->delta(var(i)), reInit() );
+            for( size_t ij = 0; ij < pairbeliefs.size(); ij++ )
+                Bi *= pairbeliefs[ij];
+        } else if( Cavity() == CavityType::PAIRINT ) {
+            Bi = calcMarginal( *cav, cav->delta(var(i)), reInit() );
+            
+            // Set interactions of order > 2 to zero
+            size_t N = delta(var(i)).size();
+            double *p = &(*Bi.p().p().begin());
+            x2x::p2logp (N, p);
+            x2x::logp2w (N, p);
+            x2x::fill (N, p, 2, 0.0);
+            x2x::w2logp (N, p);
+//            x2x::logpnorm (N, p);
+            x2x::logp2p (N, p);
+        } else if( Cavity() == CavityType::PAIRCUM ) {
+            Bi = calcMarginal( *cav, cav->delta(var(i)), reInit() );
+            
+            // Set cumulants of order > 2 to zero
+            size_t N = delta(var(i)).size();
+            double *p = &(*Bi.p().p().begin());
+            x2x::p2m (N, p);
+            x2x::m2c (N, p, N);
+            x2x::fill (N, p, 2, 0.0);
+            x2x::c2m (N, p, N);
+            x2x::m2p (N, p);
+        }
+        maxdiff = cav->MaxDiff();
+        delete cav;
+    }
+    Bi.normalize( _normtype );
+    _cavitydists[i] = Bi;
+
+    return maxdiff;
+}
+
+
+double LC::InitCavityDists (const string &name, const Properties &opts) {
+    clock_t tic = toc();
+
+    if( Verbose() >= 1 ) {
+        cout << "LC::InitCavityDists:  ";
+        if( Cavity() == CavityType::UNIFORM )
+            cout << "Using uniform initial cavity distributions" << endl;
+        else if( Cavity() == CavityType::FULL )
+            cout << "Using full " << name << opts << "...";
+        else if( Cavity() == CavityType::PAIR )
+            cout << "Using pairwise " << name << opts << "...";
+        else if( Cavity() == CavityType::PAIR2 )
+            cout << "Using pairwise(new) " << name << opts << "...";
+    }
+
+    double maxdiff = 0.0;
+    for( size_t i = 0; i < nrVars(); i++ ) {
+        double md = CalcCavityDist(i, name, opts);
+        if( md > maxdiff )
+            maxdiff = md;
+    }
+    init();
+
+    if( Verbose() >= 1 ) {
+        cout << "used " << toc() - tic << " clocks." << endl;
+    }
+
+    return maxdiff;
+}
+
+
+long LC::SetCavityDists (vector<Factor> &Q) {
+    if( Verbose() >= 1 ) 
+        cout << "LC::SetCavityDists:  Setting initial cavity distributions" << endl;
+    if( Q.size() != nrVars() )
+        return -1;
+    for( size_t i = 0; i < nrVars(); i++ ) {
+        if( _cavitydists[i].vars() != Q[i].vars() ) {
+            return i+1;
+        } else
+            _cavitydists[i] = Q[i];
+    }
+    init();
+    return 0;
+}
+
+
+void LC::init() {
+    for( size_t iI = 0; iI < nr_edges(); iI++ ) {
+        if( Updates() == UpdateType::SEQRND )
+            _phis[iI].randomize();
+        else
+            _phis[iI].fill(1.0);
+    }
+    for( size_t i = 0; i < nrVars(); i++ ) {
+        _pancakes[i] = _cavitydists[i];
+        
+        for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ ) {
+            _pancakes[i] *= factor(*I);
+            if( Updates() == UpdateType::SEQRND )
+              _pancakes[i] *= _phis[VV2E(i,*I)];
+        }
+        
+        _pancakes[i].normalize( _normtype );
+
+        CalcBelief(i);
+    }
+}
+
+
+Factor LC::NewPancake (size_t iI, bool & hasNaNs) {
+    size_t i = _iI[iI].i;
+    size_t I = _iI[iI].I;
+    iI = VV2E(i, I);
+
+    Factor piet = _pancakes[i];
+
+    // recalculate _pancake[i]
+    VarSet Ivars = factor(I).vars();
+    Factor A_I;
+    for( VarSet::const_iterator k = Ivars.begin(); k != Ivars.end(); k++ )
+        if( var(i) != *k )
+            A_I *= (_pancakes[findVar(*k)] * factor(I).inverse()).part_sum( Ivars / var(i) );
+    if( Ivars.size() > 1 )
+        A_I ^= (1.0 / (Ivars.size() - 1));
+    Factor A_Ii = (_pancakes[i] * factor(I).inverse() * _phis[iI].inverse()).part_sum( Ivars / var(i) );
+    Factor quot = A_I.divided_by(A_Ii);
+
+    piet *= quot.divided_by( _phis[iI] ).normalized( _normtype );
+    _phis[iI] = quot.normalized( _normtype );
+
+    piet.normalize( _normtype );
+
+    if( piet.hasNaNs() ) {
+        cout << "LC::NewPancake(" << iI << "):  has NaNs!" << endl;
+        hasNaNs = true;
+    }
+
+    return piet;
+}
+
+
+double LC::run() {
+    if( Verbose() >= 1 )
+        cout << "Starting " << identify() << "...";
+    if( Verbose() >= 2 )
+        cout << endl;
+
+    clock_t tic = toc();
+    Diffs diffs(nrVars(), 1.0);
+
+    updateMaxDiff( InitCavityDists(GetPropertyAs<string>("cavainame"), GetPropertyAs<Properties>("cavaiopts")) );
+
+    vector<Factor> old_beliefs;
+    for(size_t i=0; i < nrVars(); i++ )
+        old_beliefs.push_back(belief(i));
+
+    bool hasNaNs = false;
+    for( size_t i=0; i < nrVars(); i++ )
+        if( _pancakes[i].hasNaNs() ) {
+            hasNaNs = true;
+            break;
+        }
+    if( hasNaNs ) {
+        cout << "LC::run:  initial _pancakes has NaNs!" << endl;
+        return NAN;
+    }
+
+    vector<long> update_seq(nr_iI(),0);
+    for( size_t k=0; k < nr_iI(); k++ )
+        update_seq[k] = k;
+
+    size_t iter=0;
+
+    // do several passes over the network until maximum number of iterations has
+    // been reached or until the maximum belief difference is smaller than tolerance
+    for( iter=0; iter < MaxIter() && diffs.max() > Tol(); iter++ ) {
+        // Sequential updates
+        if( Updates() == UpdateType::SEQRND )
+            random_shuffle( update_seq.begin(), update_seq.end() );
+        
+        for( size_t t=0; t < nr_iI(); t++ ) {
+            long iI = update_seq[t];
+            long i = _iI[iI].i;
+            _pancakes[i] = NewPancake(iI, hasNaNs);
+            if( hasNaNs )
+                return NAN;
+            CalcBelief(i);
+        }
+
+        // compare new beliefs with old ones
+        for(size_t i=0; i < nrVars(); i++ ) {
+            diffs.push( dist( belief(i), old_beliefs[i], Prob::DISTLINF ) );
+            old_beliefs[i] = belief(i);
+        }
+
+        if( Verbose() >= 3 )
+            cout << "LC::run:  maxdiff " << diffs.max() << " after " << iter+1 << " passes" << endl;
+    }
+
+    updateMaxDiff( diffs.max() );
+
+    if( Verbose() >= 1 ) {
+        if( diffs.max() > Tol() ) {
+            if( Verbose() == 1 )
+                cout << endl;
+                cout << "LC::run:  WARNING: not converged within " << MaxIter() << " passes (" << toc() - tic << " clocks)...final maxdiff:" << diffs.max() << endl;
+        } else {
+            if( Verbose() >= 2 )
+                cout << "LC::run:  ";
+                cout << "converged in " << iter << " passes (" << toc() - tic << " clocks)." << endl;
+        }
+    }
+
+    return diffs.max();
+}
diff --git a/lc.h b/lc.h
new file mode 100644 (file)
index 0000000..1eac766
--- /dev/null
+++ b/lc.h
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __LC_H__
+#define __LC_H__
+
+
+#include "daialg.h"
+#include "enum.h"
+#include "factorgraph.h"
+
+
+using namespace std;
+
+
+class LC : public DAIAlgFG {
+    protected:
+        typedef struct { size_t i; size_t I; } _iI_type;
+
+        vector<Factor>      _pancakes;      // used by all LC types (psi_I is stored in the pancake)
+        vector<Factor>      _cavitydists;   // used by all LC types to store the approximate cavity distribution
+        /// _phis[VV2E(i,I)] corresponds to \f$ \phi^{\setminus i}_I(x_{I \setminus i}) \f$
+        vector<Factor>      _phis;
+
+        /// Single variable beliefs
+        vector<Factor>      _beliefs;
+
+        /// For each pair (i,j) with j in delta(i), store i and the common factor I
+        vector<_iI_type>    _iI;
+
+    public:
+        ENUM6(CavityType,FULL,PAIR,PAIR2,PAIRINT,PAIRCUM,UNIFORM)
+        ENUM3(UpdateType,SEQFIX,SEQRND,NONE)
+
+        CavityType Cavity() const { return GetPropertyAs<CavityType>("cavity"); }
+        UpdateType Updates() const { return GetPropertyAs<UpdateType>("updates"); }
+        bool reInit() const { return GetPropertyAs<bool>("reinit"); }
+        
+        /// Default constructor
+        LC() : DAIAlgFG() {};
+        /// Copy constructor
+        LC(const LC & x) : DAIAlgFG(x), _pancakes(x._pancakes), _cavitydists(x._cavitydists), _phis(x._phis), _beliefs(x._beliefs), _iI(x._iI) {};
+        /// Clone function
+        LC* clone() const { return new LC(*this); }
+        /// Construct LC object from a FactorGraph and parameters
+        LC(const FactorGraph & fg, const Properties &opts);
+        /// Assignment operator
+        LC& operator=(const LC & x) {
+            if( this != &x ) {
+                DAIAlgFG::operator=(x);
+                _pancakes       = x._pancakes;
+                _cavitydists    = x._cavitydists;
+                _phis           = x._phis;
+                _beliefs        = x._beliefs;
+                _iI             = x._iI;
+            }
+            return *this;
+        }
+
+        static const char *Name;
+        double CalcCavityDist (size_t i, const string &name, const Properties &opts);
+        double InitCavityDists (const string &name, const Properties &opts);
+        long SetCavityDists (vector<Factor> &Q);
+
+        void init();
+        Factor NewPancake (size_t iI, bool & hasNaNs);
+        double run();
+
+        string identify() const;
+        Factor belief (const Var &n) const { return( _beliefs[findVar(n)] ); }
+        Factor belief (const VarSet &ns) const { assert( 0 == 1 ); }
+        vector<Factor> beliefs() const { return _beliefs; }
+        Complex logZ() const { return NAN; }
+        void CalcBelief (size_t i);
+        const Factor & belief (size_t i) const { return _beliefs[i]; };
+        const Factor & pancake (size_t i) const { return _pancakes[i]; };
+        const Factor & cavitydist (size_t i) const { return _cavitydists[i]; };
+        size_t nr_iI() const { return _iI.size(); };
+
+        void clamp( const Var &n, size_t i ) { assert( 0 == 1 ); }
+        void undoProbs( const VarSet &ns ) { assert( 0 == 1 ); }
+        void saveProbs( const VarSet &ns ) { assert( 0 == 1 ); }
+        void makeFactorCavity(size_t I) { assert( 0 == 1 ); }
+        virtual void makeCavity(const Var & n) { assert( 0 == 1 ); }
+        bool checkProperties();
+};
+
+
+#endif
diff --git a/matlab/dai.cpp b/matlab/dai.cpp
new file mode 100644 (file)
index 0000000..c4e766b
--- /dev/null
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+/*=================================================================*
+ *                                                                 * 
+ * This is a MEX-file for MATLAB.                                  *
+ *                                                                 * 
+ *   [logZ,q,md] = dai(psi,method,opts);                           *
+ *                                                                 * 
+ *=================================================================*/
+
+
+#include <iostream>
+#include "matlab.h"
+#include "mex.h"
+#include "alldai.h"
+
+
+using namespace std;
+
+
+/* Input Arguments */
+
+#define PSI_IN          prhs[0]
+#define METHOD_IN       prhs[1]
+#define OPTS_IN         prhs[2]
+#define NR_IN           3
+
+
+/* Output Arguments */
+
+#define LOGZ_OUT        plhs[0]
+#define Q_OUT           plhs[1]
+#define MD_OUT          plhs[2]
+#define NR_OUT          3
+
+
+void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] )
+{ 
+    size_t buflen;
+    
+    /* Check for proper number of arguments */
+    if( (nrhs != NR_IN) || (nlhs != NR_OUT) ) { 
+        mexErrMsgTxt("Usage: [logZ,q,md] = dai(psi,method,opts)\n\n"
+        "\n"
+        "INPUT:  psi        = linear cell array containing the factors \n"
+        "                     psi{i} should be a structure with a Member field\n"
+        "                     and a P field, like a CPTAB).\n"
+        "        method     = name of the method (see README)\n"
+        "        opts       = string of options (see README)\n"
+        "\n"
+        "OUTPUT: logZ       = approximation of the logarithm of the partition sum.\n"
+        "        q          = linear cell array containing all final beliefs.\n"
+        "        md         = maxdiff (final linf-dist between new and old single node beliefs).\n");
+    } 
+    
+    char *method;
+    char *opts;
+
+
+    // Get psi and construct factorgraph
+    vector<Factor> factors = mx2Factors(PSI_IN, 0);
+    FactorGraph fg(factors);
+    long nr_v = fg.nrVars();
+
+    // Get method
+    buflen = mxGetN( METHOD_IN ) + 1;
+    method = (char *)mxCalloc( buflen, sizeof(char) );
+    mxGetString( METHOD_IN, method, buflen );
+
+    // Get options string
+    buflen = mxGetN( OPTS_IN ) + 1;
+    opts = (char *)mxCalloc( buflen, sizeof(char) );
+    mxGetString( OPTS_IN, opts, buflen );
+    // Convert to options object props
+    stringstream ss;
+    ss << opts;
+    Properties props;
+    ss >> props;
+    
+    // Construct InfAlg object, init and run
+    InfAlg *obj = newInfAlg( method, fg, props );
+    obj->init();
+    obj->run();
+
+
+    // Save logZ
+    double logZ = real( obj->logZ() );
+
+    // Save maxdiff
+    double maxdiff = obj->MaxDiff();
+
+
+    // Hand over results to MATLAB
+    LOGZ_OUT = mxCreateDoubleMatrix(1,1,mxREAL);
+    *(mxGetPr(LOGZ_OUT)) = logZ;
+    
+    Q_OUT = Factors2mx(obj->beliefs());
+    
+    MD_OUT = mxCreateDoubleMatrix(1,1,mxREAL);
+    *(mxGetPr(MD_OUT)) = maxdiff;
+
+    
+    return;
+}
diff --git a/matlab/dai.m b/matlab/dai.m
new file mode 100644 (file)
index 0000000..032ff72
--- /dev/null
@@ -0,0 +1,11 @@
+% [logZ,q,md] = dai (psi, method, opts)
+%
+%    INPUT:  psi        = linear cell array containing the factors
+%                         psi{i} should be a structure with a Member field
+%                         and a P field, like a CPTAB).
+%            method     = name of the method (see README)
+%            opts       = string of options (see README)
+%
+%    OUTPUT: logZ       = approximation of the logarithm of the partition sum.
+%            q          = linear cell array containing all final beliefs.
+%            md         = maxdiff (final linf-dist between new and old single node beliefs).
diff --git a/matlab/dai_potstrength.cpp b/matlab/dai_potstrength.cpp
new file mode 100644 (file)
index 0000000..383c0f9
--- /dev/null
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+/*=================================================================*
+ *                                                                 * 
+ * This is a MEX-file for MATLAB.                                  *
+ *                                                                 * 
+ *   N = dai_potstrength(psi,i,j);                                 *
+ *                                                                 * 
+ *=================================================================*/
+
+
+#include <iostream>
+#include "mex.h"
+#include "matlab.h"
+#include "../factor.h"
+
+
+using namespace std;
+
+
+/* Input Arguments */
+
+#define PSI_IN          prhs[0]
+#define I_IN            prhs[1]
+#define J_IN            prhs[2]
+#define NR_IN           3
+
+
+/* Output Arguments */
+
+#define N_OUT           plhs[0]
+#define NR_OUT          1
+
+
+void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] )
+{ 
+    long ilabel, jlabel;
+
+    // Check for proper number of arguments
+    if ((nrhs != NR_IN) || (nlhs != NR_OUT)) { 
+        mexErrMsgTxt("Usage: N = dai_potstrength(psi,i,j);\n\n"
+        "\n"
+        "INPUT:  psi        = structure with a Member field and a P field, like a CPTAB.\n"
+        "        i          = label of a variable in psi.\n"
+        "        j          = label of another variable in psi.\n"
+        "\n"
+        "OUTPUT: N          = strength of psi in direction i->j.\n");
+    } 
+    
+    // Get input parameters
+    Factor psi = mx2Factor(PSI_IN);
+    ilabel = (long)*mxGetPr(I_IN);
+    jlabel = (long)*mxGetPr(J_IN);
+
+    // Find variable in psi with label ilabel
+    Var i;
+    for( VarSet::const_iterator n = psi.vars().begin(); n != psi.vars().end(); n++ )
+        if( n->label() == ilabel ) {
+            i = *n;
+            break;
+        }
+    assert( i.label() == ilabel );
+
+    // Find variable in psi with label jlabel
+    Var j;
+    for( VarSet::const_iterator n = psi.vars().begin(); n != psi.vars().end(); n++ )
+        if( n->label() == jlabel ) {
+            j = *n;
+            break;
+        }
+    assert( j.label() == jlabel );
+
+    // Calculate N(psi,i,j);
+    double N = psi.strength( i, j );
+    
+    // Hand over result to MATLAB
+    N_OUT = mxCreateDoubleMatrix(1,1,mxREAL);
+    *(mxGetPr(N_OUT)) = N;
+    
+    
+    return;
+}
diff --git a/matlab/dai_potstrength.m b/matlab/dai_potstrength.m
new file mode 100644 (file)
index 0000000..04e9c2b
--- /dev/null
@@ -0,0 +1,7 @@
+% N = dai_potstrength (psi, i, j)
+%     
+%    INPUT:  psi        = structure with a Member field and a P field, like a CPTAB.
+%            i          = label of a variable in psi.
+%            j          = label of another variable in psi.
+%
+%    OUTPUT: N          = strength of psi in direction i->j.
diff --git a/matlab/dai_readfg.cpp b/matlab/dai_readfg.cpp
new file mode 100644 (file)
index 0000000..8ae625f
--- /dev/null
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+/*=================================================================*
+ *                                                                 * 
+ * This is a MEX-file for MATLAB.                                  *
+ *                                                                 * 
+ *   [psi] = dai_readfg(filename);                                 *
+ *                                                                 * 
+ *=================================================================*/
+
+
+#include <iostream>
+#include "mex.h"
+#include "matlab.h"
+#include "factorgraph.h"
+
+
+using namespace std;
+
+
+/* Input Arguments */
+
+#define FILENAME_IN     prhs[0]
+#define NR_IN           1
+
+
+/* Output Arguments */
+
+#define PSI_OUT         plhs[0]
+#define NR_OUT          1
+
+
+void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] )
+{ 
+    char *filename;
+
+    
+    // Check for proper number of arguments
+    if ((nrhs != NR_IN) || (nlhs != NR_OUT)) { 
+        mexErrMsgTxt("Usage: [psi] = dai_readfg(filename);\n\n"
+        "\n"
+        "INPUT:  filename   = filename of a .fg file\n"
+        "\n"
+        "OUTPUT: psi        = linear cell array containing the factors\n"
+        "                     (psi{i} is a structure with a Member field\n"
+        "                     and a P field, like a CPTAB).\n");
+    } 
+    
+    // Get input parameters
+    size_t buflen;
+    buflen = mxGetN( FILENAME_IN ) + 1;
+    filename = (char *)mxCalloc( buflen, sizeof(char) );
+    mxGetString( FILENAME_IN, filename, buflen );
+    
+
+    // Read factorgraph
+    FactorGraph fg;
+    if( fg.ReadFromFile( filename ) ) {
+        mexErrMsgTxt("dai_readfg: error reading file\n");
+    }
+
+
+    // Save factors
+    vector<Factor> psi;
+    for( size_t I = 0; I < fg.nrFactors(); I++ )
+        psi.push_back(fg.factor(I));
+    
+
+    // Hand over results to MATLAB
+    PSI_OUT = Factors2mx(psi);
+
+    
+    return;
+}
diff --git a/matlab/dai_readfg.m b/matlab/dai_readfg.m
new file mode 100644 (file)
index 0000000..6acf644
--- /dev/null
@@ -0,0 +1,7 @@
+% [psi] = dai_readfg (filename)
+%
+%    INPUT:  filename   = filename of a .fg file
+%
+%    OUTPUT: psi        = linear cell array containing the factors
+%                         (psi{i} is a structure with a Member field
+%                         and a P field, like a CPTAB).
diff --git a/matlab/dai_removeshortloops.cpp b/matlab/dai_removeshortloops.cpp
new file mode 100644 (file)
index 0000000..4c0ac1e
--- /dev/null
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+/*=================================================================*
+ *                                                                 * 
+ * This is a MEX-file for MATLAB.                                  *
+ *                                                                 * 
+ *   [psi_out] = dai_removeshortloops(psi_in);                     *
+ *                                                                 * 
+ *=================================================================*/
+
+
+#include <iostream>
+#include "mex.h"
+#include "matlab.h"
+#include "factorgraph.h"
+
+
+using namespace std;
+
+
+/* Input Arguments */
+
+#define PSI_IN          prhs[0]
+#define NR_IN           1
+
+
+/* Output Arguments */
+
+#define PSI_OUT         plhs[0]
+#define NR_OUT          1
+
+
+void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] ) { 
+    // Check for proper number of arguments
+    if ((nrhs != NR_IN) || (nlhs != NR_OUT)) { 
+        mexErrMsgTxt("Usage: [psi_out] = dai_removeshortloops(psi_in);\n\n"
+        "\n"
+        "INPUT:  psi_in     = linear cell array containing the factors\n"
+        "                     (psi{i} is a structure with a Member field\n"
+        "                     and a P field, like a CPTAB).\n"
+        "\n"
+        "OUTPUT: psi_out    = linear cell array containing the factors of psi_in,\n"
+        "                     where factors have been merged to prevent short loops\n"
+        "                     of length 4 in the factor graph (i.e. loops of type\n"
+        "                     var1-factor1-var2-factor2-var1),\n");
+    } 
+    
+    // Get the factors from PSI_IN
+    vector<Factor> psi = mx2Factors(PSI_IN, 0);
+
+    // Remove the short loops
+    RemoveShortLoops(psi);
+
+    // Hand over results to MATLAB
+    PSI_OUT = Factors2mx(psi);
+
+    
+    return;
+}
diff --git a/matlab/dai_removeshortloops.m b/matlab/dai_removeshortloops.m
new file mode 100644 (file)
index 0000000..264ed7e
--- /dev/null
@@ -0,0 +1,10 @@
+% [psi_out] = dai_removeshortloops (psi_in)
+%       
+%    INPUT:  psi_in     = linear cell array containing the factors
+%                         (psi{i} is a structure with a Member field
+%                         and a P field, like a CPTAB).
+%      
+%    OUTPUT: psi_out    = linear cell array containing the factors of psi_in,
+%                         where factors have been merged to prevent short loops
+%                         of length 4 in the factor graph (i.e. loops of type
+%                         var1-factor1-var2-factor2-var1).
diff --git a/matlab/dai_writefg.cpp b/matlab/dai_writefg.cpp
new file mode 100644 (file)
index 0000000..8bf5fe7
--- /dev/null
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+/*=================================================================*
+ *                                                                 * 
+ * This is a MEX-file for MATLAB.                                  *
+ *                                                                 * 
+ *   dai_writefg(psi, filename);                                   *
+ *                                                                 * 
+ *=================================================================*/
+
+
+#include <iostream>
+#include "mex.h"
+#include "matlab.h"
+#include "factorgraph.h"
+
+
+using namespace std;
+
+
+/* Input Arguments */
+
+#define PSI_IN          prhs[0]
+#define FILENAME_IN     prhs[1]
+#define NR_IN           2
+
+
+/* Output Arguments */
+
+#define NR_OUT          0
+
+
+void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] )
+{ 
+    char *filename;
+
+    
+    // Check for proper number of arguments
+    if ((nrhs != NR_IN) || (nlhs != NR_OUT)) { 
+        mexErrMsgTxt("Usage: dai_writefg(psi,filename);\n\n"
+        "\n"
+        "INPUT:  psi        = linear cell array containing the factors\n"
+        "                     (psi{i} should be a structure with a Member field\n"
+        "                     and a P field, like a CPTAB).\n"
+        "        filename   = filename of a .fg file\n");
+    } 
+    
+    // Get input parameters
+    vector<Factor> factors = mx2Factors(PSI_IN,0);
+    
+    size_t buflen;
+    buflen = mxGetN( FILENAME_IN ) + 1;
+    filename = (char *)mxCalloc( buflen, sizeof(char) );
+    mxGetString( FILENAME_IN, filename, buflen );
+    
+    // Construct factorgraph
+    FactorGraph fg(factors);
+    long nr_v = fg.nrVars();
+    long nr_f = fg.nrFactors();
+
+    if( fg.WriteToFile( filename ) ) {
+        mexErrMsgTxt("dai_writefg: error reading file\n");
+    }
+
+    return;
+}
diff --git a/matlab/dai_writefg.m b/matlab/dai_writefg.m
new file mode 100644 (file)
index 0000000..b13486e
--- /dev/null
@@ -0,0 +1,6 @@
+% dai_writefg(psi,filename)
+%
+%    INPUT:  psi        = linear cell array containing the factors
+%                         (psi{i} should be a structure with a Member field
+%                         and a P field, like a CPTAB).
+%            filename   = filename of a .fg file
diff --git a/matlab/matlab.cpp b/matlab/matlab.cpp
new file mode 100644 (file)
index 0000000..6fc3415
--- /dev/null
@@ -0,0 +1,213 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include "matlab.h"
+
+
+using namespace std;
+
+
+/* Convert vector<Factor> structure to a cell vector of CPTAB-like structs */
+mxArray *Factors2mx(const vector<Factor> &Ps) {
+    size_t nr = Ps.size();
+
+    mxArray *psi = mxCreateCellMatrix(nr,1);
+    
+    const char *fieldnames[2];
+    fieldnames[0] = "Member";
+    fieldnames[1] = "P";
+    
+    size_t I_ind = 0;
+    for( vector<Factor>::const_iterator I = Ps.begin(); I != Ps.end(); I++, I_ind++ ) {
+        mxArray *Bi = mxCreateStructMatrix(1,1,2,fieldnames);
+
+        mxArray *BiMember = mxCreateDoubleMatrix(1,I->vars().size(),mxREAL);
+        double *BiMember_data = mxGetPr(BiMember);
+        size_t i = 0;
+        vector<mwSize> dims;
+        for( VarSet::iterator j = I->vars().begin(); j != I->vars().end(); j++,i++ ) {
+            BiMember_data[i] = j->label();
+            dims.push_back( j->states() );
+        }
+
+        //      mxArray *BiP = mxCreateDoubleMatrix(I->stateSpace(),1,mxREAL);
+        mxArray *BiP = mxCreateNumericArray(I->vars().size(), &(*(dims.begin())), mxDOUBLE_CLASS, mxREAL);
+        double *BiP_data = mxGetPr(BiP);
+        for( size_t j = 0; j < I->stateSpace(); j++ )
+            BiP_data[j] = (*I)[j];
+
+        mxSetField(Bi,0,"Member",BiMember);
+        mxSetField(Bi,0,"P",BiP);
+        
+        mxSetCell(psi, I_ind, Bi);
+    }
+    return( psi );
+}
+
+
+/* Convert cell vector of CPTAB-like structs to vector<Factor> */
+vector<Factor> mx2Factors(const mxArray *psi, long verbose) {
+    set<Var> vars;
+    vector<Factor> factors;
+
+    int n1 = mxGetM(psi);
+    int n2 = mxGetN(psi);
+    if( n2 != 1 && n1 != 1 ) 
+        mexErrMsgTxt("psi should be a Nx1 or 1xN cell matrix."); 
+    size_t nr_f = n1;
+    if( n1 == 1 )
+        nr_f = n2;
+
+    // interpret psi, linear cell array of cptabs
+    for( size_t cellind = 0; cellind < nr_f; cellind++ ) {
+        if( verbose >= 3 )
+            cout << "reading factor " << cellind << ": " << endl;
+        mxArray *cell = mxGetCell(psi, cellind);
+        mxArray *mx_member = mxGetField(cell, 0, "Member"); 
+        size_t nr_mem = mxGetN(mx_member);
+        double *members = mxGetPr(mx_member);
+        const mwSize *dims = mxGetDimensions(mxGetField(cell,0,"P"));
+        double *factordata = mxGetPr(mxGetField(cell, 0, "P"));
+
+        // add variables
+        VarSet factorvars;
+        vector<long> labels(nr_mem,0);
+        if( verbose >= 3 )
+            cout << "  vars: ";
+        for( size_t mi = 0; mi < nr_mem; mi++ ) {
+            labels[mi] = (long)members[mi];
+            if( verbose >= 3 )
+                cout << labels[mi] << "(" << dims[mi] << ") ";
+            vars.insert( Var(labels[mi], dims[mi]) );
+            factorvars.insert( Var(labels[mi], dims[mi]) );
+        }
+        factors.push_back(Factor(factorvars));
+
+        // calculate permutation matrix
+        vector<size_t> perm(nr_mem,0);
+        VarSet::iterator j = factorvars.begin();
+        for( size_t mi = 0; mi < nr_mem; mi++,j++ ) {
+            long gezocht = j->label();
+            vector<long>::iterator piet = find(labels.begin(),labels.end(),gezocht);
+            perm[mi] = piet - labels.begin();
+        }
+
+        if( verbose >= 3 ) {
+            cout << endl << "  perm: ";
+            for( vector<size_t>::iterator r=perm.begin(); r!=perm.end(); r++ )
+                cout << *r << " ";
+            cout << endl;
+        }
+
+        // read Factor
+        vector<size_t> di(nr_mem,0);
+        vector<size_t> pdi(nr_mem,0);
+        for( size_t k = 0; k < nr_mem; k++ ) {
+            di[k] = dims[k];
+            pdi[k] = dims[perm[k]];
+        }
+        multind mi(di);
+        multind pmi(pdi);
+        if( verbose >= 3 ) {
+            cout << "  mi.max(): " << mi.max() << endl;
+            cout << "       ";
+            for( size_t k = 0; k < nr_mem; k++ ) 
+                cout << labels[k] << " ";
+            cout << "   ";
+            for( size_t k = 0; k < nr_mem; k++ ) 
+                cout << labels[perm[k]] << " ";
+            cout << endl;
+        }
+        for( size_t li = 0; li < mi.max(); li++ ) {
+            vector<size_t> vi = mi.vi(li);
+            vector<size_t> pvi(vi.size(),0);
+            for( size_t k = 0; k < vi.size(); k++ )
+                pvi[k] = vi[perm[k]];
+            size_t pli = pmi.li(pvi);
+            if( verbose >= 3 ) {
+                cout << "    " << li << ": ";
+                for( vector<size_t>::iterator r=vi.begin(); r!=vi.end(); r++)
+                    cout << *r << " ";
+                cout << "-> ";
+                for( vector<size_t>::iterator r=pvi.begin(); r!=pvi.end(); r++)
+                    cout << *r << " ";
+                cout << ": " << pli << endl;
+            }
+            factors.back()[pli] = factordata[li];
+        }
+    }
+
+    if( verbose >= 3 ) {
+        for(vector<Factor>::const_iterator I=factors.begin(); I!=factors.end(); I++ )
+            cout << *I << endl;
+    }
+
+    return( factors );
+}
+
+
+/* Convert CPTAB-like struct to Factor */
+Factor mx2Factor(const mxArray *psi) {
+    mxArray *mx_member = mxGetField(psi, 0, "Member");  
+    size_t nr_mem = mxGetN(mx_member);
+    double *members = mxGetPr(mx_member);
+    const mwSize *dims = mxGetDimensions(mxGetField(psi,0,"P"));
+    double *factordata = mxGetPr(mxGetField(psi, 0, "P"));
+
+    // add variables
+    VarSet vars;
+    vector<long> labels(nr_mem,0);
+    for( size_t mi = 0; mi < nr_mem; mi++ ) {
+        labels[mi] = (long)members[mi];
+        vars.insert( Var(labels[mi], dims[mi]) );
+    }
+    Factor factor(vars);
+
+    // calculate permutation matrix
+    vector<size_t> perm(nr_mem,0);
+    VarSet::iterator j = vars.begin();
+    for( size_t mi = 0; mi < nr_mem; mi++,j++ ) {
+        long gezocht = j->label();
+        vector<long>::iterator piet = find(labels.begin(),labels.end(),gezocht);
+        perm[mi] = piet - labels.begin();
+    }
+
+    // read Factor
+    vector<size_t> di(nr_mem,0);
+    vector<size_t> pdi(nr_mem,0);
+    for( size_t k = 0; k < nr_mem; k++ ) {
+        di[k] = dims[k];
+        pdi[k] = dims[perm[k]];
+    }
+    multind mi(di);
+    multind pmi(pdi);
+    for( size_t li = 0; li < mi.max(); li++ ) {
+        vector<size_t> vi = mi.vi(li);
+        vector<size_t> pvi(vi.size(),0);
+        for( size_t k = 0; k < vi.size(); k++ )
+            pvi[k] = vi[perm[k]];
+        size_t pli = pmi.li(pvi);
+        factor[pli] = factordata[li];
+    }
+
+    return( factor );
+}
diff --git a/matlab/matlab.h b/matlab/matlab.h
new file mode 100644 (file)
index 0000000..48a4b78
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include "mex.h"
+#include "../factor.h"
+
+
+/* Convert vector<Factor> structure to a cell vector of CPTAB-like structs */
+mxArray *Factors2mx(const std::vector<Factor> &Ps);
+
+/* Convert cell vector of CPTAB-like structs to vector<Factor> */
+std::vector<Factor> mx2Factors(const mxArray *psi, long verbose);
+
+/* Convert CPTAB-like struct to Factor */
+Factor mx2Factor(const mxArray *psi);
diff --git a/mf.cpp b/mf.cpp
new file mode 100644 (file)
index 0000000..630d83b
--- /dev/null
+++ b/mf.cpp
@@ -0,0 +1,196 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include <iostream>
+#include <sstream>
+#include <map>
+#include <set>
+#include "mf.h"
+#include "diffs.h"
+#include "util.h"
+
+
+using namespace std;
+
+
+const char *MF::Name = "MF";
+
+
+bool MF::checkProperties() {
+    if( !HasProperty("tol") )
+        return false;
+    if (!HasProperty("maxiter") )
+        return false;
+    if (!HasProperty("verbose") )
+        return false;
+    
+    ConvertPropertyTo<double>("tol");
+    ConvertPropertyTo<size_t>("maxiter");
+    ConvertPropertyTo<size_t>("verbose");
+
+    return true;
+}
+
+
+void MF::Regenerate() {
+    DAIAlgFG::Regenerate();
+
+    // clear beliefs
+    _beliefs.clear();
+    _beliefs.reserve( nrVars() );
+
+    // create beliefs
+    for( vector<Var>::const_iterator i = vars().begin(); i != vars().end(); i++ ) 
+        _beliefs.push_back(Factor(*i));
+}
+
+
+string MF::identify() const { 
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+void MF::init() {
+    assert( checkProperties() );
+
+    for( vector<Factor>::iterator qi = _beliefs.begin(); qi != _beliefs.end(); qi++ )
+        qi->fill(1.0);
+}
+
+
+double MF::run() {
+    clock_t tic = toc();
+
+    if( Verbose() >= 1 )
+        cout << "Starting " << identify() << "...";
+
+    size_t pass_size = _beliefs.size();
+    Diffs diffs(pass_size * 3, 1.0);
+
+    size_t t=0;
+    for( t=0; t < (MaxIter()*pass_size) && diffs.max() > Tol(); t++ ) {
+        // choose random Var i
+        size_t i = (size_t) (nrVars() * rnd_uniform());
+
+        Factor jan;
+        Factor piet;
+        for( _nb_cit I = nb1(i).begin(); I != nb1(i).end(); I++ ) {
+
+            Factor henk;
+            for( _nb_cit j = nb2(*I).begin(); j != nb2(*I).end(); j++ ) // for all j in I \ i
+                if( *j != i )
+                    henk *= _beliefs[*j];
+            piet = factor(*I).log0();
+            piet *= henk;
+            piet = piet.part_sum(var(i));
+            piet = piet.exp();
+            jan *= piet; 
+        }
+            
+        jan.normalize( _normtype );
+
+        if( jan.hasNaNs() ) {
+            cout << "MF::run():  ERROR: jan has NaNs!" << endl;
+            return NAN;
+        }
+
+        diffs.push( dist( jan, _beliefs[i], Prob::DISTLINF ) );
+
+        _beliefs[i] = jan;
+    }
+
+    updateMaxDiff( diffs.max() );
+
+    if( Verbose() >= 1 ) {
+        if( diffs.max() > Tol() ) {
+            if( Verbose() == 1 )
+                cout << endl;
+            cout << "MF::run:  WARNING: not converged within " << MaxIter() << " passes (" << toc() - tic << " clocks)...final maxdiff:" << diffs.max() << endl;
+        } else {
+            if( Verbose() >= 2 )
+                cout << "MF::run:  ";
+            cout << "converged in " << t / pass_size << " passes (" << toc() - tic << " clocks)." << endl;
+        }
+    }
+
+    return diffs.max();
+}
+
+
+Factor MF::belief1 (size_t i) const {
+    Factor piet;
+    piet = _beliefs[i];
+    piet.normalize( Prob::NORMPROB );
+    return(piet);
+}
+
+
+Factor MF::belief (const VarSet &ns) const {
+    if( ns.size() == 1 )
+        return belief( *(ns.begin()) );
+    else {
+        assert( ns.size() == 1 );
+        return Factor();
+    }
+}
+
+
+Factor MF::belief (const Var &n) const {
+    return( belief1( findVar( n) ) );
+}
+
+
+vector<Factor> MF::beliefs() const {
+    vector<Factor> result;
+    for( size_t i = 0; i < nrVars(); i++ )
+        result.push_back( belief1(i) );
+    return result;
+}
+
+
+Complex MF::logZ() const {
+    Complex sum = 0.0;
+    
+    for(size_t i=0; i < nrVars(); i++ )
+        sum -= belief1(i).entropy();
+    for(size_t I=0; I < nrFactors(); I++ ) {
+        Factor henk;
+        for( _nb_cit j = nb2(I).begin(); j != nb2(I).end(); j++ )   // for all j in I
+            henk *= _beliefs[*j];
+        henk.normalize( Prob::NORMPROB );
+        Factor piet;
+        piet = factor(I).log0();
+        piet *= henk;
+        sum -= Complex( piet.totalSum() );
+    }
+
+    return -sum;
+}
+
+
+void MF::init( const VarSet &ns ) {
+    for( size_t i = 0; i < nrVars(); i++ ) {
+        if( ns && var(i) )
+            _beliefs[i].fill( 1.0 );
+    }
+}
diff --git a/mf.h b/mf.h
new file mode 100644 (file)
index 0000000..a376161
--- /dev/null
+++ b/mf.h
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __MF_H__
+#define __MF_H__
+
+
+#include "daialg.h"
+#include "factorgraph.h"
+
+using namespace std;
+
+
+class MF : public DAIAlgFG {
+    protected:
+        vector<Factor>  _beliefs;
+        
+    public:
+        // default constructor
+        MF() : DAIAlgFG(), _beliefs() {};
+        // copy constructor
+        MF(const MF & x) : DAIAlgFG(x), _beliefs(x._beliefs) {};
+        MF* clone() const { return new MF(*this); }
+        // construct MF object from FactorGraph
+        MF(const FactorGraph & fg, const Properties &opts) : DAIAlgFG(fg, opts) {
+            assert( checkProperties() );
+            Regenerate();
+        }
+        // assignment operator
+        MF & operator=(const MF & x) {
+            if(this!=&x) {
+                DAIAlgFG::operator=(x);
+                _beliefs = x._beliefs;
+            }
+            return *this;
+        }
+
+        static const char *Name;
+        string identify() const;
+        void Regenerate();
+        void init();
+        double run();
+        Factor belief1 (size_t i) const;
+        Factor belief (const Var &n) const;
+        Factor belief (const VarSet &ns) const;
+        vector<Factor> beliefs() const;
+        Complex logZ() const;
+
+        void init( const VarSet &ns );
+        void undoProbs( const VarSet &ns ) { FactorGraph::undoProbs(ns); init(ns); }
+        bool checkProperties();
+};
+
+
+#endif
diff --git a/mr.cpp b/mr.cpp
new file mode 100644 (file)
index 0000000..33458a2
--- /dev/null
+++ b/mr.cpp
@@ -0,0 +1,657 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include "mr.h"
+#include "bp.h"
+#include "jtree.h"
+#include <stdio.h>
+#include <time.h>
+#include <math.h>
+#include <stdlib.h>
+#include "util.h"
+#include "diffs.h"
+
+
+using namespace std;
+
+
+const char *MR::Name = "MR";
+
+
+bool MR::checkProperties() {
+    if( !HasProperty("updates") )
+        return false;
+    if( !HasProperty("inits") )
+        return false;
+    if( !HasProperty("verbose") )
+        return false;
+    if( !HasProperty("tol") )
+        return false;
+    
+    ConvertPropertyTo<UpdateType>("updates");
+    ConvertPropertyTo<InitType>("inits");
+    ConvertPropertyTo<size_t>("verbose");
+    ConvertPropertyTo<double>("tol");
+
+    return true;
+}
+
+
+// init N, con, nb, tJ, theta
+void MR::init(size_t _N, double *_w, double *_th) {
+    size_t i,j;
+    
+    N = _N;
+
+    con.resize(N);
+    nb.resize(N);
+    tJ.resize(N);
+    for(i=0; i<N; i++ ) {
+        nb[i].resize(kmax);
+        tJ[i].resize(kmax);
+        con[i]=0;
+        for(j=0; j<N; j++ )
+            if( _w[i*N+j] != 0.0 ) {
+                nb[i][con[i]] = j;
+                tJ[i][con[i]] = tanh(_w[i*N+j]);
+                con[i]++;
+            }
+    }
+    
+    theta.resize(N);
+    for(i=0; i<N; i++)
+      theta[i] = _th[i];
+}
+
+
+// calculate cors
+double MR::init_cor_resp() {
+    size_t j,k,l, runx,i2;
+    double variab1, variab2;
+    double md, maxdev;
+    double thbJsite[kmax];
+    double xinter;
+    double rinter;
+    double res[kmax];
+    size_t s2;
+    size_t flag;
+    size_t concav;
+    size_t runs = 3000;
+    double eps = 0.2;
+    size_t cavity;
+
+    vector<vector<double> > tJ_org;
+    vector<vector<size_t> > nb_org;
+    vector<size_t> con_org;
+    vector<double> theta_org;
+
+    vector<double> xfield(N*kmax,0.0);
+    vector<double> rfield(N*kmax,0.0);
+    vector<double> Hfield(N,0.0);
+    vector<double> devs(N*kmax,0.0);
+    vector<double> devs2(N*kmax,0.0);
+    vector<double> dev(N,0.0);
+    vector<double> avmag(N,0.0);
+
+    // save original tJ, nb
+    nb_org = nb;
+    tJ_org = tJ;
+    con_org = con;
+    theta_org = theta;
+
+    maxdev = 0.0;
+    for(cavity=0; cavity<N; cavity++){    // for each spin to be removed
+        con = con_org;
+        concav=con[cavity];
+
+        nb = nb_org;
+        tJ = tJ_org;
+
+        //  Adapt the graph variables nb[], tJ[] and con[]
+        for(size_t i=0; i<con[cavity]; i++) {
+            size_t ij = nb[cavity][i];
+            flag=0;
+            j=0;
+            do{
+                if(nb[ij][j]==cavity){
+                    while(j<(con[ij]-1)){
+                        nb[ij][j]=nb[ij][j+1];
+                        tJ[ij][j] = tJ[ij][j+1];
+                        j++;
+                    }
+                flag=1;
+                }
+                j++;
+            } while(flag==0);
+        }
+        for(size_t i=0; i<con[cavity]; i++)
+            con[nb[cavity][i]]--;
+        con[cavity] = 0;
+
+
+        // Do everything starting from the new graph********
+
+        makekindex();
+        theta = theta_org;
+
+        for(size_t i=0; i<kmax*N; i++)
+            xfield[i] = 3.0*(2*rnd_uniform()-1.);
+
+        for(i2=0; i2<concav; i2++){ // Subsequently apply a field to each cavity spin ****************
+
+            s2 = nb[cavity][i2];    // identify the index of the cavity spin
+            for(size_t i=0; i<con[s2]; i++)
+                rfield[kmax*s2+i] = 1.;
+
+            runx=0;
+            do {      // From here start the response and belief propagation
+                runx++;
+                md=0.0;
+                for(k=0; k<N; k++){
+                    if(k!=cavity) {
+                        for(size_t i=0; i<con[k]; i++)
+                            thbJsite[i] = tJ[k][i];       
+                        for(l=0; l<con[k]; l++){
+                            xinter = 1.;      
+                            rinter = 0.;
+                            if(k==s2) rinter += 1.;
+                            for(j=0; j<con[k]; j++)
+                                if(j!=l){
+                                    variab2 = tanh(xfield[kmax*nb[k][j]+kindex[k][j]]);
+                                    variab1 = thbJsite[j]*variab2;
+                                    xinter *= (1.+variab1)/(1.-variab1);
+
+                                    rinter += thbJsite[j]*rfield[kmax*nb[k][j]+kindex[k][j]]*(1-variab2*variab2)/(1-variab1*variab1);
+                                }
+
+                            variab1 = 0.5*log(xinter);
+                            xinter = variab1 + theta[k];
+                            devs[kmax*k+l] = xinter-xfield[kmax*k+l];
+                            xfield[kmax*k+l] = xfield[kmax*k+l]+devs[kmax*k+l]*eps;
+                            if( fabs(devs[kmax*k+l]) > md )
+                                md = fabs(devs[kmax*k+l]);
+
+                            devs2[kmax*k+l] = rinter-rfield[kmax*k+l];
+                            rfield[kmax*k+l] = rfield[kmax*k+l]+devs2[kmax*k+l]*eps;
+                            if( fabs(devs2[kmax*k+l]) > md )
+                                md = fabs(devs2[kmax*k+l]);
+                        }
+                    }
+                }
+            } while((md > Tol())&&(runx<runs)); // Precision condition reached -> BP and RP finished
+            if(runx==runs)
+                if( Verbose() >= 2 )
+                    cout << "init_cor_resp: Convergence not reached (md=" << md << ")..." << endl;
+            if(md > maxdev)
+                maxdev = md;
+
+            // compute the observables (i.e. magnetizations and responses)******
+
+            for(size_t i=0; i<concav; i++){
+                rinter = 0.; 
+                xinter = 1.;
+                if(i!=i2)
+                    for(j=0; j<con[nb[cavity][i]]; j++){ 
+                        variab2 = tanh(xfield[kmax*nb[nb[cavity][i]][j]+kindex[nb[cavity][i]][j]]);
+                        variab1 = tJ[nb[cavity][i]][j]*variab2;
+                        rinter +=  tJ[nb[cavity][i]][j]*rfield[kmax*nb[nb[cavity][i]][j]+kindex[nb[cavity][i]][j]]*(1-variab2*variab2)/(1-variab1*variab1);
+                        xinter *= (1.+variab1)/(1.-variab1);
+                    }
+                xinter = tanh(0.5*log(xinter)+theta[nb[cavity][i]]);
+                res[i] = rinter*(1-xinter*xinter);
+            }
+
+            // *******************
+
+            for(size_t i=0; i<concav; i++)
+                if(nb[cavity][i]!=s2)
+            //      if(i!=i2)
+                    cors[cavity][i2][i] = res[i];
+                else
+                    cors[cavity][i2][i] = 0;
+        } // close for i2 = 0...concav
+    }
+
+    // restore nb, tJ, con
+    tJ = tJ_org;
+    nb = nb_org;
+    con = con_org;
+    theta = theta_org;
+
+    return maxdev;
+}
+
+
+double MR::T(size_t i, sub_nb A) {
+    // i is a variable index
+    // A is a subset of nb[i]
+    //
+    // calculate T{(i)}_A as defined in Rizzo&Montanari e-print (2.17)
+    
+    sub_nb _nbi_min_A(con[i]);
+    for( size_t __j = 0; __j < A.size(); __j++ )
+        _nbi_min_A -= A[__j];
+
+    double res = theta[i];
+    for( size_t __j = 0; __j < _nbi_min_A.size(); __j++ ) {
+        size_t _j = _nbi_min_A[__j];
+        res += atanh(tJ[i][_j] * M[i][_j]);
+    }
+    return tanh(res);
+}
+
+
+double MR::T(size_t i, size_t _j) {
+    sub_nb j(con[i]);
+    j.clear();
+    j += _j;
+    return T(i,j);
+}
+
+
+double MR::Omega(size_t i, size_t _j, size_t _l) {
+    sub_nb jl(con[i]);
+    jl.clear();
+    jl += _j;
+    jl += _l;
+    double Tijl = T(i,jl);
+    return Tijl / (1.0 + tJ[i][_l] * M[i][_l] * Tijl);
+}
+
+
+double MR::Gamma(size_t i, size_t _j, size_t _l1, size_t _l2) {
+    sub_nb jll(con[i]);
+    jll.clear();
+    jll += _j;
+    double Tij = T(i,jll);
+    jll += _l1;
+    jll += _l2;
+    double Tijll = T(i,jll);
+
+    return (Tijll - Tij) / (1.0 + tJ[i][_l1] * tJ[i][_l2] * M[i][_l1] * M[i][_l2] + tJ[i][_l1] * M[i][_l1] * Tijll + tJ[i][_l2] * M[i][_l2] * Tijll);
+}
+
+
+double MR::Gamma(size_t i, size_t _l1, size_t _l2) {
+    sub_nb ll(con[i]);
+    ll.clear();
+    double Ti = T(i,ll);
+    ll += _l1;
+    ll += _l2;
+    double Till = T(i,ll);
+
+    return (Till - Ti) / (1.0 + tJ[i][_l1] * tJ[i][_l2] * M[i][_l1] * M[i][_l2] + tJ[i][_l1] * M[i][_l1] * Till + tJ[i][_l2] * M[i][_l2] * Till);
+}
+
+
+double MR::_tJ(size_t i, sub_nb A) {
+    // i is a variable index
+    // A is a subset of nb[i]
+    //
+    // calculate the product of all tJ[i][_j] for _j in A
+    
+    size_t Asize = A.size();
+    switch( Asize ) {
+        case 0: return 1.0;
+//      case 1: return tJ[i][A[0]];
+//      case 2: return tJ[i][A[0]] * tJ[i][A[1]];
+//      case 3: return tJ[i][A[0]] * tJ[i][A[1]] * tJ[i][A[2]];
+        default:
+            size_t __j = Asize - 1;
+            size_t _j = A[__j];
+            sub_nb A_j = A - _j;
+            return tJ[i][_j] * _tJ(i, A_j);
+    }
+
+}
+
+
+double MR::appM(size_t i, sub_nb A) {
+    // i is a variable index
+    // A is a subset of nb[i]
+    //
+    // calculate the moment of variables in A from M and cors, neglecting higher order cumulants,
+    // defined as the sum over all partitions of A into subsets of cardinality two at most of the
+    // product of the cumulants (either first order, i.e. M, or second order, i.e. cors) of the 
+    // entries of the partitions
+    
+    size_t Asize = A.size();
+    switch( Asize ) {
+        case 0: return 1.0;
+//      case 1: return M[i][A[0]];
+//      case 2: return M[i][A[0]] * M[i][A[1]] + cors[i][A[0]][A[1]];
+//      case 3: return M[i][A[0]] * M[i][A[1]] * M[i][A[2]] + M[i][A[0]] * cors[i][A[1]][A[2]] + M[i][A[1]] * cors[i][A[0]][A[2]] + M[i][A[2]] * cors[i][A[0]][A[1]];
+        default:
+            size_t __j = Asize - 1;
+            size_t _j = A[__j];
+            sub_nb A_j = A - _j;
+
+            double result = M[i][_j] * appM(i, A_j);
+            for( size_t __k = 0; __k < __j; __k++ ) {
+                size_t _k = A[__k];
+                result += cors[i][_j][_k] * appM(i,A_j - _k);
+            }
+
+            return result;
+    }
+}
+
+
+void MR::sum_subs(size_t j, sub_nb A, double *sum_even, double *sum_odd) {
+    // j is a variable index
+    // A is a subset of nb[j]
+
+    // calculate sum over all even/odd subsets B of A of _tJ(j,B) appM(j,B)
+
+    *sum_even = 0.0;
+    *sum_odd = 0.0;
+
+    sub_nb S(A.size());
+    S.clear();
+    do { // for all subsets of A
+
+        // construct subset B of A corresponding to S
+        sub_nb B = A;
+        B.clear();
+        size_t Ssize = S.size();
+        for( size_t bit = 0; bit < Ssize; bit++ )
+            B += A[S[bit]];
+        
+        if( S.size() % 2 )
+            *sum_odd += _tJ(j,B) * appM(j,B);
+        else
+            *sum_even += _tJ(j,B) * appM(j,B);
+
+        ++S;
+    } while( !S.empty() );
+}
+            
+
+void MR::solvemcav() { 
+    double sum_even, sum_odd;
+    double maxdev;
+    size_t maxruns = 1000;
+
+    makekindex();
+    for(size_t i=0; i<N; i++)
+        for(size_t _j=0; _j<con[i]; _j++)
+            M[i][_j]=0.1;
+
+    size_t run=0;
+    do {
+        maxdev=0.0;
+        run++;
+        for(size_t i=0; i<N; i++){ // for all i
+            for(size_t _j=0; _j<con[i]; _j++){ // for all j in N_i
+                size_t _i = kindex[i][_j];
+                size_t j = nb[i][_j];
+                assert( nb[j][_i] == i );
+
+                double newM = 0.0;
+                if( Updates() == UpdateType::FULL ) {
+                    // find indices in nb[j] that do not correspond with i
+                    sub_nb _nbj_min_i(con[j]);
+                    _nbj_min_i -= kindex[i][_j];
+
+                    // find indices in nb[i] that do not correspond with j
+                    sub_nb _nbi_min_j(con[i]);
+                    _nbi_min_j -= _j;
+
+                    sum_subs(j, _nbj_min_i, &sum_even, &sum_odd);
+                    newM = (tanh(theta[j]) * sum_even + sum_odd) / (sum_even + tanh(theta[j]) * sum_odd);
+
+                    sum_subs(i, _nbi_min_j, &sum_even, &sum_odd);
+                    double denom = sum_even + tanh(theta[i]) * sum_odd;
+                    double numer = 0.0;
+                    for(size_t _k=0; _k<con[i]; _k++) if(_k != _j) {
+                        sum_subs(i, _nbi_min_j - _k, &sum_even, &sum_odd);
+                        numer += tJ[i][_k] * cors[i][_j][_k] * (tanh(theta[i]) * sum_even + sum_odd);
+                    }
+                    newM -= numer / denom;
+                } else if( Updates() == UpdateType::LINEAR ) {
+                    newM = T(j,_i);
+                    for(size_t _l=0; _l<con[i]; _l++) if( _l != _j )
+                        newM -= Omega(i,_j,_l) * tJ[i][_l] * cors[i][_j][_l];
+                    for(size_t _l1=0; _l1<con[j]; _l1++) if( _l1 != _i )
+                        for( size_t _l2=_l1+1; _l2<con[j]; _l2++) if( _l2 != _i)
+                            newM += Gamma(j,_i,_l1,_l2) * tJ[j][_l1] * tJ[j][_l2] * cors[j][_l1][_l2];
+                }
+
+                double dev = newM - M[i][_j];
+//              dev *= 0.02;
+                if( fabs(dev) >= maxdev )
+                    maxdev = fabs(dev);
+
+                newM = M[i][_j] + dev;
+                if( fabs(newM) > 1.0 )
+                    newM = sign(newM);
+                M[i][_j] = newM;
+            }
+        }
+    } while((maxdev>Tol())&&(run<maxruns));
+
+    updateMaxDiff( maxdev );
+
+    if(run==maxruns){
+        if( Verbose() >= 1 )
+            cout << "solve_mcav: Convergence not reached (maxdev=" << maxdev << ")..." << endl;
+    }
+}
+
+void MR::solveM() { 
+    for(size_t i=0; i<N; i++) {
+        if( Updates() == UpdateType::FULL ) {
+            // find indices in nb[i]
+            sub_nb _nbi(con[i]);
+
+            // calc numerator1 and denominator1
+            double sum_even, sum_odd;
+            sum_subs(i, _nbi, &sum_even, &sum_odd);
+
+            Mag[i] = (tanh(theta[i]) * sum_even + sum_odd) / (sum_even + tanh(theta[i]) * sum_odd);
+
+        } else if( Updates() == UpdateType::LINEAR ) {
+            sub_nb empty(con[i]);
+            empty.clear();
+            Mag[i] = T(i,empty);
+
+            for(size_t _l1=0; _l1<con[i]; _l1++)
+                for( size_t _l2=_l1+1; _l2<con[i]; _l2++)
+                    Mag[i] += Gamma(i,_l1,_l2) * tJ[i][_l1] * tJ[i][_l2] * cors[i][_l1][_l2];
+        }
+        if(fabs(Mag[i])>1.)
+            Mag[i] = sign(Mag[i]);
+    }
+}
+
+
+double MR::init_cor() {
+    for( size_t i = 0; i < nrVars(); i++ ) {
+        vector<Factor> pairq;
+        if( Inits() == InitType::CLAMPING ) {
+            BP bpcav(*this, Properties()("updates",string("SEQMAX"))("tol", string("1e-9"))("maxiter", string("1000UL"))("verbose", string("0UL")));
+            bpcav.makeCavity( var(i) );
+            pairq = calcPairBeliefs( bpcav, delta(var(i)), false );
+        } else if( Inits() == InitType::EXACT ) {
+            JTree jtcav(*this, Properties()("updates",string("HUGIN"))("verbose", string("0UL")) );
+            jtcav.makeCavity( var(i) );
+            pairq = calcPairBeliefs( jtcav, delta(var(i)), false );
+        }
+        for( size_t jk = 0; jk < pairq.size(); jk++ ) {
+            VarSet::const_iterator kit = pairq[jk].vars().begin();
+            size_t j = findVar( *(kit) );
+            size_t k = findVar( *(++kit) );
+            pairq[jk].normalize(Prob::NORMPROB);
+            double cor = (pairq[jk][3] - pairq[jk][2] - pairq[jk][1] + pairq[jk][0]) - (pairq[jk][3] + pairq[jk][2] - pairq[jk][1] - pairq[jk][0]) * (pairq[jk][3] - pairq[jk][2] + pairq[jk][1] - pairq[jk][0]);
+            for( size_t _j = 0; _j < con[i]; _j++ ) if( nb[i][_j] == j )
+                for( size_t _k = 0; _k < con[i]; _k++ ) if( nb[i][_k] == k ) {
+                    cors[i][_j][_k] = cor;
+                    cors[i][_k][_j] = cor;
+                }
+        }
+    }
+}
+
+
+string MR::identify() const { 
+    stringstream result (stringstream::out);
+    result << Name << GetProperties();
+    return result.str();
+}
+
+
+double MR::run() {
+    if( supported ) {
+        if( Verbose() >= 1 )
+            cout << "Starting " << identify() << "...";
+
+        clock_t tic = toc();
+//        Diffs diffs(nrVars(), 1.0);
+
+        M.resize(N);
+        for(size_t i=0; i<N; i++)
+          M[i].resize(kmax);
+
+        cors.resize(N);
+        for(size_t i=0; i<N; i++)
+          cors[i].resize(kmax);
+        for(size_t i=0; i<N; i++)
+          for(size_t j=0; j<kmax; j++)
+            cors[i][j].resize(kmax);
+
+        kindex.resize(N);
+        for(size_t i=0; i<N; i++)
+          kindex[i].resize(kmax);
+
+        if( Inits() == InitType::RESPPROP )
+            updateMaxDiff( init_cor_resp() );
+        else if( Inits() == InitType::EXACT )
+            init_cor(); // FIXME no MaxDiff() calculation
+        else if( Inits() == InitType::CLAMPING )
+            init_cor(); // FIXME no MaxDiff() calculation
+
+        solvemcav();
+
+        Mag.resize(N);
+        solveM();
+
+        if( Verbose() >= 1 )
+            cout << "MR needed " << toc() - tic << " clocks." << endl;
+
+        return 0.0;
+    } else
+        return NAN;
+}
+
+
+void MR::makekindex() {
+    for(size_t i=0; i<N; i++)
+        for(size_t j=0; j<con[i]; j++) {
+            size_t ij = nb[i][j];       // ij is the j'th neighbour of spin i
+            size_t k=0;
+            while( nb[ij][k] != i )
+                k++;
+            kindex[i][j] = k;   // the j'th neighbour of spin i has spin i as its k'th neighbour
+        }
+}
+
+
+Factor MR::belief( const Var &n ) const {
+    if( supported ) {
+        size_t i = findVar( n );
+
+        double x[2];
+        x[0] = 0.5 - Mag[i] / 2.0;
+        x[1] = 0.5 + Mag[i] / 2.0;
+
+        return Factor( n, x );
+    } else
+        return Factor();
+}
+
+
+vector<Factor> MR::beliefs() const {
+    vector<Factor> result;
+    for( size_t i = 0; i < nrVars(); i++ )
+        result.push_back( belief( var(i) ) );
+    return result;
+}
+
+
+
+MR::MR( const FactorGraph &fg, const Properties &opts ) : DAIAlgFG(fg, opts), supported(true) {
+    // check whether all vars in fg are binary
+    // check whether connectivity is <= kmax
+    for( size_t i = 0; i < fg.nrVars(); i++ )
+        if( (fg.var(i).states() > 2) || (fg.delta(fg.var(i)).size() > kmax) ) {
+            supported = false;
+            break;
+        }
+    
+    if( !supported )
+        return;
+    
+    // check whether all interactions are pairwise or single
+    for( size_t I = 0; I < fg.nrFactors(); I++ )
+        if( fg.factor(I).vars().size() > 2 ) {
+            supported = false;
+            break;
+        }
+
+    if( !supported )
+        return;
+
+    // create w and th
+    size_t _N = fg.nrVars();
+
+    double *w = new double[_N*_N];
+    double *th = new double[_N];
+    
+    for( size_t i = 0; i < _N; i++ ) {
+        th[i] = 0.0;
+        for( size_t j = 0; j < _N; j++ )
+            w[i*_N+j] = 0.0;
+    }
+
+    for( size_t I = 0; I < fg.nrFactors(); I++ ) {
+        const Factor &psi = fg.factor(I);
+        if( psi.vars().size() == 1 ) {
+            size_t i = fg.findVar( *(psi.vars().begin()) );
+            th[i] += 0.5 * log(psi[1] / psi[0]);
+        } else if( psi.vars().size() == 2 ) {
+            size_t i = fg.findVar( *(psi.vars().begin()) );
+            VarSet::const_iterator jit = psi.vars().begin();
+            size_t j = fg.findVar( *(++jit) );
+
+            w[i*_N+j] += 0.25 * log(psi[3] * psi[0] / (psi[2] * psi[1])); 
+            w[j*_N+i] += 0.25 * log(psi[3] * psi[0] / (psi[2] * psi[1])); 
+
+            th[i] += 0.25 * log(psi[3] / psi[2] * psi[1] / psi[0]);
+            th[j] += 0.25 * log(psi[3] / psi[1] * psi[2] / psi[0]);
+        }
+    }
+    
+    init(_N, w, th);
+
+    delete th;
+    delete w;
+}
diff --git a/mr.h b/mr.h
new file mode 100644 (file)
index 0000000..acd0281
--- /dev/null
+++ b/mr.h
@@ -0,0 +1,212 @@
+/*  Copyright (C) 2006-2008  Joris Mooij  [j dot mooij at science dot ru dot nl]
+    Radboud University Nijmegen, The Netherlands
+    
+    This file is part of libDAI.
+
+    libDAI is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libDAI is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libDAI; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#ifndef __MR_H__
+#define __MR_H__
+
+
+#include <vector>
+#include "factorgraph.h"
+#include "daialg.h"
+#include "enum.h"
+
+
+using namespace std;
+
+
+class sub_nb;
+
+
+class MR : public DAIAlgFG {
+    private:
+        bool supported;                             // is the underlying factor graph supported?
+
+        vector<size_t>                   con;       // con[i] = connectivity of spin i
+        vector<vector<size_t> >          nb;        // nb[i] are the neighbours of spin i
+        vector<vector<double> >          tJ;        // tJ[i][_j] is the tanh of the interaction between spin i and its neighbour nb[i][_j]
+        vector<double>                   theta;     // theta[i] is the local field on spin i
+        vector<vector<double> >          M;         // M[i][_j] is M^{(i)}_j
+        vector<vector<size_t> >          kindex;    // the _j'th neighbour of spin i has spin i as its kindex[i][_j]'th neighbour
+        vector<vector<vector<double> > > cors;
+    
+        static const size_t kmax = 31;
+        
+        size_t N;
+
+        vector<double> Mag;
+
+    public:
+        ENUM2(UpdateType,FULL,LINEAR)
+        ENUM3(InitType,RESPPROP,CLAMPING,EXACT)
+
+        UpdateType Updates() const { return GetPropertyAs<UpdateType>("updates"); }
+        InitType Inits() const { return GetPropertyAs<InitType>("inits"); }
+
+        MR( const FactorGraph & fg, const Properties &opts );
+        void init(size_t _N, double *_w, double *_th);
+        void makekindex();
+        void read_files();
+        double init_cor();
+        double init_cor_resp();
+        void solvemcav();
+        void solveM();
+        double run();
+        Factor belief( const Var &n ) const;
+        Factor belief( const VarSet &ns ) const { assert( 0 == 1 ); }
+        vector<Factor> beliefs() const;
+        Complex logZ() const { return NAN; }
+        void init() { assert( checkProperties() );