Improving minimize_mealy benchmarking
* python/spot/__init__.py: Adding helper function for inline plot of csv *spot/twaalgos/mealy_machine.cc, spot/twaalgos/mealy_machine.hh: Main changes * tests/python/_mealy.ipynb: Update * tests/python/ipnbdoctest.py: Ignore timing table * tests/python/synthesis.ipynb: Update
This commit is contained in:
parent
c63c1796b9
commit
4a24739c3f
6 changed files with 1609 additions and 63 deletions
|
|
@ -1298,6 +1298,36 @@ def sat_minimize(aut, acc=None, colored=False,
|
||||||
else:
|
else:
|
||||||
return sm(aut, args, state_based)
|
return sm(aut, args, state_based)
|
||||||
|
|
||||||
|
# Adding the inline csv-display option
|
||||||
|
def minimize_mealy(mm, opt = -1, display_log = False, return_log = False):
|
||||||
|
from spot.impl import minimize_mealy as minmealy
|
||||||
|
|
||||||
|
try:
|
||||||
|
lvl = int(opt)
|
||||||
|
opt = synthesis_info()
|
||||||
|
opt.minimize_lvl = lvl + 4
|
||||||
|
except (ValueError, TypeError) as _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if display_log or return_log:
|
||||||
|
import pandas as pd
|
||||||
|
with tempfile.NamedTemporaryFile(dir='.', suffix='.minlog') as t:
|
||||||
|
opt.opt.set_str("satlogcsv", t.name)
|
||||||
|
resmm = minmealy(mm, opt)
|
||||||
|
|
||||||
|
dfrm = pd.read_csv(t.name, dtype=object)
|
||||||
|
if display_log:
|
||||||
|
from IPython.display import display
|
||||||
|
del dfrm['instance']
|
||||||
|
display(dfrm)
|
||||||
|
if return_log:
|
||||||
|
return resmm, dfrm
|
||||||
|
else:
|
||||||
|
return resmm
|
||||||
|
else:
|
||||||
|
return minmealy(mm, opt)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_word(word, dic=_bdd_dict):
|
def parse_word(word, dic=_bdd_dict):
|
||||||
from spot.impl import parse_word as pw
|
from spot.impl import parse_word as pw
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include <spot/misc/bddlt.hh>
|
#include <spot/misc/bddlt.hh>
|
||||||
#include <spot/misc/hash.hh>
|
#include <spot/misc/hash.hh>
|
||||||
|
|
@ -88,6 +89,33 @@ namespace
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static std::unique_ptr<std::ofstream> sat_csv_file;
|
||||||
|
struct fwrapper{
|
||||||
|
std::string fname;
|
||||||
|
std::FILE* f;
|
||||||
|
fwrapper(const std::string& name)
|
||||||
|
: fname{name}
|
||||||
|
, f{std::fopen(name.c_str(), "a")}
|
||||||
|
{
|
||||||
|
if (!f)
|
||||||
|
throw std::runtime_error("File could not be oppened for writing.");
|
||||||
|
}
|
||||||
|
~fwrapper()
|
||||||
|
{
|
||||||
|
std::fclose(f);
|
||||||
|
f = nullptr;
|
||||||
|
}
|
||||||
|
fwrapper& operator=(const fwrapper&) = delete;
|
||||||
|
fwrapper& operator=(fwrapper&&) = delete;
|
||||||
|
fwrapper(const fwrapper&) = delete;
|
||||||
|
fwrapper(fwrapper&&) = delete;
|
||||||
|
};
|
||||||
|
static std::unique_ptr<fwrapper> sat_dimacs_file;
|
||||||
|
static std::string sat_instance_name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace spot
|
namespace spot
|
||||||
{
|
{
|
||||||
|
|
@ -817,6 +845,119 @@ namespace
|
||||||
#else
|
#else
|
||||||
void trace_clause(const std::vector<int>&){}
|
void trace_clause(const std::vector<int>&){}
|
||||||
#endif
|
#endif
|
||||||
|
struct satprob_info
|
||||||
|
{
|
||||||
|
stopwatch sw;
|
||||||
|
|
||||||
|
double premin_time, reorg_time, partsol_time, player_incomp_time,
|
||||||
|
incomp_time, split_all_let_time, split_min_let_time,
|
||||||
|
split_cstr_time, prob_init_build_time, sat_time,
|
||||||
|
build_time, refine_time, total_time;
|
||||||
|
long long n_classes, n_refinement, n_lit, n_clauses,
|
||||||
|
n_iteration, n_bisim_let, n_min_states, done;
|
||||||
|
std::string task;
|
||||||
|
const std::string instance;
|
||||||
|
|
||||||
|
satprob_info(const std::string& instance)
|
||||||
|
: premin_time{-1}
|
||||||
|
, reorg_time{-1}
|
||||||
|
, partsol_time{-1}
|
||||||
|
, player_incomp_time{-1}
|
||||||
|
, incomp_time{-1}
|
||||||
|
, split_all_let_time{-1}
|
||||||
|
, split_min_let_time{-1}
|
||||||
|
, split_cstr_time{-1}
|
||||||
|
, prob_init_build_time{-1}
|
||||||
|
, sat_time{-1}
|
||||||
|
, build_time{-1}
|
||||||
|
, refine_time{-1}
|
||||||
|
, total_time{-1}
|
||||||
|
, n_classes{-1}
|
||||||
|
, n_refinement{-1}
|
||||||
|
, n_lit{-1}
|
||||||
|
, n_clauses{-1}
|
||||||
|
, n_iteration{-1}
|
||||||
|
, n_bisim_let{-1}
|
||||||
|
, n_min_states{-1}
|
||||||
|
, done{-1}
|
||||||
|
, task{}
|
||||||
|
, instance{instance+","}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void start()
|
||||||
|
{
|
||||||
|
sw.start();
|
||||||
|
}
|
||||||
|
double stop()
|
||||||
|
{
|
||||||
|
return sw.stop();
|
||||||
|
}
|
||||||
|
double restart()
|
||||||
|
{
|
||||||
|
double res = sw.stop();
|
||||||
|
sw.start();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// Writing also "flushes"
|
||||||
|
void write()
|
||||||
|
{
|
||||||
|
auto f = [](std::ostream& o, auto& v, bool sep = true)
|
||||||
|
{
|
||||||
|
if (v >= 0)
|
||||||
|
o << v;
|
||||||
|
if (sep)
|
||||||
|
o.put(',');
|
||||||
|
v = -1;
|
||||||
|
};
|
||||||
|
if (!sat_csv_file)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& out = *sat_csv_file;
|
||||||
|
if (out.tellp() == 0)
|
||||||
|
{
|
||||||
|
out << "instance,task,premin_time,reorg_time,partsol_time,"
|
||||||
|
<< "player_incomp_time,incomp_time,split_all_let_time,"
|
||||||
|
<< "split_min_let_time,split_cstr_time,prob_init_build_time,"
|
||||||
|
<< "sat_time,build_time,refine_time,total_time,n_classes,"
|
||||||
|
<< "n_refinement,n_lit,n_clauses,n_iteration,n_bisim_let,"
|
||||||
|
<< "n_min_states,done\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!task.empty());
|
||||||
|
out << instance;
|
||||||
|
out << task;
|
||||||
|
task = "";
|
||||||
|
out.put(',');
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
f(ss, premin_time);
|
||||||
|
f(ss, reorg_time);
|
||||||
|
f(ss, partsol_time);
|
||||||
|
f(ss, player_incomp_time);
|
||||||
|
f(ss, incomp_time);
|
||||||
|
f(ss, split_all_let_time);
|
||||||
|
f(ss, split_min_let_time);
|
||||||
|
f(ss, split_cstr_time);
|
||||||
|
f(ss, prob_init_build_time);
|
||||||
|
f(ss, sat_time);
|
||||||
|
f(ss, build_time);
|
||||||
|
f(ss, refine_time);
|
||||||
|
f(ss, total_time);
|
||||||
|
f(ss, n_classes);
|
||||||
|
f(ss, n_refinement);
|
||||||
|
f(ss, n_lit);
|
||||||
|
f(ss, n_clauses);
|
||||||
|
f(ss, n_iteration);
|
||||||
|
f(ss, n_bisim_let);
|
||||||
|
f(ss, n_min_states);
|
||||||
|
f(ss, done, false);
|
||||||
|
out << ss.str();
|
||||||
|
out.put('\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
template <class CONT>
|
template <class CONT>
|
||||||
bool all_of(const CONT& c)
|
bool all_of(const CONT& c)
|
||||||
|
|
@ -1125,7 +1266,7 @@ namespace
|
||||||
|
|
||||||
square_matrix<bool, true>
|
square_matrix<bool, true>
|
||||||
compute_incomp(const_twa_graph_ptr mm, const unsigned n_env,
|
compute_incomp(const_twa_graph_ptr mm, const unsigned n_env,
|
||||||
stopwatch& sw)
|
satprob_info& si)
|
||||||
{
|
{
|
||||||
const unsigned n_tot = mm->num_states();
|
const unsigned n_tot = mm->num_states();
|
||||||
|
|
||||||
|
|
@ -1201,7 +1342,7 @@ namespace
|
||||||
return inc_player.get(ps2c[s1].second, ps2c[s2].second);
|
return inc_player.get(ps2c[s1].second, ps2c[s2].second);
|
||||||
};
|
};
|
||||||
|
|
||||||
dotimeprint << "Done computing player incomp " << sw.stop() << '\n';
|
si.player_incomp_time = si.restart();
|
||||||
|
|
||||||
#ifdef TRACE
|
#ifdef TRACE
|
||||||
trace << "player cond id incomp\n";
|
trace << "player cond id incomp\n";
|
||||||
|
|
@ -1284,7 +1425,7 @@ namespace
|
||||||
trace << "Env state incomp\n";
|
trace << "Env state incomp\n";
|
||||||
inc_env.print(std::cerr);
|
inc_env.print(std::cerr);
|
||||||
#endif
|
#endif
|
||||||
|
si.incomp_time = si.restart();
|
||||||
return inc_env;
|
return inc_env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1912,26 +2053,22 @@ namespace
|
||||||
std::pair<twa_graph_ptr, reduced_alphabet_t>
|
std::pair<twa_graph_ptr, reduced_alphabet_t>
|
||||||
reduce_and_split(const_twa_graph_ptr mmw, const unsigned n_env,
|
reduce_and_split(const_twa_graph_ptr mmw, const unsigned n_env,
|
||||||
const square_matrix<bool, true>& incompmat,
|
const square_matrix<bool, true>& incompmat,
|
||||||
stopwatch& sw)
|
satprob_info& si)
|
||||||
{
|
{
|
||||||
reduced_alphabet_t red;
|
reduced_alphabet_t red;
|
||||||
|
si.start();
|
||||||
|
|
||||||
std::tie(red.n_groups, red.which_group) = trans_comp_classes(incompmat);
|
std::tie(red.n_groups, red.which_group) = trans_comp_classes(incompmat);
|
||||||
dotimeprint << "Done trans comp " << red.n_groups
|
|
||||||
<< " - " << sw.stop() << '\n';
|
|
||||||
|
|
||||||
compute_all_letters(red, mmw, n_env);
|
compute_all_letters(red, mmw, n_env);
|
||||||
dotimeprint << "Done comp all letters " << " - " << sw.stop() << '\n';
|
si.split_all_let_time = si.restart();
|
||||||
|
|
||||||
compute_minimal_letters(red, mmw, n_env);
|
compute_minimal_letters(red, mmw, n_env);
|
||||||
#ifdef MINTIMINGS
|
si.split_min_let_time = si.restart();
|
||||||
dotimeprint << "Done comp all min sim letters ";
|
si.n_bisim_let = red.n_red_sigma;
|
||||||
for (const auto& al : red.bisim_letters)
|
|
||||||
dotimeprint << al.size() << ' ';
|
|
||||||
dotimeprint << " - " << sw.stop() << '\n';
|
|
||||||
#endif
|
|
||||||
|
|
||||||
twa_graph_ptr split_mmw = split_on_minimal(red, mmw, n_env);
|
twa_graph_ptr split_mmw = split_on_minimal(red, mmw, n_env);
|
||||||
dotimeprint << "Done splitting " << sw.stop() << '\n';
|
si.split_cstr_time = si.restart();
|
||||||
trace << std::endl;
|
trace << std::endl;
|
||||||
|
|
||||||
return std::make_pair(split_mmw, red);
|
return std::make_pair(split_mmw, red);
|
||||||
|
|
@ -2234,9 +2371,10 @@ namespace
|
||||||
struct mm_sat_prob_t<true>
|
struct mm_sat_prob_t<true>
|
||||||
{
|
{
|
||||||
mm_sat_prob_t(unsigned n_classes, unsigned n_env,
|
mm_sat_prob_t(unsigned n_classes, unsigned n_env,
|
||||||
unsigned n_sigma_red)
|
unsigned n_sigma_red, satprob_info& si)
|
||||||
: lm(n_classes, n_env, n_sigma_red)
|
: lm(n_classes, n_env, n_sigma_red)
|
||||||
, n_classes{lm.n_classes_}
|
, n_classes{lm.n_classes_}
|
||||||
|
, si{si}
|
||||||
{
|
{
|
||||||
state_cover_clauses.reserve(n_classes);
|
state_cover_clauses.reserve(n_classes);
|
||||||
trans_cover_clauses.reserve(n_classes*n_sigma_red);
|
trans_cover_clauses.reserve(n_classes*n_sigma_red);
|
||||||
|
|
@ -2288,6 +2426,13 @@ namespace
|
||||||
// res[i] == -1 : i not used in lit mapper
|
// res[i] == -1 : i not used in lit mapper
|
||||||
// res[i] == 0 : i is assigned false
|
// res[i] == 0 : i is assigned false
|
||||||
// res[i] == 1 : i is assigned true
|
// res[i] == 1 : i is assigned true
|
||||||
|
if (sat_dimacs_file)
|
||||||
|
{
|
||||||
|
fprintf(sat_dimacs_file->f,
|
||||||
|
"c ### Next Instance %lld %lld ###\n",
|
||||||
|
this->si.n_classes, this->si.n_refinement);
|
||||||
|
picosat_print(lm.psat_, sat_dimacs_file->f);
|
||||||
|
}
|
||||||
switch (picosat_sat(lm.psat_, -1))
|
switch (picosat_sat(lm.psat_, -1))
|
||||||
{
|
{
|
||||||
case PICOSAT_UNSATISFIABLE:
|
case PICOSAT_UNSATISFIABLE:
|
||||||
|
|
@ -2353,6 +2498,8 @@ namespace
|
||||||
std::unordered_map<int, std::vector<bdd>> cube_map;
|
std::unordered_map<int, std::vector<bdd>> cube_map;
|
||||||
// A map that indicates if two cubes are compatible or not via their id
|
// A map that indicates if two cubes are compatible or not via their id
|
||||||
std::unordered_map<std::pair<int, int>, bool, pair_hash> cube_incomp_map;
|
std::unordered_map<std::pair<int, int>, bool, pair_hash> cube_incomp_map;
|
||||||
|
// Piggy-back a struct for performance measure
|
||||||
|
satprob_info& si;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|
@ -2431,14 +2578,15 @@ namespace
|
||||||
const square_matrix<bool, true>& incompmat,
|
const square_matrix<bool, true>& incompmat,
|
||||||
const reduced_alphabet_t& red,
|
const reduced_alphabet_t& red,
|
||||||
const part_sol_t& psol,
|
const part_sol_t& psol,
|
||||||
const unsigned n_env)
|
const unsigned n_env,
|
||||||
|
satprob_info& si)
|
||||||
{
|
{
|
||||||
const auto& psolv = psol.psol;
|
const auto& psolv = psol.psol;
|
||||||
const unsigned n_classes = psolv.size();
|
const unsigned n_classes = psolv.size();
|
||||||
const unsigned n_red = red.n_red_sigma;
|
const unsigned n_red = red.n_red_sigma;
|
||||||
const unsigned n_groups = red.n_groups;
|
const unsigned n_groups = red.n_groups;
|
||||||
|
|
||||||
mm_sat_prob_t<USE_PICO> mm_pb(n_classes, n_env, n_red);
|
mm_sat_prob_t<USE_PICO> mm_pb(n_classes, n_env, n_red, si);
|
||||||
|
|
||||||
auto& lm = mm_pb.lm;
|
auto& lm = mm_pb.lm;
|
||||||
|
|
||||||
|
|
@ -3372,7 +3520,7 @@ namespace
|
||||||
const reduced_alphabet_t& red,
|
const reduced_alphabet_t& red,
|
||||||
const part_sol_t& psol,
|
const part_sol_t& psol,
|
||||||
const unsigned n_env,
|
const unsigned n_env,
|
||||||
stopwatch& sw)
|
satprob_info& si)
|
||||||
{
|
{
|
||||||
const auto& psolv = psol.psol;
|
const auto& psolv = psol.psol;
|
||||||
const unsigned n_psol = psolv.size();
|
const unsigned n_psol = psolv.size();
|
||||||
|
|
@ -3393,15 +3541,16 @@ namespace
|
||||||
mm_pb.lm.print(std::cerr);
|
mm_pb.lm.print(std::cerr);
|
||||||
#endif
|
#endif
|
||||||
mm_pb.set_variable_clauses();
|
mm_pb.set_variable_clauses();
|
||||||
dotimeprint << "Done constructing SAT " << sw.stop() << '\n';
|
si.n_lit = mm_pb.n_lits();
|
||||||
dotimeprint << "n literals " << mm_pb.n_lits()
|
si.n_clauses = mm_pb.n_clauses();
|
||||||
<< " n clauses " << mm_pb.n_clauses() << '\n';
|
si.start();
|
||||||
auto sol = mm_pb.get_sol();
|
auto sol = mm_pb.get_sol();
|
||||||
dotimeprint << "Done solving SAT " << sw.stop() << '\n';
|
si.sat_time = si.restart();
|
||||||
|
|
||||||
if (sol.empty())
|
if (sol.empty())
|
||||||
{
|
{
|
||||||
mm_pb.unset_variable_clauses();
|
mm_pb.unset_variable_clauses();
|
||||||
|
si.write();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#ifdef TRACE
|
#ifdef TRACE
|
||||||
|
|
@ -3606,20 +3755,25 @@ namespace
|
||||||
for (const auto& el : used_ziaj_map)
|
for (const auto& el : used_ziaj_map)
|
||||||
if (el.second == bddfalse)
|
if (el.second == bddfalse)
|
||||||
infeasible_classes.emplace_back(el.first.i, el.first.a);
|
infeasible_classes.emplace_back(el.first.i, el.first.a);
|
||||||
|
si.build_time = si.restart();
|
||||||
|
|
||||||
if (!infeasible_classes.empty())
|
if (!infeasible_classes.empty())
|
||||||
{
|
{
|
||||||
// Remove the variable clauses
|
// Remove the variable clauses
|
||||||
// This is suboptimal but the contexts form a stack so...
|
// This is suboptimal but the contexts form a stack so...
|
||||||
dotimeprint << "Refining constraints for "
|
auto oldrefine = si.n_refinement;
|
||||||
<< infeasible_classes.size() << " classses.\n";
|
si.write();
|
||||||
|
si.task = "refinement";
|
||||||
|
si.n_classes = n_classes;
|
||||||
|
si.n_refinement = oldrefine + infeasible_classes.size();
|
||||||
mm_pb.unset_variable_clauses();
|
mm_pb.unset_variable_clauses();
|
||||||
add_bdd_cond_constr(mm_pb, mmw, red, n_env,
|
add_bdd_cond_constr(mm_pb, mmw, red, n_env,
|
||||||
infeasible_classes, x_in_class);
|
infeasible_classes, x_in_class);
|
||||||
|
si.refine_time = si.restart();
|
||||||
continue; //retry
|
continue; //retry
|
||||||
}
|
}
|
||||||
|
|
||||||
cstr_split_mealy(minmach, red, x_in_class, used_ziaj_map);
|
cstr_split_mealy(minmach, red, x_in_class, used_ziaj_map);
|
||||||
|
|
||||||
// todo: What is the impact of chosing one of the possibilities
|
// todo: What is the impact of chosing one of the possibilities
|
||||||
minmach->set_init_state(init_class_v.front());
|
minmach->set_init_state(init_class_v.front());
|
||||||
return minmach;
|
return minmach;
|
||||||
|
|
@ -3634,8 +3788,10 @@ namespace spot
|
||||||
{
|
{
|
||||||
assert(is_mealy(mm));
|
assert(is_mealy(mm));
|
||||||
|
|
||||||
stopwatch sw;
|
satprob_info si(sat_instance_name);
|
||||||
sw.start();
|
si.task = "presat";
|
||||||
|
stopwatch sglob;
|
||||||
|
sglob.start();
|
||||||
|
|
||||||
if ((premin < -1) || (premin > 1))
|
if ((premin < -1) || (premin > 1))
|
||||||
throw std::runtime_error("premin has to be -1, 0 or 1");
|
throw std::runtime_error("premin has to be -1, 0 or 1");
|
||||||
|
|
@ -3672,7 +3828,9 @@ namespace spot
|
||||||
|
|
||||||
const_twa_graph_ptr mmw = do_premin();
|
const_twa_graph_ptr mmw = do_premin();
|
||||||
assert(is_split_mealy(mmw));
|
assert(is_split_mealy(mmw));
|
||||||
dotimeprint << "Done premin " << sw.stop() << '\n';
|
si.premin_time = si.restart();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 0 -> "Env" next is input props
|
// 0 -> "Env" next is input props
|
||||||
// 1 -> "Player" next is output prop
|
// 1 -> "Player" next is output prop
|
||||||
|
|
@ -3690,24 +3848,24 @@ namespace spot
|
||||||
print_hoa(std::cerr, mmw);
|
print_hoa(std::cerr, mmw);
|
||||||
#endif
|
#endif
|
||||||
assert(n_env != -1u);
|
assert(n_env != -1u);
|
||||||
dotimeprint << "Done reorganise " << n_env << " - "
|
si.reorg_time = si.restart();
|
||||||
<< sw.stop() << '\n';
|
|
||||||
|
|
||||||
// Compute incompatibility based on bdd
|
// Compute incompatibility based on bdd
|
||||||
auto incompmat = compute_incomp(mmw, n_env, sw);
|
auto incompmat = compute_incomp(mmw, n_env, si);
|
||||||
dotimeprint << "Done incompatibility " << sw.stop() << '\n';
|
|
||||||
#ifdef TRACE
|
#ifdef TRACE
|
||||||
incompmat.print(std::cerr);
|
incompmat.print(std::cerr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Get a partial solution
|
// Get a partial solution
|
||||||
auto partsol = get_part_sol(incompmat);
|
auto partsol = get_part_sol(incompmat);
|
||||||
dotimeprint << "Done partial solution " << partsol.psol.size()
|
si.partsol_time = si.restart();
|
||||||
<< " - " << sw.stop() << '\n';
|
|
||||||
|
|
||||||
|
|
||||||
auto early_exit = [&]()
|
auto early_exit = [&]()
|
||||||
{
|
{
|
||||||
|
si.done = 1;
|
||||||
|
si.total_time = sglob.stop();
|
||||||
|
si.write();
|
||||||
// Always keep machines split
|
// Always keep machines split
|
||||||
if (mm->get_named_prop<region_t>("state-player"))
|
if (mm->get_named_prop<region_t>("state-player"))
|
||||||
assert(is_split_mealy_specialization(mm, mmw));
|
assert(is_split_mealy_specialization(mm, mmw));
|
||||||
|
|
@ -3721,58 +3879,78 @@ namespace spot
|
||||||
// states as the original automaton -> we are done
|
// states as the original automaton -> we are done
|
||||||
if (partsol.psol.size() == n_env)
|
if (partsol.psol.size() == n_env)
|
||||||
{
|
{
|
||||||
dotimeprint << "Done trans comp " << 1 << " - " << sw.stop() << '\n';
|
|
||||||
dotimeprint << "Done comp all letters " << " - "
|
|
||||||
<< sw.stop() << '\n';
|
|
||||||
#ifdef MINTIMINGS
|
|
||||||
dotimeprint << "Done comp all min sim letters 0 - "
|
|
||||||
<< sw.stop() << '\n';
|
|
||||||
#endif
|
|
||||||
dotimeprint << "Done splitting " << sw.stop() << '\n';
|
|
||||||
dotimeprint << "Done split and reduce " << sw.stop() << '\n';
|
|
||||||
dotimeprint << "Done build init prob " << sw.stop() << '\n';
|
|
||||||
dotimeprint << "Done minimizing - " << mmw->num_states()
|
|
||||||
<< " - " << sw.stop() << '\n';
|
|
||||||
return early_exit();
|
return early_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the reduced alphabet
|
// Get the reduced alphabet
|
||||||
auto [split_mmw, reduced_alphabet] =
|
auto [split_mmw, reduced_alphabet] =
|
||||||
reduce_and_split(mmw, n_env, incompmat, sw);
|
reduce_and_split(mmw, n_env, incompmat, si);
|
||||||
dotimeprint << "Done split and reduce " << sw.stop() << '\n';
|
|
||||||
|
|
||||||
auto mm_pb = build_init_prob<true>(split_mmw, incompmat,
|
auto mm_pb = build_init_prob<true>(split_mmw, incompmat,
|
||||||
reduced_alphabet, partsol, n_env);
|
reduced_alphabet, partsol, n_env, si);
|
||||||
dotimeprint << "Done build init prob " << sw.stop() << '\n';
|
si.prob_init_build_time = si.restart();
|
||||||
|
si.write();
|
||||||
|
|
||||||
twa_graph_ptr minmachine = nullptr;
|
twa_graph_ptr minmachine = nullptr;
|
||||||
for (size_t n_classes = partsol.psol.size();
|
for (size_t n_classes = partsol.psol.size();
|
||||||
n_classes < n_env; ++n_classes)
|
n_classes < n_env; ++n_classes)
|
||||||
{
|
{
|
||||||
|
if (si.task.empty())
|
||||||
|
si.task = "sat";
|
||||||
|
si.n_iteration = (n_classes-partsol.psol.size());
|
||||||
|
si.n_refinement = 0;
|
||||||
|
si.n_classes = n_classes;
|
||||||
|
|
||||||
minmachine = try_build_min_machine(mm_pb, mmw,
|
minmachine = try_build_min_machine(mm_pb, mmw,
|
||||||
reduced_alphabet,
|
reduced_alphabet,
|
||||||
partsol, n_env,
|
partsol, n_env,
|
||||||
sw);
|
si);
|
||||||
dotimeprint << "Done try_build " << n_classes
|
|
||||||
<< " - " << sw.stop() << '\n';
|
|
||||||
|
|
||||||
if (minmachine)
|
if (minmachine)
|
||||||
break;
|
break;
|
||||||
increment_classes(split_mmw, incompmat, reduced_alphabet,
|
increment_classes(split_mmw, incompmat, reduced_alphabet,
|
||||||
partsol, mm_pb);
|
partsol, mm_pb);
|
||||||
dotimeprint << "Done incrementing " << sw.stop() << '\n';
|
|
||||||
}
|
}
|
||||||
// Is already minimal -> Return a copy
|
// Is already minimal -> Return a copy
|
||||||
// Set state players!
|
// Set state players!
|
||||||
if (!minmachine)
|
if (!minmachine)
|
||||||
return early_exit();
|
return early_exit();
|
||||||
set_synthesis_outputs(minmachine, get_synthesis_outputs(mm));
|
set_synthesis_outputs(minmachine, get_synthesis_outputs(mm));
|
||||||
dotimeprint << "Done minimizing - " << minmachine->num_states()
|
|
||||||
<< " - " << sw.stop() << '\n';
|
si.done=1;
|
||||||
|
si.n_min_states = minmachine->num_states();
|
||||||
|
si.total_time = sglob.stop();
|
||||||
|
si.write();
|
||||||
|
|
||||||
assert(is_split_mealy_specialization(mm, minmachine));
|
assert(is_split_mealy_specialization(mm, minmachine));
|
||||||
return minmachine;
|
return minmachine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
twa_graph_ptr
|
||||||
|
minimize_mealy(const const_twa_graph_ptr& mm,
|
||||||
|
synthesis_info& si)
|
||||||
|
{
|
||||||
|
if ((si.minimize_lvl < 3) || (5 < si.minimize_lvl))
|
||||||
|
throw std::runtime_error("Invalid option");
|
||||||
|
|
||||||
|
std::string csvfile = si.opt.get_str("satlogcsv");
|
||||||
|
std::string dimacsfile = si.opt.get_str("satlogdimacs");
|
||||||
|
|
||||||
|
if (!csvfile.empty())
|
||||||
|
sat_csv_file
|
||||||
|
= std::make_unique<std::ofstream>(csvfile,
|
||||||
|
std::ios_base::ate
|
||||||
|
| std::ios_base::app);
|
||||||
|
if (!dimacsfile.empty())
|
||||||
|
sat_dimacs_file
|
||||||
|
= std::make_unique<fwrapper>(dimacsfile);
|
||||||
|
sat_instance_name = si.opt.get_str("satinstancename");
|
||||||
|
auto res = minimize_mealy(mm, si.minimize_lvl-4);
|
||||||
|
sat_csv_file.reset();
|
||||||
|
sat_dimacs_file.reset();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace spot
|
namespace spot
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,27 @@ namespace spot
|
||||||
SPOT_API twa_graph_ptr
|
SPOT_API twa_graph_ptr
|
||||||
minimize_mealy(const const_twa_graph_ptr& mm, int premin = -1);
|
minimize_mealy(const const_twa_graph_ptr& mm, int premin = -1);
|
||||||
|
|
||||||
|
/// \ingroup mealy
|
||||||
|
/// \brief Minimizes an (in)completely specified mealy machine
|
||||||
|
///
|
||||||
|
/// The approach is described in \cite renkin.22.forte.
|
||||||
|
///
|
||||||
|
/// \param si synthesis_info structure used to store data for benchmarking
|
||||||
|
/// and indicates which premin level to use
|
||||||
|
///
|
||||||
|
/// \return A split mealy machines which is a minimal
|
||||||
|
/// specialization of the original machine.
|
||||||
|
///
|
||||||
|
/// \note Enabling \a premin will remove finite traces.
|
||||||
|
/// \note If si.opt contains an option "satlogcsv" detailed results will be
|
||||||
|
/// stored in this file. If it contains "satlogdimacs" all sat problems will
|
||||||
|
/// stored.
|
||||||
|
/// \see is_split_mealy_specialization
|
||||||
|
|
||||||
|
SPOT_API twa_graph_ptr
|
||||||
|
minimize_mealy(const const_twa_graph_ptr& mm,
|
||||||
|
synthesis_info& si);
|
||||||
|
|
||||||
|
|
||||||
/// \ingroup mealy
|
/// \ingroup mealy
|
||||||
/// \brief Test if the split mealy machine \a right is a specialization of
|
/// \brief Test if the split mealy machine \a right is a specialization of
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -143,6 +143,10 @@ def canonicalize(s, type, ignores):
|
||||||
# timing result we cannot compare between runs.
|
# timing result we cannot compare between runs.
|
||||||
s = re.sub(r'<table.*dataframe.*?enc.user.*?</table>', '<table></table>', s,
|
s = re.sub(r'<table.*dataframe.*?enc.user.*?</table>', '<table></table>', s,
|
||||||
flags=re.DOTALL)
|
flags=re.DOTALL)
|
||||||
|
# Table that contains premin_time are log from the mealy minimization.
|
||||||
|
# They contain timing result so we cannot compare between runs.
|
||||||
|
s = re.sub(r'<table.*dataframe.*?premin_time.*?</table>', '<table></table>',
|
||||||
|
s, flags=re.DOTALL)
|
||||||
|
|
||||||
for n, p in enumerate(ignores):
|
for n, p in enumerate(ignores):
|
||||||
s = re.sub(p, 'IGN{}'.format(n), s)
|
s = re.sub(p, 'IGN{}'.format(n), s)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 1,
|
"execution_count": 1,
|
||||||
|
"id": "4f84fa79",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
|
|
@ -13,6 +14,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "4ad017a0",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"This notebook presents functions that can be used to solve the Reactive Synthesis problem using games.\n",
|
"This notebook presents functions that can be used to solve the Reactive Synthesis problem using games.\n",
|
||||||
|
|
@ -37,6 +39,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 2,
|
||||||
|
"id": "e333be09",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -655,6 +658,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "4d030586",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Solving the game, is done with `solve_game()` as with any game. There is also a version that takes a `synthesis_info` as second argument in case the time it takes has to be recorded. Here passing `si` or not makes no difference."
|
"Solving the game, is done with `solve_game()` as with any game. There is also a version that takes a `synthesis_info` as second argument in case the time it takes has to be recorded. Here passing `si` or not makes no difference."
|
||||||
|
|
@ -663,6 +667,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 3,
|
||||||
|
"id": "f13ac820",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -1222,6 +1227,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "98aa1402",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Once a strategy has been found, it can be extracted as an automaton and simplified using 6 different levels (the default is 2). The output should be interpreted as a Mealy automaton, where transition have the form `(ins)&(outs)` where `ins` and `outs` are Boolean formulas representing possible inputs and outputs (they could be more than just conjunctions of atomic proposition). Mealy machines with this type of labels are called \"separated\" in Spot."
|
"Once a strategy has been found, it can be extracted as an automaton and simplified using 6 different levels (the default is 2). The output should be interpreted as a Mealy automaton, where transition have the form `(ins)&(outs)` where `ins` and `outs` are Boolean formulas representing possible inputs and outputs (they could be more than just conjunctions of atomic proposition). Mealy machines with this type of labels are called \"separated\" in Spot."
|
||||||
|
|
@ -1230,6 +1236,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 4,
|
||||||
|
"id": "4c93add7",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -2228,6 +2235,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "9d8d52f6",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"If needed, a separated Mealy machine can be turned into game shape using `split_sepearated_mealy()`, which is more efficient than `split_2step()`."
|
"If needed, a separated Mealy machine can be turned into game shape using `split_sepearated_mealy()`, which is more efficient than `split_2step()`."
|
||||||
|
|
@ -2236,6 +2244,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 5,
|
||||||
|
"id": "707f4cf6",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -2517,6 +2526,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "b9e4412e",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# Converting the separated Mealy machine to AIG\n",
|
"# Converting the separated Mealy machine to AIG\n",
|
||||||
|
|
@ -2529,6 +2539,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 6,
|
"execution_count": 6,
|
||||||
|
"id": "9f344931",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -2604,6 +2615,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "92bbe8d0",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"While we are at it, let us mention that you can render those circuits horizontally as follows:"
|
"While we are at it, let us mention that you can render those circuits horizontally as follows:"
|
||||||
|
|
@ -2612,6 +2624,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 7,
|
"execution_count": 7,
|
||||||
|
"id": "3ae7ce32",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -2687,6 +2700,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "44fbc0ac",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"To encode the circuit in the AIGER format (ASCII version) use:"
|
"To encode the circuit in the AIGER format (ASCII version) use:"
|
||||||
|
|
@ -2695,6 +2709,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 8,
|
"execution_count": 8,
|
||||||
|
"id": "566715d5",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -2718,6 +2733,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "ef304f36",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# Adding more inputs and outputs by force"
|
"# Adding more inputs and outputs by force"
|
||||||
|
|
@ -2725,6 +2741,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "5c2b0b78",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"It can happen that propositions declared as output are ommited in the aig circuit (either because they are not part of the specification, or because they do not appear in the winning strategy). In that case those \n",
|
"It can happen that propositions declared as output are ommited in the aig circuit (either because they are not part of the specification, or because they do not appear in the winning strategy). In that case those \n",
|
||||||
|
|
@ -2736,6 +2753,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 9,
|
"execution_count": 9,
|
||||||
|
"id": "874c7df1",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -3260,6 +3278,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "c564dba3",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"To force the presence of extra variables in the circuit, they can be passed to `mealy_machine_to_aig()`."
|
"To force the presence of extra variables in the circuit, they can be passed to `mealy_machine_to_aig()`."
|
||||||
|
|
@ -3268,6 +3287,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 10,
|
"execution_count": 10,
|
||||||
|
"id": "c31a3b38",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -3378,6 +3398,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "3323fc84",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# Combining Mealy machines\n",
|
"# Combining Mealy machines\n",
|
||||||
|
|
@ -3397,6 +3418,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 11,
|
"execution_count": 11,
|
||||||
|
"id": "5d8e4cdb",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -3991,6 +4013,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "c7a1986f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# Reading an AIGER-file\n",
|
"# Reading an AIGER-file\n",
|
||||||
|
|
@ -4005,6 +4028,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 12,
|
"execution_count": 12,
|
||||||
|
"id": "a10d7e3b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
|
|
@ -4025,6 +4049,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 13,
|
"execution_count": 13,
|
||||||
|
"id": "2c40e19b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4149,6 +4174,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 14,
|
"execution_count": 14,
|
||||||
|
"id": "0ad6c566",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4177,6 +4203,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 15,
|
"execution_count": 15,
|
||||||
|
"id": "2e1996c1",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4193,6 +4220,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "41a8e042",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"An AIG circuit can be transformed into a monitor/Mealy machine. This can be used for instance to check that it does not intersect the negation of the specification."
|
"An AIG circuit can be transformed into a monitor/Mealy machine. This can be used for instance to check that it does not intersect the negation of the specification."
|
||||||
|
|
@ -4201,6 +4229,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 16,
|
"execution_count": 16,
|
||||||
|
"id": "7399ea38",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4268,6 +4297,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
|
"id": "7ac06afc",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Note that the generation of aiger circuits from Mealy machines is flexible and accepts separated Mealy machines\n",
|
"Note that the generation of aiger circuits from Mealy machines is flexible and accepts separated Mealy machines\n",
|
||||||
|
|
@ -4277,6 +4307,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 17,
|
"execution_count": 17,
|
||||||
|
"id": "bac68923",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4424,6 +4455,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 18,
|
"execution_count": 18,
|
||||||
|
"id": "03ceb2a8",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
|
|
@ -4626,7 +4658,7 @@
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"display_name": "Python 3",
|
"display_name": "Python 3 (ipykernel)",
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"name": "python3"
|
"name": "python3"
|
||||||
},
|
},
|
||||||
|
|
@ -4640,7 +4672,7 @@
|
||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.7.3"
|
"version": "3.8.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue