├── .gitignore ├── CMakeLists.txt ├── ChangeLog ├── Design-Notes.md ├── LICENSE ├── README.md ├── demo ├── README.md ├── seir.scm └── social-network.gml_2b.png ├── examples ├── README.md ├── basic-network.scm ├── dict-loop.scm ├── dict-mixed.scm ├── dict-tree.scm ├── dict-triquad.scm ├── export-to-gml.scm ├── grammar.scm ├── n20.gml.png ├── parameters.scm └── trisexual.scm ├── opencog ├── CMakeLists.txt ├── generate │ ├── Aggregate.cc │ ├── Aggregate.h │ ├── BasicParameters.cc │ ├── BasicParameters.h │ ├── CMakeLists.txt │ ├── CollectStyle.cc │ ├── CollectStyle.h │ ├── Dictionary.cc │ ├── Dictionary.h │ ├── GenerateCallback.h │ ├── LinkStyle.cc │ ├── LinkStyle.h │ ├── Odometer.cc │ ├── Odometer.h │ ├── README.md │ ├── RandomCallback.cc │ ├── RandomCallback.h │ ├── RandomParameters.h │ ├── SimpleCallback.cc │ └── SimpleCallback.h ├── guile │ ├── CMakeLists.txt │ └── modules │ │ ├── CMakeLists.txt │ │ └── GenerateSCM.cc └── scm │ ├── CMakeLists.txt │ └── opencog │ ├── generate.scm │ └── generate │ └── gml-export.scm └── tests ├── CMakeLists.txt └── generate ├── AggregationUTest.cxxtest ├── BasicNetworkUTest.cxxtest ├── CMakeLists.txt ├── GraphUTest.cxxtest ├── basic-network.scm ├── dict-biloop.scm ├── dict-biquad.scm ├── dict-helloworld.scm ├── dict-loop.scm ├── dict-mixed.scm ├── dict-quad.scm ├── dict-tree.scm ├── dict-triquad.scm └── graph.scm /.gitignore: -------------------------------------------------------------------------------- 1 | *.ss~ 2 | *.ss#* 3 | .#*.ss 4 | 5 | *.scm~ 6 | *.scm#* 7 | .#*.scm 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Master Opencog CMake file. 3 | # 4 | # We need at least version 3.13 to get TARGET_LINK_DIRECTORIES 5 | MESSAGE("Found CMake version ${CMAKE_VERSION}") 6 | CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 7 | 8 | PROJECT(generate) 9 | 10 | # default build type 11 | IF (CMAKE_BUILD_TYPE STREQUAL "") 12 | SET(CMAKE_BUILD_TYPE Release) 13 | ENDIF (CMAKE_BUILD_TYPE STREQUAL "") 14 | 15 | MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 16 | 17 | # add the 'lib' dir to cmake's module search path 18 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/lib/") 19 | 20 | # Cogutil 21 | FIND_PACKAGE(CogUtil) 22 | IF (COGUTIL_FOUND) 23 | MESSAGE(STATUS "CogUtil found.") 24 | ADD_DEFINITIONS(-DHAVE_COGUTIL) 25 | SET(HAVE_COGUTIL 1) 26 | INCLUDE_DIRECTORIES(${COGUTIL_INCLUDE_DIR}) 27 | ELSE (COGUTIL_FOUND) 28 | MESSAGE(FATAL_ERROR "CogUtil missing: it is needed for everything!") 29 | ENDIF (COGUTIL_FOUND) 30 | 31 | # add the 'cmake' directory from cogutil to search path 32 | list(APPEND CMAKE_MODULE_PATH ${COGUTIL_DATA_DIR}/cmake) 33 | 34 | include(${COGUTIL_DATA_DIR}/cmake/OpenCogGccOptions.cmake) 35 | include(${COGUTIL_DATA_DIR}/cmake/OpenCogLibOptions.cmake) 36 | include(${COGUTIL_DATA_DIR}/cmake/OpenCogInstallOptions.cmake) 37 | include(${COGUTIL_DATA_DIR}/cmake/Summary.cmake) 38 | 39 | # =================================================================== 40 | # Check for existance of various required, optional packages. 41 | 42 | # AtomSpace 43 | FIND_PACKAGE(AtomSpace 5.0.3 REQUIRED) 44 | IF (ATOMSPACE_FOUND) 45 | MESSAGE(STATUS "AtomSpace found.") 46 | ADD_DEFINITIONS(-DHAVE_ATOMSPACE) 47 | SET(HAVE_ATOMSPACE 1) 48 | INCLUDE_DIRECTORIES(${ATOMSPACE_INCLUDE_DIR}) 49 | ELSE (ATOMSPACE_FOUND) 50 | MESSAGE(FATAL_ERROR "AtomSpace missing: it is needed for everything!") 51 | ENDIF (ATOMSPACE_FOUND) 52 | 53 | # ---------------------------------------------------------- 54 | # Needed for unit tests. 55 | 56 | FIND_PACKAGE(Cxxtest) 57 | IF (CXXTEST_FOUND) 58 | MESSAGE(STATUS "CxxTest found.") 59 | ELSE (CXXTEST_FOUND) 60 | MESSAGE(STATUS "CxxTest missing: needed for unit tests.") 61 | ENDIF (CXXTEST_FOUND) 62 | 63 | # ---------------------------------------------------------- 64 | # This is required for Guile 65 | 66 | include(OpenCogFindGuile) 67 | 68 | # =================================================================== 69 | # Global includes 70 | 71 | INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR} ${CMAKE_BINARY_DIR} 72 | ${COGUTIL_INCLUDE_DIR}) 73 | 74 | INCLUDE("${ATOMSPACE_DATA_DIR}/cmake/OpenCogGuile.cmake") 75 | 76 | # =================================================================== 77 | # Add subdirectories 78 | 79 | ADD_SUBDIRECTORY(opencog) 80 | 81 | IF (CXXTEST_FOUND) 82 | ADD_CUSTOM_TARGET(tests) 83 | ADD_SUBDIRECTORY(tests EXCLUDE_FROM_ALL) 84 | ADD_CUSTOM_TARGET(test 85 | DEPENDS tests 86 | WORKING_DIRECTORY tests 87 | COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure $(ARGS) 88 | COMMENT "Running tests..." 89 | ) 90 | ENDIF (CXXTEST_FOUND) 91 | 92 | # =================================================================== 93 | # Show a summary of what we got 94 | 95 | SUMMARY_ADD("Generate" "Graph Generation" HAVE_ATOMSPACE) 96 | SUMMARY_ADD("Scheme bindings" "Scheme (guile) bindings" HAVE_GUILE) 97 | SUMMARY_ADD("Unit tests" "Unit tests" CXXTEST_FOUND) 98 | SUMMARY_SHOW() 99 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Version 0.1.0 (17 April 2020) 2 | * Multiple examples of simple grammars. 3 | * Half-dozen configurable parameters. 4 | * Partially-assembled networks isolated in scratch space. 5 | * Epidemiology model - covid-19, of course! 6 | 7 | Version 0.0.6 (12 April 2020) 8 | * Beginnings of the random network explorer. 9 | * Prototype scheme bindings. 10 | * Export to GML (Graph Modelling Language). 11 | 12 | Version 0.0.5 (1 April 2020) 13 | * More unit tests and fixes. 14 | * Description of the breadth-first algo. 15 | 16 | Version 0.0.4 (29 March 2020) 17 | * Initial breadth-first aggregation. 18 | 19 | Version 0.0.2 (20 March 2020) 20 | * Initial depth-first aggregation. 21 | -------------------------------------------------------------------------------- /Design-Notes.md: -------------------------------------------------------------------------------- 1 | 2 | Design Notes 3 | ------------ 4 | Random ruminations on how to best implement this. 5 | 6 | 7 | ### Recursive tree-walking. 8 | This is a "simple", direct, obvious choice. 9 | Starting with a single nucleation point, pick one section, look at it's 10 | endpoints, and attach sections to each endpoint. Repeat until done. 11 | This is straight-forward to do recursively: each choice of section is 12 | a branch-point; each choice of section pushes the stack. That there's a 13 | resulting combinatorial explosion is obvious. 14 | 15 | Problem w/recursive tree-walking is that it works efficiently only when 16 | the dictionary specifies only trees. If the dictionary allows cycles, 17 | then tree-walking fails, in two distinct ways. First, any cycle allows 18 | infinite recursion; so recursion has to be blocked some way, either by 19 | depth or by cycle-detection. Second, even when made finite in this way, 20 | the walker will walk the cycle every possible way, even when the final 21 | resulting graph is the same. This leads to a lot of wasted CPU time, 22 | rediscovering the same graph. 23 | 24 | Depth-first recursion can be visualized as follows. Imagine a small 25 | tree or even a single point. A branch-tip is selected - just one - 26 | and it is grown by aggregation, until conclusion. Here "aggregation" 27 | means joining new puzzle-pieces to existing unconnected connectors. 28 | In depth-first aggregation, if the original seed has a second connector, 29 | it is completely ignored until all growths from the first have been 30 | fully explored. This is why breadth-first aggregation is intuitively 31 | more appealing: in breadth-first aggregation, all growing tips are 32 | explored in parallel. 33 | 34 | The last working version of depth-recursive tree-walking is located at 35 | ``` 36 | git commit 88b63ec3a7406929ed5ed2dd77400452c8e51596 37 | Sat Mar 21 13:37:17 2020 -0500 38 | ``` 39 | Although this "works" for simple cases, its a toy; important features 40 | have been added since then. 41 | 42 | ### Breadth-first aggregation 43 | Breadth-first aggregation is done in "parallel", with all open 44 | connectors being extended just one step, per time-step. Visually, 45 | this can be imagined as a wave or a boundary that is expanding at 46 | constant velocity: one attachment per connector per time step. 47 | Its like a wave propagating outwards; all points on boundaries are 48 | explored simultaneously. 49 | 50 | Managing choices for breadth-first search is harder, and more memory 51 | intensive. 52 | 53 | Continuing with the visualization: in general, the length of the 54 | boundary increases exponentially, and so, if visualized as a surface, 55 | that surface is hyperbolic. Back-tracking requires undoing each boundary 56 | step by one, before other possible connections can be explored. As a 57 | result, the inner-most connectors, close to the seed, are effectively 58 | frozen into place, as very deep backtracking would be required to 59 | explore alternative connections for those connectors. If a poor choice 60 | is made for the first few sections connecting to a seed, it can be a 61 | very long time before there is enough back-tracking to where those 62 | "deep alternatives" can be explored. 63 | 64 | These last thoughts suggest that a better, more balanced sampling of 65 | the search space is to maintain a population of extensions grown from 66 | a seed, and explore each one distinctly, thus allowing "deep 67 | alternatives" to be fully sampled, even as getting bogged down at 68 | the edges of each growth. 69 | 70 | ### Pruning 71 | Its hard to imagine how to improve on the above when there is a single 72 | starter nucleation point. But LG provides the counter-example, when 73 | there are many nucleation points (i.e. fixed words in sentence, each 74 | word has some number of associated sections). One can then use pruning 75 | to discard sections that cannot possibly match; this is a multi-pass 76 | algo that runs over the "global" set of sections. 77 | 78 | ### Desirable algorithmic properties 79 | Based on above, some thoughts. 80 | 81 | #### Link-type limiting features. 82 | For example, for language-generation, we want to avoid having certain 83 | link-types appear more than once; for example, neither a subject, nor an 84 | object link should appear more than once in a sentence, unless that 85 | sentence is paraphrasing: e.g. "John said that Mary is beautiful." 86 | 87 | #### Cycle preference. 88 | For example, for language-generation, the wall-noun, wall-verb and 89 | noun-verb should normally form a cycle. It's a mistake to break this 90 | cycle, and extend one of the links to a different vertex (e.g. have two 91 | different subjects). 92 | 93 | Do we need to have explicit "must-form-a-cycle" rules? Can we do this 94 | statistically? 95 | 96 | 97 | ### Random walks through possibilities 98 | Walk through possible connections, randomly. But rather than uniform 99 | weighting, we need to spread "attention" according to weights. This 100 | would require a redesign of the attention allocation subsystem. 101 | 102 | #### Fun demo! 103 | Use a small collection of words from text chat to deposit attention 104 | into the network. Then trace throught grammatically network crawls 105 | throught the net. Do this on an IRC chat channel. (This would resemble 106 | the behavior of bayesian/markov language generators, but different, 107 | because the network is defined differently.) 108 | 109 | 110 | Literature 111 | ========== 112 | 113 | Wave-function Collapse 114 | ---------------------- 115 | The "wave-function collapse algorithm" described by Marian, "[Infinite 116 | procedurally generated city with the Wave Function Collapse 117 | algorithm](https://marian42.de/article/wfc/)" (2019) appears to be a 118 | good way of assembling pieces in a fixed n-dimensional world. 119 | 120 | It divides the universe into blocks (for sentence generation, this would 121 | be a 1-D universe - a linear string). It selects a location in this 122 | grid, picking the grid-point with the lowest entropy. Naively, this is 123 | the slot with the fewest choices. More formally, its the slot with the 124 | smallest S = -sum_i p_i log p_i where -log p_i is the "cost". A single 125 | probability-weighted choice is made for that slot, and then the process 126 | is repeated. 127 | 128 | It is not clear how to enforce additional conditions, such as 129 | no-links-crossing, with this algo. 130 | 131 | Code for the blocks-world algo is here: 132 | https://github.com/mxgmn/WaveFunctionCollapse 133 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | 2 | Covid-19 Demo 3 | -------------- 4 | Demo of a random social network with a SEIR Covid-19 simulation. 5 | 6 | SEIR == "susceptible", "exposed", "infected", and "recovered". 7 | 8 | This is a demo, only! It is demonstrating several different AtomSpace 9 | programming techniques, one of which is the brand-new (version 0.1) 10 | random network generator. Another is Value Flows: the use of Values 11 | for holding mutable state, and flowing that state through a network. 12 | It is assumed that the reader is already familiar with the basics of 13 | the OpenCog AtomSpace! This demo **could** be enhanced to do actual 14 | scientific exploration; see "making this useful", below. 15 | 16 | This demo generates a random "social network", consisting of two types 17 | of relationships: "friends" and "strangers". It adopts a simple SEIR 18 | model of disease transmission and progression along this network. 19 | The model itself is a (simple) hand-coded state-transition machine. 20 | 21 | There is a 22 | [blog post](https://blog.opencog.org/2020/04/22/covid-19-modelling-and-random-social-networks/) 23 | that reviews this demo. It touches on the high-lites of the demo, 24 | avoiding all of the grunge needed to get actual, working code. Thus, 25 | reading it is recommended before diving into the code. 26 | 27 | 28 | Making this Useful 29 | ------------------ 30 | This is a demo, not a final finished product. 31 | 32 | There's no whizzy visualization of the disease progression, or the 33 | resulting stats. You can visualize snapshots of the network with 34 | CytoScape or Gephi, but it won't be animated as the disease spreads. 35 | 36 | There's no statistical analysis performed, and no graphs or curves are 37 | drawn. The demo shows how to generate the raw data, not how to analyze 38 | raw data. There are plenty of tools designed for statistical analysis. 39 | Use those. 40 | 41 | A few notes about scheme, python and Atomese. This demo would appear 42 | to be written ins scheme. It could have just as eassily been written 43 | in python, without changing it's basic form (the AtomSpace has python 44 | bindings.) It may seem hard to beleive, but the choice of language 45 | doesn't really much matter. That's because all of the hard work is 46 | done in Atomese. 47 | 48 | Atomese is a graphical programming language, and it was designed for 49 | automation. Atomese expressions are graphs; they live as graphs in the 50 | AtomSpace graph database. Atomese encodes a type of "Abstract Syntax 51 | Tree" (see [Wikipedia](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) 52 | and such a representation is useful for all of the reasons that the 53 | Wikipedia article says they are. But it also means that "coding" in 54 | Atomese can sometimes feel unusual. Like: "Why can't I just code in 55 | python? It would be so much easier!" -- Well, but that misses the 56 | point. And the point is, again: Atomese was designed for automation. 57 | 58 | What this really means is that the graphs are meant to be easy for other 59 | algorithms to manipulate. It's designed for ease-of-use by machines, 60 | not for humans! The style is more barren, unadorned, without 61 | short-cuts. Its a bit verbose at times. That's OK, machines don't mind 62 | verbosity and tedium. 63 | 64 | Consider, for example, a graphical editor - something where you can 65 | drag-n-drop, hand-draw interacting bubble diagrams. Something easy 66 | to use - something a medical professional could get the hang of in 67 | a few hours - something where a disease model could be sketched out 68 | with bubbles and lines and flow-charts. The goal of Atomese is that 69 | it becomes easy -- really easy -- to convert such diagrams into 70 | executable code. That's what Atomese is designed to do. 71 | 72 | In this demo, the disease progression model is written in Atomese. 73 | This is the model that a graphical GUI WYSIWYG whizz-bang system 74 | would generate. 75 | 76 | Atomese enables other, more abstract approaches. Consider an automatic 77 | model explorer: a system that automatically creates a variety of 78 | different disease progression models, and then explores how each 79 | model works. The model generator might try to fit an existing dataset. 80 | Alternately, it could mutate models so as to obtain certain 81 | characteristics. This moves beyond just adjusting some parameters 82 | in some hand-created, hypothesized model. This is not just some 83 | monte-carlo search or machine-learning hill-climbing for parameter 84 | tuning. This is a whole-sale creation of previously non-existent 85 | code. Atomese allows software to read, understand, mutate, modify 86 | and write programs. 87 | 88 | Thus, in reading the code, keep in mind that there is nothing special 89 | about the SEIR model; its a stand-in for what could be a generic, 90 | arbitrary state transition machine. Such machines can be hand-coded, 91 | of course, but the interesting application is when they are generated 92 | from other sources. 93 | 94 | Last but not least: keep in mind that the network generator is 95 | currently at version 0.1, and is not yet as versatile, flexible and 96 | powerful as it could be. The parameter settings used in the demo 97 | generate networks that are long and thin. Very long and thin. Like 98 | the coiled-up network below. 99 | 100 | ![Example social network](./social-network.gml_2b.png) 101 | -------------------------------------------------------------------------------- /demo/social-network.gml_2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencog/generate/8f7bc7cc9303ca9ca6971ad520f50b8d5356d39f/demo/social-network.gml_2b.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | Examples 3 | ======== 4 | A basic understanding of the AtomSpace is required. See the 5 | [AtomSpace](https://github.com/opencog/atomspace) git repo, and the 6 | [examples](https://github.com/opencog/atomspace/examples) there. 7 | 8 | In addition, at least some basic familiarity with the theory of 9 | [Link Grammar](https://www.abisource.com/projects/link-grammar/) 10 | is useful, as the work here generalizes the notions there. 11 | 12 | The examples are meant to be explored in the order given. 13 | 14 | * [basic-network](basic-network.scm) -- 15 | Definitions for generating a simple random network. 16 | * [export-to-gml](export-to-gml.scm) -- 17 | Generate a random network, and export it to GML for visualztion. 18 | * [parameters.scm](parameters.scm) -- 19 | Some of the paramaters that control the network generation. 20 | 21 | The next batch of examples define simple English-language grammars that 22 | encode dependency relations. These are written in the style of Link 23 | Grammar (LG). In LG, dependencies can consist of directed or undirected 24 | edges; the examples all use undirected edges. Unlike the above, the 25 | edges have different types, indicating the type of dependency (subject, 26 | object, adjective, determiner, etc.). In LG, the parse "tree" does not 27 | need to be strictly a tree; it may contain loops (loops, in general, 28 | serve not only to indicate secondary, interesting relationships, but 29 | also serve to constrain the parse itself.) 30 | 31 | * [dict-tree](dict-tree.scm) -- 32 | A simple grammar, with typed edges, generating a dependency tree. 33 | * [dict-loop](dict-loop.scm) -- 34 | A simple grammar generating a dependency graph that includes a loop. 35 | * [dict-triquad](dict-triquad.scm) -- 36 | A grammar generating a dependency graph that consists of three 37 | quadrilaterals. 38 | * [dict-mixed](dict-mixed.scm) -- 39 | A grammar generating a dependency graph with a mixture of loops and 40 | trees. 41 | * [grammar](grammar.scm) -- 42 | The demo itself, employing the above dictionaries to generate a 43 | (small) corpus of sentences. 44 | 45 | Some general comments about the mating rules for connectors are given below: 46 | 47 | * [trisexual](trisexual.scm) -- 48 | Examples of mono-sexual, heterosexual and tri-sexual mating rules. 49 | Plus a short explanation of why this is called "sexuality"; other 50 | code calls this "polarity" (so, mono-polar, bipolar and tri-polar). 51 | 52 | That's all for now! 53 | -------------------------------------------------------------------------------- /examples/basic-network.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; basic-network.scm 3 | ; 4 | ; Example of a lexis that can be used to generate random networks. 5 | ; The lexis contains only one node type and one edge type, and so 6 | ; the generated network will consist of only these two. Six sections 7 | ; are defined, having one through six connectors on them. These are 8 | ; given a weighting, so that when the network is assembled, the 9 | ; weights determine the probability that a node will be used. 10 | ; 11 | (use-modules (srfi srfi-1)) 12 | (use-modules (opencog) (opencog exec)) 13 | 14 | ; Six burrs with arities one through six. 15 | (define b1 16 | (Section 17 | (Concept "burr-1") 18 | (ConnectorSeq 19 | (Connector (Concept "E") (ConnectorDir "*"))))) 20 | 21 | (define b2 22 | (Section 23 | (Concept "burr-2") 24 | (ConnectorSeq 25 | (Connector (Concept "E") (ConnectorDir "*")) 26 | (Connector (Concept "E") (ConnectorDir "*"))))) 27 | 28 | (define b3 29 | (Section 30 | (Concept "burr-3") 31 | (ConnectorSeq 32 | (Connector (Concept "E") (ConnectorDir "*")) 33 | (Connector (Concept "E") (ConnectorDir "*")) 34 | (Connector (Concept "E") (ConnectorDir "*"))))) 35 | 36 | (define b4 37 | (Section 38 | (Concept "burr-4") 39 | (ConnectorSeq 40 | (Connector (Concept "E") (ConnectorDir "*")) 41 | (Connector (Concept "E") (ConnectorDir "*")) 42 | (Connector (Concept "E") (ConnectorDir "*")) 43 | (Connector (Concept "E") (ConnectorDir "*"))))) 44 | 45 | (define b5 46 | (Section 47 | (Concept "burr-5") 48 | (ConnectorSeq 49 | (Connector (Concept "E") (ConnectorDir "*")) 50 | (Connector (Concept "E") (ConnectorDir "*")) 51 | (Connector (Concept "E") (ConnectorDir "*")) 52 | (Connector (Concept "E") (ConnectorDir "*")) 53 | (Connector (Concept "E") (ConnectorDir "*"))))) 54 | 55 | (define b6 56 | (Section 57 | (Concept "burr-6") 58 | (ConnectorSeq 59 | (Connector (Concept "E") (ConnectorDir "*")) 60 | (Connector (Concept "E") (ConnectorDir "*")) 61 | (Connector (Concept "E") (ConnectorDir "*")) 62 | (Connector (Concept "E") (ConnectorDir "*")) 63 | (Connector (Concept "E") (ConnectorDir "*")) 64 | (Connector (Concept "E") (ConnectorDir "*"))))) 65 | 66 | ; Give each of them a weight; this will be the probaility of 67 | ; their being drawn (selected) when assempling the graph. 68 | (define weights (PredicateNode "weights")) 69 | 70 | (cog-set-value! b1 weights (FloatValue 1)) 71 | (cog-set-value! b2 weights (FloatValue (/ 1.0 2))) 72 | (cog-set-value! b3 weights (FloatValue (/ 1.0 3))) 73 | (cog-set-value! b4 weights (FloatValue (/ 1.0 4))) 74 | (cog-set-value! b5 weights (FloatValue (/ 1.0 5))) 75 | (cog-set-value! b6 weights (FloatValue (/ 1.0 6))) 76 | 77 | ; Place the six burrs into a common lexis 78 | (define lexis (Concept "six burrs")) 79 | (Member b1 lexis) 80 | (Member b2 lexis) 81 | (Member b3 lexis) 82 | (Member b4 lexis) 83 | (Member b5 lexis) 84 | (Member b6 lexis) 85 | 86 | ; Define the connectable directions (poles). In this case, the 87 | ; connectors are all the same, and the direction (or "pole") 88 | ; connects to itself (rather than having opposite polarity). 89 | ; Furthermore, the connections are symmetric (creating undirected 90 | ; edges.) Thus, the pole-pair set is very simple. 91 | (define polarity-set (Concept "any to any")) 92 | (Member (Set (ConnectorDir "*") (ConnectorDir "*")) polarity-set) 93 | 94 | ; Be silent when loading this file. 95 | *unspecified* 96 | -------------------------------------------------------------------------------- /examples/dict-loop.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-loop.scm 3 | ; 4 | ; This defined a dictionary that generates four sentences, containing 5 | ; one cycle (loop). The loop occurs because there is an edge from the 6 | ; root to the leading verb, and an edge to the leading noun (the 7 | ; subject), as well as a link between the subject and the head-verb. 8 | ; Thus, these edges form a triangle. 9 | ; 10 | ; All four sentences have the form: 11 | ; 12 | ; +-------WV------+----O-----+ 13 | ; +----W---+---S--+ +--D--+ 14 | ; | | | | | 15 | ; LEFT-WALL John saw a cat 16 | ; 17 | ; The other three substitute "Mary" for "John" and "dog" for "cat". 18 | ; The cycle is a triangle with edges (WV, S, W) and vertexes 19 | ; (LEFT-WALL, John, saw) 20 | ; 21 | ; The dictionary defines nine "jigsaw-puzzle pieces" in it. These 22 | ; assemble into the above graphs. It should be obvious from inspection 23 | ; how these are should be assembled. ... and, if its not ... 24 | ; The connector ID's must match, and the conector directions must 25 | ; be polar opposites. The edges connecting the vertexes are undirected 26 | ; edges; the ConnectorDir polarities instead indicate "to the left" 27 | ; and "to the right". In particular, all three edges in the triangle 28 | ; are undirected. 29 | ; 30 | (use-modules (srfi srfi-1)) 31 | (use-modules (opencog)) 32 | 33 | ; Each word in the dictionary is added by means of a MemberLink. 34 | ; The name of the dictionary here is `(Concept "dict-loop")`. 35 | (define dict-loop (Concept "dict-loop")) 36 | 37 | ; To keep the example simple and readable, the membership is done 38 | ; in a giant-sized for-loop. 39 | (for-each 40 | (lambda (sect) (Member sect dict-loop)) 41 | (list 42 | 43 | (Section 44 | (Concept "LEFT-WALL") 45 | (ConnectorSeq 46 | (Connector (Concept "WV") (ConnectorDir "+")) 47 | (Connector (Concept "W") (ConnectorDir "+")))) 48 | 49 | (Section 50 | (Concept "John") 51 | (ConnectorSeq 52 | (Connector (Concept "W") (ConnectorDir "-")) 53 | (Connector (Concept "S") (ConnectorDir "+")))) 54 | 55 | (Section 56 | (Concept "Mary") 57 | (ConnectorSeq 58 | (Connector (Concept "W") (ConnectorDir "-")) 59 | (Connector (Concept "S") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept "saw") 63 | (ConnectorSeq 64 | (Connector (Concept "S") (ConnectorDir "-")) 65 | (Connector (Concept "WV") (ConnectorDir "-")) 66 | (Connector (Concept "O") (ConnectorDir "+")))) 67 | 68 | (Section 69 | (Concept "a") 70 | (ConnectorSeq 71 | (Connector (Concept "D") (ConnectorDir "+")))) 72 | 73 | (Section 74 | (Concept "cat") 75 | (ConnectorSeq 76 | (Connector (Concept "D") (ConnectorDir "-")) 77 | (Connector (Concept "O") (ConnectorDir "-")))) 78 | 79 | (Section 80 | (Concept "dog") 81 | (ConnectorSeq 82 | (Connector (Concept "D") (ConnectorDir "-")) 83 | (Connector (Concept "O") (ConnectorDir "-")))) 84 | 85 | )) ; End of the for-each loop. 86 | -------------------------------------------------------------------------------- /examples/dict-mixed.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-mixed.scm 3 | ; 4 | ; This demo shows a more complex grammar, which generates a graph 5 | ; containing trees and multiple cycles. In this case, both cycles 6 | ; are triangles, sharing a common edge (the edge between the wall 7 | ; and the head-verb.) 8 | ; 9 | ; 10 | ; +---------------------------Xp--------------+ 11 | ; +--------------------WV---------------+ | 12 | ; | +------------S-----------+ | 13 | ; | +------MXs------+ | | 14 | ; | | +------Xd----+ | | 15 | ; +-----W------+ | +---Ds-----+ | | 16 | ; | +--D--+ | | +--A--+-Xc-+ +--Xf-+ 17 | ; | | | | | | | | | | 18 | ; LEFT-WALL the dog , a black lab , barked . 19 | ; 20 | ; The other sentences swap "dog/cat" "black/white", "barked/purred". 21 | ; 22 | (use-modules (srfi srfi-1)) 23 | (use-modules (opencog)) 24 | 25 | (define dict-mixed (Concept "dict-mixed")) 26 | 27 | (for-each 28 | (lambda (sect) (Member sect dict-mixed)) 29 | (list 30 | 31 | (Section 32 | (Concept "LEFT-WALL") 33 | (ConnectorSeq 34 | (Connector (Concept "Xp") (ConnectorDir "+")) 35 | (Connector (Concept "WV") (ConnectorDir "+")) 36 | (Connector (Concept "W") (ConnectorDir "+")))) 37 | 38 | (Section 39 | (Concept "the") 40 | (ConnectorSeq 41 | (Connector (Concept "D") (ConnectorDir "+")))) 42 | 43 | (Section 44 | (Concept "a") 45 | (ConnectorSeq 46 | (Connector (Concept "Ds") (ConnectorDir "+")))) 47 | 48 | (Section 49 | (Concept "dog") 50 | (ConnectorSeq 51 | (Connector (Concept "W") (ConnectorDir "-")) 52 | (Connector (Concept "D") (ConnectorDir "-")) 53 | (Connector (Concept "MXs") (ConnectorDir "+")) 54 | (Connector (Concept "S") (ConnectorDir "+")))) 55 | 56 | (Section 57 | (Concept "cat") 58 | (ConnectorSeq 59 | (Connector (Concept "W") (ConnectorDir "-")) 60 | (Connector (Concept "D") (ConnectorDir "-")) 61 | (Connector (Concept "MXs") (ConnectorDir "+")) 62 | (Connector (Concept "S") (ConnectorDir "+")))) 63 | 64 | (Section 65 | (Concept ",") 66 | (ConnectorSeq 67 | (Connector (Concept "Xd") (ConnectorDir "+")))) 68 | 69 | (Section 70 | (Concept ",") 71 | (ConnectorSeq 72 | (Connector (Concept "Xc") (ConnectorDir "-")))) 73 | 74 | (Section 75 | (Concept ".") 76 | (ConnectorSeq 77 | (Connector (Concept "Xp") (ConnectorDir "-")) 78 | (Connector (Concept "Xf") (ConnectorDir "-")))) 79 | 80 | (Section 81 | (Concept "black") 82 | (ConnectorSeq 83 | (Connector (Concept "A") (ConnectorDir "+")))) 84 | 85 | (Section 86 | (Concept "white") 87 | (ConnectorSeq 88 | (Connector (Concept "A") (ConnectorDir "+")))) 89 | 90 | (Section 91 | (Concept "lab") 92 | (ConnectorSeq 93 | (Connector (Concept "A") (ConnectorDir "-")) 94 | (Connector (Concept "Ds") (ConnectorDir "-")) 95 | (Connector (Concept "Xd") (ConnectorDir "-")) 96 | (Connector (Concept "MXs") (ConnectorDir "-")) 97 | (Connector (Concept "Xc") (ConnectorDir "+")))) 98 | 99 | (Section 100 | (Concept "barked") 101 | (ConnectorSeq 102 | (Connector (Concept "WV") (ConnectorDir "-")) 103 | (Connector (Concept "S") (ConnectorDir "-")) 104 | (Connector (Concept "Xf") (ConnectorDir "+")))) 105 | 106 | (Section 107 | (Concept "purred") 108 | (ConnectorSeq 109 | (Connector (Concept "WV") (ConnectorDir "-")) 110 | (Connector (Concept "S") (ConnectorDir "-")) 111 | (Connector (Concept "Xf") (ConnectorDir "+")))) 112 | 113 | )) ; End of the for-each loop. 114 | -------------------------------------------------------------------------------- /examples/dict-tree.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-tree.scm 3 | ; 4 | ; This demo shows how to construct a lexis that will generate (acyclic) 5 | ; trees with typed edges. The example uses a simple English-language 6 | ; grammar to generate four "sentences". The all have the form: 7 | ; 8 | ; +----O-----+ 9 | ; +----W---+---S--+ +--D--+ 10 | ; | | | | | 11 | ; LEFT-WALL John saw a cat 12 | ; 13 | ; The other three substitute "Mary" for "John" and "dog" for "cat". 14 | ; All four possible sentences are generated. 15 | ; 16 | ; The dictionary defines nine "jigsaw-puzzle pieces" in it. These 17 | ; assemble into the above trees. It should be obvious by inspection how 18 | ; these should be assembled. ... and, if its not ... then: 19 | ; 20 | ; The connector ID's must match, and the conector directions must be 21 | ; polar opposites. The edges connecting the vertexes are undirected 22 | ; edges; the ConnectorDir polarities instead indicate "to the left" and 23 | ; "to the right". So, for example, "cat" has two "-" ConnectorDir's 24 | ; so that it will directionally link to the left for both words. This 25 | ; differs from what one might expect in a dependency grammar, where 26 | ; the O (object) link would indicate a head-dependent arrow from 27 | ; verb to object, and the D (determiner) link would indicate a 28 | ; head-dependent arrow from noun to the determiner. Although the above 29 | ; describes the dependency relations as arrows, in this particular 30 | ; example, the actual edges are undirected. 31 | ; 32 | ; Each "word" is a point in the section. Each section indicates the 33 | ; kinds of connectors it can attach to. 34 | ; 35 | (use-modules (srfi srfi-1)) 36 | (use-modules (opencog)) 37 | 38 | ; Each word in the dictionary is added by means of a MemberLink. 39 | ; The name of the dictionary here is `(Concept "dict-tree")`. 40 | (define dict-tree (Concept "dict-tree")) 41 | 42 | ; To keep the example simple and readable, the membership is done 43 | ; in a giant-sized for-loop. 44 | (for-each 45 | (lambda (sect) (Member sect dict-tree)) 46 | (list 47 | 48 | ;; All sentences start with the "left wall" -- this is a 49 | ;; "word" at the start of the sentence, which serves as the 50 | ;; root to which subsequent words will be attached. 51 | (Section 52 | (Concept "LEFT-WALL") 53 | (ConnectorSeq 54 | (Connector (Concept "W") (ConnectorDir "+")))) 55 | 56 | (Section 57 | (Concept "John") 58 | (ConnectorSeq 59 | (Connector (Concept "W") (ConnectorDir "-")) 60 | (Connector (Concept "S") (ConnectorDir "+")))) 61 | 62 | (Section 63 | (Concept "Mary") 64 | (ConnectorSeq 65 | (Connector (Concept "W") (ConnectorDir "-")) 66 | (Connector (Concept "S") (ConnectorDir "+")))) 67 | 68 | (Section 69 | (Concept "saw") 70 | (ConnectorSeq 71 | (Connector (Concept "S") (ConnectorDir "-")) 72 | (Connector (Concept "O") (ConnectorDir "+")))) 73 | 74 | (Section 75 | (Concept "a") 76 | (ConnectorSeq 77 | (Connector (Concept "D") (ConnectorDir "+")))) 78 | 79 | (Section 80 | (Concept "cat") 81 | (ConnectorSeq 82 | (Connector (Concept "D") (ConnectorDir "-")) 83 | (Connector (Concept "O") (ConnectorDir "-")))) 84 | 85 | (Section 86 | (Concept "dog") 87 | (ConnectorSeq 88 | (Connector (Concept "D") (ConnectorDir "-")) 89 | (Connector (Concept "O") (ConnectorDir "-")))) 90 | 91 | )) ; End of the for-each loop. 92 | -------------------------------------------------------------------------------- /examples/dict-triquad.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-triquad.scm 3 | ; 4 | ; Triple-Loop test: dictionary that allows four sentences, with three 5 | ; cycles, the third one sharing edges. 6 | ; 7 | ; +---------------------Xp--------------------+ 8 | ; +----------WV------+--------CV-------+ | 9 | ; +---W---+--S-+--I--+--Ce-+--S--+--I--+--Xc--+ 10 | ; | | | | | | | | 11 | ; LEFT-WALL Mary might think John could fall . 12 | ; 13 | ; The other sentences swap "might" for "could". Two cycles are both 14 | ; quads; one with edges (W, S, I, WV), vertexes (LEFT-WALL, Mary, 15 | ; might, think). The other has edges (Ce, S, I, CV) and vertexes (think, 16 | ; John, could, fall). The two quads share a single common vertex: "think". 17 | ; 18 | ; The third cycle shares edges with the quads: it is (WV, CV, Xc, Xp) 19 | ; with vertexes (LEFT-WALL, think, fall, .) 20 | ; 21 | (use-modules (srfi srfi-1)) 22 | (use-modules (opencog)) 23 | 24 | (define dict-triquad (Concept "dict-triquad")) 25 | 26 | (for-each 27 | (lambda (sect) (Member sect dict-triquad)) 28 | (list 29 | 30 | (Section 31 | (Concept "LEFT-WALL") 32 | (ConnectorSeq 33 | (Connector (Concept "Xp") (ConnectorDir "+")) 34 | (Connector (Concept "WV") (ConnectorDir "+")) 35 | (Connector (Concept "W") (ConnectorDir "+")))) 36 | 37 | (Section 38 | (Concept "Mary") 39 | (ConnectorSeq 40 | (Connector (Concept "W") (ConnectorDir "-")) 41 | (Connector (Concept "S") (ConnectorDir "+")))) 42 | 43 | (Section 44 | (Concept "John") 45 | (ConnectorSeq 46 | (Connector (Concept "Ce") (ConnectorDir "-")) 47 | (Connector (Concept "S") (ConnectorDir "+")))) 48 | 49 | (Section 50 | (Concept "think") 51 | (ConnectorSeq 52 | (Connector (Concept "I") (ConnectorDir "-")) 53 | (Connector (Concept "WV") (ConnectorDir "-")) 54 | (Connector (Concept "CV") (ConnectorDir "+")) 55 | (Connector (Concept "Ce") (ConnectorDir "+")))) 56 | 57 | (Section 58 | (Concept "fall") 59 | (ConnectorSeq 60 | (Connector (Concept "I") (ConnectorDir "-")) 61 | (Connector (Concept "CV") (ConnectorDir "-")) 62 | (Connector (Concept "Xc") (ConnectorDir "+")))) 63 | 64 | (Section 65 | (Concept "might") 66 | (ConnectorSeq 67 | (Connector (Concept "S") (ConnectorDir "-")) 68 | (Connector (Concept "I") (ConnectorDir "+")))) 69 | 70 | (Section 71 | (Concept "could") 72 | (ConnectorSeq 73 | (Connector (Concept "S") (ConnectorDir "-")) 74 | (Connector (Concept "I") (ConnectorDir "+")))) 75 | 76 | (Section 77 | (Concept ".") 78 | (ConnectorSeq 79 | (Connector (Concept "Xp") (ConnectorDir "-")) 80 | (Connector (Concept "Xc") (ConnectorDir "-")))) 81 | 82 | )) ; End of the for-each loop. 83 | -------------------------------------------------------------------------------- /examples/export-to-gml.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; export-to-gml.scm 3 | ; 4 | ; Create some simple networks, and export them to GML - Graph Modelling 5 | ; Language, see https://en.wikipedia.org/wiki/Graph_Modelling_Language 6 | ; This allows the networks to be visualized with CytoScape or Gephi. 7 | ; 8 | ; The PNG image `n20.gml.png` is an example random network generated 9 | ; with the code below, and visualized with CytoScape. 10 | ; 11 | (use-modules (opencog) (opencog generate)) 12 | (use-modules (ice-9 textual-ports)) 13 | 14 | ; Load the dataset we plan to demo. 15 | (load "basic-network.scm") 16 | 17 | ; The parameters that will control the search. 18 | (load "parameters.scm") 19 | 20 | ;; Generate a collection of random networks, using the lexis, the 21 | ;; connectable end-points and the probability weightings previously 22 | ;; defined. The result of running this will generate multiple graphs. 23 | ;; 24 | ;; Aggregation will be started on "burr-3" -- this is the seed, or 25 | ;; root from which the rest of the graph will be constructed. 26 | ;; 27 | ;; XXX Caution, this is not the final API -- subjet to change. 28 | (define graph-set 29 | (cog-random-aggregate polarity-set lexis weights basic-net-params 30 | (Concept "burr-3"))) 31 | 32 | ;; Print the number of graphs that were generated 33 | (cog-arity graph-set) 34 | 35 | ;; Convert the Atomese representation of these networks into 36 | ;; a GML string. 37 | (define gml-string (export-to-gml graph-set)) 38 | 39 | ;; It appears that CytoScape does not really like files with multiple 40 | ;; graphs in them. Thus, we'll pick just one graph, and export that. 41 | (define just-one (Set (gar graph-set))) 42 | (define just-one-gml (export-to-gml just-one)) 43 | 44 | ;; Print the string to a file. 45 | (let ((outport (open-file "/tmp/basic-random-net.gml" "w"))) 46 | (put-string outport just-one-gml) 47 | (close outport)) 48 | 49 | ;; Visualize the resulting network. 50 | ;; -- Install and start cytoscape. 51 | ;; -- Select "File -> Import -> Network from file ..." 52 | ;; -- Select "/tmp/basic-random-net.gml" 53 | ;; -- Hit the "Apply Perfered Layout" button (the rotating arrows button) 54 | ;; Ta-dahhhh! That's it! 55 | 56 | ;; --------------------------------------------------------------- 57 | ;; OK, just for the heck of it, play around with the close-fraction 58 | ;; parameter. Then rerun the the graph generation. Setting it to zero 59 | ;; should result in a tree, containing no loops. 60 | ;; 61 | (State (Member close-fraction basic-net-params) (Number 0)) 62 | 63 | ;; May as well ask for just one tree, from the outset. 64 | (State (Member max-solutions basic-net-params) (Number 1)) 65 | ;; 66 | ;; Go! Just cut-n-paste from above, and do it again... 67 | ;; Whoops! It hangs! Infinite loop! Yowww! What happened? 68 | ;; 69 | ;; So ... there's a trick here. Its important to understand. The node 70 | ;; weightings that were set up in the `basic-network.scm` file will 71 | ;; generate trees that are infinitely large. At each iteration step, 72 | ;; there are more open, unconnected connectors created, than there are 73 | ;; connectors that are closed, so with each iteration, the tree only 74 | ;; gets larger. Infinite. With the current weightings, the tree gains 75 | ;; 22/49 = 0.449... new unconnected connectors per iteration. To obtain 76 | ;; this number, note that 1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 = 49/20 and 77 | ;; that the number of new connectors created per iteration is 78 | ;; 1/3 + 2/4 + 3/5 + 4/6 = (21/10) / (49/20) = 6/7 while the number 79 | ;; closed is 20/49, so the expected number of new nodes per iteration 80 | ;; is 6/7 - 20/49 = 22/49 = 0.449... So, an infinite tree is generated. 81 | ;; 82 | ;; That's yucky for an actual example. Instead, adjust the weights, 83 | ;; before getting trapped by an inf loop. 84 | ;; 85 | (cog-set-value! b1 weights (FloatValue 1)) 86 | (cog-set-value! b2 weights (FloatValue (/ 1.0 4))) 87 | (cog-set-value! b3 weights (FloatValue (/ 1.0 9))) 88 | (cog-set-value! b4 weights (FloatValue (/ 1.0 16))) 89 | (cog-set-value! b5 weights (FloatValue (/ 1.0 25))) 90 | (cog-set-value! b6 weights (FloatValue (/ 1.0 36))) 91 | 92 | ;; Repeating the arithmetic above, the values are: 93 | ;; norm = 1 + 1/4 + 1/9 + 1/16 + 1/25 + 1/36 = 5369/3600 94 | ;; opened = (1/9 + 2/16 + 3/25 + 4/36) / norm = 0.3133... 95 | ;; closed = 1/norm = 3600/5369 = 0.6705... 96 | ;; net = opened - closed = -0.3572... 97 | ;; So, if we start with the arity-3 root, all three connectors will 98 | ;; close off after about 3 steps, and the trees will be quite small 99 | ;; Better to start with the arity-6 root; it provides a bigger tree. 100 | 101 | (define one-tree 102 | (cog-random-aggregate polarity-set lexis weights basic-net-params 103 | (Concept "burr-6"))) 104 | 105 | ;; Create the GML string 106 | (define tree-gml-string (export-to-gml one-tree)) 107 | 108 | ;; Print the string to a file. 109 | (let ((outport (open-file "/tmp/basic-random-tree.gml" "w"))) 110 | (put-string outport tree-gml-string) 111 | (close outport)) 112 | 113 | ;; Hush printing when loading this file. 114 | *unspecified* 115 | -------------------------------------------------------------------------------- /examples/grammar.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; grammar.scm 3 | ; 4 | ; This demo shows how some small, simple English-language grammars can 5 | ; be specified, and used to generate simple English-language sentences. 6 | ; The grammars are all very small, and can therefore be exhaustively 7 | ; explored -- that is, all possible sentences are generated. Thus, this 8 | ; shows the deterministic explorer at work. 9 | ; 10 | ; As before, the demo concludes with the resulting graphs exported to 11 | ; GML; the graphs can be with CytoScape or Gephi. 12 | ; 13 | (use-modules (opencog) (opencog generate)) 14 | (use-modules (ice-9 textual-ports)) 15 | 16 | ; -------- 17 | ; There are several different grammars that will be used in this 18 | ; example. Load all of them. Each one defines a diffferent dictionary. 19 | (load "dict-tree.scm") 20 | (load "dict-loop.scm") 21 | (load "dict-triquad.scm") 22 | (load "dict-mixed.scm") 23 | 24 | ; -------- 25 | ;; All sentences start with the "left wall" -- this is a "word" at the 26 | ;; start of the sentence, which serves as the root to which subsequent 27 | ;; words will be attached. 28 | (define left-wall (Concept "LEFT-WALL")) 29 | 30 | ; Define the connectable directions (poles). In this case, there 31 | ; are two direction types: "+" and "-" denoting "to the right" and 32 | ; "to the left". The connector mating rules are that "+" can connect 33 | ; only to "-", and vice-versa. The resulting edges are NOT directed 34 | ; edges -- they are still undirected edges; the poles are only used 35 | ; to indicate word-order in the generated sentence. This convention of 36 | ; plus and minus for word-order is in keeping with the representation 37 | ; used in the Link Grammar parser. The notation used here should look 38 | ; very familiar, when compared to the notaton there. This is not 39 | ; accidental. 40 | ; 41 | ; All of the example dictionaries use this same set of direction rules. 42 | ; Its very simple: only "+" and "-" connections are allowed. 43 | ; 44 | (define dir-set (Concept "left-right connectors")) 45 | (Member (Set (ConnectorDir "+") (ConnectorDir "-")) dir-set) 46 | 47 | ; None of the examples require any parameters to control generation. 48 | ; The defaults are used. Still, a set needs to be provided - it will be 49 | ; the empty set. 50 | (define no-params (Concept "this is an empty set of parameters")) 51 | 52 | ; -------- 53 | ;; Convenience function to convert a graph to GML, and write it to 54 | ;; a file. It appears that CytoScape does not like files with multiple 55 | ;; graphs in them. Thus, we'll pick just one graph, and export that. 56 | ;; 57 | ;; The resulting graph can be visualized as follows: 58 | ;; -- Install and start cytoscape. 59 | ;; -- Select "File -> Import -> Network from file ..." 60 | ;; -- Choose the appropriate file. 61 | ;; -- Hit the "Apply Perfered Layout" button (the rotating arrows button) 62 | (define (export-to-file GRAPH-SET FILENAME) 63 | 64 | (define just-one (Set (gar GRAPH-SET))) 65 | (define just-one-gml (export-to-gml just-one)) 66 | 67 | ;; Print the string to a file. 68 | (let ((outport (open-file FILENAME "w"))) 69 | (put-string outport just-one-gml) 70 | (close outport)) 71 | 72 | *unspecified*) 73 | 74 | ; -------- 75 | ;; First example: use the "tree" dictionary to generate four sentences, 76 | ;; each of the sentences having a dependency relationship that is a 77 | ;; strict tree, having no loops. No generation paramters are used, 78 | ;; that is, the defaults are accepted. 79 | ;; 80 | ;; XXX Caution, this is not the final API -- subjet to change. 81 | (define tree-set 82 | (cog-simple-aggregate dir-set dict-tree no-params left-wall)) 83 | 84 | ;; Print the number of graphs that were generated. 85 | (cog-arity tree-set) 86 | 87 | ; Write the corpus of sentences out to a file (in GML format). 88 | (export-to-file tree-set "/tmp/corpus-tree.gml") 89 | 90 | ; -------- 91 | ; Do it again, for the other dictionaries. 92 | (export-to-file 93 | (cog-simple-aggregate dir-set dict-loop no-params left-wall) 94 | "/tmp/corpus-loop.gml") 95 | 96 | (export-to-file 97 | (cog-simple-aggregate dir-set dict-triquad no-params left-wall) 98 | "/tmp/corpus-triquad.gml") 99 | 100 | (export-to-file 101 | (cog-simple-aggregate dir-set dict-mixed no-params left-wall) 102 | "/tmp/corpus-mixed.gml") 103 | 104 | ;; Hush printing when loading this file. 105 | *unspecified* 106 | -------------------------------------------------------------------------------- /examples/n20.gml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencog/generate/8f7bc7cc9303ca9ca6971ad520f50b8d5356d39f/examples/n20.gml.png -------------------------------------------------------------------------------- /examples/parameters.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; parameters.scm 3 | ; 4 | ; Parameters that control network generation. 5 | ; Each parameter is identified by a PredicateNode with a well-known, 6 | ; fixed name. Different sets of parameters are grouped together with 7 | ; a MemberLink that names a set they belong to. That set is then passed 8 | ; to the solver on each invocation. 9 | 10 | ; Maximum number of solutions to accept. Search is halted after this 11 | ; number of networks are generated. Integer, zero or more, defaults 12 | ; to 100. 13 | (define max-solutions (Predicate "*-max-solutions-*")) 14 | 15 | ; Fraction (zero to one) of attempts that should be made to create 16 | ; a closed loop (cycle) by joining together two currently-open 17 | ; connectors. If set to 1.0, every attempt to make a connection will 18 | ; begin with an attempt to link a pair of currently-unconnected (open) 19 | ; connectors. This may not be possible, so this fraction does not 20 | ; strictly control the number of loops in a graph, nor their size. 21 | ; If set to 0.0, then loops will never form, and the resulting graph 22 | ; will always be a tree. Currently applies only to the random network 23 | ; generator. 24 | (define close-fraction (Predicate "*-close-fraction-*")) 25 | 26 | ; Maximum number of odometer steps to take, when searching for a 27 | ; solution. It's not hard to specify grammars with weighting that lead 28 | ; to infinite trees, (i.e. are infinitely recursive) and so it's 29 | ; important to be able to halt exploration in such cases. 30 | ; 31 | ; CPU's of 2016 vintage run approx 1.2K steps/second (obviously, 32 | ; this will be grammar dependent.) 33 | (define max-steps (Predicate "*-max-steps-*")) 34 | 35 | ; Maximum depth of exploration. Starting from the nucleation points, 36 | ; sections are chained on, forming a branching tree of chains. (They 37 | ; may also interlink, thus forming a network rathr than a linear 38 | ; chain or tre of chains). Ignoring the crosslinks, the max depth of 39 | ; exploration is the maximum length of the chain that will be explored. 40 | (define max-depth (Predicate "*-max-depth-*")) 41 | 42 | ; Maximum number of points in the network. Networks having more than 43 | ; this number of points in them will not be explored. 44 | (define max-network-size (Predicate "*-max-network-size-*")) 45 | 46 | ; When the network is generated, many individual instances of the 47 | ; network points will be generated. To get easy access to these, they 48 | ; can be tied at a well-known location -- specifically, they will 49 | ; be tied with a MemberLink to the specified anchor point. 50 | (define point-set-anchor (Predicate "*-point-set-anchor-*")) 51 | 52 | ; -------------------------------------------------------------- 53 | ; The parameters that are used for the `basic-network.scm` demo. 54 | (define basic-net-params (Concept "Basic network demo")) 55 | 56 | (State (Member max-solutions basic-net-params) (Number 10)) 57 | (State (Member close-fraction basic-net-params) (Number 0.5)) 58 | (State (Member max-steps basic-net-params) (Number 123123)) 59 | (State (Member point-set-anchor basic-net-params) (Anchor "Basic-Net Points")) 60 | 61 | ; Hush the output during load. 62 | *unspecified* 63 | -------------------------------------------------------------------------------- /examples/trisexual.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; trisexual.scm 3 | ; 4 | ; This demo provides a very short discussion of how connector directions 5 | ; can be specified. Three examples are given: mono-sexual, heterosexual 6 | ; and tri-sexual. The word "sexual" is used because of the obvious 7 | ; (hetero) "sexuality" of real-world cardboard jigsaw puzzle connectors. 8 | ; 9 | ; Other parts of the code call this "polarity", and so the three examples 10 | ; could be called mono-polar, bipolar and tri-polar. The concept of "mating" 11 | ; applies for either vocabulary. "Joining" and "linking" are synonymous 12 | ; ideas. Different blocks of code use these words interchangeably. 13 | ; 14 | (use-modules (opencog)) 15 | ; 16 | ; -------------------------------------- 17 | ; The mono-sexual example occurs in the `basic-network.scm` example. 18 | ; Please review that example. The relevant section is cut-n-pasted 19 | ; below: 20 | ; 21 | ; Define the connectable directions (poles). In this case, the 22 | ; connectors are all the same, and the direction (or "pole") 23 | ; connects to itself (rather than having opposite polarity). 24 | ; Furthermore, the connections are symmetric (creating undirected 25 | ; edges.) Thus, the pole-pair set is very simple. 26 | (define polarity-set (Concept "any to any")) 27 | (Member (Set (ConnectorDir "*") (ConnectorDir "*")) polarity-set) 28 | 29 | ; -------------------------------------- 30 | ; The hetero-sexual example occurs in the `dict-*.scm` examples, and is 31 | ; given in the file `grammar.scm` near the middle. The relevant section 32 | ; is cut-n-pasted below: 33 | ; 34 | ; Define the connectable directions (poles). In this case, there 35 | ; are two direction types: "+" and "-" denoting "to the right" and 36 | ; "to the left". The connector mating rules are that "+" can connect 37 | ; only to "-", and vice-versa. The resulting edges are NOT directed 38 | ; edges -- they are still undirected edges; the poles are only used 39 | ; to indicate word-order in the generated sentence. This convention of 40 | ; plus and minus for word-order is in keeping with the representation 41 | ; used in the Link Grammar parser. The notation used here should look 42 | ; very familiar, when compared to the notaton there. This is not 43 | ; accidental. 44 | ; 45 | ; All of the example dictionaries use this same set of direction rules. 46 | ; Its very simple: only "+" and "-" connections are allowed. 47 | ; 48 | (define dir-set (Concept "left-right connectors")) 49 | (Member (Set (ConnectorDir "+") (ConnectorDir "-")) dir-set) 50 | 51 | ; Please keep in mind that a `Set` in Atomese is an unordered set. Thus, 52 | ; the expression `(Set (ConnectorDir "+") (ConnectorDir "-"))` and also 53 | ; `(Set (ConnectorDir "-") (ConnectorDir "+"))` are exactly the same set. 54 | ; There is no need to specify both orderings. 55 | ; 56 | ; The above just says: "opposites attract". 57 | 58 | ; -------------------------------------- 59 | ; The tri-sexual example follows below. It is not currently used in any 60 | ; demos; the reader is encouraged to construct one. 61 | ; 62 | ; The example here is that there are three sexes, `a`, `b` and `c`. The 63 | ; mating rules are such that sex `a` can mate with `b`, but nothing else, 64 | ; so `a` cannot mate to other `a`'s nor to `c`. The sex `c` can also mate 65 | ; with `b`, but not to itself. Finally, `b` cannot mate with itself. This 66 | ; idea is encoded in the following rules: 67 | 68 | (define sexuality-set (Concept "abc example")) 69 | (Member (Set (ConnectorDir "a") (ConnectorDir "b")) sexuality-set) 70 | (Member (Set (ConnectorDir "c") (ConnectorDir "b")) sexuality-set) 71 | 72 | ; -------------------------------------- 73 | ; All three examples above are quite simple; fairly general mating rules 74 | ; can be encoded following the above pattern. 75 | ; 76 | ; That's all, folks! 77 | -------------------------------------------------------------------------------- /opencog/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | ADD_SUBDIRECTORY (generate) 3 | ADD_SUBDIRECTORY (guile) 4 | ADD_SUBDIRECTORY (scm) 5 | -------------------------------------------------------------------------------- /opencog/generate/Aggregate.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Aggregate.cc 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License v3 as 6 | * published by the Free Software Foundation and including the exceptions 7 | * at http://opencog.org/wiki/Licenses 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program; if not, write to: 16 | * Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "Aggregate.h" 29 | #include "GenerateCallback.h" 30 | 31 | using namespace opencog; 32 | 33 | // Strategy: starting from a single nucleation center (e.g. the left 34 | // wall), recursively aggregate connections until there are no 35 | // unconnected connectors. 36 | 37 | Aggregate::Aggregate(AtomSpace* as) 38 | : _as(as) 39 | { 40 | _cb = nullptr; 41 | _scratch = nullptr; 42 | } 43 | 44 | Aggregate::~Aggregate() 45 | { 46 | } 47 | 48 | /// Yuck. Should not allow re-use... XXX FIXME!? 49 | void Aggregate::clear(void) 50 | { 51 | while (not _frame_stack.empty()) _frame_stack.pop(); 52 | while (not _odo_sections.empty()) _odo_sections.pop(); 53 | while (not _odo_stack.empty()) _odo_stack.pop(); 54 | 55 | _frame.clear(); 56 | _odo.clear(); 57 | 58 | _scratch = createAtomSpace(_as); 59 | _cb->clear(_scratch.get()); 60 | } 61 | 62 | /// The nuclei are the nucleation points: points that must 63 | /// appear in sections, some section of which must be linkable. 64 | /// 65 | void Aggregate::aggregate(const HandleSet& nuclei, 66 | GenerateCallback& cb) 67 | { 68 | _cb = &cb; 69 | clear(); 70 | 71 | // Set it up and go. 72 | _cb->root_set(nuclei); 73 | while (true) 74 | { 75 | HandleSet starters = _cb->next_root(); 76 | if (starters.size() == 0) break; 77 | 78 | push_frame(); 79 | for (const Handle& sect : starters) 80 | { 81 | _frame._open_sections.insert(sect); 82 | } 83 | recurse(); 84 | pop_frame(); 85 | } 86 | } 87 | 88 | /// Breadth-first recursion. 89 | /// See the README.md for an explanation. 90 | void Aggregate::recurse(void) 91 | { 92 | // Nothing to do. 93 | if (0 == _frame._open_sections.size()) return; 94 | 95 | // Halt recursion, if need be. 96 | if (not _cb->step(_frame)) 97 | { 98 | logger().fine("Recursion halted at frame depth=%lu odo level=%lu", 99 | _frame_stack.size(), _odo_stack.size()); 100 | return; 101 | } 102 | 103 | logger().fine("Enter recurse"); 104 | 105 | // Initialize a brand-new odometer at the next recursion level. 106 | push_odo(); 107 | bool more = init_odometer(); 108 | if (not more) 109 | { 110 | pop_odo(); 111 | return; 112 | } 113 | 114 | // Take the first step. 115 | _odo._step = 0; 116 | more = do_step(); 117 | _odo._step = _odo._size-1; 118 | 119 | logger().fine("Recurse: After first step, have-more=%d", more); 120 | while (true) 121 | { 122 | // Odometer is exhausted; we are done. 123 | if (not more) 124 | { 125 | pop_odo(); 126 | return; 127 | } 128 | 129 | // If we are here, we have a valid odo state. Explore it. 130 | recurse(); 131 | logger().fine("Returned from recurse"); 132 | 133 | // Exploration is done, step to the next state. 134 | more = step_odometer(); 135 | logger().fine("Recurse: After next step, have-more=%d", more); 136 | } 137 | 138 | return; // *not-reached* 139 | } 140 | 141 | /// Initialize the odometer state. This creates an ordered list of 142 | /// all as-yet unconnected connectors in the open state. 143 | /// Returns false if initialization failed, i.e. if the current 144 | /// open-connector state is not extendable. 145 | bool Aggregate::init_odometer(void) 146 | { 147 | // Should be empty already, but just in case... 148 | _odo._from_index.clear(); 149 | _odo._to_connectors.clear(); 150 | _odo._sections.clear(); 151 | 152 | // Loop over all open connectors 153 | for (const Handle& sect: _frame._open_sections) 154 | { 155 | Handle disj = sect->getOutgoingAtom(1); 156 | const HandleSeq& conseq = disj->getOutgoingSet(); 157 | for (size_t idx = 0; idx < conseq.size(); idx++) 158 | { 159 | const Handle& from_con = conseq[idx]; 160 | 161 | // There may be fully connected links in the sequence. 162 | // Ignore those. We want unconnected connectors only. 163 | if (CONNECTOR != from_con->get_type()) continue; 164 | 165 | // Get a list of connectors that can be connected to. 166 | // If none, then this connector can never be closed. 167 | HandleSeq to_cons = _cb->joints(from_con); 168 | if (0 == to_cons.size()) return false; 169 | 170 | for (const Handle& to_con: to_cons) 171 | { 172 | _odo._sections.push_back(sect); 173 | _odo._from_index.push_back(idx); 174 | _odo._to_connectors.push_back(to_con); 175 | } 176 | } 177 | } 178 | 179 | _odo._size = _odo._to_connectors.size(); 180 | if (0 == _odo._size) return false; 181 | _odo._step = 0; 182 | 183 | logger().fine("Initialized odometer of length %lu", _odo._size); 184 | _odo.print_odometer(_frame); 185 | 186 | return true; 187 | } 188 | 189 | bool Aggregate::do_step(void) 190 | { 191 | // Erase the last connection that was made. 192 | if (_frame._wheel == _odo._step and 193 | _frame._nodo == _odo_stack.size()) pop_frame(); 194 | 195 | logger().fine("Step odometer wheel %lu of %lu at depth %lu", 196 | _odo._step, _odo._size, _odo_stack.size()); 197 | _odo.print_odometer(_frame); 198 | 199 | // Draw a new piece via callback, and attach it. 200 | bool did_step = false; 201 | for (size_t ic = _odo._step; ic < _odo._size; ic++) 202 | { 203 | Handle fm_sect = _odo._sections[ic]; 204 | size_t offset = _odo._from_index[ic]; 205 | const Handle& conseq = fm_sect->getOutgoingAtom(1); 206 | const Handle& fm_con = conseq->getOutgoingAtom(offset); 207 | const Handle& to_con = _odo._to_connectors[ic]; 208 | 209 | // Is there an open connector at this location? 210 | if (fm_con->get_type() != CONNECTOR) 211 | { 212 | logger().fine("Wheel-con not open: %lu of %lu at depth %lu", 213 | ic, _odo._size, _odo_stack.size()); 214 | _odo.print_wheel(_frame, ic); 215 | 216 | if (ic == _odo._step) 217 | { 218 | // If we are here, then this wheel has "effectively" 219 | // rolled over. We cannot continue to the remaining 220 | // wheels. 221 | _odo._step = ic - 1; 222 | return false; 223 | } 224 | continue; 225 | } 226 | 227 | // ---------------------------- 228 | // If we made it to here, then the to-connector is still free. 229 | // Draw a new section to connect to it. 230 | Handle to_sect = _cb->select(_frame, fm_sect, offset, to_con); 231 | 232 | if (nullptr == to_sect) 233 | { 234 | logger().fine("Rolled over wheel %lu of %lu at depth %lu", 235 | ic, _odo._size, _odo_stack.size()); 236 | _odo.print_wheel(_frame, ic); 237 | 238 | // If we are here, then this wheel has rolled over. 239 | // That means that it's time for the previous wheel 240 | // to take a step. Mark that wheel. 241 | _odo._step = ic - 1; 242 | return false; 243 | } 244 | 245 | did_step = true; 246 | push_frame(); 247 | _frame._wheel = ic; 248 | 249 | // Connect it up, and get the newly-connected section. 250 | HandlePair hpr = connect_section(fm_sect, offset, to_sect, to_con); 251 | 252 | // Replace the from-section with the now-connected section. 253 | for (size_t in = 0; in < _odo._size; in++) 254 | { 255 | if (*_odo._sections[in] == *fm_sect) _odo._sections[in] = hpr.first; 256 | if (*_odo._sections[in] == *to_sect) _odo._sections[in] = hpr.second; 257 | } 258 | } 259 | 260 | if (not did_step) 261 | { 262 | logger().fine("Did not step wheel: %lu of %lu at depth %lu", 263 | _odo._step, _odo._size, _odo_stack.size()); 264 | if (0 < _odo._step) _odo._step --; 265 | return false; 266 | } 267 | 268 | // Next time, we will turn just the last wheel. 269 | _odo._step = _odo._size - 1; 270 | 271 | logger().fine("Stepped odo: open-points=%lu open-sect=%lu lkg=%lu", 272 | _frame._open_points.size(), _frame._open_sections.size(), 273 | _frame._linkage.size()); 274 | 275 | // If we found a solution, let the callback accumulate it. 276 | if (0 == _frame._open_sections.size()) 277 | _cb->solution(_frame); 278 | 279 | return true; 280 | } 281 | 282 | bool Aggregate::step_odometer(void) 283 | { 284 | if (not _cb->step(_frame)) 285 | { 286 | logger().fine("Odometer halted at frame depth=%lu odo stack=%lu", 287 | _frame_stack.size(), _odo_stack.size()); 288 | return false; 289 | } 290 | 291 | // Total rollover 292 | if (_odo._size < _odo._step) return false; 293 | 294 | // Take a step. 295 | bool did_step = do_step(); 296 | while (not did_step) 297 | { 298 | // If the stepper rolled over to minus-one, then we're done. 299 | if (SIZE_MAX == _odo._step) 300 | { 301 | logger().fine("Exhaused the odometer at depth %lu", _odo_stack.size()); 302 | return false; 303 | } 304 | logger().fine("Failed to step, try wheel %lu", _odo._step); 305 | 306 | did_step = do_step(); 307 | } 308 | 309 | return did_step; 310 | } 311 | 312 | #define al _as->add_link 313 | #define an _as->add_node 314 | 315 | /// Connect a pair of sections together, by connecting two matched 316 | /// connectors. Two new sections will be created, with the connector 317 | /// in each section replaced by the link. 318 | /// 319 | /// Return the pair of newly-connected sections. 320 | HandlePair Aggregate::connect_section(const Handle& fm_sect, 321 | size_t offset, 322 | const Handle& to_sect, 323 | const Handle& to_con) 324 | { 325 | // logger().fine("Connect %s\nto %s", 326 | // fm_sect->to_string().c_str(), to_sect->to_string().c_str()); 327 | logger().fine("Connect fm-offset=%lu:", offset); 328 | OdoFrame::print_section(fm_sect); 329 | OdoFrame::print_section(to_sect); 330 | 331 | const Handle& fm_point = fm_sect->getOutgoingAtom(0); 332 | const Handle& to_point = to_sect->getOutgoingAtom(0); 333 | 334 | const Handle& conseq = fm_sect->getOutgoingAtom(1); 335 | const Handle& fm_con = conseq->getOutgoingAtom(offset); 336 | 337 | Handle link = _cb->make_link(fm_con, to_con, fm_point, to_point); 338 | 339 | // Oh dear, we need the index of the to_con in the to_sect 340 | // Perhaps the callback should provide this info? 341 | const Handle& disj = to_sect->getOutgoingAtom(1); 342 | const HandleSeq& tseq = disj->getOutgoingSet(); 343 | size_t tidx = -1; 344 | for (size_t i=0; igetOutgoingAtom(0); 370 | const Handle& disj = sect->getOutgoingAtom(1); 371 | HandleSeq oset = disj->getOutgoingSet(); 372 | 373 | // Replace the connector with the link. 374 | oset[index] = link; 375 | 376 | // Any remaining unconnected connectors? 377 | bool is_open = false; 378 | for (const Handle& fc : oset) 379 | { 380 | if (CONNECTOR == fc->get_type()) { is_open = true; break; } 381 | } 382 | 383 | // Create the now-connected linkage. Create it in the scratch 384 | // space, so as not to pollute the main space. 385 | Handle linking = 386 | _scratch->add_link(SECTION, point, 387 | _scratch->add_link(CONNECTOR_SEQ, std::move(oset))); 388 | 389 | // Remove the section from the open set. 390 | _frame._open_sections.erase(sect); 391 | 392 | // If the connected section has remaining unconnected connectors, 393 | // then add it to the unfinished set. Else we are done with it. 394 | if (is_open) 395 | { 396 | _frame._open_sections.insert(linking); 397 | _frame._open_points.insert(point); 398 | logger().fine("---- Open point %s", point->to_string().c_str()); 399 | } 400 | else 401 | { 402 | _frame._linkage.insert(linking); 403 | _frame._open_points.erase(point); 404 | logger().fine("---- Close point %s", point->to_string().c_str()); 405 | } 406 | 407 | return linking; 408 | } 409 | 410 | void Aggregate::push_frame(void) 411 | { 412 | _cb->push_frame(_frame); 413 | _frame_stack.push(_frame); 414 | _odo_sections.push(_odo._sections); 415 | _frame._nodo = _odo_stack.size(); 416 | _frame._wheel = -1; 417 | 418 | logger().fine("---- Push: Frame stack depth now %lu npts=%lu open=%lu lkg=%lu", 419 | _frame_stack.size(), _frame._open_points.size(), 420 | _frame._open_sections.size(), _frame._linkage.size()); 421 | } 422 | 423 | void Aggregate::pop_frame(void) 424 | { 425 | _cb->pop_frame(_frame); 426 | _frame = _frame_stack.top(); _frame_stack.pop(); 427 | _odo._sections = _odo_sections.top(); _odo_sections.pop(); 428 | 429 | logger().fine("---- Pop: Frame stack depth now %lu npts=%lu open=%lu lkg=%lu", 430 | _frame_stack.size(), _frame._open_points.size(), 431 | _frame._open_sections.size(), _frame._linkage.size()); 432 | _frame.print(); 433 | } 434 | 435 | /// Push the odometer state. 436 | void Aggregate::push_odo(void) 437 | { 438 | _cb->push_odometer(_odo); 439 | _odo_stack.push(_odo); 440 | 441 | logger().fine("==== Push: Odo stack depth now %lu", _odo_stack.size()); 442 | 443 | _odo._frame_depth = _frame_stack.size(); 444 | } 445 | 446 | void Aggregate::pop_odo(void) 447 | { 448 | // Realign the frame stack to where we started. 449 | while (_odo._frame_depth < _frame_stack.size()) pop_frame(); 450 | 451 | _cb->pop_odometer(_odo); 452 | _odo = _odo_stack.top(); _odo_stack.pop(); 453 | 454 | logger().fine("==== Pop: Odo stack depth now %lu", _odo_stack.size()); 455 | } 456 | 457 | // ========================== END OF FILE ========================== 458 | -------------------------------------------------------------------------------- /opencog/generate/Aggregate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Aggregate.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_AGGREGATE_H 23 | #define _OPENCOG_AGGREGATE_H 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | namespace opencog 32 | { 33 | /** \addtogroup grp_generate 34 | * @{ 35 | */ 36 | 37 | class Aggregate 38 | { 39 | private: 40 | AtomSpace* _as; 41 | AtomSpacePtr _scratch; 42 | 43 | /// Decision-maker 44 | GenerateCallback* _cb; 45 | 46 | /// Current traversal state 47 | OdoFrame _frame; 48 | Odometer _odo; 49 | 50 | std::stack _frame_stack; 51 | std::stack _odo_sections; 52 | void push_frame(); 53 | void pop_frame(); 54 | 55 | std::stack _odo_stack; 56 | void push_odo(); 57 | void pop_odo(); 58 | 59 | void clear(void); 60 | 61 | bool init_odometer(void); 62 | bool step_odometer(void); 63 | bool do_step(void); 64 | 65 | void recurse(void); 66 | 67 | HandlePair connect_section(const Handle&, size_t, 68 | const Handle&, const Handle&); 69 | Handle make_link(const Handle&, size_t, const Handle&); 70 | 71 | public: 72 | Aggregate(AtomSpace*); 73 | ~Aggregate(); 74 | 75 | void aggregate(const HandleSet&, GenerateCallback&); 76 | 77 | }; 78 | 79 | 80 | /** @}*/ 81 | } // namespace opencog 82 | 83 | #endif // _OPENCOG_AGGREGATE_H 84 | -------------------------------------------------------------------------------- /opencog/generate/BasicParameters.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/BasicParameters.cc 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License v3 as 6 | * published by the Free Software Foundation and including the exceptions 7 | * at http://opencog.org/wiki/Licenses 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program; if not, write to: 16 | * Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #include 21 | 22 | #include "BasicParameters.h" 23 | 24 | using namespace opencog; 25 | 26 | BasicParameters::BasicParameters() 27 | { 28 | // Try to close existing connectors .. sometimes. 29 | close_fraction = 0.3; 30 | } 31 | 32 | BasicParameters::~BasicParameters() 33 | {} 34 | 35 | static std::random_device seed; 36 | static std::mt19937 rangen(seed()); 37 | 38 | static inline double uniform_double(void) 39 | { 40 | static std::uniform_real_distribution<> dist(0.0, 1.0); 41 | return dist(rangen); 42 | } 43 | 44 | bool BasicParameters::connect_existing(const OdoFrame& frm) 45 | { 46 | return uniform_double() < close_fraction; 47 | } 48 | 49 | bool BasicParameters::step(const OdoFrame& frm) 50 | { 51 | return true; 52 | } 53 | -------------------------------------------------------------------------------- /opencog/generate/BasicParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/BasicParameters.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_BASIC_PARAMETERS_H 23 | #define _OPENCOG_BASIC_PARAMETERS_H 24 | 25 | #include 26 | #include 27 | 28 | namespace opencog 29 | { 30 | /** \addtogroup grp_generate 31 | * @{ 32 | */ 33 | 34 | /// The BasicParameters class is used to provide configuration info 35 | /// to the RandomCallback class, constraining the ranges and 36 | /// distributions that get used. Its currently a mish-mash of policy. 37 | /// Under construction. 38 | 39 | class BasicParameters : public RandomParameters 40 | { 41 | public: 42 | BasicParameters(); 43 | virtual ~BasicParameters(); 44 | 45 | virtual bool connect_existing(const OdoFrame&); 46 | virtual bool step(const OdoFrame&); 47 | 48 | /// Fraction of the time that an attempt should be made to join 49 | /// together two existing open connectors, if that is possible. 50 | /// When two existing connectors are joined together, the size 51 | /// of the network does not increase, and the number of existing 52 | /// unconnected connectorsdrops by two. Otherwise, a new puzzle 53 | /// piece is selected from the lexis (thus necessarily enlarging 54 | /// the network.) 55 | double close_fraction; 56 | }; 57 | 58 | 59 | /** @}*/ 60 | } // namespace opencog 61 | 62 | #endif // _OPENCOG_BASIC_PARAMETERS_H 63 | -------------------------------------------------------------------------------- /opencog/generate/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ADD_LIBRARY(generate SHARED 4 | Aggregate.cc 5 | BasicParameters.cc 6 | CollectStyle.cc 7 | Dictionary.cc 8 | LinkStyle.cc 9 | Odometer.cc 10 | RandomCallback.cc 11 | SimpleCallback.cc 12 | ) 13 | 14 | TARGET_LINK_LIBRARIES(generate 15 | ${ATOMSPACE_LIBRARIES} 16 | ${COGUTIL_LIBRARY} 17 | uuid 18 | ) 19 | 20 | INSTALL(TARGETS generate 21 | LIBRARY DESTINATION "lib${LIB_DIR_SUFFIX}/opencog") 22 | 23 | INSTALL(FILES 24 | Aggregate.h 25 | BasicParameters.h 26 | CollectStyle.h 27 | Dictionary.h 28 | GenerateCallback.h 29 | LinkStyle.h 30 | Odometer.h 31 | RandomCallback.h 32 | RandomParameters.h 33 | SimpleCallback.h 34 | DESTINATION "include/opencog/generate" 35 | ) 36 | -------------------------------------------------------------------------------- /opencog/generate/CollectStyle.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/CollectStyle.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | 24 | #include "CollectStyle.h" 25 | 26 | using namespace opencog; 27 | 28 | CollectStyle::CollectStyle(void) 29 | { 30 | } 31 | 32 | CollectStyle::~CollectStyle() {} 33 | 34 | /// In this "style" of recording a result, we just tack it onto 35 | /// a C++ container holding the solutions. Other "styles" are 36 | /// possible; we could report them elsewehre, too... 37 | void CollectStyle::record_solution(const OdoFrame& frm) 38 | { 39 | size_t nsolns = _solutions.size(); 40 | _solutions.insert(frm._linkage); 41 | size_t news = _solutions.size(); 42 | logger().fine("===================================="); 43 | if (nsolns != news) 44 | { 45 | logger().fine("Obtained new solution %lu of size %lu:", 46 | news, frm._linkage.size()); 47 | for (const Handle& lkg : frm._linkage) 48 | frm.print_section(lkg); 49 | } 50 | else 51 | { 52 | logger().fine("Rediscovered solution, still have %lu size=%lu", 53 | news, frm._linkage.size()); 54 | } 55 | logger().fine("===================================="); 56 | } 57 | 58 | /// XXX FIXME ... maybe should attach to a MemberLink or something? 59 | /// This obviously fails to scale if the section is large. 60 | /// That would be considered to be a different "style" and would 61 | /// almost surely be better than using SetLinks... 62 | Handle CollectStyle::get_solutions(void) 63 | { 64 | HandleSeq solns; 65 | for (const auto& sol : _solutions) 66 | { 67 | HandleSeq sects; 68 | for (const Handle& sect : sol) 69 | sects.push_back(sect); 70 | 71 | solns.push_back(createLink(std::move(sects), SET_LINK)); 72 | } 73 | return createLink(std::move(solns), SET_LINK); 74 | } 75 | -------------------------------------------------------------------------------- /opencog/generate/CollectStyle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/CollectStyle.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_COLLECT_STYLE_H 23 | #define _OPENCOG_COLLECT_STYLE_H 24 | 25 | #include 26 | 27 | namespace opencog 28 | { 29 | /** \addtogroup grp_generate 30 | * @{ 31 | */ 32 | 33 | /// Record completed networks. This is one possible "style" of 34 | /// recording results; other styles are possible. 35 | 36 | class CollectStyle 37 | { 38 | protected: 39 | /// Accumulated set of fully-grounded solutions. 40 | std::set _solutions; 41 | 42 | public: 43 | CollectStyle(void); 44 | ~CollectStyle(); 45 | 46 | void clear(void) { _solutions.clear(); } 47 | void record_solution(const OdoFrame&); 48 | 49 | size_t num_solutions(void) { return _solutions.size(); } 50 | std::set get_solution_set(void) { return _solutions; } 51 | Handle get_solutions(void); 52 | }; 53 | 54 | 55 | /** @}*/ 56 | } // namespace opencog 57 | 58 | #endif // _OPENCOG_COLLECT_STYLE_H 59 | -------------------------------------------------------------------------------- /opencog/generate/Dictionary.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Dictionary.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "Dictionary.h" 28 | 29 | using namespace opencog; 30 | 31 | Dictionary::Dictionary(AtomSpace* as) 32 | : _as(as) 33 | {} 34 | 35 | // =============================================================== 36 | // Pole stuff. 37 | 38 | void Dictionary::add_pole_pair(const Handle& fm_pole, 39 | const Handle& to_pole) 40 | { 41 | _pole_pairs.push_back({fm_pole, to_pole}); 42 | } 43 | 44 | /// Given the Connector `from_con`, return a list of Connectors 45 | /// that it can attach to. 46 | HandleSeq Dictionary::joints(const Handle& from_con) const 47 | { 48 | HandleSeq phs; 49 | 50 | // Nothing to do, if not a connector. 51 | // XXX FIXME. I think we should be asserting here. 52 | // Or at least throwing. 53 | if (CONNECTOR != from_con->get_type()) return phs; 54 | 55 | // Assume only one pole per connector. 56 | Handle from_pole = from_con->getOutgoingAtom(1); 57 | Handle to_pole; 58 | for (const HandlePair& popr: _pole_pairs) 59 | if (from_pole == popr.first) { to_pole = popr.second; break; } 60 | 61 | // A matching pole was not found. 62 | if (!to_pole) return phs; 63 | 64 | // Link type of the desired link to make... 65 | Handle linkty = from_con->getOutgoingAtom(0); 66 | 67 | // Find appropriate connector, if it exists. 68 | Handle matching = _as->get_atom(createLink(CONNECTOR, linkty, to_pole)); 69 | if (matching) 70 | phs.push_back(matching); 71 | 72 | return phs; 73 | } 74 | 75 | // =============================================================== 76 | // Section stuff. 77 | 78 | /// add_to_lexis() - Declare a section as valid for use in assembly. 79 | /// Basically, only those sections that have been declared to be a part 80 | /// of the lexis will be used during assembly/aggregation. 81 | /// 82 | void Dictionary::add_to_lexis(const Handle& sect) 83 | { 84 | // We are storing lexical entries in a specialized struct in this 85 | // class. This is arguably a deasign failure ... we should be 86 | // storing the lexical entries in the AtomSpace, as EvaluationLinks 87 | // with Predicate "lexis", and then using the AtomSpace API to 88 | // access... Just sayin... 89 | // 90 | 91 | // First lookup table: given a point, create a list of all the 92 | // sections it belongs to. 93 | Handle point = sect->getOutgoingAtom(0); 94 | HandleSeq slist = _entries[point]; 95 | slist.push_back(sect); 96 | _entries[point] = slist; 97 | 98 | // Second lookup table: given a connector, we can lookup a list 99 | // of sections that have that connector in it. We're using a 100 | // sequence, not a set, for two reasons: (1) fast random lookup, 101 | // and (2) the sequence can be ordered in priority order. 102 | // 103 | Handle con_seq = sect->getOutgoingAtom(1); 104 | for (const Handle& con : con_seq->getOutgoingSet()) 105 | { 106 | HandleSeq sect_list = _connectables[con]; 107 | 108 | // Only allow unique entries 109 | auto found = std::find(sect_list.begin(), sect_list.end(), sect); 110 | if (sect_list.end() == found) 111 | { 112 | sect_list.push_back(sect); 113 | _connectables[con] = sect_list; 114 | } 115 | } 116 | } 117 | 118 | #if NOT_NEEDED_RIGHT_NOW 119 | /// Sort the list of sections containing some connecter into weighted 120 | /// order, by acessing the weights located at the predicate key. 121 | /// 122 | /// That is, for each connector, the lexis matains a sequential list 123 | /// of sections holding that connector. This will sort that list, from 124 | /// greatest to least, based on the FloatValue located at `predicate` 125 | /// on the section. 126 | /// 127 | /// XXX FIXME ... this method is not needed, not in this form, and 128 | /// should probably be deleted. 129 | /// 130 | void Dictionary::sort_lexis(const Handle& predicate) 131 | { 132 | struct { 133 | Handle pred; 134 | bool operator()(Handle a, Handle b) const 135 | { 136 | FloatValuePtr va(FloatValueCast(a->getValue(pred))); 137 | FloatValuePtr vb(FloatValueCast(b->getValue(pred))); 138 | return va->value()[0] > vb->value()[0]; 139 | } 140 | } bigger; 141 | 142 | bigger.pred = predicate; 143 | 144 | for (auto& pr: _connectables) 145 | { 146 | HandleSeq& seq = pr.second; 147 | std::sort(seq.begin(), seq.end(), bigger); 148 | } 149 | } 150 | #endif 151 | 152 | /// Given a Connector, return a list of all of the Sections that 153 | /// the connector appears in. 154 | const HandleSeq& Dictionary::connectables(const Handle& connector) const 155 | { 156 | static HandleSeq empty; 157 | 158 | const auto& its = _connectables.find(connector); 159 | if (its == _connectables.end()) return empty; 160 | 161 | return its->second; 162 | } 163 | 164 | const HandleSeq& Dictionary::entries(const Handle& point) const 165 | { 166 | static HandleSeq empty_set; 167 | const auto ice = _entries.find(point); 168 | if (_entries.end() == ice) return empty_set; 169 | 170 | return ice->second; 171 | } 172 | -------------------------------------------------------------------------------- /opencog/generate/Dictionary.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Dictionary.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_DICTIONARY_H 23 | #define _OPENCOG_DICTIONARY_H 24 | 25 | #include 26 | 27 | namespace opencog 28 | { 29 | /** \addtogroup grp_generate 30 | * @{ 31 | */ 32 | 33 | typedef std::map HandleSeqMap; 34 | 35 | /// Dictionary (Lexis) of sections that can connect to one-another. 36 | /// This provides several convenience data structures. These include: 37 | /// 38 | /// * A set of pairs of connectors that can attach to one-another. 39 | /// 40 | /// * A map from Connectors to the Sections that contain them. 41 | /// 42 | /// Attention: In principle, these maps should not be stored in a 43 | /// C++ struct such as this, but should instead be pulled directly 44 | /// from the AtomSpace. However, at this time, it just seems more 45 | /// convenient to use this struct. This may change! 46 | class Dictionary 47 | { 48 | AtomSpace* _as; 49 | 50 | /// Pairings of connectors that can joint to one-another. 51 | HandlePairSeq _pole_pairs; 52 | 53 | /// Map from Connectors to Sections that hold that connector. 54 | /// This map is set up at the start, before iteration begins. 55 | // 56 | HandleSeqMap _connectables; 57 | 58 | /// Map from points to Sections rooted at that point. 59 | /// This map is set up at the start, before iteration begins. 60 | HandleSeqMap _entries; 61 | 62 | public: 63 | Dictionary(AtomSpace*); 64 | 65 | void add_pole_pair(const Handle&, const Handle&); 66 | 67 | HandleSeq joints(const Handle&) const; 68 | 69 | void add_to_lexis(const Handle&); 70 | void add_to_lexis(const HandleSet& lex) { 71 | for (const Handle& h: lex) add_to_lexis(h); 72 | } 73 | // void sort_lexis(const Handle&); 74 | 75 | const HandleSeq& connectables(const Handle&) const; 76 | const HandleSeq& entries(const Handle&) const; 77 | }; 78 | 79 | 80 | /** @}*/ 81 | } // namespace opencog 82 | 83 | #endif // _OPENCOG_DICTIONARY_H 84 | -------------------------------------------------------------------------------- /opencog/generate/GenerateCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/GenerateCallback.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_GENERATE_CALLBACK_H 23 | #define _OPENCOG_GENERATE_CALLBACK_H 24 | 25 | #include 26 | #include 27 | 28 | namespace opencog 29 | { 30 | /** \addtogroup grp_generate 31 | * @{ 32 | */ 33 | 34 | /// Aggregation selection callbacks. As an assembly is being created, 35 | /// that assembly will have unconnected, open connectors on it. 36 | /// Aggregation proceeds by attaching sections ("puzzle pieces") to 37 | /// each open connector, until there are none left. But what to connect 38 | /// to what? The master aggregation algorithm defers that choice to this 39 | /// callback API. Different algos can offer up different connections to 40 | /// try out. The master aggregation algo manages the overall process of 41 | /// connecting things up; the callbacks suggest what to connect next. 42 | /// 43 | /// The master aggregation algo is stack-based, and potentially 44 | /// combinatoric-explosive, as each the current set of open connectors 45 | /// depends on the history of what was previously attached. Because 46 | /// the algo is breadt-first (see other descriptions) two stacks are 47 | /// maintained: one for each "row" (odometer) and one for each 48 | /// odometer-wheel. 49 | /// 50 | class GenerateCallback 51 | { 52 | protected: 53 | AtomSpace* _as; 54 | public: 55 | GenerateCallback(AtomSpace* as) : _as(as) {} 56 | virtual ~GenerateCallback() {} 57 | 58 | /// Callbacks are allowed to keep dynamical state. Callbacks are 59 | /// allowed to be reused for multiple generations. This gives the 60 | /// callback the opportunity to clear dynamical state. In addition, 61 | /// this provides a scratch space where temporary results can be 62 | /// stored for the duration. 63 | virtual void clear(AtomSpace*) = 0; 64 | 65 | /// Declare a set of initial starting (nucleation) points. The 66 | /// generated graph will grow outwards from these points. 67 | virtual void root_set(const HandleSet& points) = 0; 68 | 69 | /// Iterator, returning the next untried initial starting (nucleation) 70 | /// sections. These are meant to be a set of sections, containing 71 | /// the nucleation points, from which the graph generation can grow. 72 | /// The nucleation points were previously declated by `root_set()`. 73 | /// Returns the emtpy set, when there is no more to be done. 74 | virtual HandleSet next_root(void) = 0; 75 | 76 | /// Given a connector, return a set of matching connectors 77 | /// that this particular connector could connect to. This 78 | /// set may be empty, or may contain more than one match. 79 | virtual HandleSeq joints(const Handle&) = 0; 80 | 81 | /// Given an existing connected section `fm_sect` and a connector 82 | /// `fm_con` on that section, as well as a mating `to_con`, return 83 | /// a section that could be attached. This allows the callback to 84 | /// chose sections in such a way that the highest-priority or most 85 | /// preferable sections are mated first. Return null handle to 86 | /// discontinue mating. 87 | /// 88 | /// The `fm_con` is the connector located at `offset` in the 89 | /// `fm_sect`. 90 | /// 91 | /// This should be implemented so that it behaves like a 'future' 92 | /// or a 'promise', so that, when called, it returns the next 93 | /// section from a (virtual) list of eligible sections. 94 | virtual Handle select(const OdoFrame&, 95 | const Handle& fm_sect, size_t offset, 96 | const Handle& to_con) = 0; 97 | 98 | /// Create a link from connector `fm_con` to connector `to_con`, 99 | /// which will connect `fm_pnt` to `to_pnt`. 100 | virtual Handle make_link(const Handle& fm_con, const Handle& to_con, 101 | const Handle& fm_pnt, const Handle& to_pnt) = 0; 102 | 103 | /// Return a count of the number of links between `fm_sect` and 104 | /// `to_sect` of type `link_type`. This should return zero if these 105 | /// two sections are not nearest neighbors. Return one, if there 106 | /// is exactly one link. Return two or more, if there are "parallel" 107 | /// links in place -- if the two sections are multiply-connected. 108 | virtual size_t num_links(const Handle& fm_sect, const Handle& to_sect, 109 | const Handle& link_type) = 0; 110 | 111 | virtual void push_frame(const OdoFrame&) {} 112 | virtual void pop_frame(const OdoFrame&) {} 113 | 114 | virtual void push_odometer(const Odometer&) {} 115 | virtual void pop_odometer(const Odometer&) {} 116 | 117 | /// Called before taking a step of the odometer. 118 | /// Return `true` to take the step, else false. 119 | /// Returning false will abort the current odometer. 120 | /// Traversal will resume at an earlier level. 121 | /// 122 | /// The default below allows infinite recursion. 123 | virtual bool step(const OdoFrame&) { return true; } 124 | 125 | /// Called when a solution is found. A solution is a linkage, 126 | /// with no open connectors. 127 | virtual void solution(const OdoFrame&) = 0; 128 | 129 | /// Return a SetLink with all of the solutions that were 130 | /// reported via the above callback. Perform any other AtomSpace 131 | /// maintanence pertaining to reporting the solutions. 132 | virtual Handle get_solutions(void) = 0; 133 | 134 | // --------------------------------------------------------------- 135 | /// Generic Parameters 136 | /// These are parameters that all callback systems might reasonably 137 | /// want to consult, when determining behavior. 138 | // (Should these be moved to thier own class???) 139 | 140 | /// Maximum number of solutions to accept. Search is halted after 141 | /// this number is reached. 142 | size_t max_solutions = -1; 143 | 144 | /// Allow connectors on an open section to connect back onto 145 | /// themselves (if the other mating rules allow the two connectors 146 | /// to connect). 147 | bool allow_self_connections = false; 148 | 149 | /// The maximum number of any kind of link allowed between a pair 150 | /// of sections. By default, it is one, as "most" "typical" graphs 151 | /// make sense when only one edge connects a pair of vertexes. 152 | size_t pair_any_links = 1; 153 | 154 | /// The maximum number of each specific type of link allowed between 155 | /// a pair of sections. For example, if this is set to two, then two 156 | /// edges of different types can connect the same pair of points. 157 | /// This is ignored if `pair_any_links` (above) is 1. 158 | size_t pair_typed_links = 1; 159 | 160 | /// Maximum size of the generated network. Exploration of networks 161 | /// larger than this will not be attempted. 162 | size_t max_network_size = -1; 163 | 164 | /// Maximum depth to explore from the starting point. This is 165 | /// counted in terms of the maximum depth of the stack of 166 | /// odometers. This is maximum diameter of the network, as measured 167 | /// from the starting point. 168 | size_t max_depth = -1; 169 | 170 | /// Maximum number of odometer steps to take. This needs to be 171 | /// capped, as infinite loops are possible, especially with the 172 | /// random selector, which, with approrpaite weightings, can 173 | /// explosively create infinite networks. (Its potentially 174 | /// possible to determine, in advance, if a given weighting scheme 175 | /// will create infinite trees, mon average. But this does not seem 176 | /// like a high priority task, right now. 177 | /// (2016 vintage CPU run at approx 1.2K steps/second). 178 | size_t max_steps = 25101; 179 | 180 | /// A location to which all point instances will be anchored. 181 | /// Thus, all points can be found by following the MemberLink 182 | /// from this anchor point. 183 | Handle point_set = Handle::UNDEFINED; 184 | }; 185 | 186 | 187 | /** @}*/ 188 | } // namespace opencog 189 | 190 | #endif // _OPENCOG_GENERATE_CALLBACK_H 191 | -------------------------------------------------------------------------------- /opencog/generate/LinkStyle.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/LinkStyle.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include "LinkStyle.h" 29 | 30 | using namespace opencog; 31 | 32 | LinkStyle::LinkStyle(void) : _scratch(nullptr) 33 | { 34 | } 35 | 36 | /// Given a generic section, create a unique instance of it. 37 | /// As "puzzle pieces" are assembled, each new usage represents a 38 | /// "different location" in the puzzle, and so we create a unique 39 | /// instance for each location. 40 | /// 41 | /// This assumes that the section point is a node, so that we 42 | /// generate a unique string for that node. 43 | Handle LinkStyle::create_unique_section(const Handle& sect) 44 | { 45 | uuid_t uu; 46 | uuid_generate(uu); 47 | char idstr[37]; 48 | uuid_unparse(uu, idstr); 49 | 50 | Handle point = sect->getOutgoingAtom(0); 51 | Handle disj = sect->getOutgoingAtom(1); 52 | 53 | if (not point->is_node()) 54 | throw RuntimeException(TRACE_INFO, 55 | "Expection a Node for the section point, got %s", 56 | point->to_string().c_str()); 57 | 58 | // Create a unique point. 59 | Handle upoint(_scratch->add_node(point->get_type(), 60 | point->get_name() + "@" + idstr)); 61 | 62 | // Record the point location. 63 | if (_point_set) 64 | _mempoints.emplace_back(createLink(MEMBER_LINK, upoint, _point_set)); 65 | 66 | // Create a unique instance of the section. 67 | Handle usect(_scratch->add_link(SECTION, upoint, disj)); 68 | 69 | // Record it's original type. 70 | // _inhsects.emplace_back(createLink(INHERITANCE_LINK, upoint, sect)); 71 | 72 | return usect; 73 | } 74 | 75 | /// Create an undirected edge connecting the two points `fm_pnt` and 76 | /// `to_pnt`, using the connectors `fm_con` and `to_con`. The edge 77 | /// is "undirected" because a SetLink is used to hold the two 78 | /// end-points. Recall SetLinks are unordered links, so neither point 79 | /// can be identified as head or tail. 80 | Handle LinkStyle::create_undirected_link(const Handle& fm_con, 81 | const Handle& to_con, 82 | const Handle& fm_pnt, 83 | const Handle& to_pnt) 84 | { 85 | // Create the actual link to use. 86 | Handle linkty = fm_con->getOutgoingAtom(0); 87 | Handle edg = _scratch->add_link(SET_LINK, fm_pnt, to_pnt); 88 | Handle lnk = _scratch->add_link(EVALUATION_LINK, linkty, edg); 89 | return lnk; 90 | } 91 | 92 | /// Return a count of the number of links, of type `link_type`, 93 | /// connecting the two sections. Returns zero if they are not nearest 94 | /// neighbors, otherwise return a count. 95 | size_t LinkStyle::num_undirected_links(const Handle& fm_sect, 96 | const Handle& to_sect, 97 | const Handle& link_type) 98 | { 99 | const Handle& fm_pnt = fm_sect->getOutgoingAtom(0); 100 | const Handle& to_pnt = to_sect->getOutgoingAtom(0); 101 | Handle edg = _scratch->get_link(SET_LINK, fm_pnt, to_pnt); 102 | if (nullptr == edg) return 0; 103 | Handle lnk = _scratch->get_link(EVALUATION_LINK, link_type, edg); 104 | if (nullptr == lnk) return 0; 105 | 106 | const Handle& disj = fm_sect->getOutgoingAtom(1); 107 | size_t count = 0; 108 | for (const Handle& exli : disj->getOutgoingSet()) 109 | { 110 | if (*exli == *lnk) count++; 111 | } 112 | 113 | // XXX FIXME -- undef this and save some CPU... 114 | #define SELF_TEST 115 | #ifdef SELF_TEST 116 | // Count the number of links, starting with to_sect. 117 | // The result should be the same. This is a waste of CPU time, 118 | // if everything is working correctly. 119 | const Handle& tdis = to_sect->getOutgoingAtom(1); 120 | size_t tcnt = 0; 121 | for (const Handle& exli : tdis->getOutgoingSet()) 122 | { 123 | if (*exli == *lnk) tcnt++; 124 | } 125 | OC_ASSERT(count == tcnt, "Not internally self-consistent!"); 126 | #endif 127 | 128 | return count; 129 | } 130 | 131 | /// Return a count of the number of any kind of links (no matter what 132 | /// the type), connecting the two sections. Returns zero if they are 133 | /// not nearest neighbors, otherwise return a count. Actually, as 134 | /// currently written, this does assume the links are undirected. 135 | size_t LinkStyle::num_any_links(const Handle& fm_sect, 136 | const Handle& to_sect) 137 | { 138 | const Handle& fm_pnt = fm_sect->getOutgoingAtom(0); 139 | const Handle& to_pnt = to_sect->getOutgoingAtom(0); 140 | 141 | const Handle& fm_disj = fm_sect->getOutgoingAtom(1); 142 | const Handle& to_disj = to_sect->getOutgoingAtom(1); 143 | 144 | size_t cnt = 0; 145 | for (const Handle& lnk: fm_disj->getOutgoingSet()) 146 | { 147 | if (CONNECTOR == lnk->get_type()) continue; 148 | 149 | HandleSeq djs = lnk->getIncomingSetByType(CONNECTOR_SEQ); 150 | for (const Handle& tdj : djs) 151 | { 152 | if (*tdj == *to_disj) cnt++; 153 | } 154 | } 155 | 156 | return cnt; 157 | } 158 | 159 | void LinkStyle::clear(void) 160 | { 161 | _mempoints.clear(); 162 | _inhsects.clear(); 163 | } 164 | 165 | void LinkStyle::save_work(AtomSpace* as) 166 | { 167 | for (const Handle& h : _mempoints) 168 | { 169 | Handle pt(as->add_atom(h)); 170 | 171 | #if FINISH_ME_LATER 172 | // Link tying together the fully-connected piece, and the 173 | // puzzle piece it came from. 174 | IncomingSet iset = pt->get_incoming_by_type(SECTION, as); 175 | OC_ASSERT(1 == iset.size(), "Wrong number of sections"); 176 | as 177 | #endif 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /opencog/generate/LinkStyle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/LinkStyle.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_LINK_STYLE_H 23 | #define _OPENCOG_LINK_STYLE_H 24 | 25 | #include 26 | 27 | namespace opencog 28 | { 29 | /** \addtogroup grp_generate 30 | * @{ 31 | */ 32 | 33 | /// Utility class providing a basic style of linking together two 34 | /// puzzle pieces. Other styles are possible... 35 | 36 | class LinkStyle 37 | { 38 | protected: 39 | AtomSpace* _scratch; 40 | Handle _point_set; 41 | 42 | HandleSeq _mempoints; 43 | HandleSeq _inhsects; 44 | 45 | public: 46 | LinkStyle(void); 47 | void clear(void); 48 | 49 | Handle create_unique_section(const Handle&); 50 | Handle create_undirected_link(const Handle&, const Handle&, 51 | const Handle&, const Handle&); 52 | 53 | size_t num_undirected_links(const Handle&, const Handle&, 54 | const Handle&); 55 | size_t num_any_links(const Handle&, const Handle&); 56 | 57 | void save_work(AtomSpace*); 58 | }; 59 | 60 | /** @}*/ 61 | } // namespace opencog 62 | 63 | #endif // _OPENCOG_LINK_STYLE_H 64 | -------------------------------------------------------------------------------- /opencog/generate/Odometer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Odometer.cc 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License v3 as 6 | * published by the Free Software Foundation and including the exceptions 7 | * at http://opencog.org/wiki/Licenses 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program; if not, write to: 16 | * Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "Odometer.h" 27 | 28 | using namespace opencog; 29 | 30 | void OdoFrame::clear(void) 31 | { 32 | _open_points.clear(); 33 | _open_sections.clear(); 34 | _linkage.clear(); 35 | _nodo = -1; 36 | _wheel = -1; 37 | } 38 | 39 | void Odometer::clear(void) 40 | { 41 | _sections.clear(); 42 | _from_index.clear(); 43 | _to_connectors.clear(); 44 | _size = 0; 45 | _step = -1; 46 | _frame_depth = 0; 47 | } 48 | 49 | // ================================================================= 50 | // Debug printing utilities. 51 | // Current printing format makes assumptions about connectors 52 | // which will be invalid, in general. XXX FIMXE, someday. 53 | 54 | void OdoFrame::print_section(const Handle& section) 55 | { 56 | logger().fine(" %s:", 57 | section->getOutgoingAtom(0)->get_name().c_str()); 58 | const HandleSeq& conseq = section->getOutgoingAtom(1)->getOutgoingSet(); 59 | for (const Handle& con : conseq) 60 | { 61 | if (CONNECTOR == con->get_type()) 62 | logger().fine(" %s%s", 63 | con->getOutgoingAtom(0)->get_name().c_str(), 64 | con->getOutgoingAtom(1)->get_name().c_str()); 65 | else 66 | logger().fine(" %s - %s - %s", 67 | con->getOutgoingAtom(1)->getOutgoingAtom(0)->get_name().c_str(), 68 | con->getOutgoingAtom(0)->get_name().c_str(), 69 | con->getOutgoingAtom(1)->getOutgoingAtom(1)->get_name().c_str()); 70 | } 71 | } 72 | 73 | void OdoFrame::print(void) const 74 | { 75 | logger().fine("OdoFrame:"); 76 | std::string pts; 77 | for (const Handle& pt : _open_points) 78 | pts += pt->get_name() + ", "; 79 | logger().fine(" pts: %s", pts.c_str()); 80 | 81 | logger().fine(" Open:"); 82 | for (const Handle& open : _open_sections) 83 | print_section(open); 84 | 85 | logger().fine(" Closed:"); 86 | for (const Handle& lkg : _linkage) 87 | print_section(lkg); 88 | } 89 | 90 | // ================================================================= 91 | 92 | void Odometer::print_wheel(const OdoFrame& frm, size_t i) const 93 | { 94 | bool sect_open = true; 95 | const Handle& fm_sect = _sections[i]; 96 | if (frm._open_sections.find(fm_sect) == frm._open_sections.end()) 97 | sect_open = false; 98 | 99 | const Handle& disj = fm_sect->getOutgoingAtom(1); 100 | const Handle& fm_con = disj->getOutgoingAtom(_from_index[i]); 101 | 102 | bool conn_open = (fm_con->get_type() == CONNECTOR); 103 | if (conn_open) 104 | logger().fine(" wheel %lu: %s : %s\t: %s -> %s (sect %s; conn %s)", 105 | i, 106 | _sections[i]->getOutgoingAtom(0)->get_name().c_str(), 107 | fm_con->getOutgoingAtom(0)->get_name().c_str(), 108 | fm_con->getOutgoingAtom(1)->get_name().c_str(), 109 | _to_connectors[i]->getOutgoingAtom(1)->get_name().c_str(), 110 | sect_open ? "open" : "closed", 111 | conn_open ? "open" : "closed" 112 | ); 113 | else 114 | { 115 | const Handle& lnkset = fm_con->getOutgoingAtom(1); 116 | logger().fine(" wheel %lu: %s : %s\t: %s -> %s (sect %s; conn %s)", 117 | i, 118 | _sections[i]->getOutgoingAtom(0)->get_name().c_str(), 119 | fm_con->getOutgoingAtom(0)->get_name().c_str(), 120 | lnkset->getOutgoingAtom(0)->get_name().c_str(), 121 | lnkset->getOutgoingAtom(1)->get_name().c_str(), 122 | sect_open ? "open" : "closed", 123 | conn_open ? "open" : "closed" 124 | ); 125 | } 126 | } 127 | 128 | void Odometer::print_odometer(const OdoFrame& frm) const 129 | { 130 | logger().fine("Odometer State: length %lu", _size); 131 | for (size_t i=0; i<_size; i++) 132 | print_wheel(frm, i); 133 | } 134 | 135 | // ========================== END OF FILE ========================== 136 | -------------------------------------------------------------------------------- /opencog/generate/Odometer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/Odometer.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_ODOMETER_H 23 | #define _OPENCOG_ODOMETER_H 24 | 25 | #include 26 | 27 | namespace opencog 28 | { 29 | /** \addtogroup grp_generate 30 | * @{ 31 | */ 32 | 33 | /// Current traversal state 34 | struct OdoFrame 35 | { 36 | /// Points that are unconnected 37 | HandleSet _open_points; 38 | 39 | /// Sections with unconnected connectors. 40 | HandleSet _open_sections; 41 | 42 | /// Completed links. 43 | HandleSet _linkage; 44 | 45 | /// The depth of the odometer stack, and the odometer wheel 46 | /// that this frame is isolating. State earlier than this is 47 | /// in earlier frames, and later state is in later frames. 48 | size_t _nodo; 49 | size_t _wheel; 50 | 51 | void clear(void); 52 | void print(void) const; 53 | 54 | static void print_section(const Handle&); 55 | }; 56 | 57 | /// Odometer for breadth-wise aggregation 58 | struct Odometer 59 | { 60 | /// Size of odometer. All vectors below are of this size. 61 | size_t _size; 62 | 63 | /// Ordered list of from-sections which have unconnected connectors 64 | // that are being explored as a part of this odometer. 65 | /// from-connectors. 66 | HandleSeq _sections; 67 | 68 | /// Index into the corresponding section, pointing at the open 69 | /// connector (the "from-connector"). 70 | std::vector _from_index; 71 | 72 | /// List of valid connectors that each from-connector can mate to. 73 | /// (That can be legally joined to a from-connector) Note there 74 | /// may be multiple to-connectors for each from-connector. 75 | HandleSeq _to_connectors; 76 | 77 | /// The next wheel to be stepped. This is an index into the 78 | /// above sequences. 79 | size_t _step; 80 | 81 | /// The correspoding frame-stack depth, when this odo was created. 82 | size_t _frame_depth; 83 | 84 | void clear(void); 85 | void print_odometer(const OdoFrame&) const; 86 | void print_wheel(const OdoFrame&, size_t) const; 87 | }; 88 | 89 | /** @}*/ 90 | } // namespace opencog 91 | 92 | #endif // _OPENCOG_ODOMETER_H 93 | -------------------------------------------------------------------------------- /opencog/generate/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Theory of Operation 3 | Below are the details of the data structures in use, and the algorithms 4 | used to perform the breadth-first search. 5 | 6 | ## Terminology 7 | Some terms used in the code. 8 | 9 | * Each "jigsaw-puzzle piece" is called a "section". This is a poor 10 | choice of terminology, since several connected pieces are also a 11 | section (per formal definition of "a section" in mathematics). 12 | 13 | * The label on each puzzle-piece is called a "point" or "vertex". 14 | All pieces are labeled. 15 | 16 | * "Aggregation" means attaching new pieces to the existing assembly 17 | of pieces. 18 | 19 | Other terminology is introduced below. 20 | 21 | ## Breadth-first Aggregation 22 | The breadth-first algo starts when it is given a single puzzle-piece. 23 | It creates an ordered list of the (unconnected) connectors on the puzzle 24 | piece: this is called the "odometer". It gets this name because each 25 | connector can be attached to several other puzzle-pieces; as each one is 26 | tried, one must iterate over the remaining untried pieces. Each 27 | unconnected connector corresponds to a "wheel" in the odometer; the 28 | number of markings on each wheel corresponds to the number of puzzle- 29 | pieces that could attach to that connector. The odometer is "stepped" 30 | as expected: the right-most wheel must revolve once, before the next 31 | wheel can take a single step, and so-on. This guarantees that each 32 | combination of potential connections is attempted once. 33 | 34 | When all of the initial list of connectors have been extended by new 35 | attachments, the algorithm pushes the odometer state onto a stack, 36 | and then constructs a brand-new odometer at the "next level". A new 37 | list is created, of all of the unconnected connectors. This forms the 38 | spine of the new odometer. Again, each of these must be extended; 39 | when this is done, the algo repeats: it pushes the odometer state, 40 | and creates a new odometer. 41 | 42 | The new odometer may be the empty set if there are no more unconnected 43 | connectors. In this case, a "solution" is deemed to have been found: 44 | the jigsaw puzzle is complete. If it it is impossible to obtain a 45 | connection for some open connector, then it is impossible to continue 46 | at this level, and the algorithm backtracks to the previous level. 47 | The unextendable odometer is popped off the stack and discarded. 48 | Resuming at the previous level, the odometer is stepped once, and the 49 | attachment process is restarted. This continues until all solutions 50 | have been found, or until a termination condition triggers. 51 | 52 | This is breadth-first aggregation, in that all of the connectors in the 53 | odometer get a connection, before moving to the next level. 54 | 55 | If a solution is found, or if it is impossible to proceed, then the 56 | odometer must be stepped to the next step. Conceptually, this requires 57 | detaching the previously-connected piece at the given location, then 58 | selecting a new piece, and attaching it. To facilitate this attachment- 59 | detachment process, there is a second stake, the "frame stack". The 60 | frame is a set of the currently-assembled pieces and the set of pieces 61 | that are not yet fully connected. A new frame is created just before 62 | attaching a puzzle-piece; thus, returning to the previous unconnected 63 | state is as easy as popping the frame-stack. 64 | 65 | The frame-stack could, but does not march in synchrony with the 66 | extension at each level. This is because the stepping of the odometer 67 | is faced with several complications. Foremost is that a connector 68 | at a given level might connect with another connector at that same 69 | level. When this happens, this effectively knocks out one of the 70 | odometer wheels for that level: it cannot get a new connection because 71 | it is already connected. During stepping, those wheels cannot be 72 | stepped. As a result, the frame-stack is pushed only when a new 73 | connection is possible. This saves a few CPU-cycles of un-necessary 74 | frame pushes. Each frame is marked with the corresponding odometer 75 | and odometer-wheel to allow management of the frame-pop operation. 76 | 77 | ### A curious limitation!? 78 | The current implementation of the above is such that only one link 79 | is generated to connect one puzzle piece to another. Thus, although 80 | a pair of pieces may have multiple connectors that can be connected at 81 | once, only one of these is connected, and the possibility of connecting 82 | the others is not explored. 83 | 84 | ## User-defined callbacks 85 | The above description suggests that each odometer wheel exists as a 86 | finite list of connectable pieces. This is **NOT** the case! Instead, 87 | each wheel is implemented as a future or promise, such that, when asked 88 | upon, a "user-defined" callback will provide a connectable piece. 89 | This allows the code to be cleanly split into two parts: the aggregation 90 | algorithm described above, and the specifics of managing the pool of 91 | available puzzle-pieces and the priority-order in which they are tried. 92 | 93 | In particular, the callback mechanism allows the following tricks to 94 | be applied: 95 | 96 | * Puzzle pieces can be drawn at random, or in deterministic order, from 97 | a finite or infinite pool. 98 | * Zero matches can be returned to force early termination of the search. 99 | * Pieces can be given a weighting, probabilistic or otherwise, so that 100 | a lowest-cost aggregation path can be pursued. This allows local 101 | hill-climbing algos, simulated annealing, Bayesian or Markov nets, 102 | etc. 103 | 104 | The callback is called to create the linkage between two pieces. This 105 | allows the user to create either directed or undirected edges, or have 106 | any other arbitrarily-complex markup in the linkage. 107 | 108 | Although not part of the callback, the definition of matching connectors 109 | must be provided on startup. This is a set of "polar pairs" -- polar 110 | opposites that can connect to one-another. There is no restriction on 111 | what these may be -- the aggregation algorithm explores all valid 112 | connection-types. 113 | 114 | ## The `SimpleCallback` 115 | This callback provides a minimalistic basic operation, suitable for 116 | exhaustive searches over grammars that generate a strictly finite 117 | number of graphs. 118 | 119 | This callback does the following things (subject to change): 120 | 121 | * If it is possible to perform a connection to an existing connector, 122 | this is given the highest (and only) priority. This prevents 123 | infinite recursion in graphs that contain cycles -- otherwise any 124 | cycle could be "unwound" by drawing new pieces, and attaching them, 125 | ad infinitum. 126 | 127 | * When a cyclic connection is not possible, new pieces are drawn from 128 | a "dictionary" or "lexis" -- the "box" of unconnected pieces. 129 | Thus, users of this callback need only specify the pieces in this 130 | lexis. 131 | 132 | The callback is deterministic, but the order is not specified. There 133 | is no weighting or ranking involved in the drawing of pieces. 134 | 135 | ## The `RandomCallback` 136 | 137 | Provides ranking. Provides random weighted draws. Need writeup here 138 | describing it. 139 | 140 | ## Alternatives to Aggregation 141 | There are other ways of creating network graphs. The aggregation 142 | algorithm is an implementation of the idea that networks can be 143 | "assembled", and that the assembly process is very much like assembling 144 | a jogsaw puzzle: start with an edge-piece, and keep adding more and more 145 | pieces, until one is done. But there are other possibilities. 146 | 147 | * Changing priorities during aggregation. So during early stages of 148 | aggregation, if one consistntly picks high-arity pieces, one risks 149 | combinatoric explosion and an infinite graph. To avoid this, the 150 | probabilities could be adjusted to pick only lower-arity pieces, 151 | as the number of unconnected connectors gets too large. 152 | 153 | * Swapping out pieces during aggregation. If there are too many 154 | unconnected connectors, one could hunt for pieces that are identical, 155 | in all respects, except that they have fewer connectors. These can 156 | then be swaped in place of the bigger connector. 157 | 158 | * Like the above, but making the network more sparse, by activly cutting 159 | links. 160 | 161 | * Growing a network. Inverse of the above: given an existing connected 162 | edge, break it and insert a new piece in its place. This is 163 | interesting because it is more "botanical" in it's process - its 164 | actual growth. 165 | 166 | * Growth could be governed by explicit choices of substitution rules 167 | (e.g. Lindenmeyer systems) and very specifically, the wealth of work 168 | from Przemyslaw Prusinkiewicz at http://algorithmicbotany.org/papers/ 169 | 170 | * The Prusinkiewicz work can be thought of as work with structure 171 | grammars (e.g. BNF forms, classic Chomsky context-free grammars) 172 | whereas the work here is with dependency grammars. These two grammar 173 | types are "in principle" interconvertible, but behave quite 174 | differently. A wealk of possibilities open up by stealing the 175 | Prusinkiewicz ideas and applying them to dependency grammars. 176 | 177 | ## To-do 178 | 179 | * Add weights to the polar-pairs list. 180 | * Allow pieces to be drawn at most N times, for any given piece. 181 | * Force planar graphs 182 | * Control recursion when there are degenerate link-types. 183 | (See issue #6) 184 | 185 | THE END. Thanks for paying attention! 186 | -------------------------------------------------------------------------------- /opencog/generate/RandomCallback.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/RandomCallback.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "RandomCallback.h" 28 | 29 | using namespace opencog; 30 | 31 | RandomCallback::RandomCallback(AtomSpace* as, const Dictionary& dict, 32 | RandomParameters& parms) : 33 | GenerateCallback(as), _dict(dict), _parms(&parms) 34 | { 35 | _steps_taken = 0; 36 | 37 | max_solutions = 100; 38 | 39 | // Not interested in networks larger than 20 nodes. 40 | max_network_size = 20; 41 | 42 | // Max diameter of 10 43 | max_depth = 10; 44 | } 45 | 46 | RandomCallback::~RandomCallback() {} 47 | 48 | static std::random_device seed; 49 | static std::mt19937 rangen(seed()); 50 | 51 | void RandomCallback::clear(AtomSpace* scratch) 52 | { 53 | while (not _opensel_stack.empty()) _opensel_stack.pop(); 54 | _opensel._opensect.clear(); 55 | _opensel._opendi.clear(); 56 | 57 | _root_sections.clear(); 58 | _root_dist.clear(); 59 | _distmap.clear(); 60 | _steps_taken = 0; 61 | CollectStyle::clear(); 62 | LinkStyle::clear(); 63 | LinkStyle::_point_set = point_set; 64 | LinkStyle::_scratch = scratch; 65 | } 66 | 67 | void RandomCallback::root_set(const HandleSet& roots) 68 | { 69 | for (const Handle& point: roots) 70 | { 71 | HandleSeq sects(_dict.entries(point)); 72 | if (0 == sects.size()) 73 | throw RuntimeException(TRACE_INFO, 74 | "No dictionary entry for root=%s", point->to_string().c_str()); 75 | 76 | _root_sections.push_back(sects); 77 | 78 | // Create a discrete distribution. This will randomly pick 79 | // an index into the `root_sections` array. The weight of 80 | // each index is given by the pdf aka "probability distribution 81 | // function". The pdf is just given by the weighting-key 82 | // hanging off the section (in a FloatValue). 83 | std::vector pdf; 84 | for (const Handle& sect: sects) 85 | { 86 | FloatValuePtr fvp(FloatValueCast(sect->getValue(_weight_key))); 87 | if (fvp) 88 | pdf.push_back(fvp->value()[0]); 89 | else 90 | pdf.push_back(0.0); 91 | } 92 | std::discrete_distribution dist(pdf.begin(), pdf.end()); 93 | _root_dist.push_back(dist); 94 | } 95 | } 96 | 97 | /// Perform a random draw of root sections. 98 | HandleSet RandomCallback::next_root(void) 99 | { 100 | static HandleSet empty_set; 101 | size_t len = _root_sections.size(); 102 | if (len == 0) return empty_set; 103 | 104 | // Stop iterating if limits have been reached. 105 | if (max_steps < _steps_taken) return empty_set; 106 | if (max_solutions <= num_solutions()) return empty_set; 107 | 108 | // Random drawing. 109 | HandleSet starters; 110 | for (size_t i=0; isecond; 137 | return create_unique_section(to_sects[dist(rangen)]); 138 | } 139 | 140 | // Create a discrete distribution. This will randomly pick an 141 | // index into the `to_sects` array. The weight of each index 142 | // is given by the pdf aka "probability distribution function". 143 | // The pdf is just given by the weighting-key hanging off the 144 | // section (in a FloatValue). 145 | std::vector pdf; 146 | for (const Handle& sect: to_sects) 147 | { 148 | FloatValuePtr fvp(FloatValueCast(sect->getValue(_weight_key))); 149 | if (fvp) 150 | pdf.push_back(fvp->value()[0]); 151 | else 152 | pdf.push_back(0.0); 153 | } 154 | std::discrete_distribution dist(pdf.begin(), pdf.end()); 155 | _distmap.emplace(std::make_pair(to_con, dist)); 156 | 157 | return create_unique_section(to_sects[dist(rangen)]); 158 | } 159 | 160 | /// Return a section containing `to_con`, from the set of currently 161 | /// unconnected sections. 162 | /// 163 | /// Examine the set of currently-unconnected connectors. If any of 164 | /// them are connectable to `to_con`, then randomly pick one of the 165 | /// sections, and return that. Otherwise return the undefined handle. 166 | /// 167 | /// This disallows self-connections (the from and to-sections being the 168 | /// same) unless the parameters allow it. 169 | // 170 | // This just sets up a list of candidates; the actual picking is done 171 | // by `do_select_one()`. 172 | Handle RandomCallback::select_from_open(const OdoFrame& frame, 173 | const Handle& fm_sect, size_t offset, 174 | const Handle& to_con) 175 | { 176 | // XXX FIXME -- this implements a cache of distributions 177 | // ... which are kept on a stack ... this could be RAM intensive 178 | // ... and also CPU intensive. It might be faster to just 179 | // choose on the fly, yeah? Don't know... needs investigation. 180 | 181 | // Are there any attachable connectors? 182 | auto tosit = _opensel._opensect.find(to_con); 183 | if (_opensel._opensect.end() != tosit and tosit->second.size() == 0) 184 | return Handle::UNDEFINED; 185 | 186 | // If there's only one, pick it. 187 | const HandleSeq& to_seclist = tosit->second; 188 | if (to_seclist.size() == 1) 189 | return to_seclist[0]; 190 | 191 | // Do we have a chooser for the to-connector in the current frame? 192 | // If so, then use it. 193 | auto curit = _opensel._opendi.find(to_con); 194 | if (_opensel._opendi.end() != curit) 195 | { 196 | auto dist = curit->second; 197 | return to_seclist[dist(rangen)]; 198 | } 199 | 200 | // Create a list of connectable sections 201 | const Handle& linkty = to_con->getOutgoingAtom(0); 202 | HandleSeq to_sects; 203 | for (const Handle& open_sect : frame._open_sections) 204 | { 205 | if (not allow_self_connections and open_sect == fm_sect) 206 | continue; 207 | 208 | const Handle& conseq = open_sect->getOutgoingAtom(1); 209 | for (const Handle& con : conseq->getOutgoingSet()) 210 | { 211 | if (*con == *to_con) 212 | { 213 | // Wait, are these already connected? 214 | if (pair_any_links <= num_any_links(fm_sect, open_sect)) 215 | continue; 216 | if (1 < pair_any_links and 217 | pair_typed_links <= num_undirected_links(fm_sect, 218 | open_sect, linkty)) 219 | continue; 220 | to_sects.push_back(open_sect); 221 | } 222 | } 223 | } 224 | 225 | // Save it... 226 | _opensel._opensect[to_con] = to_sects; 227 | 228 | // Oh no, dead end! 229 | if (0 == to_sects.size()) return Handle::UNDEFINED; 230 | 231 | // There's only one. Pick it. 232 | if (1 == to_sects.size()) return to_sects[0]; 233 | 234 | // Create a discrete distribution. 235 | // Argh ... XXX FIXME ... the aggregator does NOT copy 236 | // values onto the assembled linkage, and so these will 237 | // always fail to have the weight key on them. That's OK, 238 | // for now, because copying the values it probably wasteful. 239 | // but still ... this results in a uniform distribution, 240 | // and so its ... ick. Imperfect. Perfect enemy of the good. 241 | #if 0 242 | std::vector pdf; 243 | for (const Handle& sect: to_sects) 244 | { 245 | FloatValuePtr fvp(FloatValueCast(sect->getValue(_weight_key))); 246 | if (fvp) pdf.push_back(fvp->value()[0]); 247 | else pdf.push_back(1.0); 248 | } 249 | #else 250 | std::vector pdf(to_sects.size(), 1.0); 251 | #endif 252 | std::discrete_distribution dist(pdf.begin(), pdf.end()); 253 | _opensel._opendi.emplace(std::make_pair(to_con, dist)); 254 | 255 | return to_sects[dist(rangen)]; 256 | } 257 | 258 | /// Return a section containing `to_con`. 259 | /// First try to attach to an existing open section. 260 | /// If that fails, then pick a new section from the lexis. 261 | Handle RandomCallback::select(const OdoFrame& frame, 262 | const Handle& fm_sect, size_t offset, 263 | const Handle& to_con) 264 | { 265 | // See if we can find other open connectors to connect to. 266 | if (_parms->connect_existing(frame)) 267 | { 268 | Handle open_sect = select_from_open(frame, fm_sect, offset, to_con); 269 | if (open_sect) return open_sect; 270 | } 271 | 272 | // Select from the dictionary... 273 | return select_from_lexis(frame, fm_sect, offset, to_con); 274 | } 275 | 276 | /// Create an undirected edge connecting the two points `fm_pnt` and 277 | /// `to_pnt`, using the connectors `fm_con` and `to_con`. The edge 278 | /// is "undirected" because a SetLink is used to hold the two 279 | /// end-points. Recall SetLinks are unordered links, so neither point 280 | /// can be identified as head or tail. 281 | Handle RandomCallback::make_link(const Handle& fm_con, 282 | const Handle& to_con, 283 | const Handle& fm_pnt, 284 | const Handle& to_pnt) 285 | { 286 | return create_undirected_link(fm_con, to_con, fm_pnt, to_pnt); 287 | } 288 | 289 | size_t RandomCallback::num_links(const Handle& fm_sect, 290 | const Handle& to_sect, 291 | const Handle& link_type) 292 | { 293 | return num_undirected_links(fm_sect, to_sect, link_type); 294 | } 295 | 296 | void RandomCallback::push_frame(const OdoFrame& frm) 297 | { 298 | _opensel_stack.push(_opensel); 299 | _opensel._opensect.clear(); 300 | _opensel._opendi.clear(); 301 | } 302 | 303 | void RandomCallback::pop_frame(const OdoFrame& frm) 304 | { 305 | _opensel = _opensel_stack.top(); _opensel_stack.pop(); 306 | } 307 | 308 | bool RandomCallback::step(const OdoFrame& frm) 309 | { 310 | _steps_taken ++; 311 | if (max_steps < _steps_taken) return false; 312 | if (max_solutions <= num_solutions()) return false; 313 | if (max_network_size < frm._linkage.size()) return false; 314 | if (max_depth < frm._nodo) return false; 315 | return true; 316 | // return _parms->step(frm); 317 | } 318 | 319 | void RandomCallback::solution(const OdoFrame& frm) 320 | { 321 | record_solution(frm); 322 | } 323 | 324 | Handle RandomCallback::get_solutions(void) 325 | { 326 | Handle results = CollectStyle::get_solutions(); 327 | 328 | // Populate the atomspace, only if there are results to report. 329 | if (0 < results->get_arity()) LinkStyle::save_work(_as); 330 | return results; 331 | } 332 | -------------------------------------------------------------------------------- /opencog/generate/RandomCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/RandomCallback.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_RANDOM_CALLBACK_H 23 | #define _OPENCOG_RANDOM_CALLBACK_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | namespace opencog 32 | { 33 | /** \addtogroup grp_generate 34 | * @{ 35 | */ 36 | 37 | /// Callback to provide sections (aka "puzzle pieces") to extend the 38 | /// current assembly. Under construction. Meant to draw pieces using 39 | /// a stochastic, random selection process. 40 | /// 41 | 42 | class RandomCallback : 43 | public GenerateCallback, 44 | private LinkStyle, 45 | private CollectStyle 46 | { 47 | private: 48 | Dictionary _dict; 49 | RandomParameters* _parms; 50 | Handle _weight_key; 51 | size_t _steps_taken; 52 | 53 | // ------------------------------------------- 54 | // Nucleation points. 55 | HandleSeqSeq _root_sections; 56 | std::vector> _root_dist; 57 | 58 | // ------------------------------------------- 59 | // Lexical selection 60 | Handle select_from_lexis(const OdoFrame&, 61 | const Handle&, size_t, 62 | const Handle&); 63 | 64 | // Cached normalized weighted chooser. Map of all 65 | // sections in the dictionary that contain this to-connector. 66 | // Used by `select()` to return the next attachable section. 67 | std::map> _distmap; 68 | 69 | // ------------------------------------------- 70 | Handle select_from_open(const OdoFrame&, 71 | const Handle&, size_t, 72 | const Handle&); 73 | struct OpenSelections 74 | { 75 | HandleSeqMap _opensect; 76 | 77 | // Chooser, selecting a one of the open sections the current frame. 78 | std::map> _opendi; 79 | }; 80 | 81 | OpenSelections _opensel; 82 | std::stack _opensel_stack; 83 | // ------------------------------------------- 84 | 85 | public: 86 | RandomCallback(AtomSpace*, const Dictionary&, RandomParameters&); 87 | virtual ~RandomCallback(); 88 | 89 | virtual void clear(AtomSpace*); 90 | void set_weight_key(const Handle& pred) { _weight_key = pred; } 91 | 92 | virtual void root_set(const HandleSet&); 93 | virtual HandleSet next_root(void); 94 | 95 | virtual HandleSeq joints(const Handle& con) { 96 | return _dict.joints(con); 97 | } 98 | virtual Handle select(const OdoFrame&, 99 | const Handle&, size_t, 100 | const Handle&); 101 | 102 | virtual Handle make_link(const Handle&, const Handle&, 103 | const Handle&, const Handle&); 104 | virtual size_t num_links(const Handle&, const Handle&, 105 | const Handle&); 106 | virtual void push_frame(const OdoFrame&); 107 | virtual void pop_frame(const OdoFrame&); 108 | 109 | virtual bool step(const OdoFrame&); 110 | virtual void solution(const OdoFrame&); 111 | virtual Handle get_solutions(void); 112 | }; 113 | 114 | 115 | /** @}*/ 116 | } // namespace opencog 117 | 118 | #endif // _OPENCOG_RANDOM_CALLBACK_H 119 | -------------------------------------------------------------------------------- /opencog/generate/RandomParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/RandomParameters.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_RANDOM_PARAMETERS_H 23 | #define _OPENCOG_RANDOM_PARAMETERS_H 24 | 25 | #include 26 | 27 | namespace opencog 28 | { 29 | /** \addtogroup grp_generate 30 | * @{ 31 | */ 32 | 33 | /// The RandomParameters class is used to provide configuration info 34 | /// to the RandomCallback class, constraining the ranges and 35 | /// distributions that get used. 36 | /// 37 | 38 | class RandomParameters 39 | { 40 | public: 41 | RandomParameters() {} 42 | virtual ~RandomParameters() {} 43 | 44 | /// Return true attempt connecting a pair of existing open sections, 45 | /// else return false to fish for a new, fresh puzzle-piece out of 46 | /// the lexis. Consistently returning true will maximally close off 47 | /// any open connectors without enlarging the network. Rturning false 48 | /// will always increase the size of the network. 49 | virtual bool connect_existing(const OdoFrame&) = 0; 50 | 51 | /// Return true to continue stepping the odometer. Called before 52 | /// taking an odometer step. Returning false will abort the 53 | /// current odometer. 54 | virtual bool step(const OdoFrame&) = 0; 55 | }; 56 | 57 | 58 | /** @}*/ 59 | } // namespace opencog 60 | 61 | #endif // _OPENCOG_RANDOM_PARAMETERS_H 62 | -------------------------------------------------------------------------------- /opencog/generate/SimpleCallback.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/SimpleCallback.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | 24 | #include "SimpleCallback.h" 25 | 26 | using namespace opencog; 27 | 28 | SimpleCallback::SimpleCallback(AtomSpace* as, const Dictionary& dict) 29 | : GenerateCallback(as), _dict(dict) 30 | { 31 | _steps_taken = 0; 32 | } 33 | 34 | SimpleCallback::~SimpleCallback() {} 35 | 36 | void SimpleCallback::clear(AtomSpace* scratch) 37 | { 38 | while (not _lexlit_stack.empty()) _lexlit_stack.pop(); 39 | while (not _opensel_stack.empty()) _opensel_stack.pop(); 40 | _lexlit.clear(); 41 | _opensel._opensect.clear(); 42 | _opensel._openit.clear(); 43 | _root_sections.clear(); 44 | _root_iters.clear(); 45 | _steps_taken = 0; 46 | CollectStyle::clear(); 47 | LinkStyle::clear(); 48 | LinkStyle::_point_set = point_set; 49 | LinkStyle::_scratch = scratch; 50 | } 51 | 52 | void SimpleCallback::root_set(const HandleSet& roots) 53 | { 54 | for (const Handle& point: roots) 55 | { 56 | _root_sections.push_back(_dict.entries(point)); 57 | _root_iters.push_back(_root_sections.back().begin()); 58 | } 59 | } 60 | 61 | /// Return the next unexplored set of root sections. This will 62 | /// exhaustively explore all permutations, unless earlier 63 | /// termination criteria are met. 64 | HandleSet SimpleCallback::next_root(void) 65 | { 66 | static HandleSet empty_set; 67 | size_t len = _root_iters.size(); 68 | if (len == 0) return empty_set; 69 | 70 | // Stop iterating if limits have been reached. 71 | if (max_steps < _steps_taken) return empty_set; 72 | if (max_solutions <= num_solutions()) return empty_set; 73 | 74 | // This implements a kind-of odometer. It cycles through each 75 | // of the sections for each of the starting points. It does 76 | // this by keeping a sequence of iterators, bumping each 77 | // iterator in turn, when the previous one rolls over. 78 | HandleSet starters; 79 | for (size_t i=0; igetOutgoingAtom(0); 176 | HandleSeq to_sects; 177 | for (const Handle& open_sect : frame._open_sections) 178 | { 179 | const Handle& conseq = open_sect->getOutgoingAtom(1); 180 | for (const Handle& con : conseq->getOutgoingSet()) 181 | { 182 | if (*con == *to_con) 183 | { 184 | // Wait, are these already connected? 185 | if (pair_any_links <= num_any_links(fm_sect, open_sect)) 186 | continue; 187 | if (1 < pair_any_links and 188 | pair_typed_links <= num_undirected_links(fm_sect, 189 | open_sect, linkty)) 190 | continue; 191 | to_sects.push_back(open_sect); 192 | } 193 | } 194 | } 195 | 196 | // There aren't any open sections ... 197 | if (0 == to_sects.size()) return Handle::UNDEFINED; 198 | 199 | // Start iterating over the sections that contain to_con. 200 | _opensel._openit[to_con] = 0; 201 | return check_self(to_sects, fm_sect, to_con, 0); 202 | } 203 | 204 | /// Return a section containing `to_con`. 205 | /// First try to attach to an existing open section. 206 | /// If that fails, then pick a new section from the lexis. 207 | Handle SimpleCallback::select(const OdoFrame& frame, 208 | const Handle& fm_sect, size_t offset, 209 | const Handle& to_con) 210 | { 211 | // See if we can find other open connectors to connect to. 212 | Handle open_sect = select_from_open(frame, fm_sect, offset, to_con); 213 | if (open_sect) return open_sect; 214 | 215 | // If this is non-empty, the the odometer rolled over. 216 | if (_opensel._opensect.find(to_con) != _opensel._opensect.end()) 217 | return Handle::UNDEFINED; 218 | 219 | // Select from the dictionary... 220 | return select_from_lexis(frame, fm_sect, offset, to_con); 221 | } 222 | 223 | /// Create an undirected edge connecting the two points `fm_pnt` and 224 | /// `to_pnt`, using the connectors `fm_con` and `to_con`. The edge 225 | /// is "undirected" because a SetLink is used to hold the two 226 | /// end-points. Recall SetLinks are unordered links, so neither point 227 | /// can be identified as head or tail. 228 | Handle SimpleCallback::make_link(const Handle& fm_con, 229 | const Handle& to_con, 230 | const Handle& fm_pnt, 231 | const Handle& to_pnt) 232 | { 233 | return create_undirected_link(fm_con, to_con, fm_pnt, to_pnt); 234 | } 235 | 236 | size_t SimpleCallback::num_links(const Handle& fm_sect, 237 | const Handle& to_sect, 238 | const Handle& link_type) 239 | { 240 | return num_undirected_links(fm_sect, to_sect, link_type); 241 | } 242 | 243 | void SimpleCallback::push_frame(const OdoFrame& frm) 244 | { 245 | _opensel_stack.push(_opensel); 246 | _opensel._opensect.clear(); 247 | _opensel._openit.clear(); 248 | } 249 | 250 | void SimpleCallback::pop_frame(const OdoFrame& frm) 251 | { 252 | _opensel = _opensel_stack.top(); _opensel_stack.pop(); 253 | } 254 | 255 | void SimpleCallback::push_odometer(const Odometer& odo) 256 | { 257 | _lexlit_stack.push(_lexlit); 258 | _lexlit.clear(); 259 | } 260 | 261 | void SimpleCallback::pop_odometer(const Odometer& odo) 262 | { 263 | _lexlit = _lexlit_stack.top(); _lexlit_stack.pop(); 264 | } 265 | 266 | bool SimpleCallback::step(const OdoFrame& frm) 267 | { 268 | _steps_taken ++; 269 | if (max_steps < _steps_taken) return false; 270 | if (max_solutions <= num_solutions()) return false; 271 | if (max_network_size < frm._linkage.size()) return false; 272 | if (max_depth < frm._nodo) return false; 273 | return true; 274 | } 275 | 276 | void SimpleCallback::solution(const OdoFrame& frm) 277 | { 278 | record_solution(frm); 279 | } 280 | 281 | Handle SimpleCallback::get_solutions(void) 282 | { 283 | Handle results = CollectStyle::get_solutions(); 284 | 285 | // Populate the atomspace, only if there are results to report. 286 | if (0 < results->get_arity()) LinkStyle::save_work(_as); 287 | return results; 288 | } 289 | -------------------------------------------------------------------------------- /opencog/generate/SimpleCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * opencog/generate/SimpleCallback.h 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #ifndef _OPENCOG_SIMPLE_CALLBACK_H 23 | #define _OPENCOG_SIMPLE_CALLBACK_H 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace opencog 33 | { 34 | /** \addtogroup grp_generate 35 | * @{ 36 | */ 37 | 38 | //! a map from handle to unsigned 39 | typedef Counter HandleUCounter; 40 | 41 | /// Callback to provide sections (aka "puzzle pieces") to extend the 42 | /// current assembly. The current implementation here is deterministic, 43 | /// although otherwise arbitrary, when more than one choice is possible. 44 | /// 45 | /// The proposed connection always attempts to join together two open 46 | /// connectors, if that is possible, else drawing a new section from a 47 | /// dictionary (lexis). 48 | /// 49 | /// This behavior is useful only in certain small, limited cases, where 50 | /// the lexis has been designed to have only a finite number of possible 51 | /// solutions. 52 | 53 | class SimpleCallback : 54 | public GenerateCallback, 55 | private LinkStyle, 56 | private CollectStyle 57 | { 58 | private: 59 | Dictionary _dict; 60 | size_t _steps_taken; 61 | 62 | // ------------------------------------------- 63 | // Nucleation points. 64 | HandleSeqSeq _root_sections; 65 | std::vector _root_iters; 66 | 67 | // ------------------------------------------- 68 | // Lexical selection 69 | Handle select_from_lexis(const OdoFrame&, 70 | const Handle&, size_t, 71 | const Handle&); 72 | 73 | // Iterator, pointing from a to-connector, to a list of 74 | // all sections in the dictionary that contain this to-connector. 75 | // Used by `select()` to return the next attachable section. 76 | HandleUCounter _lexlit; 77 | 78 | // Stack of iterators into the lists of sections. 79 | std::stack _lexlit_stack; 80 | 81 | // ------------------------------------------- 82 | Handle select_from_open(const OdoFrame&, 83 | const Handle&, size_t, 84 | const Handle&); 85 | Handle check_self(const HandleSeq&, const Handle&, 86 | const Handle&, size_t); 87 | struct OpenSelections 88 | { 89 | HandleSeqMap _opensect; 90 | 91 | // Iterator, pointing from a to-connector, to a list of 92 | // all open sections in the current frame. 93 | HandleUCounter _openit; 94 | }; 95 | 96 | OpenSelections _opensel; 97 | std::stack _opensel_stack; 98 | // ------------------------------------------- 99 | 100 | public: 101 | SimpleCallback(AtomSpace*, const Dictionary&); 102 | virtual ~SimpleCallback(); 103 | 104 | virtual void clear(AtomSpace*); 105 | virtual bool step(const OdoFrame&); 106 | virtual HandleSeq joints(const Handle& con) { 107 | return _dict.joints(con); 108 | } 109 | 110 | virtual void root_set(const HandleSet&); 111 | virtual HandleSet next_root(void); 112 | 113 | virtual Handle select(const OdoFrame&, 114 | const Handle&, size_t, 115 | const Handle&); 116 | 117 | virtual Handle make_link(const Handle&, const Handle&, 118 | const Handle&, const Handle&); 119 | virtual size_t num_links(const Handle&, const Handle&, 120 | const Handle&); 121 | virtual void push_frame(const OdoFrame&); 122 | virtual void pop_frame(const OdoFrame&); 123 | virtual void push_odometer(const Odometer&); 124 | virtual void pop_odometer(const Odometer&); 125 | virtual void solution(const OdoFrame&); 126 | virtual Handle get_solutions(void); 127 | }; 128 | 129 | 130 | /** @}*/ 131 | } // namespace opencog 132 | 133 | #endif // _OPENCOG_SIMPLE_CALLBACK_H 134 | -------------------------------------------------------------------------------- /opencog/guile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Boilerplate for the goofy config file nonsense. Argh. 3 | DECLARE_GUILE_CONFIG_TARGET(SCM_CONFIG "opencog generate-config" "GENERATE_TEST") 4 | 5 | ADD_SUBDIRECTORY (modules) 6 | 7 | # More search-path file nonsense. I hate this. 8 | WRITE_GUILE_CONFIG( 9 | ${GUILE_BIN_DIR}/opencog/generate-config.scm 10 | SCM_CONFIG TRUE) 11 | WRITE_GUILE_CONFIG( 12 | ${GUILE_BIN_DIR}/opencog/generate-config-installable.scm 13 | SCM_CONFIG FALSE) 14 | INSTALL(FILES 15 | ${GUILE_BIN_DIR}/opencog/generate-config-installable.scm 16 | DESTINATION ${GUILE_SITE_DIR}/opencog 17 | RENAME generate-config.scm) 18 | -------------------------------------------------------------------------------- /opencog/guile/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | ADD_LIBRARY (guile-generate SHARED 3 | GenerateSCM.cc 4 | ) 5 | 6 | TARGET_LINK_LIBRARIES(guile-generate 7 | generate 8 | ${ATOMSPACE_LIBRARIES} 9 | ${COGUTIL_LIBRARY} 10 | ) 11 | ADD_GUILE_EXTENSION(SCM_CONFIG generate "opencog-ext-path-generate") 12 | 13 | INSTALL (TARGETS guile-generate 14 | EXPORT GenerateTargets 15 | DESTINATION "lib${LIB_DIR_SUFFIX}/opencog" 16 | ) 17 | -------------------------------------------------------------------------------- /opencog/guile/modules/GenerateSCM.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * GenerateSCM.cc 3 | * 4 | * Copyright (C) 2020 Linas Vepstas 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License v3 as 8 | * published by the Free Software Foundation and including the exceptions 9 | * at http://opencog.org/wiki/Licenses 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program; if not, write to: 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | using namespace opencog; 34 | namespace opencog { 35 | 36 | /** 37 | * Scheme wrapper for the generation code. Quick Hack. 38 | * Mediocre, ugly-ish API. XXX FIXME. 39 | */ 40 | 41 | class GenerateSCM : public ModuleWrap 42 | { 43 | protected: 44 | virtual void init(); 45 | 46 | Handle do_random_aggregate(Handle, Handle, Handle, Handle, Handle); 47 | Handle do_simple_aggregate(Handle, Handle, Handle, Handle); 48 | 49 | public: 50 | GenerateSCM(); 51 | }; 52 | 53 | // ---------------------------------------------------------------- 54 | /// Decode parameters. A bit ad-hoc, right now. 55 | /// 56 | /// The expected encoding for a paramter is 57 | /// (StateLink 58 | /// (MemberLink (PredicateNode "param") (ConceptNode "class")) 59 | /// (Atom "value")) 60 | /// where "param" is a well-known parameter, "class" is the particular 61 | /// grouping of paramters we care about, and `(Atom "value")` is the 62 | /// value for that parameter. 63 | /// 64 | void decode_param(const Handle& membli, 65 | GenerateCallback& cb, 66 | BasicParameters& basic) 67 | { 68 | Handle statli = StateLink::get_link(membli); 69 | if (nullptr == statli) return; 70 | 71 | const Handle& pname = membli->getOutgoingAtom(0); 72 | const Handle& pval = statli->getOutgoingAtom(1); 73 | 74 | // We expect the parameter name in a PredicateNode. Ah heck, 75 | // any node will do ... 76 | if (not pname->is_node()) 77 | throw InvalidParamException(TRACE_INFO, 78 | "Expecting a parameter name, got %s", 79 | pname->to_short_string()); 80 | const std::string& sname = pname->get_name(); 81 | 82 | // We expect a node. Well, any Atom, so we don't check. 83 | if (0 == sname.compare("*-point-set-anchor-*")) 84 | { 85 | cb.point_set = pval; 86 | return; 87 | } 88 | 89 | // All parameters below here expect a NumberNode 90 | if (not nameserver().isA(pval->get_type(), NUMBER_NODE)) 91 | throw InvalidParamException(TRACE_INFO, 92 | "Expecting a numerical value, got %s", 93 | pval->to_short_string()); 94 | double dval = NumberNodeCast(pval)->get_value(); 95 | 96 | if (0 == sname.compare("*-max-solutions-*")) 97 | cb.max_solutions = dval; 98 | 99 | else if (0 == sname.compare("*-max-steps-*")) 100 | cb.max_steps = dval; 101 | 102 | else if (0 == sname.compare("*-max-depth-*")) 103 | cb.max_depth = dval; 104 | 105 | else if(0 == sname.compare("*-max-network-size-*")) 106 | cb.max_network_size = dval; 107 | 108 | else if (0 == sname.compare("*-close-fraction-*")) 109 | basic.close_fraction = dval; 110 | } 111 | 112 | /// Decode all parameters attached to an anchor point. 113 | /// See `decode_param()` above. This is just a loop. 114 | void decode_params(const Handle& param_anchor, 115 | GenerateCallback& cb, 116 | BasicParameters& basic) 117 | { 118 | // Decode the parameters. One at a time. 119 | HandleSeq memps = param_anchor->getIncomingSetByType(MEMBER_LINK); 120 | for (const Handle& membli : memps) 121 | { 122 | if (*membli->getOutgoingAtom(1) != *param_anchor) continue; 123 | decode_param(membli, cb, basic); 124 | } 125 | } 126 | 127 | // ---------------------------------------------------------------- 128 | /// Pull the lexis out of the atomspace. 129 | Dictionary decode_lexis(AtomSpace* as, Handle poles, Handle lexis) 130 | { 131 | Dictionary dict(as); 132 | 133 | // Add the poles to the dictionary. 134 | HandleSeq poleset = poles->getIncomingSetByType(MEMBER_LINK); 135 | for (const Handle& membli : poleset) 136 | { 137 | if (*membli->getOutgoingAtom(1) != *poles) continue; 138 | 139 | const Handle& pole_pair = membli->getOutgoingAtom(0); 140 | const Handle& p0 = pole_pair->getOutgoingAtom(0); 141 | const Handle& p1 = pole_pair->getOutgoingAtom(1); 142 | dict.add_pole_pair(p0, p1); 143 | 144 | if (nameserver().isA(pole_pair->get_type(), UNORDERED_LINK) 145 | and *p0 != *p1) 146 | { 147 | dict.add_pole_pair(p1, p0); 148 | } 149 | } 150 | 151 | // Add the sections to the dictionary. 152 | HandleSeq sects = lexis->getIncomingSetByType(MEMBER_LINK); 153 | for (const Handle& membli : sects) 154 | { 155 | if (*membli->getOutgoingAtom(1) != *lexis) continue; 156 | dict.add_to_lexis(membli->getOutgoingAtom(0)); 157 | } 158 | return dict; 159 | } 160 | 161 | // ---------------------------------------------------------------- 162 | /// C++ implementation of the scheme function. 163 | Handle GenerateSCM::do_random_aggregate(Handle poles, 164 | Handle lexis, 165 | Handle weight, 166 | Handle params, 167 | Handle root) 168 | { 169 | AtomSpacePtr asp = SchemeSmob::ss_get_env_as("cog-random-aggregate"); 170 | AtomSpace* as = asp.get(); 171 | 172 | Dictionary dict(decode_lexis(as, poles, lexis)); 173 | 174 | BasicParameters basic; 175 | RandomCallback cb(as, dict, basic); 176 | cb.set_weight_key(weight); 177 | 178 | // Decode the parameters. 179 | decode_params(params, cb, basic); 180 | 181 | Aggregate ag(as); 182 | ag.aggregate({root}, cb); 183 | 184 | Handle result = cb.get_solutions(); 185 | result = as->add_atom(result); 186 | return result; 187 | } 188 | 189 | // ---------------------------------------------------------------- 190 | /// C++ implementation of the scheme function. 191 | Handle GenerateSCM::do_simple_aggregate(Handle poles, 192 | Handle lexis, 193 | Handle params, 194 | Handle root) 195 | { 196 | AtomSpacePtr asp = SchemeSmob::ss_get_env_as("cog-simple-aggregate"); 197 | AtomSpace* as = asp.get(); 198 | 199 | Dictionary dict(decode_lexis(as, poles, lexis)); 200 | 201 | BasicParameters basic; 202 | SimpleCallback cb(as, dict); 203 | decode_params(params, cb, basic); 204 | 205 | Aggregate ag(as); 206 | ag.aggregate({root}, cb); 207 | 208 | Handle result = cb.get_solutions(); 209 | result = as->add_atom(result); 210 | return result; 211 | } 212 | 213 | // ---------------------------------------------------------------- 214 | } /*end of namespace opencog*/ 215 | 216 | GenerateSCM::GenerateSCM() : ModuleWrap("opencog generate") {} 217 | 218 | /// This is called while (opencog generate) is the current module. 219 | /// Thus, all the definitions below happen in that module. 220 | void GenerateSCM::init(void) 221 | { 222 | define_scheme_primitive("cog-random-aggregate", 223 | &GenerateSCM::do_random_aggregate, this, "generate"); 224 | define_scheme_primitive("cog-simple-aggregate", 225 | &GenerateSCM::do_simple_aggregate, this, "generate"); 226 | } 227 | 228 | extern "C" { 229 | void opencog_generate_init(void); 230 | }; 231 | 232 | void opencog_generate_init(void) 233 | { 234 | static GenerateSCM generate_scm; 235 | generate_scm.module_init(); 236 | } 237 | 238 | // --------------------- END OF FILE ----------------------- 239 | -------------------------------------------------------------------------------- /opencog/scm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | ADD_GUILE_MODULE (FILES 3 | opencog/generate.scm 4 | opencog/generate/gml-export.scm 5 | MODULE_DESTINATION "${GUILE_SITE_DIR}" 6 | ) 7 | -------------------------------------------------------------------------------- /opencog/scm/opencog/generate.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; OpenCog Generation module 3 | ; 4 | 5 | (define-module (opencog generate)) 6 | 7 | (use-modules (opencog)) 8 | (use-modules (opencog generate-config)) 9 | (load-extension 10 | (string-append opencog-ext-path-generate "libguile-generate") 11 | "opencog_generate_init") 12 | 13 | (export 14 | cog-random-aggregate 15 | cog-simple-aggregate 16 | ) 17 | 18 | (include-from-path "opencog/generate/gml-export.scm") 19 | 20 | ; ---------------------------------------------------------- 21 | ; Documentation for the functions implemented as C++ code 22 | (set-procedure-property! cog-random-aggregate 'documentation 23 | " 24 | cog-random-aggregate POLES LEXIS WEIGHT PARAMS ROOT 25 | 26 | Aggregate a random network around ROOT, using the sections defined 27 | in the LEXIS, selected with weighting given by WEIGHT. The 28 | connectable enpoints are given by POLES. Some parameters 29 | controlling the search are in PARAMS. 30 | 31 | See the example `basic-network.scm` for more details. 32 | ") 33 | 34 | (set-procedure-property! cog-simple-aggregate 'documentation 35 | " 36 | cog-simple-aggregate POLES LEXIS PARAMS ROOT 37 | 38 | Aggregate a simple network around ROOT, using the sections defined 39 | in the LEXIS, and the connectable enpoints given by POLES. Some 40 | parameters controlling the search are in PARAMS. 41 | 42 | See the examples `dict-tree.scm` and `dict-loop.scm` for more details. 43 | ") 44 | -------------------------------------------------------------------------------- /opencog/scm/opencog/generate/gml-export.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; gml-export.scm 3 | ; 4 | ; Quick-n-dirty hack to export networks to GML - Graph Modeling Language 5 | ; See https://en.wikipedia.org/wiki/Graph_Modelling_Language 6 | ; 7 | 8 | (use-modules (srfi srfi-1)) 9 | (use-modules (opencog) (opencog uuid)) 10 | 11 | (define (graph-to-nodes GRAPH) 12 | ; A list of just the points 13 | (define pt-list 14 | (map (lambda (sect) (gar sect)) 15 | (cog-outgoing-set GRAPH))) 16 | 17 | (fold 18 | (lambda (point str) 19 | (string-concatenate (list 20 | "\tnode [\n" 21 | "\t\tid " 22 | (format #f "~D" (cog-assign-uuid point)) 23 | "\n" 24 | "\t\tlabel \"" 25 | (cog-name point) 26 | "\"\n" 27 | "\t]\n" 28 | str 29 | ))) 30 | "" 31 | ; A list of the unique points in the set. 32 | (delete-dup-atoms pt-list) 33 | ) 34 | ) 35 | 36 | ; Export the edge list in the graph. 37 | ; This is trickier than it seems, for several reasons. First, every 38 | ; edge occurs twice: once in the from-section, and once in the 39 | ; to-section. Thus we have to de-duplicate these, as we don't want to 40 | ; draw two edges when there is actually only one. Second, it can 41 | ; happen that there are two (or more) parallel edges between two 42 | ; vertexes. Thus, we want to deduplicate those also, while correctly 43 | ; exposing the parallelism. Thus, we have to maintain a list of 44 | ; de-duplicators: one that knows how to de-dupe single edges, and 45 | ; a second one that knows how to de-dupe double-edges, a third that 46 | ; will dedupe triple-edges, and so on. The code below works, but 47 | ; might be hard to understand because of this. 48 | (define (graph-to-edges GRAPH) 49 | 50 | ; The list of de-duplication sets. 51 | ; The n'th item in this list is a function that knows how to 52 | ; deduplicate n parallel edges. 53 | (define dupe-sets '()) 54 | (define is-duplicate? (make-atom-set)) 55 | 56 | ; Filter - remove edges we've seen already, 57 | ; but keep the duplicates. This is tail-recursive. 58 | (define (filter-dupe elist result duper-list) 59 | 60 | ; Find the de-duplicator for n edges. It will be 61 | ; the first one in the passed list, if any. 62 | (define duper? 63 | (if (null? duper-list) (make-atom-set) (car duper-list))) 64 | 65 | ; The rest of the de-duplicator list. 66 | (define duper-rest 67 | (if (null? duper-list) '() (cdr duper-list))) 68 | 69 | ; Oh dear, if there wasn't one, then save it. 70 | ; Append, so that its in the n'th place. 71 | (if (null? duper-list) 72 | (set! dupe-sets (append dupe-sets (list duper?)))) 73 | 74 | ; At last -- the tail recursion. 75 | (if (null? elist) 76 | result 77 | (filter-dupe 78 | (keep-duplicate-atoms elist) 79 | (append 80 | (filter 81 | (lambda (edge) (not (duper? edge))) 82 | elist) 83 | result) 84 | duper-rest))) 85 | 86 | ; A list of just the edges. The `(gdr sect)` is the disjunct. 87 | ; We do two fancy things here: we de-duplicate edges reported 88 | ; in other sections; however, if a given section has the same 89 | ; edge several times, we allow that. 90 | (define edge-list 91 | (append-map 92 | (lambda (sect) 93 | ; Remove unwanted edges from the list. 94 | (filter-dupe 95 | (cog-outgoing-set (gdr sect)) 96 | '() 97 | dupe-sets)) 98 | (cog-outgoing-set GRAPH))) 99 | 100 | (fold 101 | (lambda (edge str) 102 | (string-concatenate (list 103 | "\tedge [\n" 104 | "\t\tsource " 105 | (format #f "~D" (cog-assign-uuid (gadr edge))) 106 | "\n\t\ttarget " 107 | (format #f "~D" (cog-assign-uuid (gddr edge))) 108 | "\n\t\tlabel \"" 109 | (cog-name (gar edge)) 110 | "\"\n\t]\n" 111 | str 112 | ))) 113 | "" 114 | ; A list of the edges we want to draw. 115 | edge-list 116 | ) 117 | ) 118 | 119 | (define-public (export-to-gml GRAPH-SET) 120 | " 121 | export-to-gml GRAPH-SET 122 | 123 | Export the GRAPH-SET - a SetLink wrapping one or more graphs, 124 | to a UTF-8 encoded text string in GML - Graph Modeling Language 125 | format. 126 | " 127 | (define graph-id 0) 128 | (fold 129 | (lambda (grph str) 130 | (set! graph-id (+ 1 graph-id)) 131 | (string-concatenate (list 132 | "graph [\n" 133 | "\tcomment \"Created by opencog generate\"\n" 134 | "\tdirected 1\n" 135 | (format #f "\tlabel \"placeholder ~D\"\n" graph-id) 136 | (format #f "\tid ~D\n" graph-id) 137 | (graph-to-nodes grph) 138 | (graph-to-edges grph) 139 | "]\n" 140 | str))) 141 | "" 142 | (cog-outgoing-set GRAPH-SET)) 143 | ) 144 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ENABLE_TESTING() 2 | INCLUDE(AddCxxtest) 3 | 4 | ADD_DEFINITIONS(-DPROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" 5 | -DPROJECT_BINARY_DIR="${CMAKE_BINARY_DIR}") 6 | 7 | # All tests should load the atomspace scm from the build dir, unless the scm 8 | # file is specific to the test (this variable is used by ADD_CXXTEST) 9 | SET(GUILE_LOAD_PATH "${PROJECT_BINARY_DIR}/opencog/scm") 10 | 11 | # Perform tests in component-dependency order, as much as possible. 12 | # For example, since most things depends on the atomspace, 13 | # its is tested first. 14 | IF (CXXTEST_FOUND) 15 | 16 | ADD_SUBDIRECTORY (generate) 17 | 18 | ENDIF (CXXTEST_FOUND) 19 | -------------------------------------------------------------------------------- /tests/generate/AggregationUTest.cxxtest: -------------------------------------------------------------------------------- 1 | /* 2 | * AggregationUTest.cxxtest 3 | * 4 | * Basic does-it-work? unit test. Explores some very very simple 5 | * natural-language grammars that generate a handful (less than a dozen) 6 | * different sentences. No combinatoric explosions, no stochastic 7 | * elements. Uses only the SimpleCallback for everything. 8 | * 9 | * Copyright (C) 2020 Linas Vepstas 10 | * All Rights Reserved 11 | * SPDX-License-Identifier: AGPL-3.0-or-later 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | using namespace opencog; 23 | 24 | #define al as->add_link 25 | #define an as->add_node 26 | 27 | class AggregationUTest: public CxxTest::TestSuite 28 | { 29 | private: 30 | AtomSpacePtr asp; 31 | AtomSpace* as; 32 | SchemeEval* eval; 33 | Aggregate* ag; 34 | Dictionary* dict; 35 | 36 | public: 37 | AggregationUTest(); 38 | ~AggregationUTest(); 39 | 40 | void setUp(); 41 | void tearDown(); 42 | 43 | void setup_dict(); 44 | 45 | void test_hello(); 46 | void test_tree(); 47 | void test_loop(); 48 | void test_biloop(); 49 | void test_quad(); 50 | void test_biquad(); 51 | void test_triquad(); 52 | void test_mixed(); 53 | void test_multi_root(); 54 | }; 55 | 56 | AggregationUTest::AggregationUTest() 57 | { 58 | logger().set_level(Logger::DEBUG); 59 | logger().set_print_to_stdout_flag(true); 60 | logger().set_timestamp_flag(false); 61 | } 62 | 63 | AggregationUTest::~AggregationUTest() 64 | { 65 | logger().info("Completed running AggregationUTest"); 66 | 67 | // erase the log file if no assertions failed 68 | if (!CxxTest::TestTracker::tracker().suiteFailed()) 69 | std::remove(logger().get_filename().c_str()); 70 | else 71 | { 72 | logger().info("AggregationUTest failed!"); 73 | logger().flush(); 74 | } 75 | } 76 | 77 | void AggregationUTest::setUp() 78 | { 79 | asp = createAtomSpace(); 80 | as = asp.get(); 81 | eval = new SchemeEval(as); 82 | eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")"); 83 | ag = new Aggregate(as); 84 | } 85 | 86 | void AggregationUTest::tearDown() 87 | { 88 | delete ag; 89 | delete eval; 90 | } 91 | 92 | void AggregationUTest::setup_dict() 93 | { 94 | dict = new Dictionary(as); 95 | 96 | // The directions to connect. 97 | Handle plus = an(CONNECTOR_DIR_NODE, "+"); 98 | Handle minus = an(CONNECTOR_DIR_NODE, "-"); 99 | dict->add_pole_pair(plus, minus); 100 | dict->add_pole_pair(minus, plus); 101 | 102 | // The lexis to use 103 | HandleSet lex; 104 | as->get_handles_by_type(lex, SECTION); 105 | dict->add_to_lexis(lex); 106 | } 107 | 108 | // Single generation: "Hello world" 109 | void AggregationUTest::test_hello() 110 | { 111 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 112 | 113 | eval->eval("(load-from-path \"tests/generate/dict-helloworld.scm\")"); 114 | Handle wall = eval->eval_h("left-wall"); 115 | 116 | setup_dict(); 117 | SimpleCallback cb(as, *dict); 118 | ag->aggregate({wall}, cb); 119 | Handle result = cb.get_solutions(); 120 | 121 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 122 | 123 | // We expect the solution to be LEFT_WALL --W--> Hello --OH--> world 124 | // printf("Hello-world result is %s\n", result->to_string().c_str()); 125 | logger().debug("Expecting 1 solution, got %d", result->get_arity()); 126 | TSM_ASSERT("Bad result set!", result->get_arity() == 1); 127 | Handle soln = result->getOutgoingAtom(0); 128 | 129 | TSM_ASSERT("Bad section!", soln->get_arity() == 3); 130 | 131 | logger().debug("END TEST: %s", __FUNCTION__); 132 | } 133 | 134 | // Four generations: "John/Mary saw a dog/cat", as an acyclic tree. 135 | void AggregationUTest::test_tree() 136 | { 137 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 138 | 139 | eval->eval("(load-from-path \"tests/generate/dict-tree.scm\")"); 140 | Handle wall = eval->eval_h("left-wall"); 141 | 142 | setup_dict(); 143 | SimpleCallback cb(as, *dict); 144 | 145 | ag->aggregate({wall}, cb); 146 | Handle result = cb.get_solutions(); 147 | 148 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 149 | 150 | // We expect the solution to be loop-free (acyclic) 151 | // +----O-----+ 152 | // +--->W---+---S--+ +--D--+ 153 | // | | | | | 154 | // LEFT-WALL John saw a cat 155 | // 156 | // printf("Tree result is %s\n", result->to_string().c_str()); 157 | 158 | logger().debug("Expecting 4 solutions, got %d", result->get_arity()); 159 | TSM_ASSERT("Bad result set!", result->get_arity() == 4); 160 | 161 | // All of the solutions should be the same size, substituting 162 | // only John/Mary and dog/cat in the parse tree. 163 | int cnt = 0; 164 | for (const Handle& soln: result->getOutgoingSet()) 165 | { 166 | logger().debug(" Soln %d expecting 5 words, got %d", 167 | ++cnt, soln->get_arity()); 168 | TSM_ASSERT("Bad section!", soln->get_arity() == 5); 169 | } 170 | 171 | logger().debug("END TEST: %s", __FUNCTION__); 172 | } 173 | 174 | // Four generations: "John/Mary saw a dog/cat", as graph w/ one cycle. 175 | void AggregationUTest::test_loop() 176 | { 177 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 178 | 179 | eval->eval("(load-from-path \"tests/generate/dict-loop.scm\")"); 180 | Handle wall = eval->eval_h("left-wall"); 181 | 182 | setup_dict(); 183 | SimpleCallback cb(as, *dict); 184 | 185 | ag->aggregate({wall}, cb); 186 | Handle result = cb.get_solutions(); 187 | 188 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 189 | 190 | // We expect the solution to have a loop 191 | // +-------WV------+----O-----+ 192 | // +--->W---+---S--+ +--D--+ 193 | // | | | | | 194 | // LEFT-WALL John saw a cat 195 | // 196 | // printf("Loop result is %s\n", result->to_string().c_str()); 197 | printf("Loop result size is %lu expecting 4\n", result->get_arity()); 198 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 199 | 200 | // All of the solutions should be the same size, substituting 201 | // only John/Mary and dog/cat in the parse. 202 | int cnt = 0; 203 | for (const Handle& soln: result->getOutgoingSet()) 204 | { 205 | logger().debug(" Soln %d expecting 5 words, got %d", 206 | ++cnt, soln->get_arity()); 207 | TSM_ASSERT("Bad section!", soln->get_arity() == 5); 208 | } 209 | 210 | logger().debug("END TEST: %s", __FUNCTION__); 211 | } 212 | 213 | // Four generations: "John thinks Mary saw a bird", and v.v. as graph 214 | // w/ two cycles. 215 | void AggregationUTest::test_biloop() 216 | { 217 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 218 | 219 | eval->eval("(load-from-path \"tests/generate/dict-biloop.scm\")"); 220 | Handle wall = eval->eval_h("left-wall"); 221 | 222 | setup_dict(); 223 | SimpleCallback cb(as, *dict); 224 | 225 | ag->aggregate({wall}, cb); 226 | Handle result = cb.get_solutions(); 227 | 228 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 229 | 230 | // We expect the solution to have two loops 231 | // 232 | // +------WV-----+------CV----+--O---+ 233 | // +---W---+--S--+--Ce--+--S--+ +-D-+ 234 | // | | | | | | | 235 | // LEFT-WALL Mary thinks John saw a bird 236 | // 237 | // printf("Loop result is %s\n", result->to_string().c_str()); 238 | printf("Loop result size is %lu expecting 4\n", result->get_arity()); 239 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 240 | 241 | // All of the solutions should be the same size, substituting 242 | // only John/Mary being swapped in the parse. 243 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 244 | int cnt = 0; 245 | for (const Handle& soln: result->getOutgoingSet()) 246 | { 247 | logger().debug(" Soln %d expecting 7 words, got %d", 248 | ++cnt, soln->get_arity()); 249 | TSM_ASSERT("Bad section!", soln->get_arity() == 7); 250 | } 251 | 252 | logger().debug("END TEST: %s", __FUNCTION__); 253 | } 254 | 255 | // Four generations: "Mary could see the dog", and v.v. as graph 256 | // w/ one quadrilateral cycle. 257 | void AggregationUTest::test_quad() 258 | { 259 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 260 | 261 | eval->eval("(load-from-path \"tests/generate/dict-quad.scm\")"); 262 | Handle wall = eval->eval_h("left-wall"); 263 | 264 | setup_dict(); 265 | SimpleCallback cb(as, *dict); 266 | 267 | ag->aggregate({wall}, cb); 268 | Handle result = cb.get_solutions(); 269 | 270 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 271 | 272 | // We expect the solution to have one quad 273 | // 274 | // +----------WV--------+----O-----+ 275 | // +----W---+--S--+--I--+ +--D--+ 276 | // | | | | | | 277 | // LEFT-WALL Mary could see the cat 278 | // 279 | // printf("Loop result is %s\n", result->to_string().c_str()); 280 | printf("Loop result size is %lu expecting 4\n", result->get_arity()); 281 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 282 | 283 | // All of the solutions should be the same size, substituting 284 | // only John/Mary and see/hear being swapped in the parse. 285 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 286 | int cnt = 0; 287 | for (const Handle& soln: result->getOutgoingSet()) 288 | { 289 | logger().debug(" Soln %d expecting 6 words, got %d", 290 | ++cnt, soln->get_arity()); 291 | TSM_ASSERT("Bad section!", soln->get_arity() == 6); 292 | } 293 | 294 | logger().debug("END TEST: %s", __FUNCTION__); 295 | } 296 | 297 | // Four generations: "Mary might think that John could fall", and v.v. 298 | // as graph w/ two quadrilateral cycles. 299 | void AggregationUTest::test_biquad() 300 | { 301 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 302 | 303 | eval->eval("(load-from-path \"tests/generate/dict-biquad.scm\")"); 304 | Handle wall = eval->eval_h("left-wall"); 305 | 306 | setup_dict(); 307 | SimpleCallback cb(as, *dict); 308 | 309 | ag->aggregate({wall}, cb); 310 | Handle result = cb.get_solutions(); 311 | 312 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 313 | 314 | // We expect the solution to have two quads 315 | // 316 | // printf("Loop result is %s\n", result->to_string().c_str()); 317 | printf("Loop result size is %lu expecting 4\n", result->get_arity()); 318 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 319 | 320 | // All of the solutions should be the same size, substituting 321 | // only might/could in the parse. 322 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 323 | int cnt = 0; 324 | for (const Handle& soln: result->getOutgoingSet()) 325 | { 326 | logger().debug(" Soln %d expecting 7 words, got %d", 327 | ++cnt, soln->get_arity()); 328 | TSM_ASSERT("Bad section!", soln->get_arity() == 7); 329 | } 330 | 331 | logger().debug("END TEST: %s", __FUNCTION__); 332 | } 333 | 334 | // Three coupled cycles. 335 | void AggregationUTest::test_triquad() 336 | { 337 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 338 | 339 | eval->eval("(load-from-path \"tests/generate/dict-triquad.scm\")"); 340 | Handle wall = eval->eval_h("left-wall"); 341 | 342 | setup_dict(); 343 | SimpleCallback cb(as, *dict); 344 | 345 | ag->aggregate({wall}, cb); 346 | Handle result = cb.get_solutions(); 347 | 348 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 349 | 350 | // We expect the solution to have three quads 351 | // 352 | // printf("Loop result is %s\n", result->to_string().c_str()); 353 | printf("Loop result size is %lu expecting 4\n", result->get_arity()); 354 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 355 | 356 | // All of the solutions should be the same size, substituting 357 | // only might/could in the parse. 358 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 359 | int cnt = 0; 360 | for (const Handle& soln: result->getOutgoingSet()) 361 | { 362 | logger().debug(" Soln %d expecting 8 words, got %d", 363 | ++cnt, soln->get_arity()); 364 | TSM_ASSERT("Bad section!", soln->get_arity() == 8); 365 | } 366 | 367 | logger().debug("END TEST: %s", __FUNCTION__); 368 | } 369 | 370 | // Mixture of trees and cycles. 371 | void AggregationUTest::test_mixed() 372 | { 373 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 374 | 375 | eval->eval("(load-from-path \"tests/generate/dict-mixed.scm\")"); 376 | Handle wall = eval->eval_h("left-wall"); 377 | 378 | setup_dict(); 379 | SimpleCallback cb(as, *dict); 380 | 381 | ag->aggregate({wall}, cb); 382 | Handle result = cb.get_solutions(); 383 | 384 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 385 | 386 | // printf("Mixed result is %s\n", result->to_string().c_str()); 387 | printf("Mixed result size is %lu expecting 8\n", result->get_arity()); 388 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 8); 389 | 390 | // All of the solutions should be the same size, substituting 391 | // only might/could in the parse. 392 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 393 | int cnt = 0; 394 | for (const Handle& soln: result->getOutgoingSet()) 395 | { 396 | logger().debug(" Soln %d expecting 10 words, got %d", 397 | ++cnt, soln->get_arity()); 398 | TSM_ASSERT("Bad section!", soln->get_arity() == 10); 399 | } 400 | 401 | logger().debug("END TEST: %s", __FUNCTION__); 402 | } 403 | 404 | // Multiple nucleation points 405 | void AggregationUTest::test_multi_root() 406 | { 407 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 408 | 409 | eval->eval("(load-from-path \"tests/generate/dict-mixed.scm\")"); 410 | Handle wall = eval->eval_h("left-wall"); 411 | Handle black = eval->eval_h("black"); 412 | Handle dog = eval->eval_h("dog"); 413 | Handle bark = eval->eval_h("bark"); 414 | 415 | setup_dict(); 416 | SimpleCallback cb(as, *dict); 417 | 418 | // ---------------------------------------------------- 419 | ag->aggregate({wall, black}, cb); 420 | Handle result = cb.get_solutions(); 421 | 422 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 423 | 424 | // printf("wall,black result is %s\n", result->to_string().c_str()); 425 | printf("wall,black result size is %lu expecting 4\n", result->get_arity()); 426 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 4); 427 | 428 | // All of the solutions should be the same size, substituting 429 | // only might/could in the parse. 430 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 431 | int cnt = 0; 432 | for (const Handle& soln: result->getOutgoingSet()) 433 | { 434 | logger().debug(" Soln %d expecting 10 words, got %d", 435 | ++cnt, soln->get_arity()); 436 | TSM_ASSERT("Bad section!", soln->get_arity() == 10); 437 | } 438 | 439 | // ---------------------------------------------------- 440 | ag->aggregate({wall, black, dog}, cb); 441 | result = cb.get_solutions(); 442 | 443 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 444 | 445 | // printf("wall,black result is %s\n", result->to_string().c_str()); 446 | printf("wall,black,dog result size is %lu expecting 2\n", result->get_arity()); 447 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 2); 448 | 449 | // All of the solutions should be the same size, substituting 450 | // only might/could in the parse. 451 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 452 | cnt = 0; 453 | for (const Handle& soln: result->getOutgoingSet()) 454 | { 455 | logger().debug(" Soln %d expecting 10 words, got %d", 456 | ++cnt, soln->get_arity()); 457 | TSM_ASSERT("Bad section!", soln->get_arity() == 10); 458 | } 459 | 460 | // ---------------------------------------------------- 461 | ag->aggregate({wall, black, dog, bark}, cb); 462 | result = cb.get_solutions(); 463 | 464 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 465 | 466 | // printf("wall,black result is %s\n", result->to_string().c_str()); 467 | printf("wall,black,dog,bark result size is %lu expecting 1\n", result->get_arity()); 468 | TSM_ASSERT("Bad loop result set!", result->get_arity() == 1); 469 | 470 | // All of the solutions should be the same size, substituting 471 | // only might/could in the parse. 472 | // printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str()); 473 | cnt = 0; 474 | for (const Handle& soln: result->getOutgoingSet()) 475 | { 476 | logger().debug(" Soln %d expecting 10 words, got %d", 477 | ++cnt, soln->get_arity()); 478 | TSM_ASSERT("Bad section!", soln->get_arity() == 10); 479 | } 480 | 481 | logger().debug("END TEST: %s", __FUNCTION__); 482 | } 483 | -------------------------------------------------------------------------------- /tests/generate/BasicNetworkUTest.cxxtest: -------------------------------------------------------------------------------- 1 | /* 2 | * BasicNetworkUTest.cxxtest 3 | * 4 | * Basic, simple random network unit test. Generates stochastic random 5 | * networks, where all edge types are the same, and all node types are 6 | * the same, except for the node arity. 7 | * 8 | * Copyright (C) 2020 Linas Vepstas 9 | * All Rights Reserved 10 | * SPDX-License-Identifier: AGPL-3.0-or-later 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | using namespace opencog; 23 | 24 | #define al as->add_link 25 | #define an as->add_node 26 | 27 | class BasicNetworkUTest: public CxxTest::TestSuite 28 | { 29 | private: 30 | AtomSpacePtr asp; 31 | AtomSpace* as; 32 | SchemeEval* eval; 33 | Aggregate* ag; 34 | Dictionary* dict; 35 | 36 | public: 37 | BasicNetworkUTest(); 38 | ~BasicNetworkUTest(); 39 | 40 | void setUp(); 41 | void tearDown(); 42 | 43 | void setup_dict(); 44 | void check_dipole(Handle, size_t); 45 | 46 | void test_network(); 47 | }; 48 | 49 | BasicNetworkUTest::BasicNetworkUTest() 50 | { 51 | logger().set_level(Logger::DEBUG); 52 | logger().set_print_to_stdout_flag(true); 53 | logger().set_timestamp_flag(false); 54 | } 55 | 56 | BasicNetworkUTest::~BasicNetworkUTest() 57 | { 58 | logger().info("Completed running BasicNetworkUTest"); 59 | 60 | // erase the log file if no assertions failed 61 | if (!CxxTest::TestTracker::tracker().suiteFailed()) 62 | std::remove(logger().get_filename().c_str()); 63 | else 64 | { 65 | logger().info("BasicNetworkUTest failed!"); 66 | logger().flush(); 67 | } 68 | } 69 | 70 | void BasicNetworkUTest::setUp() 71 | { 72 | asp = createAtomSpace(); 73 | as = asp.get(); 74 | eval = new SchemeEval(as); 75 | eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")"); 76 | ag = new Aggregate(as); 77 | } 78 | 79 | void BasicNetworkUTest::tearDown() 80 | { 81 | delete ag; 82 | delete eval; 83 | } 84 | 85 | void BasicNetworkUTest::setup_dict() 86 | { 87 | dict = new Dictionary(as); 88 | 89 | // The directions to connect. 90 | Handle any = an(CONNECTOR_DIR_NODE, "*"); 91 | dict->add_pole_pair(any, any); 92 | 93 | // The lexis to use XXX we should generate not read from file. 94 | HandleSet lex; 95 | as->get_handles_by_type(lex, SECTION); 96 | dict->add_to_lexis(lex); 97 | } 98 | 99 | // Start out real simple... 100 | void BasicNetworkUTest::test_network() 101 | { 102 | logger().debug("BEGIN TEST: %s", __FUNCTION__); 103 | 104 | eval->eval("(load-from-path \"tests/generate/basic-network.scm\")"); 105 | 106 | // logger().set_level(Logger::FINE); 107 | setup_dict(); 108 | Handle weights = eval->eval_h("(Predicate \"weights\")"); 109 | 110 | BasicParameters basic; 111 | RandomCallback cb(as, *dict, basic); 112 | cb.set_weight_key(weights); 113 | 114 | Handle root = eval->eval_h("(Concept \"peep 3\")"); 115 | ag->aggregate({root}, cb); 116 | Handle result = cb.get_solutions(); 117 | 118 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 119 | 120 | // printf("result is %s\n", result->to_string().c_str()); 121 | printf("have %lu results\n", result->get_arity()); 122 | 123 | TSM_ASSERT("Expected more results!", 10 < result->get_arity()); 124 | 125 | logger().debug("END TEST: %s", __FUNCTION__); 126 | } 127 | -------------------------------------------------------------------------------- /tests/generate/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | LINK_LIBRARIES( 2 | generate 3 | atomspace 4 | logger 5 | ) 6 | 7 | # Run the tests in logical order, not alphabetical order. 8 | ADD_CXXTEST(AggregationUTest) 9 | ADD_CXXTEST(GraphUTest) 10 | ADD_CXXTEST(BasicNetworkUTest) 11 | -------------------------------------------------------------------------------- /tests/generate/GraphUTest.cxxtest: -------------------------------------------------------------------------------- 1 | /* 2 | * GraphUTest.cxxtest 3 | * 4 | * Assorted graphs, created using the SimpleCallback. 5 | * 6 | * Copyright (C) 2020 Linas Vepstas 7 | * All Rights Reserved 8 | * SPDX-License-Identifier: AGPL-3.0-or-later 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | using namespace opencog; 20 | 21 | #define al as->add_link 22 | #define an as->add_node 23 | 24 | class GraphUTest: public CxxTest::TestSuite 25 | { 26 | private: 27 | AtomSpacePtr asp; 28 | AtomSpace* as; 29 | SchemeEval* eval; 30 | Aggregate* ag; 31 | Dictionary* dict; 32 | 33 | public: 34 | GraphUTest(); 35 | ~GraphUTest(); 36 | 37 | void setUp(); 38 | void tearDown(); 39 | 40 | void setup_dict(); 41 | void check_dipole(Handle, size_t); 42 | 43 | void test_dipole(); 44 | }; 45 | 46 | GraphUTest::GraphUTest() 47 | { 48 | logger().set_level(Logger::DEBUG); 49 | logger().set_print_to_stdout_flag(true); 50 | logger().set_timestamp_flag(false); 51 | } 52 | 53 | GraphUTest::~GraphUTest() 54 | { 55 | logger().info("Completed running GraphUTest"); 56 | 57 | // erase the log file if no assertions failed 58 | if (!CxxTest::TestTracker::tracker().suiteFailed()) 59 | std::remove(logger().get_filename().c_str()); 60 | else 61 | { 62 | logger().info("GraphUTest failed!"); 63 | logger().flush(); 64 | } 65 | } 66 | 67 | void GraphUTest::setUp() 68 | { 69 | asp = createAtomSpace(); 70 | as = asp.get(); 71 | eval = new SchemeEval(as); 72 | eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")"); 73 | ag = new Aggregate(as); 74 | } 75 | 76 | void GraphUTest::tearDown() 77 | { 78 | delete ag; 79 | delete eval; 80 | } 81 | 82 | void GraphUTest::setup_dict() 83 | { 84 | dict = new Dictionary(as); 85 | 86 | // The directions to connect. 87 | Handle any = an(CONNECTOR_DIR_NODE, "*"); 88 | dict->add_pole_pair(any, any); 89 | 90 | // The lexis to use XXX we should generate not read from file. 91 | HandleSet lex; 92 | as->get_handles_by_type(lex, SECTION); 93 | dict->add_to_lexis(lex); 94 | } 95 | 96 | // Test create of dipole graphs by the SimpleCallback 97 | // https://en.wikipedia.org/wiki/Dipole_graph 98 | void GraphUTest::check_dipole(Handle root, size_t nedges) 99 | { 100 | SimpleCallback cb(as, *dict); 101 | cb.allow_self_connections = true; 102 | cb.pair_any_links = -1; 103 | cb.pair_typed_links = -1; 104 | 105 | Aggregate lag(as); 106 | lag.aggregate({root}, cb); 107 | Handle result = cb.get_solutions(); 108 | // printf("result is %s\n", result->to_string().c_str()); 109 | 110 | TSM_ASSERT("Bad result!", result != Handle::UNDEFINED); 111 | TSM_ASSERT("Wrong number of solutions!", result->get_arity() == 1); 112 | Handle soln = result->getOutgoingAtom(0); 113 | TSM_ASSERT("Wrong number of graph elts!", soln->get_arity() == 1); 114 | Handle sect = soln->getOutgoingAtom(0); 115 | TSM_ASSERT("Not a valid section!", sect->get_arity() == 2); 116 | Handle seq = sect->getOutgoingAtom(1); 117 | TSM_ASSERT("Wrong number of links!", seq->get_arity() == nedges); 118 | 119 | HandleSeq links = seq->getOutgoingSet(); 120 | for (size_t i=1; ieval("(load-from-path \"tests/generate/basic-network.scm\")"); 131 | setup_dict(); 132 | 133 | Handle root = eval->eval_h("(Concept \"peep 1\")"); 134 | check_dipole (root, 1); 135 | 136 | root = eval->eval_h("(Concept \"peep 2\")"); 137 | check_dipole (root, 2); 138 | 139 | root = eval->eval_h("(Concept \"peep 3\")"); 140 | check_dipole (root, 3); 141 | 142 | root = eval->eval_h("(Concept \"peep 4\")"); 143 | check_dipole (root, 4); 144 | 145 | root = eval->eval_h("(Concept \"peep 5\")"); 146 | check_dipole (root, 5); 147 | 148 | root = eval->eval_h("(Concept \"peep 6\")"); 149 | check_dipole (root, 6); 150 | 151 | logger().debug("END TEST: %s", __FUNCTION__); 152 | } 153 | -------------------------------------------------------------------------------- /tests/generate/basic-network.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; basic-network.scm 3 | ; 4 | ; Basic test: Generate random network. Dictionary contains one word, 5 | ; with arbitrary numbers of connectors, all of the same type. 6 | ; 7 | (use-modules (srfi srfi-1)) 8 | (use-modules (opencog) (opencog exec)) 9 | 10 | ; Six burrs with different arities 11 | (define v1 12 | (Section 13 | (Concept "peep 1") 14 | (ConnectorSeq 15 | (Connector (Concept "E") (ConnectorDir "*"))))) 16 | 17 | (define v2 18 | (Section 19 | (Concept "peep 2") 20 | (ConnectorSeq 21 | (Connector (Concept "E") (ConnectorDir "*")) 22 | (Connector (Concept "E") (ConnectorDir "*"))))) 23 | 24 | (define v3 25 | (Section 26 | (Concept "peep 3") 27 | (ConnectorSeq 28 | (Connector (Concept "E") (ConnectorDir "*")) 29 | (Connector (Concept "E") (ConnectorDir "*")) 30 | (Connector (Concept "E") (ConnectorDir "*"))))) 31 | 32 | (define v4 33 | (Section 34 | (Concept "peep 4") 35 | (ConnectorSeq 36 | (Connector (Concept "E") (ConnectorDir "*")) 37 | (Connector (Concept "E") (ConnectorDir "*")) 38 | (Connector (Concept "E") (ConnectorDir "*")) 39 | (Connector (Concept "E") (ConnectorDir "*"))))) 40 | 41 | (define v5 42 | (Section 43 | (Concept "peep 5") 44 | (ConnectorSeq 45 | (Connector (Concept "E") (ConnectorDir "*")) 46 | (Connector (Concept "E") (ConnectorDir "*")) 47 | (Connector (Concept "E") (ConnectorDir "*")) 48 | (Connector (Concept "E") (ConnectorDir "*")) 49 | (Connector (Concept "E") (ConnectorDir "*"))))) 50 | 51 | (define v6 52 | (Section 53 | (Concept "peep 6") 54 | (ConnectorSeq 55 | (Connector (Concept "E") (ConnectorDir "*")) 56 | (Connector (Concept "E") (ConnectorDir "*")) 57 | (Connector (Concept "E") (ConnectorDir "*")) 58 | (Connector (Concept "E") (ConnectorDir "*")) 59 | (Connector (Concept "E") (ConnectorDir "*")) 60 | (Connector (Concept "E") (ConnectorDir "*"))))) 61 | 62 | ; Weightings 63 | (define weights (PredicateNode "weights")) 64 | 65 | (cog-set-value! v1 weights (FloatValue 1)) 66 | (cog-set-value! v2 weights (FloatValue (/ 1.0 2))) 67 | (cog-set-value! v3 weights (FloatValue (/ 1.0 3))) 68 | (cog-set-value! v4 weights (FloatValue (/ 1.0 4))) 69 | (cog-set-value! v5 weights (FloatValue (/ 1.0 5))) 70 | (cog-set-value! v6 weights (FloatValue (/ 1.0 6))) 71 | 72 | *unspecified* 73 | -------------------------------------------------------------------------------- /tests/generate/dict-biloop.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-biloop.scm 3 | ; 4 | ; Double-Loop test: dictionary that allows four sentences, with two 5 | ; cycles. 6 | ; 7 | ; +------WV-----+------CV----+--O---+ 8 | ; +---W---+--S--+--Ce--+--S--+ +-D-+ 9 | ; | | | | | | | 10 | ; LEFT-WALL Mary thinks John saw a bird 11 | ; 12 | ; The other sentences swap "Mary" for "John". The two cycles are both 13 | ; triangles; one with edges (WV, S, W), vertexes (LEFT-WALL, Mary, 14 | ; thinks). The other has edges (CV, Ce, S) and vertexes (thinks, John, 15 | ; saw). The two triangles share a single common vertex: "thinks". 16 | ; 17 | (use-modules (srfi srfi-1)) 18 | (use-modules (opencog) (opencog exec)) 19 | 20 | (define left-wall (Concept "LEFT-WALL")) 21 | 22 | ; A test dictionary of basic data. 23 | (Section 24 | (Concept "LEFT-WALL") 25 | (ConnectorSeq 26 | (Connector (Concept "WV") (ConnectorDir "+")) 27 | (Connector (Concept "W") (ConnectorDir "+")))) 28 | 29 | (Section 30 | (Concept "John") 31 | (ConnectorSeq 32 | (Connector (Concept "W") (ConnectorDir "-")) 33 | (Connector (Concept "S") (ConnectorDir "+")))) 34 | 35 | (Section 36 | (Concept "Mary") 37 | (ConnectorSeq 38 | (Connector (Concept "W") (ConnectorDir "-")) 39 | (Connector (Concept "S") (ConnectorDir "+")))) 40 | 41 | (Section 42 | (Concept "John") 43 | (ConnectorSeq 44 | (Connector (Concept "Ce") (ConnectorDir "-")) 45 | (Connector (Concept "S") (ConnectorDir "+")))) 46 | 47 | (Section 48 | (Concept "Mary") 49 | (ConnectorSeq 50 | (Connector (Concept "Ce") (ConnectorDir "-")) 51 | (Connector (Concept "S") (ConnectorDir "+")))) 52 | 53 | (Section 54 | (Concept "thinks") 55 | (ConnectorSeq 56 | (Connector (Concept "S") (ConnectorDir "-")) 57 | (Connector (Concept "WV") (ConnectorDir "-")) 58 | (Connector (Concept "CV") (ConnectorDir "+")) 59 | (Connector (Concept "Ce") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept "saw") 63 | (ConnectorSeq 64 | (Connector (Concept "S") (ConnectorDir "-")) 65 | (Connector (Concept "CV") (ConnectorDir "-")) 66 | (Connector (Concept "O") (ConnectorDir "+")))) 67 | 68 | (Section 69 | (Concept "a") 70 | (ConnectorSeq 71 | (Connector (Concept "D") (ConnectorDir "+")))) 72 | 73 | (Section 74 | (Concept "bird") 75 | (ConnectorSeq 76 | (Connector (Concept "D") (ConnectorDir "-")) 77 | (Connector (Concept "O") (ConnectorDir "-")))) 78 | -------------------------------------------------------------------------------- /tests/generate/dict-biquad.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-biquad.scm 3 | ; 4 | ; Double-Loop test: dictionary that allows four sentences, with two 5 | ; cycles. 6 | ; 7 | ; +----------WV------+--------CV-------+ 8 | ; +---W---+--S-+--I--+--Ce-+--S--+--I--+ 9 | ; | | | | | | | 10 | ; LEFT-WALL Mary might think John could fall 11 | ; 12 | ; The other sentences swap "might" for "could". The two cycles are both 13 | ; quads; one with edges (W, S, I, WV), vertexes (LEFT-WALL, Mary, 14 | ; might, think). The other has edges (Ce, S, I, CV) and vertexes (think, 15 | ; John, could, fall). The two quads share a single common vertex: "think". 16 | ; 17 | (use-modules (srfi srfi-1)) 18 | (use-modules (opencog) (opencog exec)) 19 | 20 | (define left-wall (Concept "LEFT-WALL")) 21 | 22 | ; A test dictionary of basic data. 23 | (Section 24 | (Concept "LEFT-WALL") 25 | (ConnectorSeq 26 | (Connector (Concept "WV") (ConnectorDir "+")) 27 | (Connector (Concept "W") (ConnectorDir "+")))) 28 | 29 | (Section 30 | (Concept "Mary") 31 | (ConnectorSeq 32 | (Connector (Concept "W") (ConnectorDir "-")) 33 | (Connector (Concept "S") (ConnectorDir "+")))) 34 | 35 | (Section 36 | (Concept "John") 37 | (ConnectorSeq 38 | (Connector (Concept "Ce") (ConnectorDir "-")) 39 | (Connector (Concept "S") (ConnectorDir "+")))) 40 | 41 | (Section 42 | (Concept "think") 43 | (ConnectorSeq 44 | (Connector (Concept "I") (ConnectorDir "-")) 45 | (Connector (Concept "WV") (ConnectorDir "-")) 46 | (Connector (Concept "CV") (ConnectorDir "+")) 47 | (Connector (Concept "Ce") (ConnectorDir "+")))) 48 | 49 | (Section 50 | (Concept "fall") 51 | (ConnectorSeq 52 | (Connector (Concept "I") (ConnectorDir "-")) 53 | (Connector (Concept "CV") (ConnectorDir "-")))) 54 | 55 | (Section 56 | (Concept "might") 57 | (ConnectorSeq 58 | (Connector (Concept "S") (ConnectorDir "-")) 59 | (Connector (Concept "I") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept "could") 63 | (ConnectorSeq 64 | (Connector (Concept "S") (ConnectorDir "-")) 65 | (Connector (Concept "I") (ConnectorDir "+")))) 66 | -------------------------------------------------------------------------------- /tests/generate/dict-helloworld.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-helloworld.scm 3 | ; 4 | ; Basic test: dictionary that allows one sentence: "Hello world". 5 | ; 6 | ; A test dictionary with three "jigsaw-puzzle pieces" in it. 7 | ; These assemble in a linear order, from left to right. 8 | ; It should be obvious from inspection how these are should 9 | ; be assembled. ... and, if its not ... the connector ID's must 10 | ; match, and the conector directions must be polar opposites. 11 | ; The edges connecting the vertexes are undirected edges; the 12 | ; ConnectorDir polarities instead indicate "to the left" and 13 | ; "to the right". 14 | ; 15 | (use-modules (srfi srfi-1)) 16 | (use-modules (opencog) (opencog exec)) 17 | 18 | (define left-wall (Concept "LEFT-WALL")) 19 | 20 | (Section 21 | (Concept "LEFT-WALL") 22 | (ConnectorSeq 23 | (Connector (Concept "W") (ConnectorDir "+")))) 24 | 25 | (Section 26 | (Concept "hello") 27 | (ConnectorSeq 28 | (Connector (Concept "W") (ConnectorDir "-")) 29 | (Connector (Concept "OH") (ConnectorDir "+")))) 30 | 31 | (Section 32 | (Concept "world") 33 | (ConnectorSeq 34 | (Connector (Concept "OH") (ConnectorDir "-")))) 35 | -------------------------------------------------------------------------------- /tests/generate/dict-loop.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-loop.scm 3 | ; 4 | ; Loop test: dictionary that allows four sentences, with one cycle. 5 | ; 6 | ; +-------WV------+----O-----+ 7 | ; +----W---+---S--+ +--D--+ 8 | ; | | | | | 9 | ; LEFT-WALL John saw a cat 10 | ; 11 | ; The other three substitute "Mary" for "John" and "dog" for "cat". 12 | ; The cycle is a triangle with edges (WV, S, W) and vertexes 13 | ; (LEFT-WALL, John, saw) 14 | ; 15 | ; The dictionary defines nine "jigsaw-puzzle pieces" in it. 16 | ; These assemble into the above graphs. It should be obvious from 17 | ; inspection how these are should be assembled. ... and, if its not 18 | ; ... the connector ID's must match, and the conector directions must 19 | ; be polar opposites. The edges connecting the vertexes are undirected 20 | ; edges; the ConnectorDir polarities instead indicate "to the left" 21 | ; and "to the right". In particular, all three edges in the triangle 22 | ; are undirected. 23 | ; 24 | (use-modules (srfi srfi-1)) 25 | (use-modules (opencog) (opencog exec)) 26 | 27 | (define left-wall (Concept "LEFT-WALL")) 28 | 29 | ; A test dictionary of basic data. 30 | (Section 31 | (Concept "LEFT-WALL") 32 | (ConnectorSeq 33 | (Connector (Concept "WV") (ConnectorDir "+")) 34 | (Connector (Concept "W") (ConnectorDir "+")))) 35 | 36 | (Section 37 | (Concept "John") 38 | (ConnectorSeq 39 | (Connector (Concept "W") (ConnectorDir "-")) 40 | (Connector (Concept "S") (ConnectorDir "+")))) 41 | 42 | (Section 43 | (Concept "Mary") 44 | (ConnectorSeq 45 | (Connector (Concept "W") (ConnectorDir "-")) 46 | (Connector (Concept "S") (ConnectorDir "+")))) 47 | 48 | (Section 49 | (Concept "saw") 50 | (ConnectorSeq 51 | (Connector (Concept "S") (ConnectorDir "-")) 52 | (Connector (Concept "WV") (ConnectorDir "-")) 53 | (Connector (Concept "O") (ConnectorDir "+")))) 54 | 55 | (Section 56 | (Concept "a") 57 | (ConnectorSeq 58 | (Connector (Concept "D") (ConnectorDir "+")))) 59 | 60 | (Section 61 | (Concept "cat") 62 | (ConnectorSeq 63 | (Connector (Concept "D") (ConnectorDir "-")) 64 | (Connector (Concept "O") (ConnectorDir "-")))) 65 | 66 | (Section 67 | (Concept "dog") 68 | (ConnectorSeq 69 | (Connector (Concept "D") (ConnectorDir "-")) 70 | (Connector (Concept "O") (ConnectorDir "-")))) 71 | -------------------------------------------------------------------------------- /tests/generate/dict-mixed.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-mixed.scm 3 | ; 4 | ; Mixture of trees and cycles, sharing vertexes and edges. 5 | ; 6 | ; 7 | ; +---------------------------Xp--------------+ 8 | ; +--------------------WV---------------+ | 9 | ; | +------------S-----------+ | 10 | ; | +------MXs------+ | | 11 | ; | | +------Xd----+ | | 12 | ; +-----W------+ | +---Ds-----+ | | 13 | ; | +--D--+ | | +--A--+-Xc-+ +--Xf-+ 14 | ; | | | | | | | | | | 15 | ; LEFT-WALL the dog , a black lab , barked . 16 | ; 17 | ; The other sentences swap "dog/cat" "black/white", "barked/purred". 18 | ; 19 | (use-modules (srfi srfi-1)) 20 | (use-modules (opencog) (opencog exec)) 21 | 22 | (define left-wall (Concept "LEFT-WALL")) 23 | (define black (Concept "black")) 24 | (define dog (Concept "dog")) 25 | (define bark (Concept "barked")) 26 | 27 | ; A test dictionary of basic data. 28 | (Section 29 | (Concept "LEFT-WALL") 30 | (ConnectorSeq 31 | (Connector (Concept "Xp") (ConnectorDir "+")) 32 | (Connector (Concept "WV") (ConnectorDir "+")) 33 | (Connector (Concept "W") (ConnectorDir "+")))) 34 | 35 | (Section 36 | (Concept "the") 37 | (ConnectorSeq 38 | (Connector (Concept "D") (ConnectorDir "+")))) 39 | 40 | (Section 41 | (Concept "a") 42 | (ConnectorSeq 43 | (Connector (Concept "Ds") (ConnectorDir "+")))) 44 | 45 | (Section 46 | (Concept "dog") 47 | (ConnectorSeq 48 | (Connector (Concept "W") (ConnectorDir "-")) 49 | (Connector (Concept "D") (ConnectorDir "-")) 50 | (Connector (Concept "MXs") (ConnectorDir "+")) 51 | (Connector (Concept "S") (ConnectorDir "+")))) 52 | 53 | (Section 54 | (Concept "cat") 55 | (ConnectorSeq 56 | (Connector (Concept "W") (ConnectorDir "-")) 57 | (Connector (Concept "D") (ConnectorDir "-")) 58 | (Connector (Concept "MXs") (ConnectorDir "+")) 59 | (Connector (Concept "S") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept ",") 63 | (ConnectorSeq 64 | (Connector (Concept "Xd") (ConnectorDir "+")))) 65 | 66 | (Section 67 | (Concept ",") 68 | (ConnectorSeq 69 | (Connector (Concept "Xc") (ConnectorDir "-")))) 70 | 71 | (Section 72 | (Concept ".") 73 | (ConnectorSeq 74 | (Connector (Concept "Xp") (ConnectorDir "-")) 75 | (Connector (Concept "Xf") (ConnectorDir "-")))) 76 | 77 | (Section 78 | (Concept "black") 79 | (ConnectorSeq 80 | (Connector (Concept "A") (ConnectorDir "+")))) 81 | 82 | (Section 83 | (Concept "white") 84 | (ConnectorSeq 85 | (Connector (Concept "A") (ConnectorDir "+")))) 86 | 87 | (Section 88 | (Concept "lab") 89 | (ConnectorSeq 90 | (Connector (Concept "A") (ConnectorDir "-")) 91 | (Connector (Concept "Ds") (ConnectorDir "-")) 92 | (Connector (Concept "Xd") (ConnectorDir "-")) 93 | (Connector (Concept "MXs") (ConnectorDir "-")) 94 | (Connector (Concept "Xc") (ConnectorDir "+")))) 95 | 96 | (Section 97 | (Concept "barked") 98 | (ConnectorSeq 99 | (Connector (Concept "WV") (ConnectorDir "-")) 100 | (Connector (Concept "S") (ConnectorDir "-")) 101 | (Connector (Concept "Xf") (ConnectorDir "+")))) 102 | 103 | (Section 104 | (Concept "purred") 105 | (ConnectorSeq 106 | (Connector (Concept "WV") (ConnectorDir "-")) 107 | (Connector (Concept "S") (ConnectorDir "-")) 108 | (Connector (Concept "Xf") (ConnectorDir "+")))) 109 | -------------------------------------------------------------------------------- /tests/generate/dict-quad.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-quad.scm 3 | ; 4 | ; Loop test: dictionary that allows four sentences, with one cycle. 5 | ; 6 | ; +----------WV--------+----O-----+ 7 | ; +----W---+--S--+--I--+ +--D--+ 8 | ; | | | | | | 9 | ; LEFT-WALL Mary could see the cat 10 | ; 11 | ; The other three substitute "see/hear" and "John/Mary". 12 | ; The cycle is a quadrilateral with edges (WV, W, S, I) and vertexes 13 | ; (LEFT-WALL, Mary, could, see) 14 | ; 15 | ; As with the triangle variations, edges are undirected. 16 | ; 17 | (use-modules (srfi srfi-1)) 18 | (use-modules (opencog) (opencog exec)) 19 | 20 | (define left-wall (Concept "LEFT-WALL")) 21 | 22 | ; A test dictionary of basic data. 23 | (Section 24 | (Concept "LEFT-WALL") 25 | (ConnectorSeq 26 | (Connector (Concept "WV") (ConnectorDir "+")) 27 | (Connector (Concept "W") (ConnectorDir "+")))) 28 | 29 | (Section 30 | (Concept "John") 31 | (ConnectorSeq 32 | (Connector (Concept "W") (ConnectorDir "-")) 33 | (Connector (Concept "S") (ConnectorDir "+")))) 34 | 35 | (Section 36 | (Concept "Mary") 37 | (ConnectorSeq 38 | (Connector (Concept "W") (ConnectorDir "-")) 39 | (Connector (Concept "S") (ConnectorDir "+")))) 40 | 41 | (Section 42 | (Concept "could") 43 | (ConnectorSeq 44 | (Connector (Concept "S") (ConnectorDir "-")) 45 | (Connector (Concept "I") (ConnectorDir "+")))) 46 | 47 | (Section 48 | (Concept "see") 49 | (ConnectorSeq 50 | (Connector (Concept "I") (ConnectorDir "-")) 51 | (Connector (Concept "WV") (ConnectorDir "-")) 52 | (Connector (Concept "O") (ConnectorDir "+")))) 53 | 54 | (Section 55 | (Concept "hear") 56 | (ConnectorSeq 57 | (Connector (Concept "I") (ConnectorDir "-")) 58 | (Connector (Concept "WV") (ConnectorDir "-")) 59 | (Connector (Concept "O") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept "the") 63 | (ConnectorSeq 64 | (Connector (Concept "D") (ConnectorDir "+")))) 65 | 66 | (Section 67 | (Concept "dog") 68 | (ConnectorSeq 69 | (Connector (Concept "D") (ConnectorDir "-")) 70 | (Connector (Concept "O") (ConnectorDir "-")))) 71 | -------------------------------------------------------------------------------- /tests/generate/dict-tree.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-tree.scm 3 | ; See `examples/dict-tree.scm` for details. 4 | ; 5 | ; Tree test: dictionary that allows four sentences, as trees (acyclic) 6 | ; 7 | ; +----O-----+ 8 | ; +----W---+---S--+ +--D--+ 9 | ; | | | | | 10 | ; LEFT-WALL John saw a cat 11 | ; 12 | ; The other three substitute "Mary" for "John" and "dog" for "cat". 13 | ; All four possible sentences are generated. 14 | ; 15 | ; 16 | (use-modules (srfi srfi-1)) 17 | (use-modules (opencog) (opencog exec)) 18 | 19 | (define left-wall (Concept "LEFT-WALL")) 20 | 21 | ; A test dictionary of basic data. 22 | (Section 23 | (Concept "LEFT-WALL") 24 | (ConnectorSeq 25 | (Connector (Concept "W") (ConnectorDir "+")))) 26 | 27 | (Section 28 | (Concept "John") 29 | (ConnectorSeq 30 | (Connector (Concept "W") (ConnectorDir "-")) 31 | (Connector (Concept "S") (ConnectorDir "+")))) 32 | 33 | (Section 34 | (Concept "Mary") 35 | (ConnectorSeq 36 | (Connector (Concept "W") (ConnectorDir "-")) 37 | (Connector (Concept "S") (ConnectorDir "+")))) 38 | 39 | (Section 40 | (Concept "saw") 41 | (ConnectorSeq 42 | (Connector (Concept "S") (ConnectorDir "-")) 43 | (Connector (Concept "O") (ConnectorDir "+")))) 44 | 45 | (Section 46 | (Concept "a") 47 | (ConnectorSeq 48 | (Connector (Concept "D") (ConnectorDir "+")))) 49 | 50 | (Section 51 | (Concept "cat") 52 | (ConnectorSeq 53 | (Connector (Concept "D") (ConnectorDir "-")) 54 | (Connector (Concept "O") (ConnectorDir "-")))) 55 | 56 | (Section 57 | (Concept "dog") 58 | (ConnectorSeq 59 | (Connector (Concept "D") (ConnectorDir "-")) 60 | (Connector (Concept "O") (ConnectorDir "-")))) 61 | -------------------------------------------------------------------------------- /tests/generate/dict-triquad.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; dict-triquad.scm 3 | ; 4 | ; Triple-Loop test: dictionary that allows four sentences, with three 5 | ; cycles, the third one sharing edges. 6 | ; 7 | ; +---------------------Xp--------------------+ 8 | ; +----------WV------+--------CV-------+ | 9 | ; +---W---+--S-+--I--+--Ce-+--S--+--I--+--Xc--+ 10 | ; | | | | | | | | 11 | ; LEFT-WALL Mary might think John could fall . 12 | ; 13 | ; The other sentences swap "might" for "could". Two cycles are both 14 | ; quads; one with edges (W, S, I, WV), vertexes (LEFT-WALL, Mary, 15 | ; might, think). The other has edges (Ce, S, I, CV) and vertexes (think, 16 | ; John, could, fall). The two quads share a single common vertex: "think". 17 | ; 18 | ; The third cycle shares edges with the quads: it is (WV, CV, Xc, Xp) 19 | ; with vertexes (LEFT-WALL, think, fall, .) 20 | ; 21 | (use-modules (srfi srfi-1)) 22 | (use-modules (opencog) (opencog exec)) 23 | 24 | (define left-wall (Concept "LEFT-WALL")) 25 | 26 | ; A test dictionary of basic data. 27 | (Section 28 | (Concept "LEFT-WALL") 29 | (ConnectorSeq 30 | (Connector (Concept "Xp") (ConnectorDir "+")) 31 | (Connector (Concept "WV") (ConnectorDir "+")) 32 | (Connector (Concept "W") (ConnectorDir "+")))) 33 | 34 | (Section 35 | (Concept "Mary") 36 | (ConnectorSeq 37 | (Connector (Concept "W") (ConnectorDir "-")) 38 | (Connector (Concept "S") (ConnectorDir "+")))) 39 | 40 | (Section 41 | (Concept "John") 42 | (ConnectorSeq 43 | (Connector (Concept "Ce") (ConnectorDir "-")) 44 | (Connector (Concept "S") (ConnectorDir "+")))) 45 | 46 | (Section 47 | (Concept "think") 48 | (ConnectorSeq 49 | (Connector (Concept "I") (ConnectorDir "-")) 50 | (Connector (Concept "WV") (ConnectorDir "-")) 51 | (Connector (Concept "CV") (ConnectorDir "+")) 52 | (Connector (Concept "Ce") (ConnectorDir "+")))) 53 | 54 | (Section 55 | (Concept "fall") 56 | (ConnectorSeq 57 | (Connector (Concept "I") (ConnectorDir "-")) 58 | (Connector (Concept "CV") (ConnectorDir "-")) 59 | (Connector (Concept "Xc") (ConnectorDir "+")))) 60 | 61 | (Section 62 | (Concept "might") 63 | (ConnectorSeq 64 | (Connector (Concept "S") (ConnectorDir "-")) 65 | (Connector (Concept "I") (ConnectorDir "+")))) 66 | 67 | (Section 68 | (Concept "could") 69 | (ConnectorSeq 70 | (Connector (Concept "S") (ConnectorDir "-")) 71 | (Connector (Concept "I") (ConnectorDir "+")))) 72 | 73 | (Section 74 | (Concept ".") 75 | (ConnectorSeq 76 | (Connector (Concept "Xp") (ConnectorDir "-")) 77 | (Connector (Concept "Xc") (ConnectorDir "-")))) 78 | -------------------------------------------------------------------------------- /tests/generate/graph.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; graph.scm 3 | ; 4 | ; Used for the dipole graph test. 5 | ; 6 | (use-modules (srfi srfi-1)) 7 | (use-modules (opencog) (opencog exec)) 8 | 9 | (Section 10 | (Concept "peep 1") 11 | (ConnectorSeq 12 | (Connector (Concept "E") (ConnectorDir "*")))) 13 | 14 | (Section 15 | (Concept "peep 2") 16 | (ConnectorSeq 17 | (Connector (Concept "E") (ConnectorDir "*")) 18 | (Connector (Concept "E") (ConnectorDir "*")))) 19 | 20 | (Section 21 | (Concept "peep 3") 22 | (ConnectorSeq 23 | (Connector (Concept "E") (ConnectorDir "*")) 24 | (Connector (Concept "E") (ConnectorDir "*")) 25 | (Connector (Concept "E") (ConnectorDir "*")))) 26 | 27 | (Section 28 | (Concept "peep 4") 29 | (ConnectorSeq 30 | (Connector (Concept "E") (ConnectorDir "*")) 31 | (Connector (Concept "E") (ConnectorDir "*")) 32 | (Connector (Concept "E") (ConnectorDir "*")) 33 | (Connector (Concept "E") (ConnectorDir "*")))) 34 | 35 | (Section 36 | (Concept "peep 5") 37 | (ConnectorSeq 38 | (Connector (Concept "E") (ConnectorDir "*")) 39 | (Connector (Concept "E") (ConnectorDir "*")) 40 | (Connector (Concept "E") (ConnectorDir "*")) 41 | (Connector (Concept "E") (ConnectorDir "*")) 42 | (Connector (Concept "E") (ConnectorDir "*")))) 43 | 44 | (Section 45 | (Concept "peep 6") 46 | (ConnectorSeq 47 | (Connector (Concept "E") (ConnectorDir "*")) 48 | (Connector (Concept "E") (ConnectorDir "*")) 49 | (Connector (Concept "E") (ConnectorDir "*")) 50 | (Connector (Concept "E") (ConnectorDir "*")) 51 | (Connector (Concept "E") (ConnectorDir "*")) 52 | (Connector (Concept "E") (ConnectorDir "*")))) 53 | --------------------------------------------------------------------------------