Solving two consecutive dependent goals from command line

Hello,

I want to be able to solve two Prolog goals G1 and G2 from command line as follows.
$ swipl -l program.pl -g G1 -g halt
$ swipl -l program.pl -g G2 -g halt

Goal G2 can be solved independently of goal G1, or can use information available after solving G1 (Solving G2 takes potentially less time in this case). This question concerns the latter case.

My issue is how do I pass the knowledge-base state that is available once G1 is solved in $ swipl -l program.pl -g G1 -g halt, to the evaluation of G2 in $ swipl -l program.pl -g G2 -g halt?

I tried reading about saving states (Understanding Saved States) but I honestly could not figure out how to do it.

Additionally, my program starts executing from the following point:

:- main.
main:- %The program

I read in the mentioned section that I should use initialization/1 or initialization/2 instead of using the directive :- to fire up my program; but again, I do not know how this fits in everything.

My problem can be solved by allowing the user to enter the goals one after the other in one single execution of the program; but for automation purposes, this solution is not adequate for me.

So, how do I change the following two commands so that the knowledge base available at the end of solving G1 is made available to solving G2.
$ swipl -l program.pl -g G1 -g halt
$ swipl -l program.pl -g G2 -g halt

Many thanks.

I have not run any of your code or code specific to your question to test what I purpose.

In reading, Understanding saved states it reads

A SWI-Prolog saved state is a resource archive that contains the compiled program in a machine-independent format

which I am thinking saves the state of the code as well as any data. But from reading your problem you only want to pass the data out from one process and into another process. Also since the process are started from the command line and running consecutive, one process is not communicating with the other process so a requirement is that the state has to persist after a process dies/ends.

Have you looked at library(persistence) ?

While the example in the documentation is thin, I learned how to use it myself for a simple problem.

Here are some questions I asked a time ago Trying to understand library(persistency) but it really will leave you with more questions than it helps. However if your data is simple then hopefully I should be able to help you if needed. :smiley:

An alternative point of view is to consider this as a piping problem. Consider the following predicates in a file called hello.pl:

who :-
    writeln("world.").

greet :-
    read(Who),
    write("Hello, "),
    write(Who),
    writeln("!").

Now you can run two sequential processes where the first outputs to STDOUT, and the second reads that from STDIN:

:~$ swipl -g who -t halt -s hello.pl | swipl -g greet -t halt -s hello.pl
|: Hello, world!

Fun things to play with in this domain…

You can open the second instance of swipl and pipe into it right out of the first:

who :-
    setup_call_cleanup(
    open(pipe('swipl -g greet -t halt -s hello.pl'), write, Stream),
        writeln(Stream, "world."),
        close(Stream)
    ).

So only needing to run:

:~$ swipl -q -t who -s hello.pl

If you’d like to avoid appending fullstops on the output terms, read_line_to_string/2 is handy, but I can’t seem to get it to work with a piped in input stream for some reason?

Perhaps I don’t understand something, but why don’t you just run:

$ swipl -l program.pl -g "G1,G2"  -g halt
1 Like

The following is one use-case of my program.

  • User 1 uses the program to solve goal G1.
  • User 1 or another user User 2 uses the program to solve goal G2.
  • There is a way to determine that the solution of goal G2 can benefit from the solution of goal G1.
    Any given user will not know of this background speed-up feature, so they cannot issue the command to solve the conjunction of goals G1 and G2.
    In addition to this, solving G1 might be expensive, so re-solving it in the conjunction is not feasible performance-wise.
    Perhaps my use of the word “consecutive” was misleading. What I meant was that I needed the knowledge base available at the end of solving one goal to be forwarded to the process of solving another goal.
    Maybe I will just write the knowledge base to a text file at the end of solving G1 and reload it when solving G2.

Sorry to have to ask this again, but have you looked at library(persistence) ? :smiley:

If you did, I am curious as to why you can not use it for the example you have given.

Yes I did :slight_smile: and I was a bit intimidated :slight_smile: I haven’t ruled out the use of persistence, I just could not get my mind around it.

Say I have the following program.

:- dynamic entity/1.
program(Goal):-
    % Stuff
    assert(entity(A)),
    assert(entity(B)),
   % More stuff

How do I change this program using persistence so that it is possible to save the asserted entities at the end of a successful evaluation of Goal; and also consult any saved entities by a previous execution of the program? Something like:

:- dynamic entity/1.
program(Goal):-
  % Are there entity/1 predicates stored from a previous execution, if yes, load them
  % Stuff
  assert(entity(A)),
  assert(entity(B)),
  % More stuff
  % Save the entities I have previously asserted
1 Like

First an example run to make sure it does what you expect.

Start SWI-Prolog to get Toplevel.

Welcome to SWI-Prolog (threaded, 64 bits, version 8.1.12)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- 

Since all of the code for this is in a single directory to make it easier to demonstrate,
change the working directory to the location of the files. Obviously the directory name in the following example needs to be changed to what you are using.

?- working_directory(_, 'C:/Users/Eric/Documents/Projects/Prolog/Persistence example/').
true.

Load the first program and run it

?- consult("program_a.pl").
true.

?- program_1.
Starting saving 
Done 
true.

?- 

At this point the facts are still in file buffer but not written to the file. To get them saved to a file halt/0 must be run. Running halt/0 will close the Toplevel

Doing this will create a file that contains the saved facts.

file: data.journal

created(1569346573.8356802).
assert(entity("a")).
assert(entity("b")).

When I first saw the word assert in the file it confused me, but AFAIK that is how it works.

Start SWI-Prolog to get to Topelvel

Welcome to SWI-Prolog (threaded, 64 bits, version 8.1.12)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?-

Since Toplevel was restarted, need to change the directory again.

?- working_directory(_, 'C:/Users/Eric/Documents/Projects/Prolog/Persistence example/').
true.

Load the second program and run it.

?- consult("program_b.pl").
true.

?- program_2.
Starting reading program 
"a"
"b"
false.

?- 

Details

program_a.pl

:- use_module('program_database.pl').

program_1 :-
    format('Starting saving ~n',[]),
    A = "a",
    B = "b",
    % Stuff
    %assert(entity(A)),
    (
        exists_entity(A)
    ;
        add_entity(A)
    ),
    %assert(entity(B)),
    (
        exists_entity(B)
    ;
        add_entity(B)
    ),
    % More stuff
    format('Done ~n',[]).

program_b.pl

:- use_module('program_database.pl').

program_2 :-
    format('Starting reading program ~n',[]),
    % Stuff
    write_entities.

write_entities :-
    facts:entity(A),
    write_canonical(A),nl,
    fail.

The module that implements the predicates exists_entity/1 and add_entity/1

program_database.pl

:- module(
    facts,
    [
      add_entity/1,          % +Identifier:string
      exists_entity/1        % ?Identifier:string
    ]
  ).

:- use_module(library(persistency)).

:- persistent
    entity(identifier:string).

:- initialization(db_attach('data.journal', [])).

exists_entity(Identifier):-
  with_mutex(facts_journal, entity(Identifier)).

add_entity(Identifier):-
  with_mutex(facts_journal, assert_entity(Identifier)).

Now to explain it all.

The key to all of this is creating the module facts, (program_database.pl) which uses library(persistency).

The example you gave was as simple as it can get, it is only one fact entity/1 with one parameter and no need to use retract.


The first item needed is access to the persistency library

:- use_module(library(persistency)).

The next item that the persistence store needs is a term/spec.

In library(persistency) it states:

persistent +Spec
Declare dynamic database terms. Declarations appear in a directive and have the following format:

:- persistent
        <callable>,
        <callable>,
        ...

Each specification is a callable term, following the conventions of library(record), where each argument is of the form

  name:type

Types are defined by library(error).

The type parameter is found in must_be/2 but note that there are more values than listed in the table and list above the table. The real list is in the code as has_type/2

Also note that there can be more than one term/spec associated with a persistence store, but for this example there is only the one, entity.

For this example the type string will be used.

:- persistent
    entity(identifier:string).

The next item that the persistence store needs is some predicates to query/read and assert/write the term/spec.

In library(persistency) it states:

The persistent/1 expands each declaration into four predicates:

  • name(Arg, ...)
  • assert_name(Arg, ...)
  • retract_name(Arg, ...)
  • retractall_name(Arg, ...)

These four predicates are created by library(persistence) for each spec, e.g. entity(identifier:string) and how user code interacts with the persistence file. However for this case only the first two are needed.

name(Arg, ...) - queries/reads the facts from the persistence store/file
assert_name(Arg, ...) - adds facts to the persistence store/file

To ensure no access collisions, with_mutex/2 is used.

exists_entity(Identifier) :-
  with_mutex(facts_journal, entity(Identifier)).

add_entity(Identifier):-
  with_mutex(facts_journal, assert_entity(Identifier)).

Now with exists_entity/1 and add_entity/1 user code can query/read and assert/write to the persistence store/file/journal.


The next item that the persistence store needs is a file name.

In library(persistency) it states:

db_attach (:File, +Options)

Use File as persistent database for the calling module. The calling module must defined persistent/1 to declare the database terms. Defined options:

sync (+Sync)

One of
close (close journal after write),
flush (default, flush journal after write) or
none (handle as fully buffered stream).

If File is already attached this operation may change the sync behavior.

:- initialization(db_attach('data.journal', [])).

The last item is to convert the code in to a module and export the predicates.

:- module(
    facts,
    [
      add_entity/1,          % +Identifier:string
      exists_entity/1        % ?Identifier:string
    ]
  ).

The first program creates the data to be persisted makes use of the facts module

:- use_module('program_database.pl').

These predicates work in combination to ensure no value is duplicated in the persistence store, and is stored if it doesn’t exist.

    (
        exists_entity(A)
    ;
        add_entity(A)
    )

halt/0 is run to close the file so that the buffer is flushed. I am sure there is a better way, but I have not needed it so have not looked.


The second program reads the persisted data makes use of the facts module

:- use_module('program_database.pl').

It only uses a failure driven loop as there can be more than one fact of the same functor.

facts:entity(A)

If the line is changed to

exists_entity(A)

it only returns “a” and not “b”. I don’t know why (probably with_mutex/2). If anyone knows the fix for that I am interested to learn.


Hopefully this gives you the details you need to expand into what you need. As I noted, there is room for improvement and any feedback is appreciated. :smiley:

Ask questions if this does not make sense anywhere.

1 Like

Wow, that explanation is great. Intimidation gone! Thank you very much :slight_smile:

I have further questions if you don’t mind.

  • Where is the file that holds the asserted facts between the two runs stored?
  • Prolog console is gently requesting me to change my queries about the dynamic predicates from ?- entity(A). to ?- facts:entity(A).. Does this mean I should replace all my queries about dynamic facts in my program to this style once I have adopted the use of persistence?

Cheers.

1 Like

The path is given by db_attach/2, e.g.

db_attach('data.journal', [])

To keep the example simple it used the same directory as the code, e.g.

?- working_directory(_, 'C:/Users/Eric/Documents/Projects/Prolog/Persistence example/').
true.

Since db_path/2 can take a full path, you could also do something like

db_attach('C:/Users/Eric/Documents/Projects/Prolog/Persistence example/data.journal', [])

@jan Can you check this answer to make sure I am not giving very bad advise.

I would not. That warning is because I still don’t fully know the correct way to use library(persistency).

This also goes back to the statement I made in the explanation.

When writing to a file that can be open by multiple processes at the same time, the use of with_mutex/2 is probably required. However

entity(A).

is just trying to read the file and so no lock should be needed and thus with_mutex/2 should not be needed.

Since the with_mutex/2 is not needed, the predicate to read the facts, entity/1 which is in the facts module can be exported as is and the wrapper predicate exists_entity/1 can be removed. The changes to all three source code files becomes

program_a.pl

:- use_module('program_database.pl').

program_1 :-
    format('Starting saving ~n',[]),
    A = "a",
    B = "b",
    % Stuff
    %assert(entity(A)),
    (
        % exists_entity(A)
        entity(A)
    ;
        add_entity(A)
    ),
    %assert(entity(B)),
    (
        % exists_entity(B)
        entity(B)
    ;
        add_entity(B)
    ),
    % More stuff
    format('Done ~n',[]).

program_b.pl

:- use_module('program_database.pl').

program_2 :-
    format('Starting reading program ~n',[]),
    % Stuff
    write_entities.

write_entities :-
    % facts:entity(A),
    % exists_entity(A),
    entity(A),
    write_canonical(A),nl,
    fail.

program_database.pl

:- module(
    facts,
    [
      add_entity/1,          % +Identifier:string
      % exists_entity/1        % ?Identifier:string
      entity/1               % ?Identifier:string
    ]
  ).

:- use_module(library(persistency)).

:- persistent
    entity(identifier:string).

% :- initialization(db_attach('C:/Users/Eric/Documents/Projects/Prolog/Persistence example/data.journal', [])).
:- initialization(db_attach('data.journal', [])).

% exists_entity(Identifier):-
%   with_mutex(facts_journal, entity(Identifier)).

add_entity(Identifier):-
  with_mutex(facts_journal, assert_entity(Identifier)).

In other words what library(persistency) does is, that you give it a spec and it creates the 4 predicates

name(Arg, ...)
assert_name(Arg, ...)
retract_name(Arg, ...)
retractall_name(Arg, ...)

and you give it a file name, e.g. db_attach('data.journal', []) and it stores the clauses created by using assert_name(Arg, ...) into memory while the programming is running and a file when the program ends. I have not worked with the predicates such as db_detach/0, db_sync/1 and db_sync_all/1 so I can’t say more about them.

The rest is just code I added to make it more useful. It does not even need to be put into a separate module. However in the several cases I used it for and the case you used it for, it makes more sense as a module.


So you have library(persistency) working and know enough to now ask good questions, that pretty much puts you near to what I know about library(persistency), e.g. I only experimented for several hours when I asked the questions in Trying to understand library(persistency)

Thanks for asking the questions because it helps to reinforce/change my understanding of using library(persistency) and how others are using it and that some of the ways I explain it need more clarification. :smiley:

1 Like

It looks pretty much ok to me. Personally I dislike absolute paths in code, also because I use quite a few different machines with a different file system layout. I either use simple relative paths or absolute_file_name/3 and user:file_search_path/2 rules to locate (file) resources.

1 Like