// license:GPL-2.0+ // copyright-holders:Couriersud #include "nl_factory.h" #include "core/setup.h" #include "nl_setup.h" // FIXME: only needed for splitter code #include "nld_matrix_solver.h" #include "nld_ms_direct.h" #include "nld_ms_direct1.h" #include "nld_ms_direct2.h" #include "nld_ms_gcr.h" #include "nld_ms_gmres.h" #include "nld_ms_sm.h" #include "nld_ms_sor.h" #include "nld_ms_sor_mat.h" #include "nld_ms_w.h" #include "nld_solver.h" #include "plib/pomp.h" #include "plib/ptimed_queue.h" #include #include namespace netlist { namespace devices { // ---------------------------------------------------------------------------------------- // solver // ---------------------------------------------------------------------------------------- NETLIB_RESET(solver) { if (exec().use_stats()) m_fb_step.set_delegate(NETLIB_DELEGATE(fb_step)); for (auto &s : m_mat_solvers) s->reset(); for (auto &s : m_mat_solvers) m_queue.push({netlist_time_ext::zero(), s.get()}); } void NETLIB_NAME(solver)::stop() { for (auto &s : m_mat_solvers) s->log_stats(); } #if 1 template NETLIB_HANDLER(solver, fb_step) { const netlist_time_ext now(exec().time()); const std::size_t nthreads = m_params.m_parallel() < 2 ? 1 : std::min(static_cast(m_params.m_parallel()), plib::omp::get_max_threads()); const netlist_time_ext sched(now + (nthreads <= 1 ? netlist_time_ext::zero() : netlist_time_ext::from_nsec(100))); plib::uninitialised_array tmp; //NOLINT plib::uninitialised_array nt; //NOLINT std::size_t p=0; while (!m_queue.empty()) { const auto t = m_queue.top().exec_time(); auto *o = m_queue.top().object(); if (t != now) if (t > sched) break; tmp[p++] = o; m_queue.pop(); } // FIXME: Disabled for now since parallel processing will decrease performance // for tested applications. More testing required here if (true || nthreads < 2) { if (!KEEP_STATS) { for (std::size_t i = 0; i < p; i++) nt[i] = tmp[i]->solve(now, "no-parallel"); } else { stats()->m_stat_total_time.stop(); for (std::size_t i = 0; i < p; i++) { tmp[i]->stats()->m_stat_call_count.inc(); auto g(tmp[i]->stats()->m_stat_total_time.guard()); nt[i] = tmp[i]->solve(now, "no-parallel"); } stats()->m_stat_total_time.start(); } for (std::size_t i = 0; i < p; i++) { if (nt[i] != netlist_time::zero()) m_queue.push({now + nt[i], tmp[i]}); tmp[i]->update_inputs(); } } else { plib::omp::set_num_threads(nthreads); plib::omp::for_static(static_cast(0), p, [&tmp, &nt,now](std::size_t i) { nt[i] = tmp[i]->solve(now, "parallel"); }); for (std::size_t i = 0; i < p; i++) { if (nt[i] != netlist_time::zero()) m_queue.push({now + nt[i], tmp[i]}); tmp[i]->update_inputs(); } } if (!m_queue.empty()) m_Q_step.net().toggle_and_push_to_queue(m_queue.top().exec_time() - now); } void NETLIB_NAME(solver) :: reschedule(solver::matrix_solver_t *solv, netlist_time ts) { const netlist_time_ext now(exec().time()); const netlist_time_ext sched(now + ts); m_queue.remove(solv); m_queue.push({sched, solv}); if (m_Q_step.net().is_queued()) { if (m_Q_step.net().next_scheduled_time() > sched) m_Q_step.net().toggle_and_push_to_queue(ts); } else m_Q_step.net().toggle_and_push_to_queue(ts); } #else NETLIB_HANDLER(solver, fb_step) { if (m_params.m_dynamic_ts) return; netlist_time_ext now(exec().time()); // force solving during start up if there are no time-step devices // FIXME: Needs a more elegant solution bool force_solve = (now < netlist_time_ext::from_fp(2 * m_params.m_max_timestep)); std::size_t nthreads = std::min(static_cast(m_params.m_parallel()), plib::omp::get_max_threads()); std::vector &solvers = (force_solve ? m_mat_solvers_all : m_mat_solvers_timestepping); if (nthreads > 1 && solvers.size() > 1) { plib::omp::set_num_threads(nthreads); plib::omp::for_static(static_cast(0), solvers.size(), [&solvers, now](std::size_t i) { const netlist_time ts = solvers[i]->ptr->solve(now); plib::unused_var(ts); }); } else for (auto & solver : solvers) { const netlist_time ts = solver->ptr->solve(now); plib::unused_var(ts); } for (auto & solver : solvers) solver->ptr->update_inputs(); // step circuit if (!m_Q_step.net().is_queued()) { m_Q_step.net().toggle_and_push_to_queue(netlist_time::from_fp(m_params.m_max_timestep)); } } #endif // FIXME: should be created in device space template NETLIB_NAME(solver)::solver_ptr create_it(NETLIB_NAME(solver) &main_solver, pstring name, NETLIB_NAME(solver)::net_list_t &nets, const solver::solver_parameters_t *params, std::size_t size) { return plib::make_unique(main_solver, name, nets, params, size); } template NETLIB_NAME(solver)::solver_ptr NETLIB_NAME(solver)::create_solver(std::size_t size, const pstring &solvername, const solver::solver_parameters_t *params, NETLIB_NAME(solver)::net_list_t &nets) { switch (params->m_method()) { case solver::matrix_type_e::MAT_CR: return create_it>(*this, solvername, nets, params, size); case solver::matrix_type_e::MAT: return create_it>(*this, solvername, nets, params, size); case solver::matrix_type_e::GMRES: return create_it>(*this, solvername, nets, params, size); #if (NL_USE_ACADEMIC_SOLVERS) case solver::matrix_type_e::SOR: return create_it>(*this, solvername, nets, params, size); case solver::matrix_type_e::SOR_MAT: return create_it>(*this, solvername, nets, params, size); case solver::matrix_type_e::SM: // Sherman-Morrison Formula return create_it>(*this, solvername, nets, params, size); case solver::matrix_type_e::W: // Woodbury Formula return create_it>(*this, solvername, nets, params, size); #else //case solver::matrix_type_e::GMRES: case solver::matrix_type_e::SOR: case solver::matrix_type_e::SOR_MAT: case solver::matrix_type_e::SM: case solver::matrix_type_e::W: state().log().warning(MW_SOLVER_METHOD_NOT_SUPPORTED(params->m_method().name(), "MAT_CR")); return create_it>(*this, solvername, nets, params, size); #endif } return solver_ptr(); } template NETLIB_NAME(solver)::solver_ptr NETLIB_NAME(solver)::create_solvers( const pstring &sname, const solver::solver_parameters_t *params, net_list_t &nets) { std::size_t net_count = nets.size(); switch (net_count) { #if !defined(__EMSCRIPTEN__) case 1: return plib::make_unique, device_arena>(*this, sname, nets, params); case 2: return plib::make_unique, device_arena>(*this, sname, nets, params); case 3: return create_solver(3, sname, params, nets); case 4: return create_solver(4, sname, params, nets); case 5: return create_solver(5, sname, params, nets); case 6: return create_solver(6, sname, params, nets); case 7: return create_solver(7, sname, params, nets); case 8: return create_solver(8, sname, params, nets); #endif default: log().info(MI_NO_SPECIFIC_SOLVER(net_count)); if (net_count <= 16) { return create_solver(net_count, sname, params, nets); } if (net_count <= 32) { return create_solver(net_count, sname, params, nets); } if (net_count <= 64) { return create_solver(net_count, sname, params, nets); } if (net_count <= 128) { return create_solver(net_count, sname, params, nets); } if (net_count <= 256) { return create_solver(net_count, sname, params, nets); } if (net_count <= 512) { return create_solver(net_count, sname, params, nets); } return create_solver(net_count, sname, params, nets); } } struct net_splitter { void run(netlist_state_t &netlist) { for (auto & net : netlist.nets()) { netlist.log().verbose("processing {1}", net->name()); if (!net->is_rail_net() && net->has_connections()) { netlist.log().verbose(" ==> not a rail net"); // Must be an analog net auto &n = dynamic_cast(*net); if (!already_processed(n)) { groupspre.emplace_back(NETLIB_NAME(solver)::net_list_t()); process_net(netlist, n); } } } for (auto &g : groupspre) if (!g.empty()) groups.push_back(g); } std::vector groups; private: bool already_processed(const analog_net_t &n) const { // no need to process rail nets - these are known variables if (n.is_rail_net()) return true; // if it's already processed - no need to continue for (const auto & grp : groups) if (plib::container::contains(grp, &n)) return true; return false; } bool check_if_processed_and_join(const analog_net_t &n) { // no need to process rail nets - these are known variables if (n.is_rail_net()) return true; // First check if it is in a previous group. // In this case we need to merge this group into the current group if (groupspre.size() > 1) { for (std::size_t i = 0; iname(), static_cast(term->type())); // only process analog terminals if (term->is_type(detail::terminal_type::TERMINAL)) { auto &pt = dynamic_cast(*term); // check the connected terminal analog_net_t &connected_net = netlist.setup().get_connected_terminal(pt)->net(); netlist.log().verbose(" Connected net {}", connected_net.name()); if (!check_if_processed_and_join(connected_net)) process_net(netlist, connected_net); } } } } std::vector groupspre; }; void NETLIB_NAME(solver)::post_start() { log().verbose("Scanning net groups ..."); // determine net groups net_splitter splitter; splitter.run(state()); // setup the solvers log().verbose("Found {1} net groups in {2} nets\n", splitter.groups.size(), state().nets().size()); for (auto & grp : splitter.groups) { solver_ptr ms; pstring sname = plib::pfmt("Solver_{1}")(m_mat_solvers.size()); params_uptr params = plib::make_unique(*this, sname + ".", m_params); switch (params->m_fp_type()) { case solver::matrix_fp_type_e::FLOAT: if (!config::use_float_matrix()) log().info("FPTYPE {1} not supported. Using DOUBLE", params->m_fp_type().name()); ms = create_solvers>(sname, params.get(), grp); break; case solver::matrix_fp_type_e::DOUBLE: ms = create_solvers(sname, params.get(), grp); break; case solver::matrix_fp_type_e::LONGDOUBLE: if (!config::use_long_double_matrix()) log().info("FPTYPE {1} not supported. Using DOUBLE", params->m_fp_type().name()); ms = create_solvers>(sname, params.get(), grp); break; case solver::matrix_fp_type_e::FLOATQ128: #if (NL_USE_FLOAT128) ms = create_solvers(sname, params.get(), grp); #else log().info("FPTYPE {1} not supported. Using DOUBLE", params->m_fp_type().name()); ms = create_solvers(sname, params.get(), grp); #endif break; } log().verbose("Solver {1}", ms->name()); log().verbose(" ==> {1} nets", grp.size()); log().verbose(" has {1} dynamic elements", ms->dynamic_device_count()); log().verbose(" has {1} timestep elements", ms->timestep_device_count()); for (auto &n : grp) { log().verbose("Net {1}", n->name()); for (const auto &pcore : n->core_terms()) { log().verbose(" {1}", pcore->name()); } } m_mat_params.push_back(std::move(params)); m_mat_solvers.push_back(std::move(ms)); } } solver::static_compile_container NETLIB_NAME(solver)::create_solver_code(solver::static_compile_target target) { solver::static_compile_container mp; for (auto & s : m_mat_solvers) { auto r = s->create_solver_code(target); if (!r.first.empty()) // ignore solvers not supporting static compile mp.push_back(r); } return mp; } std::size_t NETLIB_NAME(solver)::get_solver_id(const solver::matrix_solver_t *net) const { for (std::size_t i=0; i < m_mat_solvers.size(); i++) if (m_mat_solvers[i].get() == net) return i; return std::numeric_limits::max(); } solver::matrix_solver_t * NETLIB_NAME(solver)::solver_by_id(std::size_t id) const { return m_mat_solvers[id].get(); } NETLIB_DEVICE_IMPL(solver, "SOLVER", "FREQ") } // namespace devices } // namespace netlist