openscad-cpp-evaluator 0.47.0__tar.gz → 0.49.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/CLAUDE.md +53 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/function_builtins.cpp +308 -1
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_feature_detection.cpp +1 -1
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_function_builtins.cpp +158 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/uv.lock +0 -0
|
@@ -460,6 +460,59 @@ grep for `ponytail:`.
|
|
|
460
460
|
unknown arguments -- `children(separate=true)` renders the wrong shape there with no warning --
|
|
461
461
|
and both read as `undef` in OpenSCAD, so a guard written against them works in both.
|
|
462
462
|
|
|
463
|
+
- **`linear_solve(A, b)`** (`src/builtins/function_builtins.cpp`, feature name `linear-solve`) —
|
|
464
|
+
returns `.x` (solution), `.det`, `.singular`. Dispatches on shape:
|
|
465
|
+
|
|
466
|
+
| shape | method | `.x` | `.det` |
|
|
467
|
+
|---|---|---|---|
|
|
468
|
+
| m = n | pivoted **LU** | the solution | product of U's diagonal, signed by row swaps |
|
|
469
|
+
| m > n | **Householder QR** | least squares | `undef` |
|
|
470
|
+
| m < n | **QR of Aᵀ** | minimum norm | `undef` |
|
|
471
|
+
|
|
472
|
+
Q is never formed. The reflectors are stored below the diagonal (v[0]
|
|
473
|
+
normalised to 1, scale in `taus`) and applied in forward order for `Qᵀb`,
|
|
474
|
+
reverse order for `Qy` — which is why this is far smaller and quicker than
|
|
475
|
+
BOSL2, whose QR materialises an m×m Q *and* an n×n permutation matrix and
|
|
476
|
+
then multiplies by it. **Minimum norm** comes from padding `y` with zeros to
|
|
477
|
+
length n before applying Q; a residual check alone will NOT catch getting
|
|
478
|
+
that wrong, since any solution passes it — assert the norm.
|
|
479
|
+
Rank deficiency without column pivoting cannot distinguish rank-deficient
|
|
480
|
+
from very ill-conditioned. It is a relative-tolerance heuristic on the
|
|
481
|
+
diagonal; say so rather than implying it is exact.
|
|
482
|
+
|
|
483
|
+
**Named to collide, deliberately.** BOSL2 defines `linear_solve()`, and since
|
|
484
|
+
0.47.0 a user function *shadows* a builtin — so this is unreachable from any
|
|
485
|
+
script that includes BOSL2 until BOSL2 drops its own. That is the intended
|
|
486
|
+
path (BelfrySCAD's author maintains BOSL2); callers then gain a `.x`, since
|
|
487
|
+
BOSL2's returns the bare solution and `[]` for singular. **Do not "fix" the
|
|
488
|
+
shadowing to make this reachable** — the shadowing is the correct, reference-
|
|
489
|
+
matching behaviour, and was itself a bug fix.
|
|
490
|
+
|
|
491
|
+
Measured per call, BOSL2 load subtracted, enough repetitions to clear noise
|
|
492
|
+
(harness checked: 200/2000/20000 reps at n=64 give 43/45/43 µs):
|
|
493
|
+
|
|
494
|
+
| n | BOSL2 `linear_solve` | ours | | n | BOSL2 `determinant` | ours |
|
|
495
|
+
|---:|---:|---:|---:|---:|---:|---:|
|
|
496
|
+
| 4 | 280 µs | 1.5 µs | 187× | 10 | 3.17 s | 0.33 s |
|
|
497
|
+
| 16 | 2.58 ms | 4.0 µs | 644× | 11 | 31.49 s | 0.33 s |
|
|
498
|
+
| 64 | 42.5 ms | 43 µs | 988× | | *cofactor expansion, O(n!)* | |
|
|
499
|
+
|
|
500
|
+
**Do not repeat the claim this replaces**: that BOSL2's solve "is not the
|
|
501
|
+
problem (128×128 in ~0.18s)". That was one-shot, with the solve lost inside
|
|
502
|
+
BOSL2's own load time. Keep the comparison fair though — BOSL2 does QR where
|
|
503
|
+
the square path here does LU, and it validates arguments every call.
|
|
504
|
+
Also correctness, not only speed: BOSL2's singularity test is a fixed
|
|
505
|
+
**absolute** `1e-9` on R's diagonal, unscaled by ‖A‖, so `2*I` scaled by
|
|
506
|
+
1e-10 is called singular there and solves correctly here.
|
|
507
|
+
|
|
508
|
+
**Profiling that shaped the scope** (don't redo it): NURBS *evaluation* is
|
|
509
|
+
0.6% linear algebra — basis eval 44%, type checks 25%. NURBS *interpolation*
|
|
510
|
+
is 35% at n=400, and every hot solve is **square**, so the QR paths are a
|
|
511
|
+
capability, not a measured win. `plane_intersection` (2×3, underdetermined)
|
|
512
|
+
is 0.1% of a `rounded_prism` model. The recurring cost across every workload
|
|
513
|
+
profiled is BOSL2's **argument type-checking**: 25–45%, millions of
|
|
514
|
+
`is_finite`/`is_num`/`is_nan` calls.
|
|
515
|
+
|
|
463
516
|
- `include/openscad_cpp_evaluator/value.hpp`, `src/value.cpp` — the `Value` variant (OpenSCAD's
|
|
464
517
|
dynamic value type) and its free-function arithmetic/comparison helpers. See the plan's §1 for
|
|
465
518
|
the type-mapping table and why there's no numpy-equivalent library.
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -169,6 +169,306 @@ Value builtinCross(const Value& aArg, const Value& bArg) {
|
|
|
169
169
|
return Value{};
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// -- linear_solve(A, b) --------------------------------------------------
|
|
173
|
+
//
|
|
174
|
+
// One pivoted LU answers three questions at once -- the solution, the
|
|
175
|
+
// determinant, and whether A is singular -- because all three fall out of
|
|
176
|
+
// the same factorisation.
|
|
177
|
+
//
|
|
178
|
+
// The motivation is BOSL2's determinant() (linalg.scad), a cofactor
|
|
179
|
+
// expansion: O(n!) time AND O(n!) intermediate list allocation. Measured
|
|
180
|
+
// through this evaluator: 0.04s at n=8, 0.38s at n=9, 2.7s at n=10, and
|
|
181
|
+
// projecting to minutes at n=12. This is O(n^3). BOSL2's linear_solve, by
|
|
182
|
+
// contrast, was already fast (128x128 in ~0.18s after its Apr 2026
|
|
183
|
+
// Householder rewrite), so speed is not the argument for the solve half --
|
|
184
|
+
// a correct singularity test is. See below.
|
|
185
|
+
//
|
|
186
|
+
// Square systems only. BOSL2's linear_solve also handles overdetermined
|
|
187
|
+
// (least-squares) and underdetermined (minimum-norm) systems via QR; LU
|
|
188
|
+
// cannot, and those paths are quick enough in script. A non-square matrix
|
|
189
|
+
// warns rather than silently doing something else.
|
|
190
|
+
|
|
191
|
+
// Defined further down, next to the other object() helpers.
|
|
192
|
+
Value objectOf(std::vector<std::pair<std::string, Value>> items);
|
|
193
|
+
|
|
194
|
+
// A rectangular numeric matrix as (row-major values, column count).
|
|
195
|
+
// nullopt for anything that is not a non-empty list of equal-length,
|
|
196
|
+
// non-empty numeric lists.
|
|
197
|
+
std::optional<std::pair<std::vector<double>, size_t>> numericMatrix(const Value& v) {
|
|
198
|
+
const ListPtr* rows = std::get_if<ListPtr>(&v);
|
|
199
|
+
if (!rows || !*rows || (*rows)->items.empty()) return std::nullopt;
|
|
200
|
+
std::vector<double> flat;
|
|
201
|
+
size_t cols = 0;
|
|
202
|
+
for (size_t r = 0; r < (*rows)->items.size(); ++r) {
|
|
203
|
+
const std::optional<std::vector<double>> row = allNumericList((*rows)->items[r]);
|
|
204
|
+
if (!row || row->empty()) return std::nullopt;
|
|
205
|
+
if (r == 0) {
|
|
206
|
+
cols = row->size();
|
|
207
|
+
} else if (row->size() != cols) {
|
|
208
|
+
return std::nullopt;
|
|
209
|
+
}
|
|
210
|
+
flat.insert(flat.end(), row->begin(), row->end());
|
|
211
|
+
}
|
|
212
|
+
return std::make_pair(std::move(flat), cols);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Householder QR of a tall matrix `a` (m rows, n cols, m >= n), in place.
|
|
216
|
+
//
|
|
217
|
+
// On return the upper triangle of `a` is R. Below the diagonal, column j
|
|
218
|
+
// holds the reflector vector v_j from element 1 onwards -- v_j[0] is
|
|
219
|
+
// normalised to 1 and therefore not stored -- and `taus[j]` is its scale,
|
|
220
|
+
// so H_j = I - tau_j v_j v_j^T. Q is never formed: everything that needs
|
|
221
|
+
// it applies the reflectors instead, which is the whole reason this is
|
|
222
|
+
// smaller and quicker than materialising an m x m matrix.
|
|
223
|
+
//
|
|
224
|
+
// The sign of alpha is chosen away from x[0] so that v[0] can never cancel
|
|
225
|
+
// to zero -- the textbook guard against catastrophic cancellation here.
|
|
226
|
+
//
|
|
227
|
+
// Returns false if a diagonal falls at or below `tol`, i.e. rank-deficient
|
|
228
|
+
// to the tolerance given. Without column pivoting this cannot tell
|
|
229
|
+
// rank-deficient from merely very ill-conditioned; see the caller's note.
|
|
230
|
+
bool householderQr(std::vector<double>& a, size_t m, size_t n, std::vector<double>& taus, double tol) {
|
|
231
|
+
taus.assign(n, 0.0);
|
|
232
|
+
for (size_t j = 0; j < n; ++j) {
|
|
233
|
+
double normx = 0.0;
|
|
234
|
+
for (size_t r = j; r < m; ++r) normx += a[r * n + j] * a[r * n + j];
|
|
235
|
+
normx = std::sqrt(normx);
|
|
236
|
+
if (normx <= tol) return false;
|
|
237
|
+
|
|
238
|
+
const double x0 = a[j * n + j];
|
|
239
|
+
const double alpha = (x0 >= 0.0) ? -normx : normx;
|
|
240
|
+
const double v0 = x0 - alpha;
|
|
241
|
+
// Store v scaled so v[0] == 1; the below-diagonal entries are the tail.
|
|
242
|
+
double vv = 1.0;
|
|
243
|
+
for (size_t r = j + 1; r < m; ++r) {
|
|
244
|
+
a[r * n + j] /= v0;
|
|
245
|
+
vv += a[r * n + j] * a[r * n + j];
|
|
246
|
+
}
|
|
247
|
+
taus[j] = 2.0 / vv;
|
|
248
|
+
a[j * n + j] = alpha;
|
|
249
|
+
|
|
250
|
+
// Apply H_j to the trailing columns.
|
|
251
|
+
for (size_t c = j + 1; c < n; ++c) {
|
|
252
|
+
double dot = a[j * n + c];
|
|
253
|
+
for (size_t r = j + 1; r < m; ++r) dot += a[r * n + j] * a[r * n + c];
|
|
254
|
+
const double f = taus[j] * dot;
|
|
255
|
+
a[j * n + c] -= f;
|
|
256
|
+
for (size_t r = j + 1; r < m; ++r) a[r * n + c] -= f * a[r * n + j];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// b <- Q^T b, for b with `k` columns. Reflectors applied in forward order.
|
|
263
|
+
void applyQtranspose(const std::vector<double>& a, size_t m, size_t n, const std::vector<double>& taus,
|
|
264
|
+
std::vector<double>& b, size_t k) {
|
|
265
|
+
for (size_t j = 0; j < n; ++j) {
|
|
266
|
+
for (size_t c = 0; c < k; ++c) {
|
|
267
|
+
double dot = b[j * k + c];
|
|
268
|
+
for (size_t r = j + 1; r < m; ++r) dot += a[r * n + j] * b[r * k + c];
|
|
269
|
+
const double f = taus[j] * dot;
|
|
270
|
+
b[j * k + c] -= f;
|
|
271
|
+
for (size_t r = j + 1; r < m; ++r) b[r * k + c] -= f * a[r * n + j];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// z <- Q z. Same reflectors, reverse order -- that is the only difference
|
|
277
|
+
// between applying Q and applying Q^T.
|
|
278
|
+
void applyQ(const std::vector<double>& a, size_t m, size_t n, const std::vector<double>& taus,
|
|
279
|
+
std::vector<double>& z, size_t k) {
|
|
280
|
+
for (size_t jj = n; jj-- > 0;) {
|
|
281
|
+
for (size_t c = 0; c < k; ++c) {
|
|
282
|
+
double dot = z[jj * k + c];
|
|
283
|
+
for (size_t r = jj + 1; r < m; ++r) dot += a[r * n + jj] * z[r * k + c];
|
|
284
|
+
const double f = taus[jj] * dot;
|
|
285
|
+
z[jj * k + c] -= f;
|
|
286
|
+
for (size_t r = jj + 1; r < m; ++r) z[r * k + c] -= f * a[r * n + jj];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
Value builtinLinearSolve(Evaluator& ev, const Value& aArg, const Value& bArg, bool haveB,
|
|
292
|
+
const oscad::ASTNode& node) {
|
|
293
|
+
const auto parsed = numericMatrix(aArg);
|
|
294
|
+
if (!parsed) {
|
|
295
|
+
ev.warn("linear_solve() requires a matrix of numbers", &node.position());
|
|
296
|
+
return Value{};
|
|
297
|
+
}
|
|
298
|
+
std::vector<double> lu = parsed->first;
|
|
299
|
+
const size_t n = parsed->second; // columns == unknowns
|
|
300
|
+
const size_t m = lu.size() / n; // rows == equations
|
|
301
|
+
double maxAbs = 0.0;
|
|
302
|
+
for (double x : lu) {
|
|
303
|
+
if (!std::isfinite(x)) {
|
|
304
|
+
ev.warn("linear_solve() matrix contains a non-finite value", &node.position());
|
|
305
|
+
return Value{};
|
|
306
|
+
}
|
|
307
|
+
maxAbs = std::max(maxAbs, std::abs(x));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Right-hand side: a vector of n is one column; a matrix of n rows is
|
|
311
|
+
// one column per column. Kept row-major alongside the factorisation.
|
|
312
|
+
size_t k = 0;
|
|
313
|
+
bool bWasVector = false;
|
|
314
|
+
std::vector<double> rhs;
|
|
315
|
+
if (haveB) {
|
|
316
|
+
if (const std::optional<std::vector<double>> vec = allNumericList(bArg); vec && vec->size() == m) {
|
|
317
|
+
bWasVector = true;
|
|
318
|
+
k = 1;
|
|
319
|
+
rhs = *vec;
|
|
320
|
+
} else if (const auto bm = numericMatrix(bArg); bm && bm->first.size() / bm->second == m) {
|
|
321
|
+
k = bm->second;
|
|
322
|
+
rhs = bm->first;
|
|
323
|
+
} else {
|
|
324
|
+
ev.warn("linear_solve() right-hand side must be a vector of " + std::to_string(m) +
|
|
325
|
+
" numbers, or a matrix with that many rows",
|
|
326
|
+
&node.position());
|
|
327
|
+
return Value{};
|
|
328
|
+
}
|
|
329
|
+
for (double x : rhs) {
|
|
330
|
+
if (!std::isfinite(x)) {
|
|
331
|
+
ev.warn("linear_solve() right-hand side contains a non-finite value", &node.position());
|
|
332
|
+
return Value{};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Relative singularity threshold. BOSL2 compares R's diagonal against a
|
|
338
|
+
// fixed ABSOLUTE 1e-9 (its _EPSILON) with no scaling by the size of the
|
|
339
|
+
// matrix, so a perfectly well-conditioned system scaled down by 1e-10
|
|
340
|
+
// is declared singular there. Scaling by maxAbs is what makes
|
|
341
|
+
// linear_solve(A*1e-10) still solvable.
|
|
342
|
+
const double tol =
|
|
343
|
+
std::numeric_limits<double>::epsilon() * static_cast<double>(std::max(m, n)) * std::max(maxAbs, 1.0);
|
|
344
|
+
|
|
345
|
+
// Non-square: QR, and no determinant to report.
|
|
346
|
+
//
|
|
347
|
+
// m > n overdetermined -> least squares. Factor A, apply Q^T to b, then
|
|
348
|
+
// back-substitute the leading n x n block of R.
|
|
349
|
+
// m < n underdetermined -> minimum norm. Factor A^T instead, forward-solve
|
|
350
|
+
// R^T y = b, and return Q [y; 0]. Padding with zeros is what makes
|
|
351
|
+
// it the SMALLEST solution rather than just any solution.
|
|
352
|
+
if (m != n) {
|
|
353
|
+
std::vector<std::pair<std::string, Value>> outNs;
|
|
354
|
+
std::vector<double> taus;
|
|
355
|
+
const auto answer = [&](Value x, bool sing) {
|
|
356
|
+
outNs.emplace_back("x", std::move(x));
|
|
357
|
+
outNs.emplace_back("det", Value{}); // undefined for a non-square matrix
|
|
358
|
+
outNs.emplace_back("singular", Value{sing});
|
|
359
|
+
return objectOf(std::move(outNs));
|
|
360
|
+
};
|
|
361
|
+
const auto shape = [&](const std::vector<double>& v, size_t rows, size_t cols) {
|
|
362
|
+
if (bWasVector) return numList(v);
|
|
363
|
+
std::vector<Value> rowsOut;
|
|
364
|
+
rowsOut.reserve(rows);
|
|
365
|
+
for (size_t r = 0; r < rows; ++r) {
|
|
366
|
+
rowsOut.push_back(numList(std::vector<double>(v.begin() + static_cast<long>(r * cols),
|
|
367
|
+
v.begin() + static_cast<long>((r + 1) * cols))));
|
|
368
|
+
}
|
|
369
|
+
return listOf(std::move(rowsOut));
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
if (m > n) {
|
|
373
|
+
if (!householderQr(lu, m, n, taus, tol)) return answer(Value{}, true);
|
|
374
|
+
if (!haveB) return answer(Value{}, false);
|
|
375
|
+
applyQtranspose(lu, m, n, taus, rhs, k);
|
|
376
|
+
for (size_t row = n; row-- > 0;) {
|
|
377
|
+
for (size_t c = 0; c < k; ++c) {
|
|
378
|
+
double acc = rhs[row * k + c];
|
|
379
|
+
for (size_t j = row + 1; j < n; ++j) acc -= lu[row * n + j] * rhs[j * k + c];
|
|
380
|
+
rhs[row * k + c] = acc / lu[row * n + row];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
rhs.resize(n * k);
|
|
384
|
+
return answer(shape(rhs, n, k), false);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// m < n: factor the transpose, which is the tall one.
|
|
388
|
+
std::vector<double> at(n * m);
|
|
389
|
+
for (size_t r = 0; r < m; ++r)
|
|
390
|
+
for (size_t c = 0; c < n; ++c) at[c * m + r] = lu[r * n + c];
|
|
391
|
+
if (!householderQr(at, n, m, taus, tol)) return answer(Value{}, true);
|
|
392
|
+
if (!haveB) return answer(Value{}, false);
|
|
393
|
+
// Forward-solve R^T y = b (R is m x m upper, so R^T is lower).
|
|
394
|
+
for (size_t row = 0; row < m; ++row) {
|
|
395
|
+
for (size_t c = 0; c < k; ++c) {
|
|
396
|
+
double acc = rhs[row * k + c];
|
|
397
|
+
for (size_t j = 0; j < row; ++j) acc -= at[j * m + row] * rhs[j * k + c];
|
|
398
|
+
rhs[row * k + c] = acc / at[row * m + row];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
std::vector<double> z(n * k, 0.0);
|
|
402
|
+
for (size_t r = 0; r < m; ++r)
|
|
403
|
+
for (size_t c = 0; c < k; ++c) z[r * k + c] = rhs[r * k + c];
|
|
404
|
+
applyQ(at, n, m, taus, z, k);
|
|
405
|
+
return answer(shape(z, n, k), false);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
double det = 1.0;
|
|
409
|
+
bool singular = false;
|
|
410
|
+
for (size_t col = 0; col < n && !singular; ++col) {
|
|
411
|
+
size_t pivot = col;
|
|
412
|
+
for (size_t r = col + 1; r < n; ++r) {
|
|
413
|
+
if (std::abs(lu[r * n + col]) > std::abs(lu[pivot * n + col])) pivot = r;
|
|
414
|
+
}
|
|
415
|
+
if (std::abs(lu[pivot * n + col]) <= tol) {
|
|
416
|
+
singular = true;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
if (pivot != col) {
|
|
420
|
+
for (size_t c = 0; c < n; ++c) std::swap(lu[col * n + c], lu[pivot * n + c]);
|
|
421
|
+
for (size_t c = 0; c < k; ++c) std::swap(rhs[col * k + c], rhs[pivot * k + c]);
|
|
422
|
+
det = -det;
|
|
423
|
+
}
|
|
424
|
+
const double p = lu[col * n + col];
|
|
425
|
+
det *= p;
|
|
426
|
+
for (size_t r = col + 1; r < n; ++r) {
|
|
427
|
+
const double f = lu[r * n + col] / p;
|
|
428
|
+
if (f == 0.0) continue;
|
|
429
|
+
lu[r * n + col] = 0.0;
|
|
430
|
+
for (size_t c = col + 1; c < n; ++c) lu[r * n + c] -= f * lu[col * n + c];
|
|
431
|
+
for (size_t c = 0; c < k; ++c) rhs[r * k + c] -= f * rhs[col * k + c];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
std::vector<std::pair<std::string, Value>> out;
|
|
436
|
+
if (singular) {
|
|
437
|
+
// Not a misuse -- "is this matrix singular?" is a legitimate
|
|
438
|
+
// question to ask linear_solve, so it answers rather than warning.
|
|
439
|
+
out.emplace_back("x", Value{});
|
|
440
|
+
out.emplace_back("det", Value{0.0});
|
|
441
|
+
out.emplace_back("singular", Value{true});
|
|
442
|
+
return objectOf(std::move(out));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (haveB) {
|
|
446
|
+
for (size_t col = n; col-- > 0;) {
|
|
447
|
+
for (size_t c = 0; c < k; ++c) {
|
|
448
|
+
double acc = rhs[col * k + c];
|
|
449
|
+
for (size_t j = col + 1; j < n; ++j) acc -= lu[col * n + j] * rhs[j * k + c];
|
|
450
|
+
rhs[col * k + c] = acc / lu[col * n + col];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (bWasVector) {
|
|
454
|
+
out.emplace_back("x", numList(rhs));
|
|
455
|
+
} else {
|
|
456
|
+
std::vector<Value> rowsOut;
|
|
457
|
+
rowsOut.reserve(n);
|
|
458
|
+
for (size_t r = 0; r < n; ++r) {
|
|
459
|
+
rowsOut.push_back(numList(std::vector<double>(rhs.begin() + static_cast<long>(r * k),
|
|
460
|
+
rhs.begin() + static_cast<long>((r + 1) * k))));
|
|
461
|
+
}
|
|
462
|
+
out.emplace_back("x", listOf(std::move(rowsOut)));
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
out.emplace_back("x", Value{});
|
|
466
|
+
}
|
|
467
|
+
out.emplace_back("det", Value{det});
|
|
468
|
+
out.emplace_back("singular", Value{false});
|
|
469
|
+
return objectOf(std::move(out));
|
|
470
|
+
}
|
|
471
|
+
|
|
172
472
|
Value builtinRands(double minv, double maxv, double nArg, const Value& seedArg) {
|
|
173
473
|
// ponytail: doesn't reproduce Python's Mersenne-Twister bit-for-bit --
|
|
174
474
|
// no script should depend on cross-language RNG equality, only on
|
|
@@ -450,6 +750,7 @@ bool isBuiltinFunctionName(const std::string& name) {
|
|
|
450
750
|
"chr", "ord", "is_undef", "is_num", "is_bool", "is_string", "is_list", "is_function", "is_object",
|
|
451
751
|
"search", "lookup", "has_key", "version", "version_num", "parent_module",
|
|
452
752
|
"object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross", "supported_feature",
|
|
753
|
+
"linear_solve",
|
|
453
754
|
};
|
|
454
755
|
return names.count(name) > 0;
|
|
455
756
|
}
|
|
@@ -591,7 +892,7 @@ enum class BuiltinFnId {
|
|
|
591
892
|
TextMetrics, FontMetrics, Abs, Sign, Ceil, Floor, Round, Sqrt, Ln, Log, Exp, Sin, Cos, Tan,
|
|
592
893
|
Asin, Acos, Atan, Atan2, Max, Min, Pow, Norm, Cross, Rands, Concat, Len, Str, Chr, Ord,
|
|
593
894
|
IsUndef, IsNum, IsBool, IsString, IsList, IsFunction, IsObject, Search, Lookup, HasKey,
|
|
594
|
-
Version, VersionNum, ParentModule, DxfDim, DxfCross, SupportedFeature,
|
|
895
|
+
Version, VersionNum, ParentModule, DxfDim, DxfCross, SupportedFeature, LinearSolve,
|
|
595
896
|
};
|
|
596
897
|
|
|
597
898
|
// supported_feature("name") -> the level at which this build implements that
|
|
@@ -611,6 +912,7 @@ enum class BuiltinFnId {
|
|
|
611
912
|
const std::unordered_map<std::string, double>& featureLevels() {
|
|
612
913
|
static const std::unordered_map<std::string, double> levels = {
|
|
613
914
|
{"render-expr", 1.0}, // render() in expression position
|
|
915
|
+
{"linear-solve", 1.0}, // linear_solve(A, b) -> {x, det, singular}
|
|
614
916
|
{"polyhedron-vnf", 1.0}, // polyhedron(vnf) / polyhedron(object)
|
|
615
917
|
{"separate-children", 1.0}, // children(..., separate=true)
|
|
616
918
|
{"minkowski-diff", 1.0}, // minkowski_difference()
|
|
@@ -645,6 +947,7 @@ const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
|
|
|
645
947
|
{"parent_module", BuiltinFnId::ParentModule},
|
|
646
948
|
{"dxf_dim", BuiltinFnId::DxfDim}, {"dxf_cross", BuiltinFnId::DxfCross},
|
|
647
949
|
{"supported_feature", BuiltinFnId::SupportedFeature},
|
|
950
|
+
{"linear_solve", BuiltinFnId::LinearSolve},
|
|
648
951
|
};
|
|
649
952
|
return ids;
|
|
650
953
|
}
|
|
@@ -726,6 +1029,7 @@ const std::unordered_map<int, BuiltinCheck>& builtinChecks() {
|
|
|
726
1029
|
add(BuiltinFnId::ParentModule, {0, 1, "1", {kNum}});
|
|
727
1030
|
add(BuiltinFnId::HasKey, {-1, -1, nullptr, {{TObj, "object"}, {TStr, "string"}}});
|
|
728
1031
|
add(BuiltinFnId::SupportedFeature, {1, 1, "1", {}});
|
|
1032
|
+
add(BuiltinFnId::LinearSolve, {1, 2, "1 or 2", {kVec}});
|
|
729
1033
|
for (BuiltinFnId id : {BuiltinFnId::IsUndef, BuiltinFnId::IsNum, BuiltinFnId::IsBool,
|
|
730
1034
|
BuiltinFnId::IsString, BuiltinFnId::IsList, BuiltinFnId::IsFunction,
|
|
731
1035
|
BuiltinFnId::IsObject}) {
|
|
@@ -1040,6 +1344,9 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
|
|
|
1040
1344
|
// The OpenSCAD release we track. version_num() is that same
|
|
1041
1345
|
// year/month/day folded as y * 10000 + m * 100 + d, exactly like the
|
|
1042
1346
|
// reference's own builtin_version_num (builtin_functions.cc).
|
|
1347
|
+
case BuiltinFnId::LinearSolve:
|
|
1348
|
+
return builtinLinearSolve(ev, getArg(args, 0, "A", Value{}), getArg(args, 1, "b", Value{}),
|
|
1349
|
+
args.findPositional(1) != nullptr || args.findNamed("b") != nullptr, node);
|
|
1043
1350
|
case BuiltinFnId::SupportedFeature: {
|
|
1044
1351
|
const Value name = getArg(args, 0, "feature", Value{});
|
|
1045
1352
|
const std::string* s = std::get_if<std::string>(&name);
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_feature_detection.cpp
RENAMED
|
@@ -35,7 +35,7 @@ std::vector<std::string> echoesFrom(const std::string& src) {
|
|
|
35
35
|
// in featureLevels() -- shows up as a 0.
|
|
36
36
|
const char* kFeatures[] = {"separate-children", "minkowski-diff", "sphere-styles", "export-name",
|
|
37
37
|
"simplify-op", "expr-import", "object-function", "roof-op",
|
|
38
|
-
"render-expr", "polyhedron-vnf"};
|
|
38
|
+
"render-expr", "polyhedron-vnf", "linear-solve"};
|
|
39
39
|
|
|
40
40
|
} // namespace
|
|
41
41
|
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/tests/test_function_builtins.cpp
RENAMED
|
@@ -725,3 +725,161 @@ TEST(StringListBuiltins, ChrIsVariadicAndRangeChecked) {
|
|
|
725
725
|
EXPECT_EQ(asStr(evalSrc("chr(55296)", ev)), ""); // 0xD800, a surrogate
|
|
726
726
|
EXPECT_EQ(asStr(evalSrc("chr(1114112)", ev)), ""); // 0x110000, one past the top
|
|
727
727
|
}
|
|
728
|
+
|
|
729
|
+
// -- linear_solve(A, b) --------------------------------------------------
|
|
730
|
+
//
|
|
731
|
+
// One pivoted LU, three answers: x, det, singular. Oracle is BOSL2 plus
|
|
732
|
+
// arithmetic -- OpenSCAD has no equivalent, so there is no reference
|
|
733
|
+
// binary to compare against.
|
|
734
|
+
//
|
|
735
|
+
// Measured against BOSL2 through the CLI, same matrices, identical
|
|
736
|
+
// determinants: n=10 3.17s -> 0.33s, n=11 31.49s -> 0.33s. BOSL2's
|
|
737
|
+
// determinant() is a cofactor expansion, growing x10 per step; this is
|
|
738
|
+
// O(n^3).
|
|
739
|
+
|
|
740
|
+
TEST(LinearSolve, SolvesASquareSystem) {
|
|
741
|
+
Evaluator ev;
|
|
742
|
+
// asList() returns a reference INTO v, so v has to outlive it -- taking
|
|
743
|
+
// asList(evalSrc(...)) directly dangles the moment the temporary dies.
|
|
744
|
+
const Value v = evalSrc("linear_solve([[2,1],[1,3]], [5,10]).x", ev);
|
|
745
|
+
const std::vector<Value>& x = asList(v);
|
|
746
|
+
ASSERT_EQ(x.size(), 2u);
|
|
747
|
+
EXPECT_NEAR(asNum(x[0]), 1.0, 1e-12);
|
|
748
|
+
EXPECT_NEAR(asNum(x[1]), 3.0, 1e-12);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
TEST(LinearSolve, DeterminantComesFreeFromTheSameFactorisation) {
|
|
752
|
+
Evaluator ev;
|
|
753
|
+
EXPECT_NEAR(asNum(evalSrc("linear_solve([[2,1],[1,3]]).det", ev)), 5.0, 1e-12);
|
|
754
|
+
EXPECT_NEAR(asNum(evalSrc("linear_solve([[1,0,0],[0,1,0],[0,0,1]]).det", ev)), 1.0, 1e-12);
|
|
755
|
+
// Sign follows the row swaps, not just the magnitude.
|
|
756
|
+
EXPECT_NEAR(asNum(evalSrc("linear_solve([[0,1],[1,0]]).det", ev)), -1.0, 1e-12);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
TEST(LinearSolve, WithNoRightHandSideThereIsNoSolution) {
|
|
760
|
+
Evaluator ev;
|
|
761
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[2,1],[1,3]]).x", ev)));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
TEST(LinearSolve, ASingularMatrixAnswersRatherThanWarning) {
|
|
765
|
+
// "Is this matrix singular?" is a fair question to ask, so it is
|
|
766
|
+
// reported, not warned about: det 0, singular true, no solution.
|
|
767
|
+
Evaluator ev;
|
|
768
|
+
EXPECT_TRUE(asBool(evalSrc("linear_solve([[1,2],[2,4]], [1,2]).singular", ev)));
|
|
769
|
+
EXPECT_NEAR(asNum(evalSrc("linear_solve([[1,2],[2,4]], [1,2]).det", ev)), 0.0, 1e-12);
|
|
770
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,2],[2,4]], [1,2]).x", ev)));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
TEST(LinearSolve, SingularityIsRelativeToTheMatrixNotAbsolute) {
|
|
774
|
+
// 2*I scaled by 1e-10 is perfectly conditioned, and BOSL2's
|
|
775
|
+
// linear_solve calls it singular -- its test is a fixed absolute 1e-9
|
|
776
|
+
// on R's diagonal with no scaling by the size of the matrix. Verified
|
|
777
|
+
// through the CLI: BOSL2 says SINGULAR at 1e-10 and 1e-12 where this
|
|
778
|
+
// solves correctly.
|
|
779
|
+
Evaluator ev;
|
|
780
|
+
EXPECT_FALSE(asBool(evalSrc("linear_solve([[2e-10,0],[0,2e-10]], [1e-10,1e-10]).singular", ev)));
|
|
781
|
+
const Value v = evalSrc("linear_solve([[2e-10,0],[0,2e-10]], [1e-10,1e-10]).x", ev);
|
|
782
|
+
const std::vector<Value>& x = asList(v);
|
|
783
|
+
ASSERT_EQ(x.size(), 2u);
|
|
784
|
+
EXPECT_NEAR(asNum(x[0]), 0.5, 1e-9);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
TEST(LinearSolve, AMatrixRightHandSideSolvesEveryColumn) {
|
|
788
|
+
Evaluator ev;
|
|
789
|
+
// Solving against the identity is the inverse; [[2,1],[1,3]] has
|
|
790
|
+
// det 5, so the inverse is [[0.6,-0.2],[-0.2,0.4]].
|
|
791
|
+
const Value v = evalSrc("linear_solve([[2,1],[1,3]], [[1,0],[0,1]]).x", ev);
|
|
792
|
+
const std::vector<Value>& rows = asList(v);
|
|
793
|
+
ASSERT_EQ(rows.size(), 2u);
|
|
794
|
+
const std::vector<Value>& r0 = asList(rows[0]);
|
|
795
|
+
ASSERT_EQ(r0.size(), 2u);
|
|
796
|
+
EXPECT_NEAR(asNum(r0[0]), 0.6, 1e-12);
|
|
797
|
+
EXPECT_NEAR(asNum(r0[1]), -0.2, 1e-12);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
TEST(LinearSolve, RejectsWhatItCannotFactor) {
|
|
801
|
+
Evaluator ev;
|
|
802
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,2],[3,\"x\"]], [1,2])", ev))); // not numeric
|
|
803
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,2],[3,4]], [1,2,3])", ev))); // wrong rhs length
|
|
804
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,2],[3,4,5]], [1,2])", ev))); // ragged
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// -- non-square: QR ------------------------------------------------------
|
|
808
|
+
//
|
|
809
|
+
// m > n is least squares, m < n is minimum norm -- the same two answers
|
|
810
|
+
// BOSL2's linear_solve gives, and cross-checked against it.
|
|
811
|
+
|
|
812
|
+
TEST(LinearSolve, OverdeterminedGivesTheLeastSquaresFit) {
|
|
813
|
+
// Fit y = a + b*x to (0,1) (1,3) (2,5) (3,7): exactly on the line
|
|
814
|
+
// y = 1 + 2x, so the residual is zero and a=1, b=2.
|
|
815
|
+
Evaluator ev;
|
|
816
|
+
const Value v = evalSrc("linear_solve([[1,0],[1,1],[1,2],[1,3]], [1,3,5,7]).x", ev);
|
|
817
|
+
const std::vector<Value>& x = asList(v);
|
|
818
|
+
ASSERT_EQ(x.size(), 2u);
|
|
819
|
+
EXPECT_NEAR(asNum(x[0]), 1.0, 1e-10);
|
|
820
|
+
EXPECT_NEAR(asNum(x[1]), 2.0, 1e-10);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
TEST(LinearSolve, OverdeterminedWithNoExactSolutionMinimisesTheResidual) {
|
|
824
|
+
// (0,0) (1,1) (2,2) (3,10): no line fits. The normal equations put the
|
|
825
|
+
// least-squares line at y = -1.4 + 3.1x -- computed independently, not
|
|
826
|
+
// read off this implementation.
|
|
827
|
+
Evaluator ev;
|
|
828
|
+
const Value v = evalSrc("linear_solve([[1,0],[1,1],[1,2],[1,3]], [0,1,2,10]).x", ev);
|
|
829
|
+
const std::vector<Value>& x = asList(v);
|
|
830
|
+
ASSERT_EQ(x.size(), 2u);
|
|
831
|
+
EXPECT_NEAR(asNum(x[0]), -1.4, 1e-9);
|
|
832
|
+
EXPECT_NEAR(asNum(x[1]), 3.1, 1e-9);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
TEST(LinearSolve, UnderdeterminedGivesTheMinimUmNormSolution) {
|
|
836
|
+
// x + y = 2 has infinitely many solutions; the smallest is [1,1].
|
|
837
|
+
// Anything satisfying the equation passes a residual check, so this
|
|
838
|
+
// asserts the NORM -- that is the whole claim of a minimum-norm solve.
|
|
839
|
+
Evaluator ev;
|
|
840
|
+
const Value v = evalSrc("linear_solve([[1,1]], [2]).x", ev);
|
|
841
|
+
const std::vector<Value>& x = asList(v);
|
|
842
|
+
ASSERT_EQ(x.size(), 2u);
|
|
843
|
+
EXPECT_NEAR(asNum(x[0]), 1.0, 1e-12);
|
|
844
|
+
EXPECT_NEAR(asNum(x[1]), 1.0, 1e-12);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
TEST(LinearSolve, UnderdeterminedSatisfiesEveryEquation) {
|
|
848
|
+
// 2 x 3: the plane_intersection shape BOSL2 relies on.
|
|
849
|
+
Evaluator ev;
|
|
850
|
+
const Value v = evalSrc("linear_solve([[1,0,1],[0,1,1]], [3,5]).x", ev);
|
|
851
|
+
const std::vector<Value>& x = asList(v);
|
|
852
|
+
ASSERT_EQ(x.size(), 3u);
|
|
853
|
+
const double a = asNum(x[0]), b = asNum(x[1]), c = asNum(x[2]);
|
|
854
|
+
EXPECT_NEAR(a + c, 3.0, 1e-12);
|
|
855
|
+
EXPECT_NEAR(b + c, 5.0, 1e-12);
|
|
856
|
+
// minimum norm: the component along the null direction [1,1,-1] is zero
|
|
857
|
+
EXPECT_NEAR(a + b - c, 0.0, 1e-9);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
TEST(LinearSolve, NonSquareHasNoDeterminant) {
|
|
861
|
+
Evaluator ev;
|
|
862
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,0],[1,1],[1,2]], [1,2,3]).det", ev)));
|
|
863
|
+
EXPECT_FALSE(asBool(evalSrc("linear_solve([[1,0],[1,1],[1,2]], [1,2,3]).singular", ev)));
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
TEST(LinearSolve, RankDeficientNonSquareIsReportedNotWrong) {
|
|
867
|
+
// Two identical columns: rank 1, not 2.
|
|
868
|
+
Evaluator ev;
|
|
869
|
+
EXPECT_TRUE(asBool(evalSrc("linear_solve([[1,1],[2,2],[3,3]], [1,2,3]).singular", ev)));
|
|
870
|
+
EXPECT_TRUE(isUndef(evalSrc("linear_solve([[1,1],[2,2],[3,3]], [1,2,3]).x", ev)));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
TEST(LinearSolve, WorksUnderBothEngines) {
|
|
875
|
+
for (bool vm : {false, true}) {
|
|
876
|
+
ScopedVm guard(vm);
|
|
877
|
+
const std::vector<std::string> e =
|
|
878
|
+
echoesOf("r = linear_solve([[2,1],[1,3]], [5,10]);\necho(r.x, r.det, r.singular);");
|
|
879
|
+
ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
|
|
880
|
+
EXPECT_NE(e[0].find("[1, 3]"), std::string::npos) << "vm=" << vm << ", got " << e[0];
|
|
881
|
+
EXPECT_NE(e[0].find("5"), std::string::npos) << "vm=" << vm << ", got " << e[0];
|
|
882
|
+
EXPECT_NE(e[0].find("false"), std::string::npos) << "vm=" << vm << ", got " << e[0];
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
File without changes
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/.github/workflows/wheels.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/eval_perf_benchmark.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/manifold_cache_reuse.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-0.47.0 → openscad_cpp_evaluator-0.49.0}/examples/minimal_debugger.cpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|