Repository: Morpho-lang/morpho Branch: main Commit: b9f31986ebfa Files: 1216 Total size: 2.4 MB Directory structure: gitextract_ml0t_fts/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ ├── build.yml │ ├── buildandtest.yml │ ├── buildandtestmultithreaded.yml │ ├── codeql.yml │ ├── examples.yml │ └── nonanboxing.yml ├── .gitignore ├── .readthedocs.yml ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples/ │ ├── catenoid/ │ │ └── catenoid.morpho │ ├── cholesteric/ │ │ └── cholesteric.morpho │ ├── cube/ │ │ └── cube.morpho │ ├── delaunay/ │ │ └── delaunay.morpho │ ├── dla/ │ │ └── dla.morpho │ ├── electrostatics/ │ │ └── electrostatics.morpho │ ├── elementtypes/ │ │ ├── README.md │ │ ├── electrostaticsCG2.morpho │ │ └── qtensorCG2.morpho │ ├── examples.py │ ├── implicitmesh/ │ │ ├── ellipsoid.morpho │ │ ├── threesurface.morpho │ │ └── torus.morpho │ ├── meshgen/ │ │ ├── disk.morpho │ │ ├── ellipse.morpho │ │ ├── ellipsoidsection.morpho │ │ ├── halfdisk.morpho │ │ ├── overlappingdisks.morpho │ │ ├── sphere.morpho │ │ ├── square.morpho │ │ ├── superellipse.morpho │ │ ├── superellipsoid.morpho │ │ └── weighted.morpho │ ├── meshslice/ │ │ ├── sphere.mesh │ │ └── testmeshslice.morpho │ ├── plot/ │ │ ├── plotfield.morpho │ │ ├── plotmeshlabels.morpho │ │ ├── plotselection.morpho │ │ └── scalebar.morpho │ ├── povray/ │ │ ├── testCamera.morpho │ │ ├── testpovray.morpho │ │ ├── testpovraytext.morpho │ │ ├── testpovraytransmitfilter.morpho │ │ └── text.morpho │ ├── qtensor/ │ │ ├── dense_disk.mesh │ │ ├── qtensor.morpho │ │ ├── src.lyx │ │ └── src.tex │ ├── tactoid/ │ │ ├── disk.mesh │ │ ├── tactoid.morpho │ │ └── tactoid2dmesh.morpho │ ├── thomson/ │ │ └── thomson.morpho │ ├── tutorial/ │ │ ├── disk.mesh │ │ ├── tutorial.morpho │ │ └── tutorial2.morpho │ └── wrap/ │ └── wrap.morpho ├── help/ │ ├── Makefile │ ├── array.md │ ├── builtin.md │ ├── classes.md │ ├── color.md │ ├── complex.md │ ├── conf.py │ ├── constants.md │ ├── controlflow.md │ ├── delaunay.md │ ├── dictionary.md │ ├── errors.md │ ├── field.md │ ├── file.md │ ├── functionals.md │ ├── functions.md │ ├── graphics.md │ ├── help.md │ ├── implicitmesh.md │ ├── index.rst │ ├── json.md │ ├── kdtree.md │ ├── list.md │ ├── make.bat │ ├── matrix.md │ ├── mesh.md │ ├── meshgen.md │ ├── meshslice.md │ ├── meshtools.md │ ├── modules.md │ ├── optimize.md │ ├── plot.md │ ├── povray.md │ ├── range.md │ ├── requirements.txt │ ├── selection.md │ ├── sparse.md │ ├── string.md │ ├── syntax.md │ ├── system.md │ ├── tuple.md │ ├── values.md │ ├── variables.md │ └── vtk.md ├── modules/ │ ├── color.morpho │ ├── constants.morpho │ ├── delaunay.morpho │ ├── functionals.morpho │ ├── graphics.morpho │ ├── histogram.morpho │ ├── implicitmesh.morpho │ ├── kdtree.morpho │ ├── meshgen.morpho │ ├── meshslice.morpho │ ├── meshtools.morpho │ ├── optimize.morpho │ ├── parser.morpho │ ├── plot.morpho │ ├── povray.morpho │ ├── shapeopt.morpho │ ├── symmetry.morpho │ └── vtk.morpho ├── releasenotes/ │ ├── version-0.5.1.md │ ├── version-0.5.2.md │ ├── version-0.5.3.md │ ├── version-0.5.4.md │ ├── version-0.5.5.md │ ├── version-0.5.6.md │ ├── version-0.5.7.md │ ├── version-0.6.0.md │ ├── version-0.6.1.md │ ├── version-0.6.2.md │ └── version-0.6.3.md ├── src/ │ ├── CMakeLists.txt │ ├── build.h │ ├── builtin/ │ │ ├── CMakeLists.txt │ │ ├── builtin.c │ │ ├── builtin.h │ │ ├── functiondefs.c │ │ └── functiondefs.h │ ├── classes/ │ │ ├── CMakeLists.txt │ │ ├── array.c │ │ ├── array.h │ │ ├── bool.c │ │ ├── bool.h │ │ ├── classes.h │ │ ├── closure.c │ │ ├── closure.h │ │ ├── clss.c │ │ ├── clss.h │ │ ├── cmplx.c │ │ ├── cmplx.h │ │ ├── dict.c │ │ ├── dict.h │ │ ├── err.c │ │ ├── err.h │ │ ├── file.c │ │ ├── file.h │ │ ├── flt.c │ │ ├── flt.h │ │ ├── function.c │ │ ├── function.h │ │ ├── instance.c │ │ ├── instance.h │ │ ├── int.c │ │ ├── int.h │ │ ├── invocation.c │ │ ├── invocation.h │ │ ├── json.c │ │ ├── json.h │ │ ├── list.c │ │ ├── list.h │ │ ├── metafunction.c │ │ ├── metafunction.h │ │ ├── range.c │ │ ├── range.h │ │ ├── strng.c │ │ ├── strng.h │ │ ├── system.c │ │ ├── system.h │ │ ├── tuple.c │ │ ├── tuple.h │ │ ├── upvalue.c │ │ └── upvalue.h │ ├── core/ │ │ ├── CMakeLists.txt │ │ ├── compile.c │ │ ├── compile.h │ │ ├── core.h │ │ ├── gc.c │ │ ├── gc.h │ │ ├── opcodes.h │ │ ├── vm.c │ │ └── vm.h │ ├── datastructures/ │ │ ├── CMakeLists.txt │ │ ├── debugannotation.c │ │ ├── debugannotation.h │ │ ├── dictionary.c │ │ ├── dictionary.h │ │ ├── error.c │ │ ├── error.h │ │ ├── object.c │ │ ├── object.h │ │ ├── program.c │ │ ├── program.h │ │ ├── signature.c │ │ ├── signature.h │ │ ├── syntaxtree.c │ │ ├── syntaxtree.h │ │ ├── value.c │ │ ├── value.h │ │ ├── varray.c │ │ ├── varray.h │ │ ├── version.c │ │ └── version.h │ ├── debug/ │ │ ├── CMakeLists.txt │ │ ├── debug.c │ │ ├── debug.h │ │ ├── profile.c │ │ └── profile.h │ ├── geometry/ │ │ ├── CMakeLists.txt │ │ ├── fespace.c │ │ ├── fespace.h │ │ ├── field.c │ │ ├── field.h │ │ ├── functional.c │ │ ├── functional.h │ │ ├── geometry.c │ │ ├── geometry.h │ │ ├── integrate.c │ │ ├── integrate.h │ │ ├── mesh.c │ │ ├── mesh.h │ │ ├── selection.c │ │ └── selection.h │ ├── linalg/ │ │ ├── CMakeLists.txt │ │ ├── matrix.c │ │ ├── matrix.h │ │ ├── sparse.c │ │ └── sparse.h │ ├── morpho.h │ └── support/ │ ├── CMakeLists.txt │ ├── common.c │ ├── common.h │ ├── extensions.c │ ├── extensions.h │ ├── format.c │ ├── format.h │ ├── lex.c │ ├── lex.h │ ├── memory.c │ ├── memory.h │ ├── parse.c │ ├── parse.h │ ├── platform.c │ ├── platform.h │ ├── random.c │ ├── random.h │ ├── resources.c │ ├── resources.h │ ├── threadpool.c │ └── threadpool.h └── test/ ├── apply/ │ ├── apply.morpho │ ├── apply_args.morpho │ ├── apply_builtin.morpho │ ├── apply_closure.morpho │ ├── apply_list.morpho │ ├── apply_method.morpho │ └── apply_recursion.morpho ├── arithmetic/ │ ├── power.morpho │ ├── redirection.morpho │ └── unary_div.morpho ├── array/ │ ├── array.morpho │ ├── array_assign_beyond_bounds.morpho │ ├── array_dim_with_initializer.morpho │ ├── array_enumerate.morpho │ ├── array_garbage_collect.morpho │ ├── array_incompatible_initializer.morpho │ ├── array_initialize_array.morpho │ ├── array_initialize_incompatible_list.morpho │ ├── array_initialize_list.morpho │ ├── array_nonnum_indices.morpho │ ├── array_print.morpho │ ├── array_read_beyond_bounds.morpho │ ├── array_three_dim.morpho │ ├── array_two_dim.morpho │ ├── array_veneer.morpho │ ├── array_wrong_dim.morpho │ ├── constructor.morpho │ └── inherited.morpho ├── assignment/ │ ├── associativity.morpho │ ├── global.morpho │ ├── grouping.morpho │ ├── infix_operator.morpho │ ├── local.morpho │ ├── prefix_operator.morpho │ ├── shorthand.morpho │ ├── syntax.morpho │ ├── to_self.morpho │ └── undefined.morpho ├── blank/ │ ├── empty_file.morpho │ └── shebang.morpho ├── block/ │ ├── empty.morpho │ ├── scope.morpho │ └── scope_error.morpho ├── bool/ │ ├── builtin.morpho │ ├── equality.morpho │ └── not.morpho ├── break/ │ ├── break_in_if_outside_loop.morpho │ ├── break_outside_loop.morpho │ ├── continue_in_if_outside_loop.morpho │ ├── continue_outside_loop.morpho │ ├── in_for.morpho │ ├── in_forin.morpho │ └── in_while.morpho ├── builtin/ │ ├── apply.morpho │ ├── bounds.morpho │ ├── boundsvargs.morpho │ ├── iscallable.morpho │ ├── maxargs.morpho │ ├── minargs.morpho │ ├── minnumbers.morpho │ ├── minvargs.morpho │ ├── mod.morpho │ └── typecheck.morpho ├── call/ │ ├── bool.morpho │ ├── call.morpho │ ├── nil.morpho │ ├── num.morpho │ ├── object.morpho │ └── string.morpho ├── class/ │ ├── apply_class.morpho │ ├── call_class_in_property.morpho │ ├── call_on_class.morpho │ ├── empty.morpho │ ├── forward_ref_in_method.morpho │ ├── inherit_self.morpho │ ├── inherited_method.morpho │ ├── is.morpho │ ├── keyword_method.morpho │ ├── keyword_property.morpho │ ├── linearization.morpho │ ├── local_fn_supersedes_method_call.morpho │ ├── local_inherit_other.morpho │ ├── local_inherit_self.morpho │ ├── local_reference_self.morpho │ ├── method_call_supersedes_global_fn.morpho │ ├── mixins.morpho │ ├── nested_class.morpho │ ├── no_self_for_method_call.morpho │ ├── prefer_local.morpho │ ├── reference_self.morpho │ ├── syntax_error_in_constructor.morpho │ └── unlinearizable.morpho ├── closure/ │ ├── assign_to_closure.morpho │ ├── assign_to_shadowed_later.morpho │ ├── close_over_function_parameter.morpho │ ├── close_over_later_variable.morpho │ ├── closed_closure_in_function.morpho │ ├── closures_in_loop.morpho │ ├── nested_closure.morpho │ ├── open_closure_in_function.morpho │ ├── prioritize_upvalues_over_globals.morpho │ ├── reference_closure_multiple_times.morpho │ ├── reuse_closure_slot.morpho │ ├── shadow_closure_with_local.morpho │ ├── unused_closure.morpho │ ├── unused_later_closure.morpho │ └── veneer.morpho ├── comments/ │ ├── line_at_eof.morpho │ ├── multline.morpho │ ├── nested.morpho │ ├── only_line_comment.morpho │ ├── only_line_comment_and_line.morpho │ ├── unicode.morpho │ └── unterminated.morpho ├── complex/ │ ├── ComplexBuiltin.morpho │ ├── ComplexTrig.morpho │ ├── arithmetic.morpho │ ├── clone.morpho │ ├── complex_overflow_literal.morpho │ ├── constructor.morpho │ ├── constructor_error.morpho │ ├── inherited.morpho │ ├── methods.morpho │ └── powers.morpho ├── constructor/ │ ├── arguments.morpho │ ├── call_init_early_return.morpho │ ├── call_init_explicitly.morpho │ ├── default.morpho │ ├── default_arguments.morpho │ ├── early_return.morpho │ ├── extra_arguments.morpho │ ├── init_not_method.morpho │ ├── missing_arguments.morpho │ ├── return_in_nested_function.morpho │ └── return_value.morpho ├── dictionary/ │ ├── clear.morpho │ ├── contains.morpho │ ├── empty_literal.morpho │ ├── inherited.morpho │ ├── key_not_found.morpho │ ├── literal_from_vars.morpho │ ├── literal_in_function.morpho │ ├── literal_in_loop.morpho │ ├── methods.morpho │ ├── missing_comma.morpho │ ├── missing_separator.morpho │ ├── remove.morpho │ ├── set_operations.morpho │ ├── syntax.morpho │ ├── tombstone.morpho │ └── unterminated.morpho ├── do/ │ ├── do_in_fn.morpho │ ├── do_with_break.morpho │ ├── missingcond.morpho │ └── syntax.morpho ├── error/ │ ├── error_incorrectly_defined.morpho │ ├── inherited.morpho │ ├── stacktrace.morpho │ ├── throw.morpho │ ├── throw_on_class.morpho │ └── throw_on_class_notag.morpho ├── field/ │ ├── assign.morpho │ ├── assign_matrix.morpho │ ├── badfnin.morpho │ ├── bounds.morpho │ ├── discretizations/ │ │ ├── boundary_line_integrals.morpho │ │ ├── cg1_area_in_2d_grad.morpho │ │ ├── cg1_area_in_2d_grad_old.morpho │ │ ├── cg1_area_in_2d_old.morpho │ │ ├── cg1_line_in_3d_grad.morpho │ │ ├── cg2_area_in_2d.morpho │ │ ├── cg2_area_in_2d_grad.morpho │ │ ├── cg2_area_in_2d_tensor_grad.morpho │ │ ├── cg2_area_in_3d_grad.morpho │ │ ├── cg2_line_in_1d.morpho │ │ ├── cg2_line_in_2d_grad.morpho │ │ ├── cg2_line_in_3d_integrate.morpho │ │ ├── cg2_vol_in_3d.morpho │ │ └── cg2_vol_in_3d_grad.morpho │ ├── enumerate.morpho │ ├── grade.morpho │ ├── inherited.morpho │ ├── inner.morpho │ ├── linearize.morpho │ ├── matrix.morpho │ ├── methods.morpho │ ├── multigrade.morpho │ ├── neg_nilMesh.morpho │ ├── negate.morpho │ ├── op.morpho │ ├── op_in_loop.morpho │ ├── scalar.morpho │ ├── scalarmul.morpho │ └── square.mesh ├── file/ │ ├── file.morpho │ ├── file_lines.morpho │ ├── file_not_found.morpho │ ├── file_readall.morpho │ ├── file_write.morpho │ ├── filename_missing.morpho │ ├── filename_not_string.morpho │ ├── folder/ │ │ ├── file1.txt │ │ ├── file2.txt │ │ └── file3.txt │ ├── folder.morpho │ ├── remote.morpho │ ├── string.txt │ ├── test.txt │ └── testout.txt ├── for/ │ ├── class_in_body.morpho │ ├── closure_in_body.morpho │ ├── fn_in_body.morpho │ ├── return_closure.morpho │ ├── return_inside.morpho │ ├── scope.morpho │ ├── statement_condition.morpho │ ├── statement_increment.morpho │ ├── statement_initializer.morpho │ ├── syntax.morpho │ └── var_in_body.morpho ├── for_in/ │ ├── forin.morpho │ ├── forin_custom.morpho │ ├── forin_in_function.morpho │ ├── forin_index.morpho │ └── forin_string.morpho ├── function/ │ ├── anonymous.morpho │ ├── anonymous_closure.morpho │ ├── anonymous_closure_assign.morpho │ ├── anonymous_closure_noparams.morpho │ ├── anonymous_in_args.morpho │ ├── anonymous_in_optional_args.morpho │ ├── body_must_be_block.morpho │ ├── constant_arg_optional.morpho │ ├── empty_body.morpho │ ├── extra_arguments.morpho │ ├── index_in_arguments.morpho │ ├── local_recursion.morpho │ ├── missing_arguments.morpho │ ├── missing_comma_in_parameters.morpho │ ├── mutual_recursion/ │ │ ├── duplicate_local_mutual_recursion.morpho │ │ ├── local_mutual_recursion.morpho │ │ ├── local_mutual_recursion_out_of_scope.morpho │ │ ├── mutual_recursion.morpho │ │ ├── mutual_recursion_in_closure.morpho │ │ ├── undefined_call_in_global.morpho │ │ ├── unresolved_forward_ref_in_func.morpho │ │ └── unresolved_forward_reference.morpho │ ├── optional.morpho │ ├── optional_invalid.morpho │ ├── optional_non_constant_default.morpho │ ├── optional_too_few_fixed.morpho │ ├── parameters.morpho │ ├── print.morpho │ ├── recursion.morpho │ ├── stack_overflow.morpho │ ├── too_many_arguments.morpho │ ├── too_many_parameters.morpho │ ├── unknown_optional.morpho │ ├── variadic.morpho │ ├── variadic_append.morpho │ ├── variadic_apply.morpho │ ├── variadic_in_method.morpho │ ├── variadic_last.morpho │ ├── variadic_only.morpho │ ├── variadic_optional.morpho │ ├── variadic_too_many.morpho │ └── veneer.morpho ├── functionals/ │ ├── area/ │ │ ├── area.morpho │ │ ├── area2.morpho │ │ ├── area2d.morpho │ │ ├── area_hessian.morpho │ │ ├── area_integrandForElement.morpho │ │ ├── square.mesh │ │ └── triangle.mesh │ ├── areaenclosed/ │ │ ├── areaenclosed.morpho │ │ ├── areaenclosed_hessian.morpho │ │ └── areaenclosed_integrandForElement.morpho │ ├── areaintegral/ │ │ ├── areaintegral.morpho │ │ ├── cg2fieldgradient.morpho │ │ ├── cgtensor.morpho │ │ ├── grad.morpho │ │ ├── grad2.morpho │ │ ├── gradvector.morpho │ │ └── normal.morpho │ ├── cube.mesh │ ├── cubeout.mesh │ ├── equielement/ │ │ ├── equielement.morpho │ │ ├── equielement_gradient.morpho │ │ ├── equielement_hessian.morpho │ │ ├── hex.mesh │ │ └── weight.morpho │ ├── err_functional_req_mesh.morpho │ ├── err_integrand.morpho │ ├── gausscurvature/ │ │ ├── disk.mesh │ │ ├── gausscurvature.morpho │ │ ├── geodesiccurvature.morpho │ │ ├── gradient.morpho │ │ ├── symm.morpho │ │ └── torus.morpho │ ├── gradsq/ │ │ ├── disk.mesh │ │ ├── gradsq.morpho │ │ ├── gradsq1d.morpho │ │ ├── gradsq3d.morpho │ │ ├── integral.morpho │ │ ├── tetrahedron.mesh │ │ └── triangle.mesh │ ├── hydrogel/ │ │ ├── hydrogel.morpho │ │ ├── hydrogel1D.morpho │ │ ├── hydrogel1D_2elements.morpho │ │ ├── hydrogel2D.morpho │ │ ├── hydrogel2D_2elements.morpho │ │ ├── hydrogel3D_2elements.morpho │ │ ├── hydrogel_field_lacks_grade.morpho │ │ └── tetrahedron.mesh │ ├── length/ │ │ ├── length.morpho │ │ ├── length2d.morpho │ │ ├── length_hessian.morpho │ │ ├── length_integrandForElement.morpho │ │ └── line.mesh │ ├── linearelasticity/ │ │ ├── linearelasticity.morpho │ │ ├── relax.morpho │ │ ├── scalesquare.mesh │ │ ├── shearsquare.mesh │ │ ├── square.mesh │ │ └── stretchsquare.mesh │ ├── linecurvaturesq/ │ │ ├── gradient.morpho │ │ ├── gradient_symm.morpho │ │ ├── hessian.morpho │ │ ├── linecurvaturesq.morpho │ │ ├── linecurvaturesq.xmorpho │ │ ├── linecurvaturesq_integrandForElement.morpho │ │ ├── linecurvaturesq_symm.morpho │ │ ├── linecurvaturesq_symm.xmorpho │ │ └── symmetry.xmorpho │ ├── lineintegral/ │ │ ├── fields_incorrect_args.morpho │ │ ├── fields_insufficient_args.morpho │ │ ├── fields_toomany_args.morpho │ │ ├── lineintegral.morpho │ │ ├── lineintegral_fieldgradient.morpho │ │ ├── lineintegral_hessian.morpho │ │ ├── selection.morpho │ │ ├── tangent.morpho │ │ └── tangent2d.morpho │ ├── linetorsionsq/ │ │ ├── gradient.morpho │ │ ├── gradient_symm.morpho │ │ ├── hessian.morpho │ │ ├── linetorsionsq.morpho │ │ ├── linetorsionsq.xmorpho │ │ └── linetorsionsq_symm.morpho │ ├── meancurvaturesq/ │ │ ├── gradient.morpho │ │ ├── gradient_symm.morpho │ │ ├── meancurvaturesq.morpho │ │ └── symm.morpho │ ├── nematic/ │ │ ├── nematic.morpho │ │ ├── nematic3d.morpho │ │ ├── nematicdim.morpho │ │ ├── square.mesh │ │ └── tetrahedron.mesh │ ├── nematicelectric/ │ │ └── nematicelectric.morpho │ ├── normsq/ │ │ ├── normsq.morpho │ │ └── triangle.mesh │ ├── numericalderivatives.morpho │ ├── on_selection.morpho │ ├── relax.morpho │ ├── relax2.morpho │ ├── scalarpotential/ │ │ ├── scalarpotential.morpho │ │ ├── scalarpotential_hessian.morpho │ │ ├── scalarpotential_ndiff.morpho │ │ ├── scalarpotential_notcallable.morpho │ │ └── tetrahedron.mesh │ ├── square.mesh │ ├── triangle.mesh │ ├── volume/ │ │ ├── tetrahedron.mesh │ │ ├── volume.morpho │ │ └── volume_hessian.morpho │ ├── volumeenclosed/ │ │ ├── tetrahedron.mesh │ │ ├── volencl.morpho │ │ ├── volencl_hessian.morpho │ │ └── volencl_zeroelement.morpho │ └── volumeintegral/ │ ├── grad.morpho │ ├── testintegrals.morpho │ └── volume_integral.morpho ├── help.py ├── if/ │ ├── class_in_else.morpho │ ├── class_in_then.morpho │ ├── dangling_else.morpho │ ├── else.morpho │ ├── fn_in_else.morpho │ ├── fn_in_then.morpho │ ├── if.morpho │ ├── if_in_method.morpho │ ├── truth.morpho │ ├── var_in_else.morpho │ └── var_in_then.morpho ├── import/ │ ├── as.morpho │ ├── as_not_imported.morpho │ ├── file_not_found.morpho │ ├── for_clause.morpho │ ├── for_clause_restrict.morpho │ ├── for_string_after_for.morpho │ ├── import_class_extends.morpho │ ├── import_file.morpho │ ├── import_for_class.morpho │ ├── import_module.morpho │ ├── import_underscore.morpho │ ├── importtest.m │ ├── module_not_found.morpho │ ├── multiple.morpho │ ├── multiple_as.morpho │ ├── nested_import_in_module.morpho │ ├── nestedimporttest.m │ ├── nestedimporttest2.m │ ├── one_for_per_line.morpho │ ├── optional.morpho │ ├── optional_in_module.morpho │ └── underscoreimporttest.m ├── inheritance/ │ ├── constructor.morpho │ ├── inherit_from_function.morpho │ ├── inherit_from_nil.morpho │ ├── inherit_from_number.morpho │ ├── inherit_methods.morpho │ ├── parenthesized_superclass.morpho │ └── set_fields_from_base_class.morpho ├── integrate/ │ ├── counter.morpho │ ├── embedding.morpho │ ├── integrals1d.morpho │ ├── integrals2d.morpho │ ├── integrals2d_quantities.morpho │ ├── integrals3d.morpho │ ├── method_entry_type.morpho │ ├── method_not_a_dict.morpho │ ├── method_rule_not_found.morpho │ ├── method_rule_unavlb.morpho │ └── too_many_subdivisions.morpho ├── invocation/ │ ├── invocation.morpho │ └── invocation_on_class.morpho ├── json/ │ ├── json.morpho │ ├── testjson.morpho │ └── tostring.morpho ├── junk/ │ ├── ctrl.morpho │ ├── makectrl.xmorpho │ └── oeq0.morpho ├── list/ │ ├── empty_index.morpho │ ├── index_out_of_bounds.morpho │ ├── inherited.morpho │ ├── initializer_with_enumerable.morpho │ ├── initializer_with_vars.morpho │ ├── insert.morpho │ ├── ismember.morpho │ ├── join.morpho │ ├── list_from_tuple.morpho │ ├── nonint_index.mopho │ ├── order.morpho │ ├── pop_bounds.morpho │ ├── pop_index.morpho │ ├── pop_negative.morpho │ ├── remove.morpho │ ├── reverse.morpho │ ├── roll.morpho │ ├── sort_with_function.morpho │ ├── sort_with_function_flt.morpho │ ├── syntax.morpho │ └── tuples.morpho ├── logical/ │ ├── and.morpho │ ├── and_truth.morpho │ ├── or.morpho │ └── or_truth.morpho ├── math/ │ ├── isinf_isnan_isfinite.morpho │ ├── math.morpho │ └── sign.morpho ├── matrix/ │ ├── Lnorm.morpho │ ├── arith_scalar.morpho │ ├── arithmetic.morpho │ ├── assign.morpho │ ├── blockmatrix_constructor.morpho │ ├── concatenate.morpho │ ├── concatenate_sparse.morpho │ ├── dimensions.morpho │ ├── eigensystem.morpho │ ├── eigenvalues.morpho │ ├── format.morpho │ ├── get_column.morpho │ ├── identity.morpho │ ├── incompatible_add.morpho │ ├── incompatible_mul.morpho │ ├── incompatible_sub.morpho │ ├── inherited.morpho │ ├── initializer.morpho │ ├── inverse.morpho │ ├── linearsolve.morpho │ ├── linearsolve3x3.morpho │ ├── negate.morpho │ ├── nonnum_indices.morpho │ ├── nonnum_initializer.morpho │ ├── norm.morpho │ ├── outer.morpho │ ├── reshape.morpho │ ├── roll.morpho │ ├── scalar_mul.morpho │ ├── set_column.morpho │ ├── trace.morpho │ └── transpose.morpho ├── mesh/ │ ├── addgradeerror.morpho │ ├── addgradetwo.morpho │ ├── addgradezero.morpho │ ├── clone.morpho │ ├── connectivity.morpho │ ├── connectivity_tetrahedron.morpho │ ├── err_empty_mesh_set_element.morpho │ ├── err_mesh_con_args.morpho │ ├── inherited.morpho │ ├── load.morpho │ ├── load_missing_coord.morpho │ ├── load_spurious_vertex_ref.morpho │ ├── maxgrade.morpho │ ├── merge.morpho │ ├── out.mesh │ ├── removegrade.morpho │ ├── resetconnectivity.morpho │ ├── save.morpho │ ├── sphere.mesh │ ├── square.mesh │ ├── square_missingcoord.mesh │ ├── square_spurious_vertex_ref.mesh │ ├── tetrahedron.mesh │ ├── tetrahedron2.mesh │ └── vertexposition.morpho ├── method/ │ ├── arity.morpho │ ├── empty_block.morpho │ ├── extra_arguments.morpho │ ├── missing_arguments.morpho │ ├── not_found.morpho │ ├── optional.morpho │ ├── optional_indirect.morpho │ ├── print_bound_method.morpho │ ├── return_in_method.morpho │ ├── too_many_arguments.morpho │ └── too_many_parameters.morpho ├── modules/ │ ├── constants.morpho │ ├── delaunay/ │ │ └── delaunay.morpho │ ├── meshgen/ │ │ └── disk.morpho │ ├── meshslice/ │ │ ├── slice.morpho │ │ ├── slice_delaunay.morpho │ │ ├── slice_empty.morpho │ │ └── tetrahedron.mesh │ ├── meshtools/ │ │ ├── dimension_inconsistent.morpho │ │ ├── dimension_unknown.morpho │ │ ├── merge_duplicates.morpho │ │ ├── mesh_still_too_large.morpho │ │ ├── mesh_too_large.morpho │ │ ├── meshrefine.morpho │ │ ├── polyhedron.morpho │ │ ├── testprune.morpho │ │ ├── testrefine3d.morpho │ │ ├── testrefineselection.morpho │ │ ├── tetrahedron.mesh │ │ ├── tetrahedron.morpho │ │ ├── tetrahedron2.mesh │ │ ├── triangle.mesh │ │ └── triangle.morpho │ ├── optimize/ │ │ └── cg.morpho │ └── povray.morpho ├── namespace/ │ ├── function.xmorpho │ ├── namespace_class.morpho │ ├── namespace_class_extends.morpho │ ├── namespace_class_restrict.morpho │ └── namespace_functioncall.morpho ├── newline/ │ ├── block.morpho │ ├── classes.morpho │ ├── for.morpho │ ├── functions.morpho │ └── variables.morpho ├── nil/ │ └── literal.morpho ├── number/ │ ├── decimal_point_at_eof.morpho │ ├── float_negative_overflow.morpho │ ├── float_overflow.morpho │ ├── integer_overflow.morpho │ ├── leading_dot.morpho │ ├── literals.morpho │ ├── nan_equality.morpho │ └── trailing_dot.morpho ├── object/ │ ├── baseclass.morpho │ ├── builtin_inheritance.morpho │ ├── class_responds_to.morpho │ ├── clone.morpho │ ├── enumerate.morpho │ ├── has.morpho │ ├── index.morpho │ ├── invoke.morpho │ ├── non_string_index.morpho │ └── responds_to.morpho ├── operator/ │ ├── add.morpho │ ├── add_bool_nil.morpho │ ├── add_bool_num.morpho │ ├── add_bool_string.morpho │ ├── add_nil_nil.morpho │ ├── add_num_nil.morpho │ ├── add_string_nil.morpho │ ├── comparison.morpho │ ├── divide.morpho │ ├── divide_nonnum_num.morpho │ ├── divide_num_nonnum.morpho │ ├── equals.morpho │ ├── equals_class.morpho │ ├── equals_method.morpho │ ├── fpcompare.morpho │ ├── greater_nonnum_num.morpho │ ├── greater_num_nonnum.morpho │ ├── greater_or_equal_nonnum_num.morpho │ ├── greater_or_equal_num_nonnum.morpho │ ├── less_nonnum_num.morpho │ ├── less_num_nonnum.morpho │ ├── less_or_equal_nonnum_num.morpho │ ├── less_or_equal_num_nonnum.morpho │ ├── more_comparison.morpho │ ├── multiply.morpho │ ├── multiply_nonnum_num.morpho │ ├── multiply_num_nonnum.morpho │ ├── negate.morpho │ ├── negate_nonnum.morpho │ ├── not.morpho │ ├── not_class.morpho │ ├── not_equals.morpho │ ├── subtract.morpho │ ├── subtract_nonnum_num.morpho │ ├── subtract_num_nonnum.morpho │ ├── ternary.morpho │ ├── ternary_in_function.morpho │ ├── ternary_in_loop.morpho │ ├── ternary_missing_colon.morpho │ └── ternary_nested.morpho ├── print/ │ └── missing_argument.morpho ├── programs/ │ ├── delta_blue.morpho │ ├── fannkuch.morpho │ ├── fibonacci.morpho │ ├── histogram.morpho │ └── integrate.morpho ├── property/ │ ├── call_function_field.morpho │ ├── call_nonfunction_field.morpho │ ├── get_on_bool.morpho │ ├── get_on_class.morpho │ ├── get_on_function.morpho │ ├── get_on_nil.morpho │ ├── get_on_num.morpho │ ├── get_on_string.morpho │ ├── index_property_in_args.morpho │ ├── many.morpho │ ├── method.morpho │ ├── method_binds_self.morpho │ ├── on_instance.morpho │ ├── property_error_in_index.morpho │ ├── property_index.morpho │ ├── set_evaluation_order.morpho │ ├── set_index_property.morpho │ ├── set_on_bool.morpho │ ├── set_on_class.morpho │ ├── set_on_function.morpho │ ├── set_on_nil.morpho │ ├── set_on_num.morpho │ ├── set_on_string.morpho │ └── undefined.morpho ├── range/ │ ├── constructor.morpho │ ├── count_down.morpho │ ├── exclusive.morpho │ ├── inclusive.morpho │ ├── inherited.morpho │ ├── invalid_constructor_too_few_args.morpho │ ├── invalid_constructor_too_many_args.morpho │ ├── invalid_constructor_type.morpho │ ├── list_constructor.morpho │ ├── step_too_fine.morpho │ └── syntax.morpho ├── return/ │ ├── after_else.morpho │ ├── after_if.morpho │ ├── after_while.morpho │ ├── at_top_level.morpho │ ├── in_for_in.morpho │ ├── in_function.morpho │ ├── in_method.morpho │ └── return_nil_if_no_value.morpho ├── selection/ │ ├── add_grade.morpho │ ├── boundary.morpho │ ├── circlesquare.mesh │ ├── clone.morpho │ ├── count.morpho │ ├── get_index.morpho │ ├── inherited.morpho │ ├── no_mesh_error.morpho │ ├── selection.morpho │ ├── selection_withmatrix.morpho │ ├── set_index.morpho │ ├── set_operations.morpho │ └── square.mesh ├── self/ │ ├── closure.morpho │ ├── nested_class.morpho │ ├── nested_closure.morpho │ ├── self_at_top_level.morpho │ ├── self_in_method.morpho │ ├── self_in_top_level_function.morpho │ └── self_set_prop_index.morpho ├── slice/ │ ├── arraySlicing.morpho │ ├── listSlicing.morpho │ └── matrixSlicing.morpho ├── sparse/ │ ├── arithmetic.morpho │ ├── block_constructor_dimensions.morpho │ ├── clone.morpho │ ├── col_indices.morpho │ ├── column.morpho │ ├── concatenate.morpho │ ├── dense_sparse_mul.morpho │ ├── dimensions.morpho │ ├── edit_sparse.morpho │ ├── empty_initializer.morpho │ ├── enumerate.morpho │ ├── incompatible_add.morpho │ ├── incompatible_mul.morpho │ ├── indices.morpho │ ├── inherited.morpho │ ├── initializer.morpho │ ├── invld_initializer.morpho │ ├── linearsolve.morpho │ ├── nonnum.morpho │ ├── row_indices.morpho │ ├── scalar_mul.morpho │ ├── set_row_indices.morpho │ ├── sparse_dense_mul.morpho │ ├── sparse_dense_mul_dimensions.morpho │ ├── sparse_empty.morpho │ ├── sparse_empty_list.morpho │ ├── todensematrix.morpho │ └── transpose.morpho ├── string/ │ ├── ctrl_in_string.morpho │ ├── empty_interpolation.morpho │ ├── error_after_multiline.morpho │ ├── escape.morpho │ ├── escchar.morpho │ ├── index.morpho │ ├── inherited.morpho │ ├── interpolation.morpho │ ├── interpolation_keyword.morpho │ ├── interpolation_propertyindex.morpho │ ├── interpolation_syntaxerror.morpho │ ├── interpolation_types.morpho │ ├── invalid_unicode.morpho │ ├── literals.morpho │ ├── multiline.morpho │ ├── split.morpho │ ├── split_gc.morpho │ ├── string_veneer.morpho │ ├── tonumber.morpho │ ├── unicode.morpho │ ├── unterminated.morpho │ └── utf8.morpho ├── super/ │ ├── bound_method.morpho │ ├── call_other_method.morpho │ ├── call_same_method.morpho │ ├── closure.morpho │ ├── constructor.morpho │ ├── extra_arguments.morpho │ ├── indirectly_inherited.morpho │ ├── missing_arguments.morpho │ ├── no_superclass_bind.morpho │ ├── no_superclass_call.morpho │ ├── no_superclass_method.morpho │ ├── parenthesized.morpho │ ├── reassign_superclass.morpho │ ├── self_in_superclass_method.morpho │ ├── super_at_top_level.morpho │ ├── super_in_closure_in_inherited_method.morpho │ ├── super_in_inherited_method.morpho │ ├── super_in_top_level_function.morpho │ ├── super_without_dot.morpho │ └── super_without_name.morpho ├── syntax/ │ ├── comments.morpho │ ├── empty_file.morpho │ ├── illegal_chars_in_symbol.morpho │ └── symbols.morpho ├── system/ │ ├── args.morpho │ ├── args.xmorpho │ └── system.morpho ├── test.py ├── try/ │ ├── break_in_catch.morpho │ ├── continue_in_catch.morpho │ ├── err_stack_overflw.morpho │ ├── return_in_try.morpho │ ├── try.morpho │ ├── try_builtin_err.morpho │ ├── try_empty_block.morpho │ ├── try_empty_catch.morpho │ ├── try_empty_label.morpho │ ├── try_err_in_function.morpho │ ├── try_in_function.morpho │ ├── try_in_loop.morpho │ ├── try_in_method.morpho │ ├── try_missing_catch.morpho │ ├── try_missing_catch_block.morpho │ ├── try_nested.morpho │ ├── try_recurse.morpho │ ├── try_reentrancy.morpho │ ├── try_reentrancy_uncaught.morpho │ └── try_var_in_catch.morpho ├── tuple/ │ ├── nested_tuple.morpho │ ├── single_nested_tuple.morpho │ ├── tuple.morpho │ ├── tuple_anonymousfn.morpho │ ├── tuple_apply.morpho │ ├── tuple_compare.morpho │ ├── tuple_enumerate.morpho │ ├── tuple_getindex.morpho │ ├── tuple_in_dictionary.morpho │ ├── tuple_index_out_of_bounds.morpho │ ├── tuple_ismember.morpho │ ├── tuple_join.morpho │ ├── tuple_slice.morpho │ └── tuple_syntax.morpho ├── type/ │ ├── iscallable.morpho │ └── typecheck.morpho ├── types/ │ ├── builtin_class_signature.morpho │ ├── function_signature.morpho │ ├── multiple_dispatch/ │ │ ├── apply.morpho │ │ ├── apply_invocation_multiple_dispatch.morpho │ │ ├── check_args.morpho │ │ ├── class_multiple_dispatch.morpho │ │ ├── class_multiple_dispatch_args.morpho │ │ ├── class_multiple_dispatch_post.morpho │ │ ├── class_multiple_initializer.morpho │ │ ├── class_visitor.morpho │ │ ├── closure.morpho │ │ ├── deep_nested_closure.morpho │ │ ├── duplicate.morpho │ │ ├── duplicate_no_arg.morpho │ │ ├── duplicate_no_type.morpho │ │ ├── duplicate_no_type_two.morpho │ │ ├── duplicate_no_type_two_arguments.morpho │ │ ├── duplicate_one_arg.morpho │ │ ├── duplicate_scope.morpho │ │ ├── free.morpho │ │ ├── import.morpho │ │ ├── import_for.morpho │ │ ├── in_function.morpho │ │ ├── inheritance.morpho │ │ ├── inheritance_single.morpho │ │ ├── instance.morpho │ │ ├── invocation_multiple_dispatch.morpho │ │ ├── multi_nested_closure.morpho │ │ ├── multiple_dispatch.morpho │ │ ├── namespace.morpho │ │ ├── namespace.xmorpho │ │ ├── namespace_for.morpho │ │ ├── namespace_for_new.morpho │ │ ├── namespace_for_overwrite.morpho │ │ ├── nested_closure.morpho │ │ ├── nparams.morpho │ │ ├── nparams_varg.morpho │ │ ├── object_overflow.morpho │ │ ├── optional.morpho │ │ ├── optional_invld.morpho │ │ ├── pets.morpho │ │ ├── pets.zmorpho │ │ ├── pets_subclass.morpho │ │ ├── recursion.morpho │ │ ├── scope.morpho │ │ ├── two.morpho │ │ ├── value.morpho │ │ ├── varg.morpho │ │ └── varg_as_backup.morpho │ ├── type_from_namespace.morpho │ ├── type_in_function.morpho │ ├── type_in_function_fake_namespace.morpho │ ├── type_in_function_namespace.morpho │ ├── type_in_function_namespace_not_found.morpho │ ├── type_in_global.morpho │ ├── type_inheritance.morpho │ ├── type_instance.morpho │ ├── type_namespace.xmorpho │ ├── type_violation_constant.morpho │ ├── type_violation_dictionary.morpho │ ├── type_violation_global.morpho │ ├── type_violation_in_function.morpho │ ├── type_violation_inheritance.morpho │ ├── type_violation_instance.morpho │ ├── type_violation_matrix.morpho │ ├── type_violation_propagation.morpho │ └── type_violation_range.morpho ├── valgrind.py ├── variable/ │ ├── duplicate_local.morpho │ ├── duplicate_parameter.morpho │ ├── early_bound.morpho │ ├── in_middle_of_block.morpho │ ├── in_nested_block.morpho │ ├── local_from_method.morpho │ ├── multiple.morpho │ ├── redeclare_global.morpho │ ├── redefine_global.morpho │ ├── scope_reuse_in_different_blocks.morpho │ ├── shadow_and_local.morpho │ ├── shadow_global.morpho │ ├── shadow_local.morpho │ ├── undefined_global.morpho │ ├── undefined_local.morpho │ ├── uninitialized.morpho │ ├── unreached_undefined.morpho │ ├── use_false_as_var.morpho │ ├── use_global_in_initializer.morpho │ ├── use_nil_as_var.morpho │ ├── use_self_as_var.morpho │ └── var_dictionary_as_label.morpho ├── veneer/ │ ├── bool.morpho │ ├── float.morpho │ ├── format_args.morpho │ ├── format_invld_args.morpho │ ├── int.morpho │ └── invocation.morpho ├── vtk/ │ ├── ensurevtkfilename.morpho │ ├── export_and_import_mesh.morpho │ ├── export_and_import_no_fieldname.morpho │ ├── export_and_import_scalar.morpho │ ├── export_and_import_scalar_and_vector.morpho │ ├── export_and_import_vector.morpho │ ├── export_and_import_vector_2d.morpho │ ├── export_import_2d.morpho │ ├── export_import_3d.morpho │ ├── export_incorrect_field_4d_vector.morpho │ ├── export_incorrect_field_grade1.morpho │ ├── export_incorrect_field_grade2.morpho │ ├── export_incorrect_field_tensor.morpho │ ├── import_external_vtk.morpho │ ├── import_mesh.morpho │ ├── import_scalar.morpho │ ├── import_scalar_vector.morpho │ ├── import_vector.morpho │ ├── importer_containsfield.morpho │ ├── importer_fieldlist.morpho │ ├── importer_incorrect_fieldname.morpho │ ├── mesh.vtk │ ├── mesh_scalar.vtk │ ├── mesh_scalar_vector.vtk │ ├── mesh_vector.vtk │ ├── rbc_001.vtk │ ├── square.mesh │ ├── tetrahedron.mesh │ ├── vtk_exporter_addfield_fname_not_str_err.morpho │ ├── vtk_exporter_fname_not_str_err.morpho │ ├── vtk_exporter_init_err.morpho │ └── vtk_exporter_invalid_fname_err.morpho ├── wc └── while/ ├── class_in_body.morpho ├── closure_in_body.morpho ├── fn_in_body.morpho ├── if_in_body.morpho ├── return_closure.morpho ├── return_inside.morpho ├── syntax.morpho └── var_in_body.morpho ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[Bug]" labels: bug, Needs Priority assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: [ "main", "dev" ] pull_request: branches: [ "main", "dev" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure run: | sudo apt update sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev python -m pip install --upgrade pip python -m pip install regex colored - name: make run: (mkdir build; cd build; cmake ..; sudo make install) ================================================ FILE: .github/workflows/buildandtest.yml ================================================ name: Build and Test on: push: branches: [ "main", "dev" ] pull_request: branches: [ "main", "dev" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure run: | sudo apt update sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev sudo apt install libunistring-dev python -m pip install --upgrade pip python -m pip install regex colored - name: make run: | mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install sudo mkdir /usr/local/lib/morpho - name: getcli run: | git clone https://github.com/Morpho-lang/morpho-cli.git cd morpho-cli mkdir build cd build cmake .. sudo make install - name: test run: | cd test python3 test.py -c ================================================ FILE: .github/workflows/buildandtestmultithreaded.yml ================================================ name: TestMultiThreaded on: push: branches: [ "main", "dev" ] pull_request: branches: [ "main", "dev" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure run: | sudo apt update sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev sudo apt install libunistring-dev python -m pip install --upgrade pip python -m pip install regex colored - name: make run: | mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install sudo mkdir /usr/local/lib/morpho - name: getcli run: | git clone https://github.com/Morpho-lang/morpho-cli.git cd morpho-cli mkdir build cd build cmake .. sudo make install - name: test run: | cd test python3 test.py -c -m ================================================ FILE: .github/workflows/codeql.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL Advanced" on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: '24 4 * * 5' jobs: analyze: name: Analyze (${{ matrix.language }}) # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read # only required for workflows in private repositories actions: read contents: read strategy: fail-fast: false matrix: include: - language: c-cpp build-mode: manual # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' # Use `c-cpp` to analyze code written in C, C++ or both # Use 'java-kotlin' to analyze code written in Java, Kotlin or both # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # If the analyze step fails for one of the languages you are analyzing with # "We were unable to automatically build your code", modify the matrix above # to set the build mode to "manual" for that language. Then modify this step # to build your code. # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - if: matrix.build-mode == 'manual' shell: bash run: | sudo apt update sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev python -m pip install --upgrade pip python -m pip install regex colored mkdir build cd build cmake .. sudo make install - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/examples.yml ================================================ name: Examples on: push: branches: [ "main", "dev" ] pull_request: branches: [ "main", "dev" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure run: | sudo apt update sudo apt install libglfw3-dev sudo apt install povray sudo apt install libfreetype6-dev sudo apt install fonts-freefont-ttf sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev sudo apt install libunistring-dev python -m pip install --upgrade pip python -m pip install regex colored - name: make run: | mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install sudo mkdir /usr/local/lib/morpho - name: getcli run: | git clone https://github.com/Morpho-lang/morpho-cli.git cd morpho-cli mkdir build cd build cmake .. sudo make install - name: morphoview run: | git clone https://github.com/morpho-lang/morpho-morphoview.git cd morpho-morphoview mkdir build cd build cmake .. sudo make install - name: morphopm run: | git clone https://github.com/Morpho-lang/morpho-morphopm.git cd morpho-morphopm ./morphopm install optimize4 - name: test run: | cd examples python3 examples.py -c ================================================ FILE: .github/workflows/nonanboxing.yml ================================================ name: No NANBoxing on: push: branches: [ "main", "dev" ] pull_request: branches: [ "main", "dev" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure run: | sudo apt update sudo apt install libsuitesparse-dev sudo apt install liblapacke-dev sudo apt install libunistring-dev python -m pip install --upgrade pip python -m pip install regex colored - name: make run: | mkdir build cd build cmake -DMORPHO_DISABLENANBOXING=ON .. sudo make install sudo mkdir /usr/local/lib/morpho - name: getcli run: | git clone https://github.com/Morpho-lang/morpho-cli.git cd morpho-cli mkdir build cd build cmake -DMORPHO_DISABLENANBOXING=ON .. sudo make install - name: test run: | cd test python3 test.py -c ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # POVray files *.pov # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf .DS_Store .entitlements .vscode/settings.json .vscode/ test/FailedTests*.txt *.png *.out manual/src/manual.lyx~ test/vtk/data.case2.vtk test/vtk/data.case3.0.vtk examples/qtensor/Qtensor_K_0.01.png examples/qtensor/Qtensor_K_0.01.png test/vtk/data.vtk test/vtk/square.vtk test/vtk/tetrahedron.vtk devguide/devguide.lyx~ build/* build-xcode/* build-win/* *.valgrind /.vs ================================================ FILE: .readthedocs.yml ================================================ # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.9" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: help/conf.py # Optionally set the version of Python and requirements required to build your docs python: install: - requirements: help/requirements.txt ================================================ FILE: CMakeLists.txt ================================================ #------------------------------------------------------------------------------- # Morpho/CMakeLists.txt #------------------------------------------------------------------------------- cmake_minimum_required(VERSION 3.13) project(morpho-libmorpho) #------------------------------------------------------------------------------- # Platform dependent options #------------------------------------------------------------------------------- set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(morpho SHARED "") #------------------------------------------------------------------------------- # Options #------------------------------------------------------------------------------- option(MORPHO_DISABLENANBOXING "Disables NAN Boxing" OFF) option(MORPHO_GCSTRESSTEST "Stress tests the garbage collector" OFF) option(MORPHO_BUILD_LINALG "Builds with linear algebra" ON) option(MORPHO_BUILD_SPARSE "Builds with sparse matrix support" ON) option(MORPHO_BUILD_GEOMETRY "Builds with geometry library" ON) #------------------------------------------------------------------------------- # Process options #------------------------------------------------------------------------------- # Option to disable NAN Boxing if(MORPHO_DISABLENANBOXING) target_compile_definitions(morpho PUBLIC _NO_NAN_BOXING) endif() # Option to stress test Garbage Collector if(MORPHO_GCSTRESSTEST) target_compile_definitions(morpho PUBLIC _DEBUG_STRESSGARBAGECOLLECTOR) endif() # Set help directory if(MORPHO_HELP_BASEDIR) target_compile_definitions(morpho PUBLIC MORPHO_HELP_BASEDIR=\"${MORPHO_HELP_BASEDIR}\") endif() # Set directory for modules supplied with morpho if(MORPHO_MODULE_BASEDIR) target_compile_definitions(morpho PUBLIC MORPHO_MODULE_BASEDIR=\"${MORPHO_MODULE_BASEDIR}\") endif() # Include geometry as part of the build if(MORPHO_BUILD_GEOMETRY) target_compile_definitions(morpho PUBLIC MORPHO_INCLUDE_GEOMETRY) endif() #------------------------------------------------------------------------------- # BLAS and LAPACK #------------------------------------------------------------------------------- if (MORPHO_BUILD_LINALG) message(STATUS "Searching for BLAS and LAPACK") # Locate a lapack version # Currently we prefer LAPACKE # TODO: Fix morpho source to select between lapack and lapacke find_library(LAPACK_LIBRARY NAMES lapacke liblapacke lapack liblapack libopenblas HINTS "C:\\Program Files\\Morpho\\lib\\" ) if (LAPACK_LIBRARY) message(STATUS "Found LAPACK at ${LAPACK_LIBRARY}") endif() # Locate cblas find_library(CBLAS_LIBRARY NAMES cblas libcblas blas libblas openblas libopenblas HINTS "C:\\Program Files\\Morpho\\lib\\" ) if (CBLAS_LIBRARY) message(STATUS "Found blas at ${CBLAS_LIBRARY}") endif() # Find cblas.h header file find_path(CBLAS_INCLUDE cblas.h HINTS "C:\\Program Files\\Morpho\\include\\lapack") # Add cblas headers to include folders target_include_directories(morpho PUBLIC ${CBLAS_INCLUDE}) message(STATUS "Found cblas headers at ${CBLAS_INCLUDE}") target_link_libraries(morpho ${CBLAS_LIBRARY}) target_link_libraries(morpho ${LAPACK_LIBRARY}) target_compile_definitions(morpho PUBLIC MORPHO_INCLUDE_LINALG) endif() #------------------------------------------------------------------------------- # SuiteSparse #------------------------------------------------------------------------------- if (MORPHO_BUILD_SPARSE) message(STATUS "Searching for Suitesparse") # Locate suitesparse find_library(SUITESPARSE_LIBRARY NAMES cxsparse libcxsparse HINTS "C:\\Program Files\\Morpho\\lib\\" ) if (SUITESPARSE_LIBRARY) message(STATUS "Found suitesparse at ${SUITESPARSE_LIBRARY}") endif() # Find suitesparse cs.h header file find_file(SUITESPARSE_HEADER cs.h HINTS /home/linuxbrew/.linuxbrew/include/suitesparse /usr/local/include /usr/local/include/suitesparse /opt/homebrew/include/suitesparse /usr/include/suitesparse "C:\\Program Files\\Morpho\\include\\suitesparse") # Identify folder that cs.h is located in from SUITESPARSE_HEADER and store in SUITESPARSE_INCLUDE get_filename_component(SUITESPARSE_INCLUDE ${SUITESPARSE_HEADER} DIRECTORY) # Add suitesparse headers to include folders target_include_directories(morpho PUBLIC ${SUITESPARSE_INCLUDE}) message(STATUS "Found cs.h at ${SUITESPARSE_INCLUDE}") target_compile_definitions(morpho PUBLIC MORPHO_INCLUDE_SPARSE) target_link_libraries(morpho ${SUITESPARSE_LIBRARY}) endif() #------------------------------------------------------------------------------- # Threads #------------------------------------------------------------------------------- set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) #------------------------------------------------------------------------------- # Add morpho sources #------------------------------------------------------------------------------- add_subdirectory(src) # Include morpho header files across the project target_include_directories(morpho PUBLIC src src/builtin src/classes src/core src/datastructures src/debug src/geometry src/linalg src/support ) # Create source groups for IDE source_group("builtin" REGULAR_EXPRESSION src/builtin/.*\\.[ch]) source_group("classes" REGULAR_EXPRESSION src/classes/.*\\.[ch]) source_group("core" REGULAR_EXPRESSION src/core/.*\\.[ch]) source_group("datastructures" REGULAR_EXPRESSION src/datastructures/.*\\.[ch]) source_group("debug" REGULAR_EXPRESSION src/debug/.*\\.[ch]) source_group("geometry" REGULAR_EXPRESSION src/geometry/.*\\.[ch]) source_group("linalg" REGULAR_EXPRESSION src/linalg/.*\\.[ch]) source_group("support" REGULAR_EXPRESSION src/support/.*\\.[ch]) target_link_libraries(morpho ${CMAKE_DL_LIBS} Threads::Threads) #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- # Install the resulting library install(TARGETS morpho) # Install morpho header files # The below works in 3.23, which is too recent for Ubuntu # install(TARGETS morpho # FILE_SET public_headers # DESTINATION include/morpho #) # So we'll use a hacky way for now file(GLOB_RECURSE MORPHO_HEADER_FILES "src/*.h") install(FILES ${MORPHO_HEADER_FILES} DESTINATION include/morpho) # Similarly install morpho modules file(GLOB_RECURSE MORPHO_MODULES_FILES "modules/*.morpho") install(FILES ${MORPHO_MODULES_FILES} DESTINATION share/morpho/modules) # Similarly install morpho help files file(GLOB_RECURSE MORPHO_HELP_FILES "help/*.md") install(FILES ${MORPHO_HELP_FILES} DESTINATION share/morpho/help) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Morpho Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at timothy.atherton@tufts.edu. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Morpho Thankyou for your interest in helping to improve Morpho! We welcome contributions from everyone. If you are unsure of anything, feel free to reach out via the Github, submit an issue or make a pull request. There are many ways you can contribute to Morpho: * If you have identified a bug, you can report it by [submitting an issue with the `Bug` label](https://github.com/Morpho-lang/morpho/issues/new?assignees=&labels=bug%2C+Needs+Priority&template=bug_report.md&title=%5BBug%5D). If you have solved a bug, first off, that's excellent, and thank you! You can submit your fix as a pull request to the [dev branch](https://github.com/Morpho-lang/morpho/tree/dev) of Morpho. This branch is where the updates are collected before eventually releasing them into a new version in the main branch. All pull requests to `dev` are passed through the automated testing suite. Check [below](#unit-tests) for more on that and about adding your own tests! * If you want to propose a new feature in Morpho, you can submit an issue with the [`enhancement`](https://github.com/Morpho-lang/morpho/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) label. * Consult our [Roadmap](https://github.com/Morpho-lang/morpho/wiki/Road-Map) to see the features we've identified as priorities for upcoming releases of Morpho. If you're interested in working on one of these, reach out to the development team. * If you use Morpho but are new to GitHub, or to contributing to Morpho, the issues labeled [`good first issue`](https://github.com/Morpho-lang/morpho/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) highlight easy-to-fix bugs that will get you started. [Here](https://gist.github.com/Chaser324/ce0505fbed06b947d962) is a guide that explains the best practices for making a pull request. * Morpho is highly modular and modules providing new features are especially welcome. The [devguide](https://github.com/Morpho-lang/morpho-devguide) explains how to package morpho code into an easily downloadable module. The [morphopm](https://github.com/Morpho-lang/morpho-morphopm) package manager can be used to download and install modules. * Help with unit tests, additional documentation etc. are also great ways to contribute to the project. All contributors are expected to follow the [Morpho Code of Conduct](https://github.com/Morpho-lang/morpho/blob/main/CODE_OF_CONDUCT.md). For further guidance and pointers, a developer's guide is gradually being assembled [devguide](https://github.com/Morpho-lang/morpho-devguide/blob/main/devguide.pdf). We also encourage you to [join our Slack community](https://join.slack.com/t/morphoco/shared_invite/zt-1hiby4iqv-UhqKEeqZih0vSG3k4gEfXQ) or get in touch via email. ## Unit tests Morpho has an extensive set of unit-tests to make sure any new piece of code doesn't break essential functionality. Moreover, code within any pull requests to `dev` or `main` is automatically put through the test suite. While this will catch failing tests, if any, you can make sure all the tests are passing on your branch beforehand by running the test suite locally: cd test python3 test.py If you have fixed a new bug, chances are the existing unit-tests didn't capture that buggy behavior. In that case, it's a good idea to _add_ new tests to lock down the behavior. You can add tests simply by adding the test file anywhere under the `test/` directory. The files are organized around topic for convenience, but any file therein will get tested. We highly welcome contributions to the testing suite. Try writing tests that don't overlap with the existing tests, and help us lock down any remaining bugs in Morpho's functionality. ### Formatting a unit test While a new unit-testing module is [in the works](https://github.com/Morpho-lang/morpho/pull/147), the current unit-test are executed in `python` by looking for the keyword `expect`. For instance, here is an example from the test `power.morpho` that tests the arithmetic power operator: // Test power operator print 2^2 // expect: 4 Here, the operation is performed and the output is printed. The special comment that starts with `// expect: ` followed by the expected output is picked up by the python testing file and compared with the output. For multiple `print` statements, the `// expect: ` comments can appear anywhere in the code, as long as they are in the right order. Other comments work as regular comments and can be used to annotate the test. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020-21 T J Atherton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![Morpho](https://github.com/Morpho-lang/morpho-manual/blob/main/src/Figures/morphologosmall.png#gh-light-mode-only)![Morpho](https://github.com/Morpho-lang/morpho-manual/blob/main/src/Figures/morphologosmall-white.png#gh-dark-mode-only) [![Build](https://github.com/Morpho-lang/morpho/actions/workflows/build.yml/badge.svg)](https://github.com/Morpho-lang/morpho/actions/workflows/build.yml) [![Test suite](https://github.com/Morpho-lang/morpho/actions/workflows/buildandtest.yml/badge.svg)](https://github.com/Morpho-lang/morpho/actions/workflows/buildandtest.yml) The Morpho language. Morpho is a programmable environment for shape optimization and scientific computing tasks more generally. Morpho aims to be: * **Familiar**. Morpho uses syntax similar to other C-family languages. The syntax fits on a postcard, so it's easy to learn. * **Fast**. Morpho programs run as efficiently as other well-implemented dynamic languages like *wren* or *lua* (Morpho is often significantly faster than Python, for example). Morpho leverages numerical libraries like *BLAS*, *LAPACK* and *SUITESPARSE* to provide high performance. * **Class-based**. Morpho is highly object-oriented, which simplifies coding and enables reusability. * **Extendable**. Functionality is easy to add via packages, both in Morpho and in C or other compiled languages. Packages can be downloaded, installed and distributed via the [morphopm](https://github.com/Morpho-lang/morpho-morphopm) package manager. *Morpho is based upon work supported by the National Science Foundation under grants DMR-1654283 and OAC-2003820.* In academic publications, please cite morpho as: * Joshi, C. et al. "A programmable environment for shape optimization and shapeshifting problems", Nat Comput Sci (2024) [doi.org/10.1038/s43588-024-00749-7](https://doi.org/10.1038/s43588-024-00749-7). A preprint of the paper is also available on the [arXiv preprint server](https://arxiv.org/abs/2208.07859). ## Learn and use morpho Documentation is available on [readthedocs](https://morpho-lang.readthedocs.io/en/latest/), an extensive [user manual](https://github.com/Morpho-lang/morpho-manual/blob/main/manual.pdf) and a [developer guide](https://github.com/Morpho-lang/morpho-devguide/blob/main/devguide.pdf). A [Slack community](https://join.slack.com/t/morphoco/shared_invite/zt-1o6azavwl-XMtjjFwxW~P6C8rc~YbBlA) is also available for people interested in using morpho and seeking support. We now have a sequence of tutorial videos on our [Youtube channel](https://www.youtube.com/@Morpho-lang) to help you learn Morpho: * An [introduction to the Morpho language](https://youtu.be/eVPGWpNDeq4) * Introduction to [shape optimization with Morpho](https://youtu.be/odCkR0PDKa0) ## Community and Contributions Morpho is under active development and we welcome contributions! Please see the [Contributor's guide](CONTRIBUTING.md) for more information about how you can get involved in the morpho project. For those interested in extending morpho or working with the source a [Developer guide](https://github.com/Morpho-lang/morpho-devguide) is also provided in a separate repository. We provide a [Roadmap](https://github.com/Morpho-lang/morpho/wiki/Road-Map) for future development plans that might give you ideas for how you could contribute. We also welcome bug reports and suggestions: Please feel free to use the *Issues* feature on our github repository to bring these to the developers' attention. Participation in the morpho community, both as users and developers, is bound by our [Code of Conduct](CODE_OF_CONDUCT.md). ## Installation Code in this repository builds morpho as a shared library. Morpho also requires two subsidiary programs, a [terminal app](https://github.com/Morpho-lang/morpho-cli), and a [viewer application](https://github.com/Morpho-lang/morpho-morphoview). For this release, morpho can be installed on all supported platforms using the homebrew package manager. Alternatively, the program can be installed from source as described below. We are continuously working on improving morpho installation, and hope to provide additional mechanisms for installation in upcoming releases. Morpho packages to extend the program's capability can be downloaded, installed and distributed via the associated [morphopm](https://github.com/Morpho-lang/morpho-morphopm) package manager. ### Installation with homebrew The simplest way to install morpho is through the [homebrew package manager](https://brew.sh). To do so: 1. If not already installed, install homebrew on your machine as described on the [homebrew website](https://brew.sh) 2. Open a terminal and type: ``` brew update brew tap morpho-lang/morpho brew install morpho morpho-cli morpho-morphoview morpho-morphopm ``` If you need to uninstall morpho, simply open a terminal and type `brew uninstall morpho-morphopm morpho-cli morpho-morphoview morpho`. It's very important to uninstall the homebrew morpho in this way before attempting to install from source as below. ### Install from source The second way to install morpho is by compiling the source code directly. Morpho now leverages the [Cmake](https://cmake.org) build system, which enables platform independent builds. Windows users must first install Windows Subsystem for Linux; some instructions to do so are found below. #### Gather dependencies You can use any appropriate package manager to install morpho's dependencies via the terminal. Using homebrew (preferred on macOS): ``` brew update brew install cmake glfw suite-sparse freetype povray libgrapheme ``` Using apt (preferred on Ubuntu): ``` sudo apt update sudo apt upgrade sudo apt install build-essential cmake sudo apt install libglfw3-dev libsuitesparse-dev liblapacke-dev povray libfreetype6-dev libunistring-dev ``` #### Build the morpho shared library You can build the shared library, hosted in this repository. 1. Obtain the source by cloning the repository: ``` git clone https://github.com/Morpho-lang/morpho.git ``` 2. Navigate to the morpho folder and build the library: ``` cd morpho mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install ``` 3. Navigate back out of the morpho folder: ``` cd ../../ ``` #### Build the morpho terminal app The [terminal app](https://github.com/Morpho-lang/morpho-cli) provides an interactive interface to morpho, and can also run morpho files. To build it: 1. Obtain the source by cloning the github public repository: ``` git clone https://github.com/Morpho-lang/morpho-cli.git ``` 2. Navigate to the morpho-cli folder and build the library: ``` cd morpho-cli mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install ``` 3. Check it works by typing: ``` morpho6 ``` 4. Assuming that the morpho terminal app starts correctly, type `quit` to return to the shell and then ``` cd ../../ ``` to navigate back out of the morph-cli folder. #### Build the morphoview viewer application [Morphoview](https://github.com/Morpho-lang/morpho-morphoview) is a simple viewer application to visualize morpho results. 1. Obtain the source by cloning the github public repository: ``` git clone https://github.com/Morpho-lang/morpho-morphoview.git ``` 2. Navigate to the morpho-cli folder and build the library: ``` cd morpho-morphoview mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo make install ``` 3. Check it works by typing: ``` morphoview ``` which should simply run and quit normally. You can then type ``` cd ../../ ``` to navigate back out of the morpho-morphoview folder. ### Windows via Windows Subsystem for Linux (WSL2) Windows support is provided through Windows Subsystem for Linux (WSL), which is an environment that enables windows to run linux applications. We highly recommend using WSL2, which is the most recent version and provides better support for GUI applications; some instructions for WSL1 are provided [in the manual](https://github.com/Morpho-lang/morpho-manual/blob/main/manual.pdf). Detailed information on running GUI applications in WSL2 is found on the [Microsoft WSL support page](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps). 1. Begin by installing the [Ubuntu App](https://ubuntu.com/desktop/wsl) from the Microsoft store. 2. Once the Ubuntu terminal is working in Windows, you can install morpho either through homebrew or by building from source. ================================================ FILE: examples/catenoid/catenoid.morpho ================================================ // This script computes the shape of a soap film attached to two circular rings separated vertically. // The surface tension minimizes the area of this film, resulting in a shape called a "catenoid". // This belongs to the family of "minimal surfaces", which are surfaces that locally minimize their area. // Showcases: AreaMesh, Area, conjugategradient, fixing boundary in the optimizer. import meshtools import plot import optimize // Set up geometrical parameters var r = 1.0 // radius var ratio = 0.4 // Separation to diameter ratio var L = 2*r*ratio // Separation // Generate a tube / cylindrical mesh var mesh = AreaMesh(fn (u, v) [r*cos(u), v, r*sin(u)], -Pi...Pi:Pi/10, -L/2..L/2:L/5, closed=[true,false] ) mesh.addgrade(1) // Select the boundary var bnd = Selection(mesh, boundary=true) var g = plotselection(mesh, bnd, grade=1) g.title = "Before" Show(g) // Define the optimizataion problem var problem = OptimizationProblem(mesh) // Add the area energy using the built-in Area functional var area = Area() problem.addenergy(area) // Define the optimizer var opt = ShapeOptimizer(problem, mesh) // Ask the optimizer to fix the boundary rings opt.fix(bnd) // Minimize! opt.conjugategradient(1000) g = plotselection(mesh, bnd, grade=1) g.title = "After" Show(g) ================================================ FILE: examples/cholesteric/cholesteric.morpho ================================================ // Test cholesteric in a square boundary import meshtools import plot import optimize var L = 0.5 var dx = 0.1 var m = AreaMesh(fn (u, v) [u, v, 0], -L..L:dx, -L..L:dx) m.addgrade(1) var substrate = Selection(m, fn (x,y,z) abs(y-(-0.5))<0.001 || abs(y-(0.5))<0.001) substrate.addgrade(1) //Show(plotselection(m, substrate, grade=1)) var nn = Field(m, Matrix([1,0,0])) var problem = OptimizationProblem(m) var lnem = Nematic(nn) problem.addenergy(lnem) // Impose planar degenerate anchoring by penalizing ny var lanch = LineIntegral(fn (x, n) n[1]^2, nn) problem.addenergy(lanch, selection=substrate) var ln=NormSq(nn) problem.addlocalconstraint(ln, field=nn, target=1) lnem.pitch = Pi/2 var opt = FieldOptimizer(problem, nn) opt.conjugategradient(1000) // Function to visualize a director field fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = v.dimensions()[1] var g = Graphics() for (i in 0...nv) { var x = v.column(i) g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=0.3)) } return g } Show(visualize(m, nn, dx/4)) ================================================ FILE: examples/cube/cube.morpho ================================================ /* Minimize the area of a surface at constant enclosed volume. */ import graphics import meshtools import plot import optimize var Nlevels = 4 // Levels of refinement var Nsteps = 1000 // Maximum number of steps per refinement level // Create an initial cube var m = PolyhedronMesh([ [-0.5, -0.5, -0.5], [ 0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [ 0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [ 0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [ 0.5, 0.5, 0.5]], [ [0,1,3,2], [4,5,7,6], [0,1,5,4], [3,2,6,7], [0,2,6,4], [1,3,7,5] ]) // Set up the problen var problem = OptimizationProblem(m) var la = Area() problem.addenergy(la) var lv = VolumeEnclosed() problem.addconstraint(lv) // Create the optimizer var opt = ShapeOptimizer(problem, m) // Perform the optimization for (i in 1..Nlevels) { opt.conjugategradient(Nsteps) if (i==Nlevels) break // Refine var mr=MeshRefiner([m]) var refmap = mr.refine() for (el in [problem, opt]) el.update(refmap) m = refmap[m] } // Compare with true area of a sphere for the same volume var V0=lv.total(m) var Af=la.total(m) var R=(V0/(4/3*Pi))^(1/3) var area = 4*Pi*R^2 print "Final area: ${Af} True area: ${area} diff: ${abs(Af-area)}" var g = plotmesh(m) g.title="Cube" Show(g) ================================================ FILE: examples/delaunay/delaunay.morpho ================================================ // Demonstrate use of the Delaunay module import plot import delaunay var N = 100 // Number of points // Explicitly check that all triangles have the property that no other point is in their circumcircle fn checkTriangulation(pts, tri, quiet=false) { var success = true for (t, k in tri) { var sph = Circumsphere(pts, t) for (i in 0...pts.count()) { if (t.ismember(i)) continue // Skip the vertices of the triangle if (sph.pointinsphere(pts[i])) { if (!quiet) print "Point ${i} is in triangle ${k}." success=false; } } } return success } // 2D random point distribution var a = [] for (i in 1..N) a.append(Matrix([2*random()-1, 2*(2*random()-1)])) var del = Delaunay(a) var tri = del.triangulate() // Returns a list of triangles [ [i, j, k], ... ] if (checkTriangulation(a, tri)) { print "2D triangulation passed." } // 3D random point distribution var b = [] for (i in 1..N) b.append(Matrix([2*random()-1, 2*(2*random()-1), 2*random()-1])) del = Delaunay(b) tri = del.triangulate() if (checkTriangulation(b, tri)) { print "3D triangulation passed." } // Directly create meshes var m2d = DelaunayMesh(a) m2d.addgrade(1) // Mesh as returned only contains grade 2 elements (triangles) so add the bars Show(plotmesh(m2d, grade=[0,1])) var m3d = DelaunayMesh(b) m3d.addgrade(1) // Mesh as returned only contains grade 2 elements (triangles) so add the bars Show(plotmesh(m3d, grade=[0,1])) ================================================ FILE: examples/dla/dla.morpho ================================================ // Diffusion Limited Aggregation import kdtree import color import graphics import povray var Np = 100 // Number of particles to add var r = 0.05 // radius of a particle var R = 3*r // Initial radius of sphere var delta = r // Size of step to take var pts = [ Matrix([0,0,0]) ] var tree = KDTree(pts) // Generate a random point fn randompt() { var x = Matrix([randomnormal(), randomnormal(), randomnormal()]) return R*x/x.norm() } for (n in 1..Np) { // Add particles one-by-one if (mod(n, 10)==0) print "Added particle no. ${n}" var x = randompt() while (true) { // Move current particle x+=Matrix([delta*randomnormal(), delta*randomnormal(), delta*randomnormal()]) // Check if it collided with another particle if ((tree.nearest(x).location-x).norm()<2*r) { tree.insert(x) pts.append(x) if (x.norm()>R/2) R = 2*x.norm() break // Move to next particle } // Catch if it wandered out of the boundary if (x.norm()>2*R) x = randompt() } } // Now visualize the result var col = Gray(0.5) var g = Graphics() g.background = White for (x in pts) g.display(Sphere(x, r, color=col)) Show(g) // And raytrace it too var pov = POVRaytracer(g) pov.viewangle = 32 pov.light = [Matrix([3,3,10]), Matrix([-3,3,10]), Matrix([0,-3,10])] pov.render("dla.pov") ================================================ FILE: examples/electrostatics/electrostatics.morpho ================================================ // Solve Laplace's equation on a square domain by minimizing |grad V|^2 import meshtools import plot import optimize var delta=0.25 // Mesh spacing var L = 1 // Size of domain // Create the mesh var mesh = AreaMesh(fn (u,v) [ u, v, 0 ], -L/2..L/2:delta, -L/2..L/2:delta) mesh.addgrade(1) // Create boundaries and select edges, building up from selecting vertices var bnd = Selection(mesh, boundary=true) var bnd1 = Selection(mesh, fn (x,y,z) abs(x+L/2)<0.01 || abs(y+L/2)<0.01) var bnd2 = Selection(mesh, fn (x,y,z) abs(x-L/2)<0.01 || abs(y-L/2)<0.01) for (b in [bnd, bnd1, bnd2]) b.addgrade(1) bnd1=bnd.intersection(bnd1) bnd2=bnd.intersection(bnd2) // Create field var phi = Field(mesh, fn (x,y,z) 0) // Set up the problem var problem = OptimizationProblem(mesh) var le = GradSq(phi) problem.addenergy(le) var v1 = 0, v2 = 1 var lt1 = LineIntegral(fn (x, v) (v-v1)^2, phi) problem.addenergy(lt1, selection=bnd1, prefactor=100) var lt2 = LineIntegral(fn (x, v) (v-v2)^2, phi) problem.addenergy(lt2, selection=bnd2, prefactor=100) // Create the optimizer and perform optimization var opt = FieldOptimizer(problem, phi) opt.conjugategradient(100) for (i in 1..10) { // Select elements that have an above average contribution to the energy var en = le.integrand(phi) // energy in each element var mean = en.sum()/en.count() // mean energy per element var srefine = Selection(mesh) for (id in 0...en.count()) if (en[0,id]>1.5*mean) srefine[2,id]=true // identify large contributions // Refine var ref = MeshRefiner([mesh, phi, bnd, bnd1, bnd2]) var refmap = ref.refine(selection=srefine) // perform the refinement for (el in [problem, opt]) el.update(refmap) // update the problem mesh = refmap[mesh] // update all our variables phi = refmap[phi] bnd = refmap[bnd] bnd1 = refmap[bnd1] bnd2 = refmap[bnd2] equiangulate(mesh) // Reminimize opt.conjugategradient(1000) } print "Final mesh has ${mesh.count(2)} elements" Show(plotmesh(mesh, grade=1)) // Display the result Show(plotfield(phi, style="interpolate")) ================================================ FILE: examples/elementtypes/README.md ================================================ # Finite Element types This folder contains some examples using finite elements beyond linear Lagrange elements (CG1). These require the new optimization package, optimize4. To install, type: morphopm install optimize4 in a terminal. ## Examples * electrostaticsCG2.morpho - Solve Laplace's equation on a square with dirichlet boundary conditions and using CG2 elements. Follows the original example in ../electrostatics * qtensorCG2.morpho - Solve the liquid crystal q tensor problem on a disk. Follows the original example in ../qtensor. ================================================ FILE: examples/elementtypes/electrostaticsCG2.morpho ================================================ // Solve Laplace's equation on a square domain by minimizing |grad V|^2 // Uses CG2 elements import meshtools import plot import optimize4 var delta=0.25/8 // Mesh spacing var L = 1 // Size of domain // Create the mesh var mesh = AreaMesh(fn (u,v) [ u, v, 0 ], -L/2..L/2:delta, -L/2..L/2:delta) mesh.addgrade(1) // Create boundaries and select edges, building up from selecting vertices var bnd = Selection(mesh, boundary=true) var bnd1 = Selection(mesh, fn (x,y,z) abs(x+L/2)<0.01 || abs(y+L/2)<0.01) var bnd2 = Selection(mesh, fn (x,y,z) abs(x-L/2)<0.01 || abs(y-L/2)<0.01) for (b in [bnd, bnd1, bnd2]) b.addgrade(1) bnd1=bnd.intersection(bnd1) bnd2=bnd.intersection(bnd2) // Create field var phi = Field(mesh, fn (x,y,z) 0, finiteelementspace=FiniteElementSpace("CG2", grade=2)) // Set up the problem var problem = OptimizationProblem(mesh) fn integrand(x, q) { var dg = grad(q).norm() return dg*dg } var v1 = 0, v2 = 1 var lt1 = LineIntegral(fn (x, v) (v-v1)^2, phi, method={}) problem.addenergy(lt1, selection=bnd1, prefactor=100) var lt2 = LineIntegral(fn (x, v) (v-v2)^2, phi, method={}) problem.addenergy(lt2, selection=bnd2, prefactor=100) var le = AreaIntegral(integrand, phi, method={}) problem.addenergy(le) var adapt = ProblemAdapter(problem, phi) var control = LBFGSController(adapt) control.optimize(1000) // Display the result Show(plotmesh(mesh, grade=1) + plotfield(phi, style="interpolate")) ================================================ FILE: examples/elementtypes/qtensorCG2.morpho ================================================ // Nematic liquid crystal with Q tensor on a disk // Solved using CG2 elements import meshgen import plot import povray import optimize4 class QTensor { init(K=0.01) { self.mesh = nil self.problem = nil self.rho = 1.3 // Density. rho>1 results in the nematic phase self.EA = 3 // Anchoring strength self.K = K // Bending modulus. K=0.01 yields two +1/2 defects, whereas K=1.0 yields a single +1 defect. self.a2 = (1-self.rho) // Coefficient of Tr(Q^2) in the Free energy self.a4 = (1+self.rho)/self.rho^2 // Coefficient of (Tr(Q^2))^2 in the Free energy } initialMesh() { var dom = CircularDomain([0,0], 1) var mg = MeshGen(dom, [-1..1:0.4, -1..1:0.4], quiet=true) var m = mg.build() m.addgrade(1) self.mesh = ChangeMeshDimension(m, 3) return self.mesh } initialField() { self.q_tensor = Field(self.mesh, fn(x,y,z) Matrix([0.01*random(1), 0.01*random(1)]), finiteelementspace=FiniteElementSpace("CG2", grade=2) ) } initialSelection() { self.bnd = Selection(self.mesh, boundary=true) self.bnd.addgrade(0) // add point elements } buildProblem() { // Specify the problem self.problem = OptimizationProblem(self.mesh) // Define bulk free energy functional fn landau(x, q) { var qt = q.norm() var qt2=qt*qt return self.a2*qt2 + self.a4*qt2*qt2 } var bulk = AreaIntegral(landau, self.q_tensor, method={}) self.problem.addenergy(bulk) // Define anchoring energy functional at the boundary fn anchoring(x, q) { var t = tangent() var wxx = t[0]*t[0]-0.5 var wxy = t[0]*t[1] return (q[0]-wxx)^2+(q[1]-wxy)^2 } var anchor = LineIntegral(anchoring, self.q_tensor, method={}) self.problem.addenergy(anchor, selection=self.bnd, prefactor = self.EA) // The elastic energy is the grad-squared of the q-tensor fn elasticity(x, q) { var g = grad(q) return g[0].inner(g[0]) + g[1].inner(g[1]) } var elastic = AreaIntegral(elasticity, self.q_tensor, method={}) self.problem.addenergy(elastic, prefactor = self.K) return self.problem } buildAdapter() { self.adapter = ProblemAdapter(self.problem, self.q_tensor) return self.adapter } build() { // Setup the problem and return an Adapter self.initialMesh() self.initialField() self.initialSelection() self.buildProblem() return self.buildAdapter() } correctMesh() { for (id in self.bnd.idlistforgrade(0)) { var x = self.mesh.vertexposition(id) self.mesh.setvertexposition(id, x/x.norm()) } } correctField() { if (self.maxgrade(self.q_tensor)<1) return var conn = self.mesh.connectivitymatrix(0, 1) for (id in 0...self.mesh.count(1)) { var v=conn.rowindices(id) self.q_tensor[1,id]=(self.q_tensor[0,v[0]] + self.q_tensor[0,v[1]])/2 } } refine(adaptive=true, lambda=1.7) { var srefine if (adaptive) { // Select elements that have a large contribution to the elastic energy var en = GradSq(self.q_tensor).integrand(self.q_tensor) var mean = en.sum()/en.count() srefine = Selection(self.mesh) // Start with an empty selection for (id in 0...en.count()) if (en[0,id]>lambda*mean) srefine[2,id]=true // Identify high (compared to the mean) energy elements print "Refining ${srefine.count(2)} elements" } // Create a mesh refiner var mr=MeshRefiner([self.mesh, self.q_tensor, self.bnd]) var refmap = mr.refine(selection=srefine) // Use the new mesh and field self.mesh = refmap[self.mesh] self.q_tensor = refmap[self.q_tensor] self.bnd = refmap[self.bnd] self.correctMesh() self.correctField() equiangulate(self.mesh) // Remake the problem and the adapter self.buildProblem() return self.buildAdapter() } maxgrade(f) { // Finds the maximum grade in a Field var shape = f.shape() var maxgrade = 0 for (s, k in shape) if (s>0) maxgrade=k return maxgrade } visualize() { var mg = self.maxgrade(self.q_tensor) // Function to get the director (unit vector n) from the Q-tensor fn qtodirector(q) { var S = 2*q.norm() var Q = q/S var nx = sqrt(Q[0]+0.5) var ny = abs(Q[1]/nx) nx = nx*sign(Q[1]) return Matrix([nx,ny,0]) } // Function to get the scalar order parameter S from the Q-tensor fn qtoorder(q) { var S = 2*q.norm() return S } /* Function to visualize a director field This function returns a `Graphics()` object that has the director field visualized in the form of cylinders. Here, 1. m is the mesh 2. nn is the director field and 3. dl is the half-length of the cylinders to be drawn. 4. aspectratio (optional argument) is the aspect ratio of the cylinders. (default is 0.3 here) */ fn visualize(m, nn, dl, aspectratio=0.3) { var v = m.vertexmatrix() var nv = v.dimensions()[1] var g = Graphics() for (i in 0...nv) { var x = v.column(i) g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=aspectratio)) } if (mg>0) { var conn = m.connectivitymatrix(0,1) for (id in 0...m.count(1)) { var vids = conn.rowindices(id) var x = (v.column(vids[0])+v.column(vids[1]))/2 g.display(Cylinder(x-nn[1,id]*(2*dl/3), x+nn[1,id]*(2*dl/3), aspectratio=aspectratio/2)) } } return g } var nn = Field(self.mesh, Matrix([1,0,0]), grade=self.q_tensor.shape()) // Initialize to some vector for (g in 0..mg) for (i in 0...self.mesh.count(g)) { nn[g,i]=qtodirector(self.q_tensor[g,i]) } var S = Field(self.mesh, 0) // Initialize to a scalar for (i in 0...self.mesh.count()) S[i]=qtoorder(self.q_tensor[i]) var splot = plotfield(S, style="interpolate", colormap = ViridisMap(), scalebar=ScaleBar(posn=[1.2,0,0])) var gnn=visualize(self.mesh, nn, 0.05) Show(splot+gnn) } } var example = QTensor() var adapt = example.build() for (i in 1..4) { if (i>1) adapt = example.refine(adaptive=false) var control = LBFGSController(adapt) control.optimize(500) example.visualize() } ================================================ FILE: examples/examples.py ================================================ #!/usr/bin/env python3 # runexamples.py # runs all the example for morpho # reporting any errors found import os, glob, sys from queue import Empty import regex as rx from functools import reduce import operator import colored from colored import stylize sys.path.append('../test') ext = "morpho" command = 'morpho6' stk = '@stacktrace' err = '@error' def finderror(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) #return rx.findall(r'.*[E|e]rror[ :].*?(.*)', str) return rx.findall(r'@error.*', str) def simplify_errors(str): # this monster regex extraxts NAME from error messages of the form error ... 'NAME' return rx.sub('.*[E|e]rror[ :]*\'([A-z;a-z]*)\'.*', err+'[\\1]', str.rstrip()) # Simplify stacktrace def simplify_stacktrace(str): return rx.sub(r'.*at line.*', stk, str.rstrip()) def iserror(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) test=rx.findall(r'@error.*', str) return len(test)>0 def remove_control_characters(str): return rx.sub(r'\x1b[^m]*m', '', str.rstrip()) def isin(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) test=rx.findall(r'.*in .*', str) return len(test)>0 def getoutput(filepath): # Load the file file_object = open(filepath, 'r') lines = file_object.readlines() file_object.close() # remove all control characters lines = list(map(remove_control_characters, lines)) # Convert errors to our universal error code lines = list(map(simplify_errors, lines)) # Identify stack trace lines lines = list(map(simplify_stacktrace, lines)) for i in range(len(lines)-1): if (iserror(lines[i])): if (isin(lines[i+1])): lines[i+1]=stk # and remove them return list(filter(lambda x: x!=stk, lines)) def run(file,testLog,CI): ret = 1 print(file+":", end=" ") # Create a temporary file in the same directory tmp = file + '.out' # Run the test os.system(command + ' ' +file + ' > ' + tmp) # If we produced output if os.path.exists(tmp): # Get the output out = getoutput(tmp) #look for erros for line in out: err = finderror(line) # Was it expected? if (iserror(line)): if not CI: print("Failed") #stylize("Failed",colored.fg("red"))) // Temporarily disable this 6/19/23 due to colored module API change else: print("::error file = {",file,"}::{",file," Failed}") #also print to the test log print(file+":", end=" ", file = testLog) print("Failed", end=" ", file = testLog) print("with error "+ err[0], file = testLog) print("\n",file = testLog) ret = 0 break if (ret ==1): if not CI: print(file+":", end=" ") print("Passed") #stylize("Passed",colored.fg("green"))) # Delete the temporary file os.system('rm ' + tmp) return ret print('--Building Examples---------------------') # open a test log # write failures to log success=0 # number of successful examples total=0 # total number of examples # look for a command line arguement that says # this is being run for continous integration CI = False for arg in sys.argv: if arg == '-c': # if the argument is -c, then we are running in CI mode CI = True files=glob.glob('**/**.'+ext, recursive=True) with open("FailedExamples.txt",'w') as testLog: for f in files: success+=run(f,testLog,CI) total+=1 print('--End testing-----------------------') print(success, 'out of', total, 'tests passed.') if CI and success1 results in the nematic phase var EA = 3 // Anchoring strength var K = 0.01 // Bending modulus. K=0.01 yields two +1/2 defects, whereas K=1.0 yields a single +1 defect. var a2 = (1-rho) // Coefficient of Tr(Q^2) in the Free energy var a4 = (1+rho)/rho^2 // Coefficient of (Tr(Q^2))^2 in the Free energy // Import disk mesh var m = Mesh("dense_disk.mesh") // Select boundary var bnd = Selection(m, boundary=true) bnd.addgrade(0) // add point elements // Create director field /* Since a 2D Q-tensor for a uniaxial nematic is symmetric and traceless, there are only two independent components, Qxx and Qxy. We thus define the q_tensor to be a 2D vector, with its components being Qxx and Qxy. We initialize it to be zero and add a random noise of 1% strength as a perturbation to start the gradient descent. The Q-tensor usually takes values of order 1, so we can use 0.01*random(1) as the initial 1% noise. */ var q_tensor = Field(m, fn(x,y,z) Matrix([0.01*random(1), 0.01*random(1)])) // Define various components of the energy. // Since these functions are meant to be arguments to AreaIntegral and LineIntegral, their first input argument has to be the position vector `x`. The field argument (the q-tensor in our case) comes after. // Define bulk free energy functional fn landau(x, q) { var qt = q.norm() var qt2=qt*qt return a2*qt2 + a4*qt2*qt2 } // Define anchoring energy functional at the boundary fn anchoring(x, q) { var t = tangent() var wxx = t[0]*t[0]-0.5 var wxy = t[0]*t[1] return (q[0]-wxx)^2+(q[1]-wxy)^2 } // The bulk free energy is now the area-integral of the landau functional with the field input being q_tensor var bulk = AreaIntegral(landau, q_tensor) // Similarly, the anchoring free energy is now the line-integral of the anchoring functional with the field input being q_tensor var anchor = LineIntegral(anchoring, q_tensor) // The elastic energy is the grad-squared of the q-tensor var elastic = GradSq(q_tensor) // Specify the problem var problem = OptimizationProblem(m) // Add the energies to the problem problem.addenergy(bulk) // The anchoring energy has a prefactor of EA, and is applied only to the boundary problem.addenergy(anchor, selection=bnd, prefactor = EA) // The elastic energy has a prefactor of K problem.addenergy(elastic, prefactor = K) // Set up the optimizer to optimize this problem wrt the Field values. var opt = FieldOptimizer(problem, q_tensor) // `iters` specifies the number of iterations for the linelearch. From emperical observation, iters=500 works well. var iters = 500 // Minimize the free energy! opt.conjugategradient(iters) // Define some helper functions to plot the result // Function to get the director (unit vector n) from the Q-tensor fn qtodirector(q) { var S = 2*q.norm() var Q = q/S var nx = sqrt(Q[0]+0.5) var ny = abs(Q[1]/nx) nx = nx*sign(Q[1]) return Matrix([nx,ny,0]) } // Function to get the scalar order parameter S from the Q-tensor fn qtoorder(q) { var S = 2*q.norm() return S } /* Function to visualize a director field This function returns a `Graphics()` object that has the director field visualized in the form of cylinders. Here, 1. m is the mesh 2. nn is the director field and 3. dl is the half-length of the cylinders to be drawn. 4. aspectratio (optional argument) is the aspect ratio of the cylinders. (default is 0.3 here) */ fn visualize(m, nn, dl, aspectratio=0.3) { var v = m.vertexmatrix() var nv = v.dimensions()[1] var g = Graphics() for (i in 0...nv) { var x = v.column(i) g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=aspectratio)) } return g } /* Adaptive mesh refinement Here, we refine the mesh based on the elastic energy density and linesearch successively. */ if (refine_adaptively){ for (i in 1..refine_iters){ // Select elements that have a large contribution to the elastic energy var en = elastic.integrand(q_tensor) var mean = en.sum()/en.count() var srefine = Selection(m) // Start with an empty selection for (id in 0...en.count()) if (en[0,id]>1.5*mean) srefine[2,id]=true // Identify high (compared to the mean) energy elements // Visualize the selected region for refimenemt if (visualize_refinement){ var gs = plotselection(m, srefine, grade=2) var nn = Field(m, Matrix([1,0,0])) // Initialize to some vector for (i in 0...m.count()) nn[i]=qtodirector(q_tensor[i]) var gnn=visualize(m, nn, 0.05) Show(gs+gnn) } // Create a mesh refiner var mr=MeshRefiner([m, q_tensor, bnd]) // Perform the refinement var refmap = mr.refine(selection=srefine) // Now that refinement is done, update the problems and optimizers for (el in [problem, opt]) el.update(refmap) // Use the new mesh and field m = refmap[m] q_tensor = refmap[q_tensor] bnd = refmap[bnd] equiangulate(m) // Visualize the refined mesh at each stage if (visualize_refinement) Show(plotmesh(m, grade=1)) opt.conjugategradient(iters) } } // Visualize the result // Convert the q-tensor to the director and order var nn = Field(m, Matrix([1,0,0])) // Initialize to some vector for (i in 0...m.count()) nn[i]=qtodirector(q_tensor[i]) var S = Field(m, 0) // Initialize to a scalar for (i in 0...m.count()) S[i]=qtoorder(q_tensor[i]) // Plot the scalar order field var splot = plotfield(S, style="interpolate", colormap = ViridisMap(), scalebar=ScaleBar(posn=[1.2,0,0])) // Plot the director using the visualize function we wrote above var gnn=visualize(m, nn, 0.05) // Both `plotfield` and `visualize` return a Graphics object. We can just add the two to get the final plot var gdisp = splot+gnn // Generate a POVRaytracer object using the Graphics() object var pov = POVRaytracer(gdisp) // Set the viewing angle in degrees. The larger the angle, the larger the field of view. pov.viewangle=35 pov.viewpoint = Matrix([0,0,6]) pov.light = [Matrix([3,4,5]), Matrix([-3,-4,5])] // Render to a .pov file. This also generates a .png image with the same name. pov.render("Qtensor_K_${K}.pov") // Open up the viewer application Show(gdisp) ================================================ FILE: examples/qtensor/src.lyx ================================================ #LyX file created by tex2lyx 2.3 \lyxformat 544 \begin_document \begin_header \save_transient_properties true \origin /Users/timatherton/Documents/Programs/morpho-public/morpho/examples/qtensor/ \textclass article \begin_preamble \usepackage{physics} \usepackage{xcolor} \definecolor{codegray}{rgb}{0.9,0.9,0.9} \usepackage{listings} \newcommand{\morpho}{\textit{morpho}} \newcommand{\Morpho}{\textit{Morpho}} \title{Q-tensor model of nematics} \author{Chaitanya Joshi} \date{October 2021} \end_preamble \use_default_options false \maintain_unincluded_children false \language english \language_package none \inputencoding utf8 \fontencoding default \font_roman "default" "default" \font_sans "default" "default" \font_typewriter "default" "default" \font_math "auto" "auto" \font_default_family default \use_non_tex_fonts false \font_sc false \font_osf false \font_sf_scale 100 100 \font_tt_scale 100 100 \use_microtype false \use_dash_ligatures true \graphics default \default_output_format default \output_sync 0 \bibtex_command default \index_command default \paperfontsize default \spacing single \use_hyperref false \papersize default \use_geometry false \use_package amsmath 1 \use_package amssymb 0 \use_package cancel 0 \use_package esint 1 \use_package mathdots 0 \use_package mathtools 0 \use_package mhchem 0 \use_package stackrel 0 \use_package stmaryrd 0 \use_package undertilde 0 \cite_engine basic \cite_engine_type default \biblio_style plain \use_bibtopic false \use_indices false \paperorientation portrait \suppress_date false \justification true \use_refstyle 0 \use_minted 0 \index Index \shortcut idx \color #008000 \end_index \secnumdepth 3 \tocdepth 3 \paragraph_separation indent \paragraph_indentation default \is_math_indent 0 \math_numbering_side default \quotes_style english \dynamic_quotes 0 \papercolumns 1 \papersides 1 \paperpagestyle default \tracking_changes false \output_changes false \html_math_output 0 \html_css_as_file 0 \html_be_strict false \end_header \begin_body \begin_layout Standard \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash lstset \end_layout \end_inset \begin_inset ERT status collapsed \begin_layout Plain Layout { \end_layout \end_inset language=Java, tabsize=4, basicstyle= \family typewriter , backgroundcolor= \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash color{codegray} \end_layout \end_inset , showstringspaces=false \begin_inset ERT status collapsed \begin_layout Plain Layout } \end_layout \end_inset \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash maketitle \end_layout \end_inset \end_layout \begin_layout Section Introduction \end_layout \begin_layout Standard In 2D, for a uniaxial nematic, we can define a Q-tensor: \begin_inset Formula \[ Q_{ij} = S (n_i n_j - 1/2 \delta_{ij}) \] \end_inset Here, the \begin_inset Formula $-1/2 \delta_{ij}$ \end_inset is added for convenience, to make the matrix traceless: \begin_inset Formula \[\text{Tr}(\mathbf{Q}) = Q_{ii} = S(n_i n_i - 1/2 \delta_{ii}) = S(1 - 1/2(2)) = 0 \] \end_inset Now, the Q-tensor is also symmetric by definition: \begin_inset Formula \[ Q_{ij} = Q_{ji} \] \end_inset Due to these two reasons we can write the Q-tensor as a function of only \begin_inset Formula $Q_{xx}$ \end_inset and \begin_inset Formula $Q_{xy}$ \end_inset : \begin_inset Formula \[ \mathbf{Q} = \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} \] \end_inset \end_layout \begin_layout Subsection Simple Passive Nematic Model \end_layout \begin_layout Standard The Landau-de Gennes equilibrium free energy for a nematic liquid crystal can be written in terms of the Q-tensor: \begin_inset Formula \begin{align*} F_{LDG} = &\int_\Omega d^2{\bf x} \ \left(\frac{a_2}{2} \text{Tr}(\mathbf{Q}^2) + \frac{a_4}{4} (\text{Tr} \mathbf{Q}^2)^2 + \frac{K}{2}(\nabla\mathbf{Q})^2 \right) \\ &+ \oint_{\partial\Omega} d{\bf x} \frac{1}{2}E_A \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] \end{align*} \end_inset where \begin_inset Formula $a_2 = (\rho-1)$ \end_inset and \begin_inset Formula $a_4 = (\rho+1)/\rho^2$ \end_inset set the isotropic to nematic transition with \begin_inset Formula $\rho$ \end_inset being the non-dimensional density. The system is in the isotropic state for \begin_inset Formula $\rho<1$ \end_inset and in the nematic phase when \begin_inset Formula $\rho>1$ \end_inset . In the nematic phase, \begin_inset Formula $\ell_n = \sqrt{K/a_2}$ \end_inset sets the nematic coherence length. Now, \begin_inset Formula \[ \mathbf{Q}^2 = \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} = (Q_{xx}^2+Q_{xy}^2) \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \] \end_inset Hence, \begin_inset Formula \[ \text{Tr}(\mathbf{Q}^2) = 2(Q_{xx}^2+Q_{xy}^2) \] \end_inset Similarly, \begin_inset Formula \[ (\nabla \mathbf{Q})^2 = \partial_i Q_{kj}\partial_i Q_{kj} = 2 \{ (\partial_x Q_{xx})^2+(\partial_xQ_{xy})^2 + (\partial_y Q_{xx})^2+(\partial_y Q_{xy})^2 \} \] \end_inset Now, the second term is a boundary integral, with \begin_inset Formula $E_A$ \end_inset being the anchoring strength. \begin_inset Formula $\mathbf{W}$ \end_inset is the tensor corresponding to the boundary condition. For instance, for parallel anchoring, \begin_inset Formula \[ W_{ij} = (t_i t_j - 1/2 \delta_{ij}) \] \end_inset where \begin_inset Formula $t_i$ \end_inset is a component of the tangent vector at the boundary. \begin_inset Formula $\mathbf{W}$ \end_inset is also a symmetric traceless tensor with two independent components \begin_inset Formula $W_{xx}$ \end_inset and \begin_inset Formula $W_{xy}$ \end_inset . The boundary term becomes: \begin_inset Formula \[ \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] = 2\{ Q_{xx}^2 + Q_{xy}^2 - 2(Q_{xx}W_{xx} + Q_{xy}W_{xy}) + W_{xx}^2 + W_{xy}^2 \} \] \end_inset \end_layout \begin_layout Section Vector formulation \end_layout \begin_layout Standard We can formulate all these expressions in terms of vector quantities: \begin_inset Formula \[\vec{q} \equiv \{ Q_{xx}, Q_{xy}\}\] \end_inset \begin_inset Formula \[\vec{w} \equiv \{ w_{xx}, w_{xy}\}\] \end_inset Thus, \begin_inset Formula \[ \text{Tr}(\mathbf{Q}^2) = 2 ||\vec{q}||^2 \] \end_inset \end_layout \begin_layout Standard \begin_inset Formula \[ (\nabla \mathbf{Q})^2 = 2 ||\nabla \vec{q}||^2 \] \end_inset \end_layout \begin_layout Standard \begin_inset Formula \[ \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] = 2 ||\vec{q}-\vec{w}||^2 \] \end_inset With these, we want to minimize the area-integral of \begin_inset Formula \[ F = \int_\Omega d^2{\bf x} \ \left( a_2 ||\vec{q}||^2 + a_4 ||\vec{q}||^4 + K||\nabla\vec{q}||^2 \right) \] \end_inset together with the line-integral energy \begin_inset Formula \[ \oint_{\partial\Omega} d{\bf x} \ E_A ||\vec{q}-\vec{w}||^2 \] \end_inset \end_layout \begin_layout Section \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash Morpho \end_layout \end_inset \begin_inset space \space{} \end_inset implementation \end_layout \begin_layout Standard This free energy is readily set up in \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash morpho \end_layout \end_inset . For this problem, we will consider a 2D disk geometry with unit radius. We use \begin_inset Formula $\rho=1.3$ \end_inset , so that we are deep in the nematic regime. We fix \begin_inset Formula $E_{\text{A}}=3$ \end_inset , which sets strong anchoring at the boundary. With this strong tangential anchoring, we get a topological charge of \begin_inset Formula $+1$ \end_inset at the boundary, and this acts as a constraint. When the nematic coherence length is comparable to the disk diameter ( \begin_inset Formula $\ell_n \sim R$ \end_inset ), the \begin_inset Formula $+1$ \end_inset charge penetrates throughout the disk, whereas if ( \begin_inset Formula $\ell_n \ll R$ \end_inset ), then a formation with 2 \begin_inset Formula $+1/2$ \end_inset defects is more stable. To test this, we use two different values of \begin_inset Formula $K$ \end_inset :, 0.01 and 1.0. \end_layout \begin_layout Standard We first define all our parameters and import \begin_inset Formula $\texttt{disk.mesh}$ \end_inset from the tactoid example: \begin_inset listings inline false status open \begin_layout Plain Layout var rho = 1.3 // Deep in the nematic phase \end_layout \begin_layout Plain Layout var EA = 3 // Anchoring strength \end_layout \begin_layout Plain Layout var K = 0.01 // Bending modulus \end_layout \begin_layout Plain Layout \end_layout \begin_layout Plain Layout var a2 = (1-rho) \end_layout \begin_layout Plain Layout var a4 = (1+rho)/rho^2 \end_layout \begin_layout Plain Layout \end_layout \begin_layout Plain Layout var m = Mesh("disk.mesh") \end_layout \begin_layout Plain Layout var m = refinemesh(m) // Refining for a better result \end_layout \begin_layout Plain Layout var bnd = Selection(m, boundary=true) \end_layout \begin_layout Plain Layout bnd.addgrade(0) // add point elements \end_layout \begin_layout Plain Layout \end_layout \end_inset We define the Q-tensor in its vector form as discussed above, initializing it to small random values: \begin_inset listings inline false status open \begin_layout Plain Layout var q_tensor = Field(m, fn(x,y,z) \end_layout \begin_layout Plain Layout Matrix([0.01*random(1), 0.01*random(1)])) \end_layout \end_inset Note that this incidentally makes the director parallel to a 45 degree line. We now define the bulk energy, the anchoring energy and the distortion free energy as follows: \begin_inset listings inline false status open \begin_layout Plain Layout // Define bulk free energy \end_layout \begin_layout Plain Layout fn landau(x, q) { \end_layout \begin_layout Plain Layout var qt = q.norm() \end_layout \begin_layout Plain Layout var qt2=qt*qt \end_layout \begin_layout Plain Layout return a2*qt2 + a4*qt2*qt2 \end_layout \begin_layout Plain Layout } \end_layout \begin_layout Plain Layout // Define anchoring energy at the boundary \end_layout \begin_layout Plain Layout fn anchoring(x, q) { \end_layout \begin_layout Plain Layout var t = tangent() \end_layout \begin_layout Plain Layout var wxx = t[0]*t[0]-0.5 \end_layout \begin_layout Plain Layout var wxy = t[0]*t[1] \end_layout \begin_layout Plain Layout return (q[0]-wxx)^2+(q[1]-wxy)^2 \end_layout \begin_layout Plain Layout } \end_layout \begin_layout Plain Layout \end_layout \begin_layout Plain Layout var bulk = AreaIntegral(landau, q_tensor) \end_layout \begin_layout Plain Layout var anchor = LineIntegral(anchoring, q_tensor) \end_layout \begin_layout Plain Layout var elastic = GradSq(q_tensor) \end_layout \end_inset Equipped with the energies, we define the \family typewriter OptimizationProblem \family default : \begin_inset listings inline false status open \begin_layout Plain Layout var problem = OptimizationProblem(m) \end_layout \begin_layout Plain Layout problem.addenergy(bulk) \end_layout \begin_layout Plain Layout problem.addenergy(elastic, prefactor = K) \end_layout \begin_layout Plain Layout problem.addenergy(anchor, selection=bnd, prefactor=EA) \end_layout \end_inset To minimize the energy with respect to the field, we define the \family typewriter FieldOptimizer \family default and perform a \family typewriter linesearch \family default : \begin_inset listings inline false status open \begin_layout Plain Layout var opt = FieldOptimizer(problem, q_tensor) \end_layout \begin_layout Plain Layout opt.linesearch(500) \end_layout \end_inset \end_layout \begin_layout Section Visualizing the result \end_layout \begin_layout Standard For visualizing the final configuration, we use the same piece of code we used for the tactoid example, and define some additional helper functions to extract the director and the order from the Q-tensor. \begin_inset listings inline false status open \begin_layout Plain Layout fn sign(x) { \end_layout \begin_layout Plain Layout if (x<0.0) return -1.0 \end_layout \begin_layout Plain Layout else if (x>0.0) return 1.0 \end_layout \begin_layout Plain Layout return 0.0 \end_layout \begin_layout Plain Layout } \end_layout \end_inset \begin_inset listings inline false status open \begin_layout Plain Layout fn qtodirector(q) { \end_layout \begin_layout Plain Layout var S = 2*q.norm() \end_layout \begin_layout Plain Layout var Q = q/S \end_layout \begin_layout Plain Layout var nx = sqrt(Q[0]+0.5) \end_layout \begin_layout Plain Layout var ny = abs(Q[1]/nx) \end_layout \begin_layout Plain Layout nx*=sign(Q[1]) \end_layout \begin_layout Plain Layout return Matrix([nx,ny,0]) \end_layout \begin_layout Plain Layout } \end_layout \end_inset \begin_inset listings inline false status open \begin_layout Plain Layout fn qtoorder(q) { \end_layout \begin_layout Plain Layout var S = 2*q.norm() \end_layout \begin_layout Plain Layout return S \end_layout \begin_layout Plain Layout } \end_layout \end_inset \begin_inset listings inline false status open \begin_layout Plain Layout // Convert the q-tensor to the director and order \end_layout \begin_layout Plain Layout var nn = Field(m, Matrix([1,0,0])) \end_layout \begin_layout Plain Layout for (i in 0...m.count()) nn[i]=qtodirector(q_tensor[i]) \end_layout \begin_layout Plain Layout var S = Field(m, 0) \end_layout \begin_layout Plain Layout for (i in 0...m.count()) S[i]=qtoorder(q_tensor[i]) \end_layout \end_inset \begin_inset listings inline false status open \begin_layout Plain Layout // Function to visualize a director field \end_layout \begin_layout Plain Layout fn visualize(m, nn, dl) { \end_layout \begin_layout Plain Layout var v = m.vertexmatrix() \end_layout \begin_layout Plain Layout var nv = v.dimensions()[1] \end_layout \begin_layout Plain Layout var g = Graphics() \end_layout \begin_layout Plain Layout for (i in 0...nv) { \end_layout \begin_layout Plain Layout var x = v.column(i) \end_layout \begin_layout Plain Layout g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, \end_layout \begin_layout Plain Layout aspectratio=0.3)) \end_layout \begin_layout Plain Layout } \end_layout \begin_layout Plain Layout return g \end_layout \begin_layout Plain Layout } \end_layout \begin_layout Plain Layout \end_layout \begin_layout Plain Layout // Visualize the result \end_layout \begin_layout Plain Layout // var g=plotmesh(m, grade=1) \end_layout \begin_layout Plain Layout var splot = plotfield(S, style="interpolate") \end_layout \begin_layout Plain Layout var gnn=visualize(m, nn, 0.05) \end_layout \begin_layout Plain Layout var gdisp = splot+gnn \end_layout \begin_layout Plain Layout Show(gdisp) \end_layout \end_inset We can go further and use the \family typewriter povray \family default module to render a \family typewriter .png \family default output: \begin_inset listings inline false status open \begin_layout Plain Layout var pov = POVRaytracer(gdisp) \end_layout \begin_layout Plain Layout pov.viewangle=35 \end_layout \begin_layout Plain Layout pov.render("Qtensor_K_${K}.pov") \end_layout \end_inset \begin_inset Float figure wide false sideways false status open \begin_layout Standard \begin_inset ERT status collapsed \begin_layout Plain Layout \backslash centering \end_layout \end_inset \begin_inset Graphics filename Qtensor_K_1.png width 45line% \end_inset \begin_inset Graphics filename Qtensor_K_0.01.png width 45line% \end_inset \begin_inset Caption Standard \begin_layout Plain Layout Final configuration of the director and order for (left) \begin_inset Formula $K=1$ \end_inset and (right) \begin_inset Formula $K=0.01$ \end_inset . The cylinders indicate the nematic director whereas the color indicates the scalar order parameter. Note how the \begin_inset Formula $K=1$ \end_inset case has a single \begin_inset Formula $+1$ \end_inset defect at the center whereas the \begin_inset Formula $K=0.01$ \end_inset case has two \begin_inset Formula $+1/2$ \end_inset defects. \end_layout \end_inset \begin_inset CommandInset label LatexCommand label name "fig:qtensor" \end_inset \end_layout \end_inset \end_layout \begin_layout Standard This creates beautiful plots of the nematic, as seen in the example Figure \begin_inset CommandInset ref LatexCommand ref reference "fig:qtensor" plural "false" caps "false" noprefix "false" \end_inset . Like the tactoid example, we can do adaptive mesh refinement based on the elastic energy density as well. The full code, along with the optional adaptive refinement can be found under \family typewriter examples/qtensor/qtensor.morpho \family default . \end_layout \end_body \end_document ================================================ FILE: examples/qtensor/src.tex ================================================ \documentclass{article} \usepackage[utf8]{inputenc} \usepackage{physics} \usepackage{xcolor} \usepackage{graphicx} \definecolor{codegray}{rgb}{0.9,0.9,0.9} \usepackage{listings} \newcommand{\morpho}{\textit{morpho}} \newcommand{\Morpho}{\textit{Morpho}} \title{Q-tensor model of nematics} \author{Chaitanya Joshi} \date{October 2021} \begin{document} \lstset{ language=Java, tabsize=4, basicstyle=\ttfamily, backgroundcolor=\color{codegray}, showstringspaces=false } \maketitle \section{Introduction} In 2D, for a uniaxial nematic, we can define a Q-tensor: \[ Q_{ij} = S (n_i n_j - 1/2 \delta_{ij}) \] Here, the $-1/2 \delta_{ij}$ is added for convenience, to make the matrix traceless: \[\text{Tr}(\mathbf{Q}) = Q_{ii} = S(n_i n_i - 1/2 \delta_{ii}) = S(1 - 1/2(2)) = 0 \] Now, the Q-tensor is also symmetric by definition: \[ Q_{ij} = Q_{ji} \] Due to these two reasons we can write the Q-tensor as a function of only $Q_{xx}$ and $Q_{xy}$: \[ \mathbf{Q} = \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} \] \subsection{Simple Passive Nematic Model} The Landau-de Gennes equilibrium free energy for a nematic liquid crystal can be written in terms of the Q-tensor: \begin{align*} F_{LDG} = &\int_\Omega d^2{\bf x} \ \left(\frac{a_2}{2} \text{Tr}(\mathbf{Q}^2) + \frac{a_4}{4} (\text{Tr} \mathbf{Q}^2)^2 + \frac{K}{2}(\nabla\mathbf{Q})^2 \right) \\ &+ \oint_{\partial\Omega} d{\bf x} \frac{1}{2}E_A \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] \end{align*} where $a_2 = (\rho-1)$ and $a_4 = (\rho+1)/\rho^2$ set the isotropic to nematic transition with $\rho$ being the non-dimensional density. The system is in the isotropic state for $\rho<1$ and in the nematic phase when $\rho>1$. In the nematic phase, $\ell_n = \sqrt{K/a_2}$ sets the nematic coherence length. Now, \[ \mathbf{Q}^2 = \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} \begin{bmatrix}Q_{xx} & Q_{xy} \\ Q_{xy} & -Q_{xx} \end{bmatrix} = (Q_{xx}^2+Q_{xy}^2) \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \] Hence, \[ \text{Tr}(\mathbf{Q}^2) = 2(Q_{xx}^2+Q_{xy}^2) \] Similarly, \[ (\nabla \mathbf{Q})^2 = \partial_i Q_{kj}\partial_i Q_{kj} = 2 \{ (\partial_x Q_{xx})^2+(\partial_xQ_{xy})^2 + (\partial_y Q_{xx})^2+(\partial_y Q_{xy})^2 \} \] Now, the second term is a boundary integral, with $E_A$ being the anchoring strength. $\mathbf{W}$ is the tensor corresponding to the boundary condition. For instance, for parallel anchoring, \[ W_{ij} = (t_i t_j - 1/2 \delta_{ij}) \] where $t_i$ is a component of the tangent vector at the boundary. $\mathbf{W}$ is also a symmetric traceless tensor with two independent components $W_{xx}$ and $W_{xy}$. The boundary term becomes: \[ \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] = 2\{ Q_{xx}^2 + Q_{xy}^2 - 2(Q_{xx}W_{xx} + Q_{xy}W_{xy}) + W_{xx}^2 + W_{xy}^2 \} \] \section{Vector formulation} We can formulate all these expressions in terms of vector quantities: \[\vec{q} \equiv \{ Q_{xx}, Q_{xy}\}\] \[\vec{w} \equiv \{ w_{xx}, w_{xy}\}\] Thus, \[ \text{Tr}(\mathbf{Q}^2) = 2 ||\vec{q}||^2 \] \[ (\nabla \mathbf{Q})^2 = 2 ||\nabla \vec{q}||^2 \] \[ \text{Tr}[(\mathbf{Q}-\mathbf{W})^2] = 2 ||\vec{q}-\vec{w}||^2 \] With these, we want to minimize the area-integral of \[ F = \int_\Omega d^2{\bf x} \ \left( a_2 ||\vec{q}||^2 + a_4 ||\vec{q}||^4 + K||\nabla\vec{q}||^2 \right) \] together with the line-integral energy \[ \oint_{\partial\Omega} d{\bf x} \ E_A ||\vec{q}-\vec{w}||^2 \] \section{\Morpho \ implementation} This free energy is readily set up in \morpho. For this problem, we will consider a 2D disk geometry with unit radius. We use $\rho=1.3$, so that we are deep in the nematic regime. We fix $E_{\text{A}}=3$, which sets strong anchoring at the boundary. With this strong tangential anchoring, we get a topological charge of $+1$ at the boundary, and this acts as a constraint. When the nematic coherence length is comparable to the disk diameter ($\ell_n \sim R$), the $+1$ charge penetrates throughout the disk, whereas if ($\ell_n \ll R$), then a formation with 2 $+1/2$ defects is more stable. To test this, we use two different values of $K$:, 0.01 and 1.0. We first define all our parameters and import $\texttt{disk.mesh}$ from the tactoid example: \begin{lstlisting} var rho = 1.3 // Deep in the nematic phase var EA = 3 // Anchoring strength var K = 0.01 // Bending modulus var a2 = (1-rho) var a4 = (1+rho)/rho^2 var m = Mesh("disk.mesh") var m = refinemesh(m) // Refining for a better result var bnd = Selection(m, boundary=true) bnd.addgrade(0) // add point elements \end{lstlisting} We define the Q-tensor in its vector form as discussed above, initializing it to small random values: \begin{lstlisting} var q_tensor = Field(m, fn(x,y,z) Matrix([0.01*random(1), 0.01*random(1)])) \end{lstlisting} Note that this incidentally makes the director parallel to a 45 degree line. We now define the bulk energy, the anchoring energy and the distortion free energy as follows: \begin{lstlisting} // Define bulk free energy fn landau(x, q) { var qt = q.norm() var qt2=qt*qt return a2*qt2 + a4*qt2*qt2 } // Define anchoring energy at the boundary fn anchoring(x, q) { var t = tangent() var wxx = t[0]*t[0]-0.5 var wxy = t[0]*t[1] return (q[0]-wxx)^2+(q[1]-wxy)^2 } var bulk = AreaIntegral(landau, q_tensor) var anchor = LineIntegral(anchoring, q_tensor) var elastic = GradSq(q_tensor) \end{lstlisting} Equipped with the energies, we define the \texttt{OptimizationProblem}: \begin{lstlisting} var problem = OptimizationProblem(m) problem.addenergy(bulk) problem.addenergy(elastic, prefactor = K) problem.addenergy(anchor, selection=bnd, prefactor=EA) \end{lstlisting} To minimize the energy with respect to the field, we define the \texttt{FieldOptimizer} and perform a \texttt{linesearch}: \begin{lstlisting} var opt = FieldOptimizer(problem, q_tensor) opt.linesearch(500) \end{lstlisting} \section{Visualizing the result} For visualizing the final configuration, we use the same piece of code we used for the tactoid example, and define some additional helper functions to extract the director and the order from the Q-tensor. \begin{lstlisting} fn sign(x) { if (x<0.0) return -1.0 else if (x>0.0) return 1.0 return 0.0 } \end{lstlisting} \begin{lstlisting} fn qtodirector(q) { var S = 2*q.norm() var Q = q/S var nx = sqrt(Q[0]+0.5) var ny = abs(Q[1]/nx) nx*=sign(Q[1]) return Matrix([nx,ny,0]) } \end{lstlisting} \begin{lstlisting} fn qtoorder(q) { var S = 2*q.norm() return S } \end{lstlisting} \begin{lstlisting} // Convert the q-tensor to the director and order var nn = Field(m, Matrix([1,0,0])) for (i in 0...m.count()) nn[i]=qtodirector(q_tensor[i]) var S = Field(m, 0) for (i in 0...m.count()) S[i]=qtoorder(q_tensor[i]) \end{lstlisting} \begin{lstlisting} // Function to visualize a director field fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = v.dimensions()[1] var g = Graphics() for (i in 0...nv) { var x = v.column(i) g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=0.3)) } return g } // Visualize the result // var g=plotmesh(m, grade=1) var splot = plotfield(S, style="interpolate") var gnn=visualize(m, nn, 0.05) var gdisp = splot+gnn Show(gdisp) \end{lstlisting} We can go further and use the \texttt{povray} module to render a \texttt{.png} output: \begin{lstlisting} var pov = POVRaytracer(gdisp) pov.viewangle=35 pov.render("Qtensor_K_${K}.pov") \end{lstlisting} \begin{figure} \centering \includegraphics[width=0.45\linewidth]{Qtensor_K_1.png} \includegraphics[width=0.45\linewidth]{Qtensor_K_0.01.png} \caption{Final configuration of the director and order for (left) $K=1$ and (right) $K=0.01$. The cylinders indicate the nematic director whereas the color indicates the scalar order parameter. Note how the $K=1$ case has a single $+1$ defect at the center whereas the $K=0.01$ case has two $+1/2$ defects.} \label{fig:qtensor} \end{figure} This creates beautiful plots of the nematic, as seen in the example Figure \ref{fig:qtensor}. Like the tactoid example, we can do adaptive mesh refinement based on the elastic energy density as well. The full code, along with the optional adaptive refinement can be found under \texttt{examples/qtensor/qtensor.morpho}. \end{document} ================================================ FILE: examples/tactoid/disk.mesh ================================================ vertices 1 -1. 0. 0 2 -0.951057 -0.309017 0 3 -0.951057 0.309017 0 4 -0.809017 -0.587785 0 5 -0.809017 0.587785 0 6 -0.587785 -0.809017 0 7 -0.587785 0.809017 0 8 -0.5 -0.5 0 9 -0.5 0. 0 10 -0.5 0.5 0 11 -0.309017 -0.951057 0 12 -0.309017 0.951057 0 13 0. -1. 0 14 0. -0.5 0 15 0. 0. 0 16 0. 0.5 0 17 0. 1. 0 18 0.309017 -0.951057 0 19 0.309017 0.951057 0 20 0.5 -0.5 0 21 0.5 0. 0 22 0.5 0.5 0 23 0.587785 -0.809017 0 24 0.587785 0.809017 0 25 0.809017 -0.587785 0 26 0.809017 0.587785 0 27 0.951057 -0.309017 0 28 0.951057 0.309017 0 29 1. 0. 0 edges 1 8 2 2 2 4 3 4 8 4 4 6 5 6 8 6 11 13 7 13 14 8 14 11 9 8 9 10 9 2 11 14 8 12 8 11 13 13 18 14 18 14 15 6 11 16 14 9 17 3 10 18 10 5 19 5 3 20 9 3 21 3 1 22 1 9 23 10 7 24 7 5 25 10 9 26 9 15 27 15 10 28 12 7 29 10 12 30 10 16 31 16 12 32 1 2 33 14 15 34 14 20 35 20 15 36 18 20 37 18 23 38 23 20 39 20 21 40 21 15 41 27 20 42 20 25 43 25 27 44 21 27 45 27 29 46 29 21 47 23 25 48 21 22 49 22 15 50 16 22 51 22 19 52 19 16 53 16 15 54 19 17 55 17 16 56 22 24 57 24 19 58 17 12 59 26 22 60 22 28 61 28 26 62 26 24 63 21 28 64 29 28 faces 1 8 2 4 2 8 4 6 3 11 13 14 4 2 8 9 5 14 8 11 6 13 18 14 7 11 8 6 8 9 8 14 9 3 10 5 10 9 3 1 11 10 7 5 12 10 9 15 13 12 7 10 14 10 16 12 15 10 3 9 16 1 2 9 17 15 9 14 18 14 20 15 19 20 14 18 20 20 18 23 21 15 20 21 22 27 20 25 23 21 27 29 24 21 20 27 25 25 20 23 26 15 21 22 27 16 22 19 28 16 15 22 29 19 17 16 30 22 24 19 31 16 17 12 32 26 22 28 33 24 22 26 34 22 21 28 35 29 28 21 36 15 16 10 ================================================ FILE: examples/tactoid/tactoid.morpho ================================================ import meshtools import optimize import plot import povray // Create mesh and director field var m = Mesh("disk.mesh") var nn = Field(m, Matrix([1,0,0])) // Create functionals var lf=Nematic(nn, kbend=1) var ln=NormSq(nn) var la=LineIntegral(fn (x, n) n.inner(tangent())^2, nn) var lt=Length() var laa=Area() var leq=EquiElement() // Initial boundary selection var bnd=Selection(m, boundary=true) bnd.addgrade(0) // Material parameters var sigma=5.0*0.04 // Surface tension var W=3.0 // Anchoring var itermax = 4 // Set up the optimization problem var problem = OptimizationProblem(m) problem.addenergy(lf) problem.addenergy(la, selection=bnd, prefactor=-W/2) problem.addenergy(lt, selection=bnd, prefactor=sigma+W) problem.addconstraint(laa) problem.addlocalconstraint(ln, field=nn, target=1) // Set up a regularization problem var reg = OptimizationProblem(m) reg.addenergy(leq) // Create shape and field optimizers var sopt = ShapeOptimizer(problem, m) var fopt = FieldOptimizer(problem, nn) var ropt = ShapeOptimizer(reg, m) ropt.fix(bnd) // Set up initial stepsize sopt.stepsize=0.05 sopt.steplimit=0.1 var mr, refmap // Refinement // A function we'll use to check each contribution to the energy fn checkenergy(problem, opt) { print "--Contributions to energy" for (en in problem.energies) { print en.functional.clss() print opt.total(en) } } // Main optimization loop for (iter in 1..itermax) { for (i in 1..10) { if (iter1.5*mean) srefine[2,id]=true // Create a mesh refiner mr=MeshRefiner([m, nn, bnd]) if (srefine.count(2)>0) { refmap = mr.refine(selection=srefine) } else { refmap = mr.refine() } // Now refinement is done update the problems and optimizers for (el in [problem, reg, sopt, fopt, ropt]) el.update(refmap) // Use the new mesh and field m = refmap[m] nn = refmap[nn] bnd = refmap[bnd] // Must ensure boundary remains correctly fixed ropt.fixed=[] ropt.fix(Selection(m, boundary=true)) // Equiangulate equiangulate(m) } // Reduce stepsize sopt.stepsize=0.05/iter sopt.steplimit=0.1/iter } // Function to visualize a director field // m - the mesh // nn - the director Field to visualize // dl - scale the director fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = m.count() var g = Graphics() for (i in 0...nv) { var x = v.column(i) g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=0.3)) } return g } // Visualize the result var g=plotmesh(m, grade=1) var gnn=visualize(m, nn, 0.2/itermax) var gdisp = g+gnn Show(gdisp) // Visualize the elastic energy density var fe = lf.integrand(nn) var ff = Field(m, grade=2) for (i in 0...m.count(2)) { ff[2,i]=fe[0,i] } var sb = ScaleBar(nticks=3, // Maximum number of ticks length=1, // Length of scalebar posn=[0,-1,0], // Position of scalebar dirn=[1,0,0], // Direction in which scalebar is to be drawn tickdirn=[0,-1,0], // Direction in which to draw ticks textdirn=[1,0,0], // Direction to draw text textvertical=[0,1,0], // Vertical direction for text textcolor=White, // Text color fontsize=6 ) // Font size Show(plotfield(ff, colormap=GrayMap(), scalebar=sb)) gdisp.background = White var pov = POVRaytracer(gdisp) pov.viewpoint = Matrix([0,0,10]) pov.background = White pov.light=[Matrix([10,10,10]), Matrix([-10,-10,10])] pov.render("out.pov") ================================================ FILE: examples/tactoid/tactoid2dmesh.morpho ================================================ import meshtools import optimize import plot import povray import meshgen // Create mesh and director field var dom = fn (x) -(x[0]^2+x[1]^2-1) var mg = MeshGen(dom, [-1..1:0.2, -1..1:0.2]) var m = mg.build() var nn = Field(m, Matrix([1,0,0])) // Create functionals var lf=Nematic(nn, kbend=1) var ln=NormSq(nn) fn integrand(x, n) { var t = tangent() return (n[0]*t[0]+n[1]*t[1])^2 } var la=LineIntegral(integrand, nn) var lt=Length() var laa=Area() var leq=EquiElement() // Initial boundary selection var bnd=Selection(m, boundary=true) bnd.addgrade(0) // Material parameters var sigma=5.0*0.04 // Surface tension var W=3.0 // Anchoring var itermax = 4 // Set up the optimization problem var problem = OptimizationProblem(m) problem.addenergy(lf) problem.addenergy(la, selection=bnd, prefactor=-W/2) problem.addenergy(lt, selection=bnd, prefactor=sigma+W) problem.addconstraint(laa) problem.addlocalconstraint(ln, field=nn, target=1) // Set up a regularization problem var reg = OptimizationProblem(m) reg.addenergy(leq) // Create shape and field optimizers var sopt = ShapeOptimizer(problem, m) var fopt = FieldOptimizer(problem, nn) var ropt = ShapeOptimizer(reg, m) ropt.fix(bnd) // Set up initial stepsize sopt.stepsize=0.05 sopt.steplimit=0.1 var mr, refmap // Refinement // A function we'll use to check each contribution to the energy fn checkenergy(problem, opt) { print "--Contributions to energy" for (en in problem.energies) { print en.functional.clss() print opt.total(en) } } // Main optimization loop for (iter in 1..itermax) { for (i in 1..10) { if (iter1.5*mean) srefine[2,id]=true // Create a mesh refiner mr=MeshRefiner([m, nn, bnd]) if (srefine.count(2)>0) { refmap = mr.refine(selection=srefine) } else { refmap = mr.refine() } // Now refinement is done update the problems and optimizers for (el in [problem, reg, sopt, fopt, ropt]) el.update(refmap) // Use the new mesh and field m = refmap[m] nn = refmap[nn] bnd = refmap[bnd] // Must ensure boundary remains correctly fixed ropt.fixed=[] ropt.fix(Selection(m, boundary=true)) // Equiangulate equiangulate(m) } // Reduce stepsize sopt.stepsize=0.05/iter sopt.steplimit=0.1/iter } // Function to visualize a director field // m - the mesh // nn - the director Field to visualize // dl - scale the director fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = m.count() var g = Graphics() for (i in 0...nv) { var x = v.column(i) var y = Matrix([x[0], x[1], 0]) g.display(Cylinder(y-nn[i]*dl, y+nn[i]*dl, aspectratio=0.3)) } return g } //System.exit() // Visualize the result var g=plotmesh(m, grade=1) var gnn=visualize(m, nn, 0.2/itermax) var gdisp = g+gnn Show(gdisp) // Visualize the elastic energy density var fe = lf.integrand(nn) var ff = Field(m, grade=2) for (i in 0...m.count(2)) { ff[2,i]=fe[0,i] } var sb = ScaleBar(nticks=3, // Maximum number of ticks length=1, // Length of scalebar posn=[0,-1,0], // Position of scalebar dirn=[1,0,0], // Direction in which scalebar is to be drawn tickdirn=[0,-1,0], // Direction in which to draw ticks textdirn=[1,0,0], // Direction to draw text textvertical=[0,1,0], // Vertical direction for text textcolor=White, // Text color fontsize=6 ) // Font size Show(plotfield(ff, colormap=GrayMap(), scalebar=sb)) gdisp.background = White var pov = POVRaytracer(gdisp) pov.viewpoint = Matrix([0,0,10]) pov.background = White pov.light=[Matrix([10,10,10]), Matrix([-10,-10,10])] pov.render("out.pov") ================================================ FILE: examples/thomson/thomson.morpho ================================================ // Thomson problem of arranging charges on a sphere // to minimize the electrostatic energy // Showcases: MeshBuilder, PairwisePotential, ScalarPotential import meshtools import plot import optimize import functionals var Np = 100 // Number of particles // Create the mesh, which consists of Np random points each representing // a charge on the unit sphere. var build = MeshBuilder() for (i in 1..Np) { var x = Matrix([2*random()-1, 2*random()-1, 2*random()-1]) x/=x.norm() // Project onto unit sphere build.addvertex(x) } var mesh = build.build() // Tell the MeshBuilder to build the mesh // Specify the problem var problem = OptimizationProblem(mesh) // The particle repel one another by a Coulomb potential. // We supply the potential and it's derivative wrt r as anonymous functions. var lv = PairwisePotential(fn (r) 1/r, fn (r) -1/r^2) problem.addenergy(lv) // Constrain the particles on the unit sphere via a level set constraint. // The level set function and its gradient are supplied as anonymous functions. var lsph = ScalarPotential(fn (x,y,z) x^2+y^2+z^2-1, fn (x,y,z) Matrix([2*x, 2*y, 2*z])) problem.addlocalconstraint(lsph) // Set up the optimizer to optimize this problem wrt the mesh vertex positions. var opt = ShapeOptimizer(problem, mesh) // Choose a stepsize opt.stepsize=0.01/sqrt(Np) // Do a few iterations at fixed stepsize to move away from the initally random // condition. [This helps condition the problem] opt.relax(5) // Now perform gradient descent opt.conjugategradient(1000) // Perform up to 1000 iterations of direct gradient descent // Visualize the results var g = Graphics() for (i in 0...mesh.count()) { // Display each particle as a sphere g.display(Sphere(mesh.vertexposition(i),1/sqrt(Np))) } Show(g) // Open up the viewer application ================================================ FILE: examples/tutorial/disk.mesh ================================================ vertices 1 -1. 0. 0 2 -0.951057 -0.309017 0 3 -0.951057 0.309017 0 4 -0.809017 -0.587785 0 5 -0.809017 0.587785 0 6 -0.587785 -0.809017 0 7 -0.587785 0.809017 0 8 -0.5 -0.5 0 9 -0.5 0. 0 10 -0.5 0.5 0 11 -0.309017 -0.951057 0 12 -0.309017 0.951057 0 13 0. -1. 0 14 0. -0.5 0 15 0. 0. 0 16 0. 0.5 0 17 0. 1. 0 18 0.309017 -0.951057 0 19 0.309017 0.951057 0 20 0.5 -0.5 0 21 0.5 0. 0 22 0.5 0.5 0 23 0.587785 -0.809017 0 24 0.587785 0.809017 0 25 0.809017 -0.587785 0 26 0.809017 0.587785 0 27 0.951057 -0.309017 0 28 0.951057 0.309017 0 29 1. 0. 0 edges 1 8 2 2 2 4 3 4 8 4 4 6 5 6 8 6 11 13 7 13 14 8 14 11 9 8 9 10 9 2 11 14 8 12 8 11 13 13 18 14 18 14 15 6 11 16 14 9 17 3 10 18 10 5 19 5 3 20 9 3 21 3 1 22 1 9 23 10 7 24 7 5 25 10 9 26 9 15 27 15 10 28 12 7 29 10 12 30 10 16 31 16 12 32 1 2 33 14 15 34 14 20 35 20 15 36 18 20 37 18 23 38 23 20 39 20 21 40 21 15 41 27 20 42 20 25 43 25 27 44 21 27 45 27 29 46 29 21 47 23 25 48 21 22 49 22 15 50 16 22 51 22 19 52 19 16 53 16 15 54 19 17 55 17 16 56 22 24 57 24 19 58 17 12 59 26 22 60 22 28 61 28 26 62 26 24 63 21 28 64 29 28 faces 1 8 2 4 2 8 4 6 3 11 13 14 4 2 8 9 5 14 8 11 6 13 18 14 7 11 8 6 8 9 8 14 9 3 10 5 10 9 3 1 11 10 7 5 12 10 9 15 13 12 7 10 14 10 16 12 15 10 3 9 16 1 2 9 17 15 9 14 18 14 20 15 19 20 14 18 20 20 18 23 21 15 20 21 22 27 20 25 23 21 27 29 24 21 20 27 25 25 20 23 26 15 21 22 27 16 22 19 28 16 15 22 29 19 17 16 30 22 24 19 31 16 17 12 32 26 22 28 33 24 22 26 34 22 21 28 35 29 28 21 36 15 16 10 ================================================ FILE: examples/tutorial/tutorial.morpho ================================================ // Morpho tutorial example import meshtools import optimize import plot var m = Mesh("disk.mesh") // Initial boundary selection var bnd=Selection(m, boundary=true) bnd.addgrade(0) //Show(plotselection(m, bnd, grade=1)) var nn = Field(m, Matrix([1,0,0])) // Define functionals var lf=Nematic(nn) var lt=Length() var la=LineIntegral(fn (x, n) n.inner(tangent())^2, nn) var ln=NormSq(nn) var laa=Area() // Set up the optimization problem var W = 1 var sigma = 1 var problem = OptimizationProblem(m) problem.addenergy(lf) problem.addenergy(la, selection=bnd, prefactor=-W/2) problem.addenergy(lt, selection=bnd, prefactor=sigma) problem.addconstraint(laa) problem.addlocalconstraint(ln, field=nn, target=1) // Create shape and field optimizers var sopt = ShapeOptimizer(problem, m) var fopt = FieldOptimizer(problem, nn) // Optimization loop for (i in 1..100) { fopt.linesearch(20) sopt.linesearch(20) } // Visualize results var g=plotmesh(m, grade=1) // Function to visualize a director field // m - the mesh // nn - the director Field to visualize // dl - scale the director fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = m.count() // Number of vertices var g = Graphics() // Create a graphics object for (i in 0...nv) { var x = v.column(i) // Get the ith vertex // Draw a cylinder aligned with nn at this vertex g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=0.3)) } return g } var gnn=visualize(m, nn, 0.2) var gdisp = g+gnn Show(gdisp) ================================================ FILE: examples/tutorial/tutorial2.morpho ================================================ // Morpho tutorial example import meshtools import optimize import plot var m = Mesh("disk.mesh") // Initial boundary selection var bnd=Selection(m, boundary=true) bnd.addgrade(0) //Show(plotselection(m, bnd, grade=1)) var nn = Field(m, Matrix([1,0,0])) // Define functionals var lf=Nematic(nn) var lt=Length() var la=LineIntegral(fn (x, n) n.inner(tangent())^2, nn) var ln=NormSq(nn) var laa=Area() // Set up the optimization problem var W = 1 var sigma = 1 var problem = OptimizationProblem(m) problem.addenergy(lf) problem.addenergy(la, selection=bnd, prefactor=-W/2) problem.addenergy(lt, selection=bnd, prefactor=sigma) problem.addconstraint(laa) problem.addlocalconstraint(ln, field=nn, target=1) // Create shape and field optimizers var sopt = ShapeOptimizer(problem, m) var fopt = FieldOptimizer(problem, nn) // Optimization loop var refmax = 3 for (refiter in 1..refmax) { print "===Refinement level ${refiter}===" for (i in 1..100) { fopt.linesearch(20) sopt.linesearch(20) } // Refinement if (refiter==refmax) break var mr=MeshRefiner([m, nn, bnd]) // Set the refiner up var refmap=mr.refine() // Perform the refinement for (el in [problem, sopt, fopt]) el.update(refmap) // Update the problem m=refmap[m]; nn=refmap[nn]; bnd=refmap[bnd] // Update variables } // Visualize results var g=plotmesh(m, grade=1) // Function to visualize a director field // m - the mesh // nn - the director Field to visualize // dl - scale the director fn visualize(m, nn, dl) { var v = m.vertexmatrix() var nv = m.count() // Number of vertices var g = Graphics() // Create a graphics object for (i in 0...nv) { var x = v.column(i) // Get the ith vertex // Draw a cylinder aligned with nn at this vertex g.display(Cylinder(x-nn[i]*dl, x+nn[i]*dl, aspectratio=0.3)) } return g } var gnn=visualize(m, nn, 0.2/refmax) var gdisp = g+gnn Show(gdisp) ================================================ FILE: examples/wrap/wrap.morpho ================================================ /* Finds a minimal surface that connects two ellipsoids. This models a fluid interface between two colloidal particles, for example */ import graphics import meshtools import plot import optimize // Create a initial cube var L = 2 var cube = [[-L, -L, -L], [-L, -L, L], [-L, L, -L], [-L, L, L], [L, -L, -L], [L, -L, L], [L, L, -L], [L, L, L]] var faces = [[7, 3, 1, 5], [7, 5, 4, 6], [7, 6, 2, 3], [3, 2, 0, 1], [0, 2, 6, 4], [1, 0, 4, 5]] var m=PolyhedronMesh(cube, faces) m=refinemesh(m) // Make a class to manufacture axis aligned ellipsoids. // To create one, call Ellipsoid(origin, principalradii) class Ellipsoid { init(x, r) { self.origin = x self.principalradii = r } // Returns a level set function for this Ellipsoid levelset() { fn phi (x,y,z) { var x0 = self.origin, rr = self.principalradii return ((x-x0[0])/rr[0])^2 + ((y-x0[1])/rr[1])^2 + ((z-x0[2])/rr[2])^2 - 1 } return phi } // Returns the a function that returns the gradient // of the level set function for this Ellipsoid gradient() { fn dphi (x,y,z) { var x0 = self.origin, rr = self.principalradii return Matrix([2*(x-x0[0])/rr[0]^2, 2*(y-x0[1])/rr[1]^2, 2*(z-x0[2])/rr[2]^2]) } return dphi } } // Now use this to manufacture some Ellipsoids var ell1 = Ellipsoid([0,1/2,0],[1/2,1/2,1]) var ell2 = Ellipsoid([0,-1/2,0],[1,1/2,1/2]) // We want to minimize the area var la = Area() // Subject to level set constraints var ls1 = ScalarPotential( ell1.levelset(), ell1.gradient() ) var ls2 = ScalarPotential( ell2.levelset(), ell2.gradient() ) var leq = EquiElement() var problem = OptimizationProblem(m) problem.addenergy(la) problem.addlocalconstraint(ls1, onesided=true) problem.addlocalconstraint(ls2, onesided=true) var reg = OptimizationProblem(m) reg.addenergy(leq) reg.addlocalconstraint(ls1, onesided=true) reg.addlocalconstraint(ls2, onesided=true) var sopt = ShapeOptimizer(problem, m) var ropt = ShapeOptimizer(reg, m) sopt.stepsize=0.025 sopt.steplimit=0.1 sopt.ctol = 1e-9 sopt.maxconstraintsteps = 100 ropt.stepsize=0.01 ropt.steplimit=0.2 for (refine in 1..3) { for (i in 1..100) { sopt.relax(5) ropt.conjugategradient(5) equiangulate(m) } var mr=MeshRefiner([m]) var refmap = mr.refine() for (el in [problem, reg, sopt, ropt]) el.update(refmap) m = refmap[m] } Show(plotmesh(m, grade=2)) ================================================ FILE: help/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: help/array.md ================================================ [comment]: # (Array class help) [version]: # (0.5) # Array [tagarray]: # (Array) Arrays are collection objects that can have any number of indices. Their size is set when they are created: var a[5] var b[2,2] var c[nv,nv,nv] Values can be retrieved with appropriate indices: print a[0,0] Arrays can be indexed with slices: print a[[0,2,4],2] print a[1,0..2] Any morpho value can be stored in an array element a[0,0] = [1,2,3] [showsubtopics]: # (subtopics) ## Dimensions [tagdimensions]: # (Dimensions) Get the dimensions of an Array object: var a[2,2] print a.dimensions() // expect: [ 2, 2 ] ================================================ FILE: help/builtin.md ================================================ [comment]: # (Builtin function help) [version]: # (0.5) # Builtin functions [tagbuiltin]: # (builtin) Morpho provides a number of built-in functions. [showsubtopics]: # (subtopics) ## Random [tagrandom]: # (random) [tagrand]: # (rand) The `random` function generates a random number from a uniform distribution on the interval [0,1]. print random() See also `randomnormal` and `randomint`. ## Randomnormal [tagrandomnormal]: # (randomnormal) The `randomnormal` function generates a random number from a normal (gaussian) distribution with unit variance and zero offset. print randomnormal() See also `random` and `randomint`. ## Randomint [tagrandomint]: # (randomint) The `randomint` function generates a random integer with a specified maximum value. print randomint(10) // Generates a random integer [0,10) ## isnil [tagisnil]: # (isnil) Returns `true` if a value is `nil` or `false` otherwise. ## isint [tagisint]: # (isint) Returns `true` if a value is an integer or `false` otherwise. ## isfloat [tagisfloat]: # (isfloat) Returns `true` if a value is a floating point number or `false` otherwise. ## isbool [tagisbool]: # (isbool) Returns `true` if a value is a boolean or `false` otherwise. ## isobject [tagisobject]: # (isobject) Returns `true` if a value is an object or `false` otherwise. ## isstring [tagisstring]: # (isstring) Returns `true` if a value is a string or `false` otherwise. ## isclass [tagisclass]: # (isclass) Returns `true` if a value is a class or `false` otherwise. ## isrange [tagisrange]: # (isrange) Returns `true` if a value is a range or `false` otherwise. ## isdictionary [tagisdictionary]: # (isdictionary) Returns `true` if a value is a dictionary or `false` otherwise. ## islist [tagislist]: # (islist) Returns `true` if a value is a list or `false` otherwise. ## isarray [tagisarray]: # (isarray) Returns `true` if a value is an array or `false` otherwise. ## ismatrix [tagismatrix]: # (ismatrix) Returns `true` if a value is a matrix or `false` otherwise. ## issparse [tagissparse]: # (issparse) Returns `true` if a value is a sparse matrix or `false` otherwise. ## isinf [tagisinf]: # (isinf) Returns `true` if a value is infinite or `false` otherwise. ## isnan [tagisnan]: # (isnan) Returns `true` if a value is a Not a Number or `false` otherwise. ## iscallable [tagiscallable]: # (iscallable) Returns `true` if a value is callable or `false` otherwise. ## isfinite [tagisfinite]: # (isfinite) Returns `true` if a value is finite or `false` otherwise. print isfinite(1) // expect: true print isfinite(1/0) // expect: false ## isnumber [tagisnumber]: # (isnumber) Returns `true` if a value is a real number, or `false` otherwise. print isnumber(1) // expect: true print isnumber(Object()) // expect: false ## ismesh [tagismesh]: # (ismesh) Returns `true` if a value is a `Mesh`, or `false` otherwise. ## isselection [tagisselection]: # (isselection) Returns `true` if a value is a `Selection`, or `false` otherwise. ## isfield [tagisfield]: # (isfield) Returns `true` if a value is a `Field`, or `false` otherwise. ## Apply [tagapply]: # (apply) Apply calls a function with the arguments provided as a list: apply(f, [0.5, 0.5]) // calls f(0.5, 0.5) It's often useful where a function or method and/or the number of parameters isn't known ahead of time. The first parameter to apply can be any callable object, including a method invocation or a closure. You may also instead omit the list and use apply with multiple arguments: apply(f, 0.5, 0.5) // calls f(0.5, 0.5) There is one edge case that occurs when you want to call a function that accepts a single list as a parameter. In this case, enclose the list in another list: apply(f, [[1,2]]) // equivalent to f([1,2]) ## Abs [tagabs]: # (abs) Returns the absolute value of a number: print abs(-10) // prints 10 ## Sign [tagsign]: # (sign) Gives the sign of a number: print sign(4) // expect: 1 print sign(-10.0) // expect: -1 print sign(0) // expect: 0 ## Arctan [tagarctan]: # (arctan) Returns the arctangent of an input value that lies from `-Inf` to `Inf`. You can use one argument: print arctan(0) // expect: 0 or use two arguments to return the angle in the correct quadrant: print arctan(x, y) Note the order `x`, `y` differs from some other languages. ## Exp [tagexp]: # (exp) Exponential function `e^x`. Inverse of `log`. print exp(0) // expect: 1 print exp(Pi*im) // expect: -1 + 0im ## Log [taglog]: # (log) Natural logarithm function. Inverse of `exp`. print log(1) // expect: 0 ## Log10 [taglog10]: # (log10) Base 10 logarithm function. print log10(10) // expect: 1 ## Sin [tagsin]: # (sin) Sine trigonometric function. print sin(0) // expect: 0 ## Sinh [tagsinh]: # (sinh) Hyperbolic sine trigonometric function. print sinh(0) // expect: 0 ## Cos [tagcos]: # (cos) Cosine trigonometric function. print cos(0) // expect: 1 ## Cosh [tagcosh]: # (cosh) Hyperbolic cosine trigonometric function. print cosh(0) // expect: 1 ## Tan [tagtan]: # (tan) Tangent trigonometric function. print tan(0) // expect: 0 ## Tanh [tagtanh]: # (tanh) Hyperbolic tangent trigonometric function. print tanh(0) // expect: 0 ## Asin [tagasin]: # (asin) Inverse sine trigonometric function. Returns a value on the interval `[-Pi/2,Pi/2]`. print asin(0) // expect: 0 ## Acos [tagacos]: # (acos) Inverse cosine trigonometric function. Returns a value on the interval `[-Pi/2,Pi/2]`. print acos(1) // expect: 0 ## Sqrt [tagsqrt]: # (sqrt) Square root function. print sqrt(4) // expect: 2 ## Min [tagmin]: # (min) Finds the minimum value of its arguments. If any of the arguments are Objects and are enumerable, (e.g. a `List`), `min` will search inside them for a minimum value. Accepts any number of arguments. print min(3,2,1) // expect: 1 print min([3,2,1]) // expect: 1 print min([3,2,1],[0,-1,2]) // expect: -2 ## Max [tagmax]: # (max) Finds the maximum value of its arguments. If any of the arguments are Objects and are enumerable, (e.g. a `List`), `max` will search inside them for a maximum value. Accepts any number of arguments. print min(3,2,1) // expect: 3 print min([3,2,1]) // expect: 3 print min([3,2,1],[0,-1,2]) // expect: 3 ## Bounds [tagbounds]: # (bounds) Returns both the results of `min` and `max` as a list, Providing a set of bounds for its arguments and any enumerable objects within them. print bounds(1,2,3) // expect: [1,3] print bounds([3,2,1],[0,-1,2]) // expect: [-1,3] ================================================ FILE: help/classes.md ================================================ [comment]: # (Morpho classes help file) [version]: # (0.5) [toplevel]: # # Classes [tagclass]: # (class) [tagmethod]: # (method) Classes are defined using the `class` keyword followed by the name of the class. The definition includes methods that the class responds to. The special `init` method is called whenever an object is created. class Cake { init(type) { self.type = type } eat() { print "A delicious "+self.type+" cake" } } Objects are created by calling the class as if it was a function: var c = Cake("carrot") Note that all objects in Morpho inherit from a base `Object` class, which provides a set of standard methods. See also `Object`. [showsubtopics]: # (subtopics) ## Methods [tagmethods]: # (methods) Classes in morpho can define *methods* to manipulate the objects defined by the class. Like functions, multiple implementations can be defined that accept different parameter types [see also topic: `signature`]: class Foo { a(List x) { print "A list!" } a(String x) { print "A string!" } a(Matrix x) { print "A matrix!" } } Having created an object with the class, var x = Foo() the correct implementation is selected at runtime: x.a([1,2]) // expect: A list! x.a("Hello") // expect: A string! ## Is [tagis]: # (is) The `is` keyword is used to specify a class's superclass: class A is B { } All methods defined by the superclass `B` are copied into the new class `A`, *before* any methods specified in the class definition. Hence, you can replace methods from the superclass simply by defining a method with the same name. ## With [tagwith]: # (with) [tagmixin]: # (mixin) The `with` keyword is used together with `is` to insert additional methods into a class definition *without* making them the superclass. These are often called `mixins`. These methods are inserted after the superclass's methods. Multiple classes can be specified after `with`; they are added in the order specified. class A is B with C, D { } Here `B` is the superclass of `A`, but methods defined by `C` and `D` are also available to `A`. If `B`, `C` and `D` define methods with the same name, those in `C` take precedence over any in `B` and those in `D` take precedence over `B` and `C`. ## Self [tagself]: # (self) The `self` keyword is used to access an object's properties and methods from within its definition. class Vehicle { init (type) { self.type = type } drive () { print "Driving my ${self.type}." } } ## Super [tagsuper]: # (super) The keyword `super` allows you to access methods provided by an object's superclass rather than its own. This is particularly useful when the programmer wants a class to extend the functionality of a parent class, but needs to make sure the old behavior is still maintained. For example, consider the following pair of classes: class Lunch { init(type) { self.type=type } } class Soup is Lunch { init(type) { print "Delicious soup!" super.init(type) } } The subclass Soup uses `super` to call the original initializer. # Objects [tagobject]: # (object) [tagobjects]: # (objects) [tagproperty]: # (property) [tagproperties]: # (properties) Objects in Morpho are created by calling a constructor function, which usually has the same name as the class of the object: var a = Color(0.5,0.5,0.5) // 50% gray You can store information in an object by assigning to its properties: a.prop = "Foo" and you can read from them similarly: print a.prop An object's `class` determines the methods that can be used on the object. You call them using the . operator: print a.clone() See also `class`. [showsubtopics]: # (subtopics) ## Has [taghas]: # (has) The `has` method is used to test if an object has a particular property: print a.has("foo") If you call `has` with no parameters, print a.has() it returns a list of all property labels that an object has. ## Respondsto [tagrespondsto]: # (respondsto) The `respondsto` method is used to test if an object provides a particular method: print a.respondsto("foo") If you call `respondsto` with no parameters, print a.respondsto() it returns a list of all methods that an object has available. ## Invoke [taginvoke]: # (invoke) The `invoke` method is used to invoke a method from its label and a list of parameters: print a.invoke("has", "foo") is equivalent to: print a.has("foo") ## Clss [tagclss]: # (clss) The `clss` method is used to get the class to which an object belongs. print a.clss() ================================================ FILE: help/color.md ================================================ [comment]: # (Color module help) [version]: # (0.5) # Color [tagcolor]: # (color) The `color` module provides support for working with color. Colors are represented in morpho by `Color` objects. The module predefines some colors including `Red`, `Green`, `Blue`, `Black`, `White`. To use the module, use import as usual: import color Create a Color object from an RGB pair: var col = Color(0.5,0.5,0.5) // A 50% gray The `color` module also provides `ColorMap`s, which are give a sequence of colors as a function of a parameter; these are useful for plotting the values of a `Field` for example. [showsubtopics]: # (subtopics) ## RGB [tagrgb]: # (rgb) Gets the rgb components of a `Color` or `ColorMap` object as a list. Takes a single argument in the range 0 to 1, although the result will only depend on this argument if the object is a `ColorMap`. var col = Color(0.1,0.5,0.7) print col.rgb(0) ## Red [tagred]: # (red) Built in `Color` object for use with the `graphics` and `plot` modules. ## Green [taggreen]: # (green) Built in `Color` object for use with the `graphics` and `plot` modules. ## Blue [tagblue]: # (blue) Built in `Color` object for use with the `graphics` and `plot` modules. ## White [tagwhite]: # (white) Built in `Color` object for use with the `graphics` and `plot` modules. ## Black [tagblack]: # (black) Built in `Color` object for use with the `graphics` and `plot` modules. ## Cyan [tagcyan]: # (cyan) Built in `Color` object for use with the `graphics` and `plot` modules. ## Magenta [tagmagenta]: # (magenta) Built in `Color` object for use with the `graphics` and `plot` modules. ## Yellow [tagyellow]: # (yellow) Built in `Color` object for use with the `graphics` and `plot` modules. ## Brown [tagbrown]: # (brown) Built in `Color` object for use with the `graphics` and `plot` modules. ## Orange [tagorange]: # (orange) Built in `Color` object for use with the `graphics` and `plot` modules. ## Pink [tagpink]: # (pink) Built in `Color` object for use with the `graphics` and `plot` modules. ## Purple [tagpurple]: # (purple) Built in `Color` object for use with the `graphics` and `plot` modules. ## Colormap [tagcolormap]: # (colormap) The `color` module provides `ColorMap`s which are subclasses of `Color` that map a single parameter in the range 0 to 1 onto a continuum of colors. `Color`s and `Colormap`s have the same interface. Get the red, green or blue components of a color or colormap: var col = HueMap() print col.red(0.5) // argument can be in range 0 to 1 Get all three components as a list: col.rgb(0) Create a grayscale: var c = Gray(0.2) // 20% gray Available ColorMaps: `GradientMap`, `GrayMap`, `HueMap`, `ViridisMap`, `MagmaMap`, `InfernoMap` and `PlasmaMap`. ## GradientMap [taggradientmap]: # (gradientmap) `GradientMap` is a `Colormap` that displays a white-green-purple sequence. ## GrayMap [taggraymap]: # (graymap) `GrayMap` is a `Colormap` that displays grayscales. ## HueMap [taghuemap]: # (huemap) `HueMap` is a `Colormap` that displays vivid colors. It is periodic on the interval 0 to 1. ## ViridisMap [tagviridismap]: # (viridismap) `ViridisMap` is a `Colormap` that displays a purple-green-yellow sequence. It is perceptually uniform and intended to be improve the accessibility of visualizations for viewers with color vision deficiency. ## MagmaMap [tagmagmamap]: # (magmamap) `MagmaMap` is a `Colormap` that displays a black-red-yellow sequence. It is perceptually uniform and intended to be improve the accessibility of visualizations for viewers with color vision deficiency. ## InfernoMap [taginfernomap]: # (infernomap) `InfernoMap` is a `Colormap` that displays a black-red-yellow sequence. It is perceptually uniform and intended to be improve the accessibility of visualizations for viewers with color vision deficiency. ## PlasmaMap [tagplasmamap]: # (plasmamap) `InfernoMap` is a `Colormap` that displays a blue-red-yellow sequence. It is perceptually uniform and intended to be improve the accessibility of visualizations for viewers with color vision deficiency. ================================================ FILE: help/complex.md ================================================ [comment]: # (Complex help) [version]: # (0.5) # Complex [tagcomplex]: # (complex) [tagim]: # (im) Morpho provides complex numbers. The keyword `im` is used to denote the imaginary part of a complex number: var a=1+5im print a*a Print values on the unit circle in the complex plane: import constants for (phi in 0..Pi:Pi/5) print exp(im*phi) Get the real and imaginary parts of a complex number: print real(a) print imag(a) or alternatively: print a.real() print a.imag() [showsubtopics]: # (subtopics) ## Angle [tagangle]: # (angle) Returns the angle `phi` associated with the polar representation of a complex number `r*exp(im*phi)`: print z.angle() ## Conj [tagconjugate]: # (conjugate) [tagconj]: # (conj) Returns the complex conjugate of a number: print z.conj() ================================================ FILE: help/conf.py ================================================ # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'morpho' copyright = '2020-2024, T J Atherton' author = 'T J Atherton' # The full version, including alpha/beta/rc tags release = '0.6.0' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['myst_parser'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # Use recommonmark to parse md files from recommonmark.parser import CommonMarkParser source_parsers = {'.md': CommonMarkParser} source_suffix = ['.rst', '.md'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] latex_engine = 'xelatex' ================================================ FILE: help/constants.md ================================================ [comment]: # (Constants module help) [version]: # (0.5) # Constants [tagconstants]: # (constants) The constants module contains a number of useful mathematical constants. Import it like any other module: import constants Available constants: * `E` the base of natural logarithms. * `Pi` ratio of the perimeter of a circle to its diameter. ================================================ FILE: help/controlflow.md ================================================ [comment]: # (Morpho control flow help file) [version]: # (0.5) [toplevel]: # # Control Flow [tagcontrol]: # (control) Control flow statements are used to determine whether and how many times a selected piece of code is executed. These include: * `if` - Selectively execute a piece of code if a condition is met. * `else` - Execute a different block of code if the test in an `if` statement fails. * `for` - Repeatedly execute a section of code with a counter * `while` - Repeatedly execute a section of code while a condition is true. ## If [tagif]: # (if) [tagelse]: # (else) `If` allows you to selectively execute a section of code depending on whether a condition is met. The simplest version looks like this: if (x<1) print x where the body of the loop, `print x`, is only executed if x is less than 1. The body can be a code block to accommodate longer sections of code: if (x<1) { ... // do something } If you want to choose between two alternatives, use `else`: if (a==b) { // do something } else { // this code is executed only if the condition is false } You can even chain multiple tests together like this: if (a==b) { // option 1 } else if (a==c) { // option 2 } else { // something else } ## While [tagwhile]: # (while) While loops repeat a section of code while a condition is true. For example, var k=1 while (k <= 4) { print k; k+=1 } ^cond ^body prints the numbers 1 to 4. The loop has two sections: `cond` is the condition to be executed and `body` is the section of code to be repeated. Simple loops like the above example, especially those that involve counting out a sequence of numbers, are more conveniently written using a `for` loop, for (k in 1..4) print k Where `while` loops can be very useful is where the state of an object is being changed in the loop, e.g. var a = List(1,2,3,4) while (a.count()>0) print a.pop() which prints 4,3,2,1. ## Do [tagdo]: # (do) A `do`...`while` loop repeats code while a condition is true---similar to a `while` loop---but the test happens at the end: var k=1 do { print k; k+=1 } while (k<5) which prints 1,2,3,4 Hence this type of loop executes at least one interation ## For [tagfor]: # (for) [tagin]: # (in) For loops allow you to repeatedly execute a section of code. They come in two versions: the simpler version looks like this, for (var i in 1..5) print i which prints the numbers 1 to 5 in turn. The variable `i` is the *loop variable*, which takes on a different value each iteration. `1..5` is a range, which denotes a sequence of numbers. The *body* of the loop, `print i`, is the code to be repeatedly executed. Morpho will implicitly insert a `var` before the loop variable if it's missing, so this works too: for (i in 1..5) print i If you want your loop variable to count in increments other than 1, you can specify a stepsize in the range: for (i in 1..5:2) print i ^step Ranges need not be integer: for (i in 0.1..0.5:0.1) print i You can also replace the range with other kinds of collection object to loop over their contents: var a = Matrix([1,2,3,4]) for (x in a) print x Morpho iterates over the collection object using an integer *counter variable* that's normally hidden. If you want to know the current value of the counter (e.g. to get the index of an element as well as its value), you can use the following: var a = [1, 2, 3] for (x, i in a) print "${i}: ${x}" Morpho also provides a second form of `for` loop similar to that in C: for (var i=0; i<5; i+=1) { print i } ^start ^test ^inc. ^body which is executed as follows: start: the variable `i` is declared and initially set to zero. test: before each iteration, the test is evaluated. If the test is `false`, the loop terminates. body: the body of the loop is executed. inc: the variable `i` is increased by 1. You can include any code that you like in each of the sections. ## Break [tagbreak]: # (break) `Break` is used inside loops to finish the loop early. For example for (i in 1..5) { if (i>3) break // --. print i // | (Once i>3) } // | ... // <-' would only print 1, 2 and 3. Once the condition `i>3` is true, the `break` statement causes execution to continue after the loop body. Both `for` and `while` loops support break. ## Continue [tagcontinue]: # (continue) `Continue` is used inside loops to skip over the rest of an iteration. For example for (i in 1..5) { // <-. print "Hello" | if (i>3) continue // --' print i } prints "Hello" five times but only prints 1, 2 and 3. Once the condition `i>3` is true, the `continue` statement causes execution to transfer to the start of the loop body. Traditional `for` loops also support `continue`: // v increment for (var i=0; i<5; i+=1) { if (i==2) continue print i } Since `continue` causes control to be transferred *to the increment section* in this kind of loop, here the program prints 0..4 but the number 2 is skipped. Use of `continue` with `while` loops is possible but isn't recommended as it can easily produce an infinite loop! var i=0 while (i<5) { if (i==2) continue print i i+=1 } In this example, when the condition `i==2` is `true`, execution skips back to the start, but `i` *isn't* incremented. The loop gets stuck in the iteration `i==2`. ## Try [tagtry]: # (try) [tagcatch]: # (catch) A `try` and `catch` statement allow you handle errors. For example try { // Do something } catch { "Tag" : // Handle the error } Code within the block after the `try` keyword is executed. If an error is generated then Morpho looks to see if the tag associated with the error matches any of the labels in the `catch` block. If it does, the code after the matching label is executed. If no error occurs, the catch block is skipped entirely. ================================================ FILE: help/delaunay.md ================================================ [comment]: # (Delaunay module help) [version]: # (0.5) # Delaunay [tagdelaunay]: # (delaunay) The `delaunay` module creates Delaunay triangulations from point clouds. It is dimensionally independent, so generates tetrahedra in 3D and higher order simplices beyond. To use the module, first import it: import delaunay To create a Delaunary triangulation from a list of points: var pts = [] for (i in 0...100) pts.append(Matrix([random(), random()])) var del=Delaunay(pts) print del.triangulate() The module also provides `DelaunayMesh` to directly create meshes from Delaunay triangulations. [showsubtopics]: # (subtopics) ## Triangulate [tagtriangulate]: # (triangulate) The `triangulate` method performs the delaunay triangulation. To use it, first construct a `Delaunay` object with the point cloud of interest: var del=Delaunay(pts) Then call `triangulate`: var tri = del.triangulate() This returns a list of triangles `[ [i, j, k], ... ]`. ## Circumsphere [tagcircumsphere]: # (circumsphere) The `Circumsphere` class calculates the circumsphere of a set of points, i.e. a sphere such that all the points are on the surface of the sphere. It is used internally by the `delaunay` module. Create a `Circumsphere` from a list of points and a triangle specified by indices into that list: var sph = Circumsphere(pts, [i,j,k]) Test if an arbitrary point is inside the `Circumsphere` or not: print sph.pointinsphere(pt) ================================================ FILE: help/dictionary.md ================================================ [comment]: # (Dictionary help) [version]: # (0.5) # Dictionary [tag]: # (Dictionary) Dictionaries are collection objects that associate a unique *key* with a particular *value*. Keys can be any kind of morpho value, including numbers, strings and objects. An example dictionary mapping states to capitals: var dict = { "Massachusetts" : "Boston", "New York" : "Albany", "Vermont" : "Montpelier" } Look up values by a given key with index notation: print dict["Vermont"] You can change the value associated with a key, or add new elements to the dictionary like this: dict["Maine"]="Augusta" Create an empty dictionary using the `Dictionary` constructor function: var d = Dictionary() Loop over keys in a dictionary: for (k in dict) print k The `keys` method returns a Morpho List of the keys. var keys = dict.keys() // will return ["Massachusetts", "New York", "Vermont"] The `contains` method returns a Bool value for whether the Dictionary contains a given key. print dict.contains("Vermont") // true print dict.contains("New Hampshire") // false The `remove` method removes a given key from the Dictionary. dict.remove("Vermont") print dict // { New York : Albany, Massachusetts : Boston } The `clear` method removes all the (key, value) pairs fromt the dictionary, resulting in an empty dictionary. dict.clear() print dict // { } ================================================ FILE: help/errors.md ================================================ [comment]: # (Errors help file) [version]: # (0.5) # Errors [tagerror]: # (error) [tagerrors]: # (errors) [tagerrors]: # (throw) [tagerrors]: # (warning) When an error occurs in running a morpho program, an error message is displayed together with an explanation of where in the program that the error happened. You can make your own custom errors using the `Error` class: var myerr = Error("Tag", "A message") Use the `throw` method to raise the error, interrupting execution unless the error is caught: myerr.throw() or myerr.throw("A custom message") You can also use the `warning` method to alert the user of a potential issue that doesn't need the program to be interrupted. myerr.warning() [showsubtopics]: # (subtopics) ## Alloc [tagalloc]: # (alloc) This error may occur when creating new objects or resizing them. It typically indicates that the computer is under memory pressure. ## Intrnl [tagintrnl]: # (intrnl) This error indicates an internal problem with morpho. Please contact the developers for support. ## InvldOp [taginvldop]: # (invldop) This error occurs when an operator like `+` or `-` is given operands that it doesn't understand. For example, print "Hello" * "Goodbye" // Causes 'InvldOp' causes this error because the multiplication operator doesn't know how to multiply strings. If the operands are objects, this means that the objects don't provide a method for the requested operation, e.g. for print object1 / object2 `object1` would need to provide a `div()` method that can successfully handle `object2`. ## CnctFld [tagcnctfld]: # (cnctfld) This error occurs when concatenation of strings or other objects fails, typically because of low memory. ## Uncallable [taguncallable]: # (uncallable) This error occurs when you try to call something that isn't a method or a function. Here, we initialize a variable with a string and call it: var f = "Not a function" f() // Causes 'Uncallable' ## GlblRtrn [tagglblrtrn]: # (glblrtrn) This error occurs when morpho encounters a `return` keyword outside of a function or method definition. ## InstFail [taginstfail]: # (instfail) This error occurs when morpho tried to create a new object, but something went wrong. ## NotAnObj [tagnotanobj]: # (notanobj) This error occurs if you try to access a property of something that isn't an object: var a = 1 a.size = 5 ## ObjLcksPrp [tagobjlcksprp]: # (objlcksprp) This error occurs if you try to access a property or method that hasn't been defined for an object: var a = Object() print a.pifflepaffle or print a.foo() ## NoInit [tagnoinit]: # (noinit) This error can occur if you try to create a new object from a class that doesn't have an `init` method: class Foo { } var a = Foo(0.3) Here, the argument to `Foo` causes the `NoInit` error because no `init` method is available to process it. ## NotAnInst [tagnotaninst]: # (notaninst) This error occurs if you try to invoke a method on something that isn't an object: var a = 4 print a.foo() ## ClssLcksMthd [tagclsslcksmthd]: # (clsslcksmthd) This error occurs if you try to invoke a method on a class that doesn't exist: class Foo { } print Foo.foo() ## InvldArgs [taginvldargs]: # (invldargs) This error occurs if you call a function with the wrong number of arguments: fn f(x) { return x } f(1,2) ## NotIndxbl [tagnotindxbl]: # (notindxbl) This error occurs if you try to index something that isn't a collection: var a = 0.3 print a[1] ## IndxBnds [tagindxbnds]: # (indxbnds) This error can occur when selecting an entry from a collection object (such as a list) if the index supplied is bigger than the number of entries: var a = [1,2,3] print a[10] ## NonNmIndx [tagnonnmindx]: # (nonnmindx) This error occurs if you try to index an array with a non-numerical index: var a[2,2] print a["foo","bar"] ## ArrayDim [tagarraydim]: # arraydim This error occurs if you try to index an array with the wrong number of indices: var a[2,2] print a[1] ## DbgQuit [tagdbgquit]: # (dbgquit) This notification is generated after selecting `Quit` within the debugger. Execution of the program is halted and control returns to the user. ## SymblUndf [tagsymblundf]: # (symblundf) This error occurs if you refer to something that has not been previously declared, for example trying to use a variable of call a function that doesn't exist. It's possible that the symbol is spelt incorrectly, or that the capitalization doesn't match the definition (*morpho* symbols are case-sensitive). A common problem is to try to assign to a variable that hasn't yet been declared: a = 5 To fix this, prefix with `var`: var a = 5 ## MtrxIncmptbl [tagmtrxincmptbl]: # (mtrxincmptbl) This error occurs when an arithmetic operation is performed on two 'incompatible' matrices. For example, two matrices must have the same dimensions, i.e. the same number of rows and columns, to be added or subtracted, var a = Matrix([[1,2],[3,4]]) var b = Matrix([[1]]) print a+b // generates a `MtrxIncmptbl` error. Or to be multiplied together, the number of columns of the left hand matrix must equal the number of rows of the right hand matrix. var a = Matrix([[1,2],[3,4]]) var b = Matrix([1,2]) print a*b // ok print b*a // generates a `MtrxIncmptbl` error. ================================================ FILE: help/field.md ================================================ [comment]: # (Field class help) [version]: # (0.5) # Field [tagfield]: # (Field) Fields are used to store information, including numbers or matrices, associated with the elements of a `Mesh` object. You can create a `Field` by applying a function to each of the vertices, var f = Field(mesh, fn (x, y, z) x+y+z) or by supplying a single constant value, var f = Field(mesh, Matrix([1,0,0])) Fields can then be added and subtracted using the `+` and `-` operators. To access elements of a `Field`, use index notation: print f[grade, element, index] where * `grade` is the grade to select * `element` is the element id * `index` is the element index As a shorthand, it's possible to omit the grade and index; these are then both assumed to be `0`: print f[2] [showsubtopics]: # (subtopics) ## Mesh [tagmesh]: # (mesh) Returns the Mesh associated with a Field object: var f.mesh() ## Grade [taggrade]: # (grade) To create fields that include grades other than just vertices, use the `grade` option to `Field`. This can be just a grade index, var f = Field(mesh, 0, grade=2) which creates an empty field with `0` for each of the facets of the mesh `mesh`. You can store more than one item per element by supplying a list to the `grade` option indicating how many items you want to store on each grade. For example, var f = Field(mesh, 1.0, grade=[0,2,1]) stores two numbers on the line (grade 1) elements and one number on the facets (grade 2) elements. Each number in the field is initialized to the value `1.0`. ## Shape [tagshape]: # (shape) The `shape` method returns a list indicating the number of items stored on each element of a particular grade. This has the same format as the list you supply to the `grade` option of the `Field` constructor. For example, [1,0,2] would indicate one item stored on each vertex and two items stored on each facet. ## Op [tagop]: # (op) The `op` method applies a function to every item stored in a `Field`, returning the result as elements of a new `Field` object. For example, f.op(fn (x) x.norm()) calls the `norm` method on each element stored in `f`. Additional `Field` objects may be supplied as extra arguments to `op`. These must have the same shape (the same number of items stored on each grade). The function supplied to `op` will now be called with the corresponding element from each field as arguments. For example, f.op(fn (x,y) x.inner(y), g) calculates an elementwise inner product between the elements of Fields `f` and `g`. ================================================ FILE: help/file.md ================================================ [comment]: # (File class help) [version]: # (0.5) # File [tagfile]: # (File) The `File` class provides the capability to read from and write to files, or to obtain the contents of a file in convenient formats. To open a file, create a File object with the filename as the argument var f = File("myfile.txt") which opens `"myfile.txt"` for *reading*. To open a file for writing or appending, you need to provide a mode selector var g = File("myfile.txt", "write") or var g = File("myfile.txt", "append") Once the file is open, you can then read or write by calling appropriate methods: f.lines() // reads the contents of the file into an array of lines. f.readline() // reads a single line f.readchar() // reads a single character. f.write(string) // writes the arguments to the file. After you're done with the file, close it with f.close() [show]: # (subtopics) ## lines [taglines]: # (lines) Returns the contents of a file as an array of strings; each element corresponds to a single line. Read in the contents of a file and print line by line: var f = File("input.txt") var s = f.lines() for (i in s) print i f.close() ## readline [tagreadline]: # (readline) Reads a single line from a file; returns the result as a string. Read in the contents of a file and print each line: var f = File("input.txt") while (!f.eof()) { print f.readline() } f.close() ## readchar [tagreadchar]: # (readchar) Reads a single character from a file; returns the result as a string. ## write [tagwrite]: # (write) Writes to a file. Write the contents of a list to a file: var f = File("output.txt", "w") for (k, i in list) f.write("${i}: ${k}") f.close() ## close [tagclose]: # (close) Closes an open file. ## eof [tageof]: # (eof) Returns true if at the end of the file; false otherwise # Folder [tagfolder]: # (Folder) The `Folder` class enables you to find whether a filepath refers to a folder, and find the contents of that folder. Find whether a path refers to a folder: print Folder.isfolder("path/folder") Get a list of a folder's contents: print Folder.contents("path/folder") ================================================ FILE: help/functionals.md ================================================ [comment]: # (Functionals help) [version]: # (0.5) # Functionals [tagfunctionals]: # (functionals) A number of `functionals` are available in Morpho. Each of these represents an integral over some `Mesh` and `Field` objects (on a particular `Selection`) and are used to define energies and constraints in an `OptimizationProblem` provided by the `optimize` module. Many functionals are built in. Additional functionals are available by importing the `functionals` module: import functionals Functionals provide a number of standard methods: * `total`(mesh) - returns the value of the integral with a provided mesh, selection and fields * `integrand`(mesh) - returns the contribution to the integral from each element * `gradient`(mesh) - returns the gradient of the functional with respect to vertex motions. * `fieldgradient`(mesh, field) - returns the gradient of the functional with respect to components of the field Each of these may be called with a mesh, a field and a selection. [showsubtopics]: # (subtopics) ## Length [taglength]: # (length) A `Length` functional calculates the length of a line element in a mesh. Evaluate the length of a circular loop: import constants import meshtools var m = LineMesh(fn (t) [cos(t), sin(t), 0], 0...2*Pi:Pi/20, closed=true) var le = Length() print le.total(m) See the `Functionals` entry for general information about functionals. ## AreaEnclosed [tagareaenclosed]: # (areaenclosed) An `AreaEnclosed` functional calculates the area enclosed by a loop of line elements. var la = AreaEnclosed() Evaluate the area enclosed of a circular loop: import constants import meshtools var m = LineMesh(fn (t) [cos(t), sin(t), 0], 0...2*Pi:Pi/20, closed=true) var larea = AreaEnclosed() print larea.total(m) See the `Functionals` entry for general information about functionals. ## Area [tagarea]: # (area) An `Area` functional calculates the area of the area elements in a mesh: var la = Area() print la.total(mesh) See the `Functionals` entry for general information about functionals. ## VolumeEnclosed [tagvolumeenclosed]: # (volumeenclosed) A `VolumeEnclosed` functional is used to calculate the volume enclosed by a surface. Note that this estimate may become inaccurate for highly deformed surfaces. var lv = VolumeEnclosed() print lv.total(mesh) See the `Functionals` entry for general information about functionals. ## Volume [tagvolume]: # (volume) A `Volume` functional calculates the volume of volume elements. var lv = Volume() See the `Functionals` entry for general information about functionals. ## ScalarPotential [tagscalarpotential]: # (scalarpotential) The `ScalarPotential` functional is applied to point elements. var ls = ScalarPotential(potential) You must supply a function (which may be anonymous) that returns the potential. You may optionally provide a function that returns the gradient as well at initialization: var ls = ScalarPotential(potential, gradient) This functional is often used to constrain the mesh to the level set of a function. For example, to confine a set of points to a sphere: import optimize fn sphere(x,y,z) { return x^2+y^2+z^2-1 } fn grad(x,y,z) { return Matrix([2*x, 2*y, 2*z]) } var lsph = ScalarPotential(sphere, grad) problem.addlocalconstraint(lsph) See the thomson example for use of this technique. See the `Functionals` entry for general information about functionals. ## LinearElasticity [taglinearelasticity]: # (linearelasticity) The `LinearElasticity` functional measures the linear elastic energy away from a reference state. You must initialize with a reference mesh: var le = LinearElasticity(mref) Manually set the poisson's ratio and grade to operate on: le.poissonratio = 0.2 le.grade = 2 The energy for each element in the Mesh is computed as follows: First the Gram matrix `S` is computed for the element as well as the Gram matrix `F` for the corresponding element in the reference Mesh. These quantities are used to compute the Cauchy-Green strain tensor: C = (F S^-1 - I)/2 The energy density is then: mu*Tr(C^2) + 1/2*lambda*Tr(C)^2 where mu and lambda are the Lamé parameters. The total energy is found by multiplying the energy density by the volume or area of the element as appropriate. See the `Functionals` entry for general information about functionals. ## EquiElement [tagequielement]: # (equielement) The `EquiElement` functional measures the discrepency between the size of elements adjacent to each vertex. It can be used to equalize elements for regularization purposes. See the `Functionals` entry for general information about functionals. ## LineCurvatureSq [taglinecurvaturesq]: # (linecurvaturesq) The `LineCurvatureSq` functional measures the integrated curvature squared of a sequence of line elements. Compute the total squared curvature of a loop: import constants import meshtools var m = LineMesh(fn (t) [cos(t), sin(t), 0], 0...2*Pi:Pi/20, closed=true) var larea = LineCurvatureSq() print larea.total(m) See the `Functionals` entry for general information about functionals. ## LineTorsionSq [taglinetorsionsq]: # (linetorsionsq) The `LineTorsionSq` functional measures the integrated torsion squared of a sequence of line elements. Compute the total squared torsion of a helix: import constants import meshtools var m = LineMesh(fn (t) [cos(t), sin(t), t], 0...2*Pi:Pi/20, closed=true) var larea = LineTorsionSq() print larea.total(m) See the `Functionals` entry for general information about functionals. ## MeanCurvatureSq [tagmeancurvsq]: # (meancurvaturesq) The `MeanCurvatureSq` functional computes the integrated mean curvature over a surface. Compute the integrated mean squared curvature of the unit sphere: import implicitmesh var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) var mesh = impl.build(stepsize=0.25) var lmsq = MeanCurvatureSq() print lmsq.total(mesh) See the `Functionals` entry for general information about functionals. ## GaussCurvature [taggausscurv]: # (gausscurvature) The `GaussCurvature` computes the integrated gaussian curvature over a surface. Note that for surfaces with a boundary, the integrand is correct only for the interior points. To compute the geodesic curvature of the boundary in that case, you can set the optional flag `geodesic` to `true` and compute the total on the boundary selection. Here is an example for a 2D disk mesh. var mesh = Mesh("disk.mesh") mesh.addgrade(1) var whole = Selection(mesh, fn(x,y,z) true) var bnd = Selection(mesh, boundary=true) var interior = whole.difference(bnd) var gauss = GaussCurvature() print gauss.total(mesh, selection=interior) // expect: 0 gauss.geodesic = true print gauss.total(mesh, selection=bnd) // expect: 2*Pi See the `Functionals` entry for general information about functionals. ## GradSq [taggradsq]: # (gradsq) The `GradSq` functional measures the integral of the gradient squared of a field. The field can be a scalar, vector or matrix function. Initialize with the required field: var le=GradSq(phi) Compute the integral of GradSq(phi): print le.total(mesh) See the `Functionals` entry for general information about functionals. ## Nematic [tagnematic]: # (nematic) The `Nematic` functional measures the elastic energy of a nematic liquid crystal. var lf=Nematic(nn) There are a number of optional parameters that can be used to set the splay, twist and bend constants: var lf=Nematic(nn, ksplay=1, ktwist=0.5, kbend=1.5, pitch=0.1) These are stored as properties of the object and can be retrieved as follows: print lf.ksplay See the `Functionals` entry for general information about functionals. ## NematicElectric [tagnematic]: # (nematic) The `NematicElectric` functional measures the integral of a nematic and electric coupling term integral((n.E)^2) where the electric field E may be computed from a scalar potential or supplied as a vector. Initialize with a director field `nn` and a scalar potential `phi`: var lne = NematicElectric(nn, phi) See the `Functionals` entry for general information about functionals. ## NormSq [tagnormsq]: # (normsq) The `NormSq` functional measures the elementwise L2 norm squared of a field. See the `Functionals` entry for general information about functionals. ## LineIntegral [taglineintegral]: # (lineintegral) The `LineIntegral` functional computes the line integral of a function. You supply an integrand function that takes a position matrix as an argument. To compute `integral(x^2+y^2)` over a line element: var la=LineIntegral(fn (x) x[0]^2+x[1]^2) The function `tangent()` returns a unit vector tangent to the current element: var la=LineIntegral(fn (x) x.inner(tangent())) You can also integrate functions that involve fields: var la=LineIntegral(fn (x, n) n.inner(tangent()), n) where `n` is a vector field. The local interpolated value of this field is passed to your integrand function. More than one field can be used; they are passed as arguments to the integrand function in the order you supply them to `LineIntegral`. The gradient of a field is available within an integrand function using the `gradient()` function. See the `Functionals` entry for general information about functionals. ## AreaIntegral [tagareaintegral]: # (areaintegral) The `AreaIntegral` functional computes the area integral of a function. You supply an integrand function that takes a position matrix as an argument. To compute integral(x*y) over an area element: var la=AreaIntegral(fn (x) x[0]*x[1]) You can also integrate functions that involve fields: var la=AreaIntegral(fn (x, phi) phi^2, phi) The local facet normal can be accessed in an integrand using the `normal()` function: var la=AreaIntegral(fn (x) x.inner(normal())^2) More than one field can be used; they are passed as arguments to the integrand function in the order you supply them to `AreaIntegral`. The gradient of a field is available within an integrand function using the `gradient()` function. See the `Functionals` entry for general information about functionals. ## VolumeIntegral [tagvolumeintegral]: # (volumeintegral) The `VolumeIntegral` functional computes the volume integral of a function. You supply an integrand function that takes a position matrix as an argument. To compute integral(x*y*z) over an volume element: var la=VolumeIntegral(fn (x) x[0]*x[1]*x[2]) You can also integrate functions that involve fields: var la=VolumeIntegral(fn (x, phi) phi^2, phi) More than one field can be used; they are passed as arguments to the integrand function in the order you supply them to `VolumeIntegral`. The gradient of a field is available within an integrand function using the `gradient()` function. See the `Functionals` entry for general information about functionals. ## Hydrogel [taghydrogel]: # (hydrogel) The `Hydrogel` functional computes the Flory-Rehner energy over an element: (a*phi*log(phi) + b*(1-phi)+log(1-phi) + c*phi*(1-phi))*V + d*(log(phiref/phi)/3 - (phiref/phi)^(2/3) + 1)*V0 The first three terms come from the Flory-Huggins mixing energy, whereas the fourth term proportional to d comes from the Flory-Rehner elastic energy. The value of phi is calculated from a reference mesh that you provide on initializing the Functional: var lfh = Hydrogel(mref) Here, a, b, c, d and phiref are parameters you can supply (they are `nil` by default), V is the current volume and V0 is the reference volume of a given element. You also need to supply the initial value of phi, labeled as phi0, which is assumed to be the same for all the elements. Manually set the coefficients and grade to operate on: lfh.a = 1; lfh.b = 1; lfh.c = 1; lfh.d = 1; lfh.grade = 2, lfh.phi0 = 0.5, lfh.phiref = 0.1 See the `Functionals` entry for general information about functionals. ================================================ FILE: help/functions.md ================================================ [comment]: # (Morpho functions help file) [version]: # (0.5) [toplevel]: # # Functions [tagfn]: # (fn) [tagfun]: # (fun) [tagfunction]: # (function) A function in morpho is defined with the `fn` keyword, followed by the function's name, a list of parameters enclosed in parentheses, and the body of the function in curly braces. This example computes the square of a number: fn sqr(x) { return x*x } Once a function has been defined you can evaluate it like any other morpho function. print sqr(2) Functions can accept optional parameters: fn fun(x, quiet=true ) { if (!quiet) print "Loud!" } Morpho functions can also be defined to restrict the type of accepted parameters: fn f(List x) { print "A list!" } Multiple implementations can be defined that accept different numbers of parameters and parameter types: fn f() { print "No parameters!" } fn f(String x) { print "A string!" } fn f(Tuple x) { print "A tuple!" } The correct implementation is then selected at runtime: f("Hello World!") // expect: A string! [show]: # (subtopics) ## Variadic [tagvariadic]: # (variadic) As well as regular parameters, functions can also be defined with *variadic* parameters: fn func(x, ...v) { for (a in v) print a } This function can then be called with 1 or more arguments: func(1) func(1, 2) func(1, 2, 3) // All valid! The variadic parameter `v` captures all the extra arguments supplied. Functions cannot be defined with more than one variadic parameter. You can mix regular, variadic and optional parameters. Variadic parameters come before optional parameters: fn func(x, ...v, optional=true) { // } ## Optional [tagoptional]: # (optional) Functions can also be defined with *optional* parameters: fn func(a=1) { print a } Each optional parameter must be defined with a default value (here `1`). The function can then be called either with or without the optional parameter: func() // a == 1 due to default value func(a=2) // a == 2 supplied by the user Note that optional parameters may not be typed. ## Return [tagreturn]: # (return) The `return` keyword is used to exit from a function, optionally passing a given value back to the caller. `return` can be used anywhere within a function. The below example calculates the `n` th Fibonacci number, fn fib(n) { if (n<2) return n return fib(n-1) + fib(n-2) } by returning early if `n<2`, otherwise returning the result by recursively calling itself. ## Signature [tagsignature]: # (signature) The *signature* of a function is a list of the types of arguments in its definition: fn f(x) {} // Accepts one parameter of any type fn f(x,y) {} // Accepts two parameters of any type fn f(List x, y) {} // The first parameter must be a list fn f(List x, List y) {} // Both parameters must be lists While you can define multiple implementations of a function that accept different parameter types, you can only define one implementation with a unique signature. Note that optional and variadic parameters are not typed. # Multiple dispatch [tagmultiple]: # (multiple) [tagdispatch]: # (dispatch) [tagmultipledispatch]: # (multipledispatch) Morpho supports *multiple dispatch*, whereby you can define several implementations of a function that accept different types: fn f(List x) { return "Accepts a list" } fn f(String x) { return "Accepts a string" } Morpho chooses the appropriate implementation at runtime: f([1,2,3]) // Selects the List implementation Any classes you define can be used as types: class A {} class B is A {} class C is B {} fn f(A x) { return "A" } fn f(B x) { return "B" } Morpho selects the *closest match*, so that: print f(A()) // expect: A print f(B()) // expect: B print f(C()) // expect: B Class `C` inherits from both `B` and `A`, but because it directly inherits from `B`, that's the closer match. # Closures [tagclosures]: # (closures) [tagclosure]: # (closure) Functions in morpho can form *closures*, i.e. they can enclose information from their local context. In this example, fn foo(a) { fn g() { return a } return g } the function `foo` returns a function that captures the value of `a`. If we now try calling `foo` and then calling the returned functions, var p=foo(1), q=foo(2) print p() // expect: 1 print q() // expect: 2 we can see that `p` and `q` seem to contain different copies of `g` that encapsulate the value that `foo` was called with. Morpho hints that a returned function is actually a closure by displaying it with double brackets: print foo(1) // expect: <> ================================================ FILE: help/graphics.md ================================================ [comment]: # (Graphics module help) [version]: # (0.5) # Graphics [taggraphics]: # (graphics) The `graphics` module provides a number of classes to provide simple visualization capabilities. To use it, you first need to import the module: import graphics The `Graphics` class acts as an abstract container for graphical information; to actually launch the display see the `Show` class. You can create an empty scene like this, var g = Graphics() Additional elements can be added using the `display` method. g.display(element) Morpho provides the following fundamental Graphical element classes: TriangleComplex You can also use functions like `Arrow`, `Tube` and `Cylinder` to create these elements conveniently. To combine graphics objects, use the add operator: var g1 = Graphics(), g2 = Graphics() // ... Show(g1+g2) [show]: # (subtopics) ## Show [tagshow]: # (Show) `Show` is used to launch an interactive graphical display using the external `morphoview` application. `Show` takes a `Graphics` object as an argument: var g = Graphics() Show(g) ## TriangleComplex [tagTriangleComplex]: # (TriangleComplex) A `TriangleComplex` is a graphical element that can be used as part of a graphical display. It consists of a list of vertices and a connectivity matrix that selects which vertices are used in each triangle. To create one, call the constructor with the following arguments: TriangleComplex(position, normals, colors, connectivity) * `position` is a `Matrix` containing vertex positions as *columns*. * `normals` is a `Matrix` with a normal for each vertex. * `colors` is the color of the object. * `connectivity` is a `Sparse` matrix where each column represents a triangle and rows correspond to vertices. You can also provide optional arguments: * `transmit` sets the transparency of the object. This parameter is only used by the povray module as of now. Default is 0. * `filter` sets the transparency of the object using a filter effect. This parameter is only used by the povray module as of now. Default is 0. For the difference between `transmit` and `filter`, checkout the [POVRay documentation](http://xahlee.info/3d/povray-glassy.html). Add to a `Graphics` object using the `display` method. ## Arrow [tagArrow]: # (Arrow) The `Arrow` function creates an arrow. It takes two arguments: arrow(start, end) * `start` and `end` are the two vertices. The arrow points `start` -> `end`. You can also provide optional arguments: * `aspectratio` controls the width of the arrow relative to its length * `n` is an integer that controls the quality of the display. Higher `n` leads to a rounder arrow. * `color` is the color of the arrow. This can be a list of RGB values or a `Color` object * `transmit` sets the transparency of the arrow. This parameter is only used by the povray module as of now. Default is 0. * `filter` sets the transparency of the arrow using a filter effect. This parameter is only used by the povray module as of now. Default is 0. For the difference between `transmit` and `filter`, checkout the [POVRay documentation](http://xahlee.info/3d/povray-glassy.html). Display an arrow: var g = Graphics([]) g.display(Arrow([-1/2,-1/2,-1/2], [1/2,1/2,1/2], aspectratio=0.05, n=10)) Show(g) ## Cylinder [tagCylinder]: # (Cylinder) The `Cylinder` function creates a cylinder. It takes two required arguments: cylinder(start, end) * `start` and `end` are the two vertices. You can also provide optional arguments: * `aspectratio` controls the width of the cylinder relative to its length. * `n` is an integer that controls the quality of the display. Higher `n` leads to a rounder cylinder. * `color` is the color of the cylinder. This can be a list of RGB values or a `Color` object. * `transmit` sets the transparency of the cylinder. This parameter is only used by the povray module as of now. Default is 0. * `filter` sets the transparency of the cylinder using a filter effect. This parameter is only used by the povray module as of now. Default is 0. For the difference between `transmit` and `filter`, checkout the [POVRay documentation](http://xahlee.info/3d/povray-glassy.html). Display an cylinder: var g = Graphics() g.display(Cylinder([-1/2,-1/2,-1/2], [1/2,1/2,1/2], aspectratio=0.1, n=10)) Show(g) ## Tube [tagTube]: # (Tube) The `Tube` function connects a sequence of points to form a tube. Tube(points, radius) * `points` is a list of points; this can be a list of lists or a `Matrix` with the positions as columns. * `radius` is the radius of the tube. You can also provide optional arguments: * `n` is an integer that controls the quality of the display. Higher `n` leads to a rounder tube. * `color` is the color of the tube. This can be a list of RGB values or a `Color` object. * `closed` is a `bool` that indicates whether the tube should be closed to form a loop. * `transmit` sets the transparency of the tube. This parameter is only used by the povray module as of now. Default is 0. * `filter` sets the transparency of the tube using a filter effect. This parameter is only used by the povray module as of now. Default is 0. For the difference between `transmit` and `filter`, checkout the [POVRay documentation](http://xahlee.info/3d/povray-glassy.html). Draw a square: var a = Tube([[-1/2,-1/2,0],[1/2,-1/2,0],[1/2,1/2,0],[-1/2,1/2,0]], 0.1, closed=true) var g = Graphics() g.display(a) ## Sphere [tagSphere]: # (Sphere) The `Sphere` function creates a sphere. Sphere(center, radius) * `center` is the position of the center of the sphere; this can be a list or column `Matrix`. * `radius` is the radius of the sphere You can also provide optional arguments: * `color` is the color of the sphere. This can be a list of RGB values or a `Color` object. * `transmit` sets the transparency of the sphere. This parameter is only used by the povray module as of now. Default is 0. * `filter` sets the transparency of the sphere using a filter effect. This parameter is only used by the povray module as of now. Default is 0. For the difference between `transmit` and `filter`, checkout the [POVRay documentation](http://xahlee.info/3d/povray-glassy.html). Draw some randomly sized spheres: var g = Graphics() for (i in 0...10) { g.display(Sphere([random()-1/2, random()-1/2, random()-1/2], 0.1*(1+random()), color=Gray(random()))) } Show(g) ## Text [tagText]: # (Text) A `Text` object is used to display text. Text(text, position) * `text` is the text to display as a string. * `position` is the position at which to display the text. You can also provide optional arguments: * `color` is the color of the text. This should be a `Color` object. * `dirn` is the direction along which the text is drawn. This should be a `List` or a `Matrix`. * `size` is the font size to use * `vertical` is the vertical direction for the text * `font` is the `Font` object to use. Draw several pieces of text around the y axis: var g = Graphics() for (phi in 0..Pi:Pi/8) { g.display(Text("Hello World", [0,0,0], size=72, dirn=[0,1,0], vertical=[cos(phi),0,sin(phi)])) } Show(g) ================================================ FILE: help/help.md ================================================ [comment]: # (Morpho language help file) [version]: # (0.5) # Help [tag]: # (help) Morpho provides an online help system. To get help about a topic called `topicname`, type help topicname A list of available topics is provided below and includes language keywords like `class`, `fn` and `for`, built in classes like `Matrix` and `File` or information about functions like `exp` and `random`. Some topics have additional subtopics: to access these type help topic subtopic For example, to get help on a method for a particular class, you could type help Classname.methodname Note that `help` ignores all punctuation. You can also use `?` as a shorthand synonym for `help` ? topic A useful feature is that, if an error occurs, simply type `help` to get more information about the error. [showtopics]: # (topics) # Quit [tagquit]: # (quit) The `quit` CLI command quits `morpho` run in interactive mode and returns to the shell. ================================================ FILE: help/implicitmesh.md ================================================ [comment]: # (Implicitmesh module help) [version]: # (0.5) # ImplicitMesh [tagimplicitmesh]: # (implicitmesh) The `implicitmesh` module allows you to build meshes from implicit functions. For example, the unit sphere could be specified using the function `x^2+y^2+z^2-1 == 0`. To use the module, first import it: import implicitmesh To create a sphere, first create an ImplicitMeshBuilder object with the implict function you'd like to use: var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) You can use an existing function (or method) as well as an anonymous function as above. Then build the mesh, var mesh = impl.build(stepsize=0.25) The `build` method takes a number of optional arguments: * `start` - the starting point. If not provided, the value Matrix([1,1,1]) is used. * `stepsize` - approximate lengthscale to use. * `maxiterations` - maximum number of iterations to use. If this limit is exceeded, a partially built mesh will be returned. ================================================ FILE: help/index.rst ================================================ Morpho ====== .. toctree:: :caption: Language :maxdepth: 1 syntax values variables controlflow functions classes modules help builtin .. toctree:: :caption: Data Types :maxdepth: 1 array complex dictionary list matrix range sparse string tuple .. toctree:: :caption: Computational Geometry :maxdepth: 1 field functionals mesh selection .. toctree:: :caption: I/O :maxdepth: 1 file system json .. toctree:: :caption: Modules :maxdepth: 1 color constants delaunay graphics implicitmesh kdtree meshgen meshslice meshtools optimize plot povray vtk .. toctree:: :caption: Error messages :maxdepth: 1 errors ================================================ FILE: help/json.md ================================================ [comment]: # (Morpho json help file) [version]: # (0.5) [toplevel]: # # JSON [tagjson]: # (json) The `JSON` class provides import and export functionality for the JSON (JavaScript Object Notation) interchange file format as defined by IETF RFC 7159. To parse a string that contains JSON, use the `parse` method: var a = JSON.parse("[1,2,3,4]") print a // expect: [ 1, 2, 3, 4 ] Elements in the JSON string are converted to equivalent morpho values. To convert basic data types to JSON, use the `tostring` method: var b = JSON.tostring([1,2,3]) The exporter supports `nil`, boolean values `true` and `false`, numbers, `String`s as well as `List` and `Dictionary` objects that may contain any of the supported types. ================================================ FILE: help/kdtree.md ================================================ [comment]: # (KDTree module help) [version]: # (0.5) # KDTree [tagkdtree]: # (kdtree) The `kdtree` module implements a k-dimensional tree, a space partitioning data structure that can be used to accelerate computational geometry calculations. To use the module, first import it: import kdtree To create a tree from a list of points: var pts = [] for (i in 0...100) pts.append(Matrix([random(), random(), random()])) var tree=KDTree(pts) Add further points: tree.insert(Matrix([0,0,0])) Test whether a given point is present in the tree: tree.ismember(Matrix([1,0,0])) Find all points within a given bounding box: var pts = tree.search([[-1,1], [-1,1], [-1,1]]) for (x in pts) print x.location Find the nearest point to a given point: var pt = tree.nearest(Matrix([0.1, 0.1, 0.5])) print pt.location [showsubtopics]: # (subtopics) ## Insert [taginsert]: # (insert) Inserts a new point into a k-d tree. Returns a KDTreeNode object. var node = tree.insert(Matrix([0,0,0])) Note that, for performance reasons, if the set of points is known ahead of time, it is generally better to build the tree using the constructor function KDTree rather than one-by-one with insert. ## Ismember [tagismember]: # (ismember) Checks if a point is a member of a k-d tree. Returns `true` or `false`. print tree.ismember(Matrix([0,0,0])) ## Nearest [tagnearest]: # (nearest) Finds the point in a k-d tree nearest to a point of interest. Returns a KDTreeNode object. var pt = tree.nearest(Matrix([0.1, 0.1, 0.5])) To get the location of this nearest point, access the location property: print pt.location ## Search [tagsearch]: # (search) Finds all points in a k-d tree that lie within a cuboidal bounding box. Returns a list of KDTreeNode objects. Find and display all points that lie in a cuboid 0<=x<=1, 0<=y<=2, 1<=z<=2: var result = tree.search([[0,1], [0,2], [1,2]]) for (x in result) print x.location ## KDTreeNode [tagkdtreenode]: # (kdtreenode) An object corresponding to a single node in a k-d tree. To get the location of the node, access the `location` property: print node.location ================================================ FILE: help/list.md ================================================ [comment]: # (List class help) [version]: # (0.5) # List [taglist]: # (List) Lists are collection objects that contain a sequence of values each associated with an integer index. Create a list like this: var list = [1, 2, 3] Look up values using index notation: list[0] Indexing can also be done with slices: list[0..2] list[[0,1,3]] You can change list entries like this: list[0] = "Hello" Create an empty list: var list = [] Loop over elements of a list: for (i in list) print i [showsubtopics]: # (subtopics) ## Append [tagappend]: # (Append) Adds an element to the end of a list: var list = [] list.append("Foo") ## Insert [taginsert]: # (Insert) Inserts an element into a list at a specified index: var list = [1,2,3] list.insert(1, "Foo") print list // prints [ 1, Foo, 2, 3 ] ## Pop [tagpop]: # (pop) Remove the last element from a list, returning the element removed: print list.pop() If an integer argument is supplied, returns and removes that element: var a = [1,2,3] print a.pop(1) // prints '2' print a // prints [ 1, 3 ] ## Sort [tagsort]: # (sort) Sorts the contents of a list into ascending order: list.sort() Note that this sorts the list "in place" (i.e. it modifies the order of the list on which it is invoked) and hence returns `nil`. You can provide your own function to use to compare values in the list list.sort(fn (a, b) a-b) This function should return a negative value if `ab` and `0` if `a` and `b` are equal. ## Order [tagorder]: # (order) Returns a list of indices that would, if used in order, would sort a list. For example var list = [2,3,1] print list.order() // expect: [2,0,1] would produce `[2,0,1]` ## Remove [tagremove]: # (remove) Remove any occurrences of a value from a list: var list = [1,2,3] list.remove(1) ## ismember [tagismember]: # (ismember) Tests if a value is a member of a list: var list = [1,2,3] print list.ismember(1) // expect: true ## Add [tagadd]: # (add) Join two lists together: var l1 = [1,2,3], l2 = [4, 5, 6] print l1+l2 // expect: [1,2,3,4,5,6] ## Tuples [tagtuples]: # (tuples) Generate all possible 2-tuples from a list: var t = [ 1, 2, 3].tuples(2) produces `[ [ 1, 1 ], [ 1, 2 ], [ 1, 3 ] ... ]`. ## Sets [tagsets]: # (sets) Generate all possible sets of order 2 from a list. var t = [ 1, 2, 3 ].sets(2) produces `[ [ 1, 2 ], [ 1, 3 ], [ 2, 3 ] ]`. Note that sets include only distinct elements from the list (no element is repeated) and ordering is unimportant, hence only one of `[ 1, 2 ]` and `[ 2, 1 ]` is returned. ================================================ FILE: help/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: help/matrix.md ================================================ [comment]: # (Matrix class help) [version]: # (0.5) # Matrix [tagmatrix]: # (Matrix) The Matrix class provides support for matrices. A matrix can be initialized with a given size, var a = Matrix(nrows,ncols) where all elements are initially set to zero. Alternatively, a matrix can be created from an array, var a = Matrix([[1,2], [3,4]]) or a Sparse matrix, var a = Sparse([[0,0,1],[1,1,1],[2,2,1]]) var b = Matrix(a) You can create a column vector like this, var v = Matrix([1,2]) Finally, you can create a Matrix by assembling other matrices like this, var a = Matrix([[0,1],[1,0]]) var b = Matrix([[a,0],[0,a]]) // produces a 4x4 matrix Once a matrix is created, you can use all the regular arithmetic operators with matrix operands, e.g. a+b a*b You can retrieved individual matrix entries with specified indices: print a[0,0] or create a submatrix using slices: print a[0..1,0..1] The division operator is used to solve a linear system, e.g. var a = Matrix([[1,2],[3,4]]) var b = Matrix([1,2]) print b/a yields the solution to the system a*x = b. [showsubtopics]: # (subtopics) ## Assign [tagassign]: # (Assign) Copies the contents of matrix B into matrix A: A.assign(B) The two matrices must have the same dimensions. ## Dimensions [tagdimensions]: # (Dimensions) Returns the dimensions of a matrix: var A = Matrix([1,2,3]) // Create a column matrix print A.dimensions() // Expect: [ 3, 1 ] ## Eigenvalues [tageigenvalues]: # (Eigenvalues) Returns a list of eigenvalues of a Matrix: var A = Matrix([[0,1],[1,0]]) print A.eigenvalues() // Expect: [1,-1] ## Eigensystem [tageigensystem]: # (Eigensystem) Returns the eigenvalues and eigenvectors of a Matrix: var A = Matrix([[0,1],[1,0]]) print A.eigensystem() Eigensystem returns a two element list: The first element is a List of eigenvalues. The second element is a Matrix containing the corresponding eigenvectors as its columns: print A.eigensystem()[0] // [ 1, -1 ] print A.eigensystem()[1] // [ 0.707107 -0.707107 ] // [ 0.707107 0.707107 ] ## Inner [taginner]: # (Inner) Computes the Frobenius inner product between two matrices: var prod = A.inner(B) ## Outer [tagouter]: # (Outer) Computes the outer produce between two vectors: var prod = A.outer(B) Note that `outer` always treats both vectors as column vectors. ## Inverse [taginverse]: # (Inverse) Returns the inverse of a matrix if it is invertible. Raises a `MtrxSnglr` error if the matrix is singular. E.g. var m = Matrix([[1,2],[3,4]]) var mi = m.inverse() yields the inverse of the matrix `m`, such that mi*m is the identity matrix. ## Norm [tagnorm]: # (Norm) Returns a matrix norm. By default the L2 norm is returned: var a = Matrix([1,2,3,4]) print a.norm() // Expect: sqrt(30) = 5.47723... You can select a different norm by supplying an argument: import constants print a.norm(1) // Expect: 10 (L1 norm is sum of absolute values) print a.norm(3) // Expect: 4.64159 (An unusual choice of norm) print a.norm(Inf) // Expect: 4 (Inf-norm corresponds to maximum absolute value) ## Reshape [tagreshape]: # (Reshape) Changes the dimensions of a matrix such that the total number of elements remains constant: var A = Matrix([[1,3],[2,4]]) A.reshape(1,4) // 1 row, 4 columns print A // Expect: [ 1, 2, 3, 4 ] Note that elements are stored in column major-order. ## Sum [tagsum]: # (Sum) Returns the sum of all entries in a matrix: var sum = A.sum() ## Transpose [tagtranspose]: # (Transpose) Returns the transpose of a matrix: var At = A.transpose() ## Trace [tagtrace]: # (Trace) Computes the trace (the sum of the diagonal elements) of a square matrix: var tr = A.trace() ## Roll [tagroll]: # (Roll) Rotates values in a Matrix about a given axis by a given shift: var r = A.roll(shift, axis) Elements that roll beyond the last position are re-introduced at the first. ## IdentityMatrix [tagidentitymatrix]: # (IdentityMatrix) Constructs an identity matrix of a specified size: var a = IdentityMatrix(size) ================================================ FILE: help/mesh.md ================================================ [comment]: # (Mesh class help) [version]: # (0.5) # Mesh [tagmesh]: # (Mesh) The `Mesh` class provides support for meshes. Meshes may consist of different kinds of element, including vertices, line elements, facets or area elements, tetrahedra or volume elements. To create a mesh, you can import it from a file: var m = Mesh("sphere.mesh") or use one of the functions available in `meshtools` or `implicitmesh` packages. Each type of element is referred to as belonging to a different `grade`. Point-like elements (vertices) are *grade 0*; line-like elements (edges) are *grade 1*; area-like elements (facets; triangles) are *grade 2* etc. The `plot` package includes functions to visualize meshes. [showsubtopics]: # (showsubtopics) ## Save [tagsave]: # (Save) Saves a mesh as a .mesh file. m.save("new.mesh") ## Vertexposition [tagvertexposition]: # (vertexposition) Retrieves the position of a vertex given an id: print m.vertexposition(id) ## Setvertexposition [tagsetvertexposition]: # (setvertexposition) Sets the position of a vertex given an id and a position vector: print m.setvertexposition(1, Matrix([0,0,0])) ## Addgrade [tagaddgrade]: # (addgrade) Adds a new grade to a mesh. This is commonly used when, for example, a mesh file includes facets but not edges. To add the missing edges: m.addgrade(1) ## Addsymmetry [tagaddsymmetry]: # (addsymmetry) Adds a symmetry to a mesh. Experimental in version 0.5. ## Maxgrade [tagmaxgrade]: # (maxgrade) Returns the highest grade element present: print m.maxgrade() ## Count [tagcount]: # (count) Counts the number of elements. If no argument is provided, returns the number of vertices. Otherwise, returns the number of elements present of a given grade: print m.count(2) // Returns the number of area-like elements. ================================================ FILE: help/meshgen.md ================================================ [comment]: # (Meshgen module help) [version]: # (0.5) # Meshgen [tagmeshgen]: # (meshgen) The `meshgen` module is used to create `Mesh` objects corresponding to a specified domain. It provides the `MeshGen` class to perform the meshing, which are created with the following arguments: MeshGen(domain, boundingbox) Domains are specified by a scalar function that is positive in the region to be meshed and locally smooth. For example, to mesh the unit disk: var dom = fn (x) -(x[0]^2+x[1]^2-1) A `MeshGen` object is then created and then used to build the `Mesh` like this: var mg = MeshGen(dom, [-1..1:0.2, -1..1:0.2]) var m = mg.build() A bounding box for the mesh must be specified as a `List` of `Range` objects, one for each dimension. The increment on each `Range` gives an approximate scale for the size of elements generated. To facilitate convenient creation of domains, a `Domain` class is provided that provides set operations `union`, `intersection` and `difference`. `MeshGen` accepts a number of optional arguments: * `weight` A scalar weight function that controls mesh density. * `quiet` Set to `true` to suppress `MeshGen` output. * `method` a list of options that controls the method used. Some method choices that are available include: * `"FixedStepSize"` Use a fixed step size in optimization. * `"StartGrid"` Start from a regular grid of points (the default). * `"StartRandom"` Start from a randomly generated collection of points. There are also a number of properties of a `MeshGen` object that can be set prior to calling `build` to control the operation of the mesh generation: * `stepsize`, `steplimit` Stepsize used internally by the `Optimizer` * `fscale` an internal "pressure" * `ttol` how far the vertices are allowed to move before retriangulation * `etol` energy tolerance for optimization problem * `maxiterations` Maximum number of iterations of minimization + retriangulation (default is 100) `MeshGen` picks default values that cover a reasonable range of uses. [showsubtopics]: # (subtopics) ## Domain [tagdomain]: # (domain) The `Domain` class is used to conveniently build a domain by composing simpler elements. Create a `Domain` from a scalar function that is positive in the region of interest: var dom = Domain(fn (x) -(x[0]^2+x[1]^2-1)) You can pass it to `MeshGen` to specify the region to mesh: var mg = MeshGen(dom, [-1..1:0.2, -1..1:0.2]) You can combine `Domain` objects using set operations `union`, `intersection` and `difference`: var a = CircularDomain(Matrix([-0.5,0]), 1) var b = CircularDomain(Matrix([0.5,0]), 1) var c = CircularDomain(Matrix([0,0]), 0.3) var dom = a.union(b).difference(c) ## CircularDomain [tagcirculardomain]: # (circulardomain) Conveniently constructs a `Domain` object correspondiong to a disk. Requires the position of the center and a radius as arguments. Create a domain corresponding to the unit disk: var c = CircularDomain([0,0], 1) ## RectangularDomain [tagrectangulardomain]: # (rectangulardomain) Conveniently constructs a `Domain` object corresponding to a rectangle. Requires a list of ranges as arguments. Works in arbitrary dimensions Create a square `Domain`: var c = RectangularDomain([-1..1, -1..1]) ## HalfSpaceDomain [halfspacedomain]: # (halfspacedomain) Conveniently constructs a `Domain` object correspondiong to a half space defined by a plane at `x0` and a normal `n`: var hs = HalfSpaceDomain(x0, n) Note `n` is an "outward" normal, so points into the *excluded* region. Half space corresponding to the allowed region `x<0`: var hs = HalfSpaceDomain(Matrix([0,0,0]), Matrix([1,0,0])) Note that `HalfSpaceDomain`s cannot be meshed directly as they correspond to an infinite region. They are useful, however, for combining with other domains. Create half a disk by cutting a `HalfSpaceDomain` from a `CircularDomain`: var c = CircularDomain([0,0], 1) var hs = HalfSpaceDomain(Matrix([0,0]), Matrix([-1,0])) var dom = c.difference(hs) var mg = MeshGen(dom, [-1..1:0.2, -1..1:0.2], quiet=false) var m = mg.build() ## MshGnDim [mshgndim]: # (mshgndim) The `MeshGen` module currently supports 2 and 3 dimensional meshes. Higher dimensional meshing will be available in a future release; please contact the developer if you are interested in this functionality. ================================================ FILE: help/meshslice.md ================================================ [comment]: # (Meshslice module help) [version]: # (0.5) # Meshslice [tagmeshslice]: # (meshslice) The `meshslice` module is used to slice a `Mesh` object along a given plane, yielding a new `Mesh` object of lower dimensionality. You can also use `meshslice` to project `Field` objects onto the new mesh. To use the module, begin by importing it: import meshslice Then construct a `MeshSlicer` object, passing the mesh you want to slice in the constructor: var slice = MeshSlicer(mesh) You then perform a slice by calling the `slice` method, passing the plane you want to slice through. This method returns a new `Mesh` object comprising the slice. A plane is defined by a point that lies on the plane `pt` and a direction normal to the plan `dirn`: var slc = slice.slice(pt, dirn) Having performed a slice, you can then project any associated `Field` objects onto the sliced mesh by calling the `slicefield` method: var phi = Field(mesh, fn (x,y,z) x+y+z) var sphi = slice.slicefield(phi) The new field returned by `slicefield` lives on the sliced mesh. You can slice any number of fields. You can perform multiple slices with the same `MeshSlicer` simply by calling `slice` again with a different plane. ## SlcEmpty [tagslcempty]: # (slcempty) This error occurs if you try to use `slicefield` on a `MeshSlicer` without having performed a slice. For example: var slice = MeshSlicer(mesh) slice.slicefield(phi) // Throws SlcEmpty slice.slice([0,0,0],[1,0,0]) To fix, call `slice` before `slicefield`: var slice = MeshSlicer(mesh) slice.slice([0,0,0],[1,0,0]) slice.slicefield(phi) // Now slices correctly ================================================ FILE: help/meshtools.md ================================================ [comment]: # (Morpho meshtools help file) [version]: # (0.5) # Meshtools [tagmeshtools]: # (meshtools) The Meshtools package contains a number of functions and classes to assist with creating and manipulating meshes. [showsubtopics]: # (subtopics) ## AreaMesh [tagareamesh]: # (areamesh) This function creates a mesh composed of triangles from a parametric function. To use it: var m = AreaMesh(function, range1, range2, closed=boolean) where * `function` is a parametric function that has one parameter. It should return a list of coordinates or a column matrix corresponding to this parameter. * `range1` is the Range to use for the first parameter of the parametric function. * `range2` is the Range to use for the second parameter of the parametric function. * `closed` is an optional parameter indicating whether to create a closed loop or not. You can supply a list where each element indicates whether the relevant parameter is closed or not. To use `AreaMesh`, import the `meshtools` module: import meshtools Create a square: var m = AreaMesh(fn (u,v) [u, v, 0], 0..1:0.1, 0..1:0.1) Create a tube: var m = AreaMesh(fn (u, v) [v, cos(u), sin(u)], -Pi...Pi:Pi/4, -1..1:0.1, closed=[true, false]) Create a torus: var c=0.5, a=0.2 var m = AreaMesh(fn (u, v) [(c + a*cos(v))*cos(u), (c + a*cos(v))*sin(u), a*sin(v)], 0...2*Pi:Pi/16, 0...2*Pi:Pi/8, closed=true) ## LineMesh [taglinemesh]: # (linemesh) This function creates a mesh composed of line elements from a parametric function. To use it: var m = LineMesh(function, range, closed=boolean) where * `function` is a parametric function that has one parameter. It should return a list of coordinates or a column matrix corresponding to this parameter. * `range` is the Range to use for the parametric function. * `closed` is an optional parameter indicating whether to create a closed loop or not. To use `LineMesh`, import the `meshtools` module: import meshtools Create a circle: import constants var m = LineMesh(fn (t) [sin(t), cos(t), 0], 0...2*Pi:2*Pi/50, closed=true) ## PolyhedronMesh [tagpolyhedronmesh]: # (polyhedron) This function creates a mesh corresponding to a polyhedron. var m = PolyhedronMesh(vertices, faces) where `vertices` is a list of vertices and `faces` is a list of faces specified as a list of vertex indices. To use `PolyhedronMesh`, import the `meshtools` module: import meshtools Create a cube: var m = PolyhedronMesh([ [-0.5, -0.5, -0.5], [ 0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [ 0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [ 0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [ 0.5, 0.5, 0.5]], [ [0,1,3,2], [4,5,7,6], [0,1,5,4], [3,2,6,7], [0,2,6,4], [1,3,7,5] ]) *Note* that the vertices in each face list must be specified strictly in cyclic order. ## DelaunayMesh [tagdelaunaymesh]: # (delaunaymesh) The `DelaunayMesh` constructor function creates a `Mesh` object directly from a point cloud using the Delaunay triangulator. var pts = [] for (i in 0...100) pts.append(Matrix([random(), random()])) var m=DelaunayMesh(pts) Show(plotmesh(m)) You can control the output dimension of the mesh (e.g. to create a 2D mesh embedded in 3D space) using the optional `outputdim` property. var m = DelaunayMesh(pts, outputdim=3) ## Equiangulate [tagequiangulate]: # (equiangulate) Attempts to equiangulate a mesh, exchanging elements to improve their regularity. equiangulate(mesh) This function takes optional arguments: * `quiet`: Set to true to silence messages. * `fix`: Supply a `Selection` containing edges that should not be modified by equiangulation. *Note* this function modifies the mesh in place; it does not create a new mesh. ## ChangeMeshDimension [tagchangemeshdimension]: # (changemeshdimension) Changes the dimension in which a mesh is embedded. For example, you may have created a mesh in 2D that you now wish to use in 3D. To use: var new = ChangeMeshDimension(mesh, dim) where `mesh` is the mesh you wish to change, and `dim` is the new embedding dimension. ## MeshBuilder [tagmeshbuiler]: # (meshbuilder) The `MeshBuilder` class simplifies user creation of meshes. To use this class, begin by creating a `MeshBuilder` object: var build = MeshBuilder() You can then add vertices, edges, etc. one by one using `addvertex`, `addedge`, `addface` and `addelement`. Each of these returns an element id: var id1=build.addvertex(Matrix([0,0,0])) var id2=build.addvertex(Matrix([1,1,1])) build.addedge([id1, id2]) Once the mesh is ready, call the `build` method to construct the `Mesh`: var m = build.build() You can specify the dimension of the `Mesh` explicitly when initializing the `MeshBuilder`: var mb = MeshBuilder(dimension=2) or implicitly when adding the first vertex: var mb = MeshBuilder() mb.addvertex([0,1]) // A 2D mesh ## MshBldDimIncnstnt [tagmshblddimincnstnt]: # (mshblddimincnstnt) This error is produced if you try to add a vertex that is inconsistent with the mesh dimension, e.g. var mb = MeshBuilder(dimension=2) mb.addvertex([1,0,0]) // Throws an error! To fix this ensure all vertices have the correct dimension. ## MshBldDimUnknwn [tagmshblddimunknwn]: # (mshblddimunknwn) This error is produced if you try to add an element to a `MeshBuilder` object but haven't yet specified the dimension (at initialization) or by adding a vertex. var mb = MeshBuilder() mb.addedge([0,1]) // No vertices have been added To fix this add the vertices first. ## MeshRefiner [tagmeshrefiner]: # (meshrefiner) The `MeshRefiner` class is used to refine meshes, and to correct associated data structures that depend on the mesh. To prepare for refining, first create a `MeshRefiner` object either with a `Mesh`, var mr = MeshRefiner(mesh) or with a list of objects that can include a `Mesh` as well as `Field`s and `Selection`s. var mr = MeshRefiner([mesh, field, selection ... ]) To perform the refinement, call the `refine` method. You can refine all elements, var dict = mr.refine() or refine selected elements using a `Selection`, var dict = mr.refine(selection=select) The `refine` method returns a `Dictionary` that maps old objects to new, refined objects. Use this to update your data structures. var newmesh = dict[oldmesh] ## MeshPruner [tagmeshpruner]: # (meshpruner) The `MeshPruner` class is used to prune excessive detail from meshes (a process that's sometimes referred to as coarsening), and to correct associated data structures that depend on the mesh. First create a `MeshPruner` object either with a `Mesh`, var mp = MeshPruner(mesh) or with a list of objects that can include a `Mesh` as well as `Field`s and `Selection`s. var mp = MeshPruner([mesh, field, selection ... ]) To perform the coarsening, call the `prune` method with a `Selection`, var dict = mp.prune(select) The `prune` method returns a `Dictionary` that maps old objects to new, refined objects. Use this to update your data structures. var newmesh = dict[oldmesh] ## MeshMerge [tagmeshmerge]: # (meshmerge) [tagmerge]: # (meshmerge) The `MeshMerge` class is used to combine meshes into a single mesh, removing any duplicate elements. To use, create a `MeshMerge` object with a list of meshes to merge, var mrg = MeshMerge([m1, m2, m3, ... ]) and then call the `merge` method to return a combined mesh: var newmesh = mrg.merge() ================================================ FILE: help/modules.md ================================================ [comment]: # (Morpho modules help file) [version]: # (0.5) [toplevel]: # # Modules [tagmodules]: # (modules) Morpho is extensible and provides a convenient module system that works like standard libraries in other languages. Modules may define useful variables, functions and classes, and can be made available using the `import` keyword. For example, import color loads the `color` module that provides functionality related to color. You can create your own modules; they're just regular morpho files that are stored in a standard place. On UNIX platforms, this is `/usr/local/share/morpho/modules`. [showsubtopics]: # (subtopics) ## Import [tagimport]: # (import) [tagas]: # (as) Import provides access to the module system and including code from multiple source files. To import code from another file, use import with the filename: import "file.morpho" which immediately includes all the contents of `"file.morpho"`. Any classes, functions or variables defined in that file can now be used, which allows you to divide your program into multiple source files. Morpho provides a number of built in modules--and you can write your own--which can be loaded like this: import color which imports the `color` module. You can selectively import symbols from a modules by using the `for` keyword: import color for HueMap, Red which imports only the `HueMap` class and the `Red` variable. You can also import a module using the 'as' keyword to place the symbols in a specified namespace: import color as col You can then use refer to specific symbols like this: print col.Red (See the help topic 'namespaces' for more information.) ## Namespaces [tagnamespace]: # (namespace) [tagnamespaces]: # (namespaces) A namespace is a collection of symbols that is imported from a module. You identify a namespace using the 'as' keyword when importing the module like this: import color as col // 'col' is the namespace Everything defined by the module with a unique symbol, including classes, functions and global variables, can be identified using the namespace, e.g. print col.Red Since the symbols are only are defined in the namespace you imported them into, you can't refer to them directly: print Red Using namespaces is recommended, becuase it helps prevent conflicts between modules. ================================================ FILE: help/optimize.md ================================================ [comment]: # (Morpho optimize help file) [version]: # (0.5) # Optimize [tagoptimize]: # (optimize) The `optimize` package contains a number of functions and classes to perform shape optimization. [showsubtopics]: # (subtopics) ## OptimizationProblem [tagoptimizationproblem]: # (optimizationproblem) An `OptimizationProblem` object defines an optimization problem, which may include functionals to optimize as well as global and local constraints. Create an `OptimizationProblem` with a mesh: var problem = OptimizationProblem(mesh) Add an energy: var la = Area() problem.addenergy(la) Add an energy that operates on a selected region, and with an optional prefactor: problem.addenergy(la, selection=sel, prefactor=2) Add a constraint: problem.addconstraint(la) Add a local constraint (here a onesided level set constraint): var ls = ScalarPotential(fn (x,y,z) z, fn (x,y,z) Matrix([0,0,1])) problem.addlocalconstraint(ls, onesided=true) ## Optimizer [tagoptimizer]: # (optimizer) `Optimizer` objects are used to optimize `Mesh`es and `Field`s. You should use the appropriate subclass: `ShapeOptimizer` or `FieldOptimizer` respectively. [showsubtopics]: # (subtopics) ## ShapeOptimizer [tagshapeoptimizer]: # (shapeoptimizer) A `ShapeOptimizer` object performs shape optimization: it moves the vertex positions to reduce an overall energy. Create a `ShapeOptimizer` object with an `OptimizationProblem` and a `Mesh`: var sopt = ShapeOptimizer(problem, m) Take a step down the gradient with fixed stepsize: sopt.relax(5) // Takes five steps Linesearch down the gradient: sopt.linesearch(5) // Performs five linesearches Perform conjugate gradient (usually gives faster convergence): sopt.conjugategradient(5) // Performs five conjugate gradient steps. Control a number of properties of the optimizer: sopt.stepsize=0.1 // The stepsize to take sopt.steplimit=0.5 // Maximum stepsize for optimizing methods sopt.etol = 1e-8 // Energy convergence tolerance sopt.ctol = 1e-9 // Tolerance to which constraints are satisfied sopt.maxconstraintsteps = 20 // Maximum number of constraint steps to use ## FieldOptimizer [tagfieldoptimizer]: # (fieldoptimizer) A `FieldOptimizer` object performs field optimization: it changes elements of a `Field` to reduce an overall energy. Create a `FieldOptimizer` object with an `OptimizationProblem` and a `Field`: var sopt = FieldOptimizer(problem, fld) Field optimizers provide the same options and methods as Shape optimizers: see the `ShapeOptimizer` documentation for details. ================================================ FILE: help/plot.md ================================================ [comment]: # (Plot module help) [version]: # (0.5.4) # Plot [tagplot]: # (plot) The `plot` module provides visualization capabilities for Meshes, Selections and Fields. These functions produce Graphics objects that can be displayed with `Show`. To use the module, first import it: import plot [showsubtopics]: # (subtopics) ## Plotmesh [tagplotmesh]: # (plotmesh) Visualizes a `Mesh` object: var g = plotmesh(mesh) Plotmesh accepts a number of optional arguments to control what is displayed: * `selection` - Only elements in a provided `Selection` are drawn. * `grade` - Only draw the specified grade. This can also be a list of multiple grades to draw. * `color` - Draw the mesh in a provided `Color`. * `filter` and `transmit` - Used by the `povray` module to indicate transparency. ## Plotmeshlabels [tagplotmeshlabels]: # (plotmeshlabels) Draws the ids for elements in a `Mesh`: var g = plotmeshlabels(mesh) Plotmeshlabels accepts a number of optional arguments to control the output: * `grade` - Only draw the specified grade. This can also be a list of multiple grades to draw. * `selection` - Only labels in a provided `Selection` are drawn. * `offset` - Local offset vector for labels. Can be a `List`, a `Matrix` or a function. * `dirn` - Text direction for labels. Can be a `List`, a `Matrix` or a function. * `vertical` - Text vertical direction. Can be a `List`, a `Matrix` or a function. * `color` - Label color. Can be a `Color` object or a `Dictionary` of colors for each grade. * `fontsize` - Font size to use. ## Plotselection [tagplotselection]: # (plotselection) Visualizes a `Selection` object: var g = plotselection(mesh, sel) Plotselection accepts a number of optional arguments to control what is displayed: * `grade` - Only draw the specified grade. This can also be a list of multiple grades to draw. * `filter` and `transmit` - Used by the `povray` module to indicate transparency. ## Plotfield [tagplotfield]: # (plotfield) Visualizes a scalar `Field` object: var g = plotfield(field) Plotfield accepts a number of optional arguments to control what is displayed: * `grade` - Draw the specified grade. * `colormap` - A `Colormap` object to use. The field is automatically scaled. * `scalebar` - A `Scalebar` object to use. * `selection` - Only elements in a provided `Selection` are drawn. * `style` - Plot style. See below. * `filter` and `transmit` - Used by the `povray` module to indicate transparency. * `cmin` and `cmax` - Can be used to define the data range covered. Values beyond these limits will be colored by the lower/upper bound of the colormap accordingly. Supported plot styles: * `default` - Color `Mesh` elements by the corresponding value of the `Field`. * `interpolate` - Interpolate `Field` quantities onto higher elements. ## ScaleBar [tagscalebar]: # (scalebar) Represents a scalebar for a plot: Show(plotfield(field, scalebar=ScaleBar(posn=[1.2,0,0]))) `ScaleBar`s can be created with many adjustable parameters: * `nticks` - Maximum number of ticks to show. * `posn` - Position to draw the `ScaleBar`. * `length` - Length of `ScaleBar` to draw. * `dirn` - Direction to draw the `ScaleBar` in. * `tickdirn` - Direction to draw the ticks in. * `colormap` - `ColorMap` to use. * `textdirn` - Direction to draw labels in. * `textvertical` - Label vertical direction. * `fontsize` - Fontsize for labels * `textcolor` - Color for labels You can draw the `ScaleBar` directly by calling the `draw` method: sb.draw(min, max) where `min` and `max` are the minimum and maximum values to display on the scalebar. ================================================ FILE: help/povray.md ================================================ [comment]: # (Povray module help) [version]: # (0.5) # POVRay [tagpovray]: # (povray) The `povray` module provides integration with POVRay, a popular open source ray-tracing package for high quality graphical rendering. To use the module, first import it: import povray To raytrace a graphic, begin by creating a `POVRaytracer` object: var pov = POVRaytracer(graphic) Create a .pov file that can be run with POVRay: pov.write("out.pov") Create, render and display a scene using POVRay: pov.render("out.pov") This also creates the .png file for the scene. The `POVRaytracer` constructor supports an optional `camera` argument: * `camera` - a `Camera` object (see below / help) containing the settings for the povray camera. The `Camera` object can be initialized as follows: var camera = Camera() This object contains the default settings of the camera, which can be changed using the following optional arguments, or by just setting the attributes after instantiation: * `antialias` - whether to antialias the output or not (`true` by default) * `width` - image width (`2048` by default) * `height` - image height (`1536` by default) * `viewangle` - camera angle (higher means wider view) (`24` by default) * `viewpoint` - position of camera (`Matrix([0,0,-5])` by default) * `look_at` - coordinate to look at (`Matrix([0,0,0])` by defualt) * `sky` - orientation pointing to the sky (`Matrix([0,1,0])` by default) The default settings generate a reasonable centered view of the x-y plane. These attributes can also be set directly for the `POVRaytracer` object: pov.look_at = Matrix([0,0,1]) The `render` method supports a few optional boolean arguments: * `quiet` - whether to suppress the parser and render statistics from `povray` or not (`false` by default) * `display` - whether to turn on the graphic display while rendering or not (`true` by default) * `shadowless` - whether to turn off the shadows while rendering (`false` by default) * `transparent` - whether to render the graphic with a transparent background in the output PNG (`false` by default) # Camera [tagcamera]: # (camera) The `Camera` object can be initialized as follows: var camera = Camera() This object contains the default settings of the camera, which can be changed using the following optional arguments, or by just setting the attributes after instantiation: * `antialias` - whether to antialias the output or not (`true` by default) * `width` - image width (`2048` by default) * `height` - image height (`1536` by default) * `viewangle` - camera angle (higher means wider view) (`24` by default) * `viewpoint` - position of camera (`Matrix([0,0,-5])` by default) * `look_at` - coordinate to look at (`Matrix([0,0,0])` by defualt) * `sky` - orientation pointing to the sky (`Matrix([0,1,0])` by default) camera.sky = Matrix([0,0,1]) The default settings generate a reasonable centered view of the x-y plane. ================================================ FILE: help/range.md ================================================ [comment]: # (Morpho range help file) [version]: # (0.5) # Range [tagrange]: # (range) Ranges represent a sequence of numerical values. There are two ways to create them depending on whether the upper value is included or not: var a = 1..5 // inclusive version, i.e. [1,2,3,4,5] var b = 1...5 // exclusive version, i.e. [1,2,3,4] By default, the increment between values is 1, but you can use a different value like this: var a = 1..5:0.5 // 1 - 5 with an increment of 0.5. You can also create Range objects using the appropriate constructor function: var a = Range(1,5,0.5) Ranges are particularly useful in writing loops: for (i in 1..5) print i They can easily be converted to a list of values: var c = List(1..5) To find the number of elements in a Range, use the `count` method print (1..5).count() [showmethodsrange]: # ================================================ FILE: help/requirements.txt ================================================ # File: docs/requirements.txt # Defining the exact version will make sure things don't break sphinx>=5.0.0 sphinx_rtd_theme>=0.5.1 myst-parser>=0.13.5 Jinja2<3.1 recommonmark ================================================ FILE: help/selection.md ================================================ [comment]: # (Morpho selection class help file) [version]: # (0.5) # Selection [tagselection]: # (selection) The Selection class enables you to select components of a mesh for later use. You can supply a function that is applied to the coordinates of every vertex in the mesh, or select components like boundaries. Create an empty selection: var s = Selection(mesh) Select vertices above the z=0 plane using an anonymous function: var s = Selection(mesh, fn (x,y,z) z>0) Select the boundary of a mesh: var s = Selection(mesh, boundary=true) Selection objects can be composed using set operations: var s = s1.union(s2) or var s = s1.intersection(s2) To add additional grades, use the addgrade method. For example, to add areas: s.addgrade(2) [showsubtopics]: # subtopics ## addgrade [tagaddgrade]: # (addgrade) Adds elements of the specified grade to a Selection. For example, to add edges to an existing selection, use s.addgrade(1) By default, this only adds an element if *all* vertices in the element are currently selected. Sometimes, it's useful to be able to add elements for which only some vertices are selected. The optional argument `partials` allows you to do this: s.addgrade(1, partials=true) Note that this method modifies the existing selection, and does not generate a new Selection object. ## removegrade [tagremovegrade]: # (removegrade) Removes elements of the specified grade from a Selection. For example, to remove edges from an existing selection, use s.removegrade(1) Note that this method modifies the existing selection, and does not generate a new Selection object. ## idlistforgrade [tagidlistforgrade]: # (idlistforgrade) Returns a list of element ids included in the selection. To find out which edges are selected: var edges = s.idlistforgrade(1) ## isselected [tagisselected]: # (isselected) Checks if an element id is selected, returning `true` or `false` accordingly. To check if edge number 5 is selected: var f = s.isselected(1, 5)) ================================================ FILE: help/sparse.md ================================================ [comment]: # (Sparse class help) [version]: # (0.5) # Sparse [tagsparse]: # (Sparse) The Sparse class provides support for sparse matrices. An empty sparse matrix can be initialized with a given size, var a = Sparse(nrows,ncols) Alternatively, a matrix can be created from an array of triplets, var a = Sparse([[row, col, value] ...]) For example, var a = Sparse([[0,0,2], [1,1,-2]]) creates the matrix [ 2 0 ] [ 0 -2 ] Once a sparse matrix is created, you can use all the regular arithmetic operators with matrix operands, e.g. a+b a*b ================================================ FILE: help/string.md ================================================ [comment]: # (String class help) [version]: # (0.5) # String [tagstring]: # (String) Strings represent textual information. They are written in Morpho like this: var a = "hello world" Unicode characters including emoji are supported. You can also create strings using the constructor function `String`, which takes any number of parameters: var a = String("Hello", "World") A very useful feature, called *string interpolation*, enables the results of any morpho expression can be interpolated into a string. Here, the values of `i` and `func(i)` will be inserted into the string as it is created: print "${i}: ${func(i)}" To get an individual character, use index notatation print "morpho"[0] You can loop over each character like this: for (c in "morpho") print c Note that strings are immutable, and hence var a = "morpho" a[0] = 4 raises an error. [showsubtopics]: # ## split [tagsplit]: # (split) The split method splits a String into a list of substrings. It takes one argument, which is a string of characters to use to split the string: print "1,2,3".split(",") gives [ 1, 2, 3 ] ================================================ FILE: help/syntax.md ================================================ [comment]: # (Morpho syntax help file) [version]: # (0.5) [toplevel]: # # Syntax [tagsyntax]: # (syntax) Morpho provides a flexible object oriented language similar to other languages in the C family (like C++, Java and Javascript) with a simplified syntax. Morpho programs are stored as plain text with the .morpho file extension. A program can be run from the command line by typing morpho5 program.morpho ## Comments [tagcomment]: # (comment) [tagcomments]: # (comments) [tagcommentvar]: # (//) [tagcommentvarr]: # (/*) [tagcommentvarrr]: # (*/) Two types of comment are available. The first type is called a 'line comment' whereby text after `//` on the same line is ignored by the interpreter. a.dosomething() // A comment Longer 'block' comments can be created by placing text between `/*` and `*/`. Newlines are ignored /* This is a longer comment */ In contrast to C, these comments can be nested /* A nested /* comment */ */ enabling the programmer to quickly comment out a section of code. ## Symbols [tagsymbols]: # (symbols) [tagnames]: # (names) Symbols are used to refer to named entities, including variables, classes, functions etc. Symbols must begin with a letter or underscore _ as the first character and may include letters or numbers as the remainder. Symbols are case sensitive. asymbol _alsoasymbol another_symbol EvenThis123 YET_ANOTHER_SYMBOL Classes are typically given names with an initial capital letter. Variable names are usually all lower case. ## Newlines [tagnewlines]: # (newlines) [tagnewline]: # (newline) Strictly, morpho ends statements with semicolons like C, but in practice these are usually optional and you can just start a new line instead. For example, instead of var a = 1; // The ; is optional you can simply use var a = 1 If you want to put several statements on the same line, you can separate them with semicolons: var a = 1; print a There are a few edge cases to be aware of: The morpho parser works by accepting a newline anywhere it expects to find a semicolon. To split a statement over multiple lines, signal to morpho that you plan to continue by leaving the statement unfinished. Hence, do this: print a + 1 rather than this: print a // < Morpho thinks this is a complete statement + 1 // < and so this line will cause a syntax error ## Booleans [tagtrue]: # (true) [tagfalse]: # (false) [tagbooleans]: # (true) Comparison operations like `==`, `<` and `>=` return `true` or `false` depending on the result of the comparison. For example, print 1==2 prints `false`. The constants `true` or `false` are provided for you to use in your own code: return true ## Nil [tagnil]: # (nil) The keyword `nil` is used to represent the absence of an object or value. Note that in `if` statements, a value of `nil` is treated like `false`. if (nil) { // Never executed. } ## Blocks [tagblocks]: # (blocks) [tagblock]: # (block) Code is divided into *blocks*, which are delimited by curly brackets like this: { var a = "Hello" print a } This syntax is used in function declarations, loops and conditional statements. Any variables declared within a block become *local* to that block, and cannot be seen outside of it. For example, var a = "Foo" { var a = "Bar" print a } print a would print "Bar" then "Foo"; the version of `a` inside the code block is said to *shadow* the outer version. ## Precedence [tagprecedence]: # (precedence) Precedence refers to the order in which morpho evaluates operations. For example, print 1+2*3 prints `7` because `2*3` is evaluated before the addition; the operator `*` is said to have higher precedence than `+`. You can always modify the order of evaluation by using parentheses: print (1+2)*3 // prints 9 ## Print [tagprint]: # (print) The `print` keyword is used to print information to the console. It can be followed by any value, e.g. print 1 print true print a print "Hello" ================================================ FILE: help/system.md ================================================ [comment]: # (System help) [version]: # (0.5) # System [tagsystem]: # (system) The `System` class provides information and access to some features of the runtime environment. [showsubtopics]: # (subtopics) ## Platform [tagplatform]: # (platform) Detect which platform morpho was compiled for: print System.platform() which returns `"macos"`, `"linux"`, `"unix"` or `"windows"`. ## Version [tagversion]: # (version) Find the current version of morpho: print System.version() ## Clock [tagclock]: # (clock) Returns the system time in seconds, with at least millisecond granularity. Primarily intended for timing: var start=System.clock() // Do something print System.clock()-start Note that `System.clock` measures the actual physical time elapsed, not the time spent in a process. ## Sleep [tagsleep]: # (sleep) Pauses the program for a specified number of seconds: System.sleep(0.5) // Sleep for half a second ## Readline [tagreadline]: # (readline) Reads a line of input from the console: var in = System.readline() ## Arguments [tagargunents]: # (arguments) Returns a `List` of arguments passed to the current morpho on the command line. var args = System.arguments() for (e in args) print e Run a morpho program with arguments: morpho5 program.morpho hello world Note that, in line with UNIX conventions, command line arguments before the program file name are passed to the `morpho5` runtime; those after are passed to the morpho program via `System.arguments`. ## Exit [tagexit]: # (exit) Stop execution of a program: System.exit() ================================================ FILE: help/tuple.md ================================================ [comment]: # (Tuple class help) [version]: # (0.6.0) # Tuple [tagtuple]: # (Tuple) Tuples are collection objects that contain a sequence of values each associated with an integer index. Unlike Lists, they can't be changed after creation. Create a tuple like this: var tuple = (1, 2, 3) Look up values using index notation: tuple[0] Indexing can also be done with slices: tuple[0..2] Loop over elements of a tuple: for (i in tuple) print i [showsubtopics]: # (subtopics) ## ismember [tagismember]: # (ismember) Tests if a value is a member of a tuple: var tuple = (1,2,3) print tuple.ismember(1) // expect: true ## Join [tagjoin]: # (join) Join two lists together: var t1 = (1,2,3), t2 = (4, 5, 6) print t1.join(t2) // expect: (1,2,3,4,5,6) ================================================ FILE: help/values.md ================================================ [comment]: # (Values help) [version]: # (0.5) # Values [tagvalues]: # (values) Values are the basic unit of information in morpho: All functions in morpho accept values as arguments and return values. [showsubtopics]: # (subtopics) ## Int [tagint]: # (int) Morpho provides integers, which work as you would expect in other languages, although you rarely need to worry about the distinction between floats and integers. Convert a floating point number to an Integer: print Int(1.3) // expect: 1 Convert a string to an integer: print Int("10")+1 // expect: 11 ## Float [tagfloat]: # (float) Morpho provides double precision floating point numbers. Convert a string to a floating point number: print Float("1.2e2")+1 // expect: 121 ## Ceil [tagceil]: # (ceil) Returns the smallest integer larger than or equal to its argument: print ceil(1.3) // expect: 2 ## Floor [tagfloor]: # (floor) Returns the largest integer smaller than or equal to its argument: print floor(1.3) // expect: 1 ## Format [tagformat]: # (format) The format method converts a number to a `String` using a given format specifier: print (1/3).format("%4.2g") // Outputs 0.33 The specifier must begin with '%' and may include: * A minimum width, given as an integer. * Number of decimal places to show, with '.' in front. * A formatting option, either 'f' or 'g' where: - 'f' displays the number in decimal form, e.g. 0.01 - 'g' uses scientific notation, e.g. 1e-2 The syntax for the formatting string is similar to that used in C and Python. ================================================ FILE: help/variables.md ================================================ [comment]: # (Morpho variables help file) [version]: # (0.5) [toplevel]: # # Variables [tagvariables]: # (variables) [tagvar]: # (var) Variables are defined using the `var` keyword followed by the variable name: var a Optionally, an initial assignment may be given: var a = 1 Variables defined in a block of code are visible only within that block, so var greeting = "Hello" { var greeting = "Goodbye" print greeting } print greeting will print *Goodbye* *Hello* Multiple variables can be defined at once by separating them with commas var a, b=2, c[2]=[1,2] where each can have its own initializer (or not). ## Indexing [taglb]: # ([) [tagrb]: # (]) [tagindex]: # (index) [tagsub]: # (subscript) Morpho provides a number of collection objects, such as `List`, `Range`, `Array`, `Dictionary`, `Matrix` and `Sparse`, that can contain more than one value. Index notation (sometimes called subscript notation) is used to access elements of these objects. To retrieve an item from a collection, you use the `[` and `]` brackets like this: var a = List("Apple", "Bag", "Cat") print a[0] which prints *Apple*. Note that the first element is accessed with `0` not `1`. Similarly, to set an entry in a collection, use: a[0]="Adder" which would replaces the first element in `a` with `"Adder"`. Some collection objects need more than one index, var a = Matrix([[1,0],[0,1]]) print a[0,0] and others such as `Dictionary` use non-numerical indices, var b = Dictionary() b["Massachusetts"]="Boston" b["California"]="Sacramento" as in this dictionary of state capitals. ================================================ FILE: help/vtk.md ================================================ [comment]: # (Morpho vtk module help file) [version]: # (0.5) # VTK [tagvtk]: # (vtk) The vtk module contains classes to allow I/O of meshes and fields using the VTK Legacy Format. Note that this currently only supports scalar or 2D/3D vector (column matrix) fields that live on the vertices ( shape `[1,0,0]`). Support for tensorial fields and fields on cells coming soon. [showsubtopics]: # (subtopics) ## VTKExporter [tagvtkexporter]: # (VTKExporter) This class can be used to export the field(s) and/or a mesh at a given state to a single .vtk file. To use it, import the `vtk` module: import vtk Initialize the `VTKExporter` var vtkE = VTKExporter(obj) where `obj` can either be * A `Mesh` object: This prepares the Mesh for exporting. * A `Field` object: This prepares both the Field and the Mesh associated with it for exporting. Use the `export` method to export to a VTK file. vtkE.export("output.vtk") Optionally, use the `addfield` method to add one or more fields before exporting: vtkE.addfield(f, fieldname="f") where, * `f` is the field object to be exported * `fieldname` is an optional argument that assigns a name to the field in the VTK file. This name is required to be a character string without embedded whitespace. If not provided, the name would be either "scalars" or "vectors" depending on the field type**. ** Note that this currently only supports scalar or 2D/3D vector (column matrix) fields that live on the vertices ( shape `[1,0,0]`). Support for tensorial fields and fields on cells coming soon. Minimal example: import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var vtkE = VTKExporter(m1) // Export just the mesh vtkE.export("mesh.vtk") var f1 = Field(m1, fn(x,y,z) x) var g1 = Field(m1, fn(x,y,z) Matrix([x,2*x,3*x])) vtkE = VTKExporter(f1, fieldname="f") // Export fields vtkE.addfield(g1, fieldname="g") vtkE.export("data.vtk") ## VTKImporter [tagvtkimporter]: # (VTKImporter) This class can be used to import the field(s) and/or the mesh at a given state from a single .vtk file. To use it, import the `vtk` module: import vtk Initialize the `VTKImporter` with the filename var vtkI = VTKImporter("output.vtk") Use the `mesh` method to get the mesh: var mesh = vtkI.mesh() Use the `field` method to get the field: var f = vtkI.field(fieldname) Use the `fieldlist` method to get the list of the names of the fields contained in the file: print vtkI.fieldlist() Use the `containsfield` method to check whether the file contains a field by a given `fieldname`: if (tkI.containsfield(fieldname)) { ... } where `fieldname` is the name assigned to the field in the .vtk file Minimal example: import vtk import meshtools var vtkI = VTKImporter("data.vtk") var m = vtkI.mesh() var f = vtkI.field("f") var g = vtkI.field("g") ================================================ FILE: modules/color.morpho ================================================ /* Color module */ import constants class Color { init(r,g,b) { self.r = r self.g = g self.b = b } red(x) { return self.r } green(x) { return self.g } blue(x) { return self.b } rgb(x) { return [self.r, self.g, self.b] } } // Some predefined colors var Red = Color(1,0,0) var Green = Color(0,1,0) var Blue = Color(0,0,1) var White = Color(1,1,1) var Black = Color(0,0,0) var Cyan = Color(0,1,1) var Magenta = Color(1,0,1) var Yellow = Color(1,1,0) var Brown = Color(0.6,0.4,0.2) var Orange = Color(1,0.5,0) var Pink = Color(1,0.5,0.5) var Purple = Color(0.5,0,0.5) fn Gray(x) { return Color(x,x,x) } /* Basic color maps */ class ColorMap { init() { } rgb(x) { return [self.red(x), self.green(x), self.blue(x)] } } class GradientMap is ColorMap { red(x) { return 0.29+0.04*x+0.66*x^2 } green(x) { return 0.33+0.42*x+0.24*x^2 } blue(x) { return 0.46-0.55*x+x^2 } } class GrayMap is ColorMap { red(x) { return x } green(x) { return x } blue(x) { return x } } class HueMap is ColorMap { red(x) { return (sin(2*Pi*(x+1/4))+1)/2 } green(x) { return (sin(2*Pi*x)+1)/2 } blue(x) { return (sin(2*Pi*(x-0.4))+1)/2 } } /* Color maps expanded onto Chebyshev polynomials */ var _errChebyShevOrderInconsistent = Error("ClrChbyshvOrdr", "Order of Chebyshev approximation inconsistent.") class ChebyshevMap is Color { init(R,G,B) { self.R = R self.G = G self.B = B self.order = R.count() if (G.count()!=self.order || B.count()!=self.order) _errChebyShevOrderInconsistent.throw() } _chebyshev(t) { var n = self.order var c[n] c[0]=1 c[1]=t for (var i=2; imax[i]) max[i]=x[i] } } return [min, max] } picksweep() { // Pick sweep axis as the dimension with greatest extent var extent = (self.bbox[1]-self.bbox[0]) var lext = [] for (dx in extent) lext.append(dx) return lext.order()[-1] } supersimplex() { // Create a supersimplex that encloses the point cloud // We create a simplex with vertices x0, x0 + lamda*[1,0,0..], x0 + lambda*[0,1,0..]... // Where x0 and lambda are computed from a sphere that encloses the bounding box var extent = (self.bbox[1]-self.bbox[0]) var xc = (self.bbox[0]+self.bbox[1])/2 // Center of the cloud var r = self.sscale*(self.bbox[1]-xc).norm() // Radius of a ball that encloses the cloud var lambda = 2*(self.dim+sqrt(self.dim))*r // Scale factor for (i in 0...self.dim) xc[i]-=r // Shift by r in all coordinate directions var pts = [xc] // Vertices for (i in 0...self.dim) { var xi = Matrix(self.dim) xi[i]=lambda // Create a vector that lies in the i'th coordinate axis pts.append(xc+xi) // Add the vertex to the list } return pts } triangulate() { // Create Delaunay triangulation var sweep=self.picksweep() // Pick sweep dimension var pts = self.pts var n = self.n var indices = List(0...n) // Create an array of indices into the vertex array // Sort points along sweep axis fn cmp (i, j) { var diff = pts[i][sweep] - pts[j][sweep] if (diff!=0) return diff return i-j } indices.sort(cmp) // Create supersimplex var ssmplx = self.supersimplex() for (x in ssmplx) pts.append(x) // And add to the point cloud self.open = [Circumsphere(pts, List(n..n+self.dim))]// List of open simplices self.closed = [] // List of finalized simplices // Incrementally add each point to the mesh self.ext = Dictionary() // Exterior (n-1) simplices for (ix in indices) { self.ext.clear() // For each open simplex, check to see if the current point is // inside its circumsphere. If it is, remove the triangle and add // its edges to an edge list. self.newopen = [] var dx, key, e for (smplx in self.open) { dx = pts[ix][sweep] - smplx.x[sweep] // If this point is to the right of this simplex's circumcircle, // then this simplex shouldn't get checked again. Add it to the // closed list and don't retain. if (dx > 0.0 && dx > smplx.r) { self.closed.append(smplx) continue } // If we're outside the circumsphere, skip this simplex. if ((pts[ix] - smplx.x).norm() - smplx.r > self.eps) { self.newopen.append(smplx) continue } // Add the simplex's exterior to the exterior list and don't retain. // sets() generates sets of order dim from the element e.g. triangles use order 2 for (e in smplx.i.sets(self.dim)) { key = "${e}" // Check if the key already exists. If so, remove the key. if (self.ext.contains(key)) { self.ext.remove(key) } else { self.ext[key] = e } } } self.open = self.newopen // Add a new simplex for each exterior (n-1) simplex. for (e in self.ext.keys()) { self.extel = self.ext[e] self.extel.append(ix) // Include the vertex being added* self.open.append(Circumsphere(pts, self.extel)) } } // Copy any remaining open triangles to the closed list for (smplx in self.open) self.closed.append(smplx) // Select all simplices that don't have a vertex from the supersimplex self.open = [] var record for (smplx in self.closed) { record = true for (ix in smplx.i) if (ix>=n) record = false if (record) self.open.append(smplx.i) } return self.open } } ================================================ FILE: modules/functionals.morpho ================================================ /* *************************************************************** * Functionals * =========== * This module provides a class that helps implement new * functionals in morpho, as well as some less common functionals **************************************************************** */ import kdtree class Functional { init(grade) { self.grade = grade } integrand(mesh) { var conn if (self.grade>0) conn=mesh.connectivitymatrix(0, self.grade) var vert=mesh.vertexmatrix() var nel = mesh.count(self.grade) var out = Matrix(nel) for (i in 0...nel) { var el if (conn) el=conn.rowindices(i) out[i]=self.integrandfn(mesh, vert, i, el) } return out } gradient(mesh) { var conn if (self.grade>0) conn=mesh.connectivitymatrix(0, self.grade) var vert=mesh.vertexmatrix() var nel = mesh.count(self.grade) var dim=vert.dimensions()[0] var nv=vert.dimensions()[1] var out = Matrix(dim,nv) for (i in 0...nel) { var el if (conn) el=conn.rowindices(i) self.gradientfn(mesh, vert, i, el, out) } return out } total(mesh) { var a = self.integrand(mesh) return a.sum() } } /** Hookean elasticity: F = 1/2 ((L-L0)/L0)^2 Construct with a field of reference lengths */ class HookeElasticity < Functional { init(field) { self.ref = field super.init(1) } integrandfn(mesh, vert, id, el) { var len = (vert.column(el[0])-vert.column(el[1])).norm() var len0 = self.ref[self.grade, id] return ((len-len0)/len0)^2/2 } gradientfn(mesh, vert, id, el, grd) { var x0 = vert.column(el[0]) var x1 = vert.column(el[1]) var dx = x0-x1 var len = dx.norm() var len0 = self.ref[self.grade, id] var g = dx*(len-len0)/(len0^2*len) grd.setcolumn(el[0], g+grd.column(el[0])) grd.setcolumn(el[1], -g+grd.column(el[1])) } } /* Pairwise potential */ class PairwisePotential < Functional { init (func, grad, cutoff=nil) { self.func = func self.grad = grad self.cutoff = cutoff super.init(0) } buildtree(mesh) { var vert = mesh.vertexmatrix() var nv = mesh.count() var pts = [] for (i in 0...nv) { pts.append(vert.column(i)) } return KDTree(pts) } integrand(mesh) { var nv = mesh.count() var out = Matrix(nv) var vert = mesh.vertexmatrix() for (i in 0...nv) { var x = vert.column(i) for (j in i+1...nv) { var r = (x-vert.column(j)).norm() if (self.cutoff && r>self.cutoff) continue var f = self.func(r) out[i]+=0.5*f out[j]+=0.5*f } } return out } gradient(mesh) { var nv = mesh.count() var vert = mesh.vertexmatrix() var dim = vert.dimensions()[0] var out = Matrix(dim, nv) for (i in 0...nv) { for (j in i+1...nv) { var dx = vert.column(i)-vert.column(j) var r = dx.norm() if (self.cutoff && r>self.cutoff) continue var df = self.grad(r)/r out.setcolumn(i, df*dx+out.column(i)) out.setcolumn(j, -df*dx+out.column(j)) } } return out } } ================================================ FILE: modules/graphics.morpho ================================================ /* Graphics */ import constants import meshtools import color var _eps = 1e-15 var _fntntfnd = Error("FntNtFnd", "Font not found.") var _txtdrnvrtprll = Error("TxtDrnVrtPrll", "Direction and vertical must not be parallel or zero.") /* ************************************** * Utility functions **************************************** */ /** Constructs a rotation matrix @param[in] theta - rotation angle @param[in] axis - axis to rotate about @returns the rotation matrix */ fn _rotation(theta, axis) { var x = axis[0], y=axis[1], z=axis[2] var ctheta = cos(theta), stheta = sin(theta), cc=1-ctheta return Matrix( [[x*x*cc+ctheta, x*y*cc-z*stheta, x*z*cc+y*stheta], [x*y*cc+z*stheta, y*y*cc+ctheta, y*z*cc-x*stheta], [z*x*cc-y*stheta, y*z*cc+x*stheta, z*z*cc+ctheta ]] ) } /** Generates a vector normal to a given vector but of arbitrary orientation */ fn _normalvector(vec) { var order = [abs(vec[0]), abs(vec[1]), abs(vec[2])].order() var out=vec.clone() out[order[0]]=0 out[order[1]]=vec[order[2]] out[order[2]]=-vec[order[1]] return out } /* 3d cross product */ fn _cross3d(a, b) { return Matrix([ a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0] ]) } /* Gram-Schmitt orthonormalization */ fn _orthogonalize(m) { var ncols=m.dimensions()[1] for (i in 0...ncols) { var x = m.column(i) for (j in 0...i) { var y = m.column(j) x -= x.inner(y)*y } var nrm = x.norm() if (nrm<_eps) _txtdrnvrtprll.throw() x/=nrm m.setcolumn(i, x) } } /* ************************************** * Graphics primitives **************************************** */ /** TriangleComplexes are a basic graphical unit that draw a set of triangles from a given set of vertices. */ class TriangleComplex { init(position, normals, colors, connectivity, filter=nil, transmit=nil) { self.position=position /* Vertex position matrix */ self.normals=normals /* Matrix of normal vectors */ self.colors=colors /* Color */ self.connectivity=connectivity /* Connectivity matrix; columns are triangles */ /* Optional arguments to be used by povray*/ self.filter = filter self.transmit = transmit } } /** Draws a cylinder @param[in] start - start point @param[in] end - end point @param[in] aspectratio - aspect ratio @param[in] n - number of faces to use @param[in] color - Color of the sphere */ class Cylinder { init(start, end, aspectratio=0.1, n=10, color=nil, filter=nil, transmit=nil) { self.start = Matrix(start) self.end = Matrix(end) self.aspectratio = aspectratio self.n = n self.color = color /* Optional arguments to be used by povray*/ self.filter = filter self.transmit = transmit } totrianglecomplex() { var aspectratio = self.aspectratio var n = self.n var col = self.color if (isnil(col)) col = [0.5,0.5,0.5] var np=2*n+2 // 2 points at the end plus 2*n point var nt=4*n var dim=self.start.count() var vertices=Matrix(dim, np); var normals=Matrix(dim, np); var x1=self.start, x2=self.end var dx=x2-x1 // Vector from x1 to x2 var length = dx.norm() if (abs(length)<1e-8) return nil var ndx=dx/length var px=_normalvector(dx) var pxnorm=px.norm() if (abs(pxnorm)<1e-15) return px=0.5*length*aspectratio*px/pxnorm // Head and base of the arrow vertices.setcolumn(0, x1) vertices.setcolumn(np-1, x2) normals.setcolumn(0, -1*dx) normals.setcolumn(np-1, dx) /* The cylinder is structured like so: [n+1...2n] \ [1..n] A------------B | | 0 1 2n+1 <---- dx ----> */ for (i in 1..n) { var theta = 2*Pi*(i-1)/n var qx = _rotation(theta, ndx)*px var nn=qx/qx.norm() var pt = qx+x1 // Find vertex ring A by rotating px about ndx, to obtain qx, and then adding x1 vertices.setcolumn(i, pt) // Vertex ring B is obtained by simply translating the first circle by dx*(1-ar) vertices.setcolumn(n+i, pt+dx) // Normals normals.setcolumn(i, nn) normals.setcolumn(n+i, nn) } var conn=Sparse(np,nt) for (i in 0...n) { // Layer 0-A conn[0,i]=1 conn[i+1,i]=1 if (i0.1 && n=_sphere.count()-1) { _sphere.append(refinemesh(_sphere[-1])) } n += 1; } return n } totrianglecomplex(scale=true) { var center = self.center, r = self.r var col = self.color if (isnil(col)) col = [0.5,0.5,0.5] var n = self.refinementlevel() var v = _sphere[n].vertexmatrix().clone() var normals = _sphere[n].vertexmatrix().clone() var conn = _sphere[n].connectivitymatrix(0,2) // Should clone! var nv = v.dimensions()[1] var x0=center if (islist(center)) x0=Matrix(center) // Ignores the center and size and just produces the unit ball if (!scale) { x0*=0; r=1; col = [0.5,0.5,0.5] } for (i in 0...nv) { // Normalize, scale and translate var x = v.column(i) var nn = x/x.norm() normals.setcolumn(i, nn) v.setcolumn(i, x0 + r*nn) } return TriangleComplex(v, normals, col, conn) } } /** Draws an arrow @param[in] start - start point @param[in] end - end point @param[in] aspectratio - aspect ratio of the arrow @param[in] n - number of faces to use @param[in] color - Color of the sphere */ class Arrow { init(start, end, aspectratio=0.1, n=10, color=nil, filter=nil, transmit=nil) { self.start = Matrix(start) self.end = Matrix(end) self.aspectratio = aspectratio self.n = n self.color = color /* Optional arguments to be used by povray*/ self.filter = filter self.transmit = transmit } totrianglecomplex () { var aspectratio = self.aspectratio var n = self.n var col = self.color if (isnil(col)) col = [0.5,0.5,0.5] var np=3*n+2 // 2 points at the end plus 3*n point var nt=6*n var dim=self.start.count() var vertices=Matrix(dim, np) var normals=Matrix(dim, np) var x1=self.start, x2=self.end var dx=x2-x1 // Vector from x1 to x2 var length = dx.norm() var ndx=dx/length var px=_normalvector(dx) var pxnorm=px.norm() if (abs(pxnorm)<1e-15) return px=0.5*length*aspectratio*px/pxnorm // Head and base of the arrow vertices.setcolumn(0, x1) vertices.setcolumn(np-1, x2) normals.setcolumn(0, -1*dx) normals.setcolumn(np-1, dx) /* The arrow is structured like so: [n+1...2n] C--[2n+1...3n] \ |\ [1..n] A------------B \ | \ 0<----- u ---> 1 3n+1 <------ dx ------> */ var u=(1-aspectratio)*dx for (i in 1..n) { var theta = 2*Pi*(i-1)/n var qx = _rotation(theta, ndx)*px var nn=qx/qx.norm() var pt = qx+x1 // Find vertex ring A by rotating px about ndx, to obtain qx, and then adding x1 vertices.setcolumn(i, pt) // Vertex ring B is obtained by simply translating the first circle by dx*(1-ar) vertices.setcolumn(n+i, pt+u) // Vertex ring C is obtained by simply adding qx to the point in vertex ring B vertices.setcolumn(2*n+i, pt+u+qx) // Normals normals.setcolumn(i, nn) normals.setcolumn(n+i, nn) normals.setcolumn(2*n+i, nn) } var conn=Sparse(np,nt) for (i in 0...n) { // Layer 0-A conn[0,i]=1 conn[i+1,i]=1 if (i /dev/null 2>&1 &") } /** Generate a random name */ _randomalphanumstring(n) { var alpha="abcdefghijklmnopqrstuvwxyz0123456789" var name="" for (i in 1..n) name+=alpha[Int(random()*(alpha.count()-1))] return name } uidinit() { self.uid = 0 } uid() { // Generate unique identifiers self.uid+=1 return self.uid } preamble(graphic, out) { out.write("S 1 3") out.write("W \"${graphic.title}\"") } /* The visit method for a generic `item`. Here, we define this separate `visitgeneric` method that performs the intended generic task, and have the actual generic `visit` method call `visitgeneric`. This enables calls to the generic fallback from *within a specific implementation*. For example, the `visit` method for a `Sphere` object can call `self.visitgeneric(item, out)` to visit the `TriangleComplex` representation of the sphere, but calling `self.visit(item, out)` would result in an infinite loop. */ visitgeneric(item, out) { self.visit(item.totrianglecomplex(), out) } // Will be used by Cylinder, Arrow and Tube visit(item, out) { self.visitgeneric(item, out) } visit(Sphere item, out) { var n = item.refinementlevel() if (item.color) { self.visitgeneric(item, out) return } else if (!self.spheres.contains(n)) { // Check if we already have a sphere object at this refinement level var tri=item.totrianglecomplex(scale=false) tri.id = self.uid() out.write("o ${tri.id}") self.trianglecomplexobjectdata(tri,out) self.spheres[n]=tri } out.write("i") out.write("s ${item.r}") out.write("t ${item.center[0]} ${item.center[1]} ${item.center[2]}") out.write("d ${self.spheres[n].id}") } trianglecomplexobjectdata(item, out) { var x = item.position var n = item.normals var col = item.colors var localcolor = false var dimensions=x.dimensions() var dim = dimensions[0] var nv = dimensions[1] if (ismatrix(col)) { if (col.dimensions()[1]>1) localcolor = true } else if (isobject(col) && !islist(col)) col = col.rgb(0) out.write("v \"xnc\"") for (i in 0...nv) { var line = "" for (j in 0...dim) { line+="${x[j,i]} " } for (j in 0...dim) { line+="${n[j,i]} " } if (localcolor) { for (j in 0...3) line+="${col[j,i]} " } else { for (j in 0...item.colors.count()) line+="${col[j]} " } out.write(line) } out.write("f") var f=item.connectivity nv=f.dimensions()[0] var nf=f.dimensions()[1] for (i in 0...nf) { var line = "" for (j in 0...nv) { if (f[j,i]!=0) line+="${j} " } out.write(line) } } visit(TriangleComplex item, out) { var id = self.uid() out.write("o ${id}") self.trianglecomplexobjectdata(item, out) out.write("i") out.write("d ${id}") } fontpath() { var platform = System.platform() if (platform=="macos") { return ["/System/Library/Fonts"] } else return ["/usr/share/fonts","/usr/local/share/fonts"] } matchfontfile(file, font) { var fname = file.split(".") if (fname[0]==font && (fname[1]=="ttc" || fname[1]=="ttf")) return true return false } searchfontfolder(folder, font) { for (f in Folder.contents(folder)) { var path = folder + "/" + f if (self.matchfontfile(f, font)) return path if (Folder.isfolder(path)) { // Recurse var result = self.searchfontfolder(path, font) if (result) return result } } return nil } findfont(font) { var base = self.fontpath() for (f in base) { var found = self.searchfontfolder(f, font) if (found) return found } return nil } defaultfontname() { var platform = System.platform() if (platform=="macos") { return "Helvetica" } else return "FreeSans" } addcolor(color, out) { if (!self.colors.contains(color)) { var id = self.colors[color] = self.colors.count() out.write("c ${id} ${color.r} ${color.g} ${color.b}") } return self.colors[color] } addfont(font, size, out) { var path = self.findfont(font) if (!path) _fntntfnd.throw() var id = self.uid() var fnt = Font(font, size, id=id) self.fonts.append(fnt) out.write("F ${id} \"${path}\" ${size}") return id } findfontid(font, size, out) { for (f in self.fonts) { if (f.name == font && f.size == size) return f.id } return self.addfont(font, size, out) } visit(Text item, out) { var font = item.font if (!font) font = self.defaultfontname() var id = self.findfontid(font, item.size, out) if (item.color) { var id = self.addcolor(item.color, out) out.write("C ${id}") } out.write("i") var m = item.transformationmatrix() if (m) { var str = "m" for (i in 0..3) for (j in 0..3) str+=" "+String(m[i,j]) out.write(str) } out.write("t ${item.posn[0]} ${item.posn[1]} ${item.posn[2]}") out.write("T ${id} \"${item.string}\"") } write(graphic, out) { self.preamble(graphic, out) for (item in graphic.displaylist) self.visit(item, out) } } ================================================ FILE: modules/histogram.morpho ================================================ /* * Histogram * Simple command line histograms. */ // Find the minimum of an enumerable object fn hmin(x) { var mn=x[0] for (i in x) { if (imx) mx=i } return mx } // Display a histogram fn histogram(lst, nbins) { var cnt[nbins] var bins[nbins+1] // Calculate the bin bounds var mx = hmax(lst), mn = hmin(lst) for (i in 0..nbins) { bins[i]=mn+i*(mx-mn)/(nbins) } // Assign each element of lst to a bin for (x in lst) { var k=0 while (x>bins[k+1]) k+=1 cnt[k]+=1 } // Show histogram for (i in 0..nbins-1) { print "${bins[i]}-${bins[i+1]}: ${cnt[i]}" } } ================================================ FILE: modules/implicitmesh.morpho ================================================ /* Create meshes that are level sets of an implicit function Marching method for implicit surfaces E. Hartmann, The Visual Computer (1998) 14:95-108 */ import constants import meshtools class ImplicitPoint { init (pt, normal) { self.location = pt var n = normal/normal.norm() self.normal = n if (n[0]>0.5 || n[1]>0.5) self.t1 = Matrix([n[1], -n[0], 0]) else self.t1 = Matrix([-n[2], 0, n[0]]) self.t1 /= self.t1.norm() self.t2 = self.cross(n, self.t1) self.t2 /= self.t2.norm() self.frontangle = 0.0 self.border_point = false self.exclude = false self.angle_changed = false } cross(a, b) { return Matrix([ a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0] ]) } } class ImplicitMeshBuilder < MeshBuilder { init(f, gradient=nil) { self.func = f self.grad = gradient self.tol = 1e-10 self.eps = 1e-8 self.maxiter = 100 self.points = [] super.init() } // Evaluate the gradient of the function gradient(x) { if (self.grad) { return self.grad(x[0], x[1], x[2]) } else { return Matrix([ (self.func(x[0]+self.eps, x[1], x[2])-self.func(x[0]-self.eps, x[1], x[2]))/2/self.eps, (self.func(x[0], x[1]+self.eps, x[2])-self.func(x[0], x[1]-self.eps, x[2]))/2/self.eps, (self.func(x[0], x[1], x[2]+self.eps)-self.func(x[0], x[1], x[2]-self.eps))/2/self.eps ]) } } // Finds a nearby point on the level set by gradient descent surfacepoint(x0) { var x=x0, grad for (iter in 1..self.maxiter) { var f = self.func(x[0], x[1], x[2]) grad = self.gradient(x) var df = grad.norm()^2 if (df=omega1) return omega2-omega1 return omega2-omega1+2*Pi } recalculatefrontangles(poly) { var minfa = 2*Pi var np=poly.count() for (i in 0...np) { var pt = self.points[poly[i]] if (pt.angle_changed && !pt.border_point) { var ir = i+1 if (i==np-1) ir = 0 var fa =self.calculatefrontangle(poly[i-1], poly[i], poly[ir]) self.points[poly[i]].frontangle = fa if (fa1) { nnewt-=1; deltaomega=omega/nnewt } else if (nnewt==1 && deltaomega>0.8) { var xl = self.points[v1].location - self.points[v2].location if (xl.norm()>1.2*stepsize) { nnewt=2; deltaomega=deltaomega/2 } } else if (omega<3) { var xl = self.points[v1].location - self.points[vk].location var s1 = xl.norm() xl = self.points[v2].location - self.points[vk].location var s2 = xl.norm() if (s1<=0.5*stepsize || s2 <= 0.5*stepsize) nnewt=1 } // 3. Generate the triangles var vi[nnewt+1] // Store indices of new vertices vi[0] = v1; vi[nnewt] = v2 if (nnewt==1) { self.addface([v1,vk,v2]) } else { var pt = self.points[vk] // Project (v1-vk) onto tangent plane var xl = self.points[v1].location - pt.location var q0 = Matrix([xl.inner(pt.t1), xl.inner(pt.t2)]) q0 /= q0.norm() for (i in 1...nnewt) { var theta = i*deltaomega var qi[2] qi[0]=q0[0]*cos(theta) - q0[1]*sin(theta) qi[1]=q0[0]*sin(theta) + q0[1]*cos(theta) var x = pt.location+stepsize*(qi[0]*pt.t1+qi[1]*pt.t2) var xi = self.surfacepoint(x) vi[i]=self.addpoint(xi) } // Generate triangles for (i in 0...nnewt) self.addface([vi[i], vi[i+1], vk]) } // Correct the front polygon for (i in 0..nnewt) self.points[vi[i]].angle_changed=true poly.pop(k) for (i in 1...nnewt) poly.insert(k, vi[nnewt-i]) } splitpolygon(poly, i, j) { self.points[poly[i]].exclude = true self.points[poly[j]].exclude = true self.points[poly[i]].angle_changed = true self.points[poly[j]].angle_changed = true var new = [] for (k in i..j) new.append(poly[k]) for (k in i+1...j) poly.pop(i+1) return new } mergepolygons(poly, i, poly2, j) { self.points[poly[i]].exclude = true self.points[poly2[j]].exclude = true self.points[poly[i]].angle_changed = true self.points[poly2[j]].angle_changed = true var cut = [] for (k in j...poly2.count()) cut.append(poly2[k]) for (k in 0..j) cut.append(poly2[k]) cut.append(poly[i]) for (p, k in cut) poly.insert(i+k+1, p) self.front.remove(poly2) } pointcheck(poly1, i, poly2, j, stepsize) { var pt1 = self.points[poly1[i]], pt2 = self.points[poly2[j]] // Are either of the pts excluded from distance checks if (pt1.exclude || pt2.exclude) return false var sep = self.points[poly1[i]].location - self.points[poly2[j]].location if (sep.norm() > stepsize) return false // Todo: Check for "bad" near points by testing front angles return true } proximitycheck(poly, stepsize) { var split var np = poly.count() // Ensure there are no front angles < ~ 60deg for (id in poly) if (self.points[id].frontanglemaxiterations) break } while (self.front.count()>0) return super.build() } } ================================================ FILE: modules/kdtree.morpho ================================================ /* *************************************************************** * KDTree * ====== * This module implements the KDTree class for spatial search **************************************************************** */ class KDTreeNode { init (location, left, right) { self.location = location self.left = left self.right = right } } class KDTree { init(pts) { self.dimension = pts[0].dimensions()[0] self.head=self.build(pts.clone(), 0) self.tol=1e-15 } build(pts, depth) { // Build a tree or subtree from a list of points var np = pts.count() if (np==0) return nil var axis = mod(depth, self.dimension) // Sort points to find median along current axis pts.sort(fn (a, b) a[axis]-b[axis]) var ipivot=Int((np-1)/2) // Identify points on the left and right of this plane var left = [], right = [] for (i in 0...ipivot) left.append(pts[i]) for (i in ipivot+1...np) right.append(pts[i]) var node = KDTreeNode(pts[ipivot], self.build(left, depth+1), self.build(right, depth+1)) return node } donearest(pt, start, cdepth, bst, bdst) { var depth = cdepth var list = [] // Search the tree and track the nodes selected for (var node = start; !isnil(node); ) { list.append(node) var pivot = mod(depth, self.dimension) if (abs(pt[pivot] - node.location[pivot])0) { depth-=1 var node = list.pop() var dist = (pt-node.location).norm() // If this point is closer, it becomes the best point if (dist=query[i][0] && node.location[i]<=query[i][1]) } if (include) result.append(node) // Now check the subnodes var pivot = mod(depth, self.dimension) var aq = query[pivot] var x = node.location[pivot] if (x>=aq[0] && node.left) self.dosearch(query, node.left, depth+1, result) if (x<=aq[1] && node.right) self.dosearch(query, node.right, depth+1, result) } insert(pt) { // Inserts a point into the tree var depth = 0 for (var node = self.head, next; node!=nil; node=next) { var pivot = mod(depth, self.dimension) if (abs(pt[pivot] - node.location[pivot])len0) return 0 return ((len-len0)/len0)^2/2 } gradientfn(mesh, vert, id, el, grd) { var x0 = vert.column(el[0]) var x1 = vert.column(el[1]) var dx = x0-x1 var len = dx.norm() var len0 = self.ref[self.grade, id] if (len>len0) return var g = dx*(len-len0)/(len0^2*len) grd.setcolumn(el[0], g+grd.column(el[0])) grd.setcolumn(el[1], -g+grd.column(el[1])) } } /* ------------------------------------------------------- * Evaluates a scalar potential at the midpoint of * an element. Will be used to detect elements inside * forbidden areas. * ------------------------------------------------------- */ class ScalarPotentialMidpoint is Functional { init(hbar) { self.func = hbar super.init(1) } integrandfn(mesh, vert, id, el) { var dim = el.count() var x = vert.column(el[0]) for (var i=1; iself.ttol) return true } } if (dx.norm()>self.ttol) return true // old criterion return super.hasconverged() } didconverge() { // Checks if we converged according to superclass return super.hasconverged() } } /* ******************************************************** * Mesh generator * ******************************************************** */ var MshGnDim = Error("MshGnDim", "MeshGen only supports 2 or 3 dimensions") class MeshGen { init (f, bbox, weight=nil, quiet=false, method=nil) { self.func = f // The function to call if (!iscallable(f) && isobject(f) && f.has("func")) self.func = f.func // You can give a Domain object directly self.bbox = bbox // Bounding box self.weight = weight // Weight function fn defaultweight (x) { return 1 } // Provide a default weight function if (isnil(weight)) self.weight = defaultweight self.dim = bbox.count() // Dimensionality of problem self.quiet = quiet // Whether to report output var sep = [] for (range in bbox) sep.append(range[1]-range[0]) // Get stepsize self.h0 = min(sep) // Element separation self.stepsize = self.h0/5 // Stepsize for optimizer self.steplimit = self.h0/2 // Steplm for optimizer self.mesh = nil // Mesh self.problem = nil // Optimization problem self.fscale = 1.2 // Internal pressure if (self.dim>2) self.fscale = 1.1 self.etol = 1e-3 // Energy tolerance for optimization problem (it's deliberately loose) self.ttol = 0.5 // How much motion of the vertices before retriangulation self.xtol = 1e-12 // Tolerance for point comparisons self.method = [] // Method selection if (isstring(method)) self.method.append(method) if (islist(method)) for (m in method) self.method.append(m) self.maxiterations = 100 } initialgridmesh() { // Create the initial mesh if (!self.quiet) print "Creating initial mesh on regular grid" var f = self.func var vert = [] if (self.dim==2) { for (v in self.bbox[1]) { for (u in self.bbox[0]) { if (f(Matrix([u,v]))>0) vert.append([u,v]) } } } else if (self.dim==3) { for (w in self.bbox[2]) { for (v in self.bbox[1]) { for (u in self.bbox[0]) { if (f(Matrix([u,v,w]))>0) vert.append([u,v,w]) } } } } else { MshGnDim.throw() } self.retriangulate(vert) } initialrandommesh() { // Create an initial mesh from random points var vert = [] var bnds = [] var vol = 1 // compute the generalized volume of the element for (b in self.bbox) { var sep = b[b.enumerate(-1)-1]-b[0] // range of this dimension bnds.append([b[0], sep]) vol *= sep } var n = ceil(vol/(self.h0)^(self.dim)) if (!self.quiet) print "Creating initial mesh with ${n} random points" for (var nsuccess=0; nsuccess0) { vert.append(pt) nsuccess+=1 } } self.retriangulate(vert) } selectbboxpts() { var bnds = [], xtol = self.xtol for (range,k in self.bbox) { bnds.append(bounds(range)) } fn selectOnBbox2D(x,y) { return ( abs(x-bnds[0][0])0) { mb.addelement(self.dim, t) } } self.mesh=mb.build() self.mesh.addgrade(1) } vertices() { // Extract a list of vertices from the current mesh var v = self.mesh.vertexmatrix() var vlist = [] for (id in 0...v.dimensions()[1]) vlist.append(v.column(id)) return vlist } setupproblem() { // Setup optimization problem self.problem = OptimizationProblem(self.mesh) // Build reference lengths var lengths = Length().integrand(self.mesh) // Evaluate the weight function at the midpoint var hbar = ScalarPotentialMidpoint(self.weight).integrand(self.mesh) // Calculate preferred lengths var len0 = Field(self.mesh, grade=1) fn lnorm(l, n) { // n-norm of a matrix var sum = 0 for (a in l) sum+=a^n return sum^(1/n) } var lscale=self.fscale*(lnorm(lengths, self.dim)/lnorm(hbar, self.dim)) for (id in 0...self.mesh.count(1)) len0[id]=lscale*hbar[id] // Impose elasticity var lel = OneSidedHookeElasticity(len0) self.problem.addenergy(lel) // Use a one sided level set constraint to keep things in the domain var ls if (self.dim==2) { // [TODO]: Rewrite this with varargs when available ls = ScalarPotential(fn (x,y) self.func(Matrix([x,y]))) } else if (self.dim==3) { ls = ScalarPotential(fn (x,y,z) self.func(Matrix([x,y,z]))) } self.problem.addlocalconstraint(ls, onesided=true) } optimize() { // Perform optimization var opt = MGShapeOptimizer(self.problem, self.mesh, ttol=self.ttol*self.h0, localcheck=self.method.ismember("LocalCheck")) opt.fix(self.selectbboxpts()) // set steplimit such that the first step doesn't take vertices // further than ttol // Calculate the initial force var frc = opt.totalforcewithconstraints() var normfrc = frc.norm() // set initial force to be the minimum of the provided steplimit and // the stepsize required to move a distance of ttol opt.steplimit = min(self.steplimit, self.ttol/normfrc) // Similar for stepsize opt.stepsize = min(self.stepsize, self.ttol/normfrc/5) opt.etol = self.etol // Use a fairly loose convergence criterion opt.quiet = self.quiet if (self.method.ismember("FixedStepSize")) { opt.relax(100) } else { opt.linesearch(1) opt.conjugategradient(100) } return opt.didconverge() // Returns true if we converged } build(outputdim=3) { // Build the mesh if (self.method.ismember("StartRandom")) { self.initialrandommesh() } else self.initialgridmesh() for (iter in 0...self.maxiterations) { if (!self.quiet) print "Mesh generator iteration ${iter}." self.setupproblem() if (self.optimize()) break // Converged self.retriangulate(self.vertices()) } return self.mesh } } ================================================ FILE: modules/meshslice.morpho ================================================ /* Meshslice - Slices a Mesh along a plane */ import meshtools import delaunay var _errSlcEmpty = Error("SlcEmpty", "No slice has yet been taken.") var _errSlcPrmType = Error("SlcPrmType", "Slice point and direction must be a List or Matrix.") class Intersection { init(u, x) { self.u = u self.x = x } } class MeshSlicer { init(mesh, ptol=1e-8) { self.mesh = mesh self.newmesh = nil self.pt = nil self.dirn = nil self.basis = nil self.ptol = ptol self.pairs = nil self.map = nil self.vert = nil self.mb = nil } basis() { // Creates a basis perpendicular to the dirn var d = [] for (x in self.dirn) d.append(abs(x)) var order = d.order() var e = [self.dirn] for (i in 0..1) { var ei = Matrix(self.dirn.count()) // Build a vector in that direction ei[order[i]]=1 e.append(ei) } var f = self.orthogonalize(e) self.basis = [ f[1], f[2] ] } orthogonalize(v) { var u = [] for (i in 0...v.count()) { var ui = v[i] for (uj in u) ui -= uj*uj.inner(v[i])/uj.inner(uj) u.append(ui/ui.norm()) } return u } setpt(Matrix pt, Matrix dirn) { self.pt = pt self.dirn = dirn self.basis() } setpt(List pt, List dirn) { self.pt = Matrix(pt) self.dirn = Matrix(dirn) self.basis() } setpt(pt, dirn) { _errSlcPrmType.throw() } testintersection(indices) { // Finds if an element with indices intersects with the plane var minus=false, plus=false for (i in indices) { var x = self.vert.column(i) var t = sign((x - self.pt).inner(self.dirn)) if (t>0) plus=true if (t<0) minus=true } return (plus && minus) } findintersection(i,j) { // returns the point between i,j that lies on the intersection plane var x0 = self.vert.column(i) var x1 = self.vert.column(j) var t0 = (x0 - self.pt).inner(self.dirn) var t1 = (x1 - self.pt).inner(self.dirn) if (t0*t1>0) return nil // check if the points lie on the same side of the plane var u = t0/(t0-t1) return Intersection(u, (1-u)*x0 + u*x1) } vertexpairs(el) { // return all possible pairs of vertices from a list of vertices var out = [] var n = el.count() for (i in 0...n-1) for (j in i+1...n) { var a=el[i], b=el[j] if (a0) newshape[0]=max(shape[0], shape[1]) var f = Field(self.newmesh, fld[0], grade=newshape) // Interpolate vertex-based fields first if (interpolate) for (k in 0...shape[0]) { for (j in 0...self.pairs.dimensions()[1]) { var ri=self.pairs.rowindices(j) for (i in ri) { var fi = fld[0,i,k], fj = fld[0,j,k] var u = self.pairs[i,j].u var id = self.pairs[i,j].id f[0,id,k]=(1-u)*fi + u*fj } } } // Then copy information from higher elements for (g in 1..self.mesh.maxgrade()) { var nvar = shape[g] var dict = self.map[g] if (nvar==0) continue if (!isdictionary(dict)) continue for (id in dict) { // Copy the contents down into the next grade var fid = dict[id] if (isint(fid)) for (k in 0...nvar) f[g-1,fid,k]=fld[g,id,k] else for (l in fid) for (k in 0...nvar) f[g-1,l,k]=fld[g,id,k] } } return f } } ================================================ FILE: modules/meshtools.morpho ================================================ /* ********************************************************* * Meshtools * A module that provides various mesh creation, refinement * and visualization tools * ********************************************************* */ // Dependencies import constants import kdtree import delaunay // Error messages var _errMshBldDimIncnstnt = Error("MshBldDimIncnstnt", "Vertex dimension inconsistent with mesh dimension.") var _errMshBldDimUnknwn = Error("MshBldDimUnknwn", "Cannot add elements until a vertex has been added or MeshBuilder initialized with a specified dimension.") var _errMltVClstrFxd = Error("MltVClstrFxd", "More than one vertex in a selected cluster is fixed.") var _errMshMaxVrt = Error("MltMaxVrt", "Maximum number of vertices in Mesh exceeded.") var _MAX_VERTICES = 2147483647 /* ************************** * Manual mesh creation * ************************** */ // Grades var vertexGrade = 0 var lineGrade = 1 var areaGrade = 2 var volumeGrade = 3 class MeshBuilder { init(dimension=nil) { self.vertices = [] self.dimension = dimension self.elements = nil if (isnumber(dimension)) self.initdim(dimension) } initdim(dimension) { self.dimension = dimension self.elements = Array(dimension) for (i in 0...dimension) { self.elements[i]=[] } } addvertex(v) { if (self.vertices.count()==_MAX_VERTICES) _errMshMaxVrt.throw() var x = v if (islist(v)) x = Matrix(v) if (self.dimension==nil) self.initdim(x.count()) if (x.count()!=self.dimension) _errMshBldDimIncnstnt.throw() self.vertices.append(x) return self.vertices.count()-1 } addelement(grade, el) { if (!self.elements) _errMshBldDimUnknwn.throw() self.elements[grade-1].append(el) return self.elements[grade-1].count()-1 } addedge(el) { return self.addelement(lineGrade, el) } addface(el) { return self.addelement(areaGrade, el) } addvolume(el) { return self.addelement(volumeGrade, el) } makeconnectivity(el) { if (el.count()==0) return nil var s = Sparse(0,0) for (lst, id in el) for (v in lst) s[v,id]=1 return s } build() { var m = Mesh(); var nv = self.vertices.count() var vert = Matrix(self.dimension, nv) for (x, i in self.vertices) vert.setcolumn(i, x) m.setvertexmatrix(vert) for (el, i in self.elements) { var s = self.makeconnectivity(el) if (s) m.addgrade(i+1, s) } return m } } /* ****************************************** * A few constructors to create simple meshes * ****************************************** */ /** Creates a mesh from a parametric function. * @param[in] f - a function that returns the vertex position as a list * @param[in] range - parameter range to use * @param[in] closed - whether to close the mesh */ fn LineMesh(f, range, closed=false) { var dim = f(range[0]).count() var mb = MeshBuilder(dimension=dim) for (t in range) mb.addvertex(f(t)) var nlines = range.count()-1 for (i in 0...nlines) mb.addedge([i,i+1]) if (closed) mb.addedge([0,nlines]) return mb.build() } /** Creates a mesh from a parametric function. * @param[in] f - a function that returns the vertex position as a list * @param[in] range - parameter range to use * @param[in] range - parameter range to use * @param[in] closed - whether to close the mesh */ fn AreaMesh(f, r1, r2, closed=false) { var dim = f(r1[0],r2[0]).count() var mb = MeshBuilder(dimension=dim) var nu = r1.count(), nv = r2.count() // How many points if (Float(nu)*Float(nv)>_MAX_VERTICES) _errMshMaxVrt.throw() for (v in r2) for (u in r1) mb.addvertex(f(u,v)) var np = nu*nv var nfaces = (nu-1)*(nv-1) for (j in 0...nv-1) { for (i in 0...nu-1) { mb.addface([i+j*nu, i+1+j*nu, i+nu+j*nu]) mb.addface([i+1+j*nu, i+nu+j*nu, i+1+nu+j*nu]) } } // Close boundaries if selected var cu = closed, cv = closed if (islist(closed)) { cu=closed[0]; cv=closed[1] } if (cu) { var n=nu-1 // Connect right column to left column for (j in 0...nv-1) { mb.addface([n+j*nu, n+1+j*nu, 0+j*nu]) mb.addface([n+j*nu, 0+nu+j*nu, n+nu+j*nu]) } } if (cv) { var n=nv-1 // Connect top row to bottom row for (i in 0...nu-1) { mb.addface([i+n*nu, i+1+n*nu, i]) mb.addface([i+1+n*nu, i, i+1]) } } if (cu && cv) { // For toroidal geometry, connect top right to bottom left mb.addface([nu*nv-1, (nv-1)*nu, nu-1]) mb.addface([0, (nv-1)*nu, nu-1]) } return mb.build() } /** Creates a mesh from a polyhedron specified as @param[in] list - a list of vertices @param[in] faces - list of lists of vertices corresponding to each face @returns a mesh */ fn PolyhedronMesh(list, faces) { var dim=list[0].count() var mb=MeshBuilder(dimension=dim) var nv=list.count() // Number of vertices in polyhedron // Vertices for (x in list) mb.addvertex(x) for (f in faces) { // Add midpoints var pt = Matrix(3) for (i in f) pt.acc(1, Matrix(list[i])) mb.addvertex(pt/f.count()) } // Faces for (face, k in faces) { // Loop over faces var fv = face.count() for (i in 0...fv) { // Loop over the vertex indices and create a triangle for every pair var v1 = face[0] if (i self.tol) { // No, so create a new one var new = self.tree.insert(x) new.indx = super.addvertex(x) return new.indx } else { // Yes, so just return the index return nrst.indx } } else { // If the tree wasn't created yet, create it and add the vertex self.tree = KDTree([x]) self.tree.head.indx = super.addvertex(x) return self.tree.head.indx } } mergevertices(m) { // Merges vertices into the mesh var vert = m.vertexmatrix() var vdict = Dictionary() for (vid in 0...m.count()) { vdict[vid] = self.addvertex(vert.column(vid)) } return vdict } _initelists() { var mg = self.maxgrade() self.elists = Array(mg+1) var nv = self.vertices.count() for (g in 1..mg) self.elists[g] = ElementList(nv, g, sort=true) } _clearelists() { self.elists = nil } addelement(g, el) { var elist = self.elists[g] if (elist.ismember(el)) return elist.addelement(el) super.addelement(g, el) } mergegrade(m, g, dict) { // Merges elements of grade g into the mesh var conn = m.connectivitymatrix(0, g) for (id in 0...m.count(g)) { var newel = [] // Convert the indices into new vertex indices var duplicate = false // Check if the merged element contains duplicate vertices for (elid in conn.rowindices(id)) { if (newel.ismember(dict[elid])) duplicate=true newel.append(dict[elid]) } if (!duplicate) self.addelement(g, newel) } } merge() { var vdict = [] for (m in self.meshes) { // First merge the vertices vdict.append(self.mergevertices(m)) } self._initelists() // Prepare lists to keep track of elements for (m, k in self.meshes) { // Then merge all higher grades for (grade in 1..m.maxgrade()) { self.mergegrade(m, grade, vdict[k]) } } self._clearelists() return self.build() // Build the mesh } } /* ******************************** * Simple refinement ******************************** */ // Calculate the midpoint of an edge fn _midpoint(vertices, edges, i) { var edge = edges.rowindices(i) return 0.5*(vertices.column(edge[0])+vertices.column(edge[1])) } // Swaps elements of an indexable object fn _swap(e, i, j) { var swp = e[i]; e[i] = e[j]; e[j]=swp } /** Refines a mesh */ fn refinemesh(m) { var vertices = m.vertexmatrix() var edges = m.connectivitymatrix(0, 1) if (isnil(edges)) { // If no edges are present, we should try to add them m.addgrade(1) edges = m.connectivitymatrix(0, 1) if (isnil(edges)) { print "Failed to add edges"; return } } var faces = m.connectivitymatrix(0, 2) // Identify number of vertices, edges and faces in the old mesh var dim = vertices.dimensions()[0] var nv = vertices.dimensions()[1] var nl = edges.dimensions()[1] var nf = faces.dimensions()[1] var new = Mesh() // Each refined edge contributes a new vertex var newvertices = Matrix(dim, nv+nl) for (i in 0...nv) newvertices.setcolumn(i, vertices.column(i)) for (i in 0...nl) { newvertices.setcolumn(nv+i, _midpoint(vertices, edges, i)) } new.setvertexmatrix(newvertices) // Each edge becomes two edges var newedges = Sparse(nv+nl, nl) // Size is automatically updated var iedge = nl for (i in 0...nl) { var edge = edges.rowindices(i) newedges[edge[0], i]=1 // ] Edge 0 newedges[nv+i, i]=1 // ] newedges[nv+i, iedge]=1 // ] Edge 1 newedges[edge[1], iedge]=1 // ] iedge+=1 } // Refine faces if present. Creates a canonical order for the face /* a (e0[0]) / \ e0 x --- y e1 / \ / \ e0[1] b --- z -- c e2 */ var newfaces = Sparse(nv+nl, 4*nf) var iface = nf // Count over new triangles if (!isnil(faces)) { var faceedge = m.connectivitymatrix(1,2) for (i in 0...nf) { var fvert = faces.rowindices(i) // Vertices in this face var fedge = faceedge.rowindices(i) // Edges in this face var evert[3] // Vertices for each edge for (f, i in fedge) evert[i]=edges.rowindices(f) // evert 0 defines vertices a and b var va=evert[0][0], vb=evert[0][1], vc=evert[1][0] if (evert[0].ismember(vc)) vc = evert[1][1] // The vertices are now in canonical order // does edge 1 connect with a? if not swap if (!evert[1].ismember(va)) _swap(fedge, 1, 2) // The edges are now in a canonical order, so that evert[1] connects to the first element of evert[0] var vx = nv+fedge[0], vy = nv+fedge[1], vz = nv+fedge[2] // Triangle a-x-y newfaces[va,i]=1; newfaces[vx,i]=1; newfaces[vy,i]=1 // Triangle b-x-z newfaces[vb,iface]=1; newfaces[vx,iface]=1; newfaces[vz,iface]=1; iface+=1 // Triangle c-y-z newfaces[vc,iface]=1; newfaces[vy,iface]=1; newfaces[vz,iface]=1; iface+=1 // Triangle x-y-z newfaces[vx,iface]=1; newfaces[vy,iface]=1; newfaces[vz,iface]=1; iface+=1 // Edge x-y newedges[vx, iedge]=1; newedges[vy, iedge]=1; iedge+=1 // Edge x-z newedges[vx, iedge]=1; newedges[vz, iedge]=1; iedge+=1 // Edge y-z newedges[vy, iedge]=1; newedges[vz, iedge]=1; iedge+=1 } } new.addgrade(1, newedges) new.addgrade(2, newfaces) return new } /* ******************************** * Advanced refinement ******************************** */ /* ********************************** * Base class for adaptive refinement * ********************************** */ /* Subclasses must implement their own version of adaptmesh, which returns a list of dictionaries, one for each grade of element, Each dictionary has: * keys corresponding to the new element ids; * values can be either - a single elementid in the old mesh OR - a list of elements which will be averaged over */ class MeshAdaptiveRefiner { init (target) { // The target can either be a single mesh or a collection of objects self.target = target self.refinemap = nil self.new = nil } mesh() { // Returns the mesh being refined if (ismesh(self.target)) return self.target else if (islist(self.target)) { for (m in self.target) if (ismesh(m)) return m } return nil } // Subclasses should replace this method with their own version adaptmesh(selection) { } adaptfield(field) { // Maps a field onto a new mesh var mesh = self.mesh() var prototype = field.enumerate(0) if (isobject(prototype)) prototype=prototype.clone() var shape = field.shape() var fespace = field.finiteelementspace() var result if (self.refinemap && prototype) { result = Field(self.new, prototype, grade=shape, finiteelementspace=fespace) for (g in 0...shape.count()) { if (shape[g]==0) continue var dict = self.refinemap[g] for (newid in dict) { // Loop over the new ids var oldid = dict[newid] if (islist(oldid)) { // If this oldid is a list, average over these values var sum, nn = oldid.count() if (isfloat(prototype)) sum = 0 if (nn==0) continue for (i in 0...nn) sum+=field[g, oldid[i]] sum/=oldid.count() result[g, newid] = sum } else { result[g, newid] = field[g, oldid] } } } } return result } selectdown(el, oldsel, newsel) { var nv=self.mesh().count() // Max vertexid in old mesh var vmap = self.refinemap[0] var vselected=false for (vid in el) { // Check at least one vertex is selected if (vid0) self.selectdown(conn.rowindices(id), sel, result) } } } } return result } adapt(selection=nil) { // Base adaptive refinement sequence var newmesh = self.adaptmesh(selection) // Returns a refinement dictionary that maps old objects to new var dict = { self.mesh() : self.new } if (islist(self.target)) { for (el in self.target) { if (isfield(el)) dict[el] = self.adaptfield(el) if (isselection(el)) dict[el] = self.adaptselection(el) } } return dict } } /* ************************** * Refinement * ************************** */ class MeshRefiner is MeshAdaptiveRefiner { init (target) { self.target = target self.refinemap = nil self._clearelists() self.new = nil } mesh() { if (ismesh(self.target)) return self.target else if (islist(self.target)) { for (m in self.target) if (ismesh(m)) return m } return nil } _initelists(maxgrade, nv) { self.elists = Array(maxgrade+1) for (g in 1..maxgrade) self.elists[g] = ElementList(nv, g, sort=true) } _clearelists() { self.elists = nil } split(id, el, vert, dict) { // split elements, generating new vertices var nv = el.count() for (i in 0...nv) { // Loop over distinct pairs for (j in i+1...nv) { // var midpoint = (vert.column(el[i])+vert.column(el[j]))/2 if (!self.tree.ismember(midpoint)) { // Check if already exists var id = self.mb.addvertex(midpoint) // Add vertex self.tree.insert(midpoint).id = id dict[id]=[el[i],el[j]] // dict[new vertex] -> [oldvertices] } } } } addelement(srcid, grade, el, dict) { if (self.elists[grade].ismember(el)) return nil var id = self.mb.addelement(grade, el) self.elists[grade].addelement(el) if (isdictionary(dict)) dict[id] = srcid return id } refine1(id, el, vert, nel, dict) { // refine edges var midpoint = (vert.column(el[0])+vert.column(el[1]))/2 var mp = self.tree.ismember(midpoint) if (mp) { self.addelement(id, lineGrade, [el[0], mp.id], dict) self.addelement(id, lineGrade, [mp.id, el[1]], dict) } // refinement dictionary key: new edge id -> old edge id return isobject(mp) } refine2(id, el, vert, nel, dict) { // refine faces var nv = el.count() var refedge = [] for (i in 0...nv) { // Generate midpoints for (j in i+1...nv) { var midpoint = (vert.column(el[i])+vert.column(el[j]))/2 var node = self.tree.ismember(midpoint) if (node) { refedge.append([el[i], el[j], node.id]) } } } /* Three possible outcomes of refinement: . . . / | \ / | \ / \ / | \ / | * * --- * / | \ / | / \ / \ / \ . --- * -- . . --- * -- . . --- * -- . */ var nref = refedge.count() if (nref==0) { return false } else if (nref==1) { var tid for (id in el) if (!refedge[0].ismember(id)) tid = id self.addelement(id, areaGrade, [refedge[0][0], refedge[0][2], tid], dict) self.addelement(id, areaGrade, [refedge[0][2], refedge[0][1], tid], dict) if (nel[1]>0) { self.addelement(id, lineGrade, [refedge[0][2], tid], nil) } } else if (nref==2) { var shareid, xid, yid for (id in el) { if (refedge[0].ismember(id) && refedge[1].ismember(id)) shareid = id if (refedge[0].ismember(id) && !refedge[1].ismember(id)) xid = id if (!refedge[0].ismember(id) && refedge[1].ismember(id)) yid = id } self.addelement(id, areaGrade, [shareid, refedge[0][2], refedge[1][2]], dict) self.addelement(id, areaGrade, [xid, refedge[0][2], refedge[1][2]], dict) self.addelement(id, areaGrade, [xid, yid, refedge[1][2]], dict) if (nel[1]>0) { self.addelement(id, lineGrade, [refedge[1][2], xid], nil) self.addelement(id, lineGrade, [refedge[0][2], refedge[1][2]], nil) } } else if (nref==3) { self.addelement(id, areaGrade, [el[0], refedge[0][2], refedge[1][2]], dict) self.addelement(id, areaGrade, [el[1], refedge[0][2], refedge[2][2]], dict) self.addelement(id, areaGrade, [el[2], refedge[1][2], refedge[2][2]], dict) self.addelement(id, areaGrade, [refedge[0][2], refedge[1][2], refedge[2][2]], dict) if (nel[1]>0) { self.addelement(id, lineGrade, [refedge[0][2], refedge[1][2]], nil) self.addelement(id, lineGrade, [refedge[1][2], refedge[2][2]], nil) self.addelement(id, lineGrade, [refedge[2][2], refedge[0][2]], nil) } } return true } refine3(id, el, vert, nel, dict) { // refine volumes var nv = el.count() // Number of vertices in the element var pts = [] // Points defining the element var localmap = Dictionary() // Maps local ids in pts to correct ids for (i in 0...nv) { // Find vertex positions and ids pts.append(vert.column(el[i])) localmap[i]=el[i] } for (i in 0...nv) { // Loop over all unique pairs for (j in i+1...nv) { // and find midpoints var midpoint = (vert.column(el[i])+vert.column(el[j]))/2 var node = self.tree.ismember(midpoint) // Find whether this midpoint actually exists if (node) { localmap[pts.count()]=node.id pts.append(midpoint) } } } var tri = Delaunay(pts).triangulate() // Now perform the Delaunday triangulation fn _mapel(el) { // Map local ids for this element onto the correct ids var ntri = [] for (id in el) ntri.append(localmap[id]) return ntri } fn _checkel(el) { // Check if this element contains any old vertices for (id in el) if (id values: new vertex ids adaptmesh(selection) { var m = self.mesh() self.mb = MeshBuilder() var vertices = m.vertexmatrix() var dim = vertices.dimensions()[0] var nv = vertices.dimensions()[1] var refmap = [ ] // Dictionary describing how element ids are mapped over var nel[dim+1] // Number of elements in each grade for (g in 0..dim) nel[g]=m.count(g) var vlist = [] var vdict = Dictionary() // Vertex map dictionary for (vid in 0...nv) { // Loop over vertexids in old mesh var x = vertices.column(vid) // Get the vertex position vlist.append(x) var newid = self.mb.addvertex(x) vdict[newid] = vid // Add the vertex to the new mesh } self.tree=KDTree(vlist) // Create new vertices for (g in 1..dim) { var el = m.connectivitymatrix(0, g) for (i in 0...nel[g]) { // Loop over elements if (selection && !selection.isselected(g, i)) continue //print "Split grade ${g} element ${i}" self.split(i, el.rowindices(i), vertices, vdict) } } refmap.append(vdict) // Now loop over all elements and either copy or refine them self._initelists(dim, self.mb.vertices.count()) for (g in 1..dim) { var refinemethod = "refine${g}" var el = m.connectivitymatrix(0, g) var dict = Dictionary() for (i in 0...nel[g]) { // Loop over elements //print "Refine grade ${g} element ${i}" var eind = el.rowindices(i) if (!self.invoke(refinemethod, i, eind, vertices, nel, dict)) { self.addelement(i, g, eind, dict) // Just copy the element across } } refmap.append(dict) } self._clearelists() self.refinemap = refmap return (self.new=self.mb.build()) } refine(selection=nil) { return self.adapt(selection=selection) } } /* ************************** * Pruning * ************************** */ class MeshPruner is MeshAdaptiveRefiner { init (target, fix=nil) { self.fix = fix // Fixed vertices self.clusters = nil // A list of clusters self.vmap = nil // A dictionary that maps elements to clusters super.init(target) } findclusterfromvertex(vid) { // Finds the cluster containing a vertex, if any if (self.vmap.contains(vid)) return self.vmap[vid] return nil } countvertices(vlist) { // Counts the number of vertices in vlist that belong to any cluster var count = 0 for (vid in vlist) { if (self.vmap.contains(vid)) count+=1 } return count } countmaxverticesincluster(vlist) { // Counts the number of maximum number of vertices in vlist that belong to a single cluster var count = Dictionary() var max = 0 for (vid in vlist) { if (self.vmap.contains(vid)) { var cluster = self.vmap[vid] if (count.contains(cluster)) count[cluster]+=1 else count[cluster]=1 } } for (c in count) if (count[c]>max) max=count[c] return max } addcluster(vlist) { // Creates a new cluster from a list of vertices var newcluster = [] self.clusters.append(newcluster) self.addverticestocluster(newcluster, vlist) } addverticestocluster(cluster, vlist) { // Adds vertices to a cluster for (v in vlist) { if (!cluster.ismember(v)) cluster.append(v) self.vmap[v]=cluster } } mergeclusters(clusters) { // Merges clusters into one for (k in 1...clusters.count()) { self.addverticestocluster(clusters[0], clusters[k]) self.clusters.remove(clusters[k]) } } collapseelement(vlist) { // Collapses the vertices in element var clusters = [] for (v in vlist) { var c = self.findclusterfromvertex(v) // Make sure we don't find duplicate clusters if (c && !_elinlist(clusters, c)) clusters.append(c) } var nc = clusters.count() // Did any of these vertices belong to a cluster? if (nc==0) { // No, so they form a new cluster self.addcluster(vlist) } else { // Yes, so we add them to an existing cluster self.addverticestocluster(clusters[0],vlist) if (nc>1) self.mergeclusters(clusters) // Merge if they combine more than one } } updateelement(vdict, vlist) { // Maps elements of vlist using vdict var nvlist = [] for (v in vlist) nvlist.append(vdict[v]) return nvlist } isfixed(vid) { // Test if a vertex is fixed if (self.fix) return self.fix[0, vid] return false } clustercenter(vlist) { // Find the center of a cluster var m = self.mesh() var fix var x for (vid in vlist) { if (self.isfixed(vid)) { if (fix) _errMltVClstrFxd.throw() fix=m.vertexposition(vid) break } x+=m.vertexposition(vid) } if (fix) x=fix else if (x) x/=vlist.count() return x } adaptmesh(selection) { var m = self.mesh() self.clusters = [] self.vmap = Dictionary() // Identify clusters of points to collapse for (g in 1..m.maxgrade()) { var conn = m.connectivitymatrix(0, g) var ids = selection.idlistforgrade(g) for (elid in ids) { var el=conn.rowindices(elid) self.collapseelement(el) } } // Now build the new mesh var mb = MeshBuilder() var refmap = [ ] // Dictionary describing how element ids are mapped over var vdict = Dictionary(), // Map newids to oldids vmap = Dictionary() // Map old vertex ids to new ids for (oldid in 0...m.count()) { // Add vertices that aren't being collapsed if (self.findclusterfromvertex(oldid)) continue var newid = mb.addvertex(m.vertexposition(oldid)) vdict[newid]=oldid vmap[oldid]=newid } for (cluster in self.clusters) { // Collapse each cluster to a new vertex var x = self.clustercenter(cluster) var newid = mb.addvertex(x) vdict[newid]=cluster for (oldid in cluster) vmap[oldid]=newid } refmap.append(vdict) // Now add appropriate elements for (g in 1..m.maxgrade()) { var conn = m.connectivitymatrix(0, g) var dict = Dictionary() var dup = Dictionary() for (id in 0...m.count(g)) { var el = conn.rowindices(id) var nvcl = self.countvertices(el) // Number of vertices in a cluster if (nvcl<2 || self.countmaxverticesincluster(el)==1) { var newel = self.updateelement(vmap, el) var indices = newel.clone() indices.sort() indices = apply(Tuple, indices) if (dup.contains(indices)) continue dup[indices]=true var newid = mb.addelement(g, newel) dict[newid] = id } } refmap.append(dict) } self.refinemap = refmap self.new = mb.build() return self.new } prune(selection) { return self.adapt(selection=selection) } } ================================================ FILE: modules/optimize.morpho ================================================ /* ************************************ * Optimization ************************************** */ // Minimize a 1-d function by Brent's algorithm fn brent(bracket, func, tol, itermax) { var zeps = 1e-10, cgold = 0.3819660 fn sign(x, y) { // Returns sign(y)*|x| if (y<0) return -abs(x) if (y>0) return abs(x) return 0.0 } var iter var a, b // Minimum lies between a and b if (bracket[0]bracket[2]) b=bracket[0] else b=bracket[2] var d=0.0, e=0.0 var xm var v=bracket[1], w=v, x=v // Initialize var fv=func(x), fw=fv, fx=fv for (iter in 0...itermax) { xm=0.5*(a+b) var tol1=tol*abs(x)+zeps, tol2=2*tol1 // Check if converged if (abs(x-xm) <= (tol2-0.5*(b-a))) return [x, fx] if (abs(e) > tol1) { // Model the function by a parabola var r = (x-w)*(fx-fv), q = (x-v)*(fx-fw), p = (x-v)*q-(x-w)*r q=2*(q-r) if (q>0) p = -p q=abs(q) var etemp = e e=d // Check if the parabolic fit is acceptable if (abs(p) >= abs(0.5*q*etemp) || p<= q*(a-x) || p>= q*(b-x)) { // Bad: Take golden section step if (x>=xm) e=a-x else e=b-x d = cgold*e } else { // Good: Use parabolic step d=p/q var u=x+d if (u-a < tol2 || b-u < tol2) d = sign(tol1, xm-x) } } else { if (x>=xm) e=a-x else e=b-x d = cgold*e } var u if (abs(d)>=tol1) u=x+d else u=x+sign(tol1, d) var fu = func(u) // Evaluate function // Update bracket if (fu<=fx) { if (u>=x) a=x else b=x v=w; w=x; x=u fv=fw; fw=fx; fx=fu } else { if (u0) { var residual, i=0 do { var dv = self.testconstraints(), fv = [] for (cons in acons) { var ff = self.gradient(cons) self.subtractlocalconstraints(ff) fv.append(ff) } var m=self.forceinnerproducts(fv) var sol = dv/m for (i in 0...nc) { v.acc(sol[i],fv[i]) } residual=self.testconstraints().norm() i+=1 if (i>self.maxconstraintsteps) { print "Warning: Too many steps in constraint satisfaction" return } } while (residual>self.ctol) } } /* Find which local constraints are active */ initlocalconstraints() { self.lcactive = [] for (cons in self.localconstraints()) { if (cons.onesided) { var integrand = self.integrand(cons) var sel = Selection(self.target, fn (q) q0) { var residual, iter=0 var dv = self.testlocalconstraints() if (self.lcnorm(dv)>self.ctol) do { var fv = [] // Calculate constraint forces for (cons, i in localconstraints) { fv.append(self.gradient(cons, selection=self.lcactive[i])) } var nactive = 0 // Loop over vertices for (k in 0...nv) { // Find the discrepencies of each force at the vertex var vv = Matrix(nc) for (i in 0...nc) vv[i] = -dv[i][0,k] // Note minus sign if (vv.norm()>self.ctol/nv) { // Identify constraints active on this vertex var va = [], fa = [] for (i in 0...nc) { if (abs(vv[i])>self.ctol/nv) { va.append(vv[i]) if (msh) fa.append(fv[i].column(k)) else fa.append(fv[i][k]) } } // Now solve for the necessary motion var m=self.forceinnerproducts(fa) if (m.norm()0) residual/=nactive iter+=1 if (iter>self.maxconstraintsteps && residual>self.ctol) { print "Warning: Too many steps in local constraint satisfaction. (Try increasing maxconstraintsteps or set ctol to a number greater than ${residual})." return } } while (residual>self.ctol) } } /* Take a step */ step(stepsize) { var target = self.gettarget() var frc = self.force // Use the force if (!frc) return target.acc(-stepsize, frc) // Take a step self.initlocalconstraints() self.reprojectlocalconstraints() // Reproject onto local constraints self.reprojectconstraints() // Push back onto constraints } /* Adaptive stepsize */ energywithstepsize(size) { var target = self.gettarget() var vsave = target.clone() self.step(size) // Take the step var energy=self.totalenergy() self.settarget(vsave) return energy } /* Bracket the stepsize */ bracketstepsize() { var s = [ 0, self.stepsize, 2*self.stepsize ] if (2*self.stepsize>self.steplimit) { s[1]=self.steplimit/2 s[2]=self.steplimit } var bracket = [ self.totalenergy(), self.energywithstepsize(s[1]), self.energywithstepsize(s[2]) ] var iter = 0 while (!(bracket[1]bracket[0]) { // Step size is too big s[1]=s[1]/2 bracket[1]=self.energywithstepsize(s[1]) } else if (bracket[2]self.steplimit) { // Ensure we don't exceed steplimit if (bracket[1]self.maxbracketsteps) { print "Too many steps in bracketing stepsize. Current stepsizes [${s[0]},${s[1]},${s[2]}] and bracket [${bracket[0]},${bracket[1]},${bracket[2]}]" return nil } iter+=1 } return [s, bracket] } /* Test for convergence */ hasconverged() { var energy = self.energy if (energy.count()>1) { var de = abs(energy[-1]-energy[-2]) if (abs(energy[-1]) self.steplimit) self.stepsize = self.steplimit self.step(self.stepsize) self.energy.append(self.totalenergy()) self.report(i) if (self.hasconverged()) break } return self.energy } /* Conjugate gradient */ conjugategradient(n) { if (self.energy.count()==0) self.energy.append(self.totalenergy()) var oforce, dk for (i in 0...n) { var force = self.totalforcewithconstraints() if (oforce) { // Fletcher-Reeves formula //var beta = force.inner(force)/oforce.inner(oforce) // Hager and Zhang formula var yk = (oforce-force) var dkyk = dk.inner(yk) var beta = (yk - 2*dk*yk.inner(yk)/dkyk).inner(force)/dkyk dk=-force+beta*dk self.force = -dk } else { self.force = force dk = -force } if (!self.dolinesearch()) break if (self.stepsize > self.steplimit) self.stepsize = self.steplimit self.step(self.stepsize) self.energy.append(self.totalenergy()) self.report(i) if (self.hasconverged()) break oforce = force } return self.energy } update(dict) { if (dict.contains(self.target)) self.target = dict[self.target] } } /* Optimize meshes */ class ShapeOptimizer is Optimizer { gettarget() { return self.problem.mesh.vertexmatrix() } settarget(v) { self.problem.mesh.setvertexmatrix(v) } energies() { return self.problem.energies } constraints() { var sc = [] for (c in self.problem.constraints) { if (c.field==nil) sc.append(c) } return sc } localconstraints() { var sc = [] for (c in self.problem.localconstraints) { if (c.field==nil) sc.append(c) } return sc } evalgradient(func, sel) { if (isselection(sel)) { return func.functional.gradient(self.target, sel) } else { return func.functional.gradient(self.target) } } sublocal(f, g) { var nv = f.dimensions()[1] for (var i=0; iself.ctol) { var fc=f.column(i) // Note we only retrieve the column of f if needed var lambda=fc.inner(gc)/gg fc.acc(-lambda, gc) f.setcolumn(i, fc) } } } fixgrad(grad) { if (islist(self.fixed)) { var zero = Matrix(grad.dimensions()[0], 1) for (i in self.fixed) { grad.setcolumn(i, zero) } } } fix(s) { if (isnil(self.fixed)) self.fixed=[] var lst = s if (isselection(s)) lst = s.idlistforgrade(0) if (islist(lst)) { // Add these to the list if they're not already in it for (i in lst) { if (!(self.fixed.ismember(i))) self.fixed.append(i) } } } unfix(s) { if (isnil(self.fixed)) return var lst = s if (isselection(s)) lst = s.idlistforgrade(0) if (islist(lst)) { // Add these to the list if they're not already in it for (i in lst) { if (self.fixed.ismember(i)) self.fixed.remove(i) } } } } /* Optimize fields */ class FieldOptimizer is Optimizer { gettarget() { return self.target } settarget(val) { self.target.assign(val) } checkfield(fld) { if (islist(fld)) return fld.ismember(self.target) return (fld==self.target) } energies() { // Selects which energies are pertinent to the optimization problem var en = [] for (e in self.problem.energies) { if (e.functional.has("field")) { if (self.checkfield(e.functional.field)) en.append(e) } } return en } constraints() { var sc = [] for (c in self.problem.constraints) { if (c.field==self.target) sc.append(c) } return sc } localconstraints() { var sc = [] for (c in self.problem.localconstraints) { if (c.field==self.target) sc.append(c) } return sc } evalgradient(func, sel) { if (isselection(sel)) { return func.functional.fieldgradient(self.target, self.problem.mesh, sel) } else { return func.functional.fieldgradient(self.target, self.problem.mesh) } } sublocal(f, g) { for (gi, i in g) { var gg=gi.inner(gi) if (abs(gg)>self.ctol) { var lambda=f[i].inner(gi)/gg f[i].acc(-lambda, gi) } } } fixgrad(grad) { var sel=self.fixed var zero = 0 var prototype = grad[0] if (ismatrix(prototype)) { var dim = prototype.dimensions() zero=Matrix(dim[0], dim[1]) } if (isselection(sel)) { var shape = grad.shape() for (g in 0...shape.count()) { if (shape[g]==0 || sel.count(g)==0) continue // skip empty grades for (id in sel.idlistforgrade(g)) { for (k in 0...shape[g]) grad[g, id, k]=zero } } } } fix(s) { if (isnil(self.fixed)) self.fixed=Selection(self.target.mesh()) if (isselection(s)) self.fixed=self.fixed.union(s) else print "fix() requires a selection" } unfix(s) { if (isnil(self.fixed)) return if (isselection(s)) self.fixed=self.fixed.difference(s) else print "unfix() requires a selection" } } ================================================ FILE: modules/parser.morpho ================================================ // Basic tokenizer class StringStream { init(str) { self.str = str self.len = str.count() self.n = 0 } readchar() { if (self.n>=self.len) return nil var out = self.str[self.n] self.n+=1 return out } eof() { return (self.n >= self.len) } } class Tokenizer { init(stream, whitespace=" \n") { self.stream = stream // The stream to use self.current = self.advance() // Current character self.whitespace = Dictionary() // Whitespace characters for (c in whitespace) self.whitespace[c] = true } advance() { self.current = self.stream.readchar() return self.current } atend() { return self.stream.eof() } iswhitespace(c) { return self.whitespace.contains(c) } skipwhitespace() { while (self.iswhitespace(self.current) && !self.atend()) { self.advance() } } next() { var token = nil self.skipwhitespace() while (!self.atend()) { var char = self.current if (!char) break if (self.iswhitespace(char)) break if (token) token+=char else token=char self.advance() } return token } } ================================================ FILE: modules/plot.morpho ================================================ /* Plotting and visualization */ import graphics import color import meshtools var _pltmshcolerr = Error("PltMshCl", "Color specification for plotmesh should be either a color, a list of [r,g,b] values or a list of colors for each grade.") var _pltinvldcolerr = Error("PltInvldCl", "Could not convert color to matrix.") /* 3d cross product */ fn cross3d(a, b) { return Matrix([ a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0] ]) } /* Finds a bounding box from a vertex matrix */ fn _bbox(m) { var col = m.transpose() var bnds = [] for (i in 0...col.dimensions()[1]) bnds.append(bounds(col.column(i))) return bnds } /** Deduces the plot color for a given grade */ fn _plotcolorforgrade(grade, color) { if (isnil(color)) return Gray(0.5) // No color provided, so use a default color if (islist(color) || isarray(color)) { if (color.count()==3 && isnumber(color[0]) && isnumber(color[1]) && isnumber(color[2])) return color // Color was given as a simple list // Different colors for each grade if (isobject(color[grade]) || islist(color[grade]) || ismatrix(color[grade])) return color[grade] if (isnil(color[grade])) return Gray(0.5) } if (isobject(color)) return color _pltmshcolerr.throw() } /** Finds the appropriate color for a given element */ fn _plotcolorforelement(color, element) { var col=color if (ismatrix(col)) col=color.column(element) return col } /** Converts a color to a matrix */ fn _plotcolortomatrix(color) { if (ismatrix(color)) return color if (islist(color)) return Matrix(color) if (isobject(color)) return Matrix(color.rgb(0)) _pltinvldcolerr.throw() return nil } /** Converts a vertex matrix to a list of 3D points */ fn _vertto3d(vert) { var out = [] var dim = vert.dimensions()[0] var nv = vert.dimensions()[1] if (dim>3) dim=3 for (i in 0...nv) { var pt = Matrix(3) var v = vert.column(i) for (j in 0...dim) pt[j]=v[j] out.append(pt) } return out } /** Plots elements of a mesh * @param[in] mesh - mesh to plot * @param[in] selection - selection * @param[in] grade - Grade to show * @param[in] color - How to color elements: can be a single color a matrix with columns corresponding to elements a list of such matrices for each grade */ fn plotmesh(mesh, selection=nil, grade=nil, color=nil, filter=nil, transmit=nil) { var g = Graphics() var glist = [] if (isnil(grade)) glist.append(mesh.maxgrade()) if (islist(grade)) for (q in grade) glist.append(q) if (isnumber(grade)) glist.append(grade) var vert = _vertto3d(mesh.vertexmatrix()) var flat = nil var bb = _bbox(mesh.vertexmatrix()) var centroid = Matrix(3) var size[bb.count()] for (x, i in bb) { size[i]=x[1]-x[0] centroid[i]=x[0]+size[i]/2 if (abs(size[i])<1e-10) flat = i } if (glist.ismember(0)) { // Show points as spheres var vcol=_plotcolorforgrade(0, color) // Will either be a color or a matrix var np = mesh.count(0) for (i in 0...np) { if (isselection(selection) && !selection.isselected(0, i)) continue // Skip unselected components g.display(Sphere(vert[i], 0.025, color=_plotcolorforelement(vcol, i), filter=filter, transmit=transmit)) } } if (glist.ismember(1)) { // Show lines as cylinders var lcol=_plotcolorforgrade(1, color) // Will either be a color or a matrix var lines=mesh.connectivitymatrix(0,1) if (issparse(lines)) { var nl = mesh.count(1) for (i in 0...nl) { if (isselection(selection) && !selection.isselected(1, i)) continue // Skip unselected components var el = lines.rowindices(i) g.display(Cylinder(vert[el[0]], vert[el[1]], aspectratio=0.05, color=_plotcolorforelement(lcol, i), filter=filter, transmit=transmit)) } } } if (glist.ismember(2)) { // Show faces var fcol=_plotcolorforgrade(2, color) // Will either be a color or a matrix var faces=mesh.connectivitymatrix(0,2) var interpolate=false if (issparse(faces)) { var nf = mesh.count(2) var nnv = 0, nnt = nf // Count number of triangles and vertices generated if (isselection(selection)) nnt=selection.idlistforgrade(2).count() if (ismatrix(fcol) && fcol.dimensions()[1]!=nf && fcol.dimensions()[1]==mesh.count(0)) interpolate = true var nvert = Matrix(3, 3*nnt) // New array of vertices var norm = Matrix(3, 3*nnt) // New array of normals var ncol = Matrix(3, 3*nnt) // New array of normals var tri = Sparse(3*nnt, nnt) // Connectivity nnt=0 for (i in 0...nf) { // Draw a triangle for each face if (isselection(selection) && !selection.isselected(2, i)) continue // Skip unselected components var el = faces.rowindices(i) var v[3] for (p, i in el) v[i]=vert[p] var normal if (flat) { normal=Matrix(bb.count()) normal[flat]=1 } else { normal=cross3d(v[2]-v[1], v[1]-v[0]) if (normal.inner(v[0]-centroid)<0) { normal=-normal } } var tcol if (!interpolate) tcol = _plotcolortomatrix(_plotcolorforelement(fcol, i)) for (j in 0...3) { tri[nnv,nnt]=1 nvert.setcolumn(nnv, v[j]) norm.setcolumn(nnv, normal) if (interpolate) tcol = _plotcolortomatrix(_plotcolorforelement(fcol, el[j])) ncol.setcolumn(nnv, tcol) nnv+=1 } nnt+=1 } g.display(TriangleComplex(nvert, norm, ncol, tri, filter=filter, transmit=transmit)) } } return g } /** Visualizes a selection * @param[in] mesh - mesh to plot * @param[in] selection - selection * @param[in] grade - Grades to show */ fn plotselection(mesh, selection, grade=nil, filter=nil, transmit=nil) { var ngrades = mesh.maxgrade() var col[ngrades+1] for (g in 0..ngrades) { col[g]=nil if (islist(grade) && !grade.ismember(g)) continue // Skip over grades else if (isint(grade) && grade!=g) continue var selected = selection.idlistforgrade(g) if (selected.count()==0) continue // Skip over empty grade var nel = mesh.count(g) // Make a color matrix var cmat = Matrix(3, nel) for (i in 0...nel) { cmat.setcolumn(i, Matrix([0.5,0.5,0.5])) } for (i in selected) { // Highlight selected cmat.setcolumn(i, Matrix([1,0,0])) } col[g]=cmat } return plotmesh(mesh, grade=grade, color=col, filter=filter, transmit=transmit) } /** Visualizes a field * @param[in] field - the field * @param[in] selection - Selection to use * @param[in] grade - Grade to show * @param[in] colormap - Colormap to use * @param[in] style - style to use * @param[in] scale - whether or not to scale values */ fn plotfield(field, selection=nil, grade=nil, colormap=nil, style=nil, scale=true, filter=nil, transmit=nil, scalebar=nil, cmin=nil, cmax=nil) { var mesh = field.mesh() var shape = field.shape() var sel = selection var ngrades = shape.count() var showgrades = [] // Grades to show var col[ngrades+1] var cm=colormap if (cm==nil) cm = ViridisMap() // Default color map var bnd=bounds(field) // Find bounds for the field if (!isnil(cmin)) bnd[0] = cmin if (!isnil(cmax)) bnd[1] = cmax var sc = bnd[1]-bnd[0] if (abs(sc)>1e-16) sc=1/sc else sc = 1 if (style=="interpolate") { // Interpolate scalar field onto areas var nv=mesh.count(0) // Number of elements in this grade var cmat = Matrix(3, nv) // Make a color matrix if (field.shape()[0]==0) { Error("PltFldInt", "Can't use 'interpolate' style: Field lacks values on vertices.").throw() } if (sel) { // Ensure we have elements to plot if (sel.count(2)==0) { // If the selection didn't include facets, infer them from the vertices sel=sel.clone() sel.addgrade(2) } else if (sel.count(0)==0) { // If the selection didn't include vertices, infer them from the facets sel=sel.clone() sel.addgrade(0) } if (sel.count(2)==0 || sel.count(0)==0) Error("PltSlctnEmpty", "Selection is empty.").throw() } for (i in 0...nv) { if (sel && !sel[0, i]) continue var val = field[0, i] if (!isnil(cmin)) val = max(cmin, val) if (!isnil(cmax)) val = min(cmax, val) if (scale) val=(val-bnd[0])*sc cmat.setcolumn(i, Matrix(cm.rgb(val))) } col[2]=cmat showgrades.append(2) } else { for (g in 0...ngrades) { if (shape[g]==0) continue var nel=mesh.count(g) // Number of elements in this grade var cmat = Matrix(3, nel) // Make a color matrix for (i in 0...nel) { if (sel && !sel[g, i]) continue var val = field[g, i] if (!isnil(cmin)) val = max(cmin, val) if (!isnil(cmax)) val = min(cmax, val) if (scale) val=(val-bnd[0])*sc cmat.setcolumn(i, Matrix(cm.rgb(val))) } col[g]=cmat showgrades.append(g) } } var out = plotmesh(mesh, selection=sel, grade=showgrades, color=col, filter=filter, transmit=transmit) if (isobject(scalebar)) { var s = scalebar s.colormap=cm out+=s.draw(bnd[0], bnd[1]) } return out } /** Plots a set of axes * @param[in] xx0 - a point at which to plot the axes */ fn plotaxes(xx0, size=1) { var x0 = xx0 if (islist(xx0)) x0 = Matrix(xx0) var axes = Graphics() axes.display(Arrow(x0, x0+Matrix([size,0,0]), color=Red)) axes.display(Arrow(x0, x0+Matrix([0,size,0]), color=Green)) axes.display(Arrow(x0, x0+Matrix([0,0,size]), color=Blue)) return axes } fn _centroid(m, g, id) { if (g==0) return m.vertexposition(id) var el=m.connectivitymatrix(0,g).rowindices(id) var x = 0 for (vid in el) x+=m.vertexposition(vid) x/=el.count() return x } fn _to3d(x) { var y = Matrix(3) for (i in 0...min(x.count(),3)) y[i]=x[i] return y } fn plotmeshlabels(mesh, grade=0, selection=nil, fontsize=10, offset=nil, dirn=nil, vertical=nil, color=nil) { var gradelist = grade if (!islist(grade)) gradelist = [grade] var gout = Graphics() var dir = dirn if (isnil(dir)) dir = [1,0,0] var vert = vertical if (isnil(vert)) vert = [0,1,0] fn _defaultoffset(x) { return 0.05*(x+Matrix([0.001,0,0])) } var off = offset if (islist(off)) off = Matrix(offset) if (isnil(off)) off = _defaultoffset var dcol = color if (isnil(color)) dcol = White for (g in gradelist) { var col = dcol if (isdictionary(color)) { if (color.contains(g)) col = color[g] else col = White } for (id in 0...mesh.count(g)) { if (selection && !selection[g,id]) continue var x = _to3d(_centroid(mesh, g, id)) if (iscallable(dirn)) dir=dirn(x) if (iscallable(vertical)) vert=vertical(x) var offset = off if (iscallable(off)) offset = off(x) if (abs(Matrix(vert).inner(Matrix(dir))-1) < 1e-10) { Error("PltLblDirn", "Text direction and vertical are colinear for element ${id} at [${x[0]}, ${x[1]}, ${x[2]}].").throw() } gout.display(Text("${id}", x+offset, size=fontsize, dirn=dir, vertical=vert, color=col)) } } return gout } // Scalebars class ScaleBar { _vector(val, default, normalize=false) { var out if (ismatrix(val)) { out=val } else if (islist(val)) { out=Matrix(val) } else { out=Matrix(default) } if (normalize) out/=out.norm() return out } init(nticks=5, colormap=nil, length=1, posn=nil, dirn=nil, tickdirn=nil, textdirn=nil, textvertical=nil, textcolor=nil, fontsize=16) { self.nticks = nticks self.length = length // Length of scalebar self.radius = 0.1*length // Radius of scalebar self.nptsamples = 5 // Number of samples self.npts = 16 // Number of azimuthal samples self.fontsize = fontsize // Maximum font size for labels self.colormap = colormap if (!self.colormap) self.colormap=ViridisMap() self.posn = self._vector(posn, [1,0,0]) self.dirn = self._vector(dirn, [0,1,0], normalize=true) self.tickdirn = self._vector(tickdirn, [1,0,0], normalize=true) var lambda = self.tickdirn.inner(self.dirn) if (abs(lambda-1)>1e-8) { self.tickdirn -= lambda*self.dirn } self.tickdirn/=self.tickdirn.norm() self.perp = cross3d(self.tickdirn, self.dirn) self.textdirn = self._vector(textdirn, self.tickdirn, normalize=true) self.textvertical = self._vector(textvertical, self.dirn, normalize=true) self.textcolor = textcolor } _ticksforstep(a, b, dw) { // Creates a possible range given bounds and a stepsize var la=dw*ceil(a/dw), lb=dw*floor(b/dw) // Multiples of dw such that la>a and lbvsep) size = floor((vsep/csize)*size) return size } drawbar() { // Draws the actual color bar var L = self.length/2 var r = self.radius var m1 = AreaMesh(fn (u,v) self.coords(r*cos(u),r*sin(u),v), -Pi..Pi:Pi/self.npts, -L..L:L/self.nptsamples, closed=[true,false]) var m2 = AreaMesh(fn (u,R) self.coords(R*cos(u),R*sin(u),-L), -Pi..Pi:Pi/self.npts, 0..r:r) var m3 = AreaMesh(fn (u,R) self.coords(R*cos(u),R*sin(u),L), -Pi..Pi:Pi/self.npts, 0..r:r) var m = MeshMerge([m1,m2,m3]).merge() return plotfield(Field(m, fn (x,y,z) Matrix([x,y,z]).inner(self.dirn) ), style="interpolate", colormap=self.colormap) } drawlabel(min, max) { // Draws the labels and ticks var ticks = self.ticks(min, max, self.nticks) var w = (max-min) var g=Graphics() var fsize = self.getfontsize(ticks) var toffset = abs(self.dirn.inner(self.textdirn)) var tvert = sqrt(1-toffset^2) for (t in ticks) { var x=(t-min)/w - 0.5 var label = String(t) var width = (0.6*label.count()*fsize/72)/2 // Estimate the width var height = fsize/72 // Estimate the height g.display(Cylinder(self.coords(1.2*self.radius,0,x*self.length),self.coords(1.5*self.radius,0,x*self.length),color=White)) g.display( Text(label, self.coords((1.8+toffset)*self.radius,0,x*self.length-0.25*height*tvert-width*toffset), size=fsize, dirn=self.textdirn, vertical=self.textvertical, color=self.textcolor) ) } return g } draw(min, max) { return self.drawbar() + self.drawlabel(min,max) } } ================================================ FILE: modules/povray.morpho ================================================ import graphics class Camera { init(antialias = true, width = 2048, height = 1536, viewangle = 24, viewpoint = nil, look_at = nil, sky = nil) { self.antialias = antialias self.width = width self.height = height self.viewangle = viewangle self.viewpoint = viewpoint self.look_at = look_at self.sky = sky self.light = nil if (!self.viewpoint) self.viewpoint = Matrix([0,0,5]) if (!self.look_at) self.look_at = Matrix([0,0,0]) if (!self.sky) self.sky = Matrix([0,1,0]) } } class POVRaytracer { init(graphic, camera = nil) { self.camera = camera if (!self.camera) self.camera = Camera() // Default Camera self.graphic = graphic self.antialias = self.camera.antialias self.width = self.camera.width self.height = self.camera.height self.viewangle = self.camera.viewangle self.viewpoint = self.camera.viewpoint self.look_at = self.camera.look_at self.sky = self.camera.sky self.light = nil } vector(v) { return "<${v[0]}, ${v[1]}, ${v[2]}>" } color(c) { if (isnil(c)) return "<0.5, 0.5, 0.5>" if (ismatrix(c)) return self.vector(c) else return self.vector(c.rgb(0)) } optionalarg(item){ var arg = "" if(isnil(item.filter) && !isnil(item.transmit)){ arg = "transmit ${item.transmit}" } else if(!isnil(item.filter) && isnil(item.transmit)){ arg = "filter ${item.filter}" } else if(!isnil(item.filter) && !isnil(item.transmit)){ arg = "filter ${item.filter} transmit ${item.transmit}" } return arg } visit(Text item, out) { var arg = self.optionalarg(item) out.write("text {" + " ttf \"cyrvetic.ttf\" \"${item.string}\" 0.1, 0 \n" + " pigment { rgb ${self.color(item.color)} ${arg} }") out.write(" scale ${item.size / 75} ") out.write(" translate ${item.posn[0]}*x + ${item.posn[1]}*y + ${item.posn[2]}*z ") var m = item.transformationmatrix() var str if (m) { str = " matrix <" for (i in 0..3) { for (j in 0..2) { str+=" "+String(m[i,j]) if (i==3 && j==2) str += "> \n }" else str += ", " if (j==2) str +="\n" } } } else { str = " } " } out.write(str) } visit(Sphere item, out) { var arg = self.optionalarg(item) out.write("sphere {"+ " ${self.vector(item.center)}, ${item.r}"+ " texture { "+ " pigment { rgb ${self.color(item.color)} ${arg}"+ "} } }") } visit(Cylinder item, out) { var radius = 0.5*(item.end - item.start).norm()*item.aspectratio var arg = self.optionalarg(item) out.write("cylinder {"+ " ${self.vector(item.start)}, ${self.vector(item.end)}, ${radius}"+ " texture { "+ " pigment { rgb ${self.color(item.color)} ${arg}"+ "} } }") } visit(Arrow item, out) { var dx = (item.end - item.start).norm() var radius = 0.5*dx*item.aspectratio var cylend = item.start + (item.end - item.start)*(1-item.aspectratio) var arg = self.optionalarg(item) out.write("cylinder {"+ " ${self.vector(item.start)}, ${self.vector(cylend)}, ${radius}"+ " texture { "+ " pigment { rgb ${self.color(item.color)} ${arg} "+ "} } }") out.write("cone {"+ " ${self.vector(cylend)}, ${2*radius}, ${self.vector(item.end)}, 0"+ " texture { "+ " pigment { rgb ${self.color(item.color)} ${arg} "+ "} } }") } visit(Tube item, out) { self.visit(item.totrianglecomplex(), out) } visit(TriangleComplex item, out) { var arg = self.optionalarg(item) out.write("mesh2 {"); var nv=item.position.dimensions()[1] out.write("vertex_vectors { ${nv}, ") for (i in 0...nv) { var s = self.vector(item.position.column(i)) if (i }") // Transparent background by setting alpha to 1 } else { out.write("background { rgb ${self.vector(self.graphic.background.rgb(0))} }") } out.write("camera {"+ "location ${self.vector(self.viewpoint)}"+ "up <0,1,0> right <-1.33,0,0> angle ${self.viewangle}"+ "look_at ${self.vector(self.look_at)} sky ${self.vector(self.sky)} }") for (item in self.graphic.displaylist) self.visit(item, out) var shadowless = "" if (self.shadowless) shadowless = " shadowless" if (self.light) { for (light in self.light) { out.write("light_source {${self.vector(light)} color White${shadowless}}") } } else out.write("light_source {<-5, -5, 8> color White${shadowless}}") out.close() return out.relativepath() } render(file, quiet=false, display=true, shadowless=false, transparent=false) { self.shadowless = shadowless // Sets the attribute to be used in the write method self.transparent = transparent // Sets the attribute to be used in the write method and in the command line argument for povray var path = self.write(file) var silent = "" if (quiet) silent = "> /dev/null 2>&1" var disp = "" if (!display) disp = "-D" var ua = "+A" if (self.transparent) ua = "+UA" // Sets alpha output on system("povray \"${path}\" ${disp} ${ua} +W${self.width} +H${self.height} ${silent}") var out = self._slice(path, 0, path.count()-4) if (!quiet && display) system("open ${out}.png") } /* private method: slice a string given a start and an end @param string: string to be slice @param start: starting index @param end: ending index(exclusive) */ _slice(string, start, end) { var s = "" for (i in start...end) { s += string[i] } return s } } ================================================ FILE: modules/shapeopt.morpho ================================================ /* ************************************ * Shape optimization ************************************** */ // Minimize a 1-d function by Brent's algorithm fn brent(bracket, func, tol, itermax) { var zeps = 1e-10, cgold = 0.3819660 fn sign(x, y) { // Returns sign(y)*|x| if (y<0) return -abs(x) if (y>0) return abs(x) return 0.0 } var iter var a, b // Minimum lies between a and b if (bracket[0]bracket[2]) b=bracket[0] else b=bracket[2] var d=0.0, e=0.0 var xm var v=bracket[1], w=v, x=v // Initialize var fv=func(x), fw=fv, fx=fv for (iter in 0...itermax) { xm=0.5*(a+b) var tol1=tol*abs(x)+zeps, tol2=2*tol1 // Check if converged if (abs(x-xm) <= (tol2-0.5*(b-a))) return [x, fx] if (abs(e) > tol1) { // Model the function by a parabola var r = (x-w)*(fx-fv), q = (x-v)*(fx-fw), p = (x-v)*q-(x-w)*r q=2*(q-r) if (q>0) p = -p q=abs(q) var etemp = e e=d // Check if the parabolic fit is acceptable if (abs(p) >= abs(0.5*q*etemp) || p<= q*(a-x) || p>= q*(b-x)) { // Bad: Take golden section step if (x>=xm) e=a-x else e=b-x d = cgold*e } else { // Good: Use parabolic step d=p/q var u=x+d if (u-a < tol2 || b-u < tol2) d = sign(tol1, xm-x) } } else { if (x>=xm) e=a-x else e=b-x d = cgold*e } var u if (abs(d)>=tol1) u=x+d else u=x+sign(tol1, d) var fu = func(u) // Evaluate function // Update bracket if (fu<=fx) { if (u>=x) a=x else b=x v=w; w=x; x=u fv=fw; fw=fx; fx=fu } else { if (u0) { var residual, i=0 do { var dv = self.testconstraints(), fv = [] for (cons in self.constraints) { var ff = self.gradient(cons) self.subtractlocalconstraints(ff) fv.append(ff) } var m=self.forceinnerproducts(fv) var sol = dv/m for (i in 0...nc) { v.acc(sol[i],fv[i]) } residual=self.testconstraints().norm() i+=1 if (i>self.maxconstraintsteps) { print "Warning: Too many steps in constraint satisfaction" return } } while (residual>self.ctol) } } /* Find which local constraints are active */ initlocalconstraints() { self.lcactive = [] for (cons in self.localconstraints) { if (cons.onesided) { var integrand = self.integrand(cons) var sel = Selection(self.mesh, fn (q) qself.ctol) { var fc=f.column(i) // Note we only retrieve the column of f if needed var lambda=fc.inner(gc)/gg fc.acc(-lambda, gc) f.setcolumn(i, fc) } } } /* Subtracts local constraints */ subtractlocalconstraints(f) { for (cons, i in self.localconstraints) { var g=self.gradient(cons, selection=self.lcactive[i]) self.sublocal(f,g) } } /* Test how closely local constraints are satisfied. Returns a list of vectors of residuals */ testlocalconstraints() { var dv = [] for (cons, i in self.localconstraints) { if (isselection(self.lcactive[i])) { dv.append(cons.functional.integrand(self.mesh, self.lcactive[i])) } else { dv.append(self.integrand(cons)) } } return dv } lcnorm(dv) { var norm = 0 for (v in dv) norm+=v.norm() return norm } /* Reproject local constraints */ reprojectlocalconstraints() { var v = self.mesh.vertexmatrix() var nv = v.dimensions()[1] var nc = self.localconstraints.count() if (nc>0) { var residual, iter=0 var dv = self.testlocalconstraints() if (self.lcnorm(dv)>self.ctol) do { var fv = [] // Calculate constraint forces for (cons, i in self.localconstraints) { fv.append(self.gradient(cons, selection=self.lcactive[i])) } var nactive = 0 // Loop over vertices for (k in 0...nv) { // Find the discrepencies of each force at the vertex var vv = Matrix(nc) for (i in 0...nc) vv[i] = -dv[i][0,k] // Note minus sign if (vv.norm()>self.ctol) { // Identify constraints active on this vertex var va = [], fa = [] for (i in 0...nc) { if (abs(vv[i])>self.ctol) { va.append(vv[i]) fa.append(fv[i].column(k)) } } // Now solve for the necessary motion var m=self.forceinnerproducts(fa) var sol = Matrix(va)/m // Move the vertex var newv=v.column(k) for (i in 0...sol.count()) { newv.acc(sol[i], fa[i]) } v.setcolumn(k,newv) nactive+=1 } } dv=self.testlocalconstraints() residual=self.lcnorm(dv) if (nactive>0) residual/=nactive iter+=1 if (iter>self.maxconstraintsteps && residual>self.ctol) { print "Warning: Too many steps in local constraint satisfaction. (Try increasing maxconstraintsteps or set ctol to a number greater than ${residual})." return } } while (residual>self.ctol) } } step(stepsize) { var v = self.mesh.vertexmatrix() var frc = self.totalforce() // Compute the total force self.initlocalconstraints() // Find which local constraints are active self.subtractlocalconstraints(frc) // Remove projections onto local constraints self.subtractconstraints(frc) // Remove projections onto constraint directions v.acc(-stepsize, frc) // Take a step self.initlocalconstraints() self.reprojectlocalconstraints() // Reproject onto local constraints self.reprojectconstraints() // Push back onto constraints } /* Perform relaxation at fixed scale */ relax(n) { //var nv = v.dimensions()[1] var energy = [ self.totalenergy() ] for (i in 0...n) { self.step(self.stepsize) energy.append(self.totalenergy()) // Track the total energy var de = abs(energy[i+1]-energy[i]) // How has it changed? // Report if (!self.quiet) print "Iteration ${i}. Energy: ${energy[i+1]} delta E: ${de}" // Test for convergence if (abs(energy[i+1])bracket[0]) { // Step size is too big s[1]=s[1]/2 bracket[1]=self.energywithstepsize(s[1]) } else { print "Cannot resolve bracket. Current stepsizes ${s} and bracket ${bracket}" return nil } if (iter>10) { print "Couldn't bracket stepsize. Adjust stepsize and retry."; return nil } iter+=1 } return [s, bracket] } /* Perform relaxation at fixed scale */ linesearch(n) { //var nv = v.dimensions()[1] var energy = [ self.totalenergy() ] for (i in 0...n) { var brack=self.bracketstepsize() if (!islist(brack)) return var step = brent(brack[0], self.energywithstepsize, self.linmintol, self.linminmax) if (isnil(step)) break self.stepsize = step[0] if (self.stepsize > self.steplimit) { self.stepsize = self.steplimit self.step(self.stepsize) } else { self.mesh.setvertexmatrix(self.last) } var de = abs(step[1]-energy[-1]) energy.append(step[1]) if (!self.quiet) print "Iteration ${i}. Energy: ${energy[-1]} delta E: ${de} stepsize: ${self.stepsize}" // Test for convergence if (abs(energy[i+1]) 0) { f.write("POINT_DATA ${self.nvertices} ") for (i in 0...self.nf) { self._writefield(f, self.fields[i], self.fieldnames[i]) } } // Close the file f.close() } } // VTKImporter: Imports Fields and / or Meshes from a .vtk file class VTKImporter is VTK { // Initialize with the filename init(filename) { // Ensure filename ends with ".vtk" self.filename = super._ensurevtk(filename) // Start a mesh builder self.mb = MeshBuilder(dimension=3) self.mesh = nil // Create a Dictionary for possible fields to be imported. The keys for the fields in this Dictionary will be the names given to those fields in the VTK file. self.fields = Dictionary() // Open the file self.f = File(self.filename) // Start the tokenizer self.tok = Tokenizer(self.f) // Go through the whole file while (!self.f.eof()) { self.t = self.tok.next() // The mesh vertices start with `POINTS` if (self.t=="POINTS") { // Number after `POINTS` is the number of vertices self.nvertices = Int(self.tok.next()) // The word after that is `float`. Let's skip that! self.t = self.tok.next() for (i in 0...self.nvertices) { self._addvertex() } } // Higher order elements start with `CELLS` if (self.t=="CELLS") { // The number after `CELLS` is the number of cells self.ncells = Int(self.tok.next()) // The number after that is for memory purposes and we // can ignore that self.t = self.tok.next() for (i in 0...self.ncells) { self._addcell() } } // If we get to POINT_DATA, that means mesh is ready // We can build it now. if (self.t=="POINT_DATA") self.mesh = self.mb.build() if (self.t=="SCALARS") { // The first word after SCALARS is the name of the field self.fieldname = self.tok.next() // The next 4 words are `float`, `1`, `LOOKUP_TABLE` and // `default`. Skipping those... for (j in 1..4) self.t = self.tok.next() // Initialize a scalar field self.field = Field(self.mesh, 0.0) // The following works ONLY when the fields are on the vertices for (i in 0...self.nvertices) { self.field[i] = Float(self.tok.next()) } self.fields[self.fieldname] = self.field } if (self.t=="VECTORS") { // The first word after VECTORS is the name of the field self.fieldname = self.tok.next() // The next word is float. Skipping that one... self.t = self.tok.next() // Initialize a vector field self.field = Field(self.mesh, Matrix([0,0,0])) // The following works ONLY when the fields are on the vertices for (i in 0...self.nvertices) { self.field[i] = self._getvector() } self.fields[self.fieldname] = self.field } } // If there are no fields, then `POINT_DATA` would never be reached. In that case, build the mesh now. if (isnil(self.mesh)) self.mesh = self.mb.build() } // Generate a vector out of the next three characters _getvector() { self.vx = Float(self.tok.next()) self.vy = Float(self.tok.next()) self.vz = Float(self.tok.next()) return Matrix([self.vx, self.vy, self.vz]) } // Add a vertex from a given point in the file to the MeshBuilder object _addvertex() { self.v = self._getvector() self.mb.addvertex(self.v) } // Add a higher order element from a given point in the file to the MeshBuilder object _addcell() { self.numPoints = Int(self.tok.next()) if (self.numPoints==2) { self.e1 = Int(self.tok.next()) self.e2 = Int(self.tok.next()) self.mb.addedge([self.e1, self.e2]) } else if (self.numPoints==3) { self.f1 = Int(self.tok.next()) self.f2 = Int(self.tok.next()) self.f3 = Int(self.tok.next()) self.mb.addface([self.f1, self.f2, self.f3]) } else if (self.numPoints==4) { self.f1 = Int(self.tok.next()) self.f2 = Int(self.tok.next()) self.f3 = Int(self.tok.next()) self.f4 = Int(self.tok.next()) self.mb.addvolume([self.f1, self.f2, self.f3, self.f4]) } } // returns the mesh mesh() { return self.mesh } // Returns the field named `fieldname` field(fieldname) { super._checkfieldname(fieldname) try { return self.fields[fieldname] } catch { "DctKyNtFnd": Error("FieldNotFound", "Couldn't find the field `${fieldname}` in the file.").throw() } } // Returns a list of the names of the fields present in the file fieldlist() { return self.fields.keys() } // Checks whether the file contains a field by a given fieldname containsfield(fieldname) { super._checkfieldname(fieldname) return self.fields.contains(fieldname) } } ================================================ FILE: releasenotes/version-0.5.1.md ================================================ # Release notes for 0.5.1 We're pleased to announce morpho 0.5.1, which contains: * Improvements to error handling: - Programs can now generate errors with the Error class. - Programs can handle errors that occur with try/catch. * FloryHuggins functional added for hydrogel simulations. * POVray module supports transparency. * Improvements to arrays. * Various bugfixes. ================================================ FILE: releasenotes/version-0.5.2.md ================================================ # Release notes for 0.5.2 We're pleased to announce morpho 0.5.2, which contains: * New `meshgen` module to create meshes in 2D and 3D. * New `delaunay` module to create Delaunay triangulations in arbitrary dimensions. * New `vtk` module to load and save meshes and fields in VTK legacy format. * `List`, `Array` and `Matrix` slices (e.g. `a[0..5]` means elements 0 to 5 of a list) * Minor improvements to apply, min, max and bounds functions. * Code improvements to facilitate future expansion of Morpho. * Numerous bugfixes. ================================================ FILE: releasenotes/version-0.5.3.md ================================================ # Release notes for 0.5.3 We're pleased to announce morpho 0.5.3, which contains: * Support for complex numbers via a `Complex` class. * Meshslice module enables taking a slice through a `Mesh` and associated `Field` objects. * Improved `File` class. * Many bugfixes. * Apple M1 support. ================================================ FILE: releasenotes/version-0.5.4.md ================================================ # Release notes for 0.5.4 We're pleased to announce morpho 0.5.4, which contains many improvements. This is a large update with many new features as below. Going forward, new incremental work will be collected in the dev branch (we started doing this for this version) and we will move to a bimonthly release schedule. ## Meshtools The `meshtools` module has been extensively revised with many new features: * New `MeshPruner` class added that enables coarsening of meshes, analogous to `MeshRefiner`. * Refinement of 3D elements. * Refinement of selections is improved. * Bugfixes for `MeshRefiner` and `MeshMerge` to prevent duplicate elements being generated in some circumstances. ## Text The `morphoview` application now supports text rendering. A number of modules have been updated to take advantage of this: * `plot` now provides `ScaleBar` objects that are useful for `plotfield`, as well as `plotmeshlabels` to label a mesh with element ids. * `graphics` now provides a `Text` class for textual elements, and has some performance improvements. * `color` now provides a number of new `ColorMap`s: `ViridisMap`, `InfernoMap`, `MagmaMap` and `PlasmaMap`, all of which are more friendly for people with color vision deficiency. ## Variadic functions You can now create functions that accept a variable number of parameters. Arguments passed to a function can be accessed as a `List`. fn func(x, ...v) { for (a in v) print a } Other improvements: * New `VolumeIntegral` module to complement `AreaIntegral` and `LineIntegral`. * Internal improvements to the morpho virtual machine. * A `System` class to enable you to get platform information. * Numerous bugfixes. * Numerous improvements to the documentation. * Improvements to the `povray` module. * New examples for `plot` module. * You can now translate the view in `morphoview` by right click and dragging or using alt-arrow keys. ================================================ FILE: releasenotes/version-0.5.5.md ================================================ # Release notes for 0.5.5 We're pleased to announce morpho 0.5.5, which contains a number of important improvements, and notably some significant performance improvements. ## Documentation We have added two new chapters to the manual, one on working with Meshes and the other describing the examples in detail. Additional chapters to follow. We've also improved the formatting of the manual, and a number of previously undocumented features are now documented in the manual and in the interactive help. ## Developer tools Morpho now provides a profiler and a rewritten debugger. To use these, run with -profile or -debug respectively. Upon completion, the profiler will produce a report of the fraction of program execution time spent in each morpho function, allowing the programmer to identify optimization targets. We've used this tool to significantly improve a number of morpho components. ## Mixins You can now create a class from multiple other classes (called a mixin) using the new `with` keyword: class Foo is Boo with Hoo, Moo { ... } Boo is the superclass of Foo, but methods defined in Hoo and Moo are copied into Foo before Foo's methods are defined. This enables greater modularity and facilitates code reuse. ## New linear algebra features Its now possible to convert Sparse matrices to dense matrices and vice-versa by passing them to the relevant constructor function, e.g. var a = Sparse([[0,0,1],[1,1,1]]) var b = Matrix(a) You can assemble matrices in block form using other matrices: var c = Matrix([[a,0],[0,a]]) You can compute the eigenvalues and eigenvectors of a matrix with the new eigenvalues() and eigensystem() methods. Preliminary work for numerical hessians is in place. ## Other improvements * Interactive mode now supports UTF8 characters. * Object now provides respondsto() and has() to determine the available methods and properties respectively. * Optimizing compiler [off by default; run with -O flag] lays the groundwork for significant future performance improvements. * MeshGen and Delauney modules run significantly faster. * Hydrogel functional is faster and dimensionally independent. * Numerous minor bugfixes. ================================================ FILE: releasenotes/version-0.5.6.md ================================================ # Release notes for 0.5.6 We're pleased to announce morpho 0.5.6, which contains a number of improvements, particularly focussed on performance and extensibility. ## Parallelized Force and Energy calculations Morpho now supports parallelized force and energy calculations, which can lead to significant speedups for some programs. To use these, run morpho with -w flag and supply the number of worker threads to use: morpho5 -w4 program.morpho Further features for parallel programming will appear in future releases. ## Resources and Packages The morpho runtime can now look for resource files---help files, morpho files, etc.---in multiple places. The default location is now configurable at installation, and also via a .morphopackages file stored in the user home directory. This enables morpho modules to live in their own git repository, together with resource files, and should make it easier for users to contribute to morpho. More details are in the dev guide. ## Extensions It's now possible to extend morpho through dynamic libraries written in C and linked at runtime. From the user's perspective, these work just like modules using the `import` keyword. ## New manual chapter on visualization We continue to improve the manual, and now include a chapter on visualization. The developer guide has also been updated. ## Other improvements * Improvements to morpho's object model. New Function, Closures and Invocation classes provided that respond to standard methods. * Fixes to some functionals to work correctly with 2D meshes. * You can now supply anonymous functions in the arguments to a function. * You can set the minimum and maximum values for plotfield using the optional cmin and cmax and arguments. * Manual contains additional information on installation ================================================ FILE: releasenotes/version-0.5.7.md ================================================ # Release notes for 0.5.7 We're pleased to announce morpho 0.5.7, which is the final release in the 0.5 series. This release contains a number of improvements and bugfixes. ## Windows install instructions fixed We have updated the installation instructions for Windows with this release to work with either WSL1 or WSL2. ## Gradients in AreaIntegral and VolumeIntegral You can now compute the local gradient of a field using the grad() function within the integrand supplied to AreaIntegral and VolumeIntegral. This significantly enhances the number of models morpho can handle. ## Improved System class * System.print(), System.readline() and System.sleep() methods added. * System.clock() now reports wall time (useful for testing the effect of parallelization) * System.arguments() provides access to the command line options morpho was run with. ## Minor improvements * New Matrix.roll() and List.roll() methods shift the contents of a List or Matrix respectively. * Field.linearize() provides access to the underlying Matrix store. * IdentityMatrix() constructor function. * Debugger now supports printing of global variables and object properties. * Fix issues with compilation on some platforms. * Experimental support for accessing integrand values for individual elements on some functionals. * Numerous minor bugfixes. ================================================ FILE: releasenotes/version-0.6.0.md ================================================ # Release notes for 0.6.0 We're pleased to announce Morpho 0.6.0, which represents a great deal of behind-the-scenes work to ready the Morpho codebase for future developments. See our Roadmap document for more details. ## Morpho now built as a shared library Rather than the previous monolithic strucutre, the Morpho codebase has been divided into a shared library ("libmorpho") and a terminal application ("morpho-cli"). This means that Morpho can easily be embedded in other applications, and improves maintainability as these components can be updated separately. Morphoview has been migrated to a separate repository. ## Internal improvements * Major code reorganization to improve the logical structure and maintainability of the morpho codebase. * Transitioned to Cmake build system to improve cross-platform compilation. * Rewritten parser to improve error reporting and enable reuse across Morpho. ## Improved quadrature Functionals like `LineIntegral`, `AreaIntegral` and `VolumeIntegral` can now make use of a greatly improved quadrature routine. This will become the default in future versions of Morpho. Particularly in 3D, the new routine offers significantly improved performance, and can be extended in future. To use the new quadrature routine simply set the `method` optional argument: var a = AreaIntegral(integrandfn, method = {}) The method Dictionary can specifically request particular quadrature rules or orders; more information will be in the dev guide. ## Namespaces You can now use the `import` keyword with a new keyword, `as`, to import the contents of a module into a given namespace: import color as col print col.Red This helps Morpho programs avoid library conflicts and improves modularization. ## Tuple data type Morpho now supports Tuples, an ordered immutable collection. The syntax is similar to Python: var t = (0,1,2,3) Tuples act much like Lists, but can be used as keys in a Dictionary. ## JSON import and export Morpho now provides a `JSON` class which supports import and output using the JavaScript Object Notation (JSON) format, widely used for data interchange. var a = JSON.parse("[1,2,3]") print a ## Minor new features * Formatted output for numbers is now available using the `format` method on the `Int` and `Float` classes. * Errors can now be raised as "warnings", which are alerts to the user that do not interrupt execution. ## Improved documentation Many previously un- or under-documented features have now been added to the interactive help. If you notice something that isn't well documented, please alert us via the issue tracker in Github. ## Minor fixes * Many improvements to the debugger, including better support for printing object properties. * Improved calculation of derivatives. * Bugfixes to closures, string interpolation, parallel force and energy calculations and many others. ================================================ FILE: releasenotes/version-0.6.1.md ================================================ # Release notes for 0.6.1 We're pleased to announce Morpho 0.6.1, which incorporates very important new language features and sets morpho up for future improvements. ## Types Morpho now supports types. Variables can be declared with a specified type like so String s = "Hello" and the type of function parameters can be specified like fn f(String s, List l) { } ## Multiple dispatch Morpho now supports *multiple dispatch*, whereby you can define multiple implementations of a function that accept different parameter types. The correct implementation to use is selected at runtime: fn f(String x) { } fn f(List x) { } Methods defined on classes also support this mechanism. You can still specify parameters that without a type, in which case all types are accepted. Multiple dispatch is implemented efficiently (it incurs only a small overhead relative to a traditional function call) and is very useful to remove complex type checking. We are using this feature to improve how morpho works internally, as well as to implement new morpho packages. ## Additional hessians LineCurvatureSq and LineTorsionSq now provide the hessian() method. ## Preliminary support for finite element discretizations We have begun to include support for additional discretizations beyond the linear elements supported by prior versions of Morpho in the codebase. This feature is a work in progress and not yet completely ready for use; we expect to complete it in forthcoming releases. ## Minor fixes * Bugfixes to parallelization. * Error messages now refer to the module in which the error was found. * Can now call throw() and warning() directly on the Error class. ================================================ FILE: releasenotes/version-0.6.2.md ================================================ # Release notes for 0.6.2 We're pleased to announce Morpho 0.6.2, which is primarily a maintenance release and incorporates a number of bugfixes and improvements. ## Morphopm package manager Alongside this release, we are pleased to announce a new package manager for morpho called `morphopm`, which makes installation of morpho packages significantly easier for users. Morphopm is available [on github](https://github.com/Morpho-lang/morpho-morphopm) and can also be installed via homebrew: brew tap morpho-lang/morpho brew install morpho-morphopm ## Benchmarks The benchmarks folder, which used to contain a number of basic benchmarks for morpho, has been moved to a [new repository](https://github.com/Morpho-lang/morpho-benchmark) with several new benchmarks added. We will be using these to continue to improve morpho's performance. ## Ternary operator Morpho now provides the ternary operator similar to other C-family languages: var a = (b < c ? b : c) ## Minor fixes * Keywords can now be used as method and property labels. * The povray module now produces silent output on linux if the quiet option is set. * apply() now works properly with metafunctions. * Bugfixes in the Sparse class. * Improvements to resource locator. * Fixes to multiple dispatch with variadic parameters. * Fixes to Range class. * Morpho running the test suite now passes valgrind memory checker. * meshtools now explicitly checks that the Mesh generated isn't too large. ================================================ FILE: releasenotes/version-0.6.3.md ================================================ # Release notes for 0.6.3 We're pleased to announce Morpho 0.6.3, which contains a number of improvements and represents the first steps towards cross-platform compatibility. ## Additional finite elements Morpho now provides finite element types beyond the linear Lagrangian elements (CG1) used previously. Quadratic Lagrangian elements (CG2) are implemented for fields on 1, 2 and 3D elements, and we will provide additional elements in future releases. Interpolated gradients are available for all element types. ## Preliminary Windows build The Morpho library and terminal app now can be natively built on Windows using Clang. To enable this, Morpho has been significantly refactored to isolate platform-specific code in a single location. We will provide a Windows installer and binaries in future releases once morphoview has also been ported. ## Self is not longer necessary for invocations It is no longer required to use the `self` keyword to invoke a method call, i.e. self.foo() can be replaced with: foo() Local variables take precedence over method labels, so that var foo = "hi" foo() raises an error even if foo is a method in the same class. ## Minor fixes * Improvements to the adaptive quadrature routines, which now use better rules by default and report clearer error messages. * Many improvements to the test suite, with tests more clearly structured. * String parsing now unicode aware. * AreaIntegral now supports an experimental option weightByReference, which weights the integral by a reference mesh, rather than the target mesh (this is useful for some elasticity problems). ================================================ FILE: src/CMakeLists.txt ================================================ target_sources(morpho PRIVATE morpho.h build.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES morpho.h build.h ) add_subdirectory(builtin) add_subdirectory(classes) add_subdirectory(core) add_subdirectory(datastructures) add_subdirectory(debug) add_subdirectory(geometry) add_subdirectory(linalg) add_subdirectory(support) ================================================ FILE: src/build.h ================================================ /** @file build.h * @author T J Atherton * * @brief Define constants that choose how Morpho is built */ #include /* ********************************************************************** * Version * ********************************************************************** */ #define MORPHO_VERSIONSTRING "0.6.3" #define MORPHO_VERSION_MAJOR 0 #define MORPHO_VERSION_MINOR 6 #define MORPHO_VERSION_PATCH 3 /* ********************************************************************** * Paths and file system * ********************************************************************** */ #ifndef MORPHO_HELP_BASEDIR #define MORPHO_HELP_BASEDIR "/usr/local/share/morpho/help" #endif #ifndef MORPHO_MODULE_BASEDIR #define MORPHO_MODULE_BASEDIR "/usr/local/share/morpho/modules" #endif #define MORPHO_HELPDIR "share/help" // Package subdir. where help files are found #define MORPHO_MODULEDIR "share/modules" // Package subdir. where modules are found #define MORPHO_EXTENSIONDIR "lib" // Package subdir. where extensions are found #define MORPHO_EXTENSION "morpho" // File extension for morpho files #define MORPHO_HELPEXTENSION "md" // File extension for help files #ifndef MORPHO_DYLIBEXTENSION #define MORPHO_DYLIBEXTENSION "dylib" // File extension for extensions #endif #define MORPHO_DIRSEPARATOR '/' // File directory separator #define MORPHO_PACKAGELIST ".morphopackages" // File in $HOME that contains package locations /* ********************************************************************** * Numeric tolerances * ********************************************************************** */ /** Value used to detect zero */ #define MORPHO_EPS DBL_EPSILON /** Relative tolerance used to compare double precision equality */ #define MORPHO_RELATIVE_EPS DBL_EPSILON /* ********************************************************************** * Size limits * ********************************************************************** */ /** @brief Maximum length of a Morpho error string. */ #define MORPHO_ERRORSTRINGSIZE 255 /** @brief Default size of input buffer. */ #define MORPHO_INPUTBUFFERDEFAULTSIZE 1024 /** @brief Maximum file name length. */ #define MORPHO_MAXIMUMFILENAMELENGTH 255 /** @brief Size of the call frame stack. */ #define MORPHO_CALLFRAMESTACKSIZE 255 /** @brief Size of the error handler stack. */ #define MORPHO_ERRORHANDLERSTACKSIZE 64 /** @brief Maximum number of object types */ #define MORPHO_MAXIMUMOBJECTDEFNS 64 /** @brief Type numbers in a value must be less than this value */ #define MORPHO_MAXIMUMVALUETYPES 8 /** @brief Maximum number of arguments */ #define MORPHO_MAXARGS 255 /** @warning Note that this cannot easily be adjusted >255 without changing the instruction encoding */ /** @brief Maximum number of constants */ #define MORPHO_MAXCONSTANTS 65536 /** @warning Note that this cannot easily be adjusted >65536 without changing the instruction encoding */ /* ********************************************************************** * Performance * ********************************************************************** */ /** @brief Build Morpho VM with computed gotos */ #define MORPHO_COMPUTED_GOTO /** @brief Build Morpho VM with small but hacky value type [NaN boxing] */ #ifndef _NO_NAN_BOXING #define MORPHO_NAN_BOXING #endif /** @brief Number of bytes to bind before GC first runs */ #define MORPHO_GCINITIAL 1024 /** It seems that DeltaBlue benefits strongly from garbage collecting while the heap is still fairly small */ /** @brief Controls how rapidly the GC tries to collect garbage */ #define MORPHO_GCGROWTHFACTOR 2 /** @brief Initial size of the stack */ #define MORPHO_STACKINITIALSIZE 256 /** @brief Controls how rapidly the stack grows */ #define MORPHO_STACKGROWTHFACTOR 2 /** @brief Limits size of statically allocated arrays on the C stack */ #define MORPHO_MAXIMUMSTACKALLOC 256 /** @brief Default number of threads */ #define MORPHO_DEFAULTTHREADNUMBER 0 /** @brief Size of L1 cache line */ #define _MORPHO_L1CACHELINESIZE 128 // M1/M2 is 128; most intel are 64 /** @brief Pad data structures involved in multiprocessing */ #define _MORPHO_PADDING char __padding[_MORPHO_L1CACHELINESIZE] /* ********************************************************************** * Core library [options set in CMake] * ********************************************************************** */ /** Build with Matrix class using BLAS/LAPACK */ //#define MORPHO_INCLUDE_LINALG /** Build with Sparse class */ //#define MORPHO_INCLUDE_SPARSE /** Build with geometry classes */ //#define MORPHO_INCLUDE_GEOMETRY /* ********************************************************************** * Libraries * ********************************************************************** */ /** Use Apple's accelerate library for dense linear algebra */ #define MORPHO_LINALG_USE_ACCELERATE /** Use the LAPACKE library for dense linear algebra */ //#define MORPHO_LINALG_USE_LAPACKE /** Use CSparse for sparse matrix */ #define MORPHO_LINALG_USE_CSPARSE /* ********************************************************************** * Debugging * ********************************************************************** */ /** @brief Include debugging features */ #define MORPHO_DEBUG /** @brief Print each instruction executed by the VM. */ //#define MORPHO_DEBUG_PRINT_INSTRUCTIONS /** @brief Display syntax tree after parsing */ //#define MORPHO_DEBUG_DISPLAYSYNTAXTREE /** @brief Display register allocations during compilation */ //#define MORPHO_DEBUG_DISPLAYREGISTERALLOCATION /** @brief Disables garbage collector */ //#define MORPHO_DEBUG_DISABLEGARBAGECOLLECTOR /** @brief Stress test garbage collector */ #ifdef _DEBUG_STRESSGARBAGECOLLECTOR #define MORPHO_DEBUG_STRESSGARBAGECOLLECTOR #endif /** @brief Log garbage collector */ //#define MORPHO_DEBUG_LOGGARBAGECOLLECTOR /** @brief Check GC size tracking */ //#define MORPHO_DEBUG_GCSIZETRACKING /** @brief Fill global constant table */ //#define MORPHO_DEBUG_FILLGLOBALCONSTANTTABLE /** @brief Debug symbol table */ //#define MORPHO_DEBUG_SYMBOLTABLE /** @brief Diagnose opcode usage */ //#define MORPHO_OPCODE_USAGE /** @brief Buiild with profile support */ #define MORPHO_PROFILER ================================================ FILE: src/builtin/CMakeLists.txt ================================================ target_sources(morpho PRIVATE builtin.c builtin.h functiondefs.c functiondefs.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES builtin.h functiondefs.h ) ================================================ FILE: src/builtin/builtin.c ================================================ /** @file builtin.c * @author T J Atherton * * @brief Morpho built in functions and classes */ #include "builtin.h" #include "common.h" #include "object.h" #include "functiondefs.h" #include "file.h" #include "system.h" #include "classes.h" #include "sparse.h" #include "geometry.h" /* ********************************************************************** * Global data * ********************************************************************** */ /** A table of built in functions */ dictionary builtin_functiontable; /** A table of built in classes */ dictionary builtin_classtable; /** A table of symbols used by built in classes */ dictionary builtin_symboltable; /** Maintain a list of objects created by builtin */ object *builtin_objects; /** Current function and class tables */ dictionary *_currentfunctiontable; dictionary *_currentclasstable; /* ********************************************************************** * Utility functions * ********************************************************************** */ /** Initialize an objectbuiltinfunction */ void builtin_init(objectbuiltinfunction *func) { func->flags=BUILTIN_FLAGSEMPTY; func->function=NULL; func->name=MORPHO_NIL; func->klass=NULL; signature_init(&func->sig); } /** Clear an objectbuiltinfunction */ void builtin_clear(objectbuiltinfunction *func) { morpho_freeobject(func->name); signature_clear(&func->sig); } /** @brief An enumerate loop. @details Successively calls enumerate on obj, passing the result to the supplied function. @param[in] v - the virtual machine @param[in] obj - object to enumerate over @param[in] fn - function to call @param[in] ref - reference to pass to the function @returns true on success */ bool builtin_enumerateloop(vm *v, value obj, builtin_loopfunction fn, void *ref) { value enumerate=MORPHO_NIL; value count=MORPHO_NIL, in=MORPHO_INTEGER(-1), val=MORPHO_NIL; if (morpho_lookupmethod(obj, enumerateselector, &enumerate)) { if (!morpho_invoke(v, obj, enumerate, 1, &in, &count)) return false; if (!MORPHO_ISINTEGER(count)) return false; for (indx i=0; inext && /* Object is not already bound to the program (or something else) */ builtin_objects!=obj && obj->status==OBJECT_ISUNMANAGED) { obj->status=OBJECT_ISBUILTIN; obj->next=builtin_objects; builtin_objects=obj; } } /* ********************************************************************** * Optional arguments * ********************************************************************** */ int vm_getoptionalargs(vm *v); /** Process optional arguments */ bool builtin_options(vm *v, int nargs, value *args, int *nfixed, int noptions, ...) { va_list optlist; va_start(optlist, noptions); int nopt=vm_getoptionalargs(v); for (unsigned int i=0; i", (MORPHO_ISNIL(f->name) ? "" : MORPHO_GETCSTRING(f->name))); } void objectbuiltinfunction_freefn(object *obj) { builtin_clear((objectbuiltinfunction *) obj); } size_t objectbuiltinfunction_sizefn(object *obj) { return sizeof(objectbuiltinfunction); } objecttypedefn objectbuiltinfunctiondefn = { .printfn=objectbuiltinfunction_printfn, .markfn=NULL, .freefn=objectbuiltinfunction_freefn, .sizefn=objectbuiltinfunction_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * Create and find builtin functions * ********************************************************************** */ /** Gets the current function table */ dictionary *builtin_getfunctiontable(void) { return _currentfunctiontable; } /** Sets the current function table */ void builtin_setfunctiontable(dictionary *dict) { _currentfunctiontable=dict; } /** Gets the current class table */ dictionary *builtin_getclasstable(void) { return _currentclasstable; } /** Sets the current class table */ void builtin_setclasstable(dictionary *dict) { _currentclasstable=dict; } /** Add a builtin function. * @param name name of the function * @param func the corresponding C function * @param flags flags to define the function * @returns value referring to the objectbuiltinfunction */ value builtin_addfunction(char *name, builtinfunction func, builtinfunctionflags flags) { value out=MORPHO_NIL; morpho_addfunction(name, NULL, func, flags, &out); return out; } /** Finds a builtin function from its name */ value builtin_findfunction(value name) { value out=MORPHO_NIL; dictionary_get(&builtin_functiontable, name, &out); return out; } objectclass *builtin_getparentclass(value fn) { if (MORPHO_ISFUNCTION(fn)) return MORPHO_GETFUNCTION(fn)->klass; else if (MORPHO_ISBUILTINFUNCTION(fn)) return MORPHO_GETBUILTINFUNCTION(fn)->klass; else if (MORPHO_ISMETAFUNCTION(fn)) return MORPHO_GETMETAFUNCTION(fn)->klass; else if (MORPHO_ISCLASS(fn)) return MORPHO_GETCLASS(fn)->superclass; return NULL; } /** Adds a new builtinfunction to a given dictionary. * @param[in] dict the dictionary * @param[in] name name of the function to add * @param[in] fn function to add * @param[out] out the function added (which may be a metafunction) * @returns true on success */ bool builtin_addfunctiontodict(dictionary *dict, value name, value fn, value *out) { bool success=false; value entry=fn; // Dictionary entry for this name value selector = dictionary_intern(&builtin_symboltable, name); // Use interned name if (dictionary_get(dict, selector, &entry)) { // There was an existing function if (MORPHO_ISBUILTINFUNCTION(entry)) { // It was a builtinfunction, so we need to create a metafunction if (builtin_getparentclass(fn) != MORPHO_GETBUILTINFUNCTION(entry)->klass) { // Override superclass methods for now dictionary_insert(dict, selector, fn); } else if (metafunction_wrap(name, entry, &entry)) { // Wrap the old definition in a metafunction builtin_bindobject(MORPHO_GETOBJECT(entry)); metafunction_add(MORPHO_GETMETAFUNCTION(entry), fn); // Add the new definition success=dictionary_insert(dict, selector, entry); } } else if (MORPHO_ISMETAFUNCTION(entry)) { // It was already a metafunction so simply add the new function success=metafunction_add(MORPHO_GETMETAFUNCTION(entry), fn); } } else success=dictionary_insert(dict, selector, fn); if (success && out) *out = entry; return success; } /** Add a function to the morpho runtime * @param name name of the function * @param signature [optional] signature for the function * @param func the corresponding C function * @param flags flags to define the function * @param[out] value the function created as usable with morpho_call * @returns true on success */ bool morpho_addfunction(char *name, char *signature, builtinfunction func, builtinfunctionflags flags, value *out) { objectbuiltinfunction *new = (objectbuiltinfunction *) object_new(sizeof(objectbuiltinfunction), OBJECT_BUILTINFUNCTION); if (!new) goto morpho_addfunction_cleanup; builtin_init(new); new->function=func; new->flags=flags; new->name=object_stringfromcstring(name, strlen(name)); if (!name) goto morpho_addfunction_cleanup; // Parse function signature if provided if (signature && !signature_parse(signature, &new->sig)) { UNREACHABLE("Syntax error in signature definition."); } value newfn = MORPHO_OBJECT(new); if (!builtin_addfunctiontodict(_currentfunctiontable, new->name, newfn, NULL)) { UNREACHABLE("Redefinition of function in same extension [in builtin.c]"); } // Retain the objectbuiltinfunction in the builtin_objects table builtin_bindobject(MORPHO_GETOBJECT(newfn)); if (out) *out = newfn; return true; morpho_addfunction_cleanup: if (new) { builtin_clear(new); object_free((object *) new); } return false; } /* ********************************************************************** * Create and find builtin classes * ********************************************************************** */ /** Defines a built in class * @param[in] name the name of the class * @param[in] desc class description; use MORPHO_GETCLASSDEFINITION(name) to obtain this * @param[in] superclass the class's superclass * @returns the class object */ value builtin_addclass(char *name, builtinclassentry desc[], value superclass) { value label = object_stringfromcstring(name, strlen(name)); builtin_bindobject(MORPHO_GETOBJECT(label)); objectclass *new = object_newclass(label); builtin_bindobject((object *) new); objectclass *superklass = NULL; if (!new) return MORPHO_NIL; /** Copy methods from superclass */ if (MORPHO_ISCLASS(superclass)) { superklass = MORPHO_GETCLASS(superclass); dictionary_copy(&superklass->methods, &new->methods); new->superclass=superklass; } for (unsigned int i=0; desc[i].name!=NULL; i++) { if (desc[i].type==BUILTIN_METHOD) { objectbuiltinfunction *newmethod = (objectbuiltinfunction *) object_new(sizeof(objectbuiltinfunction), OBJECT_BUILTINFUNCTION); builtin_init(newmethod); newmethod->function=desc[i].function; newmethod->klass=new; newmethod->name=object_stringfromcstring(desc[i].name, strlen(desc[i].name)); newmethod->flags=desc[i].flags; if (desc[i].signature) { signature_parse(desc[i].signature, &newmethod->sig); } dictionary_intern(&builtin_symboltable, newmethod->name); value method = MORPHO_OBJECT(newmethod); builtin_bindobject((object *) newmethod); builtin_addfunctiontodict(&new->methods, newmethod->name, method, NULL); } } if (dictionary_get(_currentclasstable, label, NULL)) { UNREACHABLE("Redefinition of class in same extension [in builtin.c]"); } dictionary_insert(_currentclasstable, label, MORPHO_OBJECT(new)); return MORPHO_OBJECT(new); } /** Finds a builtin class from its name */ value builtin_findclass(value name) { value out=MORPHO_NIL; dictionary_get(&builtin_classtable, name, &out); return out; } /** Copies the built in symbol table into a new dictionary */ void builtin_copysymboltable(dictionary *out) { dictionary_copy(&builtin_symboltable, out); } /** Interns a given symbol. */ value builtin_internsymbol(value symbol) { return dictionary_intern(&builtin_symboltable, symbol); } /** Interns a symbol given as a C string. */ value builtin_internsymbolascstring(char *symbol) { value selector = object_stringfromcstring(symbol, strlen(symbol)); builtin_bindobject(MORPHO_GETOBJECT(selector)); value internselector = builtin_internsymbol(selector); return internselector; } /** Checks if a symbol exists in the global symbol table */ bool builtin_checksymbol(value symbol) { value val; return dictionary_get(&builtin_symboltable, symbol, &val); } /* ********************************************************************** * Initialization/Finalization * ********************************************************************** */ extern objecttypedefn objectstringdefn; extern objecttypedefn objectclassdefn; objecttype objectbuiltinfunctiontype; void builtin_initialize(void) { dictionary_init(&builtin_functiontable); dictionary_init(&builtin_classtable); dictionary_init(&builtin_symboltable); builtin_objects=NULL; builtin_setfunctiontable(&builtin_functiontable); builtin_setclasstable(&builtin_classtable); // Initialize core object types objectstringtype=object_addtype(&objectstringdefn); objectclasstype=object_addtype(&objectclassdefn); objectbuiltinfunctiontype=object_addtype(&objectbuiltinfunctiondefn); /* Initialize builtin classes and functions */ instance_initialize(); // Must initialize first so that Object exists string_initialize(); // Classes function_initialize(); metafunction_initialize(); class_initialize(); upvalue_initialize(); invocation_initialize(); dict_initialize(); list_initialize(); closure_initialize(); array_initialize(); range_initialize(); complex_initialize(); err_initialize(); tuple_initialize(); float_initialize();// Veneer classes int_initialize(); bool_initialize(); file_initialize(); system_initialize(); json_initialize(); // Initialize function definitions functiondefs_initialize(); // Initialize linear algebra #ifdef MORPHO_INCLUDE_LINALG matrix_initialize(); #endif #ifdef MORPHO_INCLUDE_SPARSE sparse_initialize(); #endif #ifdef MORPHO_INCLUDE_GEOMETRY // Initialize geometry geometry_initialize(); #endif morpho_addfinalizefn(builtin_finalize); } void builtin_finalize(void) { while (builtin_objects!=NULL) { object *next = builtin_objects->next; object_free(builtin_objects); builtin_objects=next; } dictionary_clear(&builtin_functiontable); dictionary_clear(&builtin_classtable); dictionary_clear(&builtin_symboltable); } ================================================ FILE: src/builtin/builtin.h ================================================ /** @file builtin.h * @author T J Atherton * * @brief Morpho built in functions and classes */ #ifndef builtin_h #define builtin_h #include "object.h" #include "clss.h" #ifndef MORPHO_CORE #include "morpho.h" #endif #include "signature.h" /* ------------------------------------------------------- * Built in function objects * ------------------------------------------------------- */ /** Flags that describe properties of the built in function */ typedef unsigned int builtinfunctionflags; #define BUILTIN_FLAGSEMPTY 0 #define MORPHO_FN_FLAGSEMPTY (0) #define MORPHO_FN_PUREFN (1<<1) #define MORPHO_FN_CONSTRUCTOR (1<<2) #define MORPHO_FN_REENTRANT (1<<3) #define MORPHO_FN_OPTARGS (1<<4) /** Type of C function that implements a built in Morpho function */ typedef value (*builtinfunction) (vm *v, int nargs, value *args); /** Object type for built in function */ extern objecttype objectbuiltinfunctiontype; #define OBJECT_BUILTINFUNCTION objectbuiltinfunctiontype /** A built in function object */ typedef struct { object obj; value name; builtinfunctionflags flags; builtinfunction function; objectclass *klass; signature sig; } objectbuiltinfunction; /** Gets an objectfunction from a value */ #define MORPHO_GETBUILTINFUNCTION(val) ((objectbuiltinfunction *) MORPHO_GETOBJECT(val)) /** Tests whether an object is a function */ #define MORPHO_ISBUILTINFUNCTION(val) object_istype(val, OBJECT_BUILTINFUNCTION) /* ------------------------------------------------------- * Built in classes * ------------------------------------------------------- */ /** A type used to store the entries of a built in class */ typedef struct { enum { BUILTIN_METHOD, BUILTIN_PROPERTY } type; char *name; char *signature; builtinfunctionflags flags; builtinfunction function; } builtinclassentry; /** The following macros help to define a built in class. They should be used outside of any function declaration. * To use: * MORPHO_BEGINCLASS(Object) - Starts the declaration * MORPHO_PROPERTY("test") - Adds a property called "test" to the definition * MORPHO_METHOD("init", object_init, BUILTIN_FLAGSEMPTY) - Adds a method called "init" to the definition * MORPHO_ENDCLASS - Ends the declaration */ #define MORPHO_BEGINCLASS(name) builtinclassentry builtinclass_##name[] = { #define MORPHO_PROPERTY(label) ((builtinclassentry) { .type=(BUILTIN_PROPERTY), .name=(label), .flags=BUILTIN_FLAGSEMPTY, .function=NULL}) #define MORPHO_METHOD(label, func, flg) ((builtinclassentry) { .type=(BUILTIN_METHOD), .name=(label), .signature=NULL, .flags=flg, .function=func}) #define MORPHO_METHOD_SIGNATURE(label, sig, func, flg) ((builtinclassentry) { .type=(BUILTIN_METHOD), .name=(label), .signature=sig, .flags=flg, .function=func}) #define MORPHO_ENDCLASS , MORPHO_PROPERTY(NULL) \ }; /** Use this macro to retrieve the class definition for calling builtin_addclass */ #define MORPHO_GETCLASSDEFINITION(name) (builtinclass_##name) /** Macros and functions for built in classes */ /** Get the nth argument from the args list */ #define MORPHO_GETARG(args, n) (args[n+1]) /** This macro gets self */ #define MORPHO_SELF(args) (args[0]) /** Raise an error and return nil */ #define MORPHO_RAISE(v, err) { morpho_runtimeerror(v, err ); return MORPHO_NIL; } #define MORPHO_RAISEVARGS(v, err, ...) \ { morpho_runtimeerror(v, err, __VA_ARGS__); \ return MORPHO_NIL; } /* ------------------------------------------------------- * Loop functions to enumerate over enumerable objects * ------------------------------------------------------- */ /** Type of C function that implements a built in Morpho function */ typedef bool (*builtin_loopfunction) (vm *v, indx i, value item, void *ref); /* ------------------------------------------------------- * Interface * ------------------------------------------------------- */ dictionary *builtin_getfunctiontable(void); void builtin_setfunctiontable(dictionary *dict); dictionary *builtin_getclasstable(void); void builtin_setclasstable(dictionary *dict); value builtin_addfunction(char *name, builtinfunction func, builtinfunctionflags flags); value builtin_findfunction(value name); bool morpho_addfunction(char *name, char *signature, builtinfunction func, builtinfunctionflags flags, value *out); value builtin_addclass(char *name, builtinclassentry desc[], value superclass); value builtin_findclass(value name); void builtin_copysymboltable(dictionary *out); value builtin_internsymbol(value symbol); value builtin_internsymbolascstring(char *symbol); bool builtin_checksymbol(value symbol); bool builtin_options(vm *v, int nargs, value *args, int *nfixed, int noptions, ...); bool builtin_iscallable(value val); bool builtin_enumerateloop(vm *v, value obj, builtin_loopfunction fn, void *ref); /* ------------------------------------------------------- * Veneer classes * ------------------------------------------------------- */ void object_setveneerclass(objecttype type, value class); objectclass *object_getveneerclass(objecttype type); bool object_veneerclasstotype(objectclass *clss, objecttype *type); void value_setveneerclass(value type, value class); objectclass *value_getveneerclass(value type); objectclass *value_veneerclassfromtype(int type); bool value_veneerclasstotype(objectclass *clss, int *type); /* ------------------------------------------------------- * Initialization/finalization * ------------------------------------------------------- */ void builtin_initialize(void); void builtin_finalize(void); #endif /* builtin_h */ ================================================ FILE: src/builtin/functiondefs.c ================================================ /** @file functiondefs.c * @author T J Atherton * * @brief Built in function definitions */ #include #include #include #include "functiondefs.h" #include "random.h" #include "builtin.h" #include "common.h" #include "cmplx.h" #include "matrix.h" #include "sparse.h" #include "mesh.h" #include "field.h" #include "selection.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* ********************************************************************** * Built in functions * ********************************************************************** */ /* ************************************ * Math * *************************************/ #define BUILTIN_MATH(function) \ value builtin_##function(vm *v, int nargs, value *args) { \ if (nargs==1) { \ value arg = MORPHO_GETARG(args, 0); \ if (MORPHO_ISFLOAT(arg)) { \ return MORPHO_FLOAT(function(MORPHO_GETFLOATVALUE(arg))); \ } else if (MORPHO_ISINTEGER(arg)) { \ return MORPHO_FLOAT(function((double) MORPHO_GETINTEGERVALUE(arg))); \ } else if (MORPHO_ISCOMPLEX(arg)){\ return complex_builtin##function(v,MORPHO_GETCOMPLEX(arg));\ } else { \ morpho_runtimeerror(v, MATH_ARGS, #function);\ } \ } \ morpho_runtimeerror(v, MATH_NUMARGS, #function);\ return MORPHO_NIL; \ } /** Math functions */ BUILTIN_MATH(fabs) BUILTIN_MATH(exp) BUILTIN_MATH(log) BUILTIN_MATH(log10) BUILTIN_MATH(sin) BUILTIN_MATH(cos) BUILTIN_MATH(tan) BUILTIN_MATH(asin) BUILTIN_MATH(acos) BUILTIN_MATH(sinh) BUILTIN_MATH(cosh) BUILTIN_MATH(tanh) BUILTIN_MATH(floor) BUILTIN_MATH(ceil) #undef BUILTIN_MATH /** Boolean output function need to output morpho true or false **/ #define BUILTIN_MATH_BOOL(function) \ value builtin_##function(vm *v, int nargs, value *args) { \ if (nargs==1) { \ value arg = MORPHO_GETARG(args, 0); \ if (MORPHO_ISFLOAT(arg)) { \ return MORPHO_BOOL(function(MORPHO_GETFLOATVALUE(arg))); \ } else if (MORPHO_ISINTEGER(arg)) { \ return MORPHO_BOOL(function((double) MORPHO_GETINTEGERVALUE(arg))); \ } else if (MORPHO_ISCOMPLEX(arg)){\ return complex_builtin##function(MORPHO_GETCOMPLEX(arg));\ } else { \ morpho_runtimeerror(v, MATH_ARGS, #function);\ } \ } \ morpho_runtimeerror(v, MATH_NUMARGS, #function);\ return MORPHO_NIL; \ } BUILTIN_MATH_BOOL(isfinite) BUILTIN_MATH_BOOL(isinf) BUILTIN_MATH_BOOL(isnan) #undef BUILTIN_MATH_BOOL /** The sqrt function is needs to be able to return a complex number for negative arguments */ value builtin_sqrt(vm *v, int nargs, value *args) { if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISCOMPLEX(arg)){ return complex_builtinsqrt(v,MORPHO_GETCOMPLEX(arg)); } else if (MORPHO_ISNUMBER(arg)) { double val; if (morpho_valuetofloat(arg,&val)) { if (val<0) {// need to use complex sqrt objectcomplex C = MORPHO_STATICCOMPLEX(val, 0); return complex_builtinsqrt(v, &C); } else { return MORPHO_FLOAT(sqrt(val)); } } else morpho_runtimeerror(v, MATH_ARGS, "sqrt"); } else morpho_runtimeerror(v, MATH_ARGS, "sqrt"); } morpho_runtimeerror(v, MATH_NUMARGS, "sqrt"); return MORPHO_NIL; } /** The arctan function is special; it can either take one or two arguments */ value builtin_arctan(vm *v, int nargs, value *args) { bool useComplex = false; for (unsigned int i=0; i=0) { return MORPHO_FLOAT(0); } else return MORPHO_FLOAT(M_PI); } else if (MORPHO_ISCOMPLEX(arg)) { objectcomplex *c=MORPHO_GETCOMPLEX(arg); double val; complex_angle(c,&val); return MORPHO_FLOAT(val); } } morpho_runtimeerror(v, MATH_NUMARGS, "angle"); return MORPHO_NIL; } value builtin_conj(vm *v, int nargs, value *args) { if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISNUMBER(arg)) { return arg; } else if (MORPHO_ISCOMPLEX(arg)) { objectcomplex *a=MORPHO_GETCOMPLEX(arg); value out=MORPHO_NIL; objectcomplex *new = object_newcomplex(0,0); if (new) { complex_conj(a, new); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } } morpho_runtimeerror(v, MATH_NUMARGS, "conj"); return MORPHO_NIL; } /* ************************************ * Random numbers * *************************************/ /** Generate a random float between 0 and 1 */ value builtin_random(vm *v, int nargs, value *args) { return MORPHO_FLOAT(random_double()); } /** Generate a random normally distributed number */ value builtin_randomnormal(vm *v, int nargs, value *args) { double x,y,r; do { x=2.0*random_double()-1.0; y=2.0*random_double()-1.0; r=x*x+y*y; } while (r>=1.0); return MORPHO_FLOAT(x*sqrt((-2.0*log(r))/r)); } /** Generate a random integer with a bound. Efficient and unbiased algorithm from: https://www.pcg-random.org/posts/bounded-rands.html */ value builtin_randomint(vm *v, int nargs, value *args) { uint32_t x = random_int(); /* Leave quickly if no range was asked for */ if (nargs==0) return MORPHO_INTEGER((int) x); /* Otherwise, generate a number in range. */ int r=0; if (!morpho_valuetoint(MORPHO_GETARG(args, 0), &r)||r<0) { morpho_runtimeerror(v, VM_INVALIDARGSDETAIL,FUNCTION_RANDOMINT, 1, "positive integer"); } uint32_t range=(uint32_t) r; uint64_t m = (uint64_t) x * (uint64_t) range; uint32_t l = (uint32_t) m; if (l < range) { uint32_t t = -range; if (t >= range) { t -= range; if (t >= range) t %= range; } while (l < t) { x = random_int(); m = (uint64_t) x * (uint64_t) range; l = (uint32_t) m; } } return MORPHO_INTEGER(m >> 32); } /* ************************************ * Type checking and conversion * *************************************/ /** Typecheck functions to test for the type of a quantity */ #define BUILTIN_TYPECHECK(type, test) \ value builtin_##type(vm *v, int nargs, value *args) { \ if (nargs==1) { \ return MORPHO_BOOL(test(MORPHO_GETARG(args, 0))); \ } else morpho_runtimeerror(v, TYPE_NUMARGS, #type); \ \ return MORPHO_NIL; \ } BUILTIN_TYPECHECK(isnil, MORPHO_ISNIL) BUILTIN_TYPECHECK(isint, MORPHO_ISINTEGER) BUILTIN_TYPECHECK(isfloat, MORPHO_ISFLOAT) BUILTIN_TYPECHECK(isnumber, MORPHO_ISNUMBER) BUILTIN_TYPECHECK(iscomplex, MORPHO_ISCOMPLEX) BUILTIN_TYPECHECK(isbool, MORPHO_ISBOOL) BUILTIN_TYPECHECK(isobject, MORPHO_ISOBJECT) BUILTIN_TYPECHECK(isstring, MORPHO_ISSTRING) BUILTIN_TYPECHECK(isclass, MORPHO_ISCLASS) BUILTIN_TYPECHECK(isrange, MORPHO_ISRANGE) BUILTIN_TYPECHECK(isdictionary, MORPHO_ISDICTIONARY) BUILTIN_TYPECHECK(islist, MORPHO_ISLIST) BUILTIN_TYPECHECK(istuple, MORPHO_ISTUPLE) BUILTIN_TYPECHECK(isarray, MORPHO_ISARRAY) #ifdef MORPHO_INCLUDE_LINALG BUILTIN_TYPECHECK(ismatrix, MORPHO_ISMATRIX) #endif #ifdef MORPHO_INCLUDE_SPARSE BUILTIN_TYPECHECK(issparse, MORPHO_ISSPARSE) #endif #ifdef MORPHO_INCLUDE_GEOMETRY BUILTIN_TYPECHECK(ismesh, MORPHO_ISMESH) BUILTIN_TYPECHECK(isselection, MORPHO_ISSELECTION) BUILTIN_TYPECHECK(isfield, MORPHO_ISFIELD) #endif #undef BUILTIN_TYPECHECK /** Check if something is callable */ value builtin_iscallablefunction(vm *v, int nargs, value *args) { if (nargs==1) { if (builtin_iscallable(MORPHO_GETARG(args, 0))) return MORPHO_TRUE; } else morpho_runtimeerror(v, TYPE_NUMARGS, FUNCTION_ISCALLABLE); return MORPHO_FALSE; } /** Convert something to an integer */ value builtin_int(vm *v, int nargs, value *args) { if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISSTRING(arg)) { string_tonumber(MORPHO_GETSTRING(arg), &arg); } if (MORPHO_ISFLOAT(arg)) { return MORPHO_FLOATTOINTEGER(arg); } else if (MORPHO_ISINTEGER(arg)) { return arg; } } morpho_runtimeerror(v, MATH_NUMARGS, FUNCTION_INT); return MORPHO_NIL; } /** Convert to a floating point number */ value builtin_float(vm *v, int nargs, value *args) { if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISSTRING(arg)) { string_tonumber(MORPHO_GETSTRING(arg), &arg); } if (MORPHO_ISINTEGER(arg)) { return MORPHO_INTEGERTOFLOAT(arg); } else if (MORPHO_ISFLOAT(arg)){ return arg; } } morpho_runtimeerror(v, MATH_NUMARGS, FUNCTION_FLOAT); return MORPHO_NIL; } /** Convert to a boolean */ value builtin_bool(vm *v, int nargs, value *args) { if (nargs==1) { return MORPHO_BOOL(MORPHO_ISTRUE(MORPHO_GETARG(args, 0))); } morpho_runtimeerror(v, MATH_NUMARGS, FUNCTION_BOOL); return MORPHO_NIL; } /** Remainder */ value builtin_mod(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==2) { value a = MORPHO_GETARG(args, 0); value b = MORPHO_GETARG(args, 1); if (MORPHO_ISINTEGER(a) && MORPHO_ISINTEGER(b)) { out=MORPHO_INTEGER(MORPHO_GETINTEGERVALUE(a) % MORPHO_GETINTEGERVALUE(b)); } else { if (MORPHO_ISINTEGER(a)) a=MORPHO_INTEGERTOFLOAT(a); if (MORPHO_ISINTEGER(b)) b=MORPHO_INTEGERTOFLOAT(b); if (MORPHO_ISFLOAT(a) && MORPHO_ISFLOAT(b)) { out=MORPHO_FLOAT(fmod(MORPHO_GETFLOATVALUE(a), MORPHO_GETFLOATVALUE(b))); } else morpho_runtimeerror(v, MATH_NUMARGS, FUNCTION_INT); } } else morpho_runtimeerror(v, VM_INVALIDARGS, 2, nargs); return out; } /** Find the minimum and maximum values in an enumerable object */ typedef struct { value min; value max; } minmaxstruct; static bool minmaxfn(vm *v, indx i, value val, void *ref) { minmaxstruct *m=(minmaxstruct *) ref; value l=m->min, r=val; if (i==0 || morpho_extendedcomparevalue(l, r)<0) m->min=val; l=m->max; r=val; if (i==0 || morpho_extendedcomparevalue(l, r)>0) m->max=val; return true; } static bool builtin_minmax(vm *v, value obj, value *min, value *max) { minmaxstruct m; // intialize the minmaxstuct m.max = MORPHO_NIL; m.min = MORPHO_NIL; if (!builtin_enumerateloop(v, obj, minmaxfn, &m)) return false; if (min) *min = m.min; if (max) *max = m.max; return true; } bool builtin_minmaxargs(vm *v, int nargs, value *args, value *min, value *max, char *fname) { for (unsigned int i=0; i0) { value bounds[2]; value_minmax(nargs, minlist, &bounds[0], NULL); value_minmax(nargs, maxlist, NULL, &bounds[1]); objectlist *list = object_newlist(2, bounds); if (list) { out = MORPHO_OBJECT(list); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else morpho_runtimeerror(v, MAX_ARGS, FUNCTION_BOUNDS); } return out; } /** Find the minimum value in an enumerable object */ static value builtin_min(vm *v, int nargs, value *args) { value m[nargs+1]; value out = MORPHO_NIL; if (builtin_minmaxargs(v, nargs, args, m, NULL, FUNCTION_MIN)) { if (nargs>0) value_minmax(nargs, m, &out, NULL); else morpho_runtimeerror(v, MAX_ARGS, FUNCTION_MIN); } return out; } /** Find the maximum value in an enumerable object */ static value builtin_max(vm *v, int nargs, value *args) { value m[nargs+1]; value out = MORPHO_NIL; if (builtin_minmaxargs(v, nargs, args, NULL, m, FUNCTION_MAX)) { if (nargs>0) value_minmax(nargs, m, NULL, &out); else morpho_runtimeerror(v, MAX_ARGS, FUNCTION_MAX); } return out; } /** find the sign of a number */ static value builtin_sign(vm *v, int nargs, value *args){ if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISFLOAT(arg)) { if (MORPHO_GETFLOATVALUE(arg)>0) { return MORPHO_FLOAT(1); } else if (MORPHO_GETFLOATVALUE(arg)<0){ return MORPHO_FLOAT(-1); } else return MORPHO_FLOAT(0); } else if (MORPHO_ISINTEGER(arg)) { if (MORPHO_GETINTEGERVALUE(arg)>0) { return MORPHO_FLOAT(1); } else if (MORPHO_GETINTEGERVALUE(arg)<0){ return MORPHO_FLOAT(-1); } else return MORPHO_FLOAT(0); } else { morpho_runtimeerror(v, MATH_ARGS,FUNCTION_SIGN); } } morpho_runtimeerror(v, MATH_NUMARGS,FUNCTION_SIGN); return MORPHO_NIL; } /* ************************************ * Apply * *************************************/ /** Apply a function to a list of arguments */ value builtin_apply(vm *v, int nargs, value *args) { value ret = MORPHO_NIL; if (nargs<2) morpho_runtimeerror(v, APPLY_ARGS); value fn = MORPHO_GETARG(args, 0); value x = MORPHO_GETARG(args, 1); if (!morpho_iscallable(fn)) { morpho_runtimeerror(v, APPLY_NOTCALLABLE); return MORPHO_NIL; } if (nargs==2 && MORPHO_ISTUPLE(x)) { objecttuple *t = MORPHO_GETTUPLE(x); morpho_call(v, fn, t->length, t->tuple, &ret); } else if (nargs==2 && MORPHO_ISLIST(x)) { objectlist *lst = MORPHO_GETLIST(x); morpho_call(v, fn, lst->val.count, lst->val.data, &ret); } else { morpho_call(v, fn, nargs-1, &MORPHO_GETARG(args, 1), &ret); } return ret; } /* ************************************ * System * *************************************/ /** Call the operating system */ value builtin_system(vm *v, int nargs, value *args) { if (nargs==1) { value arg=MORPHO_GETARG(args, 0); if (MORPHO_ISSTRING(arg)) { return MORPHO_INTEGER(system(MORPHO_GETCSTRING(arg))); } } return MORPHO_NIL; } /** Clock */ value builtin_clock(vm *v, int nargs, value *args) { clock_t time; time = clock(); return MORPHO_FLOAT( ((double) time)/((double) CLOCKS_PER_SEC) ); } #define BUILTIN_MATH(function) \ builtin_addfunction(#function, builtin_##function, BUILTIN_FLAGSEMPTY); #define BUILTIN_MATH_BOOL(function) \ builtin_addfunction(#function, builtin_##function, BUILTIN_FLAGSEMPTY); #define BUILTIN_TYPECHECK(function) \ builtin_addfunction(#function, builtin_##function, BUILTIN_FLAGSEMPTY); void functiondefs_initialize(void) { builtin_addfunction(FUNCTION_CLOCK, builtin_clock, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_RANDOM, builtin_random, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_RANDOMINT, builtin_randomint, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_RANDOMNORMAL, builtin_randomnormal, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_SYSTEM, builtin_system, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_ARCTAN, builtin_arctan, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_ABS, builtin_fabs, BUILTIN_FLAGSEMPTY); BUILTIN_MATH(exp) BUILTIN_MATH(log) BUILTIN_MATH(log10) BUILTIN_MATH(sin) BUILTIN_MATH(cos) BUILTIN_MATH(tan) BUILTIN_MATH(asin) BUILTIN_MATH(acos) BUILTIN_MATH(sinh) BUILTIN_MATH(cosh) BUILTIN_MATH(tanh) BUILTIN_MATH(sqrt) BUILTIN_MATH(floor) BUILTIN_MATH(ceil) BUILTIN_MATH_BOOL(isfinite) BUILTIN_MATH_BOOL(isinf) BUILTIN_MATH_BOOL(isnan) BUILTIN_TYPECHECK(isnil) BUILTIN_TYPECHECK(isint) BUILTIN_TYPECHECK(isfloat) BUILTIN_TYPECHECK(isnumber) BUILTIN_TYPECHECK(isbool) BUILTIN_TYPECHECK(isobject) BUILTIN_TYPECHECK(isstring) BUILTIN_TYPECHECK(isclass) BUILTIN_TYPECHECK(isrange) BUILTIN_TYPECHECK(isdictionary) BUILTIN_TYPECHECK(islist) BUILTIN_TYPECHECK(istuple) BUILTIN_TYPECHECK(isarray) #ifdef MORPHO_INCLUDE_LINALG BUILTIN_TYPECHECK(ismatrix) #endif #ifdef MORPHO_INCLUDE_SPARSE BUILTIN_TYPECHECK(issparse) #endif #ifdef MORPHO_INCLUDE_GEOMETRY BUILTIN_TYPECHECK(ismesh) BUILTIN_TYPECHECK(isselection) BUILTIN_TYPECHECK(isfield) #endif builtin_addfunction(FUNCTION_REAL,builtin_real,BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_IMAG,builtin_imag,BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_ANGLE,builtin_angle,BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_CONJ,builtin_conj,BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_ISCALLABLE, builtin_iscallablefunction, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_INT, builtin_int, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_FLOAT, builtin_float, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_BOOL, builtin_bool, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_MOD, builtin_mod, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_BOUNDS, builtin_bounds, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_MIN, builtin_min, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_MAX, builtin_max, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_SIGN, builtin_sign, BUILTIN_FLAGSEMPTY); builtin_addfunction(FUNCTION_APPLY, builtin_apply, BUILTIN_FLAGSEMPTY); morpho_defineerror(MATH_ARGS, ERROR_HALT, MATH_ARGS_MSG); morpho_defineerror(MATH_NUMARGS, ERROR_HALT, MATH_NUMARGS_MSG); morpho_defineerror(MATH_ATANARGS, ERROR_HALT, MATH_ATANARGS_MSG); morpho_defineerror(TYPE_NUMARGS, ERROR_HALT, TYPE_NUMARGS_MSG); morpho_defineerror(MAX_ARGS, ERROR_HALT, MAX_ARGS_MSG); morpho_defineerror(APPLY_ARGS, ERROR_HALT, APPLY_ARGS_MSG); morpho_defineerror(APPLY_NOTCALLABLE, ERROR_HALT, APPLY_NOTCALLABLE_MSG); } #undef BUILTIN_MATH #undef BUILTIN_MATH_BOOL ================================================ FILE: src/builtin/functiondefs.h ================================================ /** @file functiondefs.h * @author T J Atherton * * @brief Built in function definitions */ #ifndef functiondefs_h #define functiondefs_h #include /* ------------------------------------------------------- * Built in function labels * ------------------------------------------------------- */ #define FUNCTION_RANDOM "random" #define FUNCTION_RANDOMINT "randomint" #define FUNCTION_RANDOMNORMAL "randomnormal" #define FUNCTION_CLOCK "clock" #define FUNCTION_SYSTEM "system" #define FUNCTION_INT "Int" #define FUNCTION_FLOAT "Float" #define FUNCTION_BOOL "Bool" #define FUNCTION_MOD "mod" #define FUNCTION_ABS "abs" #define FUNCTION_ISCALLABLE "iscallable" #define FUNCTION_MIN "min" #define FUNCTION_MAX "max" #define FUNCTION_BOUNDS "bounds" #define FUNCTION_REAL "real" #define FUNCTION_IMAG "imag" #define FUNCTION_ANGLE "angle" #define FUNCTION_CONJ "conj" #define FUNCTION_SIGN "sign" #define FUNCTION_APPLY "apply" #define FUNCTION_ARCTAN "arctan" /* ------------------------------------------------------- * Errors thrown by builtin functions * ------------------------------------------------------- */ #define MATH_ARGS "ExpctNmArgs" #define MATH_ARGS_MSG "Function '%s' expects numerical arguments." #define MATH_NUMARGS "ExpctArgNm" #define MATH_NUMARGS_MSG "Function '%s' expects 1 numerical argument." #define MATH_ATANARGS "AtanArgNm" #define MATH_ATANARGS_MSG "Function 'arctan' expects either 1 or 2 numerical arguments." #define TYPE_NUMARGS "TypArgNm" #define TYPE_NUMARGS_MSG "Function '%s' expects one argument." #define MAX_ARGS "MnMxArgs" #define MAX_ARGS_MSG "Function '%s' expects at least one numerical argument, list or matrix." #define APPLY_ARGS "ApplyArgs" #define APPLY_ARGS_MSG "Function 'apply' expects at least two arguments." #define APPLY_NOTCALLABLE "ApplyNtCllble" #define APPLY_NOTCALLABLE_MSG "Function 'apply' requires a callable object as its first argument." /* ------------------------------------------------------- * Interface to define builtin functions * ------------------------------------------------------- */ void functiondefs_initialize(void); #endif /* functions_h */ ================================================ FILE: src/classes/CMakeLists.txt ================================================ target_sources(morpho PRIVATE classes.h array.c array.h bool.c bool.h closure.c closure.h clss.c clss.h cmplx.c cmplx.h dict.c dict.h err.c err.h file.c file.h flt.c flt.h function.c function.h instance.c instance.h int.c int.h invocation.c invocation.h json.c json.h list.c list.h metafunction.c metafunction.h range.c range.h strng.c strng.h system.c system.h tuple.c tuple.h upvalue.c upvalue.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES classes.h array.h closure.h clss.h cmplx.h dict.h err.h file.h floatveneer.h function.h instance.h invocation.h json.h list.h metafunction.h range.h strng.h system.h upvalue.h ) ================================================ FILE: src/classes/array.c ================================================ /** @file array.c * @author T J Atherton * * @brief Defines array object type and Array class */ #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * Array objects * ********************************************************************** */ /** Array object definitions */ void objectarray_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectarray_markfn(object *obj, void *v) { objectarray *c = (objectarray *) obj; for (unsigned int i=0; inelements; i++) { morpho_markvalue(v, c->values[i]); } } size_t objectarray_sizefn(object *obj) { return sizeof(objectarray) + sizeof(value) * ( ((objectarray *) obj)->nelements+2*((objectarray *) obj)->ndim ); } objecttypedefn objectarraydefn = { .printfn=objectarray_printfn, .markfn=objectarray_markfn, .freefn=NULL, .sizefn=objectarray_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Initializes an array given the size */ void object_arrayinit(objectarray *array, unsigned int ndim, unsigned int *dim) { object_init((object *) array, OBJECT_ARRAY); unsigned int nel = (ndim==0 ? 0 : 1); /* Store pointers into the data array */ array->dimensions=array->data; array->multipliers=array->data+ndim; array->values=array->data+2*ndim; /* Store the description of array dimensions */ array->ndim=ndim; for (unsigned int i=0; idimensions[i]=MORPHO_INTEGER(dim[i]); array->multipliers[i]=MORPHO_INTEGER(nel); nel*=dim[i]; } /* Store the size of the object for convenient access */ array->nelements=nel; /* Arrays are initialized to nil. */ #ifdef MORPHO_NAN_BOXING memset(array->values, 0, sizeof(value)*nel); #else for (unsigned int i=0; ivalues[i]=MORPHO_FLOAT(0.0); #endif } /** @brief Creates an array object * @details Arrays are stored in memory as follows: * objectarray structure with flexible array member value * value [0..dim-1] the dimensions of the array * value [dim..2*dim-1] stores multipliers for each dimension to translate to the index * value [2*dim..] array elements in column major order, i.e. the matrix * [ [ 1, 2], * [ 3, 4] ] is stored as: * // the structure * 2, 2, // the dimensions * 1, 2, // multipliers for each index to access elements * 1, 3, 2, 4 // the elements in column major order */ objectarray *object_newarray(unsigned int ndim, unsigned int *dim) { /* Calculate the number of elements */ unsigned int nel=(ndim==0 ? 0 : dim[0]); for (unsigned int i=1; ivalues, v, sizeof(value)*n); return new; } /** Creates a new 1D array from a list of varray_value */ objectarray *object_arrayfromvarrayvalue(varray_value *v) { return object_arrayfromvaluelist(v->count, v->data); } /** Creates a new array object with the dimensions given as a list of values */ objectarray *object_arrayfromvalueindices(unsigned int ndim, value *dim) { unsigned int indx[ndim]; if (array_valuelisttoindices(ndim, dim, indx)) { return object_newarray(ndim, indx); } return NULL; } /** Clones an array. Does *not* clone the contents. */ objectarray *object_clonearray(objectarray *array) { objectarray *new = object_arrayfromvalueindices(array->ndim, array->data); if (new) memcpy(new->data, array->data, sizeof(value)*(array->nelements+2*array->ndim)); return new; } /** Recursively print a slice of an array */ bool array_print_recurse(vm *v, objectarray *a, unsigned int *indx, unsigned int dim, varray_char *out) { unsigned int bnd = MORPHO_GETINTEGERVALUE(a->dimensions[dim]); value val=MORPHO_NIL; varray_charadd(out, "[ ", 2); for (indx[dim]=0; indx[dim]ndim-1) { // Print if innermost element if (array_getelement(a, a->ndim, indx, &val)==ARRAY_OK) { morpho_printtobuffer(v, val, out); } else return false; } else if (!array_print_recurse(v, a, indx, dim+1, out)) return false; // Otherwise recurse if (indx[dim]ndim]; if (array_print_recurse(v, a, indx, 0, &out)) { varray_charwrite(&out, '\0'); // Ensure zero terminated morpho_printf(v, "%s", out.data); } varray_charclear(&out); } /** Converts an array error into an error code */ errorid array_error(objectarrayerror err) { switch (err) { case ARRAY_OUTOFBOUNDS: return VM_OUTOFBOUNDS; case ARRAY_WRONGDIM: return VM_ARRAYWRONGDIM; case ARRAY_NONINTINDX: return VM_NONNUMINDX; case ARRAY_ALLOC_FAILED: return ERROR_ALLOCATIONFAILED; case ARRAY_OK: UNREACHABLE("array_error called incorrectly."); } UNREACHABLE("Unhandled array error."); return VM_OUTOFBOUNDS; } /** Converts an array error into an matrix error code for use in slices*/ errorid array_to_matrix_error(objectarrayerror err) { #ifdef MORPHO_INCLUDE_LINALG switch (err) { case ARRAY_OUTOFBOUNDS: return MATRIX_INDICESOUTSIDEBOUNDS; case ARRAY_WRONGDIM: return MATRIX_INVLDNUMINDICES; case ARRAY_NONINTINDX: return MATRIX_INVLDINDICES; case ARRAY_ALLOC_FAILED: return ERROR_ALLOCATIONFAILED; case ARRAY_OK: UNREACHABLE("array_to_matrix_error called incorrectly."); } UNREACHABLE("Unhandled array error."); return VM_OUTOFBOUNDS; #else return array_error(err); #endif } /** Converts an array error into an list error code for use in slices*/ errorid array_to_list_error(objectarrayerror err) { switch (err) { case ARRAY_OUTOFBOUNDS: return VM_OUTOFBOUNDS; case ARRAY_WRONGDIM: return LIST_NUMARGS; case ARRAY_NONINTINDX: return LIST_ARGS; case ARRAY_ALLOC_FAILED: return ERROR_ALLOCATIONFAILED; case ARRAY_OK: UNREACHABLE("array_to_list_error called incorrectly."); } UNREACHABLE("Unhandled array error."); return VM_OUTOFBOUNDS; } /** Gets an array element */ objectarrayerror array_getelement(objectarray *a, unsigned int ndim, unsigned int *indx, value *out) { unsigned int k=0; if (ndim!=a->ndim) return ARRAY_WRONGDIM; for (unsigned int i=0; i=MORPHO_GETINTEGERVALUE(a->dimensions[i])) return ARRAY_OUTOFBOUNDS; k+=indx[i]*MORPHO_GETINTEGERVALUE(a->multipliers[i]); } *out = a->values[k]; return ARRAY_OK; } /** Creates a slice from a slicable object A. * @param[in] a - the sliceable object (array, list, matrix, etc..). * @param[in] dimFcn - a function that checks if the number of indecies is compatabile with the slicable object. * @param[in] constuctor - a function that create the a new object of the type of a. * @param[in] copy - a function that can copy information from a to out. * @param[in] ndim - the number of dimensions being indexed. * @param[in] slices - a set of indices that can be lists ranges or ints. * @param[out] out - returns the requested slice of a. */ objectarrayerror getslice(value *a, bool dimFcn(value *, unsigned int), void constructor(unsigned int *, unsigned int,value *), objectarrayerror copy(value * ,value *, unsigned int, unsigned int *, unsigned int *), unsigned int ndim, value *slices, value *out) { //dimension checking if (!(*dimFcn) (a,ndim)) return ARRAY_WRONGDIM; unsigned int slicesize[ndim]; for (unsigned int i=0; ival.count; // get the number of elements } else if (MORPHO_ISRANGE(slices[i])) { //if its a range objectrange * s = MORPHO_GETRANGE(slices[i]); slicesize[i] = range_count(s); } else return ARRAY_NONINTINDX; // by returning array a VM_NONNUMIDX will be thrown } // initialize out with the right size (constructor) (slicesize, ndim, out); if (!MORPHO_ISOBJECT(*out)) return ARRAY_ALLOC_FAILED; // fill it out recurivly unsigned int indx[ndim]; unsigned int newindx[ndim]; objectarrayerror err = setslicerecursive(a, out, copy, ndim, 0, indx, newindx, slices); if (err!=ARRAY_OK) { // Free allocated object if an error has occurred morpho_freeobject(*out); *out = MORPHO_NIL; } return err; } /** Iterates though the a ndim number of provided slices recursivly and copies the data from a to out. * @param[in] a - the sliceable object (array, list, matrix, etc..). * @param[out] out - returns the requeted slice of a. * @param[in] copy - a function that can copy information from a to out. * @param[in] ndim - the total number of dimentions being indexed. * @param[in] curdim - the current dimention being indexed. * @param[in] indx - an ndim list of indices that builds up to a locataion in a to copy data from. * @param[in] newindx - the place in out to put the data copied from a * @param[in] slices - a set of indices that can be lists ranges or ints. */ objectarrayerror setslicerecursive(value* a, value* out,objectarrayerror copy(value * ,value *, unsigned int, unsigned int *,unsigned int *),\ unsigned int ndim, unsigned int curdim, unsigned int *indx,unsigned int *newindx, value *slices){ // this gets given an array and out and a list of slices, // we resolve the top slice to a number and add it to a list objectarrayerror arrayerr; if (curdim == ndim) { // we've resolved all the indices we can now use the list arrayerr = (*copy)(a,out,ndim,indx,newindx); if (arrayerr!=ARRAY_OK) return arrayerr; } else { // we need to iterate though the current object if (MORPHO_ISINTEGER(slices[curdim])) { indx[curdim] = MORPHO_GETINTEGERVALUE(slices[curdim]); newindx[curdim] = 0; arrayerr = setslicerecursive(a, out, copy, ndim, curdim+1, indx, newindx, slices); if (arrayerr!=ARRAY_OK) return arrayerr; } else if (MORPHO_ISLIST(slices[curdim])) { // if this is a list objectlist * s = MORPHO_GETLIST(slices[curdim]); for (unsigned int i = 0; ival.count; i++ ){ // iterate through the list if (MORPHO_ISINTEGER(s->val.data[i])) { indx[curdim] = MORPHO_GETINTEGERVALUE(s->val.data[i]); newindx[curdim] = i; } else return ARRAY_NONINTINDX; arrayerr = setslicerecursive(a, out, copy, ndim, curdim+1, indx, newindx, slices); if (arrayerr!=ARRAY_OK) return arrayerr; } } else if (MORPHO_ISRANGE(slices[curdim])) { //if its a range objectrange * s = MORPHO_GETRANGE(slices[curdim]); value rangeValue; for (unsigned int i = 0; indim) return ARRAY_WRONGDIM; for (unsigned int i=0; i=MORPHO_GETINTEGERVALUE(a->dimensions[i])) return ARRAY_OUTOFBOUNDS; k+=indx[i]*MORPHO_GETINTEGERVALUE(a->multipliers[i]); } a->values[k]=in; return ARRAY_OK; } /* --------------------------- * Array constructor functions * --------------------------- */ /** Returns the maximum nesting depth in a list, including this one. * @param[in] list - the list to examine * @param[out] out - optionally return the dimensions of the nested lists. * To get dimension information: * Call list_nestingdepth with out set to NULL; this returns the size of the array needed. * Initialize the dimension array to zero. * Call list_nestingdepth again with out set to an output array */ unsigned int list_nestingdepth(objectlist *list, unsigned int *out) { unsigned int dim=0; for (unsigned int i=0; ival.count; i++) { if (MORPHO_ISLIST(list->val.data[i])) { unsigned int sdim=list_nestingdepth(MORPHO_GETLIST(list->val.data[i]), ( out ? out+1 : NULL)); if (sdim>dim) dim=sdim; } } if (out && list->val.count>*out) *out=list->val.count; return dim+1; } /* Internal function that recursively copied a nested list into an array. Use public interface array_copyfromnestedlist */ static void array_copyfromnestedlistrecurse(objectlist *list, unsigned int ndim, unsigned int *indx, unsigned int depth, objectarray *out) { for (unsigned int i=0; ival.count; i++) { indx[depth] = i; value val = list->val.data[i]; if (MORPHO_ISLIST(val)) array_copyfromnestedlistrecurse(MORPHO_GETLIST(val), ndim, indx, depth+1, out); else array_setelement(out, ndim, indx, val); } } /** Copies a nested list into an array.*/ void array_copyfromnestedlist(objectlist *in, objectarray *out) { unsigned int indx[out->ndim]; for (unsigned int i=0; indim; i++) indx[i]=0; array_copyfromnestedlistrecurse(in, out->ndim, indx, 0, out); } /** Constructs an array from a list initializer or returns NULL if the initializer isn't compatible with the requested array */ objectarray *array_constructfromlist(unsigned int ndim, unsigned int *dim, objectlist *initializer) { // Establish the dimensions of the nested list unsigned int nldim = list_nestingdepth(initializer, NULL); unsigned int ldim[nldim]; for (unsigned int i=0; i0) { // Check compatibility if (ndim!=nldim) return NULL; for (unsigned int i=0; i0) { // Check compatibility if (ndim!=initializer->ndim) return NULL; for (unsigned int i=0; idimensions[i])) return NULL; } } return object_clonearray(initializer); } /** Array constructor function */ value array_constructor(vm *v, int nargs, value *args) { unsigned int ndim; // Number of dimensions unsigned int dim[nargs+1]; // Size of each dimension value initializer=MORPHO_NIL; // An initializer if provided // Check that args are present if (nargs==0) { morpho_runtimeerror(v, ARRAY_ARGS); return MORPHO_NIL; } for (ndim=0; ndim0) array_valuelisttoindices(ndim, &MORPHO_GETARG(args, 0), dim); // Initializer is the first non-numerical argument; anything after is ignored if (ndimarray->ndim) return false; return true; } /** Constructsan array is with a generic interface */ void array_sliceconstructor(unsigned int *slicesize,unsigned int ndim,value* out){ *out = MORPHO_OBJECT(object_newarray(ndim,slicesize)); } /** Copies data from array a to array out with a generic interface */ objectarrayerror array_slicecopy(value * a,value * out, unsigned int ndim, unsigned int *indx,unsigned int *newindx){ value data; objectarrayerror arrayerr; arrayerr = array_getelement(MORPHO_GETARRAY(*a),ndim,indx,&data); // read the data if (arrayerr!=ARRAY_OK) return arrayerr; arrayerr=array_setelement(MORPHO_GETARRAY(*out), ndim, newindx, data); // write the data return arrayerr; } /* ********************************************************************** * Array class * ********************************************************************** */ /** Gets the array element with given indices */ value Array_getindex(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectarray *array=MORPHO_GETARRAY(MORPHO_SELF(args)); unsigned int indx[nargs]; if (array_valuelisttoindices(nargs, &MORPHO_GETARG(args, 0), indx)) { objectarrayerror err=array_getelement(array, nargs, indx, &out); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_error(err) ); } else { // these aren't simple indices, lets try to make a slice objectarrayerror err = getslice(&MORPHO_SELF(args),&array_slicedim,&array_sliceconstructor,&array_slicecopy,nargs,&MORPHO_GETARG(args, 0),&out); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_error(err) ); if (!MORPHO_ISNIL(out)){ morpho_bindobjects(v,1,&out); } else MORPHO_RAISE(v, VM_NONNUMINDX); } return out; } /** Sets the matrix element with given indices */ value Array_setindex(vm *v, int nargs, value *args) { objectarray *array=MORPHO_GETARRAY(MORPHO_SELF(args)); unsigned int indx[nargs-1]; if (array_valuelisttoindices(nargs-1, &MORPHO_GETARG(args, 0), indx)) { objectarrayerror err=array_setelement(array, nargs-1, indx, MORPHO_GETARG(args, nargs-1)); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_error(err) ); } else MORPHO_RAISE(v, VM_NONNUMINDX); return MORPHO_NIL; } /** Print an array */ value Array_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISARRAY(self)) return Object_print(v, nargs, args); array_print(v, MORPHO_GETARRAY(self)); return MORPHO_NIL; } /** Find an array's size */ value Array_count(vm *v, int nargs, value *args) { objectarray *slf = MORPHO_GETARRAY(MORPHO_SELF(args)); return MORPHO_INTEGER(slf->nelements); } /** Array dimensions */ value Array_dimensions(vm *v, int nargs, value *args) { objectarray *a=MORPHO_GETARRAY(MORPHO_SELF(args)); value out=MORPHO_NIL; objectlist *new=object_newlist(a->ndim, a->data); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Enumerate members of an array */ value Array_enumerate(vm *v, int nargs, value *args) { objectarray *slf = MORPHO_GETARRAY(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) { out=MORPHO_INTEGER(slf->nelements); } else if (nnelements) { out=slf->values[n]; } else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Clone an array */ value Array_clone(vm *v, int nargs, value *args) { objectarray *slf = MORPHO_GETARRAY(MORPHO_SELF(args)); value out=MORPHO_NIL; objectarray *new = object_clonearray(slf); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } MORPHO_BEGINCLASS(Array) MORPHO_METHOD(MORPHO_PRINT_METHOD, Array_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Array_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(ARRAY_DIMENSIONS_METHOD, Array_dimensions, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Array_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Array_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Array_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Array_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ objecttype objectarraytype; void array_initialize(void) { // Create array object type objectarraytype=object_addtype(&objectarraydefn); // Locate the Object class to use as the parent class of Array objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Array constructor function morpho_addfunction(ARRAY_CLASSNAME, ARRAY_CLASSNAME " (...)", array_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // Create Array veneer class value arrayclass=builtin_addclass(ARRAY_CLASSNAME, MORPHO_GETCLASSDEFINITION(Array), objclass); object_setveneerclass(OBJECT_ARRAY, arrayclass); // Array error messages morpho_defineerror(ARRAY_ARGS, ERROR_HALT, ARRAY_ARGS_MSG); morpho_defineerror(ARRAY_INIT, ERROR_HALT, ARRAY_INIT_MSG); morpho_defineerror(ARRAY_CMPT, ERROR_HALT, ARRAY_CMPT_MSG); } ================================================ FILE: src/classes/array.h ================================================ /** @file array.h * @author T J Atherton * * @brief Defines array object type and Array class */ #ifndef array_h #define array_h #include "object.h" /* ------------------------------------------------------- * Array object type * ------------------------------------------------------- */ extern objecttype objectarraytype; #define OBJECT_ARRAY objectarraytype typedef struct { object obj; unsigned int ndim; unsigned int nelements; value *values; value *dimensions; value *multipliers; value data[]; } objectarray; /** Tests whether an object is an array */ #define MORPHO_ISARRAY(val) object_istype(val, OBJECT_ARRAY) /** Gets the object as an array */ #define MORPHO_GETARRAY(val) ((objectarray *) MORPHO_GETOBJECT(val)) /** Creates an array object */ objectarray *object_newarray(unsigned int dimension, unsigned int *dim); /** Creates a new array from a list of values */ objectarray *object_arrayfromvaluelist(unsigned int n, value *v); /** Creates a new 1D array from a list of varray_value */ objectarray *object_arrayfromvarrayvalue(varray_value *v); /** Creates a new array object with the dimensions given as a list of values */ objectarray *object_arrayfromvalueindices(unsigned int ndim, value *dim); /* ------------------------------------------------------- * Array veneer class * ------------------------------------------------------- */ #define ARRAY_CLASSNAME "Array" #define ARRAY_DIMENSIONS_METHOD "dimensions" /* ------------------------------------------------------- * Array error messages * ------------------------------------------------------- */ #define ARRAY_ARGS "ArrayArgs" #define ARRAY_ARGS_MSG "Array must be called with integer dimensions as arguments." #define ARRAY_INIT "ArrayInit" #define ARRAY_INIT_MSG "Array initializer must be another array or a list." #define ARRAY_CMPT "ArrayCmpt" #define ARRAY_CMPT_MSG "Array initializer is not compatible with the requested dimensions." /* ------------------------------------------------------- * Array interface * ------------------------------------------------------- */ /* Public interfaces to various data structures */ typedef enum { ARRAY_OK, ARRAY_WRONGDIM, ARRAY_OUTOFBOUNDS, ARRAY_NONINTINDX, ARRAY_ALLOC_FAILED } objectarrayerror; errorid array_error(objectarrayerror err); errorid array_to_matrix_error(objectarrayerror err); errorid array_to_list_error(objectarrayerror err); bool array_valuelisttoindices(unsigned int ndim, value *in, unsigned int *out); objectarrayerror array_getelement(objectarray *a, unsigned int ndim, unsigned int *indx, value *out); objectarrayerror array_setelement(objectarray *a, unsigned int ndim, unsigned int *indx, value in); void array_print(vm *v, objectarray *a); objectarrayerror setslicerecursive(value* a, value* out,objectarrayerror copy(value * ,value *,\ unsigned int, unsigned int *,unsigned int *),unsigned int ndim,\ unsigned int curdim, unsigned int *indx,unsigned int *newindx, value *slices); objectarrayerror getslice(value *a, bool dimFcn(value *,unsigned int),\ void constuctor(unsigned int *,unsigned int,value *),\ objectarrayerror copy(value * ,value *, unsigned int, unsigned int *,unsigned int *),\ unsigned int ndim, value *slices, value *out); objectarrayerror array_slicecopy(value * a,value * out, unsigned int ndim, unsigned int *indx,unsigned int *newindx); void array_sliceconstructor(unsigned int *slicesize,unsigned int ndim,value* out); bool array_slicedim(value * a, unsigned int ndim); void array_initialize(void); #endif ================================================ FILE: src/classes/bool.c ================================================ /** @file bool.c * @author T J Atherton * * @brief Veneer class for bool values */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * Bool veneer class * ********************************************************************** */ MORPHO_BEGINCLASS(Bool) MORPHO_METHOD(MORPHO_CLASS_METHOD, Object_class, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_RESPONDSTO_METHOD, Object_respondsto, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INVOKE_METHOD, Object_invoke, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, MORPHO_FN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ void bool_initialize(void) { // Create Bool veneer class value boolclass=builtin_addclass(BOOL_CLASSNAME, MORPHO_GETCLASSDEFINITION(Bool), MORPHO_NIL); value_setveneerclass(MORPHO_TRUE, boolclass); } ================================================ FILE: src/classes/bool.h ================================================ /** @file bool.h * @author T J Atherton * * @brief Veneer class for bool values */ #ifndef bool_h #define bool_h /* ------------------------------------------------------- * Bool veneer class * ------------------------------------------------------- */ #define BOOL_CLASSNAME "Bool" /* ------------------------------------------------------- * Bool error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * Bool interface * ------------------------------------------------------- */ void bool_initialize(void); #endif ================================================ FILE: src/classes/classes.h ================================================ /** @file classes.h * @author T J Atherton * * @brief List of classes and object types */ #ifndef classes_h #define classes_h #include "builtin.h" #include "upvalue.h" #include "function.h" #include "metafunction.h" #include "clss.h" #include "cmplx.h" #include "closure.h" #include "invocation.h" #include "instance.h" #include "list.h" #include "array.h" #include "range.h" #include "strng.h" #include "dict.h" #include "tuple.h" #include "err.h" #include "bool.h" #include "flt.h" #include "int.h" //#include "file.h" //#include "system.h" #include "json.h" #include "matrix.h" #endif /* classes_h */ ================================================ FILE: src/classes/closure.c ================================================ /** @file closure.c * @author T J Atherton * * @brief Defines closure object type and Closure class */ #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * objectclosure definitions * ********************************************************************** */ void objectclosure_printfn(object *obj, void *v) { objectclosure *f = (objectclosure *) obj; morpho_printf(v, "<"); objectfunction_printfn((object *) f->func, v); morpho_printf(v, ">"); } void objectclosure_markfn(object *obj, void *v) { objectclosure *c = (objectclosure *) obj; morpho_markobject(v, (object *) c->func); for (unsigned int i=0; inupvalues; i++) { morpho_markobject(v, (object *) c->upvalues[i]); } } size_t objectclosure_sizefn(object *obj) { return sizeof(objectclosure)+sizeof(objectupvalue *)*((objectclosure *) obj)->nupvalues; } objecttypedefn objectclosuredefn = { .printfn=objectclosure_printfn, .markfn=objectclosure_markfn, .freefn=NULL, .sizefn=objectclosure_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Closure functions */ void object_closureinit(objectclosure *c) { c->func=NULL; } /** @brief Creates a new closure * @param sf the objectfunction of the current environment * @param func a function object to enclose * @param np the prototype number to use */ objectclosure *object_newclosure(objectfunction *sf, objectfunction *func, indx np) { objectclosure *new = NULL; varray_upvalue *up = NULL; if (npprototype.count) { up = &sf->prototype.data[np]; } if (up) { new = (objectclosure *) object_new(sizeof(objectclosure) + sizeof(objectupvalue*)*up->count, OBJECT_CLOSURE); if (new) { object_closureinit(new); new->func=func; for (unsigned int i=0; icount; i++) { new->upvalues[i]=NULL; } new->nupvalues=up->count; } } return new; } /* ********************************************************************** * objectclosure utility functions * ********************************************************************** */ /* ********************************************************************** * Closure veneer class * ********************************************************************** */ value Closure_tostring(vm *v, int nargs, value *args) { objectclosure *self=MORPHO_GETCLOSURE(MORPHO_SELF(args)); value out = MORPHO_NIL; varray_char buffer; varray_charinit(&buffer); if (self->func) { varray_charadd(&buffer, "<func->name, &buffer); varray_charadd(&buffer, ">>", 2); } out = object_stringfromvarraychar(&buffer); if (MORPHO_ISSTRING(out)) { morpho_bindobjects(v, 1, &out); } varray_charclear(&buffer); return out; } MORPHO_BEGINCLASS(Closure) MORPHO_METHOD(MORPHO_TOSTRING_METHOD, Closure_tostring, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectclosuretype; void closure_initialize(void) { // Create closure object type objectclosuretype=object_addtype(&objectclosuredefn); // Locate the Object class to use as the parent class of Closure objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // No constructor function; closures are generated by the runtime // Create Closure veneer class value closureclass=builtin_addclass(CLOSURE_CLASSNAME, MORPHO_GETCLASSDEFINITION(Closure), objclass); object_setveneerclass(OBJECT_CLOSURE, closureclass); } ================================================ FILE: src/classes/closure.h ================================================ /** @file closure.h * @author T J Atherton * * @brief Defines closure object type and Closure class */ #ifndef closure_h #define closure_h #include "object.h" /* ------------------------------------------------------- * Closure objects * ------------------------------------------------------- */ extern objecttype objectclosuretype; #define OBJECT_CLOSURE objectclosuretype typedef struct { object obj; objectfunction *func; int nupvalues; objectupvalue *upvalues[]; } objectclosure; objectclosure *object_newclosure(objectfunction *sf, objectfunction *func, indx np); /** Tests whether an object is a closure */ #define MORPHO_ISCLOSURE(val) object_istype(val, OBJECT_CLOSURE) /** Gets the object as a closure */ #define MORPHO_GETCLOSURE(val) ((objectclosure *) MORPHO_GETOBJECT(val)) /** Retrieve the function object from a closure */ #define MORPHO_GETCLOSUREFUNCTION(val) (((objectclosure *) MORPHO_GETOBJECT(val))->func) /* ------------------------------------------------------- * Closure veneer class * ------------------------------------------------------- */ #define CLOSURE_CLASSNAME "Closure" /* ------------------------------------------------------- * Closure error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * Closure interface * ------------------------------------------------------- */ void closure_initialize(void); #endif ================================================ FILE: src/classes/clss.c ================================================ /** @file clss.c * @author T J Atherton * * @brief Defines class object type */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * objectclass definitions * ********************************************************************** */ /** Class object definitions */ void objectclass_printfn(object *obj, void *v) { morpho_printf(v, "@%s", MORPHO_GETCSTRING(((objectclass *) obj)->name)); } void objectclass_markfn(object *obj, void *v) { objectclass *c = (objectclass *) obj; morpho_markvalue(v, c->name); morpho_markdictionary(v, &c->methods); morpho_markvarrayvalue(v, &c->parents); morpho_markvarrayvalue(v, &c->children); } void objectclass_freefn(object *obj) { objectclass *klass = (objectclass *) obj; morpho_freeobject(klass->name); dictionary_clear(&klass->methods); varray_valueclear(&klass->parents); varray_valueclear(&klass->children); varray_valueclear(&klass->linearization); } size_t objectclass_sizefn(object *obj) { return sizeof(objectclass); } objecttypedefn objectclassdefn = { .printfn=objectclass_printfn, .markfn=objectclass_markfn, .freefn=objectclass_freefn, .sizefn=objectclass_sizefn, .hashfn=NULL, .cmpfn=NULL }; objectclass *object_newclass(value name) { objectclass *newclass = (objectclass *) object_new(sizeof(objectclass), OBJECT_CLASS); if (newclass) { newclass->name=object_clonestring(name); dictionary_init(&newclass->methods); varray_valueinit(&newclass->parents); varray_valueinit(&newclass->children); varray_valueinit(&newclass->linearization); newclass->superclass=NULL; newclass->uid=0; } return newclass; } /* ********************************************************************** * C3 Linearization algorithm * ********************************************************************** */ /** C3 linearization aims to provide a linear ordering for a class hierarchy. It respects: * 1. Consistency with the hierarchy of classes (i.e. a class should appear AFTER any of its children). * 2. Consistency with the local precedence order for each class definition. * 3. Consistency with the extended precedence graph. * see: - Barrett et al. "A Monotonic Superclass Linearization for Dylan" [https://opendylan.org/_static/c3-linearization.pdf] * - Simionato, "The Python 2.3 Method Resolution Order" Python 2.3 [https://www.python.org/download/releases/2.3/mro/] * - Hivert & Thierry "Controlling the C3 super class linearization algorithm for large hierarchies of classes" [https://arxiv.org/pdf/2401.12740] */ void _print(varray_value *list) { printf("[ "); for (int i=0; icount; i++) { morpho_printvalue(NULL, list->data[i]); if (icount-1) printf(", "); } printf(" ]"); } /** Check if value v is in the tail of a list? */ bool _intail(varray_value *list, value v) { for (int i=1; icount; i++) { if (MORPHO_ISEQUAL(list->data[i], v)) return true; } return false; } /** Remove value v from a list in */ void _remove(varray_value *list, value v) { for (int i=0; icount; i++) { if (MORPHO_ISEQUAL(list->data[i], v)) { if (icount-1) memmove(list->data+i, list->data+i+1, sizeof(value)*(list->count-i-1)); list->count--; } } } /** Check if value v is in any tail of the set of lists */ bool _inanytail(int n, varray_value *in, value v) { for (int i=0; i0) return false; return true; } /** Performs one C3 merge operation for a set of lists */ bool _merge(int n, varray_value *in, varray_value *out) { for (int i=0; ilinearization.count) varray_valueadd(out, parent->linearization.data, parent->linearization.count); } /** Compute the linearization of a given class */ bool _linearize(objectclass *klass, varray_value *out) { // Add this class to the start of the list varray_valuewrite(out, MORPHO_OBJECT(klass)); if (klass->parents.count==0) return true; int n=klass->parents.count+1; // Start with the linearizations of the parent classes & the list of parent classes themselves varray_value lin[n]; for (int i=0; iparents.data[i]), &lin[i]); varray_valueadd(&lin[n-1], klass->parents.data, klass->parents.count); // Also add the parents to preserve their order bool success=true; while (success && !_done(n, lin)) { success=_merge(n, lin, out); } for (int i=0; ilinearization.count=0; return _linearize(klass, &klass->linearization); } /* ********************************************************************** * Class veneer class * ********************************************************************** */ MORPHO_BEGINCLASS(Class) MORPHO_METHOD(MORPHO_CLASS_METHOD, Object_class, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_RESPONDSTO_METHOD, Object_respondsto, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INVOKE_METHOD, Object_invoke, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, MORPHO_FN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectclasstype; void class_initialize(void) { // objectclass is a core type so is intialized earlier // Locate the Object class to use as the parent class of Class objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value classclass=builtin_addclass(CLASS_CLASSNAME, MORPHO_GETCLASSDEFINITION(Class), objclass); object_setveneerclass(OBJECT_CLASS, classclass); // No constructor function; classes are generated by the compiler // Class error messages morpho_defineerror(CLASS_INVK, ERROR_HALT, CLASS_INVK_MSG); } ================================================ FILE: src/classes/clss.h ================================================ /** @file clss.h * @author T J Atherton * * @brief Defines class object type */ #ifndef clss_h #define clss_h #include "object.h" /* ------------------------------------------------------- * Class objects * ------------------------------------------------------- */ extern objecttype objectclasstype; #define OBJECT_CLASS objectclasstype typedef struct sobjectclass { object obj; struct sobjectclass *superclass; /** The class's superclass */ value name; /** Class name */ dictionary methods; /** Method dictionary */ varray_value parents; /** Classes this class inherits from */ varray_value children; /** Classes that inherit from this class */ varray_value linearization; /** Classes that inherit from this class */ int uid; } objectclass; /** Tests whether an object is a class */ #define MORPHO_ISCLASS(val) object_istype(val, OBJECT_CLASS) /** Gets the object as a class */ #define MORPHO_GETCLASS(val) ((objectclass *) MORPHO_GETOBJECT(val)) /** Gets the superclass */ #define MORPHO_GETSUPERCLASS(val) (MORPHO_GETCLASS(val)->superclass) /* ------------------------------------------------------- * Class veneer class * ------------------------------------------------------- */ #define CLASS_CLASSNAME "Class" /* ------------------------------------------------------- * Class error messages * ------------------------------------------------------- */ #define CLASS_INVK "ClssInvk" #define CLASS_INVK_MSG "Cannot invoke method '%s' on a class." /* ------------------------------------------------------- * Class interface * ------------------------------------------------------- */ objectclass *object_newclass(value name); objectclass *morpho_lookupclass(value obj); bool class_linearize(objectclass *klass); void class_initialize(void); #endif ================================================ FILE: src/classes/cmplx.c ================================================ /** @file complex.c * @author D Hellstein and T J Atherton * * @brief Complex number type */ #include #include #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * Complex objects * ********************************************************************** */ objecttype objectcomplextype; /** Complex object definitions */ size_t objectcomplex_sizefn(object *obj) { return sizeof(objectcomplex); } void objectcomplex_printfn(object *obj, void *v) { complex_print(v, (objectcomplex *) obj); } int objectcomplex_cmpfn(object *a, object *b) { objectcomplex *acomp = (objectcomplex *) a; objectcomplex *bcomp = (objectcomplex *) b; return (complex_isequal(acomp, bcomp)? MORPHO_EQUAL: MORPHO_NOTEQUAL); } objecttypedefn objectcomplexdefn = { .printfn=objectcomplex_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectcomplex_sizefn, .hashfn=NULL, .cmpfn=objectcomplex_cmpfn }; /** Creates a complex object */ objectcomplex *object_newcomplex(double real,double imag) { objectcomplex *new = (objectcomplex *) object_new(sizeof(objectcomplex), OBJECT_COMPLEX); if (new) { new->Z=MCBuild(real,imag); } return new; } /* ********************************************************************** * Other constructors * ********************************************************************** */ /** Create complex number from a float */ objectcomplex *object_complexfromfloat(double val) { objectcomplex *ret=object_newcomplex(val,0.0); return ret; } /** Create complex number from a complex */ objectcomplex *object_complexfromcomplex(MorphoComplex val) { objectcomplex *ret=object_newcomplex(creal(val),cimag(val)); return ret; } /** Clone a complex */ objectcomplex *object_clonecomplex(objectcomplex *in) { objectcomplex *new = object_newcomplex(creal(in->Z),cimag(in->Z)); return new; } /** Clone a Complex Stored in a value*/ value object_clonecomplexvalue(value val) { value out = MORPHO_NIL; if (MORPHO_ISCOMPLEX(val)) { objectcomplex *c = MORPHO_GETCOMPLEX(val); out=MORPHO_OBJECT(object_clonecomplex(c)); } return out; } /* ********************************************************************** * Complex operations * ********************************************************************* */ /** @brief Gets a complex numbers real part */ void complex_getreal(objectcomplex *c, double *value) { *value = creal(c->Z); } /** @brief Gets a complex numbers imaginary part */ void complex_getimag(objectcomplex *c, double *value) { *value = cimag(c->Z); } /** @brief checks equality on two complex numbers */ bool complex_isequal(objectcomplex *a, objectcomplex *b) { return MCEq(a->Z,b->Z); } /** @brief checks equality between a complex number and a value */ bool complex_isequaltonumber(objectcomplex *a, value b) { if (MORPHO_ISNUMBER(b)){ double val; morpho_valuetofloat(b,&val); return MCEq(a->Z, MCBuild(val,0.0)); } return false; } /** Prints a complex number */ void complex_print(vm *v, objectcomplex *a) { char sign = '+'; if (cimag(a->Z)<0) { sign = '-'; } double Zr = creal(a->Z), Zi = cimag(a->Z), R = cabs(a->Z); double showZr = ( fabs(Zr) < MORPHO_RELATIVE_EPS*R ? 0 : Zr); double showZi = ( fabs(Zi) < MORPHO_RELATIVE_EPS*R ? 0 : fabs(Zi)); morpho_printf(v, "%g %c %gim", showZr, sign, showZi); } /* ********************************************************************** * Complex arithmetic * ********************************************************************* */ /** performs out = a + b */ void complex_add(objectcomplex *a, objectcomplex *b, objectcomplex *out){ out->Z = MCAdd(a->Z,b->Z); } /** performs out = a + b where a is not complex */ void complex_add_real(objectcomplex *a, double b, objectcomplex *out){ out->Z = MCAdd(a->Z, MCBuild(b,0)); } /** performs out = a - b */ void complex_sub(objectcomplex *a, objectcomplex *b, objectcomplex *out) { out->Z = MCSub(a->Z, b->Z); } /** performs out = a * b */ void complex_mul(objectcomplex *a, objectcomplex *b, objectcomplex *out){ out->Z = MCMul(a->Z, b->Z); } /** performs out = a * b where b is real */ void complex_mul_real(objectcomplex *a, double b, objectcomplex *out){ out->Z = MCScale(a->Z, b); } /** performs out = a */ void complex_copy(objectcomplex *a, objectcomplex *out) { out->Z = a->Z; } /** performs out = a ^ b where b is real*/ void complex_power(objectcomplex *a, double exponent, objectcomplex *out){ out->Z = cpow(a->Z,MCBuild(exponent, 0)); } /** performs out = a ^ b for complex numbers*/ void complex_cpower(objectcomplex *a, objectcomplex *b, objectcomplex *out){ out->Z = cpow(a->Z,b->Z); } /** performs out = a / b */ void complex_div(objectcomplex *a, objectcomplex *b, objectcomplex *out){ out->Z = MCDiv(a->Z,b->Z); } /** performs out = 1/a */ void complex_invert(objectcomplex *a, objectcomplex *out){ out->Z = MCDiv(MCBuild(1,0), a->Z); } /** performs out = conj(a) by negating the imaginary part */ void complex_conj(objectcomplex *a, objectcomplex *out) { out->Z = conj(a->Z); } /** calculates theta in the complex representation a = r e^{i theta} */ void complex_angle(objectcomplex *a, double *out){ *out = carg(a->Z); } void complex_abs(objectcomplex *a, double *out) { *out = cabs(a->Z); } /* ********************************************************************** * Builtin Mathematical Funtions For Complex Numbers * ********************************************************************* */ // Macro for creating a value from a new complex that copies val #define RET_COMPLEX(val,out) \ objectcomplex *new=NULL;\ new = object_complexfromcomplex(val);\ if (new) {\ out=MORPHO_OBJECT(new);\ morpho_bindobjects(v, 1, &out);\ } // Macro for creating a value bool #define RET_DOUBLE(val,out) \ out = MORPHO_FLOAT(val); #define COMPLEX_BUILTIN(fcn,type,MAKEVAL)\ value complex_builtin##fcn(vm * v, objectcomplex *c) {\ value out = MORPHO_NIL;\ type val = c##fcn(c->Z);\ MAKEVAL(val,out)\ return out;\ } value complex_builtinfabs(vm * v, objectcomplex *c) { double val = cabs(c->Z); return MORPHO_FLOAT(val); } COMPLEX_BUILTIN(exp,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(log,MorphoComplex,RET_COMPLEX) value complex_builtinlog10(vm * v, objectcomplex *c) { value out = MORPHO_NIL; MorphoComplex val = MCScale(clog(c->Z), 1.0/log(10)); RET_COMPLEX(val,out) return out; } COMPLEX_BUILTIN(sin,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(cos,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(tan,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(asin,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(acos,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(sinh,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(cosh,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(tanh,MorphoComplex,RET_COMPLEX) COMPLEX_BUILTIN(sqrt,MorphoComplex,RET_COMPLEX) value complex_builtinfloor(vm * v, objectcomplex *c) { value out = MORPHO_NIL; MorphoComplex val = MCBuild(floor(creal(c->Z)),floor(cimag(c->Z))); RET_COMPLEX(val,out) return out; } value complex_builtinceil(vm * v, objectcomplex *c) { value out = MORPHO_NIL; MorphoComplex val = MCBuild(ceil(creal(c->Z)), ceil(cimag(c->Z))); RET_COMPLEX(val,out) return out; } #undef COMPLEX_BUILTIN #undef RET_COMPLEX #undef RET_DOUBLE #define COMPLEX_BUILTIN_BOOL(fcn,logicalop)\ value complex_builtin##fcn(objectcomplex *c) {\ bool val = fcn(creal(c->Z)) logicalop fcn(cimag(c->Z));\ return MORPHO_BOOL(val);\ } COMPLEX_BUILTIN_BOOL(isfinite,&&) COMPLEX_BUILTIN_BOOL(isinf,||) COMPLEX_BUILTIN_BOOL(isnan,||) #undef COMPLEX_BUILTIN_BOOL value complex_builtinatan(vm *v, value c){ value out = MORPHO_NIL; MorphoComplex val = catan(MORPHO_GETCOMPLEX(c)->Z); objectcomplex *new = NULL; new = object_complexfromcomplex(val); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } value complex_builtinatan2(vm *v, value c1, value c2){ value out = MORPHO_NIL; MorphoComplex val=MCBuild(0,0); if (MORPHO_ISCOMPLEX(c1) && MORPHO_ISCOMPLEX(c2)) { val = catan(MCDiv(MORPHO_GETCOMPLEX(c1)->Z, MORPHO_GETCOMPLEX(c2)->Z)); } else if (MORPHO_ISCOMPLEX(c1) && MORPHO_ISNUMBER(c2)) { double num; morpho_valuetofloat(c2,&num); val = catan(MCScale(MORPHO_GETCOMPLEX(c1)->Z, 1.0/num)); } else if (MORPHO_ISNUMBER(c1) && MORPHO_ISCOMPLEX(c2)) { double num; morpho_valuetofloat(c1,&num); val = catan(MCDiv(MCBuild(num,0), MORPHO_GETCOMPLEX(c2)->Z)); } else { morpho_runtimeerror(v, COMPLEX_INVLDNARG); return MORPHO_NIL; } objectcomplex *new = NULL; new = object_complexfromcomplex(val); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /* ********************************************************************** * Complex veneer class * ********************************************************************* */ /** Constructs a Complex object */ value complex_constructor(vm *v, int nargs, value *args) { double real=0, imag=0; objectcomplex *new=NULL; value out=MORPHO_NIL; // expect 2 aruments if (nargs==2){ // make sure both are numbers and cast them to floats if (MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { morpho_valuetofloat(MORPHO_GETARG(args, 0), &real); } else goto complex_constructor_error; if (MORPHO_ISNUMBER(MORPHO_GETARG(args, 1))) { morpho_valuetofloat(MORPHO_GETARG(args, 1), &imag); } else goto complex_constructor_error; } else goto complex_constructor_error; new = object_newcomplex(real, imag); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; complex_constructor_error: morpho_runtimeerror(v, COMPLEX_CONSTRUCTOR); return MORPHO_NIL; } /** Gets the real part of a complex number */ value Complex_getreal(vm *v, int nargs, value *args) { objectcomplex *c=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs>0){ morpho_runtimeerror(v, COMPLEX_INVLDNARG); return out; } double real; complex_getreal(c, &real); out = MORPHO_FLOAT(real); return out; } /** Gets the imaginary part of a complex number */ value Complex_getimag(vm *v, int nargs, value *args) { objectcomplex *c=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs>0){ morpho_runtimeerror(v, COMPLEX_INVLDNARG); return out; } double imag; complex_getimag(c, &imag); out = MORPHO_FLOAT(imag); return out; } /** Prints a complex */ value Complex_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISCOMPLEX(self)) return Object_print(v, nargs, args); objectcomplex *c=MORPHO_GETCOMPLEX(self); complex_print(v, c); return MORPHO_NIL; } /** Complex add */ value Complex_add(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_add(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); if (new) { out=MORPHO_OBJECT(new); complex_add_real(a, val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Complex subtract */ value Complex_sub(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_sub(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); if (new) { out=MORPHO_OBJECT(new); complex_add_real(a, -val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Right subtract */ value Complex_subr(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_clonecomplex(a); complex_mul_real(new,-1,new); if (new) { out=MORPHO_OBJECT(new); complex_add_real(new, val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Complex multiply */ value Complex_mul(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_mul(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); if (new) { out=MORPHO_OBJECT(new); complex_mul_real(a, val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Complex divide */ value Complex_div(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_div(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); if (new) { out=MORPHO_OBJECT(new); complex_mul_real(a, 1.0/val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Complex right divide */ value Complex_divr(vm *v, int nargs, value *args) { // this gets called when we divide a nonobject (number) by a complex number objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); complex_invert(a,new); complex_mul_real(new,val,new); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else UNREACHABLE("Number did not return float value"); } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); return out; } /** Complex exponentiation */ value Complex_power(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { // raise a complex number to a complex power objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_cpower(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { // raise complex power to a number double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(0,0); if (new) { out=MORPHO_OBJECT(new); complex_power(a, val, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Complex right exponentiation */ value Complex_powerr(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISCOMPLEX(MORPHO_GETARG(args, 0))) { // raise a complex number to a complex power objectcomplex *b=MORPHO_GETCOMPLEX(MORPHO_GETARG(args, 0)); objectcomplex *new = object_newcomplex(0, 0); if (new) { out=MORPHO_OBJECT(new); complex_cpower(a, b, new); } } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { // raise a number to a complex power double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectcomplex *new = object_newcomplex(val,0); if (new) { out=MORPHO_OBJECT(new); complex_cpower(new, a, new); } } } else morpho_runtimeerror(v, COMPLEX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Angle of a complex number */ value Complex_angle(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); double val; complex_angle(a, &val); return MORPHO_FLOAT(val); } value Complex_abs(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); double val; complex_abs(a, &val); return MORPHO_FLOAT(val); } /** Conjugate of a complex */ value Complex_conjugate(vm *v, int nargs, value *args) { objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); value out=MORPHO_NIL; objectcomplex *new = object_newcomplex(0,0); if (new) { complex_conj(a, new); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Clones a complex */ value Complex_clone(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectcomplex *a=MORPHO_GETCOMPLEX(MORPHO_SELF(args)); objectcomplex *new=object_clonecomplex(a); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } MORPHO_BEGINCLASS(ComplexNum) MORPHO_METHOD(MORPHO_PRINT_METHOD, Complex_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Complex_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Complex_sub, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MUL_METHOD, Complex_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIV_METHOD, Complex_div, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADDR_METHOD, Complex_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUBR_METHOD, Complex_subr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MULR_METHOD, Complex_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIVR_METHOD, Complex_divr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_POW_METHOD, Complex_power, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_POWR_METHOD, Complex_powerr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(COMPLEX_ANGLE_METHOD, Complex_angle, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(COMPLEX_CONJUGATE_METHOD, Complex_conjugate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(COMPLEX_REAL_METHOD, Complex_getreal, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(COMPLEX_IMAG_METHOD, Complex_getimag, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(COMPLEX_ABS_METHOD, Complex_abs, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Complex_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************* */ void complex_initialize(void) { // Define complex object type objectcomplextype=object_addtype(&objectcomplexdefn); // Complex constructor function morpho_addfunction(COMPLEX_CLASSNAME, COMPLEX_CLASSNAME " (...)", complex_constructor, MORPHO_FN_CONSTRUCTOR, NULL); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Define Complex class value complexclass=builtin_addclass(COMPLEX_CLASSNAME, MORPHO_GETCLASSDEFINITION(ComplexNum), objclass); object_setveneerclass(OBJECT_COMPLEX, complexclass); // Complex error messages morpho_defineerror(COMPLEX_CONSTRUCTOR, ERROR_HALT, COMPLEX_CONSTRUCTOR_MSG); morpho_defineerror(COMPLEX_ARITHARGS, ERROR_HALT, COMPLEX_ARITHARGS_MSG); morpho_defineerror(COMPLEX_INVLDNARG, ERROR_HALT, COMPLEX_INVLDNARG_MSG); } ================================================ FILE: src/classes/cmplx.h ================================================ /** @file cmplx.h * @author D Hellstein and T J Atherton * * @brief Veneer class over the objectcomplex type */ #ifndef cmplx_h #define cmplx_h #include #include #include "classes.h" #include "platform.h" /* ------------------------------------------------------- * Complex objects * ------------------------------------------------------- */ extern objecttype objectcomplextype; #define OBJECT_COMPLEX objectcomplextype typedef struct { object obj; MorphoComplex Z; } objectcomplex; /** Creates a static complex number */ #define MORPHO_STATICCOMPLEX(real,imag) { .obj.type=OBJECT_COMPLEX, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .Z=MCBuild(real,imag)} /** Tests whether an object is a complex */ #define MORPHO_ISCOMPLEX(val) object_istype(val, OBJECT_COMPLEX) /** Gets the object as a complex */ #define MORPHO_GETCOMPLEX(val) ((objectcomplex *) MORPHO_GETOBJECT(val)) /** Gets the object as a C-style MorphoComplex */ #define MORPHO_GETDOUBLECOMPLEX(val) ((MorphoComplex) ((objectcomplex *) MORPHO_GETOBJECT(val))->Z) /** Creates a complex object */ objectcomplex *object_newcomplex(double real, double imag); /** Creates a new complex from an existing complex */ objectcomplex *object_clonecomplex(objectcomplex *array); /** Clones a value that holds a complex */ value object_clonecomplexvalue(value val); /** creates a complex object from a real value */ objectcomplex *object_complexfromfloat(double val); /** tests the equality of two complex numbers */ bool complex_isequal(objectcomplex *a, objectcomplex *b); /** tests equality between a complex number and a value */ bool complex_isequaltonumber(objectcomplex *a, value b); /* ------------------------------------------------------- * Complex class * ------------------------------------------------------- */ #define COMPLEX_CLASSNAME "Complex" #define COMPLEX_CONJUGATE_METHOD "conj" #define COMPLEX_ABS_METHOD "abs" #define COMPLEX_REAL_METHOD "real" #define COMPLEX_IMAG_METHOD "imag" #define COMPLEX_ANGLE_METHOD "angle" /* ------------------------------------------------------- * Complex error messages * ------------------------------------------------------- */ #define COMPLEX_CONSTRUCTOR "CmplxCns" #define COMPLEX_CONSTRUCTOR_MSG "Complex() constructor should be called with two floats" #define COMPLEX_ARITHARGS "CmplxInvldArg" #define COMPLEX_ARITHARGS_MSG "Complex arithmetic methods expect a complex or number as their argument." #define COMPLEX_INVLDNARG "CmpxArg" #define COMPLEX_INVLDNARG_MSG "Complex Operation did not exect those arguments." /* ------------------------------------------------------- * Complex interface * ------------------------------------------------------- */ void complex_copy(objectcomplex *a, objectcomplex *out); void complex_add(objectcomplex *a, objectcomplex *b, objectcomplex *out); void complex_sub(objectcomplex *a, objectcomplex *b, objectcomplex *out); void complex_mul(objectcomplex *a, objectcomplex *b, objectcomplex *out); void complex_div(objectcomplex *a, objectcomplex *b, objectcomplex *out); void complex_conj(objectcomplex *a, objectcomplex *out); void complex_abs(objectcomplex *a, double *out); void complex_angle(objectcomplex *a, double *out); void complex_getreal(objectcomplex *c, double *value); void complex_getimag(objectcomplex *c, double *value); void complex_print(vm *v, objectcomplex *m); /* Built-in fucntions */ value complex_builtinexp(vm *v, objectcomplex *c); value complex_builtinfabs(vm *v, objectcomplex *c); value complex_builtinexp(vm *v, objectcomplex *c); value complex_builtinlog(vm *v, objectcomplex *c); value complex_builtinlog10(vm *v, objectcomplex *c); value complex_builtinsin(vm *v, objectcomplex *c); value complex_builtincos(vm *v, objectcomplex *c); value complex_builtintan(vm *v, objectcomplex *c); value complex_builtinasin(vm *v, objectcomplex *c); value complex_builtinacos(vm *v, objectcomplex *c); value complex_builtinsinh(vm *v, objectcomplex *c); value complex_builtincosh(vm *v, objectcomplex *c); value complex_builtintanh(vm *v, objectcomplex *c); value complex_builtinsqrt(vm *v, objectcomplex *c); value complex_builtinfloor(vm *v, objectcomplex *c); value complex_builtinceil(vm *v, objectcomplex *c); value complex_builtinisfinite(objectcomplex *c); value complex_builtinisinf(objectcomplex *c); value complex_builtinisnan(objectcomplex *c); value complex_builtinatan(vm *v, value c); value complex_builtinatan2(vm *v, value c1, value c2); /* Complex methods */ value Complex_getreal(vm *v, int nargs, value *args); value Complex_getimag(vm *v, int nargs, value *args); value Complex_angle(vm *v, int nargs, value *args); value Complex_conj(vm *v, int nargs, value *args); void complex_initialize(void); #endif /* complex_h */ ================================================ FILE: src/classes/dict.c ================================================ /** @file dict.c * @author T J Atherton * * @brief Defines dictionary object type and Dictionary veneer class */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * objectdictionary definitions * ********************************************************************** */ /** Dictionary object definitions */ void objectdictionary_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectdictionary_freefn(object *obj) { objectdictionary *dict = (objectdictionary *) obj; dictionary_clear(&dict->dict); } void objectdictionary_markfn(object *obj, void *v) { objectdictionary *c = (objectdictionary *) obj; morpho_markdictionary(v, &c->dict); } size_t objectdictionary_sizefn(object *obj) { return sizeof(objectdictionary)+(((objectdictionary *) obj)->dict.capacity)*sizeof(dictionaryentry); } objecttypedefn objectdictionarydefn = { .printfn=objectdictionary_printfn, .markfn=objectdictionary_markfn, .freefn=objectdictionary_freefn, .sizefn=objectdictionary_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Creates a new dictionary */ objectdictionary *object_newdictionary(void) { objectdictionary *new = (objectdictionary *) object_new(sizeof(objectdictionary), OBJECT_DICTIONARY); if (new) dictionary_init(&new->dict); return new; } /* ********************************************************************** * objectdictionary utility functions * ********************************************************************** */ /** Extracts the dictionary from an objectdictionary. */ dictionary *object_dictionary(objectdictionary *dict) { return &dict->dict; } /** Iterates over dictionary; current implementation returns a sequence of keys */ value dictionary_iterate(objectdictionary *dict, unsigned int n) { unsigned int k=0; for (unsigned int i=0; idict.capacity; i++) { if (!MORPHO_ISNIL(dict->dict.contents[i].key)) { if (k==n) return dict->dict.contents[i].key; k++; } } return MORPHO_NIL; } /* ********************************************************************** * Dictionary veneer class * ********************************************************************** */ /** Dictionary constructor function */ value dictionary_constructor(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectdictionary *new=object_newdictionary(); if (new) { out=MORPHO_OBJECT(new); for (unsigned int i=0; i+1dict, MORPHO_GETARG(args, i), MORPHO_GETARG(args, i+1)); } morpho_bindobjects(v, 1, &out); } return out; } /** Gets a dictionary entry */ value Dictionary_getindex(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if(!dictionary_get(&slf->dict, MORPHO_GETARG(args, 0), &out)) { morpho_runtimeerror(v, DICT_DCTKYNTFND); } } return out; } /** Sets a dictionary entry */ value Dictionary_setindex(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); if (nargs==2) { unsigned int capacity = slf->dict.capacity; dictionary_insert(&slf->dict, MORPHO_GETARG(args, 0), MORPHO_GETARG(args, 1)); if (slf->dict.capacity!=capacity) morpho_resizeobject(v, (object *) slf, capacity*sizeof(dictionaryentry)+sizeof(objectdictionary), slf->dict.capacity*sizeof(dictionaryentry)+sizeof(objectdictionary)); } else morpho_runtimeerror(v, SETINDEX_ARGS); return MORPHO_NIL; } /** Returns a Bool value for whether the Dictionary contains a given key */ value Dictionary_contains(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); value out=MORPHO_FALSE; if (nargs==1) { if (dictionary_get(&slf->dict, MORPHO_GETARG(args, 0), &out)) out=MORPHO_TRUE; } return out; } /** Removes a dictionary entry with a given key */ value Dictionary_remove(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); if (nargs==1) { dictionary_remove(&slf->dict, MORPHO_GETARG(args, 0)); } return MORPHO_NIL; } /** Clears a Dictionary */ value Dictionary_clear(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); dictionary_clear(&slf->dict); return MORPHO_NIL; } /** Prints a dictionary */ value Dictionary_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISDICTIONARY(self)) return Object_print(v, nargs, args); objectdictionary *slf = MORPHO_GETDICTIONARY(self); morpho_printf(v, "{ "); unsigned int k=0; for (unsigned int i=0; idict.capacity; i++) { if (!MORPHO_ISNIL(slf->dict.contents[i].key)) { if (k>0) morpho_printf(v, " , "); morpho_printvalue(v, slf->dict.contents[i].key); morpho_printf(v, " : "); morpho_printvalue(v, slf->dict.contents[i].val); k++; } } morpho_printf(v, " }"); return MORPHO_NIL; } /** Counts number of items in dictionary */ value Dictionary_count(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); return MORPHO_INTEGER(slf->dict.count); } /** Enumerate protocol */ value Dictionary_enumerate(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) out=MORPHO_INTEGER(slf->dict.count); else out=dictionary_iterate(slf, n); } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Gets a list of keys */ value Dictionary_keys(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); objectlist *list = object_newlist(slf->dict.count, NULL); value out=MORPHO_NIL; if (list) { for (unsigned int i=0; idict.capacity; i++) { if (!MORPHO_ISNIL(slf->dict.contents[i].key)) { list_append(list, slf->dict.contents[i].key); } } out=MORPHO_OBJECT(list); morpho_bindobjects(v, 1, &out); } return out; } /** Clones a dictionary */ value Dictionary_clone(vm *v, int nargs, value *args) { objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); objectdictionary *new = object_newdictionary(); if (!new) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); value out=MORPHO_OBJECT(new); dictionary_copy(&slf->dict, &new->dict); morpho_bindobjects(v, 1, &out); return out; } #define DICTIONARY_SETOP(op) \ value Dictionary_##op(vm *v, int nargs, value *args) { \ objectdictionary *slf = MORPHO_GETDICTIONARY(MORPHO_SELF(args)); \ value out=MORPHO_NIL; \ \ if (nargs>0 && MORPHO_ISDICTIONARY(MORPHO_GETARG(args, 0))) { \ objectdictionary *new = object_newdictionary(); \ \ if (new) { \ objectdictionary *b =MORPHO_GETDICTIONARY(MORPHO_GETARG(args, 0)); \ dictionary_##op(&slf->dict, &b->dict, &new->dict); \ out=MORPHO_OBJECT(new); \ morpho_bindobjects(v, 1, &out); \ } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); \ } else morpho_runtimeerror(v, DICT_DCTSTARG); \ \ return out; \ } DICTIONARY_SETOP(union) DICTIONARY_SETOP(intersection) DICTIONARY_SETOP(difference) MORPHO_BEGINCLASS(Dictionary) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Dictionary_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Dictionary_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CONTAINS_METHOD, Dictionary_contains, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(DICTIONARY_REMOVE_METHOD, Dictionary_remove, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(DICTIONARY_CLEAR_METHOD, Dictionary_clear, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Dictionary_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Dictionary_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Dictionary_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(DICTIONARY_KEYS_METHOD, Dictionary_keys, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Dictionary_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_UNION_METHOD, Dictionary_union, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INTERSECTION_METHOD, Dictionary_intersection, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIFFERENCE_METHOD, Dictionary_difference, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Dictionary_union, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Dictionary_difference, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectdictionarytype; void dict_initialize(void) { // Create dictionary object type objectdictionarytype=object_addtype(&objectdictionarydefn); // Locate the Object class to use as the parent class of Dictionary objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Dictionary constructor function morpho_addfunction(DICTIONARY_CLASSNAME, DICTIONARY_CLASSNAME " (...)", dictionary_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // Create dictionary veneer class value dictionaryclass=builtin_addclass(DICTIONARY_CLASSNAME, MORPHO_GETCLASSDEFINITION(Dictionary), objclass); object_setveneerclass(OBJECT_DICTIONARY, dictionaryclass); // Dictionary error messages morpho_defineerror(DICT_DCTKYNTFND, ERROR_HALT, DICT_DCTKYNTFND_MSG); morpho_defineerror(DICT_DCTSTARG, ERROR_HALT, DICT_DCTSTARG_MSG); } ================================================ FILE: src/classes/dict.h ================================================ /** @file dict.h * @author T J Atherton * * @brief Defines dictionary object type and Dictionary veneer class */ #ifndef dict_h #define dict_h #include "object.h" #include "dictionary.h" /* ------------------------------------------------------- * Dictionary objects * ------------------------------------------------------- */ extern objecttype objectdictionarytype; #define OBJECT_DICTIONARY objectdictionarytype typedef struct { object obj; dictionary dict; } objectdictionary; /** Tests whether an object is a dictionary */ #define MORPHO_ISDICTIONARY(val) object_istype(val, OBJECT_DICTIONARY) /** Gets the object as a dictionary */ #define MORPHO_GETDICTIONARY(val) ((objectdictionary *) MORPHO_GETOBJECT(val)) /** Gets the object's underlying dictionary structure */ #define MORPHO_GETDICTIONARYSTRUCT(val) (&(((objectdictionary *) MORPHO_GETOBJECT(val))->dict)) objectdictionary *object_newdictionary(void); /* ------------------------------------------------------- * Dictionary veneer class * ------------------------------------------------------- */ #define DICTIONARY_CLASSNAME "Dictionary" #define DICTIONARY_KEYS_METHOD "keys" #define DICTIONARY_CONTAINS_METHOD "contains" #define DICTIONARY_REMOVE_METHOD "remove" #define DICTIONARY_CLEAR_METHOD "clear" /* ------------------------------------------------------- * Dictionary error messages * ------------------------------------------------------- */ #define DICT_DCTKYNTFND "DctKyNtFnd" #define DICT_DCTKYNTFND_MSG "Key not found in dictionary." #define DICT_DCTSTARG "DctStArg" #define DICT_DCTSTARG_MSG "Dictionary set methods (union, intersection, difference) expect a dictionary as the argument." /* ------------------------------------------------------- * Dictionary interface * ------------------------------------------------------- */ void dict_initialize(void); #endif ================================================ FILE: src/classes/err.c ================================================ /** @file err.c * @author T J Atherton * * @brief Implements the Error class */ #include "morpho.h" #include "classes.h" static value error_tagproperty; static value error_messageproperty; static value error_errtag; /* ********************************************************************** * Error class * ********************************************************************** */ /** Initializer * In: 1. Error tag * 2. Default error message */ value Error_init(vm *v, int nargs, value *args) { if ((nargs==2) && MORPHO_ISSTRING(MORPHO_GETARG(args, 0)) && MORPHO_ISSTRING(MORPHO_GETARG(args, 1))) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), error_tagproperty, MORPHO_GETARG(args, 0)); objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), error_messageproperty, MORPHO_GETARG(args, 1)); } else MORPHO_RAISE(v, ERROR_ARGS); return MORPHO_NIL; } /** Extract the tag and message for an error */ bool _err_extract(vm *v, int nargs, value *args, value *tag, value *msg) { // If an instance, extract the tag and message from the object if (MORPHO_ISINSTANCE(MORPHO_SELF(args))) { objectinstance *slf = MORPHO_GETINSTANCE(MORPHO_SELF(args)); objectinstance_getpropertyinterned(slf, error_tagproperty, tag); if (nargs==0) objectinstance_getpropertyinterned(slf, error_messageproperty, msg); } if (nargs==1) { if (!MORPHO_ISSTRING(*tag)) *tag = builtin_internsymbolascstring(ERROR_ERROR); *msg=MORPHO_GETARG(args, 0); } else if (nargs==2) { *tag=MORPHO_GETARG(args, 0); *msg=MORPHO_GETARG(args, 1); } return (MORPHO_ISSTRING(*tag) && MORPHO_ISSTRING(*msg)); } /** Throw an error */ value Error_throw(vm *v, int nargs, value *args) { value tag=MORPHO_NIL, msg=MORPHO_NIL; if (_err_extract(v, nargs, args, &tag, &msg)) { error err; error_init(&err); morpho_writeusererror(&err, MORPHO_GETCSTRING(tag), MORPHO_GETCSTRING(msg)); morpho_error(v, &err); error_clear(&err); } return MORPHO_NIL; } /** Raise a warning */ value Error_warning(vm *v, int nargs, value *args) { value tag=MORPHO_NIL, msg=MORPHO_NIL; if (_err_extract(v, nargs, args, &tag, &msg)) { error err; error_init(&err); morpho_writeusererror(&err, MORPHO_GETCSTRING(tag), MORPHO_GETCSTRING(msg)); morpho_warning(v, &err); error_clear(&err); } return MORPHO_NIL; } /** Print errors */ value Error_print(vm *v, int nargs, value *args) { morpho_printvalue(v, MORPHO_SELF(args)); return MORPHO_SELF(args); } MORPHO_BEGINCLASS(Error) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, Error_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_THROW_METHOD, Error_throw, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_WARNING_METHOD, Error_warning, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Error_print, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ void err_initialize(void) { // Locate the Object class to use as the parent class of Error objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Create Error class builtin_addclass(ERROR_CLASSNAME, MORPHO_GETCLASSDEFINITION(Error), objclass); // Create labels for Error property names error_tagproperty=builtin_internsymbolascstring(ERROR_TAG_PROPERTY); error_messageproperty=builtin_internsymbolascstring(ERROR_MESSAGE_PROPERTY); error_errtag=builtin_internsymbolascstring(ERROR_ERROR); // Error error messages morpho_defineerror(ERROR_ARGS, ERROR_HALT, ERROR_ARGS_MSG); } ================================================ FILE: src/classes/err.h ================================================ /** @file err.h * @author T J Atherton * * @brief Defines Error veneer class */ #ifndef err_h #define err_h #include "object.h" /* ------------------------------------------------------- * Error class * ------------------------------------------------------- */ #define ERROR_CLASSNAME "Error" #define ERROR_TAG_PROPERTY "tag" #define ERROR_MESSAGE_PROPERTY "message" /* ------------------------------------------------------- * Error error messages * ------------------------------------------------------- */ #define ERROR_ARGS "ErrorArgs" #define ERROR_ARGS_MSG "Error much be called with a tag and a default message as arguments." /* ------------------------------------------------------- * Error interface * ------------------------------------------------------- */ void err_initialize(void); #endif ================================================ FILE: src/classes/file.c ================================================ /** @file file.c * @author T J Atherton * * @brief Defines file object type as well as File and Folder classes */ #include #include #include "morpho.h" #include "classes.h" #include "file.h" #include "platform.h" /** Store the current working directory (relative to the filing systems cwd) */ static varray_char workingdir; /* ********************************************************************** * File objects * ********************************************************************** */ objecttype objectfiletype; /** File object definitions */ size_t objectfile_sizefn(object *obj) { return sizeof(objectfile); } void objectfile_markfn(object *obj, void *v) { objectfile *file = (objectfile *) obj; morpho_markvalue(v, file->filename); } void objectfile_freefn(object *obj) { objectfile *file = (objectfile *) obj; if (file->f) fclose(file->f); } void objectfile_printfn(object *obj, void *v) { objectfile *file = (objectfile *) obj; morpho_printf(v, "filename); morpho_printf(v, "'>"); } objecttypedefn objectfiledefn = { .printfn=objectfile_printfn, .markfn=objectfile_markfn, .freefn=objectfile_freefn, .sizefn=objectfile_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Creates a file object */ objectfile *object_newfile(value filename, FILE *f) { objectfile *new = (objectfile *) object_new(sizeof(objectfile), OBJECT_FILE); if (new) { new->filename=filename; new->f=f; } return new; } /* ********************************************************************** * File handling utility functions * ********************************************************************** */ #include /* Determine the size of a file */ bool file_getsize(FILE *f, size_t *s) { long int curr, size; curr=ftell(f); if (fseek(f, 0L, SEEK_END)!=0) return false; size = ftell(f); if (fseek(f, curr, SEEK_SET)!=0) return false; if (s) *s = size; return true; } /* Gets the current file handle */ FILE *file_getfile(value obj) { if (MORPHO_ISFILE(obj)) return MORPHO_GETFILE(obj)->f; return NULL; } /** Sets the current file handle */ void file_setfile(value obj, FILE *f) { if (MORPHO_ISFILE(obj)) MORPHO_GETFILE(obj)->f=f; } /** Sets the global current working directory (relative to the filing systems cwd. * @param[in] path - path to working directory. Any file name at the end is stripped */ void file_setworkingdirectory(const char *path) { int length = (int) strlen(path); int dirmarker = 0; /* Working backwards, find the last directory separator */ for ( dirmarker=length; dirmarker>=0; dirmarker--) { if (path[dirmarker]=='\\' || path[dirmarker]=='/') break; } workingdir.count=0; /* Clear any prior working directory */ if (dirmarker>0) { varray_charadd(&workingdir, (char *) path, dirmarker); varray_charwrite(&workingdir, '\0'); } } /** Gets the relative path for a given file */ void file_relativepath(const char *fname, varray_char *name) { /* Check the fname passed isn't a global file reference (i.e. starts with / or ~) */ if (fname[0]!='~' && fname[0]!='/' && !(fname[0]!='\0' && fname[1]==':')) { if (workingdir.count>0) { for (unsigned int i=0; icount>0) { varray_charwrite(string, '\0'); } return ic; } /** Reads a whole file into a buffer */ bool file_readintovarray(FILE *f, varray_char *string) { size_t size; if (!file_getsize(f, &size)) return false; if (size>INT_MAX) return false; if (varray_charresize(string, (int) size+1)) { size_t nread=fread(string->data, sizeof(char), size, f); string->data[nread]='\0'; } return true; } /** Reads a line using a given buffer */ value file_readlineusingvarray(FILE *f, varray_char *string) { int ic=file_readlineintovarray(f, string); if (ic!=EOF || string->count>0) { return object_stringfromvarraychar(string); } return MORPHO_NIL; } /* ********************************************************************** * File class * ********************************************************************** */ /** File constructor * In: 1. a file name * 2. (optional) a string giving the requested status, e.g. "wr+" */ value file_constructor(vm *v, int nargs, value *args) { objectfile *new=NULL; value out=MORPHO_NIL; value filename=MORPHO_NIL; char *fname=NULL; char *cmode = "r"; if (nargs>0) { if (MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { fname=MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)); } else MORPHO_RAISE(v, FILE_FILENAMEARG); if (nargs>1) { if (MORPHO_ISSTRING(MORPHO_GETARG(args, 1))) { char *mode=MORPHO_GETCSTRING(MORPHO_GETARG(args, 1)); switch (mode[0]) { case 'r': cmode="r"; break; case 'w': cmode="w"; break; case 'a': cmode="a"; break; default: MORPHO_RAISE(v, FILE_MODE); } } else MORPHO_RAISE(v, FILE_MODE); } } else MORPHO_RAISE(v, FILE_NEEDSFILENAME); if (fname) { FILE *f = file_openrelative(fname, cmode); if (!f) MORPHO_RAISEVARGS(v, FILE_OPENFAILED, fname); filename = object_stringfromcstring(fname, strlen(fname)); new = object_newfile(filename, f); } if (new) { out=MORPHO_OBJECT(new); value bind[] = { filename, out }; morpho_bindobjects(v, 2, bind); } return out; } /** Close a file */ value File_close(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); if (f) { fclose(f); file_setfile(MORPHO_SELF(args), NULL); } return MORPHO_NIL; } /** Get the contents of a file as an array */ value File_lines(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); value out=MORPHO_NIL; if (f) { varray_value lines; varray_valueinit(&lines); varray_char string; varray_charinit(&string); do { value line = file_readlineusingvarray(f, &string); if (!MORPHO_ISNIL(line)) varray_valuewrite(&lines, line); string.count=0; } while (!feof(f)); varray_charclear(&string); out=MORPHO_OBJECT(object_arrayfromvarrayvalue(&lines)); varray_valuewrite(&lines, out); // Tuck onto end of lines to bind all at once morpho_bindobjects(v, lines.count, lines.data); varray_valueclear(&lines); } return out; } /** Reads the whole file into a string */ value File_readall(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); size_t size; value out = MORPHO_NIL; if (f && file_getsize(f, &size)) { objectstring *new=object_stringwithsize(size); if (new) { size_t nbytes = fread(new->string, sizeof(char), size, f); new->length=nbytes; out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } return out; } /** Read a line */ value File_readline(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); value out=MORPHO_NIL; if (f) { varray_char string; varray_charinit(&string); out = file_readlineusingvarray(f, &string); morpho_bindobjects(v, 1, &out); varray_charclear(&string); } return out; } /** Reads a single character */ value File_readchar(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); if (f) { int ic=getc(f); if (ic!=EOF) { char c=(char) ic; value out=object_stringfromcstring(&c, 1); morpho_bindobjects(v, 1, &out); return out; } } return MORPHO_NIL; } /** Write to a file */ value File_write(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); if (f) { for (unsigned int i=0; ifilename; varray_char path; varray_charinit(&path); if (MORPHO_ISSTRING(fname)) { file_relativepath(MORPHO_GETCSTRING(fname), &path); out = object_stringfromvarraychar(&path); morpho_bindobjects(v, 1, &out); } varray_charclear(&path); return out; } /** Get the filename */ value File_filename(vm *v, int nargs, value *args) { return MORPHO_GETFILE(MORPHO_SELF(args))->filename; } /** Detects whether we're at the end of the file */ value File_eof(vm *v, int nargs, value *args) { FILE *f=file_getfile(MORPHO_SELF(args)); if (f && feof(f)) return MORPHO_TRUE; return MORPHO_FALSE; } MORPHO_BEGINCLASS(File) MORPHO_METHOD(FILE_CLOSE, File_close, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_LINES, File_lines, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_READALL, File_readall, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_READLINE, File_readline, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_READCHAR, File_readchar, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_WRITE, File_write, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_RELATIVEPATH, File_relativepath, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_FILENAME, File_filename, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FILE_EOF, File_eof, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Folder objects * ********************************************************************** */ /** Detect whether a resource is a folder */ value Folder_isfolder(vm *v, int nargs, value *args) { value ret = MORPHO_FALSE; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { varray_char name; varray_charinit(&name); file_relativepath(MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)), &name); if (platform_isdirectory(name.data)) ret=MORPHO_TRUE; varray_charclear(&name); } else morpho_runtimeerror(v, FOLDER_EXPCTPATH); return ret; } /** Return the contents of a folder */ value Folder_contents(vm *v, int nargs, value *args) { value ret = MORPHO_NIL; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { varray_char name; varray_charinit(&name); file_relativepath(MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)), &name); size_t size = platform_maxpathsize(); char buffer[size]; MorphoDirContents contents; if (platform_directorycontentsinit(&contents, name.data)) { varray_value list; varray_valueinit(&list); while (platform_directorycontents(&contents, buffer, size)) { value entry = object_stringfromcstring(buffer, strlen(buffer)); if (MORPHO_ISSTRING(entry)) varray_valuewrite(&list, entry); }; platform_directorycontentsclear(&contents); objectlist *clist = object_newlist(list.count, list.data); if (clist) { ret = MORPHO_OBJECT(clist); varray_valuewrite(&list, ret); morpho_bindobjects(v, list.count, list.data); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); varray_valueclear(&list); } else morpho_runtimeerror(v, FOLDER_NTFLDR); varray_charclear(&name); } else morpho_runtimeerror(v, FOLDER_EXPCTPATH); return ret; } MORPHO_BEGINCLASS(Folder) MORPHO_METHOD(FOLDER_ISFOLDER, Folder_isfolder, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FOLDER_CONTENTS, Folder_contents, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ void file_initialize(void) { varray_charinit(&workingdir); objectfiletype=object_addtype(&objectfiledefn); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); morpho_addfunction(FILE_CLASSNAME, FILE_CLASSNAME " (...)", file_constructor, MORPHO_FN_CONSTRUCTOR, NULL); value fileclass=builtin_addclass(FILE_CLASSNAME, MORPHO_GETCLASSDEFINITION(File), objclass); object_setveneerclass(OBJECT_FILE, fileclass); builtin_addclass(FOLDER_CLASSNAME, MORPHO_GETCLASSDEFINITION(Folder), MORPHO_NIL); morpho_defineerror(FILE_OPENFAILED, ERROR_HALT, FILE_OPENFAILED_MSG); morpho_defineerror(FILE_NEEDSFILENAME, ERROR_HALT, FILE_NEEDSFILENAME_MSG); morpho_defineerror(FILE_FILENAMEARG, ERROR_HALT, FILE_FILENAMEARG_MSG); morpho_defineerror(FILE_MODE, ERROR_HALT, FILE_MODE_MSG); morpho_defineerror(FILE_WRITEARGS, ERROR_HALT, FILE_WRITEARGS_MSG); morpho_defineerror(FILE_WRITEFAIL, ERROR_HALT, FILE_WRITEFAIL_MSG); morpho_defineerror(FOLDER_EXPCTPATH, ERROR_HALT, FOLDER_EXPCTPATH_MSG); morpho_defineerror(FOLDER_NTFLDR, ERROR_HALT, FOLDER_NTFLDR_MSG); morpho_addfinalizefn(file_finalize); } void file_finalize(void) { varray_charclear(&workingdir); } ================================================ FILE: src/classes/file.h ================================================ /** @file file.h * @author T J Atherton * * @brief Defines file object type as well as File and Folder classes */ #ifndef file_h #define file_h #include #include "object.h" #include "morpho.h" /* ------------------------------------------------------- * File objects * ------------------------------------------------------- */ extern objecttype objectfiletype; #define OBJECT_FILE objectfiletype typedef struct { object obj; value filename; FILE *f; } objectfile; /** Tests whether an object is a file */ #define MORPHO_ISFILE(val) object_istype(val, OBJECT_FILE) /** Gets the object as an matrix */ #define MORPHO_GETFILE(val) ((objectfile *) MORPHO_GETOBJECT(val)) /* ------------------------------------------------------- * File class * ------------------------------------------------------- */ #define FILE_CLASSNAME "File" #define FILE_CLOSE "close" #define FILE_LINES "lines" #define FILE_READALL "readall" #define FILE_READLINE "readline" #define FILE_READCHAR "readchar" #define FILE_WRITE "write" #define FILE_EOF "eof" #define FILE_RELATIVEPATH "relativepath" #define FILE_FILENAME "filename" #define FILE_READMODE "read" #define FILE_WRITEMODE "write" #define FILE_APPENDMODE "append" /* ------------------------------------------------------- * Folder class * ------------------------------------------------------- */ #define FOLDER_CLASSNAME "Folder" #define FOLDER_ISFOLDER "isfolder" #define FOLDER_CONTENTS "contents" /* ------------------------------------------------------- * File error messages * ------------------------------------------------------- */ #define FILE_OPENFAILED "FlOpnFld" #define FILE_OPENFAILED_MSG "Couldn't open file '%s'." #define FILE_FILENAMEARG "FlNmArgs" #define FILE_FILENAMEARG_MSG "First argument to File must be a filename." #define FILE_NEEDSFILENAME "FlNmMssng" #define FILE_NEEDSFILENAME_MSG "Filename missing." #define FILE_MODE "FlMode" #define FILE_MODE_MSG "Second argument to File should be 'read', 'write' or 'append'." #define FILE_WRITEARGS "FlWrtArgs" #define FILE_WRITEARGS_MSG "Arguments to File.write must be strings." #define FILE_WRITEFAIL "FlWrtFld" #define FILE_WRITEFAIL_MSG "Write to file failed." #define FOLDER_EXPCTPATH "FldrExpctPth" #define FOLDER_EXPCTPATH_MSG "Folder methods expect a path as an argument." #define FOLDER_NTFLDR "NtFldr" #define FOLDER_NTFLDR_MSG "Not a folder." /* ------------------------------------------------------- * File interface * ------------------------------------------------------- */ bool file_getsize(FILE *f, size_t *s); void file_setworkingdirectory(const char *script); FILE *file_openrelative(const char *fname, const char *mode); int file_readlineintovarray(FILE *f, varray_char *string); bool file_readintovarray(FILE *f, varray_char *string); /* Initialization/finalization */ void file_initialize(void); void file_finalize(void); #endif /* file_h */ ================================================ FILE: src/classes/flt.c ================================================ /** @file flt.c * @author T J Atherton * * @brief Veneer class for float values */ #include "morpho.h" #include "classes.h" #include "format.h" value Value_format(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { varray_char str; varray_charinit(&str); if (format_printtobuffer(MORPHO_SELF(args), MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)), &str)) { out = object_stringfromvarraychar(&str); if (MORPHO_ISOBJECT(out)) morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, VALUE_INVLDFRMT); varray_charclear(&str); } else { morpho_runtimeerror(v, VALUE_FRMTARG); } return out; } /* ********************************************************************** * Float veneer class * ********************************************************************** */ MORPHO_BEGINCLASS(Float) MORPHO_METHOD(MORPHO_CLASS_METHOD, Object_class, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_RESPONDSTO_METHOD, Object_respondsto, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INVOKE_METHOD, Object_invoke, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, MORPHO_FN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_FORMAT_METHOD, Value_format, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ void float_initialize(void) { // Create Float veneer class value floatclass=builtin_addclass(FLOAT_CLASSNAME, MORPHO_GETCLASSDEFINITION(Float), MORPHO_NIL); value_setveneerclass(MORPHO_FLOAT(0.0), floatclass); morpho_defineerror(VALUE_FRMTARG, ERROR_HALT, VALUE_FRMTARG_MSG); morpho_defineerror(VALUE_INVLDFRMT, ERROR_HALT, VALUE_INVLDFRMT_MSG); } ================================================ FILE: src/classes/flt.h ================================================ /** @file flt.h * @author T J Atherton * * @brief Veneer class for float values */ #ifndef float_h #define float_h /* ------------------------------------------------------- * Float veneer class * ------------------------------------------------------- */ #define FLOAT_CLASSNAME "Float" /* ------------------------------------------------------- * Float error messages * ------------------------------------------------------- */ #define VALUE_FRMTARG "FrmtArg" #define VALUE_FRMTARG_MSG "Format method requires a format string as its argument." #define VALUE_INVLDFRMT "InvldFrmt" #define VALUE_INVLDFRMT_MSG "Invalid format string." /* ------------------------------------------------------- * Float interface * ------------------------------------------------------- */ /** Method for format strings */ value Value_format(vm *v, int nargs, value *args); void float_initialize(void); #endif ================================================ FILE: src/classes/function.c ================================================ /** @file function.c * @author T J Atherton * * @brief Implement objectfunctions and the Function veneer class */ #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * objectfunction definitions * ********************************************************************** */ void objectfunction_freefn(object *obj) { objectfunction *func = (objectfunction *) obj; morpho_freeobject(func->name); varray_optionalparamclear(&func->opt); object_functionclear(func); signature_clear(&func->sig); } void objectfunction_markfn(object *obj, void *v) { objectfunction *f = (objectfunction *) obj; morpho_markvalue(v, f->name); morpho_markvarrayvalue(v, &f->konst); } size_t objectfunction_sizefn(object *obj) { return sizeof(objectfunction); } void objectfunction_printfn(object *obj, void *v) { objectfunction *f = (objectfunction *) obj; if (f) morpho_printf(v, "", (MORPHO_ISNIL(f->name) ? "" : MORPHO_GETCSTRING(f->name))); } objecttypedefn objectfunctiondefn = { .printfn=objectfunction_printfn, .markfn=objectfunction_markfn, .freefn=objectfunction_freefn, .sizefn=objectfunction_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * objectfunction utility functions * ********************************************************************** */ /** @brief Initializes a new function */ void object_functioninit(objectfunction *func) { func->entry=0; func->name=MORPHO_NIL; func->nargs=0; func->nopt=0; func->parent=NULL; func->creg=-1; func->nregs=0; varray_valueinit(&func->konst); varray_varray_upvalueinit(&func->prototype); signature_init(&func->sig); } /** @brief Clears a function */ void object_functionclear(objectfunction *func) { varray_valueclear(&func->konst); /** Clear the upvalue prototypes */ for (unsigned int i=0; iprototype.count; i++) { varray_upvalueclear(&func->prototype.data[i]); } varray_varray_upvalueclear(&func->prototype); signature_clear(&func->sig); } /** @brief Creates a new function */ objectfunction *object_newfunction(indx entry, value name, objectfunction *parent, unsigned int nargs) { objectfunction *new = (objectfunction *) object_new(sizeof(objectfunction), OBJECT_FUNCTION); if (new) { object_functioninit(new); new->entry=entry; new->name=object_clonestring(name); new->nargs=nargs; new->varg=-1; // No vargs new->parent=parent; new->klass=NULL; varray_optionalparaminit(&new->opt); } return new; } /** Gets the parent of a function */ objectfunction *object_getfunctionparent(objectfunction *func) { return func->parent; } /** Gets the name of a function */ value object_getfunctionname(objectfunction *func) { return func->name; } /** Gets the constant table associated with a function */ varray_value *object_functiongetconstanttable(objectfunction *func) { if (func) { return &func->konst; } return NULL; } /** Adds an upvalue prototype to a function * @param[in] func function object to add to * @param[in] v a varray of upvalues that will be copied into the function * definition. * @param[out] ix index of the closure created * @returns true on success */ bool object_functionaddprototype(objectfunction *func, varray_upvalue *v, indx *ix) { bool success=false; varray_upvalue new; varray_upvalueinit(&new); success=varray_upvalueadd(&new, v->data, v->count); if (success) varray_varray_upvalueadd(&func->prototype, &new, 1); if (success && ix) *ix = (indx) func->prototype.count-1; return success; } /** Returns the number of positional arguments (including a variadic arg if any) */ int function_countpositionalargs(objectfunction *func) { return func->nargs + (func->varg>=0 ? 1 : 0); } /** Returns the number of optional arguments */ int function_countoptionalargs(objectfunction *func) { return func->nopt; } /** Does a function have variadic args? */ bool function_hasvargs(objectfunction *func) { return (func->varg>=0); } /** Sets the parameter number of a variadic argument */ void function_setvarg(objectfunction *func, int varg) { func->varg=varg; } /** Sets that a function must be enclosed */ void function_setclosure(objectfunction *func, int creg) { func->creg=creg; } /** Checks if a function is enclosed */ bool function_isclosure(objectfunction *func) { return (func->creg>=0); } /** Sets the signature of a function * @param[in] func function object * @param[in] signature list of types for each parameter (length from func->nargs) */ void function_setsignature(objectfunction *func, value *signature) { signature_set(&func->sig, function_countpositionalargs(func), signature); } /** Returns true if any of the parameters are typed */ bool function_hastypedparameters(objectfunction *func) { return signature_istyped(&func->sig); } /* ********************************************************************** * Function veneer class * ********************************************************************** */ value Function_tostring(vm *v, int nargs, value *args) { objectfunction *func=MORPHO_GETFUNCTION(MORPHO_SELF(args)); value out = MORPHO_NIL; varray_char buffer; varray_charinit(&buffer); varray_charadd(&buffer, "name, &buffer); varray_charwrite(&buffer, '>'); out = object_stringfromvarraychar(&buffer); if (MORPHO_ISSTRING(out)) { morpho_bindobjects(v, 1, &out); } varray_charclear(&buffer); return out; } MORPHO_BEGINCLASS(Function) MORPHO_METHOD(MORPHO_TOSTRING_METHOD, Function_tostring, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectfunctiontype; void function_initialize(void) { // Create function object type objectfunctiontype=object_addtype(&objectfunctiondefn); // Locate the Object class to use as the parent class of Function objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Create function veneer class value functionclass=builtin_addclass(FUNCTION_CLASSNAME, MORPHO_GETCLASSDEFINITION(Function), objclass); object_setveneerclass(OBJECT_FUNCTION, functionclass); // No constructor as objectfunctions are generated by the compiler // Function error messages } ================================================ FILE: src/classes/function.h ================================================ /** @file function.h * @author T J Atherton * * @brief Defines function object type and Function veneer class */ #ifndef function_h #define function_h #include "object.h" #include "signature.h" /* ------------------------------------------------------- * Function objects * ------------------------------------------------------- */ extern objecttype objectfunctiontype; #define OBJECT_FUNCTION objectfunctiontype typedef struct { value symbol; /** Symbol associated with the variable */ indx def; /** Default value as constant */ indx reg; /** Associated register */ } optionalparam; DECLARE_VARRAY(optionalparam, optionalparam) /** A function object */ typedef struct sobjectfunction { object obj; int nargs; // Number of positional parameters int nopt; // Number of optional parameters int varg; // The parameter number of a variadic parameter. TODO: Rationalize value name; indx entry; int creg; // Closure register struct sobjectfunction *parent; int nregs; objectclass *klass; // Parent class for methods varray_value konst; varray_varray_upvalue prototype; varray_optionalparam opt; signature sig; } objectfunction; /** Gets an objectfunction from a value */ #define MORPHO_GETFUNCTION(val) ((objectfunction *) MORPHO_GETOBJECT(val)) /** Tests whether an object is a function */ #define MORPHO_ISFUNCTION(val) object_istype(val, OBJECT_FUNCTION) /* ------------------------------------------------------- * Function veneer class * ------------------------------------------------------- */ #define FUNCTION_CLASSNAME "Function" /* ------------------------------------------------------- * Function error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * Function interface * ------------------------------------------------------- */ void object_functioninit(objectfunction *func); void object_functionclear(objectfunction *func); bool object_functionaddprototype(objectfunction *func, varray_upvalue *v, indx *ix); objectfunction *object_getfunctionparent(objectfunction *func); value object_getfunctionname(objectfunction *func); varray_value *object_functiongetconstanttable(objectfunction *func); objectfunction *object_newfunction(indx entry, value name, objectfunction *parent, unsigned int nargs); int function_countpositionalargs(objectfunction *func); int function_countoptionalargs(objectfunction *func); bool function_hasvargs(objectfunction *func); void function_setvarg(objectfunction *func, int varg); void function_setclosure(objectfunction *func, int creg); bool function_isclosure(objectfunction *func); void function_setsignature(objectfunction *func, value *signature); bool function_hastypedparameters(objectfunction *func); void objectfunction_printfn(object *obj, void *v); void function_initialize(void); #endif ================================================ FILE: src/classes/instance.c ================================================ /** @file instance.c * @author T J Atherton * * @brief Implements objectinstance and the Object base class */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * objectinstance definitions * ********************************************************************** */ /** Instance object definitions */ void objectinstance_printfn(object *obj, void *v) { morpho_printf(v, "<%s>", MORPHO_GETCSTRING(((objectinstance *) obj)->klass->name)); } void objectinstance_markfn(object *obj, void *v) { objectinstance *c = (objectinstance *) obj; morpho_markdictionary(v, &c->fields); } void objectinstance_freefn(object *obj) { objectinstance *instance = (objectinstance *) obj; dictionary_clear(&instance->fields); } size_t objectinstance_sizefn(object *obj) { return sizeof(objectinstance); } objecttypedefn objectinstancedefn = { .printfn=objectinstance_printfn, .markfn=objectinstance_markfn, .freefn=objectinstance_freefn, .sizefn=objectinstance_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Create an instance */ objectinstance *object_newinstance(objectclass *klass) { objectinstance *new= (objectinstance *) object_new(sizeof(objectinstance), OBJECT_INSTANCE); if (new) { new->klass=klass; dictionary_init(&new->fields); } return new; } /* ********************************************************************** * objectinstance utility functions * ********************************************************************** */ /* @brief Inserts a value into a property * @param obj the object * @param key key to use @warning: This MUST have been previously interned into a symboltable * e.g. with builtin_internsymbol * @param val value to use * @returns true on success */ bool objectinstance_setproperty(objectinstance *obj, value key, value val) { return dictionary_insertintern(&obj->fields, key, val); } /* @brief Gets a value into a property * @param obj the object * @param key key to use * @param[out] val stores the value * @returns true on success */ bool objectinstance_getproperty(objectinstance *obj, value key, value *val) { return dictionary_get(&obj->fields, key, val); } /* @brief Interned property lookup * @param obj the object * @param key key to use @warning: This MUST have been previously interned into a symboltable * e.g. with builtin_internsymbol * @param[out] val stores the value * @returns true on success */ bool objectinstance_getpropertyinterned(objectinstance *obj, value key, value *val) { return dictionary_getintern(&obj->fields, key, val); } /* ********************************************************************** * Object veneer class * ********************************************************************** */ /** Sets an object property */ value Object_getindex(vm *v, int nargs, value *args) { value self=MORPHO_SELF(args); value out=MORPHO_NIL; if (MORPHO_ISINSTANCE(self)) { if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { if (!dictionary_get(&MORPHO_GETINSTANCE(self)->fields, MORPHO_GETARG(args, 0), &out)) { morpho_runtimeerror(v, VM_OBJECTLACKSPROPERTY, MORPHO_GETCSTRING(MORPHO_GETARG(args, 0))); } } else morpho_runtimeerror(v, GETINDEX_ARGS); } else morpho_runtimeerror(v, OBJECT_NOPRP); return out; } /** Gets an object property */ value Object_setindex(vm *v, int nargs, value *args) { value self=MORPHO_SELF(args); if (MORPHO_ISINSTANCE(self)) { if (nargs==2 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { dictionary_insert(&MORPHO_GETINSTANCE(self)->fields, MORPHO_GETARG(args, 0), MORPHO_GETARG(args, 1)); } else morpho_runtimeerror(v, SETINDEX_ARGS); } else morpho_runtimeerror(v, OBJECT_NOPRP); return MORPHO_NIL; } /** Find the object's class */ value Object_class(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); value out = MORPHO_NIL; objectclass *klass=morpho_lookupclass(self); if (klass) out = MORPHO_OBJECT(klass); return out; } /** Find the object's superclass */ value Object_super(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); objectclass *klass=morpho_lookupclass(self); return (klass && klass->superclass ? MORPHO_OBJECT(klass->superclass) : MORPHO_NIL); } /** Checks if an object responds to a method */ value Object_respondsto(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); objectclass *klass=morpho_lookupclass(self); if (nargs == 0) { value out = MORPHO_NIL; objectlist *new = object_newlist(0, NULL); if (new) { list_resize(new, klass->methods.count); for (unsigned int i=0; imethods.capacity; i++) { if (MORPHO_ISSTRING(klass->methods.contents[i].key)) { list_append(new, klass->methods.contents[i].key); } } out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } else if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { return MORPHO_BOOL(dictionary_get(&klass->methods, MORPHO_GETARG(args, 0), NULL)); } else MORPHO_RAISE(v, RESPONDSTO_ARG); return MORPHO_FALSE; } /** Checks if an object has a property */ value Object_has(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISINSTANCE(self)) return MORPHO_FALSE; if (nargs == 0) { value out = MORPHO_NIL; objectlist *new = object_newlist(0, NULL); if (new) { objectinstance *slf = MORPHO_GETINSTANCE(self); list_resize(new, MORPHO_GETINSTANCE(self)->fields.count); for (unsigned int i=0; ifields.capacity; i++) { if (MORPHO_ISSTRING(slf->fields.contents[i].key)) { list_append(new, slf->fields.contents[i].key); } } out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } else if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { return MORPHO_BOOL(dictionary_get(&MORPHO_GETINSTANCE(self)->fields, MORPHO_GETARG(args, 0), NULL)); } else MORPHO_RAISE(v, HAS_ARG); return MORPHO_FALSE; } /** Invoke a method */ value Object_invoke(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); value out=MORPHO_NIL; objectclass *klass=morpho_lookupclass(self); if (klass && nargs>0 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { value fn; if (dictionary_get(&klass->methods, MORPHO_GETARG(args, 0), &fn)) { morpho_invoke(v, self, fn, nargs-1, &MORPHO_GETARG(args, 1), &out); } else morpho_runtimeerror(v, VM_OBJECTLACKSPROPERTY, MORPHO_GETCSTRING(MORPHO_GETARG(args, 0))); } else morpho_runtimeerror(v, VM_INVALIDARGS, 1, 0); return out; } /** Generic print */ value Object_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); objectclass *klass=NULL; if (MORPHO_ISCLASS(self)) { klass=MORPHO_GETCLASS(self); morpho_printf(v, "@%s", (MORPHO_ISSTRING(klass->name) ? MORPHO_GETCSTRING(klass->name): "Object")); } else if (MORPHO_ISINSTANCE(self)) { klass=MORPHO_GETINSTANCE(self)->klass; if (klass) morpho_printf(v, "<%s>", (MORPHO_ISSTRING(klass->name) ? MORPHO_GETCSTRING(klass->name): "Object") ); } else { morpho_printvalue(v, self); } return MORPHO_NIL; } /** Count number of properties */ value Object_count(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (MORPHO_ISINSTANCE(self)) { objectinstance *obj = MORPHO_GETINSTANCE(self); return MORPHO_INTEGER(obj->fields.count); } else if (MORPHO_ISCLASS(self)) { return MORPHO_INTEGER(0); } return MORPHO_NIL; } /** Enumerate protocol */ value Object_enumerate(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (MORPHO_ISINSTANCE(self)) { dictionary *dict= &MORPHO_GETINSTANCE(self)->fields; if (n<0) { out=MORPHO_INTEGER(dict->count); } else if (ncount) { unsigned int k=0; for (unsigned int i=0; icapacity; i++) { if (!MORPHO_ISNIL(dict->contents[i].key)) { if (k==n) return dict->contents[i].key; k++; } } } else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } else if (MORPHO_ISCLASS(self)) { if (n<0) out = MORPHO_INTEGER(0); } } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Generic serializer */ value Object_serialize(vm *v, int nargs, value *args) { return MORPHO_NIL; } /** Generic clone */ value Object_clone(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); value out = MORPHO_NIL; if (MORPHO_ISINSTANCE(self)) { objectinstance *instance = MORPHO_GETINSTANCE(self); objectinstance *new = object_newinstance(instance->klass); if (new) { dictionary_copy(&instance->fields, &new->fields); out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else { morpho_runtimeerror(v, OBJECT_CANTCLONE); } return out; } value Object_linearization(vm *v, int nargs, value *args) { value slf=MORPHO_SELF(args); objectclass *klass=NULL; if (MORPHO_ISCLASS(slf)) klass=MORPHO_GETCLASS(slf); else if (MORPHO_ISINSTANCE(slf)) klass=MORPHO_GETINSTANCE(slf)->klass; else return MORPHO_NIL; value out = MORPHO_NIL; objectlist *new = object_newlist(klass->linearization.count, klass->linearization.data); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } MORPHO_BEGINCLASS(Object) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Object_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Object_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLASS_METHOD, Object_class, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUPER_METHOD, Object_super, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_RESPONDSTO_METHOD, Object_respondsto, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_HAS_METHOD, Object_has, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INVOKE_METHOD, Object_invoke, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Object_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Object_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SERIALIZE_METHOD, Object_serialize, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Object_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_LINEARIZATION_METHOD, Object_linearization, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectinstancetype; void instance_initialize(void) { // Create instance object type objectinstancetype=object_addtype(&objectinstancedefn); // Create Object and set as base class value objclass=builtin_addclass(OBJECT_CLASSNAME, MORPHO_GETCLASSDEFINITION(Object), MORPHO_NIL); morpho_setbaseclass(objclass); // Object error messages morpho_defineerror(OBJECT_CANTCLONE, ERROR_HALT, OBJECT_CANTCLONE_MSG); morpho_defineerror(OBJECT_IMMUTABLE, ERROR_HALT, OBJECT_IMMUTABLE_MSG); morpho_defineerror(OBJECT_NOPRP, ERROR_HALT, OBJECT_NOPRP_MSG); morpho_defineerror(ENUMERATE_ARGS, ERROR_HALT, ENUMERATE_ARGS_MSG); morpho_defineerror(GETINDEX_ARGS, ERROR_HALT, GETINDEX_ARGS_MSG); morpho_defineerror(SETINDEX_ARGS, ERROR_HALT, SETINDEX_ARGS_MSG); morpho_defineerror(RESPONDSTO_ARG, ERROR_HALT, RESPONDSTO_ARG_MSG); morpho_defineerror(HAS_ARG, ERROR_HALT, HAS_ARG_MSG); morpho_defineerror(ISMEMBER_ARG, ERROR_HALT, ISMEMBER_ARG_MSG); } ================================================ FILE: src/classes/instance.h ================================================ /** @file instance.h * @author T J Atherton * * @brief Defines instance object type and Object base class */ #ifndef instance_h #define instance_h #include "object.h" /* ------------------------------------------------------- * Instance objects * ------------------------------------------------------- */ extern objecttype objectinstancetype; #define OBJECT_INSTANCE objectinstancetype typedef struct { object obj; objectclass *klass; dictionary fields; } objectinstance; /** Tests whether an object is a class */ #define MORPHO_ISINSTANCE(val) object_istype(val, OBJECT_INSTANCE) /** Gets the object as a class */ #define MORPHO_GETINSTANCE(val) ((objectinstance *) MORPHO_GETOBJECT(val)) objectinstance *object_newinstance(objectclass *klass); /* ------------------------------------------------------- * Object veneer class * ------------------------------------------------------- */ #define OBJECT_CLASSNAME "Object" /* ------------------------------------------------------- * Instance error messages * ------------------------------------------------------- */ #define OBJECT_CANTCLONE "ObjCantClone" #define OBJECT_CANTCLONE_MSG "Cannot clone this object." #define OBJECT_IMMUTABLE "ObjImmutable" #define OBJECT_IMMUTABLE_MSG "Cannot modify this object." #define OBJECT_NOPRP "ObjNoPrp" #define OBJECT_NOPRP_MSG "Object does not provide properties." #define GETINDEX_ARGS "IndxArgs" #define GETINDEX_ARGS_MSG "Index method expects a String property name as its argument." #define SETINDEX_ARGS "SetIndxArgs" #define SETINDEX_ARGS_MSG "Setindex method expects an index and a value as arguments." #define ENUMERATE_ARGS "EnmrtArgs" #define ENUMERATE_ARGS_MSG "Enumerate method expects a single integer argument." #define RESPONDSTO_ARG "RspndsToArg" #define RESPONDSTO_ARG_MSG "Method respondsto expects a single string argument or no argrument." #define HAS_ARG "HasArg" #define HAS_ARG_MSG "Method has expects a single string argument or no argument." #define ISMEMBER_ARG "IsMmbrArg" #define ISMEMBER_ARG_MSG "Method ismember expects a single argument." /* ------------------------------------------------------- * Instance interface * ------------------------------------------------------- */ /* Expose a few methods for veneer classes */ value Object_class(vm *v, int nargs, value *args); value Object_respondsto(vm *v, int nargs, value *args); value Object_print(vm *v, int nargs, value *args); value Object_invoke(vm *v, int nargs, value *args); bool objectinstance_setproperty(objectinstance *obj, value key, value val); bool objectinstance_getproperty(objectinstance *obj, value key, value *val); bool objectinstance_getpropertyinterned(objectinstance *obj, value key, value *val); void instance_initialize(void); #endif ================================================ FILE: src/classes/int.c ================================================ /** @file int.c * @author T J Atherton * * @brief Veneer class for float values */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * int utility functions * ********************************************************************** */ /* ********************************************************************** * Int veneer class * ********************************************************************** */ MORPHO_BEGINCLASS(Int) MORPHO_METHOD(MORPHO_CLASS_METHOD, Object_class, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_RESPONDSTO_METHOD, Object_respondsto, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INVOKE_METHOD, Object_invoke, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, MORPHO_FN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_FORMAT_METHOD, Value_format, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ void int_initialize(void) { // Create Int veneer class value intclass=builtin_addclass(INT_CLASSNAME, MORPHO_GETCLASSDEFINITION(Int), MORPHO_NIL); value_setveneerclass(MORPHO_INTEGER(1), intclass); } ================================================ FILE: src/classes/int.h ================================================ /** @file int.h * @author T J Atherton * * @brief Veneer class for integer values */ #ifndef int_h #define int_h /* ------------------------------------------------------- * Int veneer class * ------------------------------------------------------- */ #define INT_CLASSNAME "Int" /* ------------------------------------------------------- * Int error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * Int interface * ------------------------------------------------------- */ void int_initialize(void); #endif ================================================ FILE: src/classes/invocation.c ================================================ /** @file invocation.c * @author T J Atherton * * @brief Implements the Invocation class */ #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * objectinvocation definitions * ********************************************************************** */ /** Invocation object definitions */ void objectinvocation_printfn(object *obj, void *v) { objectinvocation *c = (objectinvocation *) obj; morpho_printvalue(v, c->receiver); morpho_printf(v, "."); morpho_printvalue(v, c->method); } void objectinvocation_markfn(object *obj, void *v) { objectinvocation *c = (objectinvocation *) obj; morpho_markvalue(v, c->receiver); morpho_markvalue(v, c->method); } size_t objectinvocation_sizefn(object *obj) { return sizeof(objectinvocation); } objecttypedefn objectinvocationdefn = { .printfn=objectinvocation_printfn, .markfn=objectinvocation_markfn, .freefn=NULL, .sizefn=objectinvocation_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * objectinvocation utility functions * ********************************************************************** */ /** Create a new invocation */ objectinvocation *object_newinvocation(value receiver, value method) { objectinvocation *new = (objectinvocation *) object_new(sizeof(objectinvocation), OBJECT_INVOCATION); if (new) { new->receiver=receiver; new->method=method; } return new; } /* ********************************************************************** * Invocation veneer class * ********************************************************************** */ /** Creates a new invocation object */ value invocation_constructor(vm *v, int nargs, value *args) { value out=MORPHO_NIL; if (nargs==2) { value receiver = MORPHO_GETARG(args, 0); value selector = MORPHO_GETARG(args, 1); if (!MORPHO_ISOBJECT(receiver) || !MORPHO_ISSTRING(selector)) { morpho_runtimeerror(v, INVOCATION_ARGS); return MORPHO_NIL; } value method = MORPHO_NIL; objectclass *klass=morpho_lookupclass(receiver); if (dictionary_get(&klass->methods, selector, &method)) { objectinvocation *new = object_newinvocation(receiver, method); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } } else morpho_runtimeerror(v, INVOCATION_ARGS); return out; } /** Converts to a string for string interpolation */ value Invocation_tostring(vm *v, int nargs, value *args) { objectinvocation *inv=MORPHO_GETINVOCATION(MORPHO_SELF(args)); value out = MORPHO_NIL; varray_char buffer; varray_charinit(&buffer); morpho_printtobuffer(v, inv->receiver, &buffer); varray_charwrite(&buffer, '.'); morpho_printtobuffer(v, inv->method, &buffer); out = object_stringfromvarraychar(&buffer); if (MORPHO_ISSTRING(out)) { morpho_bindobjects(v, 1, &out); } varray_charclear(&buffer); return out; } /** Clones a range */ value Invocation_clone(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); value out = MORPHO_NIL; if (!MORPHO_ISINVOCATION(self)) return MORPHO_NIL; objectinvocation *slf = MORPHO_GETINVOCATION(self); objectinvocation *new = object_newinvocation(slf->receiver, slf->method); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } MORPHO_BEGINCLASS(Invocation) MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_TOSTRING_METHOD, Invocation_tostring, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Invocation_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectinvocationtype; void invocation_initialize(void) { // Create invocation object type objectinvocationtype=object_addtype(&objectinvocationdefn); // Locate the Object class to use as the parent class of Invocation objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Invocation constructor function morpho_addfunction(INVOCATION_CLASSNAME, INVOCATION_CLASSNAME " (...)", invocation_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // Create invocation veneer class value invocationclass=builtin_addclass(INVOCATION_CLASSNAME, MORPHO_GETCLASSDEFINITION(Invocation), objclass); object_setveneerclass(OBJECT_INVOCATION, invocationclass); // Invocation error messages morpho_defineerror(INVOCATION_ARGS, ERROR_HALT, INVOCATION_ARGS_MSG); morpho_defineerror(INVOCATION_METHOD, ERROR_HALT, INVOCATION_METHOD_MSG); } ================================================ FILE: src/classes/invocation.h ================================================ /** @file invocation.h * @author T J Atherton * * @brief Defines invocation object type and Invocation class */ /** Invocations (or bound methods) are created by the runtime when a method is accessed from an object without a call, e.g. var inv = Object.clone An invocation can then be called at a later point, and behaves as other callable objects like functions var a = inv() */ #ifndef invocation_h #define invocation_h #include "object.h" /* ------------------------------------------------------- * Invocation objects * ------------------------------------------------------- */ extern objecttype objectinvocationtype; #define OBJECT_INVOCATION objectinvocationtype typedef struct { object obj; value receiver; value method; } objectinvocation; /** Tests whether an object is an invocation */ #define MORPHO_ISINVOCATION(val) object_istype(val, OBJECT_INVOCATION) /** Gets the object as an invocation */ #define MORPHO_GETINVOCATION(val) ((objectinvocation *) MORPHO_GETOBJECT(val)) objectinvocation *object_newinvocation(value receiver, value method); /* ------------------------------------------------------- * Invocation veneer class * ------------------------------------------------------- */ #define INVOCATION_CLASSNAME "Invocation" /* ------------------------------------------------------- * Invocation error messages * ------------------------------------------------------- */ #define INVOCATION_ARGS "InvocationArgs" #define INVOCATION_ARGS_MSG "Invocation must be called with an object and a method name as arguments." #define INVOCATION_METHOD "InvocationMethod" #define INVOCATION_METHOD_MSG "Method not found." /* ------------------------------------------------------- * Invocation interface * ------------------------------------------------------- */ void invocation_initialize(void); #endif ================================================ FILE: src/classes/json.c ================================================ /** @file json.c * @author T J Atherton * * @brief JSON class * @details Aims to be compliant with RFC 8259, tested against https://github.com/nst/JSONTestSuite * Currently passes all except "n_multidigit_number_then_00.json" */ #include #include "morpho.h" #include "classes.h" #include "common.h" #include "parse.h" #include "dictionary.h" #include "json.h" /* ********************************************************************** * JSON lexer * ********************************************************************** */ /* ------------------------------------------------------- * JSON token types and process functions * ------------------------------------------------------- */ bool json_lexstring(lexer *l, token *tok, error *err); bool json_lexnumber(lexer *l, token *tok, error *err); enum { JSON_LEFTCURLYBRACE, JSON_RIGHTCURLYBRACE, JSON_LEFTSQUAREBRACE, JSON_RIGHTSQUAREBRACE, JSON_COMMA, JSON_COLON, JSON_MINUS, JSON_QUOTE, JSON_TRUE, JSON_FALSE, JSON_NULL, JSON_STRING, JSON_NUMBER, JSON_FLOAT, JSON_EOF }; tokendefn jsontokens[] = { { "{", JSON_LEFTCURLYBRACE , NULL }, { "}", JSON_RIGHTCURLYBRACE , NULL }, { "[", JSON_LEFTSQUAREBRACE , NULL }, { "]", JSON_RIGHTSQUAREBRACE , NULL }, { ",", JSON_COMMA , NULL }, { ":", JSON_COLON , NULL }, { "-", JSON_MINUS , json_lexnumber }, { "\"", JSON_QUOTE , json_lexstring }, { "true", JSON_TRUE , NULL }, { "false", JSON_FALSE , NULL }, { "null", JSON_NULL , NULL }, { "", TOKEN_NONE , NULL } }; /** Skip over JSON whitespace */ bool json_lexwhitespace(lexer *l, token *tok, error *err) { for (;;) { char c = lex_peek(l); switch (c) { case '\n': lex_newline(l); // V Intentional fallthrough case ' ': case '\t': case '\r': lex_advance(l); break; default: return true; } } return true; } /** Record JSON strings as a token */ bool json_lexstring(lexer *l, token *tok, error *err) { while (lex_peek(l) != '"' && !lex_isatend(l)) { if (lex_peek(l)=='\\') lex_advance(l); // Detect an escaped character lex_advance(l); } if (lex_isatend(l)) { morpho_writeerrorwithid(err, LEXER_UNTERMINATEDSTRING, NULL, tok->line, tok->posn); return false; } lex_advance(l); // Advance over final quote lex_recordtoken(l, JSON_STRING, tok); return true; } /** Record JSON numbers as a token */ bool json_lexnumber(lexer *l, token *tok, error *err) { bool hasexp=false; tokentype type = JSON_NUMBER; // Detect if we are missing digits (ie an isolated '-') char c = lex_peek(l); if (c=='0') { lex_advance(l); if (lex_isdigit(lex_peek(l))) goto json_lexnumberinvld; // Cannot follow '0' by digits. } if (lex_isdigit(c)) { // Advance through initial digits while (lex_isdigit(lex_peek(l)) && !lex_isatend(l)) lex_advance(l); } else goto json_lexnumberinvld; // Detect fractional separator if (lex_peek(l)=='.') { lex_advance(l); type = JSON_FLOAT; // Digits are required after fractional separator if (!lex_isdigit(lex_peek(l))) goto json_lexnumberinvld; while (lex_isdigit(lex_peek(l)) && !lex_isatend(l)) lex_advance(l); }; if (lex_peek(l)=='e' || lex_peek(l)=='E') { lex_advance(l); hasexp=true; type = JSON_FLOAT; } if (lex_peek(l)=='+' || lex_peek(l)=='-') { if (hasexp) lex_advance(l); else goto json_lexnumberinvld; // Only allow +/- after exp } // Digits are required after exponent if (hasexp && !lex_isdigit(lex_peek(l))) goto json_lexnumberinvld; while (lex_isdigit(lex_peek(l)) && !lex_isatend(l)) lex_advance(l); lex_recordtoken(l, type, tok); return true; json_lexnumberinvld: morpho_writeerrorwithid(err, JSON_NMBRFRMT, NULL, tok->line, tok->posn); return false; } /** Lexer token preprocessor function */ bool json_lexpreprocess(lexer *l, token *tok, error *err) { char c = lex_peek(l); if (lex_isdigit(c)) return json_lexnumber(l, tok, err); return false; } /* ------------------------------------------------------- * Initialize a JSON lexer * ------------------------------------------------------- */ void json_initializelexer(lexer *l, char *src) { lex_init(l, src, 0); lex_settokendefns(l, jsontokens); lex_setprefn(l, json_lexpreprocess); lex_setwhitespacefn(l, json_lexwhitespace); lex_seteof(l, JSON_EOF); } /* ********************************************************************** * JSON parser * ********************************************************************** */ /* ------------------------------------------------------- * Output structure * ------------------------------------------------------- */ /** Type for output of the type function */ typedef struct { value out; varray_value *objects; } jsonoutput; /** Place a value into the opaque output structure */ void json_setoutput(void *out, value v) { jsonoutput *output = (jsonoutput *) out; output->out=v; if (output->objects && MORPHO_ISOBJECT(v)) varray_valuewrite(output->objects, v); } /** Retrieve output value from opaque output structure */ value json_getoutput(jsonoutput *out) { return out->out; } /** Initializes the output structure with an optional output varray. @warning: The output array must have been previously initialized */ void json_outputinit(jsonoutput *out, varray_value *output) { out->out=MORPHO_NIL; out->objects=output; if (output) out->objects->count=0; } /* ------------------------------------------------------- * JSON parse functions * ------------------------------------------------------- */ bool json_parsevalue(parser *p, void *out); /** Parses a string */ bool json_parsestring(parser *p, void *out) { bool success=false; varray_char str; varray_charinit(&str); const char *input = p->previous.start; unsigned int length = p->previous.length; for (unsigned int i=1; idict, json_getoutput(&key), json_getoutput(&val)); if (!parse_checktoken(p, JSON_RIGHTCURLYBRACE)) { if (!parse_checkrequiredtoken(p, JSON_COMMA, PARSE_DCTSPRTR)) goto json_parseobjectcleanup; if (parse_checkdisallowedtoken(p, JSON_RIGHTCURLYBRACE, JSON_BLNKELMNT)) goto json_parseobjectcleanup; } } if (!parse_checkrequiredtoken(p, JSON_RIGHTCURLYBRACE, PARSE_DCTTRMNTR)) goto json_parseobjectcleanup; json_setoutput(out, MORPHO_OBJECT(new)); return true; json_parseobjectcleanup: if (new) object_free((object *) new); return false; } /** Parses an array, e.g. [ 1, 2, 3 ] */ bool json_parsearray(parser *p, void *out) { objectlist *new = object_newlist(0, NULL); if (!new) { parse_error(p, true, ERROR_ALLOCATIONFAILED); return false; } while (!parse_checktoken(p, JSON_RIGHTSQUAREBRACE) && !parse_checktoken(p, JSON_EOF)) { if (parse_checkdisallowedtoken(p, JSON_COMMA, JSON_BLNKELMNT)) goto json_parsearraycleanup; jsonoutput v = *(jsonoutput *) out; if (json_parsevalue(p, &v)) { list_append(new, json_getoutput(&v)); } else goto json_parsearraycleanup; if (!parse_checktoken(p, JSON_RIGHTSQUAREBRACE)) { if (!parse_checkrequiredtoken(p, JSON_COMMA, PARSE_MSSNGCOMMA)) goto json_parsearraycleanup; if (parse_checkdisallowedtoken(p, JSON_RIGHTSQUAREBRACE, JSON_BLNKELMNT)) goto json_parsearraycleanup; } } if (!parse_checkrequiredtoken(p, JSON_RIGHTSQUAREBRACE, PARSE_MSSNGSQBRC)) goto json_parsearraycleanup; json_setoutput(out, MORPHO_OBJECT(new)); return true; json_parsearraycleanup: if (new) object_free((object *) new); return false; } /** Parses a json value using the parse table */ bool json_parsevalue(parser *p, void *out) { if (!parse_incrementrecursiondepth(p)) return false; // Increment and check bool success=parse_precedence(p, PREC_ASSIGN, out); parse_decrementrecursiondepth(p); return success; } /** Base JSON parse type */ bool json_parseelement(parser *p, void *out) { if (parse_checkdisallowedtoken(p, JSON_EOF, JSON_BLNKELMNT)) return false; bool success=json_parsevalue(p, out); if (success && p->current.type!=JSON_EOF) { parse_error(p, false, JSON_EXTRNSTOK); return false; } return success; } /* ------------------------------------------------------- * JSON parse table * ------------------------------------------------------- */ parserule json_rules[] = { PARSERULE_PREFIX(JSON_LEFTCURLYBRACE, json_parseobject), PARSERULE_PREFIX(JSON_LEFTSQUAREBRACE, json_parsearray), PARSERULE_PREFIX(JSON_STRING, json_parsestring), PARSERULE_PREFIX(JSON_NUMBER, json_parsenumber), PARSERULE_PREFIX(JSON_FLOAT, json_parsefloat), PARSERULE_PREFIX(JSON_TRUE, json_parsetrue), PARSERULE_PREFIX(JSON_FALSE, json_parsefalse), PARSERULE_PREFIX(JSON_NULL, json_parsenull), PARSERULE_UNUSED(TOKEN_NONE) }; /* ------------------------------------------------------- * Initialize a JSON parser * ------------------------------------------------------- */ /** Initializes a parser to parse JSON */ void json_initializeparser(parser *p, lexer *l, error *err, void *out) { parse_init(p, l, err, out); parse_setbaseparsefn(p, json_parseelement); parse_setparsetable(p, json_rules); parse_setskipnewline(p, false, TOKEN_NONE); } /* ********************************************************************** * Interface to JSON parser * ********************************************************************** */ /** @brief Parses JSON into a value. @param[in] in - source string @param[in] err - error block to fill out on failure @param[out] out - value on succes @param[out] objects - [optional] a varray filled out with all objects generated in parsing @returns true on success, false otherwise */ bool json_parse(char *in, error *err, value *out, varray_value *objects) { varray_value obj; varray_valueinit(&obj); jsonoutput output; json_outputinit(&output, &obj); lexer l; json_initializelexer(&l, in); parser p; json_initializeparser(&p, &l, err, &output); bool success=parse(&p); parse_clear(&p); lex_clear(&l); if (success) { *out = json_getoutput(&output); if (objects) varray_valueadd(objects, obj.data, obj.count); } else { // Free any objects that were already allocated for (unsigned int i=0; istring; cstring+str->length; ) { int nbytes = morpho_utf8numberofbytes(c); if (!nbytes) return false; if (nbytes==1 && iscntrl((unsigned char) *c)) { switch (*c) { case '\b': success=varray_charadd(out, "\\b", 2); break; case '\f': success=varray_charadd(out, "\\f", 2); break; case '\n': success=varray_charadd(out, "\\n", 2); break; case '\r': success=varray_charadd(out, "\\r", 2); break; case '\t': success=varray_charadd(out, "\\t", 2); break; case '\\': success=varray_charadd(out, "\\\\", 2); break; default: { char temp[128]; int n = snprintf(temp, 128, "\\u%04x", (int) *c); success=varray_charadd(out, temp, n); } } } else { success=varray_charadd(out, c, nbytes); } c+=nbytes; } success=varray_charadd(out, "\"", 1); } else if (MORPHO_ISLIST(in)) { objectlist *lst = MORPHO_GETLIST(in); varray_charadd(out, "[", 1); for (int i=0; ival.count; i++) { if (!json_valuetovarraychar(v, lst->val.data[i], out)) return false; if (ival.count-1) varray_charadd(out, ",", 1); } success=varray_charadd(out, "]", 1); } else if (MORPHO_ISDICTIONARY(in)) { objectdictionary *dict = MORPHO_GETDICTIONARY(in); varray_charadd(out, "{", 1); for (unsigned int i=0, k=0; idict.capacity; i++) { value key = dict->dict.contents[i].key; if (MORPHO_ISNIL(key)) continue; if (!MORPHO_ISSTRING(key)) success=varray_charadd(out, "\"", 1); if (!json_valuetovarraychar(v, key, out)) return false; if (!MORPHO_ISSTRING(key)) success=varray_charadd(out, "\"", 1); success=varray_charadd(out, ":", 1); if (!json_valuetovarraychar(v, dict->dict.contents[i].val, out)) return false; if (kdict.count-1) varray_charadd(out, ",", 1); k++; } success=varray_charadd(out, "}", 1); } return success; } bool json_tostring(vm *v, value in, value *out) { bool success=false; varray_char str; varray_charinit(&str); success=json_valuetovarraychar(v, in, &str); if (success) { *out=object_stringfromvarraychar(&str); if (!MORPHO_ISSTRING(*out)) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); success=false; } } varray_charclear(&str); return success; } /* ********************************************************************** * JSON class * ********************************************************************** */ value JSON_parse(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { char *src = MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)); varray_value objects; varray_valueinit(&objects); error err; error_init(&err); if (json_parse(src, &err, &out, &objects)) { morpho_bindobjects(v, objects.count, objects.data); } else { morpho_runtimeerror(v, err.id); } varray_valueclear(&objects); } else morpho_runtimeerror(v, JSON_PRSARGS); return out; } value JSON_tostring(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==1) { if (json_tostring(v, MORPHO_GETARG(args, 0), &out)) { morpho_bindobjects(v, 1, &out); } } return out; } MORPHO_BEGINCLASS(JSON) MORPHO_METHOD(JSON_PARSEMETHOD, JSON_parse, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_TOSTRING_METHOD, JSON_tostring, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization/finalization * ********************************************************************** */ void json_initialize(void) { // Locate the Object class to use as the parent class of JSON objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // JSON class builtin_addclass(JSON_CLASSNAME, MORPHO_GETCLASSDEFINITION(JSON), objclass); morpho_defineerror(JSON_OBJCTKEY, ERROR_PARSE, JSON_OBJCTKEY_MSG); morpho_defineerror(JSON_PRSARGS, ERROR_PARSE, JSON_PRSARGS_MSG); morpho_defineerror(JSON_EXTRNSTOK, ERROR_PARSE, JSON_EXTRNSTOK_MSG); morpho_defineerror(JSON_NMBRFRMT, ERROR_PARSE, JSON_NMBRFRMT_MSG); morpho_defineerror(JSON_BLNKELMNT, ERROR_PARSE, JSON_BLNKELMNT_MSG); } ================================================ FILE: src/classes/json.h ================================================ /** @file json.h * @author T J Atherton * * @brief JSON parser */ #ifndef json_h #define json_h #include "object.h" #include "dictionary.h" #define JSON_NULL_LABEL "null" /* ------------------------------------------------------- * JSON class * ------------------------------------------------------- */ #define JSON_CLASSNAME "JSON" #define JSON_PARSEMETHOD "parse" /* ------------------------------------------------------- * JSON error messages * ------------------------------------------------------- */ #define JSON_OBJCTKEY "JSONObjctKey" #define JSON_OBJCTKEY_MSG "JSON object keys must be strings." #define JSON_EXTRNSTOK "JSONExtrnsTkn" #define JSON_EXTRNSTOK_MSG "Extraneous token after JSON element." #define JSON_PRSARGS "JSONPrsArgs" #define JSON_PRSARGS_MSG "Method 'parse' requires a string as the argument." #define JSON_NMBRFRMT "JSONNmbrFrmt" #define JSON_NMBRFRMT_MSG "Improperly formatted number." #define JSON_BLNKELMNT "JSONBlnkElmnt" #define JSON_BLNKELMNT_MSG "Blank element." /* ------------------------------------------------------- * JSON interface * ------------------------------------------------------- */ /* Parse JSON into a value */ bool json_parse(char *in, error *err, value *out, varray_value *objects); void json_initialize(void); #endif ================================================ FILE: src/classes/list.c ================================================ /** @file list.c * @author T J Atherton * * @brief Implements the List class */ #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * objectlist definitions * ********************************************************************** */ void objectlist_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectlist_freefn(object *obj) { objectlist *list = (objectlist *) obj; varray_valueclear(&list->val); } void objectlist_markfn(object *obj, void *v) { objectlist *c = (objectlist *) obj; morpho_markvarrayvalue(v, &c->val); } size_t objectlist_sizefn(object *obj) { return sizeof(objectlist)+sizeof(value) * ((objectlist *) obj)->val.capacity; } objecttypedefn objectlistdefn = { .printfn=objectlist_printfn, .markfn=objectlist_markfn, .freefn=objectlist_freefn, .sizefn=objectlist_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Creates a new list */ objectlist *object_newlist(unsigned int nval, value *val) { objectlist *new = (objectlist *) object_new(sizeof(objectlist), OBJECT_LIST); if (new) { varray_valueinit(&new->val); if (nval>0) { if (val) varray_valueadd(&new->val, val, nval); else varray_valueresize(&new->val, nval); } } return new; } /* ********************************************************************** * objectlist utility functions * ********************************************************************** */ /** Resizes a list */ bool list_resize(objectlist *list, int size) { return varray_valueresize(&list->val, size); } /** Appends an item to a list */ void list_append(objectlist *list, value v) { varray_valuewrite(&list->val, v); } /** Returns the length of a list */ unsigned int list_length(objectlist *list) { return list->val.count; } /** Removes an element from a list * @param[in] list a list object * @param[in] indx position to insert * @param[in] nval number of values to insert * @param[in] vals the entries to insert * @returns true on success */ bool list_insert(objectlist *list, int indx, int nval, value *vals) { int i = indx; while (i<0) i+=list->val.count+1; if (i>list->val.count) return false; if (nval>list->val.capacity-list->val.count) if (!list_resize(list, list->val.count+nval)) return false; memmove(list->val.data+i+nval, list->val.data+i, sizeof(value)*(list->val.count-i)); memcpy(list->val.data+i, vals, sizeof(value)*nval); list->val.count+=nval; return true; } /** Removes an element from a list * @param[in] list a list object * @param[in] val the entry to remove * @returns true on success */ bool list_remove(objectlist *list, value val) { /* Find the element */ for (unsigned int i=0; ival.count; i++) { if (MORPHO_ISEQUAL(list->val.data[i], val)) { /* Remove it if we're not at the end of the list */ if (ival.count-1) memmove(list->val.data+i, list->val.data+i+1, sizeof(value)*(list->val.count-i-1)); list->val.count--; return true; } } return false; } /** Gets an element from a list * @param[in] list a list object * @param[in] i the index (may be negative) * @param[in] out filled out on exit if index is in bounds * @returns true on success */ bool list_getelement(objectlist *list, int i, value *out) { if (!(i>=-(int) list->val.count && i<(int) list->val.count)) return false; if (i>=0) *out=list->val.data[i]; else *out=list->val.data[list->val.count+i]; return true; } /** Sort function for list_sort */ int list_sortfunction(const void *a, const void *b) { value l=*(value *) a, r=*(value *) b; return -morpho_extendedcomparevalue(l, r); } /** Sort the contents of a list */ void list_sort(objectlist *list) { qsort(list->val.data, list->val.count, sizeof(value), list_sortfunction); } static vm *list_sortwithfn_vm; static value list_sortwithfn_fn; static bool list_sortwithfn_err; /** Sort function for list_sort */ int list_sortfunctionwfn(const void *a, const void *b) { value args[2] = {*(value *) a, *(value *) b}; value ret; if (morpho_call(list_sortwithfn_vm, list_sortwithfn_fn, 2, args, &ret)) { if (MORPHO_ISINTEGER(ret)) return MORPHO_GETINTEGERVALUE(ret); if (MORPHO_ISFLOAT(ret)) return morpho_comparevalue(MORPHO_FLOAT(0), ret); } list_sortwithfn_err=true; return 0; } /** Sort the contents of a list */ bool list_sortwithfn(vm *v, value fn, objectlist *list) { list_sortwithfn_vm=v; list_sortwithfn_fn=fn; list_sortwithfn_err=false; qsort(list->val.data, list->val.count, sizeof(value), list_sortfunctionwfn); return !list_sortwithfn_err; } /** Sort function for list_order */ typedef struct { unsigned int indx; value val; } listorderstruct; /** Sort function for list_order */ int list_orderfunction(const void *a, const void *b) { return -morpho_comparevalue(((listorderstruct *) a)->val, ((listorderstruct *) b)->val); } /* Returns a list of indices giving the ordering of a list */ objectlist *list_order(objectlist *list) { listorderstruct *order = MORPHO_MALLOC(list->val.count*sizeof(listorderstruct)); objectlist *new = NULL; if (order) { for (unsigned int i=0; ival.count; i++) { order[i].indx=i; order[i].val=list->val.data[i]; } qsort(order, list->val.count, sizeof(listorderstruct), list_orderfunction); new=object_newlist(list->val.count, NULL); if (new) { for (unsigned int i=0; ival.count; i++) { new->val.data[i]=MORPHO_INTEGER(order[i].indx); } new->val.count=list->val.count; } MORPHO_FREE(order); } return new; } /** Reverses a list in place */ void list_reverse(objectlist *list) { unsigned int hlen = list->val.count / 2; for (unsigned int i=0; ival.data[i]; unsigned int j=list->val.count-i-1; list->val.data[i]=list->val.data[j]; list->val.data[j]=swp; } } /** Tests if a value is a member of a list */ bool list_ismember(objectlist *list, value v) { for (unsigned int i=0; ival.count; i++) { if (MORPHO_ISEQUAL(list->val.data[i], v)) return true; } return false; } /** Clones a list */ objectlist *list_clone(objectlist *list) { return object_newlist(list->val.count, list->val.data); } /** Concatenates two lists */ objectlist *list_concatenate(objectlist *a, objectlist *b) { objectlist *new=object_newlist(a->val.count+b->val.count, NULL); if (new) { if (new->val.data) { memcpy(new->val.data, a->val.data, sizeof(value)*a->val.count); memcpy(new->val.data+a->val.count, b->val.data, sizeof(value)*b->val.count); } new->val.count=a->val.count+b->val.count; } return new; } /** Rolls a list by a number of elements */ objectlist *list_roll(objectlist *a, int nplaces) { objectlist *new=object_newlist(a->val.count, NULL); if (new) { new->val.count=a->val.count; unsigned int N = a->val.count; int n = abs(nplaces); if (n>N) n = n % N; unsigned int Np = N - n; // Number of elements to roll if (nplaces<0) { memcpy(new->val.data, a->val.data+n, sizeof(value)*Np); memcpy(new->val.data+Np, a->val.data, sizeof(value)*n); } else { memcpy(new->val.data+n, a->val.data, sizeof(value)*Np); if (n>0) memcpy(new->val.data, a->val.data+Np, sizeof(value)*n); } } return new; } /** Loop function for enumerable initializers */ static bool list_enumerableinitializer(vm *v, indx i, value val, void *ref) { objectlist *list = (objectlist *) ref; list_append(list, val); return true; } /* Constructs a new list of a given size with a generic interface */ void list_sliceconstructor(unsigned int *slicesize, unsigned int ndim, value* out){ objectlist *list = object_newlist(slicesize[0], NULL); list->val.count = slicesize[0]; *out = MORPHO_OBJECT(list); } /* Checks that a list is indexed with only one value with a generic interface */ bool list_slicedim(value * a, unsigned int ndim){ if (ndim>1||ndim<0) return false; return true; } /* Copies data from list a at position indx to list out at position newindx with a generic interface */ objectarrayerror list_slicecopy(value * a,value * out, unsigned int ndim, unsigned int *indx,unsigned int *newindx){ value data; objectlist *outList = MORPHO_GETLIST(*out); if (list_getelement(MORPHO_GETLIST(*a),indx[0],&data)){ outList->val.data[newindx[0]] = data; } else return ARRAY_OUTOFBOUNDS; return ARRAY_OK; } /** Generate sets/tuples and return as a list of lists */ value list_generatetuples(vm *v, objectlist *list, unsigned int n, tuplemode mode) { unsigned int nval=list->val.count; unsigned int work[2*n]; value tuple[n]; morpho_tuplesinit(list->val.count, n, work, mode); objectlist *new = object_newlist(0, NULL); if (!new) goto list_generatetuples_cleanup; while (morpho_tuples(nval, list->val.data, n, work, mode, tuple)) { objectlist *el = object_newlist(n, tuple); if (el) { list_append(new, MORPHO_OBJECT(el)); } else { goto list_generatetuples_cleanup; } } list_append(new, MORPHO_OBJECT(new)); morpho_bindobjects(v, new->val.count, new->val.data); new->val.count--; // And pop it back off return MORPHO_OBJECT(new); list_generatetuples_cleanup: morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); if (new) { // Deallocate partially created list for (unsigned int i=0; ival.count; i++) { value el=new->val.data[i]; if (MORPHO_ISOBJECT(el)) object_free(MORPHO_GETOBJECT(el)); } object_free((object *) new); } return MORPHO_NIL; } /* ********************************************************************** * List veneer class * ********************************************************************** */ /** Constructor function for Lists */ value list_constructor(vm *v, int nargs, value *args) { value out=MORPHO_NIL; value init=MORPHO_NIL; objectlist *new=NULL; if (nargs==1) { value x = MORPHO_GETARG(args, 0); if (MORPHO_ISRANGE(MORPHO_GETARG(args, 0))) { init = x; // Enumerate will handle this new = object_newlist(0, NULL); } else if (MORPHO_ISTUPLE(MORPHO_GETARG(args, 0))) { new = object_newlist(MORPHO_GETTUPLELENGTH(x), MORPHO_GETTUPLEVALUES(x)); } } if (!new) new = object_newlist(nargs, args+1); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); if (!MORPHO_ISNIL(init)) { builtin_enumerateloop(v, init, list_enumerableinitializer, new); } } return out; } /** Append an element to a list */ value List_append(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); unsigned int capacity = slf->val.capacity; varray_valueadd(&slf->val, args+1, nargs); if (slf->val.capacity!=capacity) morpho_resizeobject(v, (object *) slf, capacity*sizeof(value)+sizeof(objectlist), slf->val.capacity*sizeof(value)+sizeof(objectlist)); return MORPHO_SELF(args); } /** Remove a element from a list */ value List_remove(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (nargs==1) { if (!list_remove(slf, MORPHO_GETARG(args, 0))) morpho_runtimeerror(v, LIST_ENTRYNTFND); } else morpho_runtimeerror(v, VM_INVALIDARGS, 1, nargs); return MORPHO_NIL; } /** Inserts an element at a specified position */ value List_insert(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (nargs>=2) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int indx = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (!list_insert(slf, indx, nargs-1, &MORPHO_GETARG(args, 1))) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } } else morpho_runtimeerror(v, VM_INVALIDARGS, 2, nargs); return MORPHO_NIL; } /** Pops an element from the end of a list */ value List_pop(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out=MORPHO_NIL; if (slf->val.count>0) { if (nargs>0 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int indx = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (indx<0) indx+=slf->val.count; if (indx<0 || indx>slf->val.count) { morpho_runtimeerror(v, VM_OUTOFBOUNDS); return MORPHO_NIL; } out=slf->val.data[indx]; memmove(slf->val.data+indx, slf->val.data+indx+1, sizeof(value)*(slf->val.count-indx-1)); } else { out=slf->val.data[slf->val.count-1]; } slf->val.count--; } return out; } /** Get an element */ value List_getindex(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (!list_getelement(slf, i, &out)) { morpho_runtimeerror(v, VM_OUTOFBOUNDS); } } else { objectarrayerror err = getslice(&MORPHO_SELF(args),&list_slicedim,&list_sliceconstructor,&list_slicecopy,nargs,&MORPHO_GETARG(args, 0),&out); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_to_list_error(err) ); if (MORPHO_ISOBJECT(out)){ morpho_bindobjects(v,1,&out); } else MORPHO_RAISE(v, VM_NONNUMINDX); } } else MORPHO_RAISE(v, LIST_NUMARGS) return out; } /** Sets an element */ value List_setindex(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (nargs==2) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (ival.count) slf->val.data[i]=MORPHO_GETARG(args, 1); else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } else morpho_runtimeerror(v, SETINDEX_ARGS); } else morpho_runtimeerror(v, SETINDEX_ARGS); return MORPHO_SELF(args); } /** Print a list */ value List_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISLIST(self)) return Object_print(v, nargs, args); objectlist *lst=MORPHO_GETLIST(self); morpho_printf(v, "[ "); for (unsigned int i=0; ival.count; i++) { morpho_printvalue(v, lst->val.data[i]); if (ival.count-1) morpho_printf(v, ", "); } morpho_printf(v, " ]"); return MORPHO_NIL; } /** Convert a list to a string */ value List_tostring(vm *v, int nargs, value *args) { objectlist *lst=MORPHO_GETLIST(MORPHO_SELF(args)); value out = MORPHO_NIL; varray_char buffer; varray_charinit(&buffer); varray_charadd(&buffer, "[ ", 2); for (unsigned int i=0; ival.count; i++) { morpho_printtobuffer(v, lst->val.data[i], &buffer); if (ival.count-1) varray_charadd(&buffer, ", ", 2); } varray_charadd(&buffer, " ]", 2); out = object_stringfromvarraychar(&buffer); if (MORPHO_ISSTRING(out)) { morpho_bindobjects(v, 1, &out); } varray_charclear(&buffer); return out; } /** Enumerate members of a list */ value List_enumerate(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) { out=MORPHO_INTEGER(slf->val.count); } else if (nval.count) { return slf->val.data[n]; } else { morpho_runtimeerror(v, VM_OUTOFBOUNDS); } } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Get number of entries */ value List_count(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); return MORPHO_INTEGER(slf->val.count); } /** Generate a list of n-tuples from a list */ value List_tuples(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); unsigned int n=2; if (nargs>0 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<2) n=2; } return list_generatetuples(v, slf, n, MORPHO_TUPLEMODE); } /** Generate a list of n-tuples from a list */ value List_sets(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); unsigned int n=2; if (nargs>0 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<2) n=2; if (n>slf->val.capacity) n = slf->val.capacity; } return list_generatetuples(v, slf, n, MORPHO_SETMODE); } /** Clones a list */ value List_clone(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); objectlist *new = list_clone(slf); if (!new) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); value out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); return out; } /** Joins two lists together */ value List_join(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISLIST(MORPHO_GETARG(args, 0))) { objectlist *operand = MORPHO_GETLIST(MORPHO_GETARG(args, 0)); objectlist *new = list_concatenate(slf, operand); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, LIST_ADDARGS); return out; } /** Roll a list */ value List_roll(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { int roll; morpho_valuetoint(MORPHO_GETARG(args, 0), &roll); objectlist *new = list_roll(slf, roll); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, LIST_ADDARGS); return out; } /** Sorts a list */ value XList_sort(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (nargs==0) { list_sort(slf); } else if (nargs==1 && MORPHO_ISCALLABLE(MORPHO_GETARG(args, 0))) { if (!list_sortwithfn(v, MORPHO_GETARG(args, 0), slf)) { morpho_runtimeerror(v, LIST_SRTFN); } } return MORPHO_NIL; } /** Sorts a list */ value List_sort(vm *v, int nargs, value *args) { list_sort(MORPHO_GETLIST(MORPHO_SELF(args))); return MORPHO_NIL; } value List_sort_fn(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (!list_sortwithfn(v, MORPHO_GETARG(args, 0), slf)) { morpho_runtimeerror(v, LIST_SRTFN); } return MORPHO_NIL; } /** Returns a list of indices that would sort the list self */ value List_order(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); value out=MORPHO_NIL; objectlist *new=list_order(slf); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Returns a list with the order reversed */ value List_reverse(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); list_reverse(slf); return MORPHO_NIL; } /** Tests if a list has a value as a member */ value List_ismember(vm *v, int nargs, value *args) { objectlist *slf = MORPHO_GETLIST(MORPHO_SELF(args)); if (nargs==1) { return MORPHO_BOOL(list_ismember(slf, MORPHO_GETARG(args, 0))); } else morpho_runtimeerror(v, ISMEMBER_ARG, 1, nargs); return MORPHO_NIL; } MORPHO_BEGINCLASS(List) MORPHO_METHOD(MORPHO_APPEND_METHOD, List_append, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_REMOVE_METHOD, List_remove, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_INSERT_METHOD, List_insert, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_POP_METHOD, List_pop, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_GETINDEX_METHOD, List_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, List_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, List_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_TOSTRING_METHOD, List_tostring, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, List_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, List_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_TUPLES_METHOD, List_tuples, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_SETS_METHOD, List_sets, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, List_clone, BUILTIN_FLAGSEMPTY), //MORPHO_METHOD(MORPHO_ADD_METHOD, List_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_JOIN_METHOD, List_join, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ROLL_METHOD, List_roll, BUILTIN_FLAGSEMPTY), MORPHO_METHOD_SIGNATURE(LIST_SORT_METHOD, "()", List_sort, BUILTIN_FLAGSEMPTY), MORPHO_METHOD_SIGNATURE(LIST_SORT_METHOD, "(_)", List_sort_fn, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_ORDER_METHOD, List_order, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_REVERSE_METHOD, List_reverse, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_ISMEMBER_METHOD, List_ismember, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CONTAINS_METHOD, List_ismember, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectlisttype; void list_initialize(void) { // Define list objecttype objectlisttype=object_addtype(&objectlistdefn); // Locate the Object class to use as the parent class of List objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Define List class value listclass=builtin_addclass(LIST_CLASSNAME, MORPHO_GETCLASSDEFINITION(List), objclass); object_setveneerclass(OBJECT_LIST, listclass); // List constructor function morpho_addfunction(LIST_CLASSNAME, LIST_CLASSNAME " (...)", list_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // List error messages morpho_defineerror(LIST_ENTRYNTFND, ERROR_HALT, LIST_ENTRYNTFND_MSG); morpho_defineerror(LIST_ADDARGS, ERROR_HALT, LIST_ADDARGS_MSG); morpho_defineerror(LIST_SRTFN, ERROR_HALT, LIST_SRTFN_MSG); morpho_defineerror(LIST_ARGS, ERROR_HALT, LIST_ARGS_MSG); morpho_defineerror(LIST_NUMARGS, ERROR_HALT, LIST_NUMARGS_MSG); } ================================================ FILE: src/classes/list.h ================================================ /** @file list.h * @author T J Atherton * * @brief Defines list object type and List class */ #ifndef list_h #define list_h #include "object.h" /* ------------------------------------------------------- * List object type * ------------------------------------------------------- */ extern objecttype objectlisttype; #define OBJECT_LIST objectlisttype typedef struct { object obj; varray_value val; } objectlist; /** Tests whether an object is a list */ #define MORPHO_ISLIST(val) object_istype(val, OBJECT_LIST) /** Gets the object as a list */ #define MORPHO_GETLIST(val) ((objectlist *) MORPHO_GETOBJECT(val)) /** Create a static list - you must initialize the list separately */ #define MORPHO_STATICLIST { .obj.type=OBJECT_LIST, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .val.count=0, .val.capacity=0, .val.data=NULL } objectlist *object_newlist(unsigned int nval, value *val); /* ------------------------------------------------------- * List veneer class * ------------------------------------------------------- */ #define LIST_CLASSNAME "List" #define LIST_ISMEMBER_METHOD "ismember" #define LIST_SORT_METHOD "sort" #define LIST_ORDER_METHOD "order" #define LIST_POP_METHOD "pop" #define LIST_INSERT_METHOD "insert" #define LIST_REMOVE_METHOD "remove" #define LIST_TUPLES_METHOD "tuples" #define LIST_SETS_METHOD "sets" #define LIST_REVERSE_METHOD "reverse" /* ------------------------------------------------------- * List error messages * ------------------------------------------------------- */ #define LIST_ENTRYNTFND "EntryNtFnd" #define LIST_ENTRYNTFND_MSG "Entry not found." #define LIST_ADDARGS "LstAddArgs" #define LIST_ADDARGS_MSG "Add method requires a list." #define LIST_SRTFN "LstSrtFn" #define LIST_SRTFN_MSG "List sort function must return an integer." #define LIST_ARGS "LstArgs" #define LIST_ARGS_MSG "Lists must be called with integer dimensions as arguments." #define LIST_NUMARGS "LstNumArgs" #define LIST_NUMARGS_MSG "Lists can only be indexed with one argument." /* ------------------------------------------------------- * List interface * ------------------------------------------------------- */ value List_getindex(vm *v, int nargs, value *args); bool list_resize(objectlist *list, int size); void list_append(objectlist *list, value v); unsigned int list_length(objectlist *list); bool list_getelement(objectlist *list, int i, value *out); void list_sort(objectlist *list); objectlist *list_clone(objectlist *list); void list_initialize(void); #endif ================================================ FILE: src/classes/metafunction.c ================================================ /** @file metafunction.c * @author T J Atherton * * @brief Implement objectmetafunctions and the Metafunction veneer class */ #include #include "morpho.h" #include "classes.h" #include "common.h" /* ********************************************************************** * Metafunction opcodes * ********************************************************************** */ enum { MF_RESOLVE, MF_FAIL, MF_CHECKNARGSNEQ, MF_CHECKNARGSLT, MF_CHECKVALUE, MF_CHECKOBJECT, MF_CHECKINSTANCE, MF_BRANCH, MF_BRANCHNARGS, MF_BRANCHVALUETYPE, MF_BRANCHOBJECTTYPE, MF_BRANCHINSTANCE }; /* ********************************************************************** * objectmetafunction definitions * ********************************************************************** */ void objectmetafunction_freefn(object *obj) { objectmetafunction *f = (objectmetafunction *) obj; morpho_freeobject(f->name); varray_valueclear(&f->fns); metafunction_clearinstructions(f); } void objectmetafunction_markfn(object *obj, void *v) { objectmetafunction *f = (objectmetafunction *) obj; morpho_markvalue(v, f->name); // Mark the name for (int i=0; iresolver.count; i++) { // Mark any functions in the resolver mfinstruction *instr = &f->resolver.data[i]; if (instr->opcode==MF_RESOLVE) morpho_markvalue(v,instr->data.resolvefn); } } size_t objectmetafunction_sizefn(object *obj) { objectmetafunction *f = (objectmetafunction *) obj; return sizeof(objectmetafunction)+sizeof(value)*f->fns.count; } void objectmetafunction_printfn(object *obj, void *v) { objectmetafunction *f = (objectmetafunction *) obj; if (f) morpho_printf(v, "", (MORPHO_ISNIL(f->name) ? "" : MORPHO_GETCSTRING(f->name))); } objecttypedefn objectmetafunctiondefn = { .printfn=objectmetafunction_printfn, .markfn=objectmetafunction_markfn, .freefn=objectmetafunction_freefn, .sizefn=objectmetafunction_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * objectmetafunction utility functions * ********************************************************************** */ /** Creates a new metafunction */ objectmetafunction *object_newmetafunction(value name) { objectmetafunction *new = (objectmetafunction *) object_new(sizeof(objectmetafunction), OBJECT_METAFUNCTION); if (new) { new->name=MORPHO_NIL; if (MORPHO_ISSTRING(name)) new->name=object_clonestring(name); new->klass=NULL; varray_valueinit(&new->fns); varray_mfinstructioninit(&new->resolver); } return new; } /** Clone a metafunction */ objectmetafunction *metafunction_clone(objectmetafunction *f) { objectmetafunction *new = object_newmetafunction(f->name); if (new) { varray_valueadd(&new->fns, f->fns.data, f->fns.count); } return new; } /** Wraps a function in a metafunction */ bool metafunction_wrap(value name, value fn, value *out) { if (!MORPHO_ISCALLABLE(fn)) return false; objectmetafunction *mf = object_newmetafunction(name); if (!mf) return false; metafunction_add(mf, fn); *out = MORPHO_OBJECT(mf); return true; } /** Adds a function to a metafunction */ bool metafunction_add(objectmetafunction *f, value fn) { return varray_valuewrite(&f->fns, fn); } /** Extracts a type from a value */ bool metafunction_typefromvalue(value v, value *out) { objectclass *clss = NULL; if (MORPHO_ISINSTANCE(v)) { clss=MORPHO_GETINSTANCE(v)->klass; } else if (MORPHO_ISOBJECT(v)) { clss = object_getveneerclass(MORPHO_GETOBJECT(v)->type); } else clss = value_getveneerclass(v); if (clss) *out = MORPHO_OBJECT(clss); return clss; } /** Checks if val matches a given type */ bool metafunction_matchtype(value type, value val) { value match; if (!metafunction_typefromvalue(val, &match)) return false; if (MORPHO_ISNIL(type) || // If type is unset, we always match MORPHO_ISEQUAL(type, match)) return true; // Or if the types are the same return false; } /** Sets the parent class of a metafunction */ void metafunction_setclass(objectmetafunction *f, objectclass *klass) { f->klass=klass; } /** Returns a metafunction's class if any */ objectclass *metafunction_class(objectmetafunction *f) { return f->klass; } /** Finds whether an implementation f occurs in a metafunction */ bool metafunction_matchfn(objectmetafunction *fn, value f) { for (int i=0; ifns.count; i++) if (MORPHO_ISEQUAL(fn->fns.data[i], f)) return true; return false; } /** Checks if a metafunction matches a given list of implementations */ bool metafunction_matchset(objectmetafunction *fn, int n, value *fns) { for (int i=0; isig; } else if (MORPHO_ISBUILTINFUNCTION(fn)) { return &MORPHO_GETBUILTINFUNCTION(fn)->sig; } else if (MORPHO_ISCLOSURE(fn)) { return &MORPHO_GETCLOSURE(fn)->func->sig; } return NULL; } value _getname(value fn) { if (MORPHO_ISFUNCTION(fn)) { return MORPHO_GETFUNCTION(fn)->name; } else if (MORPHO_ISBUILTINFUNCTION(fn)) { return MORPHO_GETBUILTINFUNCTION(fn)->name; } else if (MORPHO_ISCLOSURE(fn)) { return MORPHO_GETCLOSURE(fn)->func->name; } return MORPHO_NIL; } /* ********************************************************************** * Fast metafunction resolver * ********************************************************************** */ DEFINE_VARRAY(mfinstruction, mfinstruction); #define MFINSTRUCTION_EMPTY -1 #define MFINSTRUCTION_FAIL { .opcode=MF_FAIL, .branch=MFINSTRUCTION_EMPTY } #define MFINSTRUCTION_RESOLVE(fn) { .opcode=MF_RESOLVE, .data.resolvefn=fn, .branch=MFINSTRUCTION_EMPTY } #define MFINSTRUCTION_OPTARGS { .opcode=MF_OPTARGS, .branch=MFINSTRUCTION_EMPTY } #define MFINSTRUCTION_CHECKNARGS(op, n, brnch) { .opcode=op, .narg=n, .branch=brnch } #define MFINSTRUCTION_CHECKTYPE(op, n, t, brnch) { .opcode=op, .data.tindx=t, .narg=n, .branch=brnch } #define MFINSTRUCTION_BRANCH(brnch) { .opcode=MF_BRANCH, .branch=brnch } #define MFINSTRUCTION_BRANCHNARG(table, brnch) { .opcode=MF_BRANCHNARGS, .data.btable=table, .branch=brnch } #define MFINSTRUCTION_BRANCHTABLE(op, n, table, brnch) { .opcode=op, .narg=n, .data.btable=table, .branch=brnch } typedef struct { signature *sig; /** Signature of the target */ value fn; /** The target */ int indx; /** Used to sort */ } mfresult; typedef struct { int count; mfresult *rlist; } mfset; /** Static intiializer for the mfset */ #define MFSET(c, l) { .count=c, .rlist=l } DECLARE_VARRAY(mfset, mfset) DEFINE_VARRAY(mfset, mfset) typedef struct { objectmetafunction *fn; varray_int checked; // Stack of checked parameters error err; } mfcompiler; /** Initialize the metafunction compiler */ void mfcompiler_init(mfcompiler *c, objectmetafunction *fn) { c->fn=fn; varray_intinit(&c->checked); error_init(&c->err); } /** Clear the metafunction compiler */ void mfcompiler_clear(mfcompiler *c, objectmetafunction *fn) { varray_intclear(&c->checked); error_clear(&c->err); } /** Report an error during metafunction compilation */ void mfcompiler_error(mfcompiler *c, errorid id) { morpho_writeerrorwithid(&c->err, id, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE); } /** Pushes a parameter check onto the stack*/ void mfcompiler_pushcheck(mfcompiler *c, int i) { varray_intwrite(&c->checked, i); } /** Pops a parameter check from the stack*/ int mfcompiler_popcheck(mfcompiler *c) { c->checked.count--; return c->checked.data[c->checked.count]; } /** Tests if a parameter has been checked according to the check stack */ bool mfcompiler_ischecked(mfcompiler *c, int i) { for (int j=0; jchecked.count; j++) if (i==c->checked.data[j]) return true; return false; } void _mfcompiler_disassemblebranchtable(mfinstruction *instr, mfindx i) { for (int k=0; kdata.btable.count; k++) { printf(" %i -> %i\n", k, i+instr->data.btable.data[k]+1); } } /** Disassemble */ void mfcompiler_disassemble(mfcompiler *c) { int ninstr = c->fn->resolver.count; morpho_printvalue(NULL, MORPHO_OBJECT(c->fn)); printf(":\n"); for (int i=0; ifn->resolver.data[i]; printf("%5i : ", i) ; switch(instr->opcode) { case MF_CHECKNARGSNEQ: { printf("checknargsneq %i -> (%i)", instr->narg, i+instr->branch+1); break; } case MF_CHECKNARGSLT: { printf("checknargslt %i -> (%i)", instr->narg, i+instr->branch+1); break; } case MF_CHECKVALUE: { objectclass *klass=value_veneerclassfromtype(instr->data.tindx); printf("checkvalue (%i) [%s] -> (%i)", instr->narg, MORPHO_GETCSTRING(klass->name), i+instr->branch+1); break; } case MF_CHECKOBJECT: { objectclass *klass=object_getveneerclass(instr->data.tindx); printf("checkobject (%i) [%s] -> (%i)", instr->narg, MORPHO_GETCSTRING(klass->name), i+instr->branch+1); break; } case MF_CHECKINSTANCE: { printf("checkinstance (%i) [%i] -> (%i)", instr->narg, instr->data.tindx, i+instr->branch+1); break; } case MF_BRANCH: { printf("branch -> (%i)", i+instr->branch+1); break; } case MF_BRANCHNARGS: { printf("branchnargs (%i) -> (%i)\n", instr->narg, i+instr->branch+1); _mfcompiler_disassemblebranchtable(instr, i); break; } case MF_BRANCHVALUETYPE: { printf("branchvalue (%i) -> (%i)\n", instr->narg, i+instr->branch+1); for (int k=0; kdata.btable.count; k++) { if (instr->data.btable.data[k]==0) continue; objectclass *klass=value_veneerclassfromtype(k); printf(" %i [%s] -> %i\n", k, (klass ? MORPHO_GETCSTRING(klass->name) : ""), i+instr->data.btable.data[k]+1); } break; } case MF_BRANCHOBJECTTYPE: { printf("branchobjtype (%i) -> (%i)\n", instr->narg, i+instr->branch+1); for (int k=0; kdata.btable.count; k++) { if (instr->data.btable.data[k]==0) continue; objectclass *klass=object_getveneerclass(k); printf(" %i [%s] -> %i\n", k, (klass ? MORPHO_GETCSTRING(klass->name) : ""), i+instr->data.btable.data[k]+1); } break; } case MF_BRANCHINSTANCE: { printf("branchinstance (%i) -> (%i)\n", instr->narg, i+instr->branch+1); _mfcompiler_disassemblebranchtable(instr, i); break; } case MF_RESOLVE: { printf("resolve "); signature *sig = metafunction_getsignature(instr->data.resolvefn); printf(" "); if (sig) signature_print(sig); break; } case MF_FAIL: printf("fail"); break; } printf("\n"); } } /** Counts the range of parameters for the function call */ void mfcompile_countparams(mfcompiler *c, mfset *set, int *min, int *max) { int imin=INT_MAX, imax=INT_MIN; for (int i=0; icount; i++) { int nparams; signature_paramlist(set->rlist[i].sig, &nparams, NULL); if (nparamsimax) imax=nparams; } if (min) *min = imin; if (max) *max = imax; } /** Places the various outcomes for a parameter into a dictionary */ bool mfcompile_outcomes(mfcompiler *c, mfset *set, int i, dictionary *out) { for (int k=0; kcount; k++) { // Loop over outcomes value type; if (!signature_getparamtype(set->rlist[k].sig, i, &type)) continue; if (!dictionary_insert(out, (MORPHO_ISNIL(type) ? MORPHO_FALSE : type), MORPHO_NIL)) return false; } return true; } /** Find the parameter number that has most variety in types */ bool mfcompile_countoutcomes(mfcompiler *c, mfset *set, int *best) { varray_int count; varray_intinit(&count); dictionary dict; dictionary_init(&dict); // Loop over parameters, counting the number of outcomes. while (true) { mfcompile_outcomes(c, set, count.count, &dict); if (!dict.count) break; varray_intwrite(&count, dict.count); dictionary_clear(&dict); // Not needed if dict.count was zero }; // Find the parameter that has most variability that has not already been checked int max=0, maxindx=-1; for (int i=0; imax) { max=count.data[i]; maxindx=i; } } varray_intclear(&count); if (maxindx<0) return false; if (best) *best = maxindx; return true; } mfindx mfcompile_insertinstruction(mfcompiler *c, mfinstruction instr) { return varray_mfinstructionwrite(&c->fn->resolver, instr); } mfindx mfcompile_currentinstruction(mfcompiler *c) { return c->fn->resolver.count-1; } mfindx mfcompile_nextinstruction(mfcompiler *c) { return c->fn->resolver.count; } void mfcompile_setbranch(mfcompiler *c, mfindx i, mfindx branch) { if (i>=c->fn->resolver.count) return; c->fn->resolver.data[i].branch=branch; } void mfcompile_replaceinstruction(mfcompiler *c, mfindx i, mfinstruction instr) { if (i>=c->fn->resolver.count) return; c->fn->resolver.data[i] = instr; } enum { MF_VENEERVALUE, MF_INSTANCE, MF_VENEEROBJECT, MF_ANY }; /** Detects the kind of type */ int _detecttype(value type, int *tindx) { if (MORPHO_ISCLASS(type)) { objectclass *klass = MORPHO_GETCLASS(type); if (object_veneerclasstotype(klass, tindx)) { return MF_VENEEROBJECT; } else if (value_veneerclasstotype(klass, tindx)) { return MF_VENEERVALUE; } else { if (tindx) *tindx=klass->uid; return MF_INSTANCE; } } return MF_ANY; } mfindx mfcompile_fail(mfcompiler *c); mfindx mfcompile_resolve(mfcompiler *c, mfresult *res); mfindx mfcompile_dispatchonparam(mfcompiler *c, mfset *set, int i); mfindx mfcompile_dispatchonnarg(mfcompiler *c, mfset *set, int min, int max); mfindx mfcompile_set(mfcompiler *c, mfset *set); /** Inserts a fail instruction */ mfindx mfcompile_fail(mfcompiler *c) { mfinstruction fail = MFINSTRUCTION_FAIL; return mfcompile_insertinstruction(c, fail); } /** Checks a parameter i for type */ mfindx mfcompile_check(mfcompiler *c, int i, value type) { int tindx; int opcode[MF_ANY] = { MF_CHECKVALUE, MF_CHECKINSTANCE, MF_CHECKOBJECT }; int k=_detecttype(type, &tindx); if (k==MF_ANY) return MFINSTRUCTION_EMPTY; mfinstruction check = MFINSTRUCTION_CHECKTYPE(opcode[k], i, tindx, 0); return mfcompile_insertinstruction(c, check); } /** Compiles a single result */ mfindx mfcompile_resolve(mfcompiler *c, mfresult *res) { mfindx start = mfcompile_nextinstruction(c); // Check all arguments have been resolved signature *sig = res->sig; for (int i=0; itypes.count; i++) { if (MORPHO_ISNIL(sig->types.data[i]) || mfcompiler_ischecked(c, i)) continue; mfcompile_check(c, i, sig->types.data[i]); } mfindx end = mfcompile_nextinstruction(c); mfinstruction instr = MFINSTRUCTION_RESOLVE(res->fn); mfcompile_insertinstruction(c, instr); if (start!=end) { mfindx fail = mfcompile_fail(c); for (mfindx i=start; icount && set->rlist[k].indx<0) k++; // Deal with each outcome while (kcount) { int indx=set->rlist[k].indx, n=0; while (k+ncount && set->rlist[k+n].indx==indx) n++; mfset out = MFSET(n, &set->rlist[k]); // Set the branch point btable->data[indx]=mfcompile_currentinstruction(c)-bindx; mfcompile_set(c, &out); k+=n; } } void _insertchildren(dictionary *dict, value v) { if (!MORPHO_ISCLASS(v) || dictionary_get(dict, v, NULL)) return; dictionary_insert(dict, v, MORPHO_NIL); // Insert the class objectclass *klass = MORPHO_GETCLASS(v); // and its children for (int i=0; ichildren.count; i++) _insertchildren(dict, klass->children.data[i]); } bool _resolve(objectclass *klass, dictionary *types, value *out) { for (int k=0; klinearization.count; k++) { if (dictionary_get(types, klass->linearization.data[k], NULL)) { *out = klass->linearization.data[k]; return true; } } return false; } int _maxindx(dictionary *dict) { int indx=0, maxindx=0; for (int i=0; icapacity; i++) { _detecttype(dict->contents[i].key, &indx); if (indx>maxindx) maxindx=indx; } return maxindx; } int _mfresultsortfn (const void *a, const void *b) { mfresult *aa = (mfresult *) a, *bb = (mfresult *) b; int ai = aa->indx, bi = bb->indx; if (aa->sig->varg) ai=-1; // Ensure vargs end up first if (bb->sig->varg) bi=-1; return ai-bi; } /** Constructs a dispatch table from the set of implementations */ mfindx mfcompile_dispatchtable(mfcompiler *c, mfset *set, int i, int otype, int opcode) { dictionary types, children; dictionary_init(&types); // Keep track of the available types provided by the implementation dictionary_init(&children); // and all of their children // Extract the type index for each member of the set for (int k=0; kcount; k++) { value type; if (!signature_getparamtype(set->rlist[k].sig, i, &type)) UNREACHABLE("Incorrect parameter type"); if (_detecttype(type, &set->rlist[k].indx)==otype) { dictionary_insert(&types, type, MORPHO_NIL); _insertchildren(&children, type); } else set->rlist[k].indx=-1; // Exclude from the branch table } // Sort the set on the type index qsort(set->rlist, set->count, sizeof(mfresult), _mfresultsortfn); // Create the branch table int maxindx=_maxindx(&children); varray_int btable; varray_intinit(&btable); for (int i=0; i<=maxindx; i++) varray_intwrite(&btable, 0); // Insert the branch instruction mfinstruction instr = MFINSTRUCTION_BRANCHTABLE(opcode, i, btable, 0); mfindx bindx = mfcompile_insertinstruction(c, instr); // Fail if an object type isn't in the table mfcompile_fail(c); // Compile the branch table mfcompile_branchtable(c, set, bindx, &btable); // Fix branch table to include child classes for (int i=0; icount]; int n=0; // Find implementations that accept any type for (int k=0; kcount; k++) { value type; if (!signature_getparamtype(set->rlist[k].sig, i, &type)) return MFINSTRUCTION_EMPTY; if (_detecttype(type, &set->rlist[k].indx)==MF_ANY) { rlist[n] = set->rlist[k]; n++; } } mfindx bindx = mfcompile_nextinstruction(c); mfset anyset = MFSET(n, rlist); mfcompile_set(c, &anyset); return bindx; } /** Fixes a fallthrough fail */ void mfcompile_fixfallthrough(mfcompiler *c, mfindx i, mfindx branchto) { mfinstruction instr = MFINSTRUCTION_BRANCH(branchto-i-1); mfcompile_replaceinstruction(c, i, instr); } typedef mfindx (mfcompile_dispatchfn) (mfcompiler *c, mfset *set, int i); /** Attempts to dispatch based on a parameter i */ mfindx mfcompile_dispatchonparam(mfcompiler *c, mfset *set, int i) { mfcompiler_pushcheck(c, i); int typecount[MF_ANY+1] = { 0, 0, 0, 0}; // Determine what types are present for (int k=0; kcount; k++) { value type; if (!signature_getparamtype(set->rlist[k].sig, i, &type)) continue; typecount[_detecttype(type, NULL)]++; } mfindx bindx[MF_ANY+1]; mfcompile_dispatchfn *dfn[MF_ANY+1] = { mfcompile_dispatchveneervalue, mfcompile_dispatchinstance, mfcompile_dispatchveneerobj, mfcompile_dispatchany}; // Cycle through all value types, building a chain of branchtables int n=0; for (int j=0; j<=MF_ANY; j++) { if (typecount[j] && dfn[j]) { bindx[n]=(dfn[j]) (c, set, i); if (n>0) mfcompile_setbranch(c, bindx[n-1], bindx[n]-bindx[n-1]-1); n++; } } if (typecount[MF_ANY]) { // Fix branch table fallthroughs to point to any for (int j=0; jsig) ? MF_CHECKNARGSLT : MF_CHECKNARGSNEQ ), signature_countparams(res->sig), 0); mfindx bindx = mfcompile_insertinstruction(c, instr); // Write the check nargs instruction mfcompile_resolve(c, res); return bindx; } /** Attempts to dispatch based on the number of arguments */ mfindx mfcompile_dispatchonnarg(mfcompiler *c, mfset *set, int min, int max) { mfindx bindx = MFINSTRUCTION_EMPTY; // Sort the set into order given by number of parameters; resolution with varg is always first for (int k=0; kcount; k++) { set->rlist[k].indx=signature_countparams(set->rlist[k].sig); } qsort(set->rlist, set->count, sizeof(mfresult), _mfresultsortfn); if (set->count==2) { for (int i=1; i>=0; i--) { bindx = mfcompile_checknarg(c, &set->rlist[i]); mfindx eindx = mfcompile_currentinstruction(c); mfcompile_setbranch(c, bindx, eindx-bindx); } mfcompile_fail(c); } else { // If more than two options, generate a branch table varray_int btable; varray_intinit(&btable); for (int i=0; i<=max; i++) varray_intwrite(&btable, 0); // Insert the branch table instruction mfinstruction table = MFINSTRUCTION_BRANCHNARG(btable, 0); bindx = mfcompile_insertinstruction(c, table); // Immediately follow by a fail instruction if this falls through mfindx fail = mfcompile_fail(c); // Compile the branch table mfcompile_branchtable(c, set, bindx, &btable); // Correct branch table for varg resolution if (set->rlist[0].sig->varg) { mfindx varg = bindx+1; int nmin = set->rlist[0].sig->types.count-1; // varg can match this many args or more for (int i=nmin; icount==1) return mfcompile_resolve(c, set->rlist); int min, max; // Count the range of possible parameters mfcompile_countparams(c, set, &min, &max); // Dispatch on the number of parameters if it's in doubt if (min!=max) return mfcompile_dispatchonnarg(c, set, min, max); // If just one parameter, dispatch on it if (min==1 && !mfcompiler_ischecked(c, 0)) { return mfcompile_dispatchonparam(c, set, 0); } int best; if (mfcompile_countoutcomes(c, set, &best)) return mfcompile_dispatchonparam(c, set, best); mfcompiler_error(c, METAFUNCTION_CMPLAMBGS); return MFINSTRUCTION_EMPTY; } /** Clears the compiled code from a given metafunction */ void metafunction_clearinstructions(objectmetafunction *fn) { for (int i=0; iresolver.count; i++) { mfinstruction *mf = &fn->resolver.data[i]; if (mf->opcode>=MF_BRANCHNARGS && mf->opcode<=MF_BRANCHINSTANCE) varray_intclear(&mf->data.btable); } varray_mfinstructionclear(&fn->resolver); } /** Compiles the metafunction resolver */ bool metafunction_compile(objectmetafunction *fn, error *err) { mfset set; set.count = fn->fns.count; if (!set.count) return false; mfresult rlist[set.count]; set.rlist=rlist; for (int i=0; ifns.data[i]); rlist[i].fn=fn->fns.data[i]; } mfcompiler compiler; mfcompiler_init(&compiler, fn); mfcompile_set(&compiler, &set); //mfcompiler_disassemble(&compiler); bool success=!morpho_checkerror(&compiler.err); if (!success && err) *err=compiler.err; mfcompiler_clear(&compiler, fn); return success; } /** Attempt to find the desired class uid in the linearization of a given class */ bool _finduidinlinearization(objectclass *klass, int uid) { for (int k=0; klinearization.count; k++) { if (MORPHO_GETCLASS(klass->linearization.data[k])->uid == uid) return true; } return false; } /** Execute the metafunction's resolver @param[in] fn - the metafunction to resolve @param[in] nargs - number of positional arguments @param[in] args - positional arguments @warning: the first user-visible argument should be in the zero position @param[out] err - error block to be filled out @param[out] out - resolved function @returns true if the metafunction was successfully resolved */ bool metafunction_resolve(objectmetafunction *fn, int nargs, value *args, error *err, value *out) { if (!fn->resolver.data && !metafunction_compile(fn, err)) return false; mfinstruction *pc = fn->resolver.data; if (!pc) return false; do { switch(pc->opcode) { case MF_CHECKNARGSNEQ: if (nargs!=pc->narg) pc+=pc->branch; break; case MF_CHECKNARGSLT: if (nargsnarg) pc+=pc->branch; break; case MF_CHECKVALUE: { if (!MORPHO_ISOBJECT(args[pc->narg])) { int tindx = (int) MORPHO_GETORDEREDTYPE(args[pc->narg]); if (pc->data.tindx!=tindx) pc+=pc->branch; } else pc+=pc->branch; } break; case MF_CHECKOBJECT: { if (MORPHO_ISOBJECT(args[pc->narg])) { int tindx = (int) MORPHO_GETOBJECTTYPE(args[pc->narg]); if (pc->data.tindx!=tindx) pc+=pc->branch; } else pc+=pc->branch; } break; case MF_CHECKINSTANCE: { if (MORPHO_ISINSTANCE(args[pc->narg])) { objectclass *klass = MORPHO_GETINSTANCE(args[pc->narg])->klass; if (!(klass->uid==pc->data.tindx || _finduidinlinearization(klass, pc->data.tindx))) { pc+=pc->branch; } } else pc+=pc->branch; } break; case MF_BRANCH: pc+=pc->branch; break; case MF_BRANCHNARGS: if (nargsdata.btable.count) { pc+=pc->data.btable.data[nargs]; } else pc+=pc->branch; break; case MF_BRANCHVALUETYPE: { if (!MORPHO_ISOBJECT(args[pc->narg])) { int type = (int) MORPHO_GETORDEREDTYPE(args[pc->narg]); if (typedata.btable.count) pc+=pc->data.btable.data[type]; } else pc+=pc->branch; } break; case MF_BRANCHOBJECTTYPE: { if (MORPHO_ISOBJECT(args[pc->narg])) { int type = MORPHO_GETOBJECTTYPE(args[pc->narg]); if (typedata.btable.count) pc+=pc->data.btable.data[type]; } else pc+=pc->branch; } break; case MF_BRANCHINSTANCE: { if (MORPHO_ISINSTANCE(args[pc->narg])) { // TODO: Check for btable bound objectclass *klass = MORPHO_GETINSTANCE(args[pc->narg])->klass; if (klass->uiddata.btable.count) pc+=pc->data.btable.data[klass->uid]; } else pc+=pc->branch; } break; case MF_RESOLVE: *out = pc->data.resolvefn; return true; case MF_FAIL: return false; } pc++; } while(true); } /* ********************************************************************** * Metafunction veneer class * ********************************************************************** */ /** Constructor function for Metafunctions */ value metafunction_constructor(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==0) return MORPHO_NIL; value name = _getname(MORPHO_GETARG(args, 0)); if (!MORPHO_ISSTRING(name)) return MORPHO_NIL; objectmetafunction *new = object_newmetafunction(name); if (new) { for (int i=0; ifns.count); } MORPHO_BEGINCLASS(Metafunction) MORPHO_METHOD(MORPHO_COUNT_METHOD, Metafunction_count, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectmetafunctiontype; void metafunction_initialize(void) { // Create function object type objectmetafunctiontype=object_addtype(&objectmetafunctiondefn); // Locate the Object class to use as the parent class of Metafunction objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Metafunction constructor function morpho_addfunction(METAFUNCTION_CLASSNAME, METAFUNCTION_CLASSNAME " (...)", metafunction_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // Create function veneer class value metafunctionclass=builtin_addclass(METAFUNCTION_CLASSNAME, MORPHO_GETCLASSDEFINITION(Metafunction), objclass); object_setveneerclass(OBJECT_METAFUNCTION, metafunctionclass); // Metafunction error messages morpho_defineerror(METAFUNCTION_CMPLAMBGS, ERROR_PARSE, METAFUNCTION_CMPLAMBGS_MSG); } ================================================ FILE: src/classes/metafunction.h ================================================ /** @file metafunction.h * @author T J Atherton * * @brief Defines metafunction object type and Metafunction veneer class */ #ifndef metafunction_h #define metafunction_h #include "object.h" /* ------------------------------------------------------- * Metafunction objects * ------------------------------------------------------- */ extern objecttype objectmetafunctiontype; #define OBJECT_METAFUNCTION objectmetafunctiontype /** Index type for metafunction resolver */ typedef int mfindx; /** Compiled metafunction instruction set */ typedef struct { int opcode; int narg; union { int tindx; value resolvefn; varray_int btable; } data; mfindx branch; /* Branch the pc by this amount on fail */ } mfinstruction; DECLARE_VARRAY(mfinstruction, mfinstruction); /** A metafunction object */ typedef struct sobjectmetafunction { object obj; value name; objectclass *klass; // Parent class for metafunction methods varray_value fns; varray_mfinstruction resolver; } objectmetafunction; /** Gets an objectmetafunction from a value */ #define MORPHO_GETMETAFUNCTION(val) ((objectmetafunction *) MORPHO_GETOBJECT(val)) /** Tests whether an object is a metafunction */ #define MORPHO_ISMETAFUNCTION(val) object_istype(val, OBJECT_METAFUNCTION) /* ------------------------------------------------------- * Metafunction veneer class * ------------------------------------------------------- */ #define METAFUNCTION_CLASSNAME "Metafunction" /* ------------------------------------------------------- * Metafunction error messages * ------------------------------------------------------- */ #define METAFUNCTION_CMPLAMBGS "MltplDisptchAmbg" #define METAFUNCTION_CMPLAMBGS_MSG "Ambiguous or duplicate implementations in multiple dispatch." /* ------------------------------------------------------- * Metafunction interface * ------------------------------------------------------- */ objectmetafunction *object_newmetafunction(value name); objectmetafunction *metafunction_clone(objectmetafunction *f); bool metafunction_wrap(value name, value fn, value *out); bool metafunction_add(objectmetafunction *f, value fn); bool metafunction_typefromvalue(value v, value *out); void metafunction_setclass(objectmetafunction *f, objectclass *klass); objectclass *metafunction_class(objectmetafunction *f); bool metafunction_matchfn(objectmetafunction *fn, value f); bool metafunction_matchset(objectmetafunction *fn, int n, value *fns); signature *metafunction_getsignature(value fn); bool metafunction_compile(objectmetafunction *fn, error *err); void metafunction_clearinstructions(objectmetafunction *fn); bool metafunction_resolve(objectmetafunction *f, int nargs, value *args, error *err, value *fn); void metafunction_initialize(void); #endif ================================================ FILE: src/classes/range.c ================================================ /** @file range.c * @author T J Atherton * * @brief Implements the Range class */ #include #include #include "morpho.h" #include "classes.h" /* ********************************************************************** * objectrange definitions * ********************************************************************** */ void objectrange_printfn(object *obj, void *v) { objectrange *r = (objectrange *) obj; morpho_printvalue(v, r->start); morpho_printf(v, (r->inclusive ? ".." : "...")); morpho_printvalue(v, r->end); if (!MORPHO_ISNIL(r->step)) { morpho_printf(v, ":"); morpho_printvalue(v, r->step); } } size_t objectrange_sizefn(object *obj) { return sizeof(objectrange); } objecttypedefn objectrangedefn = { .printfn=objectrange_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectrange_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Determine the number of steps in a range */ bool _range_count(objectrange *range) { range->nsteps=0; if (MORPHO_ISFLOAT(range->start)) { double diff=MORPHO_GETFLOATVALUE(range->end)-MORPHO_GETFLOATVALUE(range->start); double stp=(MORPHO_ISNIL(range->step) ? 1 : MORPHO_GETFLOATVALUE(range->step)); double cnt = floor(diff / stp); if (cnt>(double) INT_MAX) return false; if (isfinite(cnt)) range->nsteps = (int) cnt; if (range->inclusive) { if (MORPHO_ISEQUAL(range->start, range->end)) range->nsteps=1; else while (morpho_comparevalue(MORPHO_FLOAT(fabs(diff)), MORPHO_FLOAT(fabs(range->nsteps*stp)))<=0) range->nsteps++; } else { while (morpho_comparevalue(MORPHO_FLOAT(fabs(diff)), MORPHO_FLOAT(fabs(range->nsteps*stp)))<0) range->nsteps++; } } else { int diff=MORPHO_GETINTEGERVALUE(range->end)-MORPHO_GETINTEGERVALUE(range->start); int stp=(MORPHO_ISNIL(range->step) ? 1 : MORPHO_GETINTEGERVALUE(range->step)); if (stp) range->nsteps = diff / stp ; if (range->inclusive) { if (diff==0) range->nsteps=1; else if (range->nsteps*stp<=diff) range->nsteps++; } } if (range->nsteps < 0) range->nsteps=0; return true; } /** Create a new range. Step may be set to MORPHO_NIL to use the default value of 1. @param[out] errid - errid is filled in if the range can't be initialized */ objectrange *object_newrange(value start, value end, value step, bool inclusive, errorid *errid) { value v[3]={start, end, step}; /* Ensure all three values are either integer or floating point */ if (!value_promotenumberlist((MORPHO_ISNIL(step) ? 2 : 3), v)) { *errid = RANGE_ARGS; return NULL; } objectrange *new = (objectrange *) object_new(sizeof(objectrange), OBJECT_RANGE); if (new) { new->start=v[0]; new->end=v[1]; new->step=v[2]; new->inclusive=inclusive; if (!_range_count(new)) { *errid = RANGE_STPSZ; object_free((object *) new); new=NULL; } } else *errid = ERROR_ALLOCATIONFAILED; return new; } /* ********************************************************************** * objectrange utility functions * ********************************************************************** */ /** Return the number of steps in a range */ int range_count(objectrange *range) { return range->nsteps; } /** Find the ith value of a range object */ value range_iterate(objectrange *range, unsigned int i) { if (MORPHO_ISFLOAT(range->start)) { return MORPHO_FLOAT( MORPHO_GETFLOATVALUE(range->start) + i*(MORPHO_ISNIL(range->step) ? 1.0 : MORPHO_GETFLOATVALUE(range->step))); } else { return MORPHO_INTEGER( MORPHO_GETINTEGERVALUE(range->start) + i*(MORPHO_ISNIL(range->step) ? 1 : MORPHO_GETINTEGERVALUE(range->step))); } } /* ********************************************************************** * Range veneer class * ********************************************************************** */ static value _rangeconstructor(vm *v, int nargs, value *args, bool inclusive) { value out=MORPHO_NIL; objectrange *new=NULL; value in[3] = { MORPHO_NIL, MORPHO_NIL, MORPHO_NIL}; for (int i=0; insteps) return range_iterate(slf, n); else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } return MORPHO_SELF(args); } /** Enumerate members of a range */ value Range_enumerate(vm *v, int nargs, value *args) { objectrange *slf = MORPHO_GETRANGE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) return MORPHO_INTEGER(slf->nsteps); else return range_iterate(slf, n); } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Count number of items in a range */ value Range_count(vm *v, int nargs, value *args) { objectrange *slf = MORPHO_GETRANGE(MORPHO_SELF(args)); return MORPHO_INTEGER(slf->nsteps); } /** Clones a range */ value Range_clone(vm *v, int nargs, value *args) { value out = MORPHO_NIL; objectrange *slf = MORPHO_GETRANGE(MORPHO_SELF(args)); errorid errid=RANGE_ARGS; objectrange *new = object_newrange(slf->start, slf->end, slf->step, slf->inclusive, &errid); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, errid); return out; } MORPHO_BEGINCLASS(Range) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Range_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Range_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Range_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Range_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ objecttype objectrangetype; void range_initialize(void) { // Create range object type objectrangetype=object_addtype(&objectrangedefn); // Locate the Object class to use as the parent class of Range objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Create range veneer class value rangeclass=builtin_addclass(RANGE_CLASSNAME, MORPHO_GETCLASSDEFINITION(Range), objclass); object_setveneerclass(OBJECT_RANGE, rangeclass); // Range constructor function morpho_addfunction(RANGE_CLASSNAME, RANGE_CLASSNAME " (_,_)", range_constructor, MORPHO_FN_CONSTRUCTOR, NULL); morpho_addfunction(RANGE_CLASSNAME, RANGE_CLASSNAME " (_,_,_)", range_constructor, MORPHO_FN_CONSTRUCTOR, NULL); morpho_addfunction(RANGE_CLASSNAME, RANGE_CLASSNAME " (...)", range_invldconstructor, MORPHO_FN_CONSTRUCTOR, NULL); // Inclusive range constructor morpho_addfunction(RANGE_INCLUSIVE_CONSTRUCTOR, RANGE_CLASSNAME " (_,_)", range_inclusiveconstructor, MORPHO_FN_CONSTRUCTOR, NULL); morpho_addfunction(RANGE_INCLUSIVE_CONSTRUCTOR, RANGE_CLASSNAME " (_,_,_)", range_inclusiveconstructor, MORPHO_FN_CONSTRUCTOR, NULL); morpho_addfunction(RANGE_INCLUSIVE_CONSTRUCTOR, RANGE_CLASSNAME " (...)", range_invldconstructor, MORPHO_FN_CONSTRUCTOR, NULL); // Range error messages morpho_defineerror(RANGE_ARGS, ERROR_HALT, RANGE_ARGS_MSG); morpho_defineerror(RANGE_STPSZ, ERROR_HALT, RANGE_STPSZ_MSG); } ================================================ FILE: src/classes/range.h ================================================ /** @file range.h * @author T J Atherton * * @brief Defines range object type and Range class */ #ifndef range_h #define range_h #include "object.h" /* ------------------------------------------------------- * Range objects * ------------------------------------------------------- */ extern objecttype objectrangetype; #define OBJECT_RANGE objectrangetype typedef struct { object obj; unsigned int nsteps; value start; value end; value step; bool inclusive; } objectrange; /** Tests whether an object is a range */ #define MORPHO_ISRANGE(val) object_istype(val, OBJECT_RANGE) /** Gets the object as a range */ #define MORPHO_GETRANGE(val) ((objectrange *) MORPHO_GETOBJECT(val)) /** Creates a new range object */ objectrange *object_newrange(value start, value end, value step, bool inclusive, errorid *errid); /* ------------------------------------------------------- * Range veneer class * ------------------------------------------------------- */ #define RANGE_CLASSNAME "Range" #define RANGE_INCLUSIVE_CONSTRUCTOR "InclusiveRange" /* ------------------------------------------------------- * Range error messages * ------------------------------------------------------- */ #define RANGE_ARGS "RngArgs" #define RANGE_ARGS_MSG "Range expects numerical arguments: a start, an end and an optional stepsize." #define RANGE_STPSZ "RngStpSz" #define RANGE_STPSZ_MSG "Range stepsize too small." /* ------------------------------------------------------- * Range interface * ------------------------------------------------------- */ int range_count(objectrange *range); value range_iterate(objectrange *range, unsigned int i); void range_initialize(void); #endif ================================================ FILE: src/classes/strng.c ================================================ /** @file strng.c * @author T J Atherton * * @brief Defines string object type and String class */ #include #include "morpho.h" #include "classes.h" #include "lex.h" #include "common.h" /* ********************************************************************** * String objects * ********************************************************************** */ /** String object definitions */ void objectstring_printfn(object *obj, void *v) { morpho_printf(v, "%s", ((objectstring *) obj)->string); } size_t objectstring_sizefn(object *obj) { return sizeof(objectstring)+((objectstring *) obj)->length+1; } hash objectstring_hashfn(object *obj) { objectstring *str = (objectstring *) obj; return dictionary_hashcstring(str->string, str->length); } int objectstring_cmpfn(object *a, object *b) { objectstring *astring = (objectstring *) a; objectstring *bstring = (objectstring *) b; size_t len = (astring->length > bstring->length ? astring->length : bstring->length); return -strncmp(astring->string, bstring->string, len); } objecttypedefn objectstringdefn = { .printfn = objectstring_printfn, .markfn = NULL, .freefn = NULL, .sizefn = objectstring_sizefn, .hashfn = objectstring_hashfn, .cmpfn = objectstring_cmpfn }; /** @brief Creates a string from an existing character array with given length * @param in the string to copy * @param length length of string to copy * @returns the object (as a value) which will be MORPHO_NIL on failure */ value object_stringfromcstring(const char *in, size_t length) { value out = MORPHO_NIL; objectstring *new = (objectstring *) object_new(sizeof(objectstring) + sizeof(char) * (length + 1), OBJECT_STRING); if (new) { new->string=new->stringdata; if (in) { memcpy(new->string, in, length); } else { memset(new->string, 0, length); } new->string[length] = '\0'; /* Zero terminate the string to be compatible with C */ new->length=strlen(new->string); out = MORPHO_OBJECT(new); } return out; } /** @brief Creates a string with given length * @param length length of string to allocate * @returns the object (as a value) which will be MORPHO_NIL on failure */ objectstring *object_stringwithsize(size_t length) { objectstring *new = (objectstring *) object_new(sizeof(objectstring) + sizeof(char) * (length + 1), OBJECT_STRING); if (new) { new->string=new->stringdata; new->string[length] = '\0'; // Ensure pre-null terminated memset(new->string, 0, length); new->length=length; return new; } return NULL; } /** @brief Converts a varray_char into a string. * @param in the varray to convert * @returns the object (as a value) which will be MORPHO_NIL on failure */ value object_stringfromvarraychar(varray_char *in) { return object_stringfromcstring(in->data, in->count); } /* Clones a string object */ value object_clonestring(value val) { value out = MORPHO_NIL; if (MORPHO_ISSTRING(val)) { objectstring *s = MORPHO_GETSTRING(val); out=object_stringfromcstring(s->string, s->length); } return out; } /** @brief Concatenates strings together * @param a first string * @param b second string * @returns the object (as a value) which will be MORPHO_NIL on failure */ value object_concatenatestring(value a, value b) { objectstring *astring = MORPHO_GETSTRING(a); objectstring *bstring = MORPHO_GETSTRING(b); size_t length = (astring ? astring->length : 0) + (bstring ? bstring->length : 0); value out = MORPHO_NIL; objectstring *new = (objectstring *) object_new(sizeof(objectstring) + sizeof(char) * (length + 1), OBJECT_STRING); if (new) { new->string=new->stringdata; new->length=length; /* Copy across old strings */ if (astring) memcpy(new->string, astring->string, astring->length); if (bstring) memcpy(new->string+(astring ? astring->length : 0), bstring->string, bstring->length); new->string[length]='\0'; out = MORPHO_OBJECT(new); } return out; } /* ********************************************************************** * String utility functions * ********************************************************************** */ /** Convert a string to a number */ bool string_tonumber(objectstring *string, value *out) { bool minus=false; lexer l; token tok; error err; error_init(&err); lex_init(&l, string->string, 0); if (lex(&l, &tok, &err)) { if (tok.type==TOKEN_MINUS) { // Check for leading minus minus=true; if (!lex(&l, &tok, &err)) return false; } else if (tok.type==TOKEN_PLUS) { // or plus if (!lex(&l, &tok, &err)) return false; } if (tok.type==TOKEN_INTEGER) { long i = strtol(tok.start, NULL, 10); if (minus) i=-i; *out = MORPHO_INTEGER((int) i); return true; } else if (tok.type==TOKEN_NUMBER) { double f = strtod(tok.start, NULL); if (minus) f=-f; *out = MORPHO_FLOAT(f); return true; } } lex_clear(&l); return false; } /** Count number of characters in a string */ int string_countchars(objectstring *s) { int n=0; for (char *c = s->string; *c!='\0'; ) { c+=morpho_utf8numberofbytes(c); n++; } return n; } /** Get a pointer to the i'th character of a string */ char *string_index(objectstring *s, int i) { int n=0; for (char *c = s->string; *c!='\0'; ) { if (i==n) return (char *) c; c+=morpho_utf8numberofbytes(c); n++; } return NULL; } /* ********************************************************************** * String class * ********************************************************************** */ /** Constructor function for strings */ value string_constructor(vm *v, int nargs, value *args) { value out=morpho_concatenate(v, nargs, args+1); if (MORPHO_ISOBJECT(out)) morpho_bindobjects(v, 1, &out); return out; } /** Find a string's length */ value String_count(vm *v, int nargs, value *args) { objectstring *slf = MORPHO_GETSTRING(MORPHO_SELF(args)); return MORPHO_INTEGER(string_countchars(slf)); } /** Prints a string */ value String_print(vm *v, int nargs, value *args) { morpho_printvalue(v, MORPHO_SELF(args)); return MORPHO_SELF(args); } /** Clones a string */ value String_clone(vm *v, int nargs, value *args) { objectstring *slf = MORPHO_GETSTRING(MORPHO_SELF(args)); value out = object_stringfromcstring(slf->string, slf->length); if (MORPHO_ISNIL(out)) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); morpho_bindobjects(v, 1, &out); return out; } /** Enumerate members of a string */ value String_enumerate(vm *v, int nargs, value *args) { objectstring *slf = MORPHO_GETSTRING(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) { out=MORPHO_INTEGER(string_countchars(slf)); } else { char *c=string_index(slf, n); if (c) { out=object_stringfromcstring(c, morpho_utf8numberofbytes(c)); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Tests if a string encodes a number */ value String_isnumber(vm *v, int nargs, value *args) { objectstring *slf = MORPHO_GETSTRING(MORPHO_SELF(args)); value out=MORPHO_NIL; if (string_tonumber(slf, &out)) return MORPHO_TRUE; return MORPHO_FALSE; } /** Splits a string */ value String_split(vm *v, int nargs, value *args) { objectstring *slf = MORPHO_GETSTRING(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { objectstring *split = MORPHO_GETSTRING(MORPHO_GETARG(args, 0)); objectlist *new = object_newlist(0, NULL); if (!new) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return MORPHO_NIL; } char *last = slf->string; for (char *c = slf->string; *c!='\0'; c+=morpho_utf8numberofbytes(c)) { // Loop over string for (char *s = split->string; *s!='\0';) { // Loop over split chars int nbytes = morpho_utf8numberofbytes(s); if (strncmp(c, s, nbytes)==0) { value newstring = object_stringfromcstring(last, c-last); if (MORPHO_ISNIL(newstring)) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); list_append(new, newstring); last=c+nbytes; } s+=nbytes; } } value newstring = object_stringfromcstring(last, slf->string+slf->length-last); if (MORPHO_ISNIL(newstring)) morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); list_append(new, newstring); out=MORPHO_OBJECT(new); list_append(new, out); morpho_bindobjects(v, new->val.count, new->val.data); new->val.count-=1; } return out; } MORPHO_BEGINCLASS(String) MORPHO_METHOD(MORPHO_COUNT_METHOD, String_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, String_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, String_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_GETINDEX_METHOD, String_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, String_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(STRING_ISNUMBER_METHOD, String_isnumber, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(STRING_SPLIT_METHOD, String_split, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ objecttype objectstringtype; void string_initialize(void) { // Create string object type //objectstringtype=object_addtype(&objectstringdefn); // Locate the Object class to use as the parent class of Range objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Create String veneer class value stringclass=builtin_addclass(STRING_CLASSNAME, MORPHO_GETCLASSDEFINITION(String), objclass); object_setveneerclass(OBJECT_STRING, stringclass); // String constructor function morpho_addfunction(STRING_CLASSNAME, STRING_CLASSNAME " (...)", string_constructor, MORPHO_FN_CONSTRUCTOR, NULL); } ================================================ FILE: src/classes/strng.h ================================================ /** @file strng.h * @author T J Atherton * * @brief Defines string object type and String class */ #ifndef strng_h #define strng_h #include #include "object.h" /* ------------------------------------------------------- * String object type * ------------------------------------------------------- */ extern objecttype objectstringtype; #define OBJECT_STRING objectstringtype /** A string object */ typedef struct { object obj; size_t length; char *string; char stringdata[]; } objectstring; /** Tests whether an object is a string */ #define MORPHO_ISSTRING(val) object_istype(val, OBJECT_STRING) /** Extracts the objectstring from a value */ #define MORPHO_GETSTRING(val) ((objectstring *) MORPHO_GETOBJECT(val)) /** Extracts a C string from a value */ #define MORPHO_GETCSTRING(val) (((objectstring *) MORPHO_GETOBJECT(val))->string) /** Extracts the string length from a value */ #define MORPHO_GETSTRINGLENGTH(val) (((objectstring *) MORPHO_GETOBJECT(val))->length) /** Use to create static strings on the C stack */ #define MORPHO_STATICSTRING(cstring) { .obj.type=OBJECT_STRING, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .string=cstring, .length=strlen(cstring) } /** Use to create static strings on the C stack */ #define MORPHO_STATICSTRINGWITHLENGTH(cstring, len) { .obj.type=OBJECT_STRING, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .string=cstring, .length=len } #define OBJECT_STRINGLABEL "string" // These are only used by the parser... Should be moved? #define OBJECT_SYMBOLLABEL "symbol" /** Create a string object from a C string */ value object_stringfromcstring(const char *in, size_t length); /** Create an empty string of specified size */ objectstring *object_stringwithsize(size_t length); /** Create a string object from a character varray */ value object_stringfromvarraychar(varray_char *in); /** Clone a string */ value object_clonestring(value val); /** Concatenate two strings */ value object_concatenatestring(value a, value b); /* ------------------------------------------------------- * String veneer class * ------------------------------------------------------- */ #define STRING_CLASSNAME "String" #define STRING_SPLIT_METHOD "split" #define STRING_ISNUMBER_METHOD "isnumber" /* ------------------------------------------------------- * String error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * String interface * ------------------------------------------------------- */ bool string_tonumber(objectstring *string, value *out); int string_countchars(objectstring *s); char *string_index(objectstring *s, int i); void string_initialize(void); #endif ================================================ FILE: src/classes/system.c ================================================ /** @file system.c * @author T J Atherton * * @brief Defines System class to provide access to the runtime and system */ #include #include "morpho.h" #include "classes.h" #include "system.h" #include "platform.h" /* ********************************************************************** * System utility functions * ********************************************************************** */ /** Set arguments passed to morpho program */ static value arglist; /** Set arguments with which the host program was called with */ void morpho_setargs(int argc, const char * argv[]) { if (!MORPHO_ISLIST(arglist)) return; objectlist *alist = MORPHO_GETLIST(arglist); for (int i=0; i #include "morpho.h" /* ------------------------------------------------------- * System class * ------------------------------------------------------- */ #define SYSTEM_CLASSNAME "System" #define SYSTEM_PLATFORM_METHOD "platform" #define SYSTEM_VERSION_METHOD "version" #define SYSTEM_CLOCK_METHOD "clock" #define SYSTEM_READLINE_METHOD "readline" #define SYSTEM_SLEEP_METHOD "sleep" #define SYSTEM_ARGUMENTS_METHOD "arguments" #define SYSTEM_EXIT_METHOD "exit" #define SYSTEM_HOMEFOLDER_METHOD "homefolder" #define SYSTEM_WORKINGFOLDER_METHOD "workingfolder" #define SYSTEM_SETWORKINGFOLDER_METHOD "setworkingfolder" /* ------------------------------------------------------- * System error messages * ------------------------------------------------------- */ #define SLEEP_ARGS "SystmSlpArgs" #define SLEEP_ARGS_MSG "Sleep method expects a time in seconds." #define STWRKDR_ARGS "SystmStWrkDrArgs" #define STWRKDR_ARGS_MSG "Setworkingdirectory method expects a path name." #define SYS_STWRKDR "SystmStWrkDr" #define SYS_STWRKDR_MSG "Couldn't set working directory." void system_initialize(void); void system_finalize(void); #endif /* system_h */ ================================================ FILE: src/classes/tuple.c ================================================ /** @file tuple.c * @author T J Atherton * * @brief Defines tuple object type and Tuple class */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * Tuple objects * ********************************************************************** */ /** Tuple object definitions */ void objecttuple_printfn(object *obj, void *v) { objecttuple *t = (objecttuple *) obj; morpho_printf(v, "("); for (unsigned int i=0; ilength; i++) { morpho_printvalue(v, t->tuple[i]); if (ilength-1) morpho_printf(v, ", "); } morpho_printf(v, ")"); } void objecttuple_markfn(object *obj, void *v) { objecttuple *t = (objecttuple *) obj; for (unsigned int i=0; ilength; i++) morpho_markvalue(v, t->tuple[i]); } size_t objecttuple_sizefn(object *obj) { return sizeof(objecttuple)+(((objecttuple *) obj)->length)*sizeof(value); } hash objecttuple_hashfn(object *obj) { objecttuple *tuple = (objecttuple *) obj; return dictionary_hashvaluelist(tuple->length, tuple->tuple); } int objecttuple_cmpfn(object *a, object *b) { objecttuple *atuple = (objecttuple *) a; objecttuple *btuple = (objecttuple *) b; if (atuple->length!=btuple->length) return MORPHO_NOTEQUAL; int cmp=0; for (unsigned int i=0; ilength && cmp==0; i++) { cmp=morpho_comparevalue(atuple->tuple[i], btuple->tuple[i]); } return cmp; } objecttypedefn objecttupledefn = { .printfn = objecttuple_printfn, .markfn = objecttuple_markfn, .freefn = NULL, .sizefn = objecttuple_sizefn, .hashfn = objecttuple_hashfn, .cmpfn = objecttuple_cmpfn }; /** @brief Creates a tuple from an existing C array of values * @param length length of list * @param in list of values * @returns the object or NULL on failure */ objecttuple *object_newtuple(unsigned int length, value *in) { objecttuple *new = (objecttuple *) object_new(sizeof(objecttuple) + sizeof(value)*length, OBJECT_TUPLE); if (new) { new->tuple=new->tupledata; new->length=length; if (in) memcpy(new->tuple, in, sizeof(value)*length); else for (unsigned int i=0; ituple[i]=MORPHO_NIL; } return new; } /* ********************************************************************** * Tuple interface * ********************************************************************** */ /** Returns the length of a tuple */ unsigned int tuple_length(objecttuple *tuple) { return tuple->length; } /** Gets an element from the tuple */ bool tuple_getelement(objecttuple *tuple, int i, value *out) { if (!(i>=-(int) tuple->length && i<(int) tuple->length)) return false; if (i>=0) *out=tuple->tuple[i]; else *out=tuple->tuple[tuple->length+i]; return true; } /** Tests if a value is a member of a list */ bool tuple_ismember(objecttuple *tuple, value v) { for (unsigned int i=0; ilength; i++) { if (MORPHO_ISEQUAL(tuple->tuple[i], v)) return true; } return false; } /** Concatenates two tuples */ objecttuple *tuple_concatenate(objecttuple *a, objecttuple *b) { unsigned int newlength = a->length+b->length; objecttuple *new=object_newtuple(newlength, NULL); if (new) { memcpy(new->tuple, a->tuple, sizeof(value)*a->length); memcpy(new->tuple + a->length, b->tuple, sizeof(value)*b->length); new->length=newlength; } return new; } /* ------------------------------------------------------- * Slicing * ------------------------------------------------------- */ /* Constructs a new list of a given size with a generic interface */ void tuple_sliceconstructor(unsigned int *slicesize, unsigned int ndim, value *out){ objecttuple *tuple = object_newtuple(slicesize[0], NULL); *out = MORPHO_OBJECT(tuple); } /* Return number of dimensions to slice-there's only one */ bool tuple_slicedim(value *a, unsigned int ndim){ if (ndim>1||ndim<0) return false; return true; } /* Copies data from tuple a at position indx to tuple out at position newindx with a generic interface */ objectarrayerror tuple_slicecopy(value *a,value *out, unsigned int ndim, unsigned int *indx, unsigned int *newindx){ value data; objecttuple *outtuple = MORPHO_GETTUPLE(*out); if (tuple_getelement(MORPHO_GETTUPLE(*a),indx[0],&data)){ outtuple->tuple[newindx[0]] = data; } else return ARRAY_OUTOFBOUNDS; return ARRAY_OK; } /** Converts an array error into an list error code for use in slices*/ errorid array_to_tuple_error(objectarrayerror err) { switch (err) { case ARRAY_OUTOFBOUNDS: return VM_OUTOFBOUNDS; case ARRAY_WRONGDIM: return TUPLE_NUMARGS; case ARRAY_NONINTINDX: return TUPLE_ARGS; case ARRAY_ALLOC_FAILED: return ERROR_ALLOCATIONFAILED; case ARRAY_OK: UNREACHABLE("array_to_tuple_error called incorrectly."); } UNREACHABLE("Unhandled array error."); return VM_OUTOFBOUNDS; } /* ********************************************************************** * Tuple class * ********************************************************************** */ /** Constructor function for tuples */ value tuple_constructor(vm *v, int nargs, value *args) { value out = MORPHO_NIL; objecttuple *new=object_newtuple(nargs, & MORPHO_GETARG(args, 0)); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Find a tuple's length */ value Tuple_count(vm *v, int nargs, value *args) { objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); return MORPHO_INTEGER(slf->length); } /** Clones a tuple */ value Tuple_clone(vm *v, int nargs, value *args) { value out = MORPHO_NIL; objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); objecttuple *new = object_newtuple(slf->length, slf->tuple); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Get an element */ value Tuple_getindex(vm *v, int nargs, value *args) { objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (!tuple_getelement(slf, i, &out)) morpho_runtimeerror(v, VM_OUTOFBOUNDS); } else { objectarrayerror err = getslice(&MORPHO_SELF(args), tuple_slicedim, tuple_sliceconstructor, tuple_slicecopy, nargs, &MORPHO_GETARG(args, 0), &out); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_to_tuple_error(err) ); if (MORPHO_ISOBJECT(out)){ morpho_bindobjects(v,1,&out); } else MORPHO_RAISE(v, VM_NONNUMINDX); } } else MORPHO_RAISE(v, LIST_NUMARGS) return out; } /** Setindex just raises an error */ value Tuple_setindex(vm *v, int nargs, value *args) { morpho_runtimeerror(v, OBJECT_IMMUTABLE); return MORPHO_NIL; } /** Enumerate members of a tuple */ value Tuple_enumerate(vm *v, int nargs, value *args) { objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int n=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (n<0) { out=MORPHO_INTEGER(slf->length); } else { if (nlength) { out=slf->tuple[n]; } else morpho_runtimeerror(v, VM_OUTOFBOUNDS); } } else MORPHO_RAISE(v, ENUMERATE_ARGS); return out; } /** Joins two tuples together */ value Tuple_join(vm *v, int nargs, value *args) { objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISTUPLE(MORPHO_GETARG(args, 0))) { objecttuple *operand = MORPHO_GETTUPLE(MORPHO_GETARG(args, 0)); objecttuple *new = tuple_concatenate(slf, operand); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, LIST_ADDARGS); return out; } /** Tests if a tuple has a value as a member */ value Tuple_ismember(vm *v, int nargs, value *args) { objecttuple *slf = MORPHO_GETTUPLE(MORPHO_SELF(args)); if (nargs==1) { return MORPHO_BOOL(tuple_ismember(slf, MORPHO_GETARG(args, 0))); } else morpho_runtimeerror(v, ISMEMBER_ARG, 1, nargs); return MORPHO_NIL; } MORPHO_BEGINCLASS(Tuple) MORPHO_METHOD(MORPHO_COUNT_METHOD, Tuple_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Object_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Tuple_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Tuple_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Tuple_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Tuple_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_JOIN_METHOD, Tuple_join, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(LIST_ISMEMBER_METHOD, Tuple_ismember, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CONTAINS_METHOD, Tuple_ismember, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ objecttype objecttupletype; void tuple_initialize(void) { // Create tuple object type objecttupletype=object_addtype(&objecttupledefn); // Locate the Object class to use as the parent class of Range objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); // Create tuple veneer class value tupleclass=builtin_addclass(TUPLE_CLASSNAME, MORPHO_GETCLASSDEFINITION(Tuple), objclass); object_setveneerclass(OBJECT_TUPLE, tupleclass); // Tuple constructor function morpho_addfunction(TUPLE_CLASSNAME, TUPLE_CLASSNAME " (...)", tuple_constructor, MORPHO_FN_CONSTRUCTOR, NULL); // Tuple error messages morpho_defineerror(TUPLE_ARGS, ERROR_HALT, TUPLE_ARGS_MSG); morpho_defineerror(TUPLE_NUMARGS, ERROR_HALT, TUPLE_NUMARGS_MSG); } ================================================ FILE: src/classes/tuple.h ================================================ /** @file tuple.h * @author T J Atherton * * @brief Defines tuple object type and Tuple class */ #ifndef tuple_h #define tuple_h #include "object.h" /* ------------------------------------------------------- * Tuple object type * ------------------------------------------------------- */ extern objecttype objecttupletype; #define OBJECT_TUPLE objecttupletype /** A string object */ typedef struct { object obj; unsigned int length; value *tuple; value tupledata[]; } objecttuple; /** Tests whether an object is a tuple */ #define MORPHO_ISTUPLE(val) object_istype(val, OBJECT_TUPLE) /** Extracts the objecttuple from a value */ #define MORPHO_GETTUPLE(val) ((objecttuple *) MORPHO_GETOBJECT(val)) /** Extracts the length from a value */ #define MORPHO_GETTUPLELENGTH(val) ((MORPHO_GETTUPLE(val))->length) /** Extracts the value list from a value */ #define MORPHO_GETTUPLEVALUES(val) ((MORPHO_GETTUPLE(val))->tuple) /** Use to create static tuples on the C stack */ #define MORPHO_STATICTUPLE(list, len) { .obj.type=OBJECT_TUPLE, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .tuple=list, .length=len } /* ------------------------------------------------------- * Tuple veneer class * ------------------------------------------------------- */ #define TUPLE_CLASSNAME "Tuple" /* ------------------------------------------------------- * Tuple error messages * ------------------------------------------------------- */ #define TUPLE_ARGS "TplArgs" #define TUPLE_ARGS_MSG "Tuples must be called with integer dimensions as arguments." #define TUPLE_NUMARGS "TpmNumArgs" #define TUPLE_NUMARGS_MSG "Tuples can only be indexed with one argument." /* ------------------------------------------------------- * Tuple interface * ------------------------------------------------------- */ /** Create a tuple with an (optional) list of values */ objecttuple *object_newtuple(unsigned int length, value *v); unsigned int tuple_length(objecttuple *tuple); bool tuple_getelement(objecttuple *tuple, int i, value *out); void tuple_initialize(void); #endif ================================================ FILE: src/classes/upvalue.c ================================================ /** @file upvalue.c * @author T J Atherton * * @brief Implements upvalue object type */ #include "morpho.h" #include "classes.h" /* ********************************************************************** * objectupvalue definitions * ********************************************************************** */ /** Upvalue object definitions */ void objectupvalue_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectupvalue_markfn(object *obj, void *v) { morpho_markvalue(v, ((objectupvalue *) obj)->closed); } size_t objectupvalue_sizefn(object *obj) { return sizeof(objectupvalue); } objecttypedefn objectupvaluedefn = { .printfn=objectupvalue_printfn, .markfn=objectupvalue_markfn, .freefn=NULL, .sizefn=objectupvalue_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Initializes a new upvalue object. */ void object_upvalueinit(objectupvalue *c) { object_init(&c->obj, OBJECT_UPVALUE); c->location=NULL; c->closed=MORPHO_NIL; c->next=NULL; } /* ********************************************************************** * objectupvalue utility functions * ********************************************************************** */ /** Creates a new upvalue for the register pointed to by reg. */ objectupvalue *object_newupvalue(value *reg) { objectupvalue *new = (objectupvalue *) object_new(sizeof(objectupvalue), OBJECT_UPVALUE); if (new) { object_upvalueinit(new); new->location=reg; } return new; } /* ********************************************************************** * Initialization and finalization * ********************************************************************** */ DEFINE_VARRAY(upvalue, upvalue); DEFINE_VARRAY(varray_upvalue, varray_upvalue); objecttype objectupvaluetype; void upvalue_initialize(void) { // Define upvalue object type objectupvaluetype=object_addtype(&objectupvaluedefn); } ================================================ FILE: src/classes/upvalue.h ================================================ /** @file upvalue.h * @author T J Atherton * * @brief Defines upvalue object type */ #ifndef upvalue_h #define upvalue_h #include "object.h" /** Upvalues are used by the virtual machine to implement closures; they are not visible to the user */ /* ------------------------------------------------------- * Upvalue structure * ------------------------------------------------------- */ /** An upvalue descriptor */ typedef struct { bool islocal; /** Set if the upvalue is local to this function */ indx reg; /** An index that either: if islocal - refers to the register OR otherwise - refers to the upvalue array in the current closure */ } upvalue; DECLARE_VARRAY(upvalue, upvalue) DECLARE_VARRAY(varray_upvalue, varray_upvalue) /* --------------------------- * Upvalue objects * --------------------------- */ extern objecttype objectupvaluetype; #define OBJECT_UPVALUE objectupvaluetype typedef struct sobjectupvalue { object obj; value* location; /** Pointer to the location of the upvalue */ value closed; /** Closed value of the upvalue */ struct sobjectupvalue *next; } objectupvalue; void object_upvalueinit(objectupvalue *c); objectupvalue *object_newupvalue(value *reg); /** Gets an upvalue from a value */ #define MORPHO_GETUPVALUE(val) ((objectupvalue *) MORPHO_GETOBJECT(val)) /** Tests whether an object is an upvalue */ #define MORPHO_ISUPVALUE(val) object_istype(val, object_upvaluetype) /* ------------------------------------------------------- * Upvalue error messages * ------------------------------------------------------- */ /* ------------------------------------------------------- * Upvalue interface * ------------------------------------------------------- */ /* Initialization/finalization */ void upvalue_initialize(void); #endif ================================================ FILE: src/core/CMakeLists.txt ================================================ target_sources(morpho PRIVATE compile.c compile.h gc.c gc.h vm.c vm.h core.h opcodes.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES compile.h gc.h optimize.h vm.h core.h opcodes.h ) ================================================ FILE: src/core/compile.c ================================================ /** @file compile.c * @author T J Atherton * * @brief Compiles raw input to Morpho instructions */ #include #include #include "compile.h" #include "error.h" #include "vm.h" #include "morpho.h" #include "classes.h" #include "file.h" #include "resources.h" #include "extensions.h" /** Base class for instances */ static objectclass *baseclass; static optimizerfn *optimizer; /* ********************************************************************** * Bytecode compiler * ********************************************************************** */ /* ------------------------------------------ * Utility functions * ------------------------------------------- */ /** Checks if the compiler is in an error state */ static bool compiler_haserror(compiler *c) { return (c->err.cat!=ERROR_NONE); } /** @brief Fills out the error record * @param c the compiler * @param node the node the error occurred at * @param id error id * @param ... additional data for sprintf. */ static void compiler_error(compiler *c, syntaxtreenode *node, errorid id, ... ) { if (c->err.cat!=ERROR_NONE) return; // Ensure errors are not overwritten. va_list args; int line = (node ? node->line : ERROR_POSNUNIDENTIFIABLE); int posn = (node ? node->posn : ERROR_POSNUNIDENTIFIABLE); char *file = (MORPHO_ISSTRING(c->currentmodule) ? MORPHO_GETCSTRING(c->currentmodule) : NULL); va_start(args, id); morpho_writeerrorwithidvalist(&c->err, id, file, line, posn, args); va_end(args); } /** Returns true if the compiler has encountered an error */ static bool compiler_checkerror(compiler *c) { return (c->err.cat!=ERROR_NONE); // Ensure errors are not overwritten. } /** @brief Catches a compiler error, resetting the errror state to none. * @param c the compiler * @param id error id to match * @returns true if the error was matched */ static bool compiler_catch(compiler *c, errorid id) { if (morpho_matcherror(&c->err, id)) { error_clear(&c->err); return true; } return false; } /** Gets a node given an index */ static inline syntaxtreenode *compiler_getnode(compiler *c, syntaxtreeindx indx) { if (indx==SYNTAXTREE_UNCONNECTED) return NULL; return c->tree.tree.data+indx; } /** Gets a node given an index */ static inline syntaxtree *compiler_getsyntaxtree(compiler *c) { return &c->tree; } /** Adds an instruction to the current program */ static instructionindx compiler_addinstruction(compiler *c, instruction instr, syntaxtreenode *node) { debugannotation_addnode(&c->out->annotations, node); return varray_instructionwrite(&c->out->code, instr); } /** Gets the instruction index of the current instruction */ static instructionindx compiler_currentinstructionindex(compiler *c) { return (instructionindx) c->out->code.count; } /** Modifies the instruction at a given index */ static void compiler_setinstruction(compiler *c, instructionindx indx, instruction instr) { c->out->code.data[indx]=instr; } /* ------------------------------------------ * The functionstate stack * ------------------------------------------- */ DEFINE_VARRAY(registeralloc, registeralloc); DEFINE_VARRAY(forwardreference, forwardreference); /** Initializes a functionstate structure */ static void compiler_functionstateinit(functionstate *state) { state->func=NULL; state->scopedepth=0; state->loopdepth=0; state->inargs=false; state->nreg=0; state->type=FUNCTION; state->varg=REGISTER_UNALLOCATED; varray_registerallocinit(&state->registers); varray_forwardreferenceinit(&state->forwardref); varray_upvalueinit(&state->upvalues); varray_functionrefinit(&state->functionref); } /** Clears a functionstate structure */ static void compiler_functionstateclear(functionstate *state) { state->func=NULL; state->scopedepth=0; state->loopdepth=0; state->nreg=0; varray_registerallocclear(&state->registers); varray_forwardreferenceclear(&state->forwardref); varray_upvalueclear(&state->upvalues); varray_functionrefclear(&state->functionref); } /** Initializes the function stack */ void compiler_fstackinit(compiler *c) { for (unsigned int i=0; ifstack[i]); } c->fstackp=0; } /** Clears the function stack */ void compiler_fstackclear(compiler *c) { for (unsigned int i=0; ifstack[i]); } c->fstackp=0; } /** Gets the current functionstate structure */ static inline functionstate *compiler_currentfunctionstate(compiler *c) { return c->fstack+c->fstackp; } /** The parent functionstate */ static inline functionstate *compiler_parentfunctionstate(compiler *c) { if (c->fstackp==0) return NULL; return c->fstack+c->fstackp-1; } /** Detect if we're currently compiling an initializer */ static inline bool compiler_ininitializer(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); return FUNCTIONTYPE_ISINITIALIZER(f->type); } /* ------------------------------------------ * Increment and decrement the fstack * ------------------------------------------- */ /** Begins a new function, advancing the fstack pointer */ void compiler_beginfunction(compiler *c, objectfunction *func, functiontype type) { c->fstackp++; compiler_functionstateinit(&c->fstack[c->fstackp]); c->fstack[c->fstackp].func=func; c->fstack[c->fstackp].type=type; debugannotation_setfunction(&c->out->annotations, func); } /** Sets the function register count */ void compiler_setfunctionregistercount(compiler *c) { functionstate *f=&c->fstack[c->fstackp]; if (f->nreg>f->func->nregs) f->func->nregs=f->nreg; } /** Ends a function, decrementing the fstack pointer */ void compiler_endfunction(compiler *c) { functionstate *f=&c->fstack[c->fstackp]; c->prevfunction=f->func; /* Retain the function in case it needs to be bound as a method */ compiler_setfunctionregistercount(c); compiler_functionstateclear(f); c->fstackp--; debugannotation_setfunction(&c->out->annotations, c->fstack[c->fstackp].func); } /** Gets the current function */ objectfunction *compiler_getcurrentfunction(compiler *c) { return c->fstack[c->fstackp].func; } /** Gets the current constant table */ varray_value *compiler_getcurrentconstanttable(compiler *c) { objectfunction *f = compiler_getcurrentfunction(c); if (!f) { UNREACHABLE("find current constant table [No current function defined]."); } return &f->konst; } /** Gets constant i from the current constant table */ value compiler_getconstant(compiler *c, unsigned int i) { value ret = MORPHO_NIL; objectfunction *f = compiler_getcurrentfunction(c); if (f && ikonst.count) ret = f->konst.data[i]; return ret; } /** Gets the most recently compiled function */ objectfunction *compiler_getpreviousfunction(compiler *c) { return c->prevfunction; } /* ------------------------------------------ * Types * ------------------------------------------- */ value _closuretype; /* ------------------------------------------ * Argument declarations * ------------------------------------------- */ /** Begins arguments */ static inline void compiler_beginargs(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); if (f) { f->inargs=true; } } /** Ends arguments */ static inline void compiler_endargs(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); if (f) f->inargs=false; } /** Are we in an args statement? */ static inline bool compiler_inargs(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); return f->inargs; } /* ------------------------------------------ * Class declarations * ------------------------------------------- */ static inline void compiler_beginclass(compiler *c, objectclass *klass) { /* If we're already compiling a class, retain it in a linked list */ if (c->currentclass) klass->obj.next = (object *) klass; c->currentclass=klass; debugannotation_setclass(&c->out->annotations, klass); } static inline void compiler_endclass(compiler *c) { /* Delink current class from list */ objectclass *current = c->currentclass; c->currentclass=(objectclass *) current->obj.next; debugannotation_setclass(&c->out->annotations, c->currentclass); current->obj.next=NULL; /* as the class is no longer part of the list */ } /** Gets the current class */ static objectclass *compiler_getcurrentclass(compiler *c) { return c->currentclass; } /** Adds an objectclass to the compilers dictionary of classes */ void compiler_addclass(compiler *c, objectclass *klass) { klass->uid = program_addclass(c->out, MORPHO_OBJECT(klass)); dictionary_insert(&c->classes, klass->name, MORPHO_OBJECT(klass)); } /** Finds a class in the compiler's dictionary of classes */ objectclass *compiler_findclass(compiler *c, value name) { value val; if (dictionary_get(&c->classes, name, &val) && MORPHO_ISCLASS(val)) return MORPHO_GETCLASS(val); if (c->parent) return compiler_findclass(c->parent, name); return NULL; } /** Adds a class to a class's parent list, and also links the class into the parent's child list */ void compiler_addparent(compiler *c, objectclass *klass, objectclass *parent) { varray_valuewrite(&klass->parents, MORPHO_OBJECT(parent)); varray_valuewrite(&parent->children, MORPHO_OBJECT(klass)); } /* ------------------------------------------ * Types * ------------------------------------------- */ /** Identifies a type from a label */ bool compiler_findtype(compiler *c, value label, value *out) { value type=MORPHO_NIL; objectclass *clss=compiler_findclass(c, label); // A class we defined if (clss) { type = MORPHO_OBJECT(clss); } else type = builtin_findclass(label); // Or a built in one if (!MORPHO_ISNIL(type)) *out = type; return (!MORPHO_ISNIL(type)); } /** Identify a type from a label */ bool compiler_findtypefromcstring(compiler *c, char *label, value *out) { objectstring str = MORPHO_STATICSTRING(label); return compiler_findtype(c, MORPHO_OBJECT(&str), out); } /** Identifies a type from a value */ bool compiler_typefromvalue(compiler *c, value v, value *out) { return metafunction_typefromvalue(v, out); } /** Recursively searches the parents list of classes to see if the type 'match' is present */ bool compiler_findtypeinparent(compiler *c, objectclass *type, value match) { for (int i=0; iparents.count; i++) { if (MORPHO_ISEQUAL(type->parents.data[i], match) || compiler_findtypeinparent(c, MORPHO_GETCLASS(type->parents.data[i]), match)) return true; } return false; } /** Checks if type "match" matches a given type "type" */ bool compiler_checktype(compiler *c, value type, value match) { if (MORPHO_ISNIL(type) || // If type is unset, we always match MORPHO_ISEQUAL(type, match)) return true; // Or if the types are the same // Also match if 'match' inherits from 'type' if (MORPHO_ISCLASS(match)) return compiler_findtypeinparent(c, MORPHO_GETCLASS(match), type); return false; } /** Determines the type associated with a constant */ bool compiler_getconstanttype(compiler *c, unsigned int i, value *type) { value val = compiler_getconstant(c, i); return compiler_typefromvalue(c, val, type); } /* ------------------------------------------ * Modules * ------------------------------------------- */ /** Sets the current module */ static void compiler_setmodule(compiler *c, value module) { c->currentmodule=module; } /** Gets the current module */ static value compiler_getmodule(compiler *c) { return c->currentmodule; } /* ------------------------------------------ * Loops * ------------------------------------------- */ /* 9 : bif r3 f 6 10 : mov r3, r1 11 : mov r4, r0 12 : invoke r3, c4, 1 ; c4=enumerate 13 : b 2 14 : add r0, r0, c2 ; c2=1 15 : b -8 16 : end */ /** Checks through a loop, updating any placeholders for break or continue * @param[in] c the current compiler * @param[in] start first instruction in the loop body * @param[in] inc position of the loop increment section (continue statements redirect here) * @param[in] end position of the first instruction AFTER the loop (break sections redirect here) */ static void compiler_fixloop(compiler *c, instructionindx start, instructionindx inc, instructionindx end) { instruction *code=c->out->code.data; for (instructionindx i=start; iloopdepth++; } /** End a loop */ static void compiler_endloop(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); f->loopdepth--; } /** Check if we are in a loop */ static bool compiler_inloop(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); return (f->loopdepth>0); } /* ------------------------------------------ * Register allocation and deallocation * ------------------------------------------- */ /** Finds a free register in the current function state and claims it */ static registerindx compiler_regallocwithstate(compiler *c, functionstate *f, value symbol) { registeralloc r = REGISTERALLOC_EMPTY(f->scopedepth, symbol); registerindx i = REGISTER_UNALLOCATED; if (compiler_inargs(c)) { /* Search backwards from the end to find the register AFTER the last allocated register */ for (i=f->registers.count; i>0; i--) { if (f->registers.data[i-1].isallocated) break; } if (iregisters.count) { f->registers.data[i]=r; goto regalloc_end; } } else { /* Search forwards to find any unallocated register */ for (i=0; iregisters.count; i++) { if (!f->registers.data[i].isallocated) { f->registers.data[i]=r; goto regalloc_end; } } } /* No unallocated register was found, so allocate one at the end */ if (varray_registerallocadd(&f->registers, &r, 1)) { i = f->registers.count-1; if (f->registers.count>f->nreg) f->nreg=f->registers.count; } regalloc_end: if (!MORPHO_ISNIL(symbol)) debugannotation_setreg(&c->out->annotations, i, symbol); return i; } static registerindx compiler_regalloc(compiler *c, value symbol) { functionstate *f = compiler_currentfunctionstate(c); return compiler_regallocwithstate(c, f, symbol); } /** Sets the symbol associated with a register */ static void compiler_regsetsymbol(compiler *c, registerindx reg, value symbol) { functionstate *f = compiler_currentfunctionstate(c); if (regregisters.count && f->registers.data[reg].isallocated) { f->registers.data[reg].symbol=symbol; if (!MORPHO_ISNIL(symbol)) debugannotation_setreg(&c->out->annotations, reg, symbol); } } /** Allocates a temporary register that is guaranteed to be at the top of the stack */ static registerindx compiler_regalloctop(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); registerindx i = REGISTER_UNALLOCATED; registeralloc r = REGISTERALLOC_EMPTY(f->scopedepth, MORPHO_NIL); /* Search backwards from the end to find the register AFTER the last allocated register */ for (i=f->registers.count; i>0; i--) { if (f->registers.data[i-1].isallocated) break; } if (iregisters.count) { f->registers.data[i]=r; return i; } if (varray_registerallocadd(&f->registers, &r, 1)) { i = f->registers.count-1; if (f->registers.count>f->nreg) f->nreg=f->registers.count; } return i; } /** Claims a free register if reqreg is REGISTER_UNALLOCATED */ static registerindx compiler_regtemp(compiler *c, registerindx reqreg) { return (reqreg==REGISTER_UNALLOCATED ? compiler_regalloc(c, MORPHO_NIL) : reqreg); } /** Treis to allocate a specific register as a temporary register */ static registerindx compiler_regtempwithindx(compiler *c, registerindx reg) { functionstate *f = compiler_currentfunctionstate(c); if (reg>=f->registers.count) { registeralloc empty = REGISTERALLOC_EMPTY(f->scopedepth, MORPHO_NIL); while (reg>=f->registers.count) { if (!varray_registerallocadd(&f->registers, &empty, 1)) break; if (f->registers.count>f->nreg) f->nreg=f->registers.count; } } if (regregisters.count) { f->registers.data[reg].isallocated=true; } return reg; } /** Releases a register that has been previously claimed */ static void compiler_regfree(compiler *c, functionstate *f, registerindx reg) { if (regregisters.count) { f->registers.data[reg].isallocated=false; f->registers.data[reg].isoptionalarg=false; f->registers.data[reg].scopedepth=0; if (!MORPHO_ISNIL(f->registers.data[reg].symbol)) { debugannotation_setreg(&c->out->annotations, reg, MORPHO_NIL); } f->registers.data[reg].symbol=MORPHO_NIL; f->registers.data[reg].type=MORPHO_NIL; f->registers.data[reg].currenttype=MORPHO_NIL; } } /** Frees a register if it is not a local */ static void compiler_regfreetemp(compiler *c, registerindx reg) { functionstate *f = compiler_currentfunctionstate(c); if (reg!=REGISTER_UNALLOCATED && regregisters.count && f->registers.data[reg].isallocated && MORPHO_ISNIL(f->registers.data[reg].symbol)) { compiler_regfree(c, f, reg); } } /** Frees all registers beyond and including reg */ static void compiler_regfreetoend(compiler *c, registerindx reg) { functionstate *f = compiler_currentfunctionstate(c); if (f) for (registerindx i=reg; iregisters.count; i++) { compiler_regfree(c, f, i); } } /** @brief Releases an operand. * @detail A node should call this for each operand it uses the result of. */ static void compiler_releaseoperand(compiler *c, codeinfo info) { if (CODEINFO_ISREGISTER(info)) { compiler_regfreetemp(c, info.dest); } } /** Releases all registers at a given scope depth */ static void compiler_regfreeatscope(compiler *c, unsigned int scopedepth) { functionstate *f = compiler_currentfunctionstate(c); for (registerindx i=0; iregisters.count; i++) { if (f->registers.data[i].isallocated && f->registers.data[i].scopedepth>=scopedepth) { compiler_regfree(c, f, i); } } } /** Sets the type associated with a register */ void compiler_regsettype(compiler *c, registerindx reg, value type) { functionstate *f = compiler_currentfunctionstate(c); if (reg<0 || reg>=f->registers.count) return; f->registers.data[reg].type=type; } /** Gets the current type of a register */ bool compiler_regtype(compiler *c, registerindx reg, value *type) { functionstate *f = compiler_currentfunctionstate(c); if (reg>=f->registers.count) return false; *type = f->registers.data[reg].type; return true; } /** Raises a type violation error */ void compiler_typeviolation(compiler *c, syntaxtreenode *node, value type, value badtype, value symbol) { char *tname="(unknown)", *bname="(unknown)"; char *sym=""; if (MORPHO_ISCLASS(type)) tname=MORPHO_GETCSTRING(MORPHO_GETCLASS(type)->name); if (MORPHO_ISCLASS(badtype)) bname=MORPHO_GETCSTRING(MORPHO_GETCLASS(badtype)->name); if (MORPHO_ISSTRING(symbol)) sym = MORPHO_GETCSTRING(symbol); compiler_error(c, node, COMPILE_TYPEVIOLATION, bname, tname, sym); } /** Sets the current type of a register. Raises a type violation error if this is not compatible with the required type */ bool compiler_regsetcurrenttype(compiler *c, syntaxtreenode *node, registerindx reg, value type) { functionstate *f = compiler_currentfunctionstate(c); if (reg>=f->registers.count) return false; if (compiler_checktype(c, f->registers.data[reg].type, type)) { f->registers.data[reg].currenttype=type; return true; } compiler_typeviolation(c, node, f->registers.data[reg].type, type, f->registers.data[reg].symbol); return false; } /** Gets the current type of a register */ bool compiler_regcurrenttype(compiler *c, registerindx reg, value *type) { functionstate *f = compiler_currentfunctionstate(c); if (reg>=f->registers.count) return false; *type = f->registers.data[reg].currenttype; return true; } /** @brief Finds the register that contains symbol in a given functionstate * @details Searches backwards so that the innermost scope has priority */ static registerindx compiler_findsymbol(functionstate *f, value symbol) { if (f) for (registerindx i=f->registers.count-1; i>=0; i--) { if (f->registers.data[i].isallocated) { if (MORPHO_ISEQUAL(f->registers.data[i].symbol, symbol)) { return i; } } } return REGISTER_UNALLOCATED; } /** @brief Finds the register that contains symbol in a given functionstate with a scopedepth of scopedepth or higher * @details Searches backwards so that the innermost scope has priority */ static registerindx compiler_findsymbolwithscope(functionstate *f, value symbol, unsigned int scopedepth) { if (f) for (registerindx i=f->registers.count-1; i>=0; i--) { if (f->registers.data[i].scopedepthregisters.data[i].isallocated) { if (MORPHO_ISEQUAL(f->registers.data[i].symbol, symbol)) { return i; } } } return REGISTER_UNALLOCATED; } /** @brief Find the last allocated register * @returns Index of the last allocated register, or REGISTER_UNALLOCATED if there are no registers allocated */ static registerindx compiler_regtop(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); for (registerindx i=f->registers.count-1; i>=0; i--) { if (f->registers.data[i].isallocated) return i; } return REGISTER_UNALLOCATED; } /** @brief Check if a register is temporary or associated with a symbol * @returns True if the register is temporary, false otherwise */ static bool compiler_isregtemp(compiler *c, registerindx indx) { functionstate *f = compiler_currentfunctionstate(c); return (indxregisters.count && MORPHO_ISNIL(f->registers.data[indx].symbol)); } /** @brief Check if a register is allocated * @returns True if the register is allocated, false otherwise */ static bool compiler_isregalloc(compiler *c, registerindx indx) { functionstate *f = compiler_currentfunctionstate(c); return (indxregisters.count && f->registers.data[indx].isallocated); } /** @brief Sets that a register contains an optional argument */ void compiler_regsetoptionalarg(compiler *c, registerindx reg) { functionstate *f = compiler_currentfunctionstate(c); if (reg<0 || reg>=f->registers.count) return; f->registers.data[reg].isoptionalarg=true; } /** @brief Checks if a register contains an optional argument */ static bool compiler_isregoptionalarg(compiler *c, registerindx indx) { functionstate *f = compiler_currentfunctionstate(c); return (indxregisters.count && f->registers.data[indx].isoptionalarg); } /** Gets the number of args from the most recent argument specifier*/ void compiler_regcountargs(compiler *c, registerindx start, registerindx end, int *nposn, int *nopt) { functionstate *f = compiler_currentfunctionstate(c); int np=0, nop=0; if (f) { for (registerindx r=start; r<=end; r++) { if (compiler_isregoptionalarg(c, r)) nop++; else np++; } *nposn=np; *nopt=nop/2; } } /** @brief Get the scope level associated with a register * @param[in] c compiler * @param[in] indx register to examine * @param[out] scope the scope * @returns True if the requested register has a meaningful scope */ static bool compiler_getregscope(compiler *c, registerindx indx, unsigned int *scope) { functionstate *f = compiler_currentfunctionstate(c); if (indxregisters.count && !MORPHO_ISNIL(f->registers.data[indx].symbol)) { if (scope) *scope=f->registers.data[indx].scopedepth; return true; } return false; } /** @brief Function to tell if a codeinfo block has returned something that is at the top of the stack * @details Calls and invocations, inter alia, rely on things being put in the last available register. * This function checks whether this has been achieved; if not a call to * compiler_movetoregister may be made. * @returns true if the codeinfo meets these requirements is satisfied, false otherwise */ static bool compiler_iscodeinfotop(compiler *c, codeinfo func) { return ( CODEINFO_ISREGISTER(func) && // We're in a register compiler_isregtemp(c, func.dest) && // and it's a temporary register func.dest==compiler_regtop(c)); // and it's the top of the stack } /** @brief Shows the current allocation of the registers */ static void compiler_regshow(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); printf("--Registers (%u in use)\n",f->nreg); for (unsigned int i=0; iregisters.count; i++) { registeralloc *r=f->registers.data+i; printf("r%u ",i); if (r->isallocated) { if (i==0 && FUNCTIONTYPE_ISMETHOD(f->type)) { printf("self"); } else if (!MORPHO_ISNIL(r->symbol)) { morpho_printvalue(NULL, r->symbol); } else { printf("temporary"); } printf(" [%u]", r->scopedepth); if (r->iscaptured) printf(" (captured)"); if (r->isoptionalarg) printf(" (optarg)"); } else { printf("unallocated"); } if (!MORPHO_ISNIL(r->type)) { printf(" ["); morpho_printvalue(NULL, r->type); printf("]"); } if (!MORPHO_ISNIL(r->currenttype)) { printf(" contains: "); morpho_printvalue(NULL, r->currenttype); } printf("\n"); } printf("--End registers\n"); } /* ------------------------------------------ * Track scope * ------------------------------------------- */ /** Increments the scope counter in the current functionstate */ void compiler_beginscope(compiler *c) { functionstate *f=compiler_currentfunctionstate(c); f->scopedepth++; } void compiler_functionreffreeatscope(compiler *c, unsigned int scope); /** Decrements the scope counter in the current functionstate */ void compiler_endscope(compiler *c) { functionstate *f=compiler_currentfunctionstate(c); compiler_regfreeatscope(c, f->scopedepth); compiler_functionreffreeatscope(c, f->scopedepth); f->scopedepth--; } /** Gets the scope counter in the current functionstate */ unsigned int compiler_currentscope(compiler *c) { functionstate *f=compiler_currentfunctionstate(c); return f->scopedepth; } /* ------------------------------------------ * Constants * ------------------------------------------- */ /** Writes a constant to the current constant table * @param c the compiler * @param node current syntaxtree node * @param constant the constant to add * @param usestrict whether to use a strict e * @param clone whether to clone the constant if it's not already present * (typically this is set to copy strings from the syntax tree) */ static registerindx compiler_addconstant(compiler *c, syntaxtreenode *node, value constant, bool usestrict, bool clone) { varray_value *konst = compiler_getcurrentconstanttable(c); if (!konst) return REGISTER_UNALLOCATED; registerindx out=REGISTER_UNALLOCATED; unsigned int prev=0; if (konst) { /* Was a similar previous constant already added? */ if (usestrict) { if (varray_valuefindsame(konst, constant, &prev)) out=(registerindx) prev; } else { if (varray_valuefind(konst, constant, &prev)) out=(registerindx) prev; } /* No, so create a new one */ if (out==REGISTER_UNALLOCATED) { if (konst->count>=MORPHO_MAXCONSTANTS) { compiler_error(c, node, COMPILE_TOOMANYCONSTANTS); return REGISTER_UNALLOCATED; } else { value add = constant; if (clone && MORPHO_ISOBJECT(add)) { /* If clone is set, we should try to clone the contents if the thing is an object. */ if (MORPHO_ISSTRING(add)) { add=object_clonestring(add); } else if (MORPHO_ISCOMPLEX(add)) { add=object_clonecomplexvalue(add); } else { UNREACHABLE("Erroneously being asked to clone a non-string non-complex constant."); } } bool success=varray_valueadd(konst, &add, 1); out=konst->count-1; if (!success) compiler_error(c, node, ERROR_ALLOCATIONFAILED); /* If the constant is an object and we cloned it, make sure it's bound to the program */ if (clone && MORPHO_ISOBJECT(add)) { program_bindobject(c->out, MORPHO_GETOBJECT(add)); } } } } return out; } /** Write a symbol to the constant table, performing interning. * @param c the compiler * @param node current syntaxtree node * @param symbol the constant to add */ static registerindx compiler_addsymbol(compiler *c, syntaxtreenode *node, value symbol) { /* Intern the symbol */ value add=program_internsymbol(c->out, symbol); return compiler_addconstant(c, node, add, true, false); } /** Finds a builtin function and loads it into a register at the top of the stack * @param c the compiler * @param node current syntax tree node * @param name the symbol to lookup * @param req requested register * @details This function is oriented to setting up a function call, so req is checked whether it's at the top of the stack. */ codeinfo compiler_findbuiltin(compiler *c, syntaxtreenode *node, char *name, registerindx req) { objectstring symbol = MORPHO_STATICSTRING(name); codeinfo ret=CODEINFO_EMPTY; registerindx rfn=req; /* Find the function */ value fn=builtin_findfunction(MORPHO_OBJECT(&symbol)); if (MORPHO_ISNIL(fn)) { UNREACHABLE("Compiler couldn't locate builtin function."); } registerindx cfn=compiler_addconstant(c, node, fn, false, false); /* Ensure output register is at top of stack */ if (rfn==REGISTER_UNALLOCATED || rfnregisters.data[reg].isallocated && !MORPHO_ISNIL(f->registers.data[reg].symbol)) return true; } return false; }*/ /** @brief Moves the results of a codeinfo block into a register * @details includes constants, upvalues etc. * @param c the current compiler * @param node current syntaxtreenode * @param info a codeinfo struct * @param reg destination register, or REGISTER_UNALLOCATED to allocate a new one * @returns Number of instructions generated */ static codeinfo compiler_movetoregister(compiler *c, syntaxtreenode *node, codeinfo info, registerindx reg) { value type = MORPHO_NIL; codeinfo out = info; out.ninstructions=0; if (CODEINFO_ISCONSTANT(info)) { out.returntype=REGISTER; out.dest=compiler_regtemp(c, reg); if (compiler_getconstanttype(c, info.dest, &type)) { compiler_regsetcurrenttype(c, node, out.dest, type); } compiler_addinstruction(c, ENCODE_LONG(OP_LCT, out.dest, info.dest), node); out.ninstructions++; } else if (CODEINFO_ISUPVALUE(info)) { /* Move upvalues */ out.dest=compiler_regtemp(c, reg); out.returntype=REGISTER; compiler_addinstruction(c, ENCODE_DOUBLE(OP_LUP, out.dest, info.dest), node); out.ninstructions++; } else if (CODEINFO_ISGLOBAL(info)) { /* Move globals */ out.dest=compiler_regtemp(c, reg); out.returntype=REGISTER; compiler_addinstruction(c, ENCODE_LONG(OP_LGL, out.dest, info.dest), node); out.ninstructions++; } else { /* Move between registers */ if (reg==REGISTER_UNALLOCATED) { out.dest=compiler_regtemp(c, reg); } else { out.dest=reg; } if (out.dest!=info.dest) { if (compiler_regcurrenttype(c, info.dest, &type)) compiler_regsetcurrenttype(c, node, out.dest, type); compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, out.dest, info.dest), node); out.ninstructions++; } } return out; } /** Write a symbol to the constant table, performing interning and checking the result fits into a register definition * @param c the compiler * @param node current syntaxtree node * @param symbol the constant to add */ codeinfo compiler_addsymbolwithsizecheck(compiler *c, syntaxtreenode *node, value symbol) { codeinfo out = CODEINFO(CONSTANT, 0, 0); out.dest = compiler_addsymbol(c, node, symbol); out = compiler_movetoregister(c, node, out, REGISTER_UNALLOCATED); return out; } /* ------------------------------------------ * Optional and variadic args * ------------------------------------------- */ DEFINE_VARRAY(optionalparam, optionalparam); /** Adds a variadic parameter */ static inline registerindx compiler_addpositionalarg(compiler *c, syntaxtreenode *node, value symbol) { functionstate *f = compiler_currentfunctionstate(c); if (f) { if (!function_hasvargs(f->func)) { f->func->nargs++; value sym=program_internsymbol(c->out, symbol); return compiler_addlocal(c, node, sym); } else compiler_error(c, node, COMPILE_VARPRMLST); } return REGISTER_UNALLOCATED; } /** Adds an optional argument */ static inline void compiler_addoptionalarg(compiler *c, syntaxtreenode *node, value symbol, value def) { functionstate *f = compiler_currentfunctionstate(c); if (f) { f->func->nopt++; value sym=program_internsymbol(c->out, symbol); registerindx reg = compiler_addlocal(c, node, sym); registerindx val = compiler_addconstant(c, node, def, false, true); optionalparam param = {.symbol=sym, .def=val, .reg=reg}; varray_optionalparamwrite(&f->func->opt, param); } } /** Adds a variadic parameter */ static inline void compiler_addvariadicarg(compiler *c, syntaxtreenode *node, value symbol) { functionstate *f = compiler_currentfunctionstate(c); if (f) { if (function_hasvargs(f->func)) { compiler_error(c, node, COMPILE_MLTVARPRMTR); return; } value sym=program_internsymbol(c->out, symbol); registerindx reg = compiler_addlocal(c, node, sym); function_setvarg(f->func, reg-1); } } /* ------------------------------------------ * Global variables * ------------------------------------------- */ /** Should we use global variables or registers? */ bool compiler_checkglobal(compiler *c) { return ((c->fstackp==0) && (c->fstack[0].scopedepth==0)); } /** Finds a global symbol, optionally searching successively through parent compilers */ globalindx compiler_findglobal(compiler *c, value symbol, bool recurse) { for (compiler *cc=c; cc!=NULL; cc=cc->parent) { value indx; if (dictionary_get(&cc->globals, symbol, &indx)) { if (MORPHO_ISINTEGER(indx)) return (globalindx) MORPHO_GETINTEGERVALUE(indx); else UNREACHABLE("Unknown type in global table."); } if (!recurse) break; } return GLOBAL_UNALLOCATED; } /** Adds a global variable to */ globalindx compiler_addglobal(compiler *c, syntaxtreenode *node, value symbol) { globalindx indx=compiler_findglobal(c, symbol, false); if (indx==GLOBAL_UNALLOCATED) { indx = program_addglobal(c->out, symbol); value key = program_internsymbol(c->out, symbol); if (dictionary_insert(&c->globals, key, MORPHO_INTEGER(indx))) { debugannotation_setglobal(&c->out->annotations, indx, symbol); } } return indx; } /** Sets the type of a global variable */ void compiler_setglobaltype(compiler *c, globalindx indx, value type) { program_globalsettype(c->out, indx, type); } /** Checks if the type match satisfies the type of the global variable indx */ bool compiler_checkglobaltype(compiler *c, syntaxtreenode *node, globalindx indx, value match) { value type=MORPHO_NIL; if (!program_globaltype(c->out, indx, &type)) return false; bool success=compiler_checktype(c, type, match); if (!success) { value symbol=MORPHO_NIL; program_globalsymbol(c->out, indx, &symbol); compiler_typeviolation(c, node, type, match, symbol); } return success; } /** Shows all currently allocated globals */ void compiler_globalshow(compiler *c) { int nglobals = program_countglobals(c->out); printf("--Globals (%u in use)\n", nglobals); for (unsigned int i=0; iout->globals.data[i]; printf("g%u ",i); if (!MORPHO_ISNIL(r->symbol)) morpho_printvalue(NULL, r->symbol); if (!MORPHO_ISNIL(r->type)) { printf(" ["); morpho_printvalue(NULL, r->type); printf("]"); } printf("\n"); } printf("--End globals\n"); } /* Moves the result of a calculation to an global variable */ codeinfo compiler_movetoglobal(compiler *c, syntaxtreenode *node, codeinfo in, globalindx slot) { codeinfo use = in; codeinfo out = CODEINFO_EMPTY; bool tmp=false; if (!(CODEINFO_ISREGISTER(in))) { use=compiler_movetoregister(c, node, in, REGISTER_UNALLOCATED); out.ninstructions+=use.ninstructions; tmp=true; } value type=MORPHO_NIL; if (compiler_regcurrenttype(c, in.dest, &type)) { if (!compiler_checkglobaltype(c, node, slot, type)) goto compiler_movetoglobal_cleanup; } compiler_addinstruction(c, ENCODE_LONG(OP_SGL, use.dest, slot) , node); out.ninstructions++; compiler_movetoglobal_cleanup: if (tmp) compiler_releaseoperand(c, use); return out; } codeinfo compiler_addvariable(compiler *c, syntaxtreenode *node, value symbol) { codeinfo out=CODEINFO_EMPTY; if (compiler_checkglobal(c)) { out.dest=compiler_addglobal(c, node, symbol); out.returntype=GLOBAL; } else { out.dest=compiler_addlocal(c, node, symbol); out.returntype=REGISTER; } return out; } /* ------------------------------------------ * Upvalues * ------------------------------------------- */ /** Adds an upvalue to a functionstate */ registerindx compiler_addupvalue(functionstate *f, bool islocal, indx ix) { upvalue v = (upvalue) { .islocal = islocal, .reg = ix}; /* Does this upvalue already exist? */ for (registerindx i=0; iupvalues.count; i++) { upvalue *up = &f->upvalues.data[i]; if (up->islocal==islocal && up->reg==ix) { return i; } } /* If not, add it */ varray_upvalueadd(&f->upvalues, &v, 1); return (registerindx) f->upvalues.count-1; } /** Propagates upvalues up the functionstate stack. @param c the compiler @param start the initial functionstate @param sindx starting index @returns register index at the top of the function state */ registerindx compiler_propagateupvalues(compiler *c, functionstate *start, registerindx sindx) { registerindx indx=sindx; for (functionstate *f = start; ffstack+c->fstackp; f++) { indx=compiler_addupvalue(f, f==start, indx); } return indx; } /** @brief Determines whether a symbol refers to something outside its scope @param c the compiler @param symbol symbol to resolve @returns the index of the upvalue, or REGISTER_UNALLOCATED if not found */ static registerindx compiler_resolveupvalue(compiler *c, value symbol) { registerindx indx=REGISTER_UNALLOCATED; functionstate *found=NULL; for (functionstate *f = c->fstack+c->fstackp-1; f>=c->fstack; f--) { /* Try to find the symbol */ indx = compiler_findsymbol(f, symbol); if (indx!=REGISTER_UNALLOCATED) { /* Mark that this register must be captured as an upvalue */ f->registers.data[indx].iscaptured=true; found=f; break; } } /* Now walk up the functionstate stack adding in the upvalues */ if (found) indx=compiler_propagateupvalues(c, found, indx); return indx; } /* Moves the result of a calculation to an upvalue */ static codeinfo compiler_movetoupvalue(compiler *c, syntaxtreenode *node, codeinfo in, registerindx slot) { codeinfo use = in; codeinfo out = CODEINFO_EMPTY; bool tmp=false; if (!CODEINFO_ISREGISTER(in)) { use=compiler_movetoregister(c, node, in, REGISTER_UNALLOCATED); out.ninstructions+=use.ninstructions; tmp=true; } compiler_addinstruction(c, ENCODE_DOUBLE(OP_SUP, slot, use.dest), node); out.ninstructions++; if (tmp) { compiler_releaseoperand(c, use); } return out; } /** Creates code to generate a closure for the current environment. */ indx compiler_closure(compiler *c, syntaxtreenode *node, registerindx reg) { functionstate *f=compiler_currentfunctionstate(c); objectfunction *func = f->func; indx ix=REGISTER_UNALLOCATED; if (f->upvalues.count>0) { object_functionaddprototype(func, &f->upvalues, &ix); } return ix; } /* ------------------------------------------ * Manage the functionref stack * ------------------------------------------- */ DEFINE_VARRAY(functionref, functionref) /** Adds a reference to a function in the current functionstate */ int compiler_addfunctionref(compiler *c, objectfunction *func) { functionstate *f=compiler_currentfunctionstate(c); functionref ref = { .function = func, .symbol = func->name, .scopedepth = f->scopedepth}; return varray_functionrefwrite(&f->functionref, ref); } /** Removes functions visible at a given scope level */ void compiler_functionreffreeatscope(compiler *c, unsigned int scope) { functionstate *f=compiler_currentfunctionstate(c); while (f->functionref.count>0 && f->functionref.data[f->functionref.count-1].scopedepth>=scope) f->functionref.count--; } void _addmatchingfunctionref(compiler *c, value symbol, value fn, value *out) { value in = *out; if (MORPHO_ISNIL(in)) { // If the function has a signature, will need to wrap in a metafunction if (MORPHO_ISFUNCTION(fn) && function_hastypedparameters(MORPHO_GETFUNCTION(fn))) { if (metafunction_wrap(symbol, fn, out)) { program_bindobject(c->out, MORPHO_GETOBJECT(*out)); } } else *out=fn; } else if (MORPHO_ISFUNCTION(in)) { if (metafunction_wrap(symbol, in, out)) { metafunction_add(MORPHO_GETMETAFUNCTION(*out), fn); program_bindobject(c->out, MORPHO_GETOBJECT(*out)); } } else if (MORPHO_ISMETAFUNCTION(in)) { metafunction_add(MORPHO_GETMETAFUNCTION(in), fn); } } /** Finds an existing metafunction in the current context that matches a given set of implementations */ bool compiler_findmetafunction(compiler *c, value symbol, int n, value *fns, codeinfo *out) { functionstate *f=compiler_currentfunctionstate(c); for (int i=0; ifunc->konst.count; i++) { value v = f->func->konst.data[i]; if (MORPHO_ISMETAFUNCTION(v) && MORPHO_ISEQUAL(MORPHO_GETMETAFUNCTION(v)->name, symbol) && metafunction_matchset(MORPHO_GETMETAFUNCTION(v), n, fns)) { *out = CODEINFO(CONSTANT, i, 0); return true; } } return false; } /** Finds a closure, looking back up the functionstate stack and propagating upvalues as necessary */ void _findclosure(compiler *c, objectfunction *closure, codeinfo *out) { functionstate *fc = compiler_currentfunctionstate(c); if (fc->func==closure->parent) { out->returntype=REGISTER; out->dest = (registerindx) closure->creg; } else { out->returntype=UPVALUE; for (functionstate *f=fc; f>=c->fstack; f--) { if (f->func==closure->parent) { f->registers.data[closure->creg].iscaptured=true; out->dest=compiler_propagateupvalues(c, f, closure->creg); return; } } UNREACHABLE("Couldn't locate parent of closure."); } } /** Compile a metafunction constructor by setting up a call to the Metafunction() constructor */ codeinfo compiler_metafunction(compiler *c, syntaxtreenode *node, int n, value *fns) { codeinfo out = compiler_findbuiltin(c, node, METAFUNCTION_CLASSNAME, REGISTER_UNALLOCATED); for (int i=0; icount; i++) { functionref *r = &refs->data[i]; // Match functionrefs with the same signature, but only if they're // in a different scope or parent if (signature_isequal(&r->function->sig, &match->function->sig) && (r->function->parent!=match->function->parent || r->scopedepth!=match->scopedepth)) return true; } return false; } /** Collects function implementations that match a given symbol */ static void _findfunctionref(compiler *c, value symbol, bool *hasclosure, varray_value *out) { bool closure=false; varray_functionref refs; varray_functionrefinit(&refs); functionstate *fc = compiler_currentfunctionstate(c); for (functionstate *f=fc; f>=c->fstack; f--) { // Go backwards to prioritize recent def'ns for (int i=f->functionref.count-1; i>=0; i--) { // Go backwards functionref *ref=&f->functionref.data[i]; if (MORPHO_ISEQUAL(ref->symbol, symbol) && !_checkduplicateref(&refs, ref)) { closure |= function_isclosure(ref->function); varray_functionrefadd(&refs, ref, 1); } } } // Return the collected implementations for (int i=0; i1) { // If the list contains a closure, we must build the MF at runtime *out = compiler_metafunction(c, node, fns.count, fns.data); } else { // If just one closure, no need to build a metafunction _findclosure(c, MORPHO_GETFUNCTION(fns.data[0]), out); out->ninstructions=0; } } else if (!compiler_findmetafunction(c, symbol, fns.count, fns.data, out)) { // If a suitable MF doesn't exist in the constant table, we should build one for (int i=0; ierr); } out->returntype=CONSTANT; out->dest=compiler_addconstant(c, node, outfn, true, false); out->ninstructions=0; } varray_valueclear(&fns); return true; } /* ------------------------------------------ * Helper function to move from a register * ------------------------------------------- */ /* Moves the result of a calculation from a register to another destination */ codeinfo compiler_movefromregister(compiler *c, syntaxtreenode *node, codeinfo dest, registerindx reg) { codeinfo in=CODEINFO(REGISTER, reg, 0), out=CODEINFO_EMPTY; if (CODEINFO_ISUPVALUE(dest)) { out=compiler_movetoupvalue(c, node, in, dest.dest); } else if (CODEINFO_ISGLOBAL(dest)) { out=compiler_movetoglobal(c, node, in, dest.dest); } else if (CODEINFO_ISCONSTANT(dest)) { UNREACHABLE("Cannot move to a constant."); } else { out=compiler_movetoregister(c, node, in, dest.dest); } return out; } /* ------------------------------------------ * Self * ------------------------------------------- */ /** @brief Resolves self by walking back along the functionstate stack @param c the compiler @returns the index of self */ static registerindx compiler_resolveself(compiler *c) { registerindx indx=REGISTER_UNALLOCATED; functionstate *found=NULL; for (functionstate *f = c->fstack+c->fstackp-1; f>=c->fstack; f--) { /* If this is a method, we can capture self from it */ if (FUNCTIONTYPE_ISMETHOD(f->type)) { indx=0; f->registers.data[indx].iscaptured=true; found=f; break; } } /* Now walk up the functionstate stack adding in the upvalues */ if (found) indx = compiler_propagateupvalues(c, found, indx); return indx; } /* ------------------------------------------ * Forward references * ------------------------------------------- */ /** Adds a forward reference * @param[in] c the compiler * @param[in] symbol symbol corresponding to forward reference */ static codeinfo compiler_addforwardreference(compiler *c, syntaxtreenode *node, value symbol) { codeinfo ret = CODEINFO_EMPTY; functionstate *fparent = compiler_parentfunctionstate(c); if (!fparent) { // If in global scope, generate symbol not defined char *label = MORPHO_GETCSTRING(node->content); compiler_error(c, node, COMPILE_SYMBOLNOTDEFINED, label); return ret; } forwardreference ref = { .symbol = symbol, .node = node, .returntype=REGISTER, .dest=compiler_regallocwithstate(c, fparent, symbol), .scopedepth = fparent->scopedepth }; ret.returntype = UPVALUE; ret.dest=compiler_addupvalue(fparent, true, ref.dest); varray_forwardreferencewrite(&fparent->forwardref, ref); return ret; } /** Checks if a symbol resolves a forward reference */ static bool compiler_resolveforwardreference(compiler *c, value symbol, codeinfo *out) { bool success=false; functionstate *f = compiler_currentfunctionstate(c); for (unsigned int i=0; iforwardref.count; i++) { forwardreference *ref = f->forwardref.data+i; if (MORPHO_ISEQUAL(symbol, ref->symbol) && ref->scopedepth==compiler_currentscope(c)) { out->returntype=ref->returntype; out->dest=ref->dest; ref->symbol=MORPHO_NIL; // Forward reference was successfully resolved success=true; } } return success; } /** Check for outstanding forward references */ bool compiler_checkoutstandingforwardreference(compiler *c) { functionstate *f = compiler_currentfunctionstate(c); for (unsigned int i=0; iforwardref.count; i++) { forwardreference *ref = f->forwardref.data+i; if (!MORPHO_ISNIL(ref->symbol)) { compiler_error(c, ref->node, COMPILE_FORWARDREF, MORPHO_GETCSTRING(ref->symbol)); return false; } } return true; } /* ------------------------------------------ * Namespaces * ------------------------------------------- */ /** Adds a namespace to the compiler */ namespc *compiler_addnamespace(compiler *c, value symbol) { namespc *new=MORPHO_MALLOC(sizeof(namespc)); if (new) { new->label=symbol; dictionary_init(&new->symbols); dictionary_init(&new->classes); new->next=c->namespaces; // Link namespace to compiler c->namespaces=new; } return new; } /** Checks if a given label corresponds to a namespace */ namespc *compiler_isnamespace(compiler *c, value label) { for (namespc *spc=c->namespaces; spc!=NULL; spc=spc->next) { if (MORPHO_ISEQUAL(spc->label, label)) return spc; } return NULL; } /** Attempts to locate a symbol given a namespace label */ bool compiler_findsymbolwithnamespace(compiler *c, syntaxtreenode *node, value label, value symbol, value *out) { bool success=false; namespc *spc = compiler_isnamespace(c, label); // Now try to find the symbol if (spc) { success=dictionary_get(&spc->symbols, symbol, out); if (!success) compiler_error(c, node, COMPILE_SYMBOLNOTDEFINEDNMSPC, MORPHO_GETCSTRING(symbol), MORPHO_GETCSTRING(label)); } return success; } /** Attempts to locate a class given a namespace label */ bool compiler_findclasswithnamespace(compiler *c, syntaxtreenode *node, value label, value symbol, value *out) { bool success=false; namespc *spc = compiler_isnamespace(c, label); // Now try to find the symbol if (spc) { success=dictionary_get(&spc->classes, symbol, out); if (!success) compiler_error(c, node, COMPILE_SYMBOLNOTDEFINEDNMSPC, MORPHO_GETCSTRING(symbol), MORPHO_GETCSTRING(label)); } return success; } /** Clears the namespace list, freeing attached data */ void compiler_clearnamespacelist(compiler *c) { namespc *next=NULL; for (namespc *spc=c->namespaces; spc!=NULL; spc=next) { next=spc->next; dictionary_clear(&spc->symbols); dictionary_clear(&spc->classes); MORPHO_FREE(spc); } c->namespaces=NULL; } /* ------------------------------------------ * Compiler node implementation functions * ------------------------------------------- */ static codeinfo compiler_constant(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_list(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_dictionary(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_index(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_negate(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_not(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_binary(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_ternary(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_property(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_dot(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_grouping(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_sequence(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_interpolation(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_range(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_scope(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_print(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_if(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_while(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_for(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_do(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_break(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_try(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_logical(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_declaration(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_function(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_arglist(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_call(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_invoke(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_return(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_class(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_symbol(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_self(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_super(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_assign(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_import(compiler *c, syntaxtreenode *node, registerindx reqout); static codeinfo compiler_breakpoint(compiler *c, syntaxtreenode *node, registerindx out); static codeinfo compiler_nodetobytecode(compiler *c, syntaxtreeindx indx, registerindx out); /* ------------------------------------------ * Compiler definition table * ------------------------------------------- */ #define NODE_NORULE NULL #define NODE_UNDEFINED { NODE_NORULE } /** Compiler definition table. This specifies a compiler function that handles each node type */ compilenoderule noderules[] = { NODE_UNDEFINED, // NODE_BASE { compiler_constant }, // NODE_NIL, { compiler_constant }, // NODE_BOOL, { compiler_constant }, // NODE_FLOAT { compiler_constant }, // NODE_INTEGER { compiler_constant }, // NODE_STRING { compiler_symbol }, // NODE_SYMBOL { compiler_self }, // NODE_SELF { compiler_super }, // NODE_SUPER { compiler_constant }, // NODE_IMAG NODE_UNDEFINED, // NODE_LEAF { compiler_negate }, // NODE_NEGATE { compiler_not }, // NODE_NOT NODE_UNDEFINED, // NODE_UNARY { compiler_binary }, // NODE_ADD { compiler_binary }, // NODE_SUBTRACT { compiler_binary }, // NODE_MULTIPLY { compiler_binary }, // NODE_DIVIDE { compiler_binary }, // NODE_POW { compiler_assign }, // NODE_ASSIGN { compiler_binary }, // NODE_EQ { compiler_binary }, // NODE_NEQ { compiler_binary }, // NODE_LT { compiler_binary }, // NODE_GT { compiler_binary }, // NODE_LTEQ { compiler_binary }, // NODE_GTEQ { compiler_logical }, // NODE_AND { compiler_logical }, // NODE_OR { compiler_ternary }, // NODE_TERNARY { compiler_dot }, // NODE_DOT { compiler_range }, // NODE_RANGE { compiler_range }, // NODE_INCLUSIVERANGE NODE_UNDEFINED, // NODE_OPERATOR { compiler_print }, // NODE_PRINT { compiler_declaration }, // NODE_DECLARATION { compiler_declaration }, // NODE_TYPE { compiler_function }, // NODE_FUNCTION { NODE_NORULE }, // NODE_METHOD { compiler_class }, // NODE_CLASS { compiler_return }, // NODE_RETURN { compiler_if }, // NODE_IF { NODE_NORULE }, // NODE_ELSE { compiler_while }, // NODE_WHILE { compiler_for }, // NODE_FOR { compiler_do }, // NODE_DO { NODE_NORULE }, // NODE_IN { compiler_break }, // NODE_BREAK { compiler_break }, // NODE_CONTINUE { compiler_try }, // NODE_TRY NODE_UNDEFINED, // NODE_STATEMENT { compiler_grouping }, // NODE_GROUPING { compiler_sequence }, // NODE_SEQUENCE { compiler_dictionary }, // NODE_DICTIONARY NODE_UNDEFINED, // NODE_DICTENTRY { compiler_interpolation }, // NODE_INTERPOLATION { compiler_arglist }, // NODE_ARGLIST { compiler_scope }, // NODE_SCOPE { compiler_call }, // NODE_CALL { compiler_index }, // NODE_INDEX { compiler_list }, // NODE_LIST { compiler_list }, // NODE_TUPLE { compiler_import }, // NODE_IMPORT NODE_UNDEFINED, // NODE_AS { compiler_breakpoint } // NODE_BREAKPOINT }; /** Get the associated node rule for a given node type */ static compilenoderule *compiler_getrule(syntaxtreenodetype type) { return &noderules[type]; } /** Compile a constant */ static codeinfo compiler_constant(compiler *c, syntaxtreenode *node, registerindx reqout) { registerindx indx = compiler_addconstant(c, node, node->content, (node->type==NODE_FLOAT ? true : false), true); return CODEINFO(CONSTANT, indx, 0); } /** Compiles a list or tuple */ static codeinfo compiler_list(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreenodetype dictentrytype[] = { NODE_ARGLIST }; varray_syntaxtreeindx entries; /* Set up a call to the List() function */ char *classname = LIST_CLASSNAME; if (node->type==NODE_TUPLE) classname = TUPLE_CLASSNAME; codeinfo out = compiler_findbuiltin(c, node, classname, reqout); value listtype=MORPHO_NIL; /* Set the type associated with the register */ if (compiler_findtypefromcstring(c, classname, &listtype)) { if (!compiler_regsetcurrenttype(c, node, out.dest, listtype)) return CODEINFO_EMPTY; } varray_syntaxtreeindxinit(&entries); if (node->right!=SYNTAXTREE_UNCONNECTED) syntaxtree_flatten(compiler_getsyntaxtree(c), node->right, 1, dictentrytype, &entries); /* Now loop over the nodes */ unsigned int nargs=0; for (int i=0; ileft!=SYNTAXTREE_UNCONNECTED) syntaxtree_flatten(compiler_getsyntaxtree(c), node->left, 2, dictentrytype, &entries); if (node->right!=SYNTAXTREE_UNCONNECTED) syntaxtree_flatten(compiler_getsyntaxtree(c), node->right, 2, dictentrytype, &entries); /* Now loop over the nodes */ unsigned int nargs=0; for (int i=0; itype==NODE_INCLUSIVERANGE; /* Determine whether we have start..end or start..end:step */ syntaxtreenode *left=compiler_getnode(c, node->left); if (left && (left->type==NODE_RANGE || left->type==NODE_INCLUSIVERANGE)) { s[0]=left->left; s[1]=left->right; s[2]=node->right; inclusive = left->type==NODE_INCLUSIVERANGE; } else { s[0]=node->left; s[1]=node->right; } /* Set up a call to the Range() function */ codeinfo rng = compiler_findbuiltin(c, node, (inclusive ? RANGE_INCLUSIVE_CONSTRUCTOR: RANGE_CLASSNAME), reqout); value rngtype=MORPHO_NIL; /* Set the type associated with the register */ if (compiler_findtypefromcstring(c, RANGE_CLASSNAME, &rngtype)) { if (!compiler_regsetcurrenttype(c, node, rng.dest, rngtype)) return CODEINFO_EMPTY; } /* Construct the arguments */ unsigned int n; for (n=0; n<3; n++) { if (s[n]!=SYNTAXTREE_UNCONNECTED) { registerindx rarg=compiler_regalloctop(c); codeinfo data=compiler_nodetobytecode(c, s[n], rarg); rng.ninstructions+=data.ninstructions; if (!(CODEINFO_ISREGISTER(data) && (data.dest==rarg))) { compiler_releaseoperand(c, data); data=compiler_movetoregister(c, node, data, rarg); rng.ninstructions+=data.ninstructions; } } else { break; } } /* Make the function call */ compiler_addinstruction(c, ENCODE_DOUBLE(OP_CALL, rng.dest, n), node); rng.ninstructions++; compiler_regfreetoend(c, rng.dest+1); return rng; } /* @brief Compiles a index node * @param[in] c - the compiler * @param[in] indxnode - the root syntaxtreenode (should be of type NODE_INDEX) * @param[out] start - first register used * @param[out] end - last register used * @param[out] out - codeinfo struct with number of instructions used * @returns true on success */ codeinfo compiler_compileindexlist(compiler *c, syntaxtreenode *indxnode, registerindx *start, registerindx *end) { registerindx istart=compiler_regtop(c), iend; if (indxnode->right==SYNTAXTREE_UNCONNECTED) { compiler_error(c, indxnode, COMPILE_MSSNGINDX); return CODEINFO_EMPTY; } compiler_beginargs(c); codeinfo right = compiler_nodetobytecode(c, indxnode->right, REGISTER_UNALLOCATED); compiler_endargs(c); iend=compiler_regtop(c); if (iend==istart) compiler_error(c, indxnode, PARSE_VARBLANKINDEX); if (start) *start = istart+1; if (end) *end = iend; return right; } /** Compiles a lookup of an indexed variable */ static codeinfo compiler_index(compiler *c, syntaxtreenode *node, registerindx reqout) { registerindx start, end; /* Compile the index selector */ codeinfo left = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; if (!CODEINFO_ISREGISTER(left)) { left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } /* Compile indices */ codeinfo out = compiler_compileindexlist(c, node, &start, &end); if (compiler_haserror(c)) return CODEINFO_EMPTY; ninstructions+=out.ninstructions; /* Compile instruction */ compiler_addinstruction(c, ENCODE(OP_LIX, left.dest, start, end), node); ninstructions++; /* Free anything we're done with */ compiler_releaseoperand(c, left); compiler_regfreetoend(c, start+1); codeinfo iout = CODEINFO(REGISTER, start, ninstructions); if (reqout>=0 && start!=reqout) { compiler_regfreetemp(c, start); iout = compiler_movetoregister(c, node, iout, reqout); ninstructions+=iout.ninstructions; } iout.ninstructions=ninstructions; return iout; } /** Compile negation. Note that this is compiled as A=(0-B) */ static codeinfo compiler_negate(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreenode *operand = compiler_getnode(c, node->left); codeinfo out; if (operand->type==NODE_FLOAT) { registerindx neg = compiler_addconstant(c, node, MORPHO_FLOAT(-MORPHO_GETFLOATVALUE(operand->content)), true, false); out = CODEINFO(CONSTANT, neg, 0); } else { out = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=out.ninstructions; if (!CODEINFO_ISREGISTER(out)) { /* Ensure we're working with a register */ out=compiler_movetoregister(c, node, out, REGISTER_UNALLOCATED); ninstructions+=out.ninstructions; } registerindx zero = compiler_addconstant(c, node, MORPHO_INTEGER(0), false, false); codeinfo zeroinfo = CODEINFO(CONSTANT, zero, 0); zeroinfo=compiler_movetoregister(c, node, zeroinfo, REGISTER_UNALLOCATED); ninstructions+=zeroinfo.ninstructions; registerindx rout = compiler_regtemp(c, reqout); compiler_addinstruction(c, ENCODE(OP_SUB, rout, zeroinfo.dest, out.dest), node); ninstructions++; compiler_releaseoperand(c, out); compiler_releaseoperand(c, zeroinfo); out = CODEINFO(REGISTER, rout, ninstructions); } return out; } /** Compile not operator */ static codeinfo compiler_not(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo left = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; registerindx out = compiler_regtemp(c, reqout); if (!CODEINFO_ISREGISTER(left)) { /* Ensure we're working with a register or a constant */ left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } compiler_addinstruction(c, ENCODE_DOUBLE(OP_NOT, out, left.dest), node); ninstructions++; compiler_releaseoperand(c, left); return CODEINFO(REGISTER, out, ninstructions); } /** Compile arithmetic operators */ static codeinfo compiler_binary(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo left = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; if (!(CODEINFO_ISREGISTER(left))) { /* Ensure we're working with a register */ left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } codeinfo right = compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=right.ninstructions; if (!(CODEINFO_ISREGISTER(right))) { /* Ensure we're working with a register */ right=compiler_movetoregister(c, node, right, REGISTER_UNALLOCATED); ninstructions+=right.ninstructions; } registerindx out = compiler_regtemp(c, reqout); opcode op=OP_NOP; switch (node->type) { case NODE_ADD: op=OP_ADD; break; case NODE_SUBTRACT: op=OP_SUB; break; case NODE_MULTIPLY: op=OP_MUL; break; case NODE_DIVIDE: op=OP_DIV; break; case NODE_POW: op=OP_POW; break; case NODE_EQ: op=OP_EQ; break; case NODE_NEQ: op=OP_NEQ; break; case NODE_LT: op=OP_LT; break; case NODE_LTEQ: op=OP_LE; break; case NODE_GT: { /* a>b is equivalent to b=b is equivalent to b<=a */ codeinfo swap = right; right=left; left = swap; op=OP_LE; } break; default: UNREACHABLE("in compiling binary instruction [check bytecode compiler table]"); } if (compiler_haserror(c)) return CODEINFO_EMPTY; compiler_addinstruction(c, ENCODE(op, out, left.dest, right.dest), node); ninstructions++; compiler_releaseoperand(c, left); compiler_releaseoperand(c, right); return CODEINFO(REGISTER, out, ninstructions); } /** @brief Compiles the ternary operator * @details Ternary operators are encoded in the syntax tree * *
 *                 TERNARY
 *                  /  \
 *              cond    SEQUENCE
 *                       /    \
 *            true outcome    false outcome
 *           
* and are compiled to: * * bif , fail: ; branch if condition isn't met * .. true outcome * b end ; generated if an else statement is present * fail: * .. false outcome * end: */ static codeinfo compiler_ternary(compiler *c, syntaxtreenode *node, registerindx reqout) { unsigned int ninstructions=0; // Compile the condition codeinfo cond = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; // And make sure it's in a register */ if (!CODEINFO_ISREGISTER(cond)) { cond=compiler_movetoregister(c, node, cond, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ } // Generate empty instruction to contain the conditional branch instructionindx cbrnchindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); ninstructions++; // We're now done with the result of the condition compiler_releaseoperand(c, cond); // Claim an output register registerindx rout=compiler_regtemp(c, reqout); // Get the possible outcomes syntaxtreenode *outcomes = compiler_getnode(c, node->right); // Compile true outcome codeinfo left = compiler_nodetobytecode(c, outcomes->left, rout); ninstructions+=left.ninstructions; // Ensure the result is in the output register left = compiler_movetoregister(c, outcomes, left, rout); ninstructions+=left.ninstructions; // Generate empty instruction to branch to the end instructionindx brnchendindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); ninstructions++; // Compile false outcome codeinfo right = compiler_nodetobytecode(c, outcomes->right, rout); ninstructions+=right.ninstructions; // Ensure the result is in the output register right = compiler_movetoregister(c, outcomes, right, rout); ninstructions+=right.ninstructions; instructionindx end=compiler_currentinstructionindex(c); // Patch up branches compiler_setinstruction(c, cbrnchindx, ENCODE_LONG(OP_BIFF, cond.dest, brnchendindx-cbrnchindx)); compiler_setinstruction(c, brnchendindx, ENCODE_LONG(OP_B, REGISTER_UNALLOCATED, end-brnchendindx-1)); return CODEINFO(REGISTER, rout, ninstructions); } /** Compiles a group (used to modify precedence) */ static codeinfo compiler_grouping(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo out = CODEINFO_EMPTY; if (node->left!=SYNTAXTREE_UNCONNECTED) out=compiler_nodetobytecode(c, node->left, reqout); return out; } /** Compiles a sequence of nodes */ static codeinfo compiler_sequence(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo left = CODEINFO_EMPTY; codeinfo right = CODEINFO_EMPTY; bool inargs=compiler_inargs(c); if (node->left!=SYNTAXTREE_UNCONNECTED) left=compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); if (!inargs) compiler_releaseoperand(c, left); if (node->right!=SYNTAXTREE_UNCONNECTED) right=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); if (!inargs) compiler_releaseoperand(c, right); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, left.ninstructions+right.ninstructions); } /** Compiles a string interpolation */ static codeinfo compiler_interpolation(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo exp; registerindx start = REGISTER_UNALLOCATED; unsigned int ninstructions=0; registerindx r = REGISTER_UNALLOCATED; for (syntaxtreenode *current = node; current!=NULL; current=compiler_getnode(c, current->right)) { /* Add the contents of the node as a constant and move them into a register at the top of the stack */ registerindx cindx = compiler_addconstant(c, current, current->content, false, true); r=compiler_regalloctop(c); codeinfo string=compiler_movetoregister(c, current, CODEINFO(CONSTANT, cindx, 0), r); ninstructions+=string.ninstructions; if (start==REGISTER_UNALLOCATED) start = string.dest; /* Now compile the interpolation expression */ if (current->left!=SYNTAXTREE_UNCONNECTED) { r = compiler_regalloctop(c); exp=compiler_nodetobytecode(c, current->left, r); ninstructions+=exp.ninstructions; /* Make sure this goes into the correct register */ if (!(exp.returntype==REGISTER && exp.dest==r)) { compiler_regtempwithindx(c, r); exp=compiler_movetoregister(c, current, exp, r); ninstructions+=exp.ninstructions; } /* Free any registers above the result */ compiler_regfreetoend(c, r+1); } } compiler_addinstruction(c, ENCODE(OP_CAT, (reqout!=REGISTER_UNALLOCATED ? reqout : start), start, r), node); ninstructions++; /* Free all the registers used, including start if it wasn't the destination for the output */ if (start!=REGISTER_UNALLOCATED) compiler_regfreetoend(c, start + (reqout!=REGISTER_UNALLOCATED ? 0: 1)); return CODEINFO(REGISTER, (reqout!=REGISTER_UNALLOCATED ? reqout : start), ninstructions); } /** Inserts instructions to close upvalues */ static codeinfo compiler_closeupvaluesforscope(compiler *c, syntaxtreenode *node) { functionstate *f = compiler_currentfunctionstate(c); codeinfo out=CODEINFO_EMPTY; indx closereg=VM_MAXIMUMREGISTERNUMBER; /* Keep track of the lowest register number to close */ bool closed=false; /* Do we need to close anything? */ for (unsigned int i=0; iupvalues.count; i++) { if (f->upvalues.data[i].islocal) { indx reg = f->upvalues.data[i].reg; if (f->registers.data[reg].scopedepth>=f->scopedepth) { if (regright!=REGISTER_UNALLOCATED) { out=compiler_nodetobytecode(c, node->right, reqout); } up=compiler_closeupvaluesforscope(c, node); out.ninstructions+=up.ninstructions; compiler_endscope(c); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, out.ninstructions); } /** Compile print statements */ static codeinfo compiler_print(compiler *c, syntaxtreenode *node, registerindx reqout) { if (node->left==REGISTER_UNALLOCATED) return CODEINFO_EMPTY; codeinfo left=compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; if (!CODEINFO_ISREGISTER(left)) { left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } else if (c->err.cat==ERROR_NONE && left.dest==REGISTER_UNALLOCATED) { UNREACHABLE("print was passed an invalid operand"); } compiler_addinstruction(c, ENCODE_SINGLE(OP_PRINT, left.dest), node); ninstructions++; compiler_releaseoperand(c, left); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** @brief Compile if statements * @details If statements come in two flavors, those with else clauses and those * without. They are encoded in the syntax tree as follows: * *
 *                   IF                        IF
 *                  /  \                      /  \
 *              cond    then statement    cond    THEN
 *                                               /    \
 *                                 then statement      else statement
 *           
* * and are compiled to bytecode that looks like * *
 *              test   rtst, rB, rC
 *              bif    rtst, fail:    ; branch if condition isn't met
 *              .. then statement...
 *              b      end            ; generated if an else statement is present
 *           fail:
 *              .. else statement (if present)
 *           end:
 *           
*/ static codeinfo compiler_if(compiler *c, syntaxtreenode *node, registerindx reqout) { unsigned int ninstructions=0; bool unreachable=false; /* The left node is the condition; compile it already */ codeinfo cond = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; if (CODEINFO_ISCONSTANT(cond) && MORPHO_ISFALSE(compiler_getconstant(c, cond.dest)) ) unreachable=true; /* And make sure it's in a register */ if (!CODEINFO_ISREGISTER(cond)) { cond=compiler_movetoregister(c, node, cond, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ } compiler_releaseoperand(c, cond); /* The right node may be a then node or just a regular statement */ syntaxtreenode *right = compiler_getnode(c, node->right); /* Hold the position of the then and else statements */ codeinfo then=CODEINFO_EMPTY, els=CODEINFO_EMPTY; /* Remember where the if conditional branch is located */ instructionindx ifindx=REGISTER_UNALLOCATED, elsindx=REGISTER_UNALLOCATED; /* Keep track of the number of instructions */ unsigned int nextra=0; /* Generate empty instruction to contain the conditional branch */ ifindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); ninstructions++; if (right->type==NODE_THEN) { /* If the right node is a THEN node, the then/else statements are located off it. */ if (!unreachable) { then = compiler_nodetobytecode(c, right->left, REGISTER_UNALLOCATED); } ninstructions+=then.ninstructions; /* Create a blank instruction */ elsindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), right); ninstructions++; nextra++; /* Keep track of this extra instruction for the original branch */ /* Now compile the els clause */ els = compiler_nodetobytecode(c, right->right, REGISTER_UNALLOCATED); ninstructions+=els.ninstructions; } else { /* Otherwise, the then statement is just the right operand */ if (!unreachable) { then = compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); } ninstructions+=then.ninstructions; } /* Now generate the conditional branch over the then clause */ compiler_setinstruction(c, ifindx, ENCODE_LONG(OP_BIFF, cond.dest, then.ninstructions+nextra)); /* If necessary generate the unconditional branch over the else clause */ if (right->type==NODE_THEN) { compiler_setinstruction(c, elsindx, ENCODE_LONG(OP_B, REGISTER_UNALLOCATED, els.ninstructions)); } compiler_releaseoperand(c, then); compiler_releaseoperand(c, els); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** @brief Compiles a while statement * @details While statements are encoded as follows: * *
 *                   WHILE
 *                  /     \
 *              cond       body
 *           
* * and are compiled to bytecode that looks like * *
 *           start:
 *               test   rtst, rB, rC
 *               bif    rtst, end:    ; branch if condition isn't met
 *               .. body ..
 *               b      start
 *           end:
 *           
*/ static codeinfo compiler_while(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo cond = CODEINFO_EMPTY, body = CODEINFO_EMPTY, inc = CODEINFO_EMPTY; unsigned int ninstructions=0; instructionindx condindx=REGISTER_UNALLOCATED; /* Where is the condition located */ instructionindx startindx=compiler_currentinstructionindex(c); instructionindx nextindx=startindx; /* Where should continue jump to? */ /* The left node is the condition; compile it already */ if (node->left!=SYNTAXTREE_UNCONNECTED) { cond=compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ /* And make sure it's in a register */ if (!CODEINFO_ISREGISTER(cond)) { cond=compiler_movetoregister(c, node, cond, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ } /* Generate empty instruction to contain the conditional branch */ condindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); nextindx=condindx; ninstructions++; compiler_releaseoperand(c, cond); } compiler_beginloop(c); if (node->right!=SYNTAXTREE_UNCONNECTED) { syntaxtreenode *bodynode=compiler_getnode(c, node->right); /* Check if we're in a for loop */ if (bodynode->type!=NODE_SEQUENCE) { body = compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=body.ninstructions; compiler_releaseoperand(c, body); } else { if (bodynode->left!=SYNTAXTREE_UNCONNECTED) { body = compiler_nodetobytecode(c, bodynode->left, REGISTER_UNALLOCATED); compiler_releaseoperand(c, body); } nextindx = compiler_currentinstructionindex(c); if (bodynode->right!=SYNTAXTREE_UNCONNECTED) { inc = compiler_nodetobytecode(c, bodynode->right, REGISTER_UNALLOCATED); compiler_releaseoperand(c, inc); } body.ninstructions+=inc.ninstructions; // Used for the branch back ninstructions+=body.ninstructions; // Track all the instructions } } compiler_endloop(c); /* Compile the unconditional branch back to the test instruction */ instructionindx end=compiler_addinstruction(c, ENCODE_LONG(OP_B, REGISTER_UNALLOCATED, -ninstructions-1), node); ninstructions++; compiler_fixloop(c, startindx, nextindx, end+1); /* If we did have a condition... */ if (node->left!=SYNTAXTREE_UNCONNECTED) { /* And generate the conditional branch at the start of the loop. The extra 1 is to skip the loop instruction */ compiler_setinstruction(c, condindx, ENCODE_LONG(OP_BIFF, cond.dest, body.ninstructions+1)); } return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** @brief Compiles a for .. in loop * @details For..in loops enable looping over the contents of a collection without knowing how many elements are present * forin * / \ * in body * / \ * init collection * This works by successively calling enumerate() on the collections, first with no arguments to get the bound, then * with the integer counter as in the below code * * Register allocation * | rObj | rIndx | | rMax | rEnum | rVal | rTmp | ... * * Find maximum value of counter * lct rEnum, c ; "enumerate" * mov rVal, rObj ; * lct rTmp, c ; -1 * invoke rEnum, 1, 0 * mov rMax, rVal * loopstart: * lt rTmp, rIndx, rMax * biff rTmp, * mov rVal, rObj * mov rTmp, rIndx * invoke rEnum, 1, 0 * ... Loop body ... * lct rTmp, c ; 1 * add rIndx, rIndx, rTmp ; * b * loopend: */ static codeinfo compiler_for(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo body; unsigned int ninstructions=0; syntaxtreenode *innode=NULL, *initnode=NULL, *indxnode=NULL, *collnode=NULL; instructionindx condindx=REGISTER_UNALLOCATED; /* Where is the condition located */ compiler_beginscope(c); /* Collect the syntaxtree nodes */ innode=compiler_getnode(c, node->left); if (innode) { initnode=compiler_getnode(c, innode->left); if (initnode->type==NODE_SEQUENCE) { // Unpack a variable, indx sequence indxnode=compiler_getnode(c, initnode->right); initnode=compiler_getnode(c, initnode->left); } if (initnode->type==NODE_DECLARATION) initnode=compiler_getnode(c, initnode->left); if (indxnode && indxnode->type==NODE_DECLARATION) indxnode=compiler_getnode(c, indxnode->left); collnode=compiler_getnode(c, innode->right); } // Register allocation for the loop // | rObj | rIndx || rMax | rEnum | rVal | rTmp | ... // Fetch the collection object codeinfo coll=compiler_nodetobytecode(c, innode->right, REGISTER_UNALLOCATED); ninstructions+=coll.ninstructions; if (!CODEINFO_ISREGISTER(coll)) { coll=compiler_movetoregister(c, collnode, coll, REGISTER_UNALLOCATED); ninstructions+=coll.ninstructions; } registerindx rObj = coll.dest; // Initialize the index variable registerindx rIndx=compiler_regalloc(c, MORPHO_NIL); if (indxnode) compiler_regsetsymbol(c, rIndx, indxnode->content); int cNil = compiler_addconstant(c, node, MORPHO_INTEGER(0), false, false); compiler_addinstruction(c, ENCODE_LONG(OP_LCT, rIndx, cNil), node); ninstructions++; /* Obtain the maximum value of rIndx by invoking enumerate */ // Allocate register to contain maximum value of the counter registerindx rMax=compiler_regalloctop(c); // Initialize enumerate selector registerindx rEnum=compiler_regalloctop(c); int cEnum = compiler_addconstant(c, node, enumerateselector, false, false); compiler_addinstruction(c, ENCODE_LONG(OP_LCT, rEnum, cEnum), node); ninstructions++; // Place the object into the register after rEnum registerindx rVal=compiler_regalloctop(c); compiler_regsetsymbol(c, rVal, initnode->content); compiler_addinstruction(c, ENCODE_LONG(OP_MOV, rVal, rObj), node); ninstructions++; // Parameter is -1 to query size of collection registerindx rTmp=compiler_regalloctop(c); registerindx cNegOne = compiler_addconstant(c, node, MORPHO_INTEGER(-1), false, false); compiler_addinstruction(c, ENCODE_LONG(OP_LCT, rTmp, cNegOne), node); ninstructions++; compiler_addinstruction(c, ENCODE(OP_INVOKE, rEnum, 1, 0), collnode); ninstructions++; // Store the maximum value compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, rMax, rVal), collnode); ninstructions++; /* Test index against the maximum value */ instructionindx tst=compiler_addinstruction(c, ENCODE(OP_LT, rTmp, rIndx, rMax), node); condindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); // Placeholder for branch ninstructions+=2; /* Load enumerated value */ compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, rVal, rObj), node); compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, rTmp, rIndx), node); compiler_addinstruction(c, ENCODE(OP_INVOKE, rEnum, 1, 0), collnode); ninstructions+=3; compiler_beginloop(c); /* Compile the body */ if (node->right==SYNTAXTREE_UNCONNECTED) { compiler_error(c, node, COMPILE_MSSNGLOOPBDY); } else { body=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=body.ninstructions; compiler_releaseoperand(c, body); } compiler_endloop(c); /* Increment the counter */ instructionindx inc=compiler_currentinstructionindex(c); int cOne = compiler_addconstant(c, node, MORPHO_INTEGER(1), false, false); compiler_addinstruction(c, ENCODE_LONG(OP_LCT, rTmp, cOne), node); instructionindx add=compiler_addinstruction(c, ENCODE(OP_ADD, rIndx, rIndx, rTmp), node); ninstructions+=2; /* Compile the unconditional branch back to the test instruction */ instructionindx end=compiler_addinstruction(c, ENCODE_LONG(OP_B, REGISTER_UNALLOCATED, -(add-tst)-2), node); ninstructions++; /* Go back and generate the condition instruction */ compiler_setinstruction(c, condindx, ENCODE_LONG(OP_BIFF, rTmp, (add-tst) )); compiler_fixloop(c, tst, inc, end+1); compiler_regfreetemp(c, rObj); compiler_regfreetemp(c, rIndx); compiler_regfreetoend(c, rMax); compiler_endscope(c); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** @brief Compiles a do...while statement * @details While statements are encoded as follows: * *
 *                   DO
 *                  /     \
 *              body        cond
 *           
* * and are compiled to bytecode that looks like * *
 *           start:
 *               .. body ..
 *           test   rtst, rB, rC
 *               bif    rtst, end:    ; branch if condition isn't met
 *           end:
 *           
*/ static codeinfo compiler_do(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo cond = CODEINFO_EMPTY, body = CODEINFO_EMPTY; unsigned int ninstructions=0; instructionindx startindx=compiler_currentinstructionindex(c); instructionindx nextindx=REGISTER_UNALLOCATED; /* Where should continue jump to? */ compiler_beginloop(c); /* Compile the body */ if (node->left!=SYNTAXTREE_UNCONNECTED) { body = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); ninstructions+=body.ninstructions; compiler_releaseoperand(c, body); } compiler_endloop(c); nextindx=compiler_currentinstructionindex(c); /* The left node is the condition; compile it already */ if (node->right!=SYNTAXTREE_UNCONNECTED) { cond=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ /* And make sure it's in a register */ if (!CODEINFO_ISREGISTER(cond)) { cond=compiler_movetoregister(c, node, cond, REGISTER_UNALLOCATED); ninstructions+=cond.ninstructions; /* Keep track of instructions */ } /* Generate empty instruction to contain the conditional branch */ compiler_addinstruction(c, ENCODE_LONG(OP_BIF, cond.dest, -ninstructions-1), node); ninstructions++; compiler_releaseoperand(c, cond); } instructionindx end=compiler_currentinstructionindex(c); compiler_fixloop(c, startindx, nextindx, end); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** @brief Compiles a break or continue statement. * @details Break and continue statements are inserted as NOP instructions with the a register set to a marker. * */ static codeinfo compiler_break(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo out = CODEINFO_EMPTY; if (compiler_inloop(c)) { compiler_addinstruction(c, ENCODE(OP_NOP, (node->type==NODE_BREAK ? 'b' : 'c'), 0, 0), node); out.ninstructions++; } else compiler_error(c, node, (node->type==NODE_BREAK ? COMPILE_BRKOTSDLP : COMPILE_CNTOTSDLP)); return out; } /** Checks through a catch block, fixing any references * @param[in] c the current compiler * @param[in] start first instruction in the loop body * @param[in] inc position of the loop increment section (continue statements redirect here) * @param[in] end position of the first instruction AFTER the loop (break sections redirect here) */ static void compiler_fixcatch(compiler *c, instructionindx start, instructionindx inc, instructionindx end) { instruction *code=c->out->code.data; for (instructionindx i=start; iout, (object *) cdict); registerindx cdictindx = compiler_addconstant(c, node, MORPHO_OBJECT(cdict), false, false); compiler_addinstruction(c, ENCODE_LONG(OP_PUSHERR, 0, cdictindx), node); out.ninstructions++; debugannotation_pusherr(&c->out->annotations, cdict); /* Compile the body */ if (node->left!=SYNTAXTREE_UNCONNECTED) { codeinfo body = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); out.ninstructions+=body.ninstructions; compiler_releaseoperand(c, body); } instructionindx popindx = compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); out.ninstructions++; /* Compile the catch dictionary */ varray_syntaxtreeindx switchnodes; varray_syntaxtreeindx labelnodes; varray_syntaxtreeindxinit(&switchnodes); varray_syntaxtreeindxinit(&labelnodes); syntaxtreenodetype match[] = { NODE_DICTIONARY }; syntaxtree_flatten(compiler_getsyntaxtree(c), node->right, 1, match, &switchnodes); for (unsigned int i=0; iright); if (body) { codeinfo entrybody = compiler_nodetobytecode(c, entry->right, REGISTER_UNALLOCATED); out.ninstructions+=entrybody.ninstructions; compiler_releaseoperand(c, entrybody); } // Add an effective 'break' instruction after each entry body except for the last if (i!=switchnodes.count-1) { compiler_addinstruction(c, ENCODE(OP_NOP, 's', 0, 0), node); out.ninstructions++; } /* Now flatten the label nodes */ labelnodes.count=0; syntaxtreenodetype labelmatch[] = { NODE_SEQUENCE }; syntaxtree_flatten(compiler_getsyntaxtree(c), entry->left, 1, labelmatch, &labelnodes); for (unsigned int j=0; jtype!=NODE_STRING) { compiler_error(c, label, COMPILE_INVLDLBL); break; } registerindx labelsymbol=compiler_addsymbol(c, entry, label->content); value symbolkey = compiler_getconstant(c, labelsymbol); dictionary_insert(&cdict->dict, symbolkey, MORPHO_INTEGER(entryindx)); } } instructionindx endindx = compiler_currentinstructionindex(c); /* Fix the poperr instruction that jumps around the switch block */ compiler_setinstruction(c, popindx, ENCODE_LONG(OP_POPERR, 0, endindx-popindx-1)); /* Fix any nop instructions in the switch block to jump to the end of block */ compiler_fixcatch(c, popindx, popindx, endindx); varray_syntaxtreeindxclear(&switchnodes); varray_syntaxtreeindxclear(&labelnodes); debugannotation_poperr(&c->out->annotations); return out; } /** @brief Compiles logical operators */ static codeinfo compiler_logical(compiler *c, syntaxtreenode *node, registerindx reqout) { /* An AND operator must branch if the first operand is false, an OR operator must branch if the first operator is true */ bool biffflag = (node->type==NODE_AND ? true : false); // Generate a BIFF instruction registerindx out = compiler_regtemp(c, reqout); instructionindx condindx=0; /* Where is the condition located */ unsigned int linstructions=0, rinstructions=0; /* Size of code for both operands */ /* Generate code to get the left hand expression */ codeinfo left = compiler_nodetobytecode(c, node->left, out); linstructions+=left.ninstructions; if (!(CODEINFO_ISREGISTER(left) && left.dest==out)) { compiler_releaseoperand(c, left); compiler_regtempwithindx(c, out); left=compiler_movetoregister(c, node, left, out); linstructions+=left.ninstructions; } /* Generate empty instruction to contain the conditional branch */ condindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); linstructions++; /* Now evaluate right operand */ codeinfo right = compiler_nodetobytecode(c, node->right, out); rinstructions+=right.ninstructions; if (!(CODEINFO_ISREGISTER(right) && right.dest==out)) { compiler_releaseoperand(c, right); compiler_regtempwithindx(c, out); right=compiler_movetoregister(c, node, right, out); rinstructions+=right.ninstructions; } /* Generate the branch instruction */ compiler_setinstruction(c, condindx, ENCODE_LONG((biffflag ? OP_BIFF : OP_BIF), out, rinstructions)); return CODEINFO(REGISTER, out, linstructions+rinstructions); } /** Compile declarations */ static codeinfo compiler_declaration(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreenode *decnode = node; syntaxtreenode *typenode = NULL; if (node->type==NODE_TYPE) { typenode=compiler_getnode(c, node->left); decnode=compiler_getnode(c, node->right); } syntaxtreenode *varnode = NULL; syntaxtreenode *lftnode = NULL, *indxnode = NULL; codeinfo right=CODEINFO_EMPTY; value var=MORPHO_NIL, type=MORPHO_NIL; registerindx reg; unsigned int ninstructions = 0; varnode=compiler_getnode(c, decnode->left); /* Find the symbol */ if (varnode) { if (varnode->type==NODE_SYMBOL) { var = varnode->content; } else if (varnode->type==NODE_INDEX) { lftnode=compiler_getnode(c, varnode->left); if (lftnode && lftnode->type==NODE_SYMBOL) { indxnode=varnode; varnode=lftnode; var = varnode->content; } else { UNREACHABLE("Unexpected node type in variable declaration"); } } } if (!MORPHO_ISNIL(var)) { /* Create the variable */ codeinfo vloc = compiler_addvariable(c, varnode, var); codeinfo array = CODEINFO_EMPTY; if (vloc.returntype==REGISTER) { reg=vloc.dest; /* The variable was assigned to a register, so we can use it directly */ } else { /* The variable was assigned somewhere else, so use that instead */ reg=compiler_regtemp(c, REGISTER_UNALLOCATED); } if (typenode && compiler_findtype(c, typenode->content, &type)) { compiler_regsettype(c, reg, type); if (vloc.returntype==GLOBAL) compiler_setglobaltype(c, vloc.dest, type); } /* If this is an array, we must create it */ if (indxnode) { /* Set up a call to the Array() function */ array=compiler_findbuiltin(c, decnode, ARRAY_CLASSNAME, reqout); ninstructions+=array.ninstructions; // Dimensions registerindx istart=REGISTER_UNALLOCATED, iend=REGISTER_UNALLOCATED; codeinfo indxinfo=compiler_compileindexlist(c, indxnode, &istart, &iend); ninstructions+=indxinfo.ninstructions; // Initializer if (decnode->right!=SYNTAXTREE_UNCONNECTED) { iend=compiler_regalloctop(c); right = compiler_nodetobytecode(c, decnode->right, iend); ninstructions+=right.ninstructions; right=compiler_movetoregister(c, decnode, right, iend); // Ensure in register ninstructions+=right.ninstructions; } // Call Array() compiler_addinstruction(c, ENCODE_DOUBLE(OP_CALL, array.dest, iend-istart+1), node); ninstructions++; compiler_regfreetoend(c, istart); if (vloc.returntype==REGISTER && array.dest!=vloc.dest) { // Move to correct register codeinfo move=compiler_movetoregister(c, decnode, array, vloc.dest); ninstructions+=move.ninstructions; } else reg=array.dest; } else if (decnode->right!=SYNTAXTREE_UNCONNECTED) { /* Not an array, but has an initializer */ right = compiler_nodetobytecode(c, decnode->right, reg); ninstructions+=right.ninstructions; /* Ensure operand is in the desired register */ right=compiler_movetoregister(c, decnode, right, reg); ninstructions+=right.ninstructions; } else { /* Otherwise, we should zero out the register */ registerindx cnil = compiler_addconstant(c, decnode, MORPHO_NIL, false, false); compiler_addinstruction(c, ENCODE_LONG(OP_LCT, reg, cnil), node); ninstructions++; } if (vloc.returntype!=REGISTER) { codeinfo mv=compiler_movefromregister(c, decnode, vloc, reg); ninstructions+=mv.ninstructions; compiler_regfreetemp(c, reg); } compiler_releaseoperand(c, right); } return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** Compiles an parameter declaration */ static registerindx compiler_functionparameters(compiler *c, syntaxtreeindx indx) { syntaxtreenode *node = compiler_getnode(c, indx); if (!node) return REGISTER_UNALLOCATED; switch(node->type) { case NODE_SYMBOL: return compiler_addpositionalarg(c, node, node->content); break; case NODE_TYPE: { value type=MORPHO_NIL; syntaxtreenode *typenode = compiler_getnode(c, node->left); if (!typenode) UNREACHABLE("Incorrectly formed type node."); if (typenode->type==NODE_DOT) { syntaxtreenode *nsnode = compiler_getnode(c, typenode->left); syntaxtreenode *labelnode = compiler_getnode(c, typenode->right); if (!(nsnode && labelnode && MORPHO_ISSTRING(nsnode->content) && MORPHO_ISSTRING(labelnode->content))) UNREACHABLE("Incorrectly formed type namespace node."); if (!compiler_isnamespace(c, nsnode->content)) { compiler_error(c, nsnode, COMPILE_UNKNWNNMSPC, MORPHO_GETCSTRING(nsnode->content)); return REGISTER_UNALLOCATED; } if (!compiler_findclasswithnamespace(c, typenode, nsnode->content, labelnode->content, &type)) { compiler_error(c, typenode, COMPILE_SYMBOLNOTDEFINEDNMSPC, MORPHO_GETCSTRING(nsnode->content), MORPHO_GETCSTRING(labelnode->content)); return REGISTER_UNALLOCATED; } } else if (MORPHO_ISSTRING(typenode->content)) { if (!compiler_findtype(c, typenode->content, &type)) { compiler_error(c, node, COMPILE_UNKNWNTYPE, MORPHO_GETCSTRING(typenode->content)); return REGISTER_UNALLOCATED; } } else UNREACHABLE("Type node should have string label."); registerindx reg = compiler_functionparameters(c, node->right); compiler_regsettype(c, reg, type); compiler_regsetcurrenttype(c, node, reg, type); } break; case NODE_ASSIGN: { syntaxtreenode *name=compiler_getnode(c, node->left); syntaxtreenode *def=compiler_getnode(c, node->right); if (SYNTAXTREE_ISLEAF(def->type)) { compiler_addoptionalarg(c, node, name->content, def->content); } else compiler_error(c, def, COMPILE_OPTPRMDFLT); break; } case NODE_ARGLIST: { compiler_functionparameters(c, node->left); compiler_functionparameters(c, node->right); } break; case NODE_RANGE: { syntaxtreenode *name=compiler_getnode(c, node->right); compiler_addvariadicarg(c, node, name->content); break; } default: compiler_error(c, node, COMPILE_ARGSNOTSYMBOLS); break; } return REGISTER_UNALLOCATED;; } value _selfsymbol; /** Compiles a function declaration */ static codeinfo compiler_function(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreeindx body=node->right; /* Function body */ codeinfo bodyinfo = CODEINFO_EMPTY; /* Code info generated by the body */ indx closure = REGISTER_UNALLOCATED; registerindx kindx=REGISTER_UNALLOCATED; registerindx reg=REGISTER_UNALLOCATED; /* Register where the function is stored */ instructionindx bindx; unsigned int ninstructions=0; bool ismethod = (c->currentmethod==node); bool isanonymous = MORPHO_ISNIL(node->content); bool isinitializer = false; objectstring initlabel = MORPHO_STATICSTRING(MORPHO_INITIALIZER_METHOD); if (!isanonymous) isinitializer=MORPHO_ISEQUAL(MORPHO_OBJECT(&initlabel), node->content); /* We preface the function code with a branch; for now simply create a blank instruction and store the indx */ bindx=compiler_addinstruction(c, ENCODE_BYTE(OP_NOP), node); objectfunction *func = object_newfunction(bindx+1, node->content, compiler_getcurrentfunction(c), 0); if (!func) { compiler_error(c, node, ERROR_ALLOCATIONFAILED); return CODEINFO_EMPTY; } program_bindobject(c->out, (object *) func); /* Record the class is a method */ if (ismethod) func->klass=compiler_getcurrentclass(c); /* Add the function as a constant */ kindx=compiler_addconstant(c, node, MORPHO_OBJECT(func), false, false); /* Keep a reference to the function */ if (!ismethod) compiler_addfunctionref(c, func); /* Begin the new function definition, finding whether the current function is a regular function or a method declaration by looking at currentmethod */ functiontype ftype = (ismethod ? METHOD : FUNCTION); if (ismethod && MORPHO_ISSTRING(node->content) && isinitializer ) ftype=INITIALIZER; compiler_beginfunction(c, func, ftype); /* The function has a reference to itself in r0, which may be 'self' if we're in a method */ value r0symbol=node->content; if (ismethod) r0symbol=_selfsymbol; compiler_regalloc(c, r0symbol); /* -- Compile the parameters -- */ compiler_functionparameters(c, node->left); value signature[func->nargs+1]; for (int i=0; inargs; i++) compiler_regtype(c, i+1, &signature[i]); function_setsignature(func, signature); signature_setvarg(&func->sig, function_hasvargs(func)); /* Check we don't have too many arguments */ if (func->nargs+func->nopt>MORPHO_MAXARGS) { compiler_error(c, node, COMPILE_TOOMANYPARAMS); return CODEINFO_EMPTY; } /* -- Compile the body -- */ if (body!=REGISTER_UNALLOCATED) bodyinfo=compiler_nodetobytecode(c, body, REGISTER_UNALLOCATED); ninstructions+=bodyinfo.ninstructions; /* Add a return instruction if necessary */ if (ismethod) { // Methods automatically return self unless another argument is specified compiler_addinstruction(c, ENCODE_DOUBLE(OP_RETURN, 1, 0), node); /* Add a return */ } else { compiler_addinstruction(c, ENCODE_BYTE(OP_RETURN), node); /* Add a return */ } ninstructions++; /* Verify if we have any outstanding forward references */ compiler_checkoutstandingforwardreference(c); /* Correct the branch instruction before the function definition code */ compiler_setinstruction(c, bindx, ENCODE_LONG(OP_B, REGISTER_UNALLOCATED, ninstructions)); ninstructions++; /* Restore the old function */ compiler_endfunction(c); if (!ismethod) { /* Generate a closure prototype if necessary */ closure=compiler_closure(c, node, REGISTER_UNALLOCATED); /* Allocate a variable to refer to the function definition, but only in global context */ /* TODO: Do we need to do this now functionstates capture function info? */ codeinfo fvar=CODEINFO_EMPTY; fvar.dest=compiler_regtemp(c, reqout); fvar.returntype=REGISTER; if (!isanonymous) { if (!compiler_resolveforwardreference(c, func->name, &fvar) && compiler_checkglobal(c)) { compiler_regfreetemp(c, fvar.dest); fvar=compiler_addvariable(c, node, node->content); } } reg=fvar.dest; /* If it's not in a register, allocate a temporary register */ if (fvar.returntype!=REGISTER) reg=compiler_regtemp(c, REGISTER_UNALLOCATED); /* Move function into register */ compiler_addinstruction(c, ENCODE_LONG(OP_LCT, reg, kindx), node); ninstructions++; /* Wrap in a closure if necessary */ if (closure!=REGISTER_UNALLOCATED) { // Save the register where the closure is to be found compiler_regsetsymbol(c, reg, func->name); compiler_regsettype(c, reg, _closuretype); function_setclosure(func, reg); compiler_addinstruction(c, ENCODE_DOUBLE(OP_CLOSURE, reg, (registerindx) closure), node); ninstructions++; } /* If the variable wasn't a local one, move to the correct place */ if (fvar.returntype!=REGISTER) { codeinfo mv=compiler_movefromregister(c, node, fvar, reg); ninstructions+=mv.ninstructions; compiler_regfreetemp(c, reg); } } return CODEINFO(REGISTER, (isanonymous ? reg : REGISTER_UNALLOCATED), ninstructions); } /** Compiles a list of arguments, flattening child nodes */ static codeinfo compiler_arglist(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo arginfo; unsigned int ninstructions=0; varray_syntaxtreeindx argnodes; varray_syntaxtreeindxinit(&argnodes); /* Collapse all these types of nodes */ syntaxtreenodetype match[] = {node->type}; /* Flatten both left and right nodes */ syntaxtree_flatten(compiler_getsyntaxtree(c), node->left, 1, match, &argnodes); syntaxtree_flatten(compiler_getsyntaxtree(c), node->right, 1, match, &argnodes); for (unsigned int i=0; itype==NODE_ASSIGN) { syntaxtreenode *symbol = compiler_getnode(c, arg->left); /* Intern the symbol and add to the constant table */ value s=program_internsymbol(c->out, symbol->content); registerindx sym=compiler_addconstant(c, arg, s, true, false); /* For the call, move the interned symbol to a register */ codeinfo info=CODEINFO(CONSTANT, sym, 0); info=compiler_movetoregister(c, arg, info, reg); ninstructions+=info.ninstructions; compiler_regsetoptionalarg(c, info.dest); reg=compiler_regalloctop(c); arginfo=compiler_nodetobytecode(c, arg->right, reg); if (CODEINFO_ISREGISTER(arginfo)) compiler_regsetoptionalarg(c, arginfo.dest); isOptional=true; } else { arginfo=compiler_nodetobytecode(c, argnodes.data[i], reg); } ninstructions+=arginfo.ninstructions; /* If the child node didn't put it in the right place, move to the register */ if (!(CODEINFO_ISREGISTER(arginfo) && arginfo.dest==reg)) { compiler_releaseoperand(c, arginfo); compiler_regtempwithindx(c, reg); arginfo=compiler_movetoregister(c, node, arginfo, reg); ninstructions+=arginfo.ninstructions; if (isOptional) compiler_regsetoptionalarg(c, arginfo.dest); } if (!compiler_haserror(c) && compiler_regtop(c)!=reg) { compiler_regshow(c); UNREACHABLE("Incorrectly freed registers in compiling argument list."); } } varray_syntaxtreeindxclear(&argnodes); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } bool _islocalfunction(compiler *c, value symbol) { functionstate *fc = compiler_currentfunctionstate(c); // Go backwards to prioritize recent def'ns for (functionstate *f=fc; f>c->fstack; // Note inequality: don't include the global functionstate f--) { for (int i=f->functionref.count-1; i>=0; i--) { // Go backwards functionref *ref=&f->functionref.data[i]; if (MORPHO_ISEQUAL(ref->symbol, symbol)) return true; } } return false; } /** Is this a method invocation? */ static bool compiler_isinvocation(compiler *c, syntaxtreenode *call) { bool isinvocation=false; syntaxtreenode *selector, *target, *method; /* Get the selector node */ selector=compiler_getnode(c, call->left); if (selector->type==NODE_DOT) { /* Check that if the target is a namespace */ target=compiler_getnode(c, selector->left); if (target->type==NODE_SYMBOL && compiler_isnamespace(c, target->content)) { return false; } /* Check that the method is a symbol */ method=compiler_getnode(c, selector->right); if (method->type==NODE_SYMBOL) isinvocation=true; } else if (selector->type==NODE_SYMBOL) { objectclass *klass = compiler_getcurrentclass(c); if (klass && !_islocalfunction(c, selector->content) && dictionary_get(&klass->methods, selector->content, NULL)) { isinvocation=true; } } return isinvocation; } /** Compiles a function call */ static codeinfo compiler_call(compiler *c, syntaxtreenode *node, registerindx reqout) { unsigned int ninstructions=0; if (compiler_haserror(c)) return CODEINFO_EMPTY; if (compiler_isinvocation(c, node)) { return compiler_invoke(c, node, reqout); } registerindx top=compiler_regtop(c); compiler_beginargs(c); // Check if the call is a constructor syntaxtreenode *selnode=compiler_getnode(c, node->left); value rtype=MORPHO_NIL; if (selnode->type==NODE_SYMBOL) { // A regular call from a symbol compiler_findtype(c, selnode->content, &rtype); } else if (selnode->type==NODE_DOT) { // An constructor in a namespace? syntaxtreenode *nsnode = compiler_getnode(c, selnode->left); syntaxtreenode *snode = compiler_getnode(c, selnode->right); if (nsnode && snode && compiler_isnamespace(c, nsnode->content)) { compiler_findclasswithnamespace(c, snode, nsnode->content, snode->content, &rtype); compiler_catch(c, COMPILE_SYMBOLNOTDEFINEDNMSPC); // We don't care if it wasn't there } } // Compile the selector codeinfo func = compiler_nodetobytecode(c, node->left, (reqouttype==NODE_SYMBOL && compiler_catch(c, COMPILE_SYMBOLNOTDEFINED)) { syntaxtreenode *symbol=compiler_getnode(c, node->left); func=compiler_addforwardreference(c, symbol, symbol->content); } ninstructions+=func.ninstructions; /* Move selector into a temporary register unless we already have one that's at the top of the stack */ if (!compiler_iscodeinfotop(c, func)) { registerindx otop = compiler_regalloctop(c); func=compiler_movetoregister(c, node, func, otop); ninstructions+=func.ninstructions; } /* Compile the arguments */ codeinfo args = CODEINFO_EMPTY; if (node->right!=SYNTAXTREE_UNCONNECTED) args=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=args.ninstructions; /* Remember the last argument */ registerindx lastarg=compiler_regtop(c); /* Check we don't have too many arguments */ if (lastarg-func.dest>MORPHO_MAXARGS) { compiler_error(c, node, COMPILE_TOOMANYARGS); return CODEINFO_EMPTY; } compiler_endargs(c); /* Generate the call instruction */ int nposn=0, nopt=0; compiler_regcountargs(c, func.dest+1, lastarg, &nposn, &nopt); compiler_addinstruction(c, ENCODE(OP_CALL, func.dest, nposn, nopt), node); ninstructions++; /* Free all the registers used for the call */ compiler_regfreetoend(c, func.dest+1); /* Set the current type of the register */ compiler_regsetcurrenttype(c, selnode, func.dest, rtype); /* Move the result to the requested register */ if (reqout!=REGISTER_UNALLOCATED && func.dest!=reqout) { codeinfo mv = compiler_movetoregister(c, node, func, reqout); ninstructions+=mv.ninstructions; compiler_regfreetemp(c, func.dest); func.dest=reqout; } return CODEINFO(REGISTER, func.dest, ninstructions); } #include /* Compiles a method invocation: node | node / \ | / \ DOT args | method args / \ | (self) object method */ static codeinfo compiler_invoke(compiler *c, syntaxtreenode *node, registerindx reqout) { unsigned int ninstructions=0; codeinfo object=CODEINFO_EMPTY; /* Retrieve the selector node */ syntaxtreenode *selectornode=compiler_getnode(c, node->left), *methodnode=NULL, *objectnode=NULL; if (selectornode->type==NODE_DOT) { objectnode=compiler_getnode(c, selectornode->left); methodnode=compiler_getnode(c, selectornode->right); } else if (selectornode->type==NODE_SYMBOL) { methodnode=selectornode; } compiler_beginargs(c); registerindx rSel = compiler_regalloctop(c); registerindx rObj = compiler_regalloctop(c); // Fetch the method codeinfo cSel = CODEINFO(CONSTANT, 0, 0); cSel.dest = compiler_addsymbol(c, methodnode, methodnode->content); codeinfo method=compiler_movetoregister(c, methodnode, cSel, rSel); ninstructions+=method.ninstructions; // Fetch the object if (objectnode) { bool invokeclass=false; // Patch to ensure that builtin classes are prioritized over constructor functions. if (objectnode->type==NODE_SYMBOL) { value klass=builtin_findclass(objectnode->content); if (MORPHO_ISCLASS(klass)) { registerindx kindx = compiler_addconstant(c, objectnode, klass, true, false); object=CODEINFO(CONSTANT, kindx, 0); invokeclass=true; } } // Otherwise just fetch the object normally if (!invokeclass) { object=compiler_nodetobytecode(c, selectornode->left, rObj); ninstructions+=object.ninstructions; } // Ensure register allocations remain correct if (object.returntype==REGISTER && object.dest!=rObj) { compiler_regfreetemp(c, object.dest); compiler_regtempwithindx(c, rObj); // Ensure rObj remains allocated } } else { // If no objectnode, fetch self from r0 object=CODEINFO(REGISTER, 0 /* <- r0 */, 0); } // Move the object into place for the invocation object=compiler_movetoregister(c, selectornode, object, rObj); ninstructions+=object.ninstructions; // Compile the arguments codeinfo args = CODEINFO_EMPTY; if (node->right!=SYNTAXTREE_UNCONNECTED) args=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED); ninstructions+=args.ninstructions; // Remember the last argument registerindx lastarg=compiler_regtop(c); // Check we don't have too many arguments if (lastarg-rSel>MORPHO_MAXARGS) { compiler_error(c, node, COMPILE_TOOMANYARGS); return CODEINFO_EMPTY; } compiler_endargs(c); // Generate the call instruction int nposn=0, nopt=0; compiler_regcountargs(c, object.dest+1, lastarg, &nposn, &nopt); compiler_addinstruction(c, ENCODE(OP_INVOKE, rSel, nposn, nopt), node); ninstructions++; // Free all the registers used for the call compiler_regfreetemp(c, rSel); compiler_regfreetoend(c, rObj+1); // Move the result to the requested register if (reqout!=REGISTER_UNALLOCATED && object.dest!=reqout) { compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, reqout, rObj), node); ninstructions++; compiler_regfreetemp(c, rObj); object.dest=reqout; } return CODEINFO(REGISTER, object.dest, ninstructions); } /** Compile a return statement */ static codeinfo compiler_return(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo left = CODEINFO_EMPTY; unsigned int ninstructions=0; bool isinitializer = compiler_ininitializer(c); if (node->left!=SYNTAXTREE_UNCONNECTED) { if (isinitializer) { compiler_error(c, node, COMPILE_RETURNININITIALIZER); } else { left=compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); ninstructions=left.ninstructions; /* Ensure we're working with a register */ if (!(CODEINFO_ISREGISTER(left))) { left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } compiler_addinstruction(c, ENCODE_DOUBLE(OP_RETURN, 1, left.dest), node); ninstructions++; } compiler_releaseoperand(c, left); } else { /* Methods return self unless a return value is specified */ if (compiler_getcurrentclass(c)) { compiler_addinstruction(c, ENCODE_DOUBLE(OP_RETURN, 1, 0), node); /* Add a return */ } else { compiler_addinstruction(c, ENCODE_DOUBLE(OP_RETURN, 0, 0), node); } ninstructions++; } return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** Overrides or adds to an existing method implementation */ void compiler_overridemethod(compiler *c, syntaxtreenode *node, objectfunction *method, value prev) { value symbol = method->name; objectclass *klass=compiler_getcurrentclass(c); if (MORPHO_ISMETAFUNCTION(prev)) { objectmetafunction *f = MORPHO_GETMETAFUNCTION(prev); if (f->klass!=klass) { f=metafunction_clone(f); if (f) program_bindobject(c->out, (object *) f); } if (f) { metafunction_setclass(f, klass); dictionary_insert(&klass->methods, symbol, MORPHO_OBJECT(f)); for (int i=0; ifns.count; i++) { // Check if this overrides signature *sig = metafunction_getsignature(f->fns.data[i]); if (sig && signature_isequal(sig, &method->sig)) { // TODO: Should check for duplicate implementation here f->fns.data[i] = MORPHO_OBJECT(method); return; } } metafunction_add(f, MORPHO_OBJECT(method)); } } else if (MORPHO_ISFUNCTION(prev)) { objectfunction *prevmethod = MORPHO_GETFUNCTION(prev); if (signature_isequal(&prevmethod->sig, &method->sig)) { // Does the method overshadow an old one? if (prevmethod->klass!=klass) { // If so, is the old one in the parent or ancestor class? dictionary_insert(&klass->methods, symbol, MORPHO_OBJECT(method)); } else { // It's a redefinition compiler_error(c, node, COMPILE_CLSSDPLCTIMPL, MORPHO_GETCSTRING(symbol), MORPHO_GETCSTRING(klass->name)); } } else { // It doesn't override the old definition so wrap in a metafunction objectmetafunction *f = object_newmetafunction(symbol); if (f) { metafunction_add(f, prev); metafunction_add(f, MORPHO_OBJECT(method)); metafunction_setclass(f, klass); dictionary_insert(&klass->methods, symbol, MORPHO_OBJECT(f)); program_bindobject(c->out, (object *) f); } } } else if (MORPHO_ISBUILTINFUNCTION(prev)) { // A builtin function can only come from a parent class, so overwrite it dictionary_insert(&klass->methods, symbol, MORPHO_OBJECT(method)); } } /** Compiles a list of method declarations. */ static codeinfo compiler_classbody(compiler *c, syntaxtreeindx startindx, registerindx reqout) { codeinfo out; unsigned int ninstructions=0; objectclass *klass=compiler_getcurrentclass(c); syntaxtreenodetype seqtype[] = { NODE_SEQUENCE }; varray_syntaxtreeindx entries; varray_syntaxtreeindxinit(&entries); syntaxtree_flatten(compiler_getsyntaxtree(c), startindx, 1, seqtype, &entries); // Pass through body declaration to ensure all method labels are defined for (int i=0; itype==NODE_FUNCTION) { if (!dictionary_get(&klass->methods, node->content, NULL)) { value symbol = program_internsymbol(c->out, node->content); dictionary_insert(&klass->methods, symbol, MORPHO_NIL); } } else UNREACHABLE("Incorrect node type found in class declaration"); } // Now compile method definitions for (int i=0; itype==NODE_FUNCTION) { // Store the current method so that compiler_function can recognize that // it is in a method definition c->currentmethod=node; // Compile the method declaration out=compiler_function(c, node, reqout); ninstructions+=out.ninstructions; // Insert the compiled function into the method dictionary, making sure the method name is interned objectfunction *method = compiler_getpreviousfunction(c); if (method) { value omethod = MORPHO_OBJECT(method); value symbol = program_internsymbol(c->out, node->content), prev=MORPHO_NIL; dictionary_get(&klass->methods, symbol, &prev); if (MORPHO_ISNIL(prev)) { // Just insert if we don't have any definition dictionary_insert(&klass->methods, symbol, omethod); } else compiler_overridemethod(c, node, method, prev); // Override or create a metafunction } } } varray_syntaxtreeindxclear(&entries); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** Compiles a class declaration */ static codeinfo compiler_class(compiler *c, syntaxtreenode *node, registerindx reqout) { unsigned int ninstructions=0; registerindx kindx; codeinfo mout=CODEINFO_EMPTY; if (compiler_getcurrentclass(c)) { compiler_error(c, node, COMPILE_NSTDCLSS); return CODEINFO_EMPTY; } objectclass *klass=object_newclass(node->content); if (!klass) { compiler_error(c, node, ERROR_ALLOCATIONFAILED); return CODEINFO_EMPTY; } compiler_beginclass(c, klass); /** Store the object class as a constant */ kindx=compiler_addconstant(c, node, MORPHO_OBJECT(klass), false, false); /** Add the class to the class table */ if (ERROR_SUCCEEDED(c->err)) compiler_addclass(c, klass); /* Is there a superclass and/or mixins? */ if (node->left!=SYNTAXTREE_UNCONNECTED) { syntaxtreenodetype dictentrytype[] = { NODE_SEQUENCE }; varray_syntaxtreeindx entries; varray_syntaxtreeindxinit(&entries); syntaxtree_flatten(compiler_getsyntaxtree(c), node->left, 1, dictentrytype, &entries); for (int i=entries.count-1; i>=0; i--) { // Loop over super and mixins in reverse order // As super will be LAST in this list syntaxtreenode *snode = syntaxtree_nodefromindx(compiler_getsyntaxtree(c), entries.data[i]) ; objectclass *superclass=NULL; value classlabel=MORPHO_NIL; if (snode->type==NODE_SYMBOL) { classlabel=snode->content; superclass=compiler_findclass(c, classlabel); } else if (snode->type==NODE_DOT) { syntaxtreenode *left = compiler_getnode(c, snode->left), *right = compiler_getnode(c, snode->right); if (left->type!=NODE_SYMBOL || right->type!=NODE_SYMBOL) UNREACHABLE("Superclass or mixin namespace node should have symbols"); classlabel=right->content; value klass=MORPHO_NIL; compiler_findclasswithnamespace(c, snode, left->content, classlabel, &klass); if (MORPHO_ISCLASS(klass)) superclass=MORPHO_GETCLASS(klass); } else { UNREACHABLE("Superclass or mixin node should be a symbol."); } if (superclass) { if (superclass!=klass) { if (!klass->superclass) klass->superclass=superclass; // Only the first class is the super class, all others are mixins. compiler_addparent(c, klass, superclass); dictionary_copy(&superclass->methods, &klass->methods); } else { compiler_error(c, snode, COMPILE_CLASSINHERITSELF); } } else { if (MORPHO_ISSTRING(classlabel)) { compiler_error(c, snode, COMPILE_SUPERCLASSNOTFOUND, MORPHO_GETCSTRING(classlabel)); } else UNREACHABLE("No class label available"); } } varray_syntaxtreeindxclear(&entries); } else { klass->superclass=baseclass; if (baseclass) dictionary_copy(&baseclass->methods, &klass->methods); } /* Now compute the class linearization */ if (!class_linearize(klass)) { compiler_error(c, node, COMPILE_CLSSLNRZ, MORPHO_GETCSTRING(klass->name)); } /* Compile the body */ if (node->right!=SYNTAXTREE_UNCONNECTED) { mout=compiler_classbody(c, node->right, reqout); ninstructions+=mout.ninstructions; } /* End class definition */ compiler_endclass(c); compiler_checkoutstandingforwardreference(c); /* Allocate a variable to refer to the class definition */ codeinfo cvar=compiler_addvariable(c, node, node->content); registerindx reg=cvar.dest; /* If it's not in a register, allocate a temporary register */ if (cvar.returntype!=REGISTER) reg=compiler_regtemp(c, REGISTER_UNALLOCATED); /* Move function into register */ compiler_addinstruction(c, ENCODE_LONG(OP_LCT, reg, kindx), node); ninstructions++; /* If the variable wasn't a local one, move to the correct place */ if (cvar.returntype!=REGISTER) { codeinfo mv=compiler_movefromregister(c, node, cvar, reg); ninstructions+=mv.ninstructions; compiler_regfreetemp(c, reg); } /* Bind the klass to the program to be freed on exit */ if (klass) program_bindobject(c->out, (object *) klass); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions); } /** Compile a reference to self */ static codeinfo compiler_self(compiler *c, syntaxtreenode *node, registerindx reqout) { objectclass *klass = compiler_getcurrentclass(c); /* If we're in a method, self is available in r0 */ codeinfo ret=CODEINFO(REGISTER, 0, 0); /* Check that we're in a class definition */ if (!klass) { compiler_error(c, node, COMPILE_SELFOUTSIDECLASS); } /* If we're inside a function embedded in a method, we need to capture self as an upvalue */ functionstate *fstate = compiler_currentfunctionstate(c); if (fstate->type==FUNCTION) { ret.dest = compiler_resolveself(c); if (ret.dest!=REGISTER_UNALLOCATED) { ret.returntype=UPVALUE; } } return ret; } /** Compile a reference to super */ static codeinfo compiler_super(compiler *c, syntaxtreenode *node, registerindx reqout) { objectclass *klass = compiler_getcurrentclass(c); codeinfo ret=CODEINFO_EMPTY; /* Check that we're in a class definition */ if (klass) { if (klass->superclass) { ret.returntype=CONSTANT; ret.dest=compiler_addconstant(c, node, MORPHO_OBJECT(klass->superclass), false, false); } else { compiler_error(c, node, COMPILE_NOSUPER); } } else { compiler_error(c, node, COMPILE_SUPEROUTSIDECLASS); } return ret; } /** Lookup a symbol */ static codeinfo compiler_symbol(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo ret=CODEINFO_EMPTY; value type; /* Is it a local variable? */ ret.dest=compiler_getlocal(c, node->content); if (ret.dest!=REGISTER_UNALLOCATED && compiler_regtype(c, ret.dest, &type) && // If it's a closure it should be resolved later !MORPHO_ISEQUAL(type, _closuretype)) { return ret; } /* Is it a reference to a function? */ if (compiler_resolvefunctionref(c, node, node->content, &ret)) { return ret; } /* Is it an upvalue? */ ret.dest = compiler_resolveupvalue(c, node->content); if (ret.dest!=REGISTER_UNALLOCATED) { ret.returntype=UPVALUE; return ret; } /* Is it a global variable */ ret.dest=compiler_findglobal(c, node->content, true); if (ret.dest!=REGISTER_UNALLOCATED) { ret.returntype=GLOBAL; return ret; } /* Is it a builtin function or class? */ value binf = builtin_findfunction(node->content); if (MORPHO_ISNIL(binf)) binf = builtin_findclass(node->content); if (!MORPHO_ISNIL(binf)) { /* It is; so add it to the constant table */ ret.returntype=CONSTANT; ret.dest=compiler_addconstant(c, node, binf, false, false); return ret; } /* Is it a class? */ objectclass *klass = compiler_findclass(c, node->content); if (klass) { /* It is; so add it to the constant table */ ret.returntype=CONSTANT; ret.dest=compiler_addconstant(c, node, MORPHO_OBJECT(klass), false, false); return ret; } char *label = MORPHO_GETCSTRING(node->content); compiler_error(c, node, COMPILE_SYMBOLNOTDEFINED, label); return ret; } static codeinfo compiler_movetoproperty(compiler *c, syntaxtreenode *node, codeinfo in, syntaxtreenode *obj); /** Assign to a symbol */ static codeinfo compiler_assign(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreenode *varnode = compiler_getnode(c, node->left); syntaxtreenode *indxnode = NULL; codeinfo ret, right=CODEINFO_EMPTY; value var=MORPHO_NIL; registerindx reg=REGISTER_UNALLOCATED, istart=0, iend=0, tmp=REGISTER_UNALLOCATED; enum { ASSIGN_VAR, ASSIGN_UPVALUE, ASSIGN_OBJ, ASSIGN_GLBL, ASSIGN_INDEX, ASSIGN_UPINDEX } mode=ASSIGN_VAR; unsigned int ninstructions = 0; /* Find the symbol or check if it's an object */ if (varnode) { if (varnode->type==NODE_SYMBOL) { var = varnode->content; } else if (varnode->type==NODE_DOT) { mode=ASSIGN_OBJ; /* Or object */ } else if (varnode->type==NODE_INDEX) { mode=ASSIGN_INDEX; indxnode=varnode; varnode=compiler_getnode(c, varnode->left); var = varnode->content; if (varnode->type==NODE_DOT || varnode->type==NODE_SELF) { codeinfo mv=compiler_nodetobytecode(c, indxnode->left, reg); ninstructions+=mv.ninstructions; reg=mv.dest; } } } if (!MORPHO_ISNIL(var) || mode==ASSIGN_OBJ || mode==ASSIGN_INDEX) { if (mode!=ASSIGN_OBJ) { /* Find the local variable and get the assigned register */ if (reg==REGISTER_UNALLOCATED) reg=compiler_getlocal(c, var); /* Perhaps it's an upvalue? */ if (reg==REGISTER_UNALLOCATED) { reg=compiler_resolveupvalue(c, var); if (reg!=REGISTER_UNALLOCATED) mode=(mode==ASSIGN_INDEX ? ASSIGN_UPINDEX : ASSIGN_UPVALUE); } /* .. or a global? */ if (reg==REGISTER_UNALLOCATED) { reg=compiler_findglobal(c, var, true); if (reg!=REGISTER_UNALLOCATED) { if (indxnode) { /* If an indexed global, move the global into a register */ tmp=compiler_regalloctop(c); codeinfo mv=compiler_movetoregister(c, node, CODEINFO(GLOBAL, reg, 0), tmp); ninstructions+=mv.ninstructions; reg=tmp; mode=ASSIGN_INDEX; } else mode=ASSIGN_GLBL; } } /* Still couldn't resolve it, so generate an error */ if (reg==REGISTER_UNALLOCATED) { compiler_error(c, node, COMPILE_SYMBOLNOTDEFINED, (MORPHO_ISSTRING(var) ? MORPHO_GETCSTRING(var) : "")); return CODEINFO_EMPTY; } } if (indxnode) { right=compiler_compileindexlist(c, indxnode, &istart, &iend); ninstructions+=right.ninstructions; } /* Evaluate the rhs */ right = compiler_nodetobytecode(c, node->right, (mode!=ASSIGN_VAR ? REGISTER_UNALLOCATED : reg)); ninstructions+=right.ninstructions; switch (mode) { case ASSIGN_VAR: /* Move to a register */ ret=compiler_movetoregister(c, node, right, reg); ninstructions+=ret.ninstructions; break; case ASSIGN_UPVALUE: ret=compiler_movetoupvalue(c, node, right, reg); ninstructions+=ret.ninstructions; break; case ASSIGN_OBJ: ret=compiler_movetoproperty(c, node, right, varnode); ninstructions+=ret.ninstructions; break; case ASSIGN_GLBL: ret=compiler_movetoglobal(c, node, right, reg); ninstructions+=ret.ninstructions; break; case ASSIGN_INDEX: { /* Make sure the rhs is in the register after the last index */ if (!(CODEINFO_ISREGISTER(right) && right.dest==iend+1)) { registerindx last=compiler_regtempwithindx(c, iend+1); compiler_releaseoperand(c, right); right=compiler_movetoregister(c, node, right, last); ninstructions+=right.ninstructions; } compiler_regfreetoend(c, right.dest+1); if (right.dest!=iend+1) { UNREACHABLE("Failed register allocation in compiling SIX instruction."); } compiler_addinstruction(c, ENCODE(OP_SIX, reg, istart, right.dest), node); ninstructions++; } break; case ASSIGN_UPINDEX: UNREACHABLE("Assign to indexed upvalue not implemented."); break; } } else { compiler_error(c, node, COMPILE_INVALIDASSIGNMENT); return CODEINFO_EMPTY; } if (tmp!=REGISTER_UNALLOCATED) compiler_regfreetemp(c, tmp); /* Make sure the correct number of instructions is returned */ ret=right; ret.ninstructions=ninstructions; return ret; } /* Compiles property lookup */ static codeinfo compiler_property(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo left = CODEINFO_EMPTY, prop = CODEINFO_EMPTY; registerindx out = compiler_regtemp(c, reqout); /* The left hand side should evaluate to the object in question */ left = compiler_nodetobytecode(c, node->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; if (!(CODEINFO_ISREGISTER(left))) { /* Ensure we're working with a register */ left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } /* The right hand side should be a method name */ syntaxtreenode *selector = compiler_getnode(c, node->right); if (selector->type==NODE_SYMBOL) { prop=compiler_addsymbolwithsizecheck(c, selector, selector->content); ninstructions+=prop.ninstructions; } else { compiler_error(c, selector, COMPILE_PROPERTYNAMERQD); } if (out !=REGISTER_UNALLOCATED) { compiler_addinstruction(c, ENCODE(OP_LPR, out, left.dest, prop.dest), node); ninstructions++; compiler_releaseoperand(c, left); if (CODEINFO_ISREGISTER(prop)) compiler_releaseoperand(c, prop); } return CODEINFO(REGISTER, out, ninstructions); } /** Compiles the dot operator, which may be property lookup or a method call */ static codeinfo compiler_dot(compiler *c, syntaxtreenode *node, registerindx reqout) { syntaxtreenode *left = compiler_getnode(c, node->left), *right = compiler_getnode(c, node->right); value out=MORPHO_NIL; if (left->type==NODE_SYMBOL && right->type==NODE_SYMBOL && compiler_findsymbolwithnamespace(c, node, left->content, right->content, &out)) { if (MORPHO_ISINTEGER(out)) { return CODEINFO(GLOBAL, MORPHO_GETINTEGERVALUE(out), 0); } else if (MORPHO_ISFUNCTION(out) || MORPHO_ISMETAFUNCTION(out) || MORPHO_ISBUILTINFUNCTION(out) || MORPHO_ISCLASS(out)) { registerindx indx = compiler_addconstant(c, node, out, true, false); return CODEINFO(CONSTANT, indx, 0); } else { UNREACHABLE("Namespace dictionary contains noninteger value"); } } return compiler_property(c, node, reqout); } /* Moves the result of a calculation to an object property */ static codeinfo compiler_movetoproperty(compiler *c, syntaxtreenode *node, codeinfo in, syntaxtreenode *obj) { codeinfo prop = CODEINFO_EMPTY; /* The left hand side of obj should evaluate to the object in question */ codeinfo left = compiler_nodetobytecode(c, obj->left, REGISTER_UNALLOCATED); unsigned int ninstructions=left.ninstructions; if (!(CODEINFO_ISREGISTER(left) || CODEINFO_ISSHORTCONSTANT(left))) { /* Ensure we're working with a register or a constant */ left=compiler_movetoregister(c, node, left, REGISTER_UNALLOCATED); ninstructions+=left.ninstructions; } /* The right hand side of obj should be a property name */ syntaxtreenode *selector = compiler_getnode(c, obj->right); if (selector->type==NODE_SYMBOL) { prop=compiler_addsymbolwithsizecheck(c, selector, selector->content); ninstructions+=prop.ninstructions; } else { compiler_error(c, selector, COMPILE_PROPERTYNAMERQD); return CODEINFO_EMPTY; } codeinfo store = in; if (!CODEINFO_ISREGISTER(in)) { /* Ensure we're working with a register */ store=compiler_movetoregister(c, node, store, REGISTER_UNALLOCATED); ninstructions+=store.ninstructions; compiler_releaseoperand(c, store); } compiler_addinstruction(c, ENCODE(OP_SPR, left.dest, prop.dest, store.dest), node); ninstructions++; if (CODEINFO_ISREGISTER(prop)) compiler_releaseoperand(c, prop); return CODEINFO(CODEINFO_ISCONSTANT(in), in.dest, ninstructions); } /** Compiles a node to bytecode */ static codeinfo compiler_nodetobytecode(compiler *c, syntaxtreeindx indx, registerindx reqout) { syntaxtreenode *node = NULL; codeinfo ret = CODEINFO_EMPTY; if (indx!=SYNTAXTREE_UNCONNECTED) { node=compiler_getnode(c, indx); } else { UNREACHABLE("compiling an unexpectedly blank node [run with debugger]"); } if (!node) return CODEINFO_EMPTY; #ifdef MORPHO_DEBUG_DISPLAYREGISTERALLOCATION compiler_regshow(c); #endif compiler_nodefn compilenodefn = compiler_getrule(node->type)->nodefn; if (compilenodefn!=NULL) { ret = (*compilenodefn) (c, node, reqout); if (CODEINFO_ISREGISTER(ret) && ret.dest!=REGISTER_UNALLOCATED &&!compiler_isregalloc(c, ret.dest)) { UNREACHABLE("compiler node returned an unallocated register"); } } else { UNREACHABLE("unhandled syntax tree node type [Check bytecode compiler definition table]"); } return ret; } /** Compiles the current syntax tree to bytecode */ static bool compiler_tobytecode(compiler *c, program *out) { if (c->tree.tree.count>0 && c->tree.entry!=SYNTAXTREE_UNCONNECTED) { codeinfo info=compiler_nodetobytecode(c, c->tree.entry, REGISTER_UNALLOCATED); compiler_releaseoperand(c, info); } compiler_checkoutstandingforwardreference(c); if (c->tree.tree.count==0) { compiler_addinstruction(c, ENCODE_BYTE(OP_END), NULL); } else if (c->tree.entry>=0) { compiler_addinstruction(c, ENCODE_BYTE(OP_END), syntaxtree_nodefromindx(&c->tree, c->tree.entry)); } return true; } /* ********************************************************************** * Modules * ********************************************************************** */ /** Strip an 'end' instruction at the end of the program */ void compiler_stripend(compiler *c) { program *out = c->out; instructionindx last = out->code.count; /* End of old code */ if (last>0 && out->code.data[last-1] == ENCODE_BYTE(OP_END)) { out->code.count--; debugannotation_stripend(&out->annotations); } } /** Copies the globals across from one compiler to another. The globals dictionary maps keys to global numbers * @param[in] src source dictionary * @param[in] dest destination dictionary * @param[in] compare (optional) a dictionary to check the contents against; globals are only copied if they also appear in compare */ void compiler_copysymbols(dictionary *src, dictionary *dest, dictionary *compare) { for (unsigned int i=0; icapacity; i++) { value key = src->contents[i].key; if (!MORPHO_ISNIL(key)) { if (compare && !dictionary_get(compare, key, NULL)) continue; if (MORPHO_ISSTRING(key) && MORPHO_GETCSTRING(key)[0]=='_') continue; dictionary_insert(dest, key, src->contents[i].val); } } } /** Copies the global function ref into the destination compiler's current function ref */ void compiler_copyfunctionref(compiler *src, compiler *dest, dictionary *fordict) { functionstate *in=compiler_currentfunctionstate(src); functionstate *out=compiler_currentfunctionstate(dest); if (fordict) { for (int i=0; ifunctionref.count; i++) { functionref *ref=&in->functionref.data[i]; if (!dictionary_get(fordict, ref->function->name, NULL)) continue; varray_functionrefwrite(&out->functionref, in->functionref.data[i]); } } else varray_functionrefadd(&out->functionref, in->functionref.data, in->functionref.count); } /** Copies the global function ref into the designated namespace, checking whether the functions are present in the dictionary, and creating metafunctions where necessary */ void compiler_copyfunctionreftonamespace(compiler *src, namespc *dest, dictionary *fordict) { functionstate *f=compiler_currentfunctionstate(src); dictionary symbols; dictionary_init(&symbols); for (int i=0; ifunctionref.count; i++) { functionref *ref=&f->functionref.data[i]; // Skip if not in the fordict if (fordict && !dictionary_get(fordict, ref->function->name, NULL)) continue; value fn=MORPHO_OBJECT(ref->function); if (dictionary_get(&symbols, ref->function->name, &fn)) { // If the function already exists, wrap in a metafunction _addmatchingfunctionref(src, ref->function->name, MORPHO_OBJECT(ref->function), &fn); } dictionary_insert(&symbols, ref->function->name, fn); } compiler_copysymbols(&symbols, &dest->symbols, NULL); dictionary_clear(&symbols); } /** Searches for a module with given name, returns the file name for inclusion. */ bool compiler_findmodule(char *name, varray_char *fname) { value out=MORPHO_NIL; bool success=morpho_findresource(MORPHO_RESOURCE_MODULE, name, &out); if (success) { fname->count=0; if (MORPHO_ISSTRING(out)) { varray_charadd(fname, MORPHO_GETCSTRING(out), (int) MORPHO_GETSTRINGLENGTH(out)); varray_charwrite(fname, '\0'); } morpho_freeobject(out); } return success; } /** Import a module */ static codeinfo compiler_import(compiler *c, syntaxtreenode *node, registerindx reqout) { varray_char filename; syntaxtreenode *module = compiler_getnode(c, node->left); syntaxtreenode *qual = compiler_getnode(c, node->right); dictionary fordict; namespc *nmspace=NULL; char *fname=NULL; unsigned int start=0, end=0; FILE *f = NULL; dictionary_init(&fordict); varray_charinit(&filename); if (compiler_checkerror(c)) return CODEINFO_EMPTY; while (qual) { if (qual->type==NODE_FOR) { syntaxtreenode *l = compiler_getnode(c, qual->left); if (l && l->type==NODE_SYMBOL) { dictionary_insert(&fordict, l->content, MORPHO_NIL); } else UNREACHABLE("Incorrect syntax tree structure in FOR node."); } else if (qual->type==NODE_AS) { syntaxtreenode *l = compiler_getnode(c, qual->left); if (l && l->type==NODE_SYMBOL) { nmspace=compiler_addnamespace(c, l->content); if (!nmspace) { compiler_error(c, node, ERROR_ALLOCATIONFAILED); return CODEINFO_EMPTY; } } else UNREACHABLE("Incorrect syntax tree structure in AS node."); } else UNREACHABLE("Unexpected node type."); qual=compiler_getnode(c, qual->right); } if (module) { if (module->type==NODE_SYMBOL) { dictionary *fndict, *clssdict; if (extension_load(MORPHO_GETCSTRING(module->content), &fndict, &clssdict)) { compiler_copysymbols(clssdict, (nmspace ? &nmspace->symbols: builtin_getclasstable()), (fordict.count>0 ? &fordict : NULL)); compiler_copysymbols(fndict, (nmspace ? &nmspace->symbols: builtin_getfunctiontable()), (fordict.count>0 ? &fordict : NULL)); if (nmspace) { // Copy classes into the namespace's class table compiler_copysymbols(clssdict, &nmspace->classes, (fordict.count>0 ? &fordict : NULL)); } } else if (compiler_findmodule(MORPHO_GETCSTRING(module->content), &filename)) { fname=filename.data; } else { compiler_error(c, module, COMPILE_MODULENOTFOUND, MORPHO_GETCSTRING(module->content)); } } else if (module->type==NODE_STRING) { fname=MORPHO_GETCSTRING(module->content); } compiler *root = c; while (root->parent!=NULL) root=root->parent; // Check if the module was previously imported if (fname) { objectstring chkmodname = MORPHO_STATICSTRING(fname); value symboldict=MORPHO_NIL; if (dictionary_get(&root->modules, MORPHO_OBJECT(&chkmodname), &symboldict)) { // If so, copy its symbols into the compiler compiler_copysymbols(MORPHO_GETDICTIONARYSTRUCT(symboldict), (nmspace ? &nmspace->symbols: &c->globals), (fordict.count>0 ? &fordict : NULL)); goto compiler_import_cleanup; } } if (fname) f=file_openrelative(fname, "r"); else goto compiler_import_cleanup; if (f) { value modname=object_stringfromcstring(fname, strlen(fname)); value symboldict=MORPHO_NIL; /* Read in source */ varray_char src; varray_charinit(&src); if (!file_readintovarray(f, &src)) { compiler_error(c, module, COMPILE_IMPORTFLD, fname); goto compiler_import_cleanup; } /* Remember the initial position of the code */ start=c->out->code.count; /* Set up the compiler */ compiler cc; compiler_init(src.data, c->out, &cc); compiler_setmodule(&cc, modname); debugannotation_setmodule(&c->out->annotations, modname); cc.parent=c; /* Ensures global variables can be found */ morpho_compile(src.data, &cc, false, &c->err); if (ERROR_SUCCEEDED(c->err)) { compiler_stripend(c); compiler_copysymbols(&cc.globals, (nmspace ? &nmspace->symbols: &c->globals), (fordict.count>0 ? &fordict : NULL)); if (nmspace) { // If we're in a namespace, copy the class table into that compiler_copysymbols(&cc.classes, &nmspace->classes, (fordict.count>0 ? &fordict : NULL)); compiler_copyfunctionreftonamespace(&cc, nmspace, (fordict.count>0 ? &fordict : NULL)); } else { // Otherwise just put it into the parent compiler's class table compiler_copysymbols(&cc.classes, &c->classes, (fordict.count>0 ? &fordict : NULL)); compiler_copyfunctionref(&cc, c, (fordict.count>0 ? &fordict : NULL)); } objectdictionary *dict = object_newdictionary(); // Preserve all symbols for further imports if (dict) { compiler_copysymbols(&cc.globals, &dict->dict, NULL); symboldict = MORPHO_OBJECT(dict); } } else { c->err.file = (cc.err.file ? cc.err.file : MORPHO_GETCSTRING(modname)); } debugannotation_setmodule(&c->out->annotations, compiler_getmodule(c)); end=c->out->code.count; compiler_clear(&cc); varray_charclear(&src); dictionary_insert(&root->modules, modname, symboldict); } else compiler_error(c, module, COMPILE_FILENOTFOUND, fname); } compiler_import_cleanup: if (f) fclose(f); varray_charclear(&filename); dictionary_clear(&fordict); return CODEINFO(REGISTER, REGISTER_UNALLOCATED, end-start); } /** Compile a breakpoint */ static codeinfo compiler_breakpoint(compiler *c, syntaxtreenode *node, registerindx reqout) { codeinfo info = CODEINFO_EMPTY; compiler_addinstruction(c, ENCODE_BYTE(OP_BREAK), node); if (node->left!=SYNTAXTREE_UNCONNECTED) { info=compiler_nodetobytecode(c, node->left, reqout); } info.ninstructions++; return info; } /* ********************************************************************** * Compiler initialization/finalization * ********************************************************************** */ /** @brief Initialize a compiler * @param[in] source source code to compile * @param[in] out destination program to compile to * @param[out] c compiler structure is filled out */ void compiler_init(const char *source, program *out, compiler *c) { lex_init(&c->lex, source, 1); error_init(&c->err); syntaxtree_init(&c->tree); parse_init(&c->parse, &c->lex, &c->err, &c->tree); compiler_fstackinit(c); dictionary_init(&c->globals); dictionary_init(&c->classes); dictionary_init(&c->modules); if (out) c->fstack[0].func=out->global; /* The global pseudofunction */ c->out = out; c->prevfunction = NULL; c->currentclass = NULL; c->currentmethod = NULL; c->namespaces = NULL; c->currentmodule = MORPHO_NIL; c->parent = NULL; c->line = 1; // Count from 1 } /** @brief Clear attached data structures from a compiler * @param[in] c compiler to clear */ void compiler_clear(compiler *c) { lex_clear(&c->lex); parse_clear(&c->parse); compiler_fstackclear(c); syntaxtree_clear(&c->tree); compiler_clearnamespacelist(c); dictionary_clear(&c->globals); // Keys are bound to the program dictionary_freecontents(&c->modules, true, true); dictionary_clear(&c->modules); dictionary_clear(&c->classes); } /* ********************************************************************** * Interfaces * ********************************************************************** */ /** Interface to the compiler * @param[in] in A string to compile * @param[in] c The compiler * @param[in] opt Whether or not to invoke the optimizer * @param[out] err Pointer to error block on failure * @returns A bool indicating success or failure */ bool morpho_compile(char *in, compiler *c, bool opt, error *err) { program *out = c->out; bool success = false; error_clear(&c->err); error_clear(err); /* Remove any previous END instruction */ compiler_stripend(c); instructionindx last = out->code.count; /* End of old code */ /* Initialize lexer */ lex_init(&c->lex, in, c->line); /* Count lines from 1. */ if ((!morpho_parse(&c->parse)) || !ERROR_SUCCEEDED(c->err)) { *err = c->err; } else { #ifdef MORPHO_DEBUG_DISPLAYSYNTAXTREE syntaxtree_print(&c->tree); #endif #ifdef MORPHO_DEBUG_FILLGLOBALCONSTANTTABLE if (out->global->konst.count<255) { for (unsigned int i=0; i<256; i++) compiler_addconstant(c, NULL, MORPHO_INTEGER(i), false, false); } #endif compiler_tobytecode(c, out); if (ERROR_SUCCEEDED(c->err)) { compiler_setfunctionregistercount(c); program_setentry(out, last); success=true; } else { *err = c->err; } } if (success) { if (opt && optimizer) (*optimizer) (c->out); c->line=c->lex.line+1; // Update the line counter if compilation was a success; assumes a new line every time morpho_compile is called. } return success; } /* ********************************************************************** * Public interfaces * ********************************************************************** */ /** Creates a new compiler */ compiler *morpho_newcompiler(program *out) { compiler *new = MORPHO_MALLOC(sizeof(compiler)); if (new) compiler_init("", out, new); return new; } /** Frees a compiler */ void morpho_freecompiler(compiler *c) { compiler_clear(c); MORPHO_FREE(c); } /* ********************************************************************** * Initialization/Finalization * ********************************************************************** */ void morpho_setbaseclass(value klss) { if (MORPHO_ISCLASS(klss)) baseclass=MORPHO_GETCLASS(klss); } void morpho_setoptimizer(optimizerfn *opt) { optimizer = opt; } /** Initializes the compiler */ void compile_initialize(void) { _selfsymbol=builtin_internsymbolascstring("self"); /** Types we need to refer to */ _closuretype = MORPHO_OBJECT(object_getveneerclass(OBJECT_CLOSURE)); optimizer = NULL; /* Compile errors */ morpho_defineerror(COMPILE_SYMBOLNOTDEFINED, ERROR_COMPILE, COMPILE_SYMBOLNOTDEFINED_MSG); morpho_defineerror(COMPILE_SYMBOLNOTDEFINEDNMSPC, ERROR_COMPILE, COMPILE_SYMBOLNOTDEFINEDNMSPC_MSG); morpho_defineerror(COMPILE_TOOMANYCONSTANTS, ERROR_COMPILE, COMPILE_TOOMANYCONSTANTS_MSG); morpho_defineerror(COMPILE_ARGSNOTSYMBOLS, ERROR_COMPILE, COMPILE_ARGSNOTSYMBOLS_MSG); morpho_defineerror(COMPILE_PROPERTYNAMERQD, ERROR_COMPILE, COMPILE_PROPERTYNAMERQD_MSG); morpho_defineerror(COMPILE_SELFOUTSIDECLASS, ERROR_COMPILE, COMPILE_SELFOUTSIDECLASS_MSG); morpho_defineerror(COMPILE_RETURNININITIALIZER, ERROR_COMPILE, COMPILE_RETURNININITIALIZER_MSG); morpho_defineerror(COMPILE_SUPERCLASSNOTFOUND, ERROR_COMPILE, COMPILE_SUPERCLASSNOTFOUND_MSG); morpho_defineerror(COMPILE_SUPEROUTSIDECLASS, ERROR_COMPILE, COMPILE_SUPEROUTSIDECLASS_MSG); morpho_defineerror(COMPILE_NOSUPER, ERROR_COMPILE, COMPILE_NOSUPER_MSG); morpho_defineerror(COMPILE_INVALIDASSIGNMENT, ERROR_COMPILE, COMPILE_INVALIDASSIGNMENT_MSG); morpho_defineerror(COMPILE_CLASSINHERITSELF, ERROR_COMPILE, COMPILE_CLASSINHERITSELF_MSG); morpho_defineerror(COMPILE_TOOMANYARGS, ERROR_COMPILE, COMPILE_TOOMANYARGS_MSG); morpho_defineerror(COMPILE_TOOMANYPARAMS, ERROR_COMPILE, COMPILE_TOOMANYPARAMS_MSG); morpho_defineerror(COMPILE_ISOLATEDSUPER, ERROR_COMPILE, COMPILE_ISOLATEDSUPER_MSG); morpho_defineerror(COMPILE_VARALREADYDECLARED, ERROR_COMPILE, COMPILE_VARALREADYDECLARED_MSG); morpho_defineerror(COMPILE_FILENOTFOUND, ERROR_COMPILE, COMPILE_FILENOTFOUND_MSG); morpho_defineerror(COMPILE_MODULENOTFOUND, ERROR_COMPILE, COMPILE_MODULENOTFOUND_MSG); morpho_defineerror(COMPILE_IMPORTFLD, ERROR_COMPILE, COMPILE_IMPORTFLD_MSG); morpho_defineerror(COMPILE_BRKOTSDLP, ERROR_COMPILE, COMPILE_BRKOTSDLP_MSG); morpho_defineerror(COMPILE_CNTOTSDLP, ERROR_COMPILE, COMPILE_CNTOTSDLP_MSG); morpho_defineerror(COMPILE_OPTPRMDFLT, ERROR_COMPILE, COMPILE_OPTPRMDFLT_MSG); morpho_defineerror(COMPILE_FORWARDREF, ERROR_COMPILE, COMPILE_FORWARDREF_MSG); morpho_defineerror(COMPILE_MLTVARPRMTR, ERROR_COMPILE, COMPILE_MLTVARPRMTR_MSG); morpho_defineerror(COMPILE_MSSNGLOOPBDY, ERROR_COMPILE, COMPILE_MSSNGLOOPBDY_MSG); morpho_defineerror(COMPILE_NSTDCLSS, ERROR_COMPILE, COMPILE_NSTDCLSS_MSG); morpho_defineerror(COMPILE_VARPRMLST, ERROR_COMPILE, COMPILE_VARPRMLST_MSG); morpho_defineerror(COMPILE_INVLDLBL, ERROR_COMPILE, COMPILE_INVLDLBL_MSG); morpho_defineerror(COMPILE_MSSNGINDX, ERROR_COMPILE, COMPILE_MSSNGINDX_MSG); morpho_defineerror(COMPILE_TYPEVIOLATION, ERROR_COMPILE, COMPILE_TYPEVIOLATION_MSG); morpho_defineerror(COMPILE_UNKNWNTYPE, ERROR_COMPILE, COMPILE_UNKNWNTYPE_MSG); morpho_defineerror(COMPILE_UNKNWNNMSPC, ERROR_COMPILE, COMPILE_UNKNWNNMSPC_MSG); morpho_defineerror(COMPILE_UNKNWNTYPENMSPC, ERROR_COMPILE, COMPILE_UNKNWNTYPENMSPC_MSG); morpho_defineerror(COMPILE_CLSSLNRZ, ERROR_COMPILE, COMPILE_CLSSLNRZ_MSG); morpho_defineerror(COMPILE_CLSSDPLCTIMPL, ERROR_COMPILE, COMPILE_CLSSDPLCTIMPL_MSG); morpho_addfinalizefn(compile_finalize); } /** Finalizes the compiler */ void compile_finalize(void) { } ================================================ FILE: src/core/compile.h ================================================ /** @file compile.h * @author T J Atherton * * @brief Compiles raw input to Morpho instructions */ #ifndef compile_h #define compile_h #define MORPHO_CORE #include "core.h" #include "syntaxtree.h" #include "parse.h" #include "debug.h" /* ********************************************************************** * Compiler error messages * ********************************************************************** */ #define COMPILE_SYMBOLNOTDEFINED "SymblUndf" #define COMPILE_SYMBOLNOTDEFINED_MSG "Symbol '%s' not defined." #define COMPILE_SYMBOLNOTDEFINEDNMSPC "SymblUndfNmSpc" #define COMPILE_SYMBOLNOTDEFINEDNMSPC_MSG "Symbol '%s' not defined in namespace '%s'." #define COMPILE_TOOMANYCONSTANTS "TooMnyCnst" #define COMPILE_TOOMANYCONSTANTS_MSG "Too many constants." #define COMPILE_ARGSNOTSYMBOLS "FnPrmSymb" #define COMPILE_ARGSNOTSYMBOLS_MSG "Function parameters must be symbols." #define COMPILE_PROPERTYNAMERQD "PptyNmRqd" #define COMPILE_PROPERTYNAMERQD_MSG "Property name required." #define COMPILE_SELFOUTSIDECLASS "SlfOtsdClss" #define COMPILE_SELFOUTSIDECLASS_MSG "Cannot use 'self' outside of a class." #define COMPILE_RETURNININITIALIZER "InitRtn" #define COMPILE_RETURNININITIALIZER_MSG "Cannot return a value in an initializer." #define COMPILE_SUPERCLASSNOTFOUND "SprNtFnd" #define COMPILE_SUPERCLASSNOTFOUND_MSG "Superclass '%s' not found." #define COMPILE_SUPEROUTSIDECLASS "SprOtsdClss" #define COMPILE_SUPEROUTSIDECLASS_MSG "Cannot use 'super' outside of a class." #define COMPILE_NOSUPER "SprSelMthd" #define COMPILE_NOSUPER_MSG "Can only use 'super' to select a method." #define COMPILE_INVALIDASSIGNMENT "InvldAssgn" #define COMPILE_INVALIDASSIGNMENT_MSG "Invalid assignment target." #define COMPILE_CLASSINHERITSELF "ClssCrcRf" #define COMPILE_CLASSINHERITSELF_MSG "A class cannot inherit from itself." #define COMPILE_TOOMANYARGS "TooMnyArg" #define COMPILE_TOOMANYARGS_MSG "Too many arguments." #define COMPILE_TOOMANYPARAMS "TooMnyPrm" #define COMPILE_TOOMANYPARAMS_MSG "Too many parameters." #define COMPILE_ISOLATEDSUPER "IsoSpr" #define COMPILE_ISOLATEDSUPER_MSG "Expect '.' after 'super'." #define COMPILE_VARALREADYDECLARED "VblDcl" #define COMPILE_VARALREADYDECLARED_MSG "Variable with this name already declared in this scope." #define COMPILE_FILENOTFOUND "FlNtFnd" #define COMPILE_FILENOTFOUND_MSG "File '%s' not found." #define COMPILE_MODULENOTFOUND "MdlNtFnd" #define COMPILE_MODULENOTFOUND_MSG "Module '%s' not found." #define COMPILE_IMPORTFLD "ImprtFld" #define COMPILE_IMPORTFLD_MSG "Import of file '%s' failed." #define COMPILE_BRKOTSDLP "BrkOtsdLp" #define COMPILE_BRKOTSDLP_MSG "Break encountered outside a loop." #define COMPILE_CNTOTSDLP "CntOtsdLp" #define COMPILE_CNTOTSDLP_MSG "Continue encountered outside a loop." #define COMPILE_OPTPRMDFLT "OptPrmDflt" #define COMPILE_OPTPRMDFLT_MSG "Optional parameter default values must be constants." #define COMPILE_FORWARDREF "UnrslvdFrwdRf" #define COMPILE_FORWARDREF_MSG "Function '%s' is called but not defined in the same scope." #define COMPILE_MLTVARPRMTR "MltVarPrmtr" #define COMPILE_MLTVARPRMTR_MSG "Functions can have at most one variadic parameter." #define COMPILE_VARPRMLST "VarPrLst" #define COMPILE_VARPRMLST_MSG "Cannot have fixed parameters after a variadic parameter." #define COMPILE_MSSNGLOOPBDY "MssngLoopBdy" #define COMPILE_MSSNGLOOPBDY_MSG "Missing loop body." #define COMPILE_NSTDCLSS "NstdClss" #define COMPILE_NSTDCLSS_MSG "Cannot define a class within another class." #define COMPILE_INVLDLBL "InvldLbl" #define COMPILE_INVLDLBL_MSG "Invalid label in catch statment." #define COMPILE_MSSNGINDX "MssngIndx" #define COMPILE_MSSNGINDX_MSG "Missing index or indices." #define COMPILE_TYPEVIOLATION "TypeErr" #define COMPILE_TYPEVIOLATION_MSG "Type violation: Attempting to assign type %s to %s variable %s." #define COMPILE_UNKNWNTYPE "UnknwnType" #define COMPILE_UNKNWNTYPE_MSG "Unknown type '%s'." #define COMPILE_UNKNWNNMSPC "UnknwnNmSpc" #define COMPILE_UNKNWNNMSPC_MSG "Unknown namespace '%s'." #define COMPILE_UNKNWNTYPENMSPC "UnknwnTypeNmSpc" #define COMPILE_UNKNWNTYPENMSPC_MSG "Unknown type '%s' in namespace '%s'." #define COMPILE_CLSSLNRZ "ClssLnrz" #define COMPILE_CLSSLNRZ_MSG "Can't linearize class %s: Check parent and ancestor classes for conflicting inheritance order." #define COMPILE_CLSSDPLCTIMPL "ClssDplctImpl" #define COMPILE_CLSSDPLCTIMPL_MSG "Duplicate implementation of method %s with same signature in class %s" /* ********************************************************************** * Compiler typedefs * ********************************************************************** */ /* ------------------------------------------------------- * Track globals * ------------------------------------------------------- */ /** @brief Value to indicate a global has not been allocated */ #define GLOBAL_UNALLOCATED -1 /* ------------------------------------------------------- * Track register allocation * ------------------------------------------------------- */ /** @brief Index of a register */ typedef int registerindx; /** @brief Value to indicate a register has not been allocated */ #define REGISTER_UNALLOCATED -1 /** This structure tracks the contents of each register as the function is being compiled. */ typedef struct { bool isallocated; /** Whether the register has been allocated */ bool iscaptured; /** Whether the register becomes an upvalue */ bool isoptionalarg; /** Whether the register contains an optional argument */ unsigned int scopedepth; /** Scope depth at which the register was allocated */ value symbol; /** Symbol associated with the register */ value type; /** Type associated with the register */ value currenttype; /** Current type held by the register */ } registeralloc; #define REGISTERALLOC_EMPTY(sdepth, symb) ((registeralloc) {.isallocated=true, .iscaptured=false, .isoptionalarg=false, .scopedepth=sdepth, .symbol=symb, .type=MORPHO_NIL, .currenttype=MORPHO_NIL}) DECLARE_VARRAY(registeralloc, registeralloc) /* ------------------------------------------------------- * Codeinfo * ------------------------------------------------------- */ typedef enum { REGISTER, CONSTANT, UPVALUE, GLOBAL, VALUE, // Used by the optimizer NOTHING // } returntype; typedef struct { returntype returntype; registerindx dest; unsigned int ninstructions; } codeinfo; #define CODEINFO_ISREGISTER(info) (info.returntype==REGISTER) #define CODEINFO_ISCONSTANT(info) (info.returntype==CONSTANT) #define CODEINFO_ISSHORTCONSTANT(info) (info.returntype==CONSTANT && info.dest #include typedef struct svm vm; #include "error.h" #include "random.h" #include "varray.h" #include "value.h" #include "object.h" #include "common.h" #include "dictionary.h" #include "builtin.h" #include "platform.h" #include "program.h" /* ********************************************************************** * Instruction format * ********************************************************************** */ /** @brief A Morpho instruction * * @details Each instruction fits into a 32 bit unsigned int with the following arrangement *
 *         24      16      8       0
 *  .......|.......|.......|.......|
 *                          ***op*** Opcode (gives 256 instructions)
 *                  ***A****         Operand A
 *          ***B****                 } Operands B & C
 *  ***C****                         }
 *                                      OR
 *  **Bx************                 } Operand Bx as unsigned short
 *  *sBx************                 } Signed operand Bx
 *                                      OR
 *  ***********LBx**********         } 24 bit unsigned integer
 * 
*/ /* --------------------------------------------- * Macros for encoding and decoding instructions * --------------------------------------------- */ /** Encodes an instruction with operands A, B and C. */ #define ENCODE(op, A, B, C) ( (((unsigned) op) & 0xff) | ((A & 0xff) << 8) | ((B & 0xff) << 16) | ((C & 0xff) << 24) ) /** Encodes an instruction with no operands */ #define ENCODE_BYTE(op) (((unsigned) op) & 0xff) /** Encodes an instruction with operand A */ #define ENCODE_SINGLE(op, A) ( (((unsigned) op) & 0xff) | ((A & 0xff) << 8)) /** Encodes an instruction with two operands A and B */ #define ENCODE_DOUBLE(op, A, B) ( (((unsigned) op) & 0xff) | ((A & 0xff) << 8) | ((B & 0xff) << 16)) /** Encodes an instruction with operand A and long operand Bx */ #define ENCODE_LONG(op, A, Bx) ( (instruction) (((unsigned) op) & 0xff) | (instruction) ((A & 0xff) << 8) | (instruction) ((Bx & 0xffff) << 16) ) /** Encodes an instruction with operand Ax and extended operand Bl */ #define ENCODE_EXTENDED(op, Ax) ( (((unsigned) op) & 0xff) | ((A & 0xffffff) << 8) ) #define MASK_OP (0xff) #define MASK_A (0xff << 8) #define MASK_B (0xff << 16) #define MASK_C (0xff << 24) /** Encodes an empty operand */ #define ENCODE_EMPTYOPERAND 0 /** Decode the opcode */ #define DECODE_OP(x) (x & 0xff) /** Decode operand A */ #define DECODE_A(x) ((x>>8) & (0xff)) /** Decode operand B */ #define DECODE_B(x) ((x>>16) & (0xff)) /** Decode operand C */ #define DECODE_C(x) ((x>>24) & (0xff)) /** Decode long operand Bx */ #define DECODE_Bx(x) ((x>>16) & (0xffff)) /** Decode signed long operand Bx */ #define DECODE_sBx(x) ((short) ((x>>16) & (0xffff))) /* ----------------------------- * Opcodes (built automatically) * ----------------------------- */ typedef enum { #define OPCODE(name) OP_##name, #include "opcodes.h" #undef OPCODE } opcode; /* ********************************************************************** * Virtual machine * ********************************************************************** */ /** @brief Maximum number of registers per call frame. */ #define MORPHO_MAXREGISTERS 255 typedef struct { objectfunction *function; objectclosure *closure; ptrdiff_t roffset; // Offset of register from base instruction *pc; unsigned int stackcount; unsigned int returnreg; // Stores where any return value should be placed bool ret; // Should the interpreter return from this frame? int nopt; // Number of optional arguments called #ifdef MORPHO_PROFILER objectbuiltinfunction *inbuiltinfunction; // Keep track if we're in a built in function #endif } callframe; /* ********************************************************************** * Error handlers * ********************************************************************** */ typedef struct { callframe *fp; value dict; } errorhandler; /* ********************************************************************** * Debugger backend * ********************************************************************** */ typedef struct { bool singlestep; /** Is single step mode on? */ struct svm *currentvm; /** Current virtual machine on entry */ int currentline; /** Record current line */ objectfunction *currentfunc; /** Record current function */ value currentmodule; /** Current module */ instructionindx iindx; /** Record current instruction */ error *err; /** Report errors */ int nbreakpoints; /** Number of active breakpoints */ varray_char breakpoints; /** Keep track of breakpoints */ } debugger; /* ********************************************************************** * Profiler * ********************************************************************** */ #ifdef MORPHO_PROFILER /** @brief Morpho profiler */ typedef struct { MorphoThread profiler; MorphoMutex profile_lock; bool profiler_quit; dictionary profile_dict; clock_t start; clock_t end; program *program; } profiler; #endif /* ********************************************************************** * Virtual machines * ********************************************************************** */ /** Gray list for garbage collection */ typedef struct { unsigned int graycount; unsigned int graycapacity; object **list; } graylist; /** @brief Highest register addressable in a window. */ #define VM_MAXIMUMREGISTERNUMBER 255 /** Varrays of vms */ DECLARE_VARRAY(vm, vm*) /** @brief A Morpho virtual machine and its current state */ struct svm { program *current; /** The current program being executed */ varray_value globals; /** Global variables */ varray_value tlvars; /** Thread-local variables */ varray_value stack; /** The stack */ varray_value retain; /** List of values to retain across GC */ callframe frame[MORPHO_CALLFRAMESTACKSIZE]; /** The call frame stack */ errorhandler errorhandlers[MORPHO_ERRORHANDLERSTACKSIZE]; /** Error handler stack */ instruction *instructions; /* Base of instructions */ value *konst; /* Current constant table */ callframe *fp; /* Frame pointer saved on exit */ callframe *fpmax; /* Maximum value of the frame pointer */ errorhandler *ehp; /* Error handler pointer */ error err; /** An error struct that will be filled out when an error occurs */ callframe *errfp; /** Record frame pointer when an error occured */ object *objects; /** Linked list of objects */ graylist gray; /** Graylist for garbage collection */ size_t bound; /** Estimated size of bound bytes */ size_t nextgc; /** Next garbage collection threshold */ debugger *debug; #ifdef MORPHO_PROFILER profiler *profiler; enum { VM_RUNNING, VM_INGC } status; #endif objectupvalue *openupvalues; /** Linked list of open upvalues */ vm *parent; /** Parent vm */ varray_vm subkernels; /** Subkernels */ morphoprintfn printfn; /** Print callback */ void *printref; /** Print callback reference */ varray_char buffer; /** Buffer for printing */ morphoinputfn inputfn; /** Input callback */ void *inputref; /** Input callback reference */ morphowarningfn warningfn; /** Warning callback */ void *warningref; /** Warning callback reference */ morphodebuggerfn debuggerfn; /** Debugger callback */ void *debuggerref; /** Debugger callback reference */ _MORPHO_PADDING; /** Ensure subkernels do not cause false sharing */ }; /* ********************************************************************** * Initializers * ********************************************************************** */ void compile_initialize(void); void compile_finalize(void); #endif /* core_h */ ================================================ FILE: src/core/gc.c ================================================ /** @file gc.c * @author T J Atherton * * @brief Morpho garbage collector */ #include "vm.h" #include "gc.h" extern vm *globalvm; #include "compile.h" #include "morpho.h" #ifdef MORPHO_DEBUG_GCSIZETRACKING extern dictionary sizecheck; #endif /* ********************************************************************** * Gray list * ********************************************************************** */ /* Initialize the gray list */ void vm_graylistinit(graylist *g) { g->graycapacity=0; g->graycount=0; g->list=NULL; } /* Clear the gray list */ void vm_graylistclear(graylist *g) { if (g->list) free(g->list); vm_graylistinit(g); } /* Add an object to the gray list */ void vm_graylistadd(graylist *g, object *obj) { if (g->graycount+1>=g->graycapacity) { g->graycapacity*=2; if (g->graycapacity<8) g->graycapacity=8; g->list=realloc(g->list, g->graycapacity*sizeof(object *)); } if (g->list) { g->list[g->graycount]=obj; g->graycount++; } } /* ********************************************************************** * Garbage collector * ********************************************************************** */ /** Recalculates the size of bound objects to the VM */ size_t vm_gcrecalculatesize(vm *v) { size_t size = 0; for (object *ob=v->objects; ob!=NULL; ob=ob->next) { size+=object_size(ob); } return size; } /** Marks an object as reachable */ void vm_gcmarkobject(vm *v, object *obj) { if (!obj || obj->status!=OBJECT_ISUNMARKED) return; #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "Marking %p ", obj); morpho_printvalue(v, MORPHO_OBJECT(obj)); morpho_printf(v, "\n"); #endif obj->status=OBJECT_ISMARKED; vm_graylistadd(&v->gray, obj); } /** Marks a value as reachable */ void vm_gcmarkvalue(vm *v, value val) { if (MORPHO_ISOBJECT(val)) { vm_gcmarkobject(v, MORPHO_GETOBJECT(val)); } } /** Marks all entries in a dictionary */ void vm_gcmarkdictionary(vm *v, dictionary *dict) { for (unsigned int i=0; icapacity; i++) { if (!MORPHO_ISNIL(dict->contents[i].key)) { vm_gcmarkvalue(v, dict->contents[i].key); vm_gcmarkvalue(v, dict->contents[i].val); } } } /** Marks all entries in an array */ void vm_gcmarkarray(vm *v, varray_value *array) { if (array) for (unsigned int i=0; icount; i++) { vm_gcmarkvalue(v, array->data[i]); } } /** Public veneers */ void morpho_markobject(void *v, object *obj) { vm_gcmarkobject((vm *) v, obj); } void morpho_markvalue(void *v, value val) { vm_gcmarkvalue((vm *) v, val); } void morpho_markdictionary(void *v, dictionary *dict) { vm_gcmarkdictionary((vm *) v, dict); } void morpho_markvarrayvalue(void *v, varray_value *array) { vm_gcmarkarray((vm *) v, array); } /** Searches a vm for all reachable objects */ void vm_gcmarkroots(vm *v) { /** Mark anything on the stack */ #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Stack.\n"); #endif value *stacktop = v->stack.data+v->fp->roffset+v->fp->function->nregs-1; /* Find the largest stack position currently in play */ /*for (callframe *f=v->frame; ffp; f++) { value *ftop = v->stack.data+f->roffset+f->function->nregs-1; if (ftop>stacktop) stacktop=ftop; }*/ //debug_showstack(v); for (value *s=stacktop; s>=v->stack.data; s--) { if (MORPHO_ISOBJECT(*s)) vm_gcmarkvalue(v, *s); } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Globals.\n"); #endif for (unsigned int i=0; iglobals.count; i++) { vm_gcmarkvalue(v, v->globals.data[i]); } /** Mark closure objects in use */ #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Closures.\n"); #endif for (callframe *f=v->frame; f && v->fp && f<=v->fp; f++) { if (f->closure) vm_gcmarkobject(v, (object *) f->closure); } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Open upvalues.\n"); #endif for (objectupvalue *u=v->openupvalues; u!=NULL; u=u->next) { vm_gcmarkobject(v, (object *) u); } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Retain list.\n"); #endif for (int i=0; iretain.count; i++) { vm_gcmarkvalue(v, v->retain.data[i]); } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> Thread local storage.\n"); #endif for (int i=0; itlvars.count; i++) { vm_gcmarkvalue(v, v->tlvars.data[i]); } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "> End mark roots.\n"); #endif } void vm_gcmarkretainobject(vm *v, object *obj) { #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "Searching object %p ", (void *) obj); morpho_printvalue(v, MORPHO_OBJECT(obj)); morpho_printf(v, "\n"); #endif objecttypedefn *defn=object_getdefn(obj); if (defn->markfn) defn->markfn(obj, v); } /** Forces the GC to search an unmanaged object */ void morpho_searchunmanagedobject(void *v, object *obj) { vm_gcmarkretainobject((vm *) v, obj); } /** Trace all objects on the graylist */ void vm_gctrace(vm *v) { while (v->gray.graycount>0) { object *obj=v->gray.list[v->gray.graycount-1]; v->gray.graycount--; vm_gcmarkretainobject(v, obj); } } /** Go through the VM's object list and free all unmarked objects */ void vm_gcsweep(vm *v) { object *prev=NULL; object *obj = v->objects; while (obj!=NULL) { if (obj->status==OBJECT_ISMARKED) { prev=obj; obj->status=OBJECT_ISUNMARKED; /* Clear for the next cycle */ obj=obj->next; } else { object *unreached = obj; size_t size=object_size(obj); #ifdef MORPHO_DEBUG_GCSIZETRACKING value xsize; if (dictionary_get(&sizecheck, MORPHO_OBJECT(unreached), &xsize)) { size_t isize = MORPHO_GETINTEGERVALUE(xsize); if (size!=isize) { morpho_printvalue(v, MORPHO_OBJECT(unreached)); UNREACHABLE("Object doesn't match its declared size"); } } #endif v->bound-=size; /* Delink */ obj=obj->next; if (prev!=NULL) { prev->next=obj; } else { v->objects=obj; } #ifndef MORPHO_DEBUG_GCSIZETRACKING object_free(unreached); #endif } } } /** Collects garbage */ void vm_collectgarbage(vm *v) { #ifdef MORPHO_DEBUG_DISABLEGARBAGECOLLECTOR return; #endif vm *vc = (v!=NULL ? v : globalvm); if (!vc) return; if (vc->parent) return; // Don't garbage collect in subkernels #ifdef MORPHO_PROFILER vc->status=VM_INGC; #endif if (vc && vc->bound>0) { size_t init=vc->bound; #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "--- begin garbage collection ---\n"); #endif vm_gcmarkroots(vc); vm_gctrace(vc); vm_gcsweep(vc); if (vc->bound>init) { #ifdef MORPHO_DEBUG_GCSIZETRACKING morpho_printf(v, "GC collected %ld bytes (from %zu to %zu) next at %zu.\n", init-vc->bound, init, vc->bound, vc->bound*MORPHO_GCGROWTHFACTOR); UNREACHABLE("VM bound object size < 0"); #else // This catch has been put in to prevent the garbarge collector from completely seizing up. vc->bound=vm_gcrecalculatesize(v); #endif } vc->nextgc=vc->bound*MORPHO_GCGROWTHFACTOR; #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "--- end garbage collection ---\n"); if (vc) morpho_printf(v, " collected %ld bytes (from %zu to %zu) next at %zu.\n", init-vc->bound, init, vc->bound, vc->nextgc); #endif } #ifdef MORPHO_PROFILER vc->status=VM_RUNNING; #endif } ================================================ FILE: src/core/gc.h ================================================ /** @file gc.h * @author T J Atherton * * @brief Morpho garbage collector */ #ifndef gc_h #define gc_h /* ********************************************************************** * Interface * ********************************************************************** */ void vm_graylistinit(graylist *g); void vm_graylistclear(graylist *g); void vm_graylistadd(graylist *g, object *obj); void vm_unbindobject(vm *v, value obj); void vm_freeobjects(vm *v); void vm_collectgarbage(vm *v); #endif /* vm_h */ ================================================ FILE: src/core/opcodes.h ================================================ /** @file opcodes.h * @author T J Atherton * * @brief Morpho opcodes */ #ifdef OPCODE /** No operation */ OPCODE(NOP) /** Moves contents between registers */ OPCODE(MOV) /** Moves a constant into a register */ OPCODE(LCT) /** Add the contents of two registers */ OPCODE(ADD) /** Subtract the contents of two registers */ OPCODE(SUB) /** Multiply the contents of two registers */ OPCODE(MUL) /** Divide the contents of two registers */ OPCODE(DIV) /** Raise to a power */ OPCODE(POW) /** Comparison test */ OPCODE(EQ) /** Comparison test */ OPCODE(NEQ) /** Comparison test */ OPCODE(LT) /** Comparison test */ OPCODE(LE) /** Logical NOT of a register */ OPCODE(NOT) /** Branching */ OPCODE(B) /** Branch if true */ OPCODE(BIF) /** Branch if false */ OPCODE(BIFF) /** Call */ OPCODE(CALL) /** Invoke */ OPCODE(INVOKE) /** Method call */ OPCODE(METHOD) /** Return */ OPCODE(RETURN) /** Create a closure */ OPCODE(CLOSURE) /** Load upvalue */ OPCODE(LUP) /** Store upvalue */ OPCODE(SUP) /** Close upvalues */ OPCODE(CLOSEUP) /** Load property */ OPCODE(LPR) /** Store property */ OPCODE(SPR) /** Load index */ OPCODE(LIX) /** Load index list */ OPCODE(LIXL) /** Store index */ OPCODE(SIX) /** Load global */ OPCODE(LGL) /** Store global */ OPCODE(SGL) /** Push error handler */ OPCODE(PUSHERR) /** Pop error handler */ OPCODE(POPERR) /** Creates an array */ //OPCODE(ARRAY) /** Converts a sequence of registers to strings if necessary and concatenates them */ OPCODE(CAT) /** Print the cotents of a register */ OPCODE(PRINT) /** Raise error */ //OPCODE(RAISE) /** Breakpoint */ OPCODE(BREAK) /** End program */ OPCODE(END) #endif ================================================ FILE: src/core/vm.c ================================================ /** @file vm.c * @author T J Atherton * * @brief Morpho virtual machine */ #include #include #include "vm.h" #include "gc.h" #include "compile.h" #include "morpho.h" #include "classes.h" #include "debug.h" #include "profile.h" #include "resources.h" #include "extensions.h" value initselector = MORPHO_NIL; value indexselector = MORPHO_NIL; value setindexselector = MORPHO_NIL; value addselector = MORPHO_NIL; value addrselector = MORPHO_NIL; value subselector = MORPHO_NIL; value subrselector = MORPHO_NIL; value mulselector = MORPHO_NIL; value mulrselector = MORPHO_NIL; value divselector = MORPHO_NIL; value divrselector = MORPHO_NIL; value powselector = MORPHO_NIL; value powrselector = MORPHO_NIL; value printselector = MORPHO_NIL; value enumerateselector = MORPHO_NIL; value countselector = MORPHO_NIL; value cloneselector = MORPHO_NIL; #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary sizecheck; #endif /* ********************************************************************** * VM objects * ********************************************************************** */ vm *globalvm=NULL; /** Initializes a virtual machine */ static void vm_init(vm *v) { globalvm=v; v->current=NULL; v->instructions=NULL; v->objects=NULL; v->openupvalues=NULL; v->fp=NULL; v->fpmax=&v->frame[MORPHO_CALLFRAMESTACKSIZE-1]; // Last valid value of v->fp v->ehp=NULL; v->bound=0; v->nextgc=MORPHO_GCINITIAL; v->debug=NULL; vm_graylistinit(&v->gray); varray_valueinit(&v->stack); varray_valueinit(&v->tlvars); varray_valueinit(&v->globals); varray_valueinit(&v->retain); varray_valueresize(&v->stack, MORPHO_STACKINITIALSIZE); varray_charinit(&v->buffer); error_init(&v->err); v->errfp=NULL; #ifdef MORPHO_PROFILER v->profiler=NULL; v->status=VM_RUNNING; #endif v->parent=NULL; varray_vminit(&v->subkernels); v->printfn=NULL; v->printref=NULL; v->inputfn=NULL; v->inputref=NULL; v->warningfn=NULL; v->warningref=NULL; v->debuggerfn=NULL; v->debuggerref=NULL; } /** Clears a virtual machine */ static void vm_clear(vm *v) { varray_valueclear(&v->stack); varray_valueclear(&v->globals); varray_valueclear(&v->tlvars); varray_valueclear(&v->retain); vm_graylistclear(&v->gray); varray_charclear(&v->buffer); vm_freeobjects(v); for (int i=0; isubkernels.count; i++) { vm *subkernel = v->subkernels.data[i]; subkernel->globals.capacity=0; // Globals duplicates the parent vm so ignore subkernel->globals.data=NULL; vm_clear(subkernel); MORPHO_FREE(subkernel); } varray_vmclear(&v->subkernels); } /** Prepares a vm to run program p */ bool vm_start(vm *v, program *p) { /* Set the current program */ v->current=p; /* Clear current error state */ error_clear(&v->err); v->errfp=NULL; /* Set up the callframe stack */ v->fp=v->frame; /* Set the frame pointer to the bottom of the stack */ v->fp->function=p->global; v->fp->closure=NULL; v->fp->roffset=0; v->fp->nopt=0; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif /* Set instruction base */ v->instructions = p->code.data; if (!v->instructions) return false; /* Set up the constant table */ varray_value *konsttable=object_functiongetconstanttable(p->global); if (!konsttable) return false; v->konst = konsttable->data; return true; } /** Frees all objects bound to a virtual machine */ void vm_freeobjects(vm *v) { #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR long k = 0; morpho_printf(v, "--- Freeing objects bound to VM ---\n"); #endif object *next=NULL; for (object *e=v->objects; e!=NULL; e=next) { next = e->next; object_free(e); #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR k++; #endif } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR morpho_printf(v, "--- Freed %li objects bound to VM ---\n", k); #endif } /* ********************************************************************** * Binding and unbinding objects to the VM * ********************************************************************** */ /** Unbinds an object from a VM. */ void vm_unbindobject(vm *v, value obj) { object *ob=MORPHO_GETOBJECT(obj); if (v->objects==ob) { v->objects=ob->next; } else { for (object *e=v->objects; e!=NULL; e=e->next) { if (e->next==ob) { e->next=ob->next; break; } } } // Correct estimate of bound size. if (MORPHO_ISGARBAGECOLLECTED(obj)) { v->bound-=object_size(ob); ob->status=OBJECT_ISUNMANAGED; } } /** @brief Binds an object to a Virtual Machine. * @details Any object created during execution should be bound to a VM; this object is then managed by the garbage collector. * @param v the virtual machine * @param obj object to bind */ static void vm_bindobject(vm *v, value obj) { object *ob = MORPHO_GETOBJECT(obj); ob->status=OBJECT_ISUNMARKED; ob->next=v->objects; v->objects=ob; size_t size=object_size(ob); #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary_insert(&sizecheck, obj, MORPHO_INTEGER(size)); #endif v->bound+=size; #ifdef MORPHO_DEBUG_STRESSGARBAGECOLLECTOR vm_collectgarbage(v); #else if (v->bound>v->nextgc) vm_collectgarbage(v); #endif } /** @brief Binds an object to a Virtual Machine without garbage collection. * @details Any object created during execution should be bound to a VM; this object is then managed by the garbage collector. * @param v the virtual machine * @param obj object to bind * @warning: This should only be used in circumstances where the internal state of the VM is not consistent (i.e. calling the GC could cause a sigsev) */ static void vm_bindobjectwithoutcollect(vm *v, value obj) { object *ob = MORPHO_GETOBJECT(obj); ob->status=OBJECT_ISUNMARKED; ob->next=v->objects; v->objects=ob; size_t size=object_size(ob); #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary_insert(&sizecheck, obj, MORPHO_INTEGER(size)); #endif v->bound+=size; } /* ********************************************************************** * Virtual machine * ********************************************************************** */ /** @brief Raises a runtime error * @param v the virtual machine * @param id error id * @param ... additional data for sprintf. */ void vm_runtimeerror(vm *v, ptrdiff_t iindx, errorid id, ...) { va_list args; int line=ERROR_POSNUNIDENTIFIABLE, posn=ERROR_POSNUNIDENTIFIABLE; value module=MORPHO_NIL; debug_infofromindx(v->current, iindx, &module, &line, &posn, NULL, NULL); va_start(args, id); morpho_writeerrorwithidvalist(&v->err, id, NULL, line, posn, args); va_end(args); } /** @brief Raises a BadOp error * @param v the virtual machine * @param id error id * @param op the opertion that went bad in (human readable) * @param left the left hand side of the bad operation * @param right the right hand side of the bad operation */ void vm_throwOpError(vm *v, ptrdiff_t iindx, errorid id, char* op, value left, value right){ varray_char left_buffer; varray_char right_buffer; varray_charinit(&left_buffer); varray_charinit(&right_buffer); morpho_printtobuffer(v, left, &left_buffer); morpho_printtobuffer(v, right, &right_buffer); varray_charresize(&left_buffer,left_buffer.count); varray_charresize(&right_buffer,right_buffer.count); // ensure the the rest of the alocated data is empty for (int i = left_buffer.count; iopenupvalues; objectupvalue *new = NULL; /* Is there an existing open upvalue that points to the same location? */ for (;up!=NULL && up->location>reg;up=up->next) { prev=up; } /* If so, return it */ if (up != NULL && up->location==reg) return up; /* If not create a new one */ new=object_newupvalue(reg); if (new) { /* And link it into the list */ new->next=up; if (prev) { prev->next=new; } else { v->openupvalues=new; } vm_bindobject(v, MORPHO_OBJECT(new)); } return new; } /** @brief Closes upvalues that refer beyond a specified register * @param v the virtual machine * @param reg register to capture */ static inline void vm_closeupvalues(vm *v, value *reg) { while (v->openupvalues!=NULL && v->openupvalues->location>=reg) { objectupvalue *up = v->openupvalues; up->closed=*up->location; /* Store closed value */ up->location=&up->closed; /* Point to closed value */ v->openupvalues=up->next; /* Delink from openupvalues list */ up->next=NULL; } } /** Check if we should break at a given pc */ bool vm_shouldbreakatpc(vm *v, instruction *pc) { if (!v->debug) return false; if (debugger_insinglestep(v->debug)) return true; instructionindx iindx = pc-v->current->code.data-1; if (debugger_shouldbreakat(v->debug, iindx)) return true; return false; } /** Return the previous instruction index */ instructionindx vm_previnstruction(vm *v) { if (v->fp->pc>v->current->code.data) return v->fp->pc-v->current->code.data-1; return 0; } /** Return the current instruction index */ instructionindx vm_currentinstruction(vm *v) { return v->fp->pc-v->current->code.data-1; } /** Return the current debugger */ debugger *vm_getdebugger(vm *v) { return v->debug; } /** @brief Expands the stack by a specified amount * @param v the virtual machine * @param reg the current register base * @param n Number of stack spaces to expand by */ static inline void vm_expandstack(vm *v, value **reg, unsigned int n) { if (v->stack.count+n>v->stack.capacity) { /* Calculate new size */ unsigned int newsize=MORPHO_STACKGROWTHFACTOR*v->stack.capacity; if (newsizestack.data; varray_ptrdiff diff; varray_ptrdiffinit(&diff); /* Preserve open upvalue offsets */ for (objectupvalue *u=v->openupvalues; u!=NULL; u=u->next) { ptrdiff_t p=u->location-v->stack.data; varray_ptrdiffadd(&diff, &p, 1); } /* Resize the stack */ varray_valueresize(&v->stack, newsize); /* Recalculate upvalues */ unsigned int k=0; for (objectupvalue *u=v->openupvalues; u!=NULL; u=u->next) { u->location=v->stack.data+diff.data[k]; k++; } /* Free our varray of ptrdiffs */ varray_ptrdiffclear(&diff); /* Correct the stack pointer */ *reg = v->stack.data+roffset; } v->stack.count+=n; } /** Process optional args */ bool vm_optargs(vm *v, ptrdiff_t iindx, objectfunction *func, unsigned int nopt, value *args, value *outreg) { unsigned int nfopt = func->opt.count; // Copy across default values for (unsigned int i=0; ikonst.data[func->opt.data[i].def]; } for (unsigned int i=0; inopt; k++) if (MORPHO_ISSAME(func->opt.data[k].symbol, key)) break; if (k>=func->nopt) { // If we didn't find a match, we're done with optional arguments if (MORPHO_ISSTRING(key)) { vm_runtimeerror(v, iindx, VM_UNKNWNOPTARG, MORPHO_GETCSTRING(key)); return false; } break; } outreg[k]=args[2*i+1]; } return true; } /** Process variadic and optional arguments * @param[in] v - the VM * @param[in] iindx - instruction index (used to raise errors if need be) * @param[in] func - function being called * @param[in] regcall - Register for the call * @param[in] nargs - number of arguments being called with * @param[in] args - arguments used * @param[in] reg - register base * @param[in] newreg - new register base */ static inline bool vm_vargs(vm *v, ptrdiff_t iindx, objectfunction *func, unsigned int nargs, value *args, value *newreg) { int nfixed = func->nargs, // No. of fixed params rVarg = nfixed+1, // Position of first optional parameter in output nposn=nargs; // No. of positional arguments this function was called with if (func->varg>=0) { if (nposnstack.data && arglist>v->stack.data && argliststack.data+v->stack.capacity); } else { /** Otherwise use the registers after regcall */ arglist=(*reg)+regcall+1; } if (argsonstack) aoffset=arglist-v->stack.data; /** If args on stack retain where they are */ /* In the old frame... */ v->fp->pc=*pc; /* Save the program counter */ v->fp->stackcount=v->fp->function->nregs+(unsigned int) v->fp->roffset; /* Store the stacksize */ v->fp->returnreg=regcall; /* Store the return register */ unsigned int oldnregs = v->fp->function->nregs; /* Get the old number of registers */ if (v->fp==v->fpmax) { // Detect stack overflow vm_runtimeerror(v, (*pc) - v->instructions, VM_STCKOVFLW); return false; } v->fp++; /* Advance frame pointer */ v->fp->pc=*pc; /* We will also store the program counter in the new frame; this will be used to detect whether the VM should return on OP_RETURN */ #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif if (MORPHO_ISCLOSURE(fn)) { objectclosure *closure=MORPHO_GETCLOSURE(fn); /* Closure object in use */ func=closure->func; v->fp->closure=closure; } else { v->fp->closure=NULL; } v->fp->ret=false; /* Interpreter should not return from this frame */ v->fp->function=func; /* Store the function */ /* Do we need to expand the stack? */ if (v->stack.count+func->nregs>v->stack.capacity) { vm_expandstack(v, reg, func->nregs); /* Expand the stack */ } else { v->stack.count+=func->nregs; } v->konst = func->konst.data; /* Load the constant table */ *reg += oldnregs; /* Shift the register frame */ v->fp->roffset=*reg-v->stack.data; /* Store the register index */ /* Copy arguments into new register window */ if (argsonstack) { arglist = v->stack.data+aoffset; // If they were on the stack, the pointer may be invalid so update it. (*reg)[0] = arglist[-1]; // Copy the caller into r0 } else { (*reg)[0] = fn; } for (unsigned int i=0; ivarg>=0) { if (!vm_vargs(v, (*pc) - v->instructions, func, nargs, arglist, *reg)) return false; nvarg=1; } else if (func->nargs!=nargs) { vm_runtimeerror(v, (*pc) - v->instructions, VM_INVALIDARGS, func->nargs, nargs); return false; } /* Handle optional args */ if (func->opt.count>0) { if (!vm_optargs(v, (*pc) - v->instructions, func, nopt, arglist+nargs, (*reg)+func->nargs+nvarg+1)) return false; } else if (nopt>0) { vm_runtimeerror(v, (*pc) - v->instructions, VM_NOOPTARG); return false; } /* Zero out registers beyond args up to the top of the stack This has to be fast: memset was too slow. Zero seems to be faster than MORPHO_NIL */ for (value *r = *reg + func->nregs-1; r > *reg + func->nargs + func->nopt + nvarg; r--) *r = MORPHO_INTEGER(0); *pc=v->instructions+func->entry; /* Jump to the function */ return true; } /** Invokes a method on a given object by name */ static inline bool vm_invoke(vm *v, value obj, value method, int nargs, value *args, value *out) { if (MORPHO_ISINSTANCE(obj)) { /* Look up the method */ objectinstance *instance=MORPHO_GETINSTANCE(obj); value fn=MORPHO_NIL; if (dictionary_getintern(&instance->klass->methods, method, &fn)) { return morpho_invoke(v, obj, fn, nargs, args, out); } } else if (MORPHO_ISCLASS(obj)) { objectclass *klass=MORPHO_GETCLASS(obj); value fn=MORPHO_NIL; if (dictionary_getintern(&klass->methods, method, &fn)) { return morpho_invoke(v, obj, fn, nargs, args, out); } } else if (MORPHO_ISOBJECT(obj)) { /* If it's an object, it may have a veneer class */ objectclass *klass = object_getveneerclass(MORPHO_GETOBJECTTYPE(obj)); if (klass) { value ifunc; if (dictionary_getintern(&klass->methods, method, &ifunc)) { if (MORPHO_ISBUILTINFUNCTION(ifunc)) { value sargs[nargs+1]; sargs[0]=obj; for (unsigned int i=0; ifunction) (v, nargs, sargs); return true; } } } } return false; } /** Recovers the number of optional arguments */ int vm_getoptionalargs(vm *v) { return v->fp->nopt; } /** @brief Executes a sequence of code * @param v The virtual machine to use * @param rstart Starting register pointer * @param istart Instruction to begin at * @returns A morpho error */ bool morpho_interpret(vm *v, value *rstart, instructionindx istart) { /* Set the register pointer to the bottom of the stack */ value *reg = rstart; /* Set the program counter to start */ instruction *pc=v->instructions+istart; /* Pointer to the next instruction to be executed */ /* Temporary variables to */ int op=OP_NOP, a, b, c; /* Opcode and operands a, b, c */ instruction bc; /* The current bytecode */ value left, right; #ifdef MORPHO_DEBUG_PRINT_INSTRUCTIONS #define MORPHO_DISASSEMBLE_INSRUCTION(bc,pc,k,r) { morpho_printf(v, " "); debugger_disassembleinstruction(v, bc, pc-1, k, r); morpho_printf(v, "\n"); } #else #define MORPHO_DISASSEMBLE_INSRUCTION(bc,pc,k,r); #endif #ifdef MORPHO_OPCODE_USAGE unsigned long opcount[OP_END+1]; unsigned long opopcount[OP_END+1][OP_END+1]; for (unsigned int i=0; ifp->pc=pc; \ v->fp->roffset=reg-v->stack.data; \ debugger_enter(v->debug, v); \ } /** Define the interpreter loop. Computed gotos or regular switch statements can be used here. */ #ifdef MORPHO_COMPUTED_GOTO /** The dispatch table, containing the entry points for each opcode */ static void* dispatchtable[] = { #define OPCODE(name) &&code_##name, #include "opcodes.h" #undef OPCODE }; static void* debugdispatchtable[] = { // Backup copy of the dispatch table #define OPCODE(name) &&code_##name, #include "opcodes.h" #undef OPCODE }; /** The interpret loop begins by dispatching an instruction */ #define INTERPRET_LOOP DISPATCH(); /** Create a label corresponding to each opcode */ #define CASE_CODE(name) code_##name int nopcodes; for (nopcodes=0; dispatchtable[nopcodes]!=&&code_END; ) nopcodes++; #define DEBUG_ENABLE() { if (v->debug) for (int i=0; idebug) for (int i=0; iinstructions,v->konst, reg) \ } #define DISPATCH() \ do { \ FETCHANDDECODE() \ goto *dispatchtable[op]; \ } while(false); #else /** Every iteration of the interpret loop we fetch, decode and switch */ #define INTERPRET_LOOP \ loop: \ bc=*pc++; \ OPOPCODECNT(pp, bc) \ op=DECODE_OP(bc); \ OPCODECNT(op) \ MORPHO_DISASSEMBLE_INSRUCTION(bc,pc-v->instructions,v->konst, reg) \ if (vm_shouldbreakatpc(v, pc)) ENTERDEBUGGER(); \ switch (op) /** Each opcode generates a case statement */ #define CASE_CODE(name) case OP_##name /** Dispatch means return to the beginning of the loop */ #define DISPATCH() goto loop; #endif /** Macros for error checking */ #define ERROR(id) { vm_runtimeerror(v, pc-v->instructions, id); goto vm_error; } #define VERROR(id, ...) { vm_runtimeerror(v, pc-v->instructions, id, __VA_ARGS__); goto vm_error; } #define OPERROR(op){vm_throwOpError(v,pc-v->instructions,VM_INVLDOP,op,left,right); goto vm_error; } #define ERRORCHK() if (v->err.cat!=ERROR_NONE) goto vm_error; /** Macro to redirect an opcode to a method call on an object */ #define OPREDIRECT(leftselector, rightselector, regout) \ if (MORPHO_ISOBJECT(left)) { \ if (vm_invoke(v, left, leftselector, 1, &right, ®[regout])) { \ ERRORCHK(); \ if (!MORPHO_ISNIL(reg[a])) DISPATCH(); \ } \ } \ if (MORPHO_ISOBJECT(right)) { \ if (vm_invoke(v, right, rightselector, 1, &left, ®[regout])) { \ ERRORCHK(); \ DISPATCH(); \ } \ } INTERPRET_LOOP { CASE_CODE(NOP): DISPATCH(); CASE_CODE(MOV): a=DECODE_A(bc); b=DECODE_B(bc); reg[a] = reg[b]; DISPATCH(); CASE_CODE(LCT): a=DECODE_A(bc); b=DECODE_Bx(bc); reg[a] = v->konst[b]; DISPATCH(); CASE_CODE(ADD): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISFLOAT(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) + MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) + (double) MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } else if (MORPHO_ISINTEGER(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( (double) MORPHO_GETINTEGERVALUE(left) + MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_INTEGER( MORPHO_GETINTEGERVALUE(left) + MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } else if (MORPHO_ISSTRING(left) && MORPHO_ISSTRING(right)) { reg[a] = object_concatenatestring(left, right); if (!MORPHO_ISNIL(reg[a])) { vm_bindobject(v, reg[a]); DISPATCH(); } else { ERROR(VM_CNCTFLD); DISPATCH(); } } OPREDIRECT(addselector, addrselector, a); OPERROR("Add"); DISPATCH(); CASE_CODE(SUB): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISFLOAT(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) - MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) - (double) MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } else if (MORPHO_ISINTEGER(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( (double) MORPHO_GETINTEGERVALUE(left) - MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_INTEGER( MORPHO_GETINTEGERVALUE(left) - MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } OPREDIRECT(subselector, subrselector, a); OPERROR("Subtract") DISPATCH(); CASE_CODE(MUL): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISFLOAT(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) * MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) * (double) MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } else if (MORPHO_ISINTEGER(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( (double) MORPHO_GETINTEGERVALUE(left) * MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_INTEGER( MORPHO_GETINTEGERVALUE(left) * MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } OPREDIRECT(mulselector, mulrselector, a); OPERROR("Multiply") DISPATCH(); CASE_CODE(DIV): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISFLOAT(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) / MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( MORPHO_GETFLOATVALUE(left) / (double) MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } else if (MORPHO_ISINTEGER(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( (double) MORPHO_GETINTEGERVALUE(left) / MORPHO_GETFLOATVALUE(right)); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( (double) MORPHO_GETINTEGERVALUE(left) / (double) MORPHO_GETINTEGERVALUE(right)); DISPATCH(); } } OPREDIRECT(divselector, divrselector, a); OPERROR("Divide"); DISPATCH(); CASE_CODE(POW): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISFLOAT(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( pow(MORPHO_GETFLOATVALUE(left), MORPHO_GETFLOATVALUE(right)) ); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( pow(MORPHO_GETFLOATVALUE(left), (double) MORPHO_GETINTEGERVALUE(right)) ); DISPATCH(); } } else if (MORPHO_ISINTEGER(left)) { if (MORPHO_ISFLOAT(right)) { reg[a] = MORPHO_FLOAT( pow((double) MORPHO_GETINTEGERVALUE(left), MORPHO_GETFLOATVALUE(right)) ); DISPATCH(); } else if (MORPHO_ISINTEGER(right)) { reg[a] = MORPHO_FLOAT( pow((double) MORPHO_GETINTEGERVALUE(left), (double) MORPHO_GETINTEGERVALUE(right)) ); DISPATCH(); } } OPREDIRECT(powselector, powrselector, a); OPERROR("Exponentiate") DISPATCH(); CASE_CODE(NOT): a=DECODE_A(bc); b=DECODE_B(bc); left = reg[b]; if (MORPHO_ISBOOL(left)) { reg[a] = MORPHO_BOOL(!MORPHO_GETBOOLVALUE(left)); } else { reg[a] = MORPHO_BOOL(MORPHO_ISNIL(left)); } DISPATCH(); CASE_CODE(EQ): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; reg[a] = (morpho_extendedcomparevalue(left, right)==0 ? MORPHO_BOOL(true) : MORPHO_BOOL(false)); DISPATCH(); CASE_CODE(NEQ): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; reg[a] = (morpho_extendedcomparevalue(left, right)!=0 ? MORPHO_BOOL(true) : MORPHO_BOOL(false)); DISPATCH(); CASE_CODE(LT): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if ( !( (MORPHO_ISFLOAT(left) || MORPHO_ISINTEGER(left)) && (MORPHO_ISFLOAT(right) || MORPHO_ISINTEGER(right)) ) ) { OPERROR("Compare"); } reg[a] = (morpho_extendedcomparevalue(left, right)>0 ? MORPHO_BOOL(true) : MORPHO_BOOL(false)); DISPATCH(); CASE_CODE(LE): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if ( !( (MORPHO_ISFLOAT(left) || MORPHO_ISINTEGER(left)) && (MORPHO_ISFLOAT(right) || MORPHO_ISINTEGER(right)) ) ) { OPERROR("Compare"); } reg[a] = (morpho_extendedcomparevalue(left, right)>=0 ? MORPHO_BOOL(true) : MORPHO_BOOL(false)); DISPATCH(); CASE_CODE(B): b=DECODE_sBx(bc); pc+=b; DISPATCH(); CASE_CODE(BIF): a=DECODE_A(bc); left=reg[a]; if (MORPHO_ISTRUE(left)) pc+=DECODE_sBx(bc); DISPATCH(); CASE_CODE(BIFF): a=DECODE_A(bc); left=reg[a]; if (MORPHO_ISFALSE(left)) pc+=DECODE_sBx(bc); DISPATCH(); CASE_CODE(CALL): a=DECODE_A(bc); left=reg[a]; b=DECODE_B(bc); // Number of positional arguments c=DECODE_C(bc); // Number of optional arguments callfunction: // Jump here if an instruction becomes a call if (MORPHO_ISINVOCATION(left)) { /* An method invocation */ objectinvocation *inv = MORPHO_GETINVOCATION(left); left=inv->method; reg[a]=inv->receiver; } if (MORPHO_ISMETAFUNCTION(left) && !metafunction_resolve(MORPHO_GETMETAFUNCTION(left), b, reg+a+1, &v->err, &left)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } if (MORPHO_ISFUNCTION(left) || MORPHO_ISCLOSURE(left)) { if (!vm_call(v, left, a, b, c, NULL, &pc, ®)) goto vm_error; } else if (MORPHO_ISBUILTINFUNCTION(left)) { /* Save program counter in the old callframe */ v->fp->pc=pc; v->fp->nopt=c; objectbuiltinfunction *f = MORPHO_GETBUILTINFUNCTION(left); #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=f; #endif value ret = (f->function) (v, b, reg+a); #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); reg=v->stack.data+v->fp->roffset; /* Ensure register pointer is correct */ reg[a]=ret; } else if (MORPHO_ISCLASS(left)) { /* A function call on a class instantiates it */ objectclass *klass = MORPHO_GETCLASS(left); objectinstance *instance = object_newinstance(klass); if (instance) { reg[a] = MORPHO_OBJECT(instance); vm_bindobject(v, reg[a]); /* Call the initializer if class provides one */ value ifunc; if (dictionary_getintern(&klass->methods, initselector, &ifunc)) { if (MORPHO_ISMETAFUNCTION(ifunc) && !metafunction_resolve(MORPHO_GETMETAFUNCTION(ifunc), b, reg+a+1, &v->err, &ifunc)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } /* If so, call it */ if (MORPHO_ISFUNCTION(ifunc)) { if (!vm_call(v, ifunc, a, b, c, NULL, &pc, ®)) goto vm_error; } else if (MORPHO_ISBUILTINFUNCTION(ifunc)) { v->fp->nopt=c; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=MORPHO_GETBUILTINFUNCTION(ifunc); #endif (MORPHO_GETBUILTINFUNCTION(ifunc)->function) (v, b, reg+a); #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); } } else { if (b>0) { VERROR(VM_NOINITIALIZER, MORPHO_GETCSTRING(klass->name)); } } } else { ERROR(VM_INSTANTIATEFAILED); } } else { ERROR(VM_UNCALLABLE); } DISPATCH(); CASE_CODE(METHOD): // Direct method call a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); right=reg[a]; // The method left=reg[a+1]; // The object if (MORPHO_ISMETAFUNCTION(right)) { if (!metafunction_resolve(MORPHO_GETMETAFUNCTION(right), b, reg+a+2, &v->err, &right)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } } if (MORPHO_ISFUNCTION(right)) { if (!vm_call(v, right, a+1, b, c, NULL, &pc, ®)) goto vm_error; } else if (MORPHO_ISBUILTINFUNCTION(right)) { v->fp->nopt=c; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=MORPHO_GETBUILTINFUNCTION(right); #endif value ret = (MORPHO_GETBUILTINFUNCTION(right)->function) (v, b, reg+a+1); reg=v->fp->roffset+v->stack.data; /* Restore registers */ reg[a+1] = ret; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); } DISPATCH(); CASE_CODE(INVOKE): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); right=reg[a]; // The method left=reg[a+1]; // The object if (MORPHO_ISINSTANCE(left)) { objectinstance *instance = MORPHO_GETINSTANCE(left); value ifunc; /* Check if we have this method */ if (dictionary_getintern(&instance->klass->methods, right, &ifunc)) { if (MORPHO_ISMETAFUNCTION(ifunc)) { if (!metafunction_resolve(MORPHO_GETMETAFUNCTION(ifunc), b, reg+a+2, &v->err, &ifunc)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } } /* If so, call it */ if (MORPHO_ISFUNCTION(ifunc)) { if (!vm_call(v, ifunc, a+1, b, c, NULL, &pc, ®)) goto vm_error; } else if (MORPHO_ISBUILTINFUNCTION(ifunc)) { v->fp->nopt=c; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=MORPHO_GETBUILTINFUNCTION(ifunc); #endif value ret = (MORPHO_GETBUILTINFUNCTION(ifunc)->function) (v, b, reg+a+1); reg=v->fp->roffset+v->stack.data; /* Restore registers */ reg[a+1] = ret; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); } } else if (dictionary_getintern(&instance->fields, right, &left)) { /* Otherwise, if it's a property, try to call it */ if (morpho_iscallable(left)) { a+=1; reg[a]=left; // Make sure the function is in r0 goto callfunction; // Transmute into a call instruction } else { ERROR(VM_UNCALLABLE); } } else { /* Otherwise, raise an error */ char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_OBJECTLACKSPROPERTY, p); } } else if (MORPHO_ISCLASS(left)) { objectclass *klass = MORPHO_GETCLASS(left); value ifunc; if (dictionary_getintern(&klass->methods, right, &ifunc)) { /* If we're not in the global context, invoke the method on self which is in r0 */ if (v->fp>v->frame) reg[a+1]=reg[0]; /* Copy self into r[a+1] and call */ if (MORPHO_ISMETAFUNCTION(ifunc)) { if (!metafunction_resolve(MORPHO_GETMETAFUNCTION(ifunc), b, reg+a+2, &v->err, &ifunc)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } } if (MORPHO_ISFUNCTION(ifunc)) { if (!vm_call(v, ifunc, a+1, b, c, NULL, &pc, ®)) goto vm_error; } else if (MORPHO_ISBUILTINFUNCTION(ifunc)) { v->fp->nopt=c; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=MORPHO_GETBUILTINFUNCTION(ifunc); #endif value ret = (MORPHO_GETBUILTINFUNCTION(ifunc)->function) (v, b, reg+a+1); reg=v->fp->roffset+v->stack.data; /* Restore registers */ reg[a+1] = ret; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); } } else { /* Otherwise, raise an error */ char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_CLASSLACKSPROPERTY, p); } } else { /* Check if the operand has a veneer class */ objectclass *klass; if (MORPHO_ISOBJECT(left)) klass = object_getveneerclass(MORPHO_GETOBJECTTYPE(left)); else klass = value_getveneerclass(left); if (klass) { value ifunc; if (dictionary_getintern(&klass->methods, right, &ifunc)) { if (MORPHO_ISMETAFUNCTION(ifunc)) { if (!metafunction_resolve(MORPHO_GETMETAFUNCTION(ifunc), b, reg+a+2, &v->err, &ifunc)) { ERRORCHK(); ERROR(VM_MLTPLDSPTCHFLD); } } if (MORPHO_ISBUILTINFUNCTION(ifunc)) { v->fp->nopt=c; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=MORPHO_GETBUILTINFUNCTION(ifunc); #endif value ret = (MORPHO_GETBUILTINFUNCTION(ifunc)->function) (v, b, reg+a+1); reg=v->fp->roffset+v->stack.data; /* Restore registers */ reg[a+1] = ret; #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif ERRORCHK(); } } else { char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_CLASSLACKSPROPERTY, p); } } else { ERROR(VM_NOTANINSTANCE); } } DISPATCH(); CASE_CODE(RETURN): a=DECODE_A(bc); if (v->openupvalues) { /* Close upvalues */ vm_closeupvalues(v, reg); } if (v->ehp) { /* Remove any error handlers from this call frame */ while (v->ehp->fp==v->fp && v->ehp>=v->errorhandlers) v->ehp--; if (v->ehperrorhandlers) v->ehp=NULL; // If the stack is empty rest to NULL } value retvalue; if (a>0) { b=DECODE_B(bc); retvalue = reg[b]; } else { retvalue = MORPHO_NIL; /* No return value; returns nil */ } if (v->fp>v->frame) { bool shouldreturn = (v->fp->ret); // value *or = reg + v->fp->function->nargs; v->fp--; v->konst=v->fp->function->konst.data; /* Restore the constant table */ reg=v->fp->roffset+v->stack.data; /* Restore registers */ v->stack.count=v->fp->stackcount; /* Restore the stack size */ reg[v->fp->returnreg]=retvalue; /* Copy the return value */ // Clear registers // for (value *r = reg + v->fp->function->nregs-1; r > or; r--) *r = MORPHO_INTEGER(0); pc=v->fp->pc; /* Jump back */ if (shouldreturn) return true; DISPATCH(); } else { ERROR(VM_GLBLRTRN); } CASE_CODE(CLOSURE): { a=DECODE_A(bc); b=DECODE_B(bc); objectclosure *closure = object_newclosure(v->fp->function, MORPHO_GETFUNCTION(reg[a]), (indx) b); /* Now capture or copy upvalues from this frame */ if (closure) { for (unsigned int i=0; inupvalues; i++) { upvalue *up = &v->fp->function->prototype.data[b].data[i]; if (up->islocal) { closure->upvalues[i]=vm_captureupvalue(v, ®[up->reg]); } else { if (v->fp->closure) closure->upvalues[i]=v->fp->closure->upvalues[up->reg]; } } reg[a] = MORPHO_OBJECT(closure); vm_bindobject(v, MORPHO_OBJECT(closure)); } } DISPATCH(); CASE_CODE(LUP): a=DECODE_A(bc); b=DECODE_B(bc); if (v->fp->closure && v->fp->closure->upvalues[b]) { reg[a]=*v->fp->closure->upvalues[b]->location; } else { UNREACHABLE("Closure unavailable"); } DISPATCH(); CASE_CODE(SUP): a=DECODE_A(bc); b=DECODE_B(bc); right = reg[b]; if (v->fp->closure && v->fp->closure->upvalues[a]) { *v->fp->closure->upvalues[a]->location=right; } else { UNREACHABLE("Closure unavailable"); } DISPATCH(); CASE_CODE(LGL): a=DECODE_A(bc); b=DECODE_Bx(bc); reg[a]=v->globals.data[b]; DISPATCH(); CASE_CODE(SGL): a=DECODE_A(bc); b=DECODE_Bx(bc); v->globals.data[b]=reg[a]; DISPATCH(); CASE_CODE(CLOSEUP): a=DECODE_A(bc); vm_closeupvalues(v, ®[a]); DISPATCH(); CASE_CODE(LPR): /* Load property */ a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[b]; right = reg[c]; if (MORPHO_ISINSTANCE(left)) { objectinstance *instance = MORPHO_GETINSTANCE(left); /* Is there a property with this id? */ if (dictionary_getintern(&instance->fields, right, ®[a])) { } else if (dictionary_getintern(&instance->klass->methods, right, ®[a])) { /* ... or a method? */ objectinvocation *bound=object_newinvocation(left, reg[a]); if (bound) { /* Bind into the VM */ reg[a]=MORPHO_OBJECT(bound); vm_bindobject(v, reg[a]); } } else if (dictionary_get(&instance->fields, right, ®[a])) { } else { /* Otherwise, raise an error */ char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_OBJECTLACKSPROPERTY, p); } } else if (MORPHO_ISCLASS(left)) { /* If it's a class, we lookup the method and create the invocation */ objectclass *klass = MORPHO_GETCLASS(left); if (klass && dictionary_get(&klass->methods, right, ®[a])) { objectinvocation *bound=object_newinvocation(left, reg[a]); if (bound) { /* Bind into the VM */ reg[a]=MORPHO_OBJECT(bound); vm_bindobject(v, reg[a]); } } else { /* Otherwise, raise an error */ char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_CLASSLACKSPROPERTY, p); } } else { /* Check for veneer class */ objectclass *klass; if (MORPHO_ISOBJECT(left)) klass = object_getveneerclass(MORPHO_GETOBJECTTYPE(left)); else klass = value_getveneerclass(left); if (klass) { value ifunc; if (dictionary_get(&klass->methods, right, &ifunc)) { objectinvocation *bound=object_newinvocation(left, ifunc); if (bound) { /* Bind into the VM */ reg[a]=MORPHO_OBJECT(bound); vm_bindobject(v, reg[a]); } } else { char *p = (MORPHO_ISSTRING(right) ? MORPHO_GETCSTRING(right) : ""); VERROR(VM_CLASSLACKSPROPERTY, p); } } else { ERROR(VM_NOTANOBJECT); } } DISPATCH(); CASE_CODE(SPR): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[a]; right = reg[c]; if (MORPHO_ISINSTANCE(left)) { objectinstance *instance = MORPHO_GETINSTANCE(left); left = reg[b]; dictionary_insertintern(&instance->fields, left, right); } else { ERROR(VM_NOTANOBJECT); } DISPATCH(); CASE_CODE(LIX): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[a]; if (MORPHO_ISARRAY(left)) { unsigned int ndim = c-b+1; unsigned int indx[ndim]; if (array_valuelisttoindices(ndim, ®[b], indx)){ objectarrayerror err=array_getelement(MORPHO_GETARRAY(left), ndim, indx, ®[b]); if (err!=ARRAY_OK) ERROR( array_error(err) ); } else { value newval = MORPHO_NIL; objectarrayerror err = getslice(&left,&array_slicedim,&array_sliceconstructor,\ &array_slicecopy,ndim,®[b],&newval); if (err!=ARRAY_OK) ERROR(array_error(err)); if (!MORPHO_ISNIL(newval)) { reg[b] = newval; vm_bindobject(v, reg[b]); } else ERROR(VM_NONNUMINDX); } } else { if (!vm_invoke(v, left, indexselector, c-b+1, ®[b], ®[b])) { ERROR(VM_NOTINDEXABLE); } ERRORCHK(); } DISPATCH(); CASE_CODE(LIXL): { a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); /* Variant 1: */ value args[2] = { reg[b], reg[c] }; reg[a] = List_getindex(v, 1, args); ERRORCHK(); /* Variant 2: Potentially even faster objectlist *list = MORPHO_GETLIST(reg[b]); int i=MORPHO_GETINTEGERVALUE(reg[c]); if (i>list->val.count) ERROR(VM_OUTOFBOUNDS); reg[a]=list->val.data[i];*/ DISPATCH(); } CASE_CODE(SIX): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); left = reg[a]; if (MORPHO_ISARRAY(left)) { unsigned int ndim = c-b; unsigned int indx[ndim]; if (!array_valuelisttoindices(ndim, ®[b], indx)) ERROR(VM_NONNUMINDX); objectarrayerror err=array_setelement(MORPHO_GETARRAY(left), ndim, indx, reg[c]); if (err!=ARRAY_OK) ERROR( array_error(err) ); } else { if (!vm_invoke(v, left, setindexselector, c-b+1, ®[b], &right)) { ERROR(VM_NOTINDEXABLE); } ERRORCHK(); } DISPATCH(); CASE_CODE(PUSHERR): b=DECODE_Bx(bc); if (v->ehp && v->ehp>=v->errorhandlers+MORPHO_ERRORHANDLERSTACKSIZE-1) { ERROR(VM_ERRSTCKOVFLW); } if (!v->ehp) v->ehp=v->errorhandlers; else v->ehp++; // Add new error handler to the error stack v->ehp->fp=v->fp; // Store the current frame pointer v->ehp->dict=v->konst[b]; // Store the error handler dictionary from the constant table DISPATCH(); CASE_CODE(POPERR): b=DECODE_sBx(bc); // Optional branch pc+=b; // Advance program counter v->ehp--; // Pull error handler off error stack if (v->ehperrorhandlers) v->ehp=NULL; // If the stack is empty rest to NULL DISPATCH(); CASE_CODE(CAT): a=DECODE_A(bc); b=DECODE_B(bc); c=DECODE_C(bc); reg[a]=morpho_concatenate(v, c-b+1, reg+b); vm_bindobject(v, reg[a]); DISPATCH(); CASE_CODE(PRINT): a=DECODE_A(bc); left=reg[a]; if (!vm_invoke(v, left, printselector, 0, NULL, &right)) { morpho_printvalue(v, left); } morpho_printf(v, "\n"); DISPATCH(); CASE_CODE(BREAK): if (v->debug) { if (vm_shouldbreakatpc(v, pc) || op==OP_BREAK) { ENTERDEBUGGER(); ERRORCHK(); } #ifdef MORPHO_COMPUTED_GOTO // If using computed gotos, all instructions are routed through OP_BREAK // when the debugger is active. When this is happening we must perform a regular // dispatch after we've checked whether to enter the debugger bool debugdispatchactive = (dispatchtable[0]==&&code_BREAK); if (debugger_isactive(v->debug)) { // Check if singlestep or breakpoints are active if (!debugdispatchactive) DEBUG_ENABLE() } else if (debugdispatchactive) DEBUG_DISABLE() if (op==OP_BREAK) DISPATCH(); // Perform a regular dispatch if we stopped at OP_BREAK // If the debug dispatch table was active, must dispatch to execute the instruction if (debugdispatchactive) goto *debugdispatchtable[op]; #endif } DISPATCH(); CASE_CODE(END): #ifdef MORPHO_OPCODE_USAGE { char *opname[] = { #define OPCODE(name) #name, #include "opcodes.h" "" }; #undef OPCODE for (unsigned int i=0; ierr.id); value errid = MORPHO_OBJECT(&erridstring); /* Find the most recent callframe that requires us to return */ callframe *retfp=NULL; for (retfp=v->fp; retfp>v->frame && !retfp->ret; retfp--); /* Search down the error stack for an error handler that can handle the error */ for (errorhandler *eh=v->ehp; eh && eh>=v->errorhandlers; eh--) { /* Abort if we pass an intermediate frame that requires us to return */ if (eh->fpehp=eh; // Pop off all earlier error handlers break; } if (MORPHO_ISDICTIONARY(eh->dict)) { value branchto = MORPHO_NIL; objectdictionary *dict = MORPHO_GETDICTIONARY(eh->dict); if (dictionary_get(&dict->dict, errid, &branchto)) { error_clear(&v->err); // Jump to the error handler v->fp=eh->fp; v->konst=v->fp->function->konst.data; pc=v->instructions+MORPHO_GETINTEGERVALUE(branchto); reg=v->stack.data+v->fp->roffset; if (v->openupvalues) { /* Close any upvalues */ vm_closeupvalues(v, reg+v->fp->function->nregs); } v->ehp=eh-1; // Unwind the error handler stack if (v->ehperrorhandlers) v->ehp=NULL; DISPATCH() } } } /* The error was not caught; unwind the stack to the point where we have to return */ if (!v->errfp) { v->errfp=v->fp; // Record frame pointer for stacktrace v->errfp->pc=pc; } v->fp=retfp-1; } #undef INTERPRET_LOOP #undef CASE_CODE #undef DISPATCH //v->fp->pc=pc; return false; } /* ********************************************************************** * VM public interfaces * ********************************************************************** */ /** Creates a new virtual machine */ vm *morpho_newvm(void) { vm *new = MORPHO_MALLOC(sizeof(vm)); if (new) vm_init(new); return new; } /** Frees a virtual machine */ void morpho_freevm(vm *v) { vm_clear(v); MORPHO_FREE(v); } /** Configure input callback function */ void morpho_setinputfn(vm *v, morphoinputfn inputfn, void *ref) { v->inputfn=inputfn; v->inputref=ref; } /** Configure print callback function */ void morpho_setprintfn(vm *v, morphoprintfn printfn, void *ref) { v->printfn=printfn; v->printref=ref; } /** Configure warning callback function */ void morpho_setwarningfn(vm *v, morphowarningfn warningfn, void *ref) { v->warningfn=warningfn; v->warningref=ref; } /** Configure debugger callback function */ void morpho_setdebuggerfn(vm *v, morphodebuggerfn debuggerfn, void *ref) { v->debuggerfn=debuggerfn; v->debuggerref=ref; } /** Returns a VM's error block */ error *morpho_geterror(vm *v) { return &v->err; } /** @brief Raises an error described by an error structure * @param v the virtual machine * @param err error to raise */ void morpho_error(vm *v, error *err) { if (err->cat!=ERROR_WARNING) { // Errors of category ERROR_WARNING are always raised as warnings v->err = *err; } else morpho_warning(v, err); } /** @brief Raises an warning described by an error structure */ void morpho_warning(vm *v, error *err) { if (v->warningfn) { (v->warningfn) (v, v->warningref, err); } else { fprintf(stderr, "Warning [%s]: %s\n", err->id, err->msg); } } /** @brief Raise a runtime error given an id * @param v the virtual machine * @param id error id * @param ... additional data for sprintf. */ void morpho_runtimeerror(vm *v, errorid id, ...) { va_list args; error err; error_init(&err); va_start(args, id); morpho_writeerrorwithidvalist(&err, id, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE, args); va_end(args); morpho_error(v, &err); error_clear(&err); } /** @brief Raise a runtime warning given an id */ void morpho_runtimewarning(vm *v, errorid id, ...) { va_list args; error err; error_init(&err); va_start(args, id); morpho_writeerrorwithidvalist(&err, id, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE, args); va_end(args); morpho_warning(v, &err); error_clear(&err); } /** @brief Binds a set of objects to a Virtual Machine; public interface. * @details Any object created during execution should be bound to a VM; this object is then managed by the garbage collector. * @param v the virtual machine * @param obj objects to bind */ void morpho_bindobjects(vm *v, int nobj, value *obj) { /* Now bind the new objects in. */ for (unsigned int i=0; istatusstatus=OBJECT_ISUNMARKED; ob->next=v->objects; v->objects=ob; size_t size=object_size(ob); v->bound+=size; #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary_insert(&sizecheck, obj[i], MORPHO_INTEGER(size)); #endif } } /* Check if size triggers garbage collection */ #ifndef MORPHO_DEBUG_STRESSGARBAGECOLLECTOR if (v->bound>v->nextgc) #endif { int handle=morpho_retainobjects(v, nobj, obj); vm_collectgarbage(v); morpho_releaseobjects(v, handle); } } /** @brief Convenience function to wrap a single object into a value and bind to the VM * @param v VM to use * @param out Object to wrap * @returns object wrapped in a value, or MORPHO_NIL if obj is NULL */ value morpho_wrapandbind(vm *v, object *obj) { value out = MORPHO_NIL; if (obj) { out=MORPHO_OBJECT(obj); morpho_bindobjects(v, 1, &out); } return out; } /** @brief Temporarily retain objects across multiple reentrant calls to the VM. * @param v the virtual machine * @param nobj number of objects to retain * @param obj objects to retain * @returns an integer handle to pass to releaseobjects */ int morpho_retainobjects(vm *v, int nobj, value *obj) { int gcount=v->retain.count; varray_valueadd(&v->retain, obj, nobj); return gcount; } /** @brief Release objects temporarily retained by the VM. * @param v the virtual machine * @param handle a handle returned by morpho_retainobjects. */ void morpho_releaseobjects(vm *v, int handle) { if (handle>=0) v->retain.count=handle; } /** @brief Inform the VM that the size of an object has changed * @param v the virtual machine * @param obj the object to resize * @param oldsize old size * @param newsize new size */ void morpho_resizeobject(vm *v, object *obj, size_t oldsize, size_t newsize) { #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary_insert(&sizecheck, MORPHO_OBJECT(obj), MORPHO_INTEGER(newsize)); #endif if (!MORPHO_ISGARBAGECOLLECTED(MORPHO_OBJECT(obj))) return; v->bound-=oldsize; v->bound+=newsize; } /** @brief Checks if an object is managed by the garbage collector * @param obj the object to check * @returns true if it is managed, false otherwise */ bool morpho_ismanagedobject(object *obj) { return (obj->status==OBJECT_ISUNMARKED || obj->status==OBJECT_ISMARKED); } /** Runs a program * @param[in] v - the virtual machine to use * @param[in] p - program to run * @returns true on success, false if an error occurred */ bool morpho_run(vm *v, program *p) { if (!vm_start(v, p)) return false; /* Initialize global variables */ int oldsize = v->globals.count; int nglobals = program_countglobals(p); varray_valueresize(&v->globals, nglobals); v->globals.count=nglobals; for (int i=oldsize; iglobals.data[i]=MORPHO_NIL; /* Zero out globals */ /* and initially set the register pointer to the bottom of the stack */ value *reg = v->stack.data; /* Expand and clear the stack if necessary */ if (v->fp->function->nregs>v->stack.count) { unsigned int oldcount=v->stack.count; vm_expandstack(v, ®, v->fp->function->nregs-v->stack.count); for (unsigned int i=oldcount; istack.count; i++) v->stack.data[i]=MORPHO_NIL; } instructionindx start = program_getentry(p); int success = morpho_interpret(v, reg, start); if (!success && morpho_matcherror(morpho_geterror(v), VM_EXIT)) { success=true; error_clear(morpho_geterror(v)); } return success; } /* Call a morpho function from C code */ bool morpho_call(vm *v, value f, int nargs, value *args, value *ret) { bool success=false; value fn=f; value r0=f; if (MORPHO_ISINVOCATION(fn)) { /* An method invocation */ objectinvocation *inv = MORPHO_GETINVOCATION(f); fn=inv->method; r0=inv->receiver; } if (MORPHO_ISMETAFUNCTION(fn) && !metafunction_resolve(MORPHO_GETMETAFUNCTION(fn), nargs, args, &v->err, &fn)) { if (!morpho_checkerror(&v->err)) morpho_runtimeerror(v, VM_MLTPLDSPTCHFLD); return false; } if (MORPHO_ISBUILTINFUNCTION(fn)) { objectbuiltinfunction *f = MORPHO_GETBUILTINFUNCTION(fn); /* Copy arguments across to comply with call standard */ value xargs[nargs+1]; xargs[0]=r0; for (unsigned int i=0; ifp->nopt=0; // TODO: Extend morpho call to support optional args. #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=f; #endif *ret=(f->function) (v, nargs, xargs); #ifdef MORPHO_PROFILER v->fp->inbuiltinfunction=NULL; #endif success=true; } else if (MORPHO_ISFUNCTION(fn) || MORPHO_ISCLOSURE(fn)) { value *reg=v->stack.data+v->fp->roffset; instruction *pc=v->fp->pc; /* Set up the function call, advancing the frame pointer and expanding the stack if necessary */ if (vm_call(v, fn, v->fp->function->nregs, nargs, 0, args, &pc, ®)) { /* Now ensure the function (or self) is in r0 */ reg[0]=r0; /* Set return to true in this callframe */ v->fp->ret=true; /* Keep track of the stack in case it is reallocated */ value *stackbase=v->stack.data; ptrdiff_t roffset=reg-v->stack.data; success=morpho_interpret(v, reg, pc-v->instructions); /* Restore reg if stack has expanded */ if (v->stack.data!=stackbase) reg=v->stack.data+roffset; if (success) *ret=reg[0]; /* Return value */ } } else if (MORPHO_ISCLASS(fn)) { /* A function call on a class instantiates it */ objectclass *klass = MORPHO_GETCLASS(fn); objectinstance *instance = object_newinstance(klass); if (instance) { value obj = MORPHO_OBJECT(instance); /* Call the initializer if the class provides one */ value ifunc; if (morpho_lookupmethod(obj, initselector, &ifunc)) { success=morpho_invoke(v, obj, ifunc, nargs, args, ret); } else if (nargs==0) { success=true; } else morpho_runtimeerror(v, VM_NOINITIALIZER, MORPHO_GETCSTRING(klass->name)); if (success) { vm_bindobjectwithoutcollect(v, obj); // 4/2/25 Changed to disable collection because we can't guarantee the external context of the caller (e.g. apply) *ret = obj; } else morpho_freeobject(obj); } else morpho_runtimeerror(v, VM_INSTANTIATEFAILED); } return success; } /** Find the class associated with a value */ objectclass *morpho_lookupclass(value v) { objectclass *klass=NULL; if (MORPHO_ISINSTANCE(v)) klass=MORPHO_GETINSTANCE(v)->klass; else if (MORPHO_ISCLASS(v)) klass=MORPHO_GETCLASS(v); else if (MORPHO_ISOBJECT(v)) klass=object_getveneerclass(MORPHO_GETOBJECTTYPE(v)); else klass=value_getveneerclass(v); return klass; } /** Finds a method */ bool morpho_lookupmethod(value obj, value label, value *method) { objectclass *klass = morpho_lookupclass(obj); if (klass) return dictionary_get(&klass->methods, label, method); return false; } /** Invoke a method on an object. @param[in] v - the virtual machine @param[in] obj - object to call on @param[in] method - method to invoke. NOTE lookup this first with morpho_lookupmethod if you just have a string @param[in] nargs - number of arguments @param[in] args - the arguments @param[out] ret - result of call @returns true on success, false otherwise */ bool morpho_invoke(vm *v, value obj, value method, int nargs, value *args, value *ret) { objectinvocation inv; object_init((object *) &inv, OBJECT_INVOCATION); inv.receiver=obj; inv.method=method; return morpho_call(v, MORPHO_OBJECT(&inv), nargs, args, ret); } /* ********************************************************************** * I/O * ********************************************************************** */ /** Prints a formatted string to the VM's output channel */ int morpho_printf(vm *v, char *format, ...) { int nchars=0; va_list args; if (v && v->printfn) { for (;;) { va_start(args, format); nchars = vsnprintf(v->buffer.data, v->buffer.capacity, format, args); va_end(args); if (nchars+1<=v->buffer.capacity) break; if (!varray_charresize(&v->buffer, nchars+1)) return 0; } v->buffer.count=nchars; (v->printfn) (v, v->printref, v->buffer.data); } else { // If no VM or no print function available, resort to regular printf va_start(args, format); nchars=vprintf(format, args); va_end(args); } return nchars; } /** Reads a line of text from the VM's input channel; returns the number of bytes read */ int morpho_readline(vm *v, varray_char *buffer) { int start=buffer->count; if (v && v->inputfn) { (v->inputfn) (v, v->inputref, MORPHO_INPUT_LINE, buffer); } else { // If no VM or no input function available, resort to regular fgetc do { int c = fgetc(stdin); if (c==EOF || c=='\n') break; varray_charwrite(buffer, (char) c); } while (true); } return buffer->count-start; } /* ********************************************************************** * Subkernels * ********************************************************************** */ DEFINE_VARRAY(vm, struct svm *) /** Obtain subkernels from the VM for use in a thread */ bool vm_subkernels(vm *v, int nkernels, vm **subkernels) { int nk=0; /* Check for unused subkernels */ for (int i=0; isubkernels.count; i++) { vm *kernel=v->subkernels.data[i]; if (!kernel->parent) { // Check whether subkernel is unused subkernels[nk]=kernel; kernel->parent=v; nk++; } } /* Create any additional kernels that need to be made */ for (int i=nk; isubkernels, &new, 1)) return false; vm_start(new, v->current); new->globals.count=v->globals.count; new->globals.data=v->globals.data; new->parent=v; subkernels[i]=new; } return true; } /** Release a subkernels from the VM for use in a thread */ void vm_releasesubkernel(vm *subkernel) { vm *v = subkernel->parent; if (!v) return; /** Transfer objects from subkernel to kernel */ if (subkernel->objects) { object *obj; for (obj=subkernel->objects; obj!=NULL; obj=obj->next) { if (obj->next==NULL) break; } /* Add the subkernel's objects to the parent */ obj->next=v->objects; v->objects=subkernel->objects; /* Include this in the bound list */ v->bound+=subkernel->bound; /* Remove from the subkernel */ subkernel->objects=NULL; subkernel->bound=0; } /** Check if the subkernel is in an error state */ if (!ERROR_SUCCEEDED(subkernel->err) && ERROR_SUCCEEDED(v->err)) { v->err=subkernel->err; } subkernel->parent=NULL; } /** Clean out attached objects from a subkernel */ void vm_cleansubkernel(vm *subkernel) { object *next=NULL; for (object *obj=subkernel->objects; obj!=NULL; obj=next) { next=obj->next; object_free(obj); } subkernel->objects=NULL; subkernel->bound=0; } /* ********************************************************************** * Thread local storage * ********************************************************************** */ int ntlvars=0; /** Adds a thread local variable, returning the handle */ int vm_addtlvar(void) { int out = ntlvars; ntlvars++; return out; } /** Initialize threadlocal variables for a vm */ bool vm_inittlvars(vm *v) { if (v->tlvars.capacitytlvars, ntlvars)) return false; v->tlvars.count=ntlvars; for (int i=0; itlvars.data[i]=MORPHO_NIL; } return true; } /** Sets the value of a thread local variable */ bool vm_settlvar(vm *v, int handle, value val) { bool success=false; if (handletlvars.data[handle]=val; success=true; } return success; } /** Gets the value of a thread local variable */ bool vm_gettlvar(vm *v, int handle, value *out) { bool success=false; if (handletlvars.data[handle]; success=true; } return success; } /* ********************************************************************** * Finalize functions * ********************************************************************** */ varray_value _finalizefns; void morpho_addfinalizefn(morpho_finalizefn finalizefn) { varray_valuewrite(&_finalizefns, MORPHO_OBJECT(finalizefn)); } /* ********************************************************************** * Initialization * ********************************************************************** */ /** Initializes morpho */ void morpho_initialize(void) { varray_valueinit(&_finalizefns); random_initialize(); error_initialize(); value_initialize(); // } Must be first object_initialize(); // } builtin_initialize(); // Must come before initialization of any classes or similar resources_initialize(); // Must come before compiler and extensions lex_initialize(); parse_initialize(); compile_initialize(); debugger_initialize(); extensions_initialize(); #ifdef MORPHO_DEBUG_GCSIZETRACKING dictionary_init(&sizecheck); #endif morpho_defineerror(ERROR_ALLOCATIONFAILED, ERROR_EXIT, ERROR_ALLOCATIONFAILED_MSG); morpho_defineerror(ERROR_INTERNALERROR, ERROR_EXIT, ERROR_INTERNALERROR_MSG); morpho_defineerror(ERROR_ERROR, ERROR_HALT, ERROR_ERROR_MSG); morpho_defineerror(VM_STCKOVFLW, ERROR_HALT, VM_STCKOVFLW_MSG); morpho_defineerror(VM_ERRSTCKOVFLW, ERROR_HALT, VM_ERRSTCKOVFLW_MSG); morpho_defineerror(VM_INVLDOP, ERROR_HALT, VM_INVLDOP_MSG); morpho_defineerror(VM_CNCTFLD, ERROR_HALT, VM_CNCTFLD_MSG); morpho_defineerror(VM_UNCALLABLE, ERROR_HALT, VM_UNCALLABLE_MSG); morpho_defineerror(VM_GLBLRTRN, ERROR_HALT, VM_GLBLRTRN_MSG); morpho_defineerror(VM_INSTANTIATEFAILED, ERROR_HALT, VM_INSTANTIATEFAILED_MSG); morpho_defineerror(VM_NOTANOBJECT, ERROR_HALT, VM_NOTANOBJECT_MSG); morpho_defineerror(VM_OBJECTLACKSPROPERTY, ERROR_HALT, VM_OBJECTLACKSPROPERTY_MSG); morpho_defineerror(VM_NOINITIALIZER, ERROR_HALT, VM_NOINITIALIZER_MSG); morpho_defineerror(VM_NOTANINSTANCE, ERROR_HALT, VM_NOTANINSTANCE_MSG); morpho_defineerror(VM_CLASSLACKSPROPERTY, ERROR_HALT, VM_CLASSLACKSPROPERTY_MSG); morpho_defineerror(VM_INVALIDARGS, ERROR_HALT, VM_INVALIDARGS_MSG); morpho_defineerror(VM_NOOPTARG, ERROR_HALT, VM_NOOPTARG_MSG); morpho_defineerror(VM_UNKNWNOPTARG, ERROR_HALT, VM_UNKNWNOPTARG_MSG); morpho_defineerror(VM_INVALIDARGSDETAIL, ERROR_HALT, VM_INVALIDARGSDETAIL_MSG); morpho_defineerror(VM_NOTINDEXABLE, ERROR_HALT, VM_NOTINDEXABLE_MSG); morpho_defineerror(VM_OUTOFBOUNDS, ERROR_HALT, VM_OUTOFBOUNDS_MSG); morpho_defineerror(VM_NONNUMINDX, ERROR_HALT, VM_NONNUMINDX_MSG); morpho_defineerror(VM_ARRAYWRONGDIM, ERROR_HALT, VM_ARRAYWRONGDIM_MSG); morpho_defineerror(VM_DVZR, ERROR_HALT, VM_DVZR_MSG); morpho_defineerror(VM_GETINDEXARGS, ERROR_HALT, VM_GETINDEXARGS_MSG); morpho_defineerror(VM_MLTPLDSPTCHFLD, ERROR_HALT, VM_MLTPLDSPTCHFLD_MSG); morpho_defineerror(VM_DBGQUIT, ERROR_HALT, VM_DBGQUIT_MSG); /* Selector for initializers */ initselector=builtin_internsymbolascstring(MORPHO_INITIALIZER_METHOD); indexselector=builtin_internsymbolascstring(MORPHO_GETINDEX_METHOD); setindexselector=builtin_internsymbolascstring(MORPHO_SETINDEX_METHOD); addselector=builtin_internsymbolascstring(MORPHO_ADD_METHOD); addrselector=builtin_internsymbolascstring(MORPHO_ADDR_METHOD); subselector=builtin_internsymbolascstring(MORPHO_SUB_METHOD); subrselector=builtin_internsymbolascstring(MORPHO_SUBR_METHOD); mulselector=builtin_internsymbolascstring(MORPHO_MUL_METHOD); mulrselector=builtin_internsymbolascstring(MORPHO_MULR_METHOD); divselector=builtin_internsymbolascstring(MORPHO_DIV_METHOD); divrselector=builtin_internsymbolascstring(MORPHO_DIVR_METHOD); powselector=builtin_internsymbolascstring(MORPHO_POW_METHOD); powrselector=builtin_internsymbolascstring(MORPHO_POWR_METHOD); enumerateselector=builtin_internsymbolascstring(MORPHO_ENUMERATE_METHOD); countselector=builtin_internsymbolascstring(MORPHO_COUNT_METHOD); cloneselector=builtin_internsymbolascstring(MORPHO_CLONE_METHOD); printselector=builtin_internsymbolascstring(MORPHO_PRINT_METHOD); } /** Finalizes morpho, calling all finalize functions */ void morpho_finalize(void) { for (int i=_finalizefns.count-1; i>=0; i--) { morpho_finalizefn fn=(morpho_finalizefn) MORPHO_GETOBJECT(_finalizefns.data[i]); fn(); } varray_valueclear(&_finalizefns); } ================================================ FILE: src/core/vm.h ================================================ /** @file vm.h * @author T J Atherton * * @brief The Morpho virtual machine */ #ifndef vm_h #define vm_h #define MORPHO_CORE #include "core.h" /* ********************************************************************** * Interface * ********************************************************************** */ void morpho_initialize(void); void morpho_finalize(void); instructionindx vm_previnstruction(vm *v); instructionindx vm_currentinstruction(vm *v); int vm_getoptionalargs(vm *v); debugger *vm_getdebugger(vm *v); #endif /* vm_h */ ================================================ FILE: src/datastructures/CMakeLists.txt ================================================ target_sources(morpho PRIVATE dictionary.c dictionary.h debugannotation.c debugannotation.h error.c error.h object.c object.h program.c program.h signature.c signature.h syntaxtree.c syntaxtree.h value.c value.h varray.c varray.h version.c version.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES dictionary.h debugannotation.h error.h object.h program.h signature.h syntaxtree.h value.h varray.h version.h ) ================================================ FILE: src/datastructures/debugannotation.c ================================================ /** @file debugannotation.c * @author T J Atherton * * @brief Debugging annotations for programs */ #include "debugannotation.h" /* ********************************************************************** * Debugging annotations * ********************************************************************** */ DEFINE_VARRAY(debugannotation, debugannotation); /** Retrieve the last annotation */ debugannotation *debugannotation_last(varray_debugannotation *list) { if (list->count>0) return &list->data[list->count-1]; return NULL; } /** Adds an annotation to a list */ void debugannotation_add(varray_debugannotation *list, debugannotation *annotation) { varray_debugannotationadd(list, annotation, 1); } /** Removes the last annotation */ void debugannotation_stripend(varray_debugannotation *list) { if (list->count>0) list->data[list->count-1].content.element.ninstr--; } /** Sets the current function */ void debugannotation_setfunction(varray_debugannotation *list, objectfunction *func) { debugannotation ann = { .type = DEBUG_FUNCTION, .content.function.function = func}; debugannotation_add(list, &ann); } /** Sets the current class */ void debugannotation_setclass(varray_debugannotation *list, objectclass *klass) { debugannotation ann = { .type = DEBUG_CLASS, .content.klass.klass = klass}; debugannotation_add(list, &ann); } /** Sets the current module */ void debugannotation_setmodule(varray_debugannotation *list, value module) { debugannotation ann = { .type = DEBUG_MODULE, .content.module.module = module }; debugannotation_add(list, &ann); } /** Associates a register with a symbol */ void debugannotation_setreg(varray_debugannotation *list, indx reg, value symbol) { if (!MORPHO_ISSTRING(symbol)) return; value sym = object_clonestring(symbol); debugannotation ann = { .type = DEBUG_REGISTER, .content.reg.reg = reg, .content.reg.symbol = sym }; debugannotation_add(list, &ann); } /** Associates a global with a symbol */ void debugannotation_setglobal(varray_debugannotation *list, indx gindx, value symbol) { if (!MORPHO_ISSTRING(symbol)) return; value sym = object_clonestring(symbol); debugannotation ann = { .type = DEBUG_GLOBAL, .content.global.gindx = gindx, .content.global.symbol = sym }; debugannotation_add(list, &ann); } /** Pushes an error handler onto the stack */ void debugannotation_pusherr(varray_debugannotation *list, objectdictionary *dict) { debugannotation ann = { .type = DEBUG_PUSHERR, .content.errorhandler.handler = dict}; debugannotation_add(list, &ann); } /** Pops an error handler from the stack */ void debugannotation_poperr(varray_debugannotation *list) { debugannotation ann = { .type = DEBUG_POPERR }; debugannotation_add(list, &ann); } /** Uses information from a syntaxtreenode to associate a sequence of instructions with source */ void debugannotation_addnode(varray_debugannotation *list, syntaxtreenode *node) { if (!node) return; debugannotation *last = debugannotation_last(list); if (last && last->type==DEBUG_ELEMENT && node->line==last->content.element.line && node->posn==last->content.element.posn) { last->content.element.ninstr++; } else { debugannotation ann = { .type = DEBUG_ELEMENT, .content.element.line = node->line, .content.element.posn = node->posn, .content.element.ninstr=1 }; debugannotation_add(list, &ann); } } /** Clear debugging list, freeing attached info */ void debugannotation_clear(varray_debugannotation *list) { for (unsigned int j=0; jcount; j++) { value sym=MORPHO_NIL; switch (list->data[j].type) { case DEBUG_REGISTER: sym = list->data[j].content.reg.symbol; break; case DEBUG_GLOBAL: sym=list->data[j].content.global.symbol; break; default: break; } if (MORPHO_ISOBJECT(sym)) object_free(MORPHO_GETOBJECT(sym)); } varray_debugannotationclear(list); } /* ********************************************************************** * Display annotations * ********************************************************************** */ /** Prints all the annotations for a program */ void debugannotation_showannotations(varray_debugannotation *list) { indx ix = 0; printf("Showing %u annotations.\n", list->count); for (unsigned int j=0; jcount; j++) { printf("%u: ", j); debugannotation *ann = &list->data[j]; switch (ann->type) { case DEBUG_CLASS: printf("Class: "); if (!ann->content.klass.klass) { printf("(none)"); } else { morpho_printvalue(NULL, MORPHO_OBJECT(ann->content.klass.klass)); } break; case DEBUG_ELEMENT: printf("Element: [%ti] instructions: %i line: %i posn: %i", ix, ann->content.element.ninstr, ann->content.element.line, ann->content.element.posn); ix+=ann->content.element.ninstr; break; case DEBUG_FUNCTION: printf("Function: "); morpho_printvalue(NULL, MORPHO_OBJECT(ann->content.function.function)); break; case DEBUG_MODULE: printf("Module: "); morpho_printvalue(NULL, ann->content.module.module); break; case DEBUG_PUSHERR: printf("Pusherr: "); morpho_printvalue(NULL, MORPHO_OBJECT(ann->content.errorhandler.handler)); break; case DEBUG_POPERR: printf("Poperr: "); break; case DEBUG_REGISTER: printf("Register: %ti ", ann->content.reg.reg); morpho_printvalue(NULL, ann->content.reg.symbol); break; case DEBUG_GLOBAL: printf("Global: %ti ", ann->content.global.gindx); morpho_printvalue(NULL, ann->content.reg.symbol); break; } printf("\n"); } } ================================================ FILE: src/datastructures/debugannotation.h ================================================ /** @file debugannotation.h * @author T J Atherton * * @brief Debugging annotations for programs */ #ifndef debugannotation_h #define debugannotation_h #include "syntaxtree.h" #include "object.h" #include "program.h" /* ------------------------------------------------------- * Debug annotations contain debugging information * ------------------------------------------------------- */ /** Annotations for the compiled code to link back to the source */ typedef struct { enum { DEBUG_FUNCTION, // Set the current function DEBUG_CLASS, // Set the current class DEBUG_MODULE, // Set the current module DEBUG_REGISTER, // Associates a symbol with a register DEBUG_GLOBAL, // Associates a symbol with a global DEBUG_ELEMENT, // Associates a sequence of instructions with a code element DEBUG_PUSHERR, // Push an error handler DEBUG_POPERR // Pop an error handler } type; union { struct { objectdictionary *handler; } errorhandler; struct { objectfunction *function; } function; struct { objectclass *klass; } klass; struct { value module; } module; struct { indx reg; value symbol; } reg; struct { indx gindx; value symbol; } global; struct { int ninstr; int line; int posn; } element; } content; } debugannotation; DECLARE_VARRAY(debugannotation, debugannotation) /* ------------------------------------------------------- * Work with debug annotations * ------------------------------------------------------- */ debugannotation *debugannotation_last(varray_debugannotation *list); void debugannotation_add(varray_debugannotation *list, debugannotation *annotation); void debugannotation_stripend(varray_debugannotation *list); void debugannotation_setfunction(varray_debugannotation *list, objectfunction *func); void debugannotation_setclass(varray_debugannotation *list, objectclass *klass); void debugannotation_setmodule(varray_debugannotation *list, value module); void debugannotation_setreg(varray_debugannotation *list, indx reg, value symbol); void debugannotation_setglobal(varray_debugannotation *list, indx gindx, value symbol); void debugannotation_pusherr(varray_debugannotation *list, objectdictionary *dict); void debugannotation_poperr(varray_debugannotation *list); void debugannotation_addnode(varray_debugannotation *list, syntaxtreenode *node); void debugannotation_clear(varray_debugannotation *list); void debugannotation_showannotations(varray_debugannotation *list); #endif /* debugannotation_h */ ================================================ FILE: src/datastructures/dictionary.c ================================================ /** @file dictionary.h * @author T J Atherton * * @brief Dictionary (hashtable) data structure */ #include #include #include "dictionary.h" #include "common.h" #include "object.h" /* ********************************************************************** * Macros that control the behavior of the dictionary. * ********************************************************************** */ /** The initial size of non-empty dictionary */ #define DICTIONARY_DEFAULTSIZE 16 /** An empty value */ #define DICTIONARY_EMPTYVALUE MORPHO_NIL /** Value used to indicate a tombstone */ #define DICTIONARY_TOMBSTONEVALUE MORPHO_TRUE /** Literal for an empty entry */ #define DICTIONARY_EMPTYENTRY ((dictionaryentry) { DICTIONARY_EMPTYVALUE, DICTIONARY_EMPTYVALUE}) /** Literal for a tombstone entry */ #define DICTIONARY_TOMBSTONEENTRY ((dictionaryentry) { DICTIONARY_EMPTYVALUE, DICTIONARY_TOMBSTONEVALUE}) /* * These macros can be changed to tune the algorithm */ /** Define if we need to enforce power of two size in our implementation */ #define DICTIONARY_ENFORCEPOWEROFTWO /** Test if we should resize - the below triggers a resize at 3/4 capacity */ #define DICTIONARY_SIZEINCREASETHRESHOLD(x) (((x)>>1) + ((x)>>2)) /** Test if we should resize - the below triggers a resize at 1/4 capacity */ #define DICTIONARY_SIZEDECREASETHRESHOLD(x) ((x)>>2) /** Generate a new larger size given the current size - currently multiplies by 2 */ #define DICTIONARY_INCREASESIZE(x) (x<<1) /** Generate a new smaller size given the current size - currently divides by 2 */ #define DICTIONARY_DECREASESIZE(x) (x>>1) /** If defined, use Jenkins integer hash function */ //#define DICTIONARY_INTEGERHASH_JENKINS /** If defined, use Fibonacci integer hash function */ #define DICTIONARY_INTEGERHASH_FIBONACCI /** Reduce functions */ /** Integer modulo */ //#define DICTIONARY_REDUCE(x, size) (x % size) /** Faster version for power of two sizes */ #define DICTIONARY_REDUCE(x, size) (x & (size-1)) /** Faster version for arbitrary sizes - https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ */ /*static inline uint32_t dictionary_reduce64(uint32_t x, uint32_t N) { return ((uint64_t) x * (uint64_t) N) >> 32 ; }*/ //#define DICTIONARY_REDUCE(x, size) (dictionary_reduce64(x,size)) /* ********************************************************************** * Hash functions * ********************************************************************** */ /** Integer hash function from 32 ubit int to 32 bit uint due to Robert Jenkins */ #ifdef DICTIONARY_INTEGERHASH_JENKINS hash dictionary_hashint( uint32_t a) { a = (a+0x7ed55d16) + (a<<12); a = (a^0xc761c23c) ^ (a>>19); a = (a+0x165667b1) + (a<<5); a = (a+0xd3a2646c) ^ (a<<9); a = (a+0xfd7046c5) + (a<<3); a = (a^0xb55a4f09) ^ (a>>16); return a; } #endif #ifdef DICTIONARY_INTEGERHASH_FIBONACCI hash dictionary_hashint(uint32_t hash) { return (hash * 2654435769u); } #endif hash dictionary_hashpointer(void *hash) { uintptr_t ptr = (uintptr_t) hash; #if UINTPTR_MAX == UINT64_MAX return (ptr * 11400714819323198485llu) >> 32; #elif UINTPTR_MAX == UINT32_MAX return (ptr * 2654435769u); #else UNREACHABLE("dictionary pointer hash function undefined. [Check dictionary_hashpointer]"); #endif } /** String hashing function FNV-1a combined with Fibonacci hash */ hash dictionary_hashcstring(const char* key, size_t length) { uint32_t hash = 2166136261u; for (unsigned int i=0; i < length; i++) { hash ^= key[i]; hash *= 16777619u; // FNV prime number for 32 bits } return dictionary_hashint(hash); } /** Hash multiple values with FNV-1a */ hash dictionary_hashvaluelist(size_t length, value *key) { uint32_t hash = 2166136261u; for (unsigned int i=0; i < length; i++) { hash ^= dictionary_hashvalue(key[i]); hash *= 16777619u; } return dictionary_hashint(hash); } /* ********************************************************************** * Dictionary implementation * ********************************************************************** */ /** @brief Hashes a value * @param key the key to hash * @param intern set to true to use a precomputed hash (i.e. if it has been interned). * @returns the corresponding hash */ hash dictionary_hash(value key, bool intern) { if (MORPHO_ISINTEGER(key)) { return dictionary_hashint((uint32_t) MORPHO_GETINTEGERVALUE(key)); } else if (MORPHO_ISOBJECT(key)){ if (intern) { return MORPHO_GETOBJECTHASH(key); } else return object_hash(MORPHO_GETOBJECT(key)); } return 0; } /** Veneer onto interned hash */ hash dictionary_hashvalue(value key) { return dictionary_hash(key, false); } /** @brief Initializes a dictionary * @param dict the dictionary to initialize */ void dictionary_init(dictionary *dict) { dict->capacity=0; dict->count=0; dict->contents=NULL; } /** @brief Clears a dictionary structure, freeing attached memory * @param dict the dictionary to clear * @warning This doens't free keys or values in the dictionary. */ void dictionary_clear(dictionary *dict) { if (dict->contents) MORPHO_FREE(dict->contents); dictionary_init(dict); } /** @brief Frees a dictionary's contents * @param dict the dictionary to clear * @param freekeys whether to free the keys * @param freevals whether to free the vals */ void dictionary_freecontents(dictionary *dict, bool freekeys, bool freevals) { if (dict->contents) { for (unsigned int i=0; icapacity; i++) { dictionaryentry *e = &dict->contents[i]; if (!MORPHO_ISNIL(e->key)) { if (freekeys) morpho_freeobject(e->key); if (freevals) morpho_freeobject(e->val); } } } } /** @brief Resizes a dictionary. * @param dict the dictionary to resize * @param size a new size for the dictionary * @returns true on success */ bool dictionary_resize(dictionary *dict, unsigned int size) { dictionaryentry *new=NULL, *old = dict->contents; unsigned int oldsize = dict->capacity; #ifdef DICTIONARY_ENFORCEPOWEROFTWO unsigned int newsize = morpho_powerof2ceiling(size); #else unsigned int newsize = size; #endif /* Don't resize below the minimum */ if (dict->contents && newsizecapacity=newsize; dict->contents=new; dict->count=0; /* Restart from no entries */ if (old) { /* Copy the contents over */ for (unsigned int i=0; ikey)) continue; dictionary_insert(dict, e->key, e->val); } MORPHO_FREE(old); } return (bool) dict->contents; } /** @brief Searches for an entry in a dictionary * @param[in] dict the dictionary to search * @param[in] key the key to search for * @param[in] intern whether to use a strict equality search for objects or a fast search * @param[out] entry the dictionary entry corresponding to the key or a blank entry. * @returns true if the entry was found, false otherwise */ static bool dictionary_find(dictionary *dict, value key, bool intern, dictionaryentry **entry) { /* If there's nothing in the hashtable, return immediately */ if (!dict->contents) return false; /* Find the starting point by hashing the key */ unsigned int start_index = DICTIONARY_REDUCE(dictionary_hash(key, intern), dict->capacity); unsigned int indx = start_index; /* Store a pointer to any tombstones we encounter along the way. */ dictionaryentry *tombstone = NULL; dictionaryentry *e = NULL; /* Loop over entries */ do { e = &dict->contents[indx]; if (intern) { /* If intern is set, we use MORPHO_SAME (tests for equality depending on whether objects are the same not just equivalent. */ if (MORPHO_ISSAME(e->key, key)) { /* We found the key! */ *entry = e; return true; } } else if (MORPHO_ISEQUAL(e->key, key)) { /* If intern is false, we can use the slower equivalence test */ /* We found the key! */ *entry = e; return true; } /* If the key for this entry is blank, it's empty or could be a tombstone */ if (MORPHO_ISNIL(e->key)) { if (MORPHO_ISEQUAL(e->val, DICTIONARY_TOMBSTONEVALUE)) { if (!tombstone) tombstone = e; /* We found a tombstone */ } else { /* Otherwise, this is an empty slot. We can now break the loop and eventually return this, or the tombstone */ break; } } indx = DICTIONARY_REDUCE((indx + 1), dict->capacity); } while (indx!=start_index); /* Loop will always terminate if we do a full cycle of the entries */ // If we did not find an existing entry, return the blank, or // preferably a tombstone *entry = (tombstone ? tombstone : e); return false; } /** @brief Internal function that inserts a value in a hashtable given a key * @param[in] dict the dictionary * @param[in] key key to insert * @param[in] val value to insert * @param[in] intern use an interned key * @returns true if successful, false otherwise * @warning If an entry already exists, it is overwritten. Caller should check for existing keys * if this is necessary. */ static inline bool _dictionary_insert(dictionary *dict, value key, value val, bool intern) { dictionaryentry *entry=NULL; if (!dict->contents) { dictionary_resize(dict, DICTIONARY_DEFAULTSIZE); } else if (dict->count+1 > DICTIONARY_SIZEINCREASETHRESHOLD(dict->capacity)) { /* Trigger a resize */ dictionary_resize(dict, DICTIONARY_INCREASESIZE(dict->capacity)); } if (dict->contents) { if (dictionary_find(dict, key, intern, &entry)) { /* Entry already exists */ entry->val=val; return true; } else { /* Entry doesn't exist, */ if (entry) { entry->key=key; entry->val=val; dict->count++; return true; } else { UNREACHABLE("Dictionary failed to insert an entry"); } } } return false; } /** @brief Inserts a value in a hashtable given a key * @param[in] dict the dictionary * @param[in] key key to insert * @param[in] val value to insert * @returns true if successful, false otherwise * @warning If an entry already exists, it is overwritten. Caller should check for existing keys * if this is necessary. */ bool dictionary_insert(dictionary *dict, value key, value val) { return _dictionary_insert(dict, key, val, false); } /** @brief Inserts a value in a hashtable given a key, assuming the key has been interned * @param[in] dict the dictionary * @param[in] key key to insert * @param[in] val value to insert * @returns true if successful, false otherwise * @warning If an entry already exists, it is overwritten. Caller should check for existing keys * if this is necessary. */ bool dictionary_insertintern(dictionary *dict, value key, value val) { return _dictionary_insert(dict, key, val, true); } /** @brief Interns a new key * @detail Looks to see if a similar key [one that passes MORPHO_ISEQUAL] is already * in the dictionary, and if so returns it. Otherwise, inserts this key into * the dictionary. * @param[in] dict the dictionary * @param[in] key a new key to intern * @returns the internalized key, or MORPHO_NIL on failure. */ value dictionary_intern(dictionary *dict, value key) { dictionaryentry *entry=NULL; /* Is the key already in the dictionary? */ if (dictionary_find(dict, key, false, &entry)) { return entry->key; } else { /* If not, insert it with a blank value */ if (dictionary_insert(dict, key, MORPHO_NIL)) { MORPHO_SETOBJECTHASH( key, dictionary_hash(key, false)); return key; } } return MORPHO_NIL; } /** @brief Internal function that retrieves a value from a dictionary given a key * @param[in] dict the dictionary to get * @param[in] key key to locate * @param[in] intern should we assume the key has been interned? * i.e. that object equivalence is tested with MORPHO_ISSAME rather than MORPHO_ISEQUAL * @param[out] val Stores the result in this value if found. * @returns true if found, false otherwise */ static inline bool _dictionary_get(dictionary *dict, value key, bool intern, value *val) { dictionaryentry *entry=NULL; if (dictionary_find(dict, key, intern, &entry)) { if (val) *val = entry->val; return true; } return false; } /** @brief Retrieves a value from a dictionary given a key * @param[in] dict the dictionary to get * @param[in] key key to locate * @param[out] val Stores the result in this value if found. * @returns true if found, false otherwise */ bool dictionary_get(dictionary *dict, value key, value *val) { return _dictionary_get(dict, key, false, val); } /** @brief Retrieves a value from a dictionary given a key assuming * that key has been interned. * @param[in] dict the dictionary to get * @param[in] key key to locate * @param[out] val Stores the result in this value if found. * @returns true if found, false otherwise */ bool dictionary_getintern(dictionary *dict, value key, value *val) { return _dictionary_get(dict, key, true, val); } /** @brief Removes a key from a dictionary given a key * @param[in] dict the dictionary to initialize * @param[in] key key to remove * @returns true if the key was found, false otherwise */ bool dictionary_remove(dictionary *dict, value key) { dictionaryentry *entry=NULL; if (dictionary_find(dict, key, false, &entry)) { *entry = DICTIONARY_TOMBSTONEENTRY; dict->count--; /* If we have lost our last entry, clear the dictionary */ if (dict->count==0) { dictionary_clear(dict); } else if (dict->count < DICTIONARY_SIZEDECREASETHRESHOLD(dict->capacity)) { /* Trigger a resize if we are below some threshhold */ dictionary_resize(dict, DICTIONARY_DECREASESIZE(dict->capacity)); } return true; } return false; } /** @brief Copies the entries of one dictionary to another */ bool dictionary_copy(dictionary *src, dictionary *dest) { if (src->contents) { for (unsigned int i=0; icapacity; i++) { dictionaryentry *e = &src->contents[i]; if (!MORPHO_ISNIL(e->key)) { if (!dictionary_insert(dest, e->key, e->val)) return false; } } } return true; } /* ********************************************************************** * Set functions, i.e. union, intersection, etc. * ********************************************************************** */ /** @brief Computes the union of two dictionaries, i.e. the output dictionary contains all keys that occur in either a or b * @param[in] a - input dictionary (values from this dictionary take priority) * @param[in] b - input dictionary * @param[out] out - output dictionary * @returns true on success. */ bool dictionary_union(dictionary *a, dictionary *b, dictionary *out) { dictionary_clear(out); if (!dictionary_copy(b, out)) return false; if (!dictionary_copy(a, out)) return false; return true; } /** @brief Computes the intersection of two dictionaries, i.e. the output dictionary contains only keys that occur in both a and b * @param[in] a - input dictionary (values are copied from this dictionary) * @param[in] b - input dictionary * @param[out] out - output dictionary * @returns true on success. */ bool dictionary_intersection(dictionary *a, dictionary *b, dictionary *out) { bool success=true; dictionary_clear(out); if (a->contents) { for (unsigned int i=0; icapacity; i++) { dictionaryentry *e = &a->contents[i]; if (!MORPHO_ISNIL(e->key)) { if (dictionary_get(b, e->key, NULL)) { if (!dictionary_insert(out, e->key, e->val)) return false; } } } } return success; } /** @brief Computes the difference of two dictionaries, i.e. the output dictionary contains only keys from a that do NOT occur in B * @param[in] a - input dictionary (keys are copied from this) * @param[in] b - input dictionary * @param[out] out - output dictionary * @returns true on success. */ bool dictionary_difference(dictionary *a, dictionary *b, dictionary *out) { bool success=true; dictionary_clear(out); if (a->contents) { for (unsigned int i=0; icapacity; i++) { dictionaryentry *e = &a->contents[i]; if (!MORPHO_ISNIL(e->key)) { if (!dictionary_get(b, e->key, NULL)) { if (!dictionary_insert(out, e->key, e->val)) return false; } } } } return success; } ================================================ FILE: src/datastructures/dictionary.h ================================================ /** @file dictionary.h * @author T J Atherton * * @brief Dictionary (hashtable) data structure */ #ifndef dictionary_h #define dictionary_h #include "value.h" /* ------------------------------------------------------- * Hash type definition * ------------------------------------------------------- */ typedef uint32_t hash; #define HASH_EMPTY 0 /* ------------------------------------------------------- * Dictionary entry type definition * ------------------------------------------------------- */ /** @brief A single dictionary entry */ typedef struct { value key; value val; } dictionaryentry; /* ------------------------------------------------------- * Dictionary type definition * ------------------------------------------------------- */ /** @brief dictionary data structure that maps keys to values */ typedef struct { unsigned int capacity; /** capacity of the dictionary */ unsigned int count; /** number of items in the dictionary */ dictionaryentry *contents; /** contents of the dictionary */ } dictionary; /* ------------------------------------------------------- * Hash functions that can be used in object definitions * ------------------------------------------------------- */ hash dictionary_hashvalue(value key); hash dictionary_hashint(uint32_t a); hash dictionary_hashpointer(void *hash); hash dictionary_hashcstring(const char *key, size_t length); hash dictionary_hashvaluelist(size_t nel, value *key); /* ------------------------------------------------------- * Dictionary interface * ------------------------------------------------------- */ void dictionary_init(dictionary *dict); void dictionary_clear(dictionary *dict); void dictionary_freecontents(dictionary *dict, bool freekeys, bool freevals); bool dictionary_insert(dictionary *dict, value key, value val); bool dictionary_insertintern(dictionary *dict, value key, value val); value dictionary_intern(dictionary *dict, value key); bool dictionary_get(dictionary *dict, value key, value *val); bool dictionary_getintern(dictionary *dict, value key, value *val); bool dictionary_remove(dictionary *dict, value key); bool dictionary_copy(dictionary *src, dictionary *dest); bool dictionary_union(dictionary *a, dictionary *b, dictionary *out); bool dictionary_intersection(dictionary *a, dictionary *b, dictionary *out); bool dictionary_difference(dictionary *a, dictionary *b, dictionary *out); #endif /* dictionary_h */ ================================================ FILE: src/datastructures/error.c ================================================ /** @file error.c * @author T J Atherton * * @brief Morpho error data structure and handling */ #include #include #include #include "error.h" #include "strng.h" #include "common.h" #include "dictionary.h" /* ********************************************************************** * Global data * ********************************************************************** */ /** A table of errors */ static dictionary error_table; /** A table of error strings. */ static varray_errordefinition error_messages; /* ********************************************************************** * Utility functions * ********************************************************************** */ DEFINE_VARRAY(errordefinition, errordefinition) /** Prints to an error block * @param err Error struct to fill out * @param cat The category of error */ static void error_printf(error *err, errorcategory cat, char *file, int line, int posn, char *message, va_list args) { err->cat=cat; err->file=file; err->line=line; err->posn=posn; /* Print the message with requested args */ vsnprintf(err->msg, MORPHO_ERRORSTRINGSIZE, message, args); } /** Clears an error structure * @param err Error struct to fill out */ void error_clear(error *err) { err->cat=ERROR_NONE; err->id=NULL; err->file=NULL; err->line=ERROR_POSNUNIDENTIFIABLE; err->posn=ERROR_POSNUNIDENTIFIABLE; } /** Clears an error structure * @param err Error struct to fill out */ void error_init(error *err) { error_clear(err); } /** Gets an error definition given an errorid * @param[in] id Error to retrieve * @param[out] def The error definition * @returns true on success */ bool morpho_getdefinitionfromid(errorid id, errordefinition **def) { value result; int indx; bool success=false; value key = object_stringfromcstring(id, strlen(id)); if (dictionary_get(&error_table, key, &result)) { indx=MORPHO_GETINTEGERVALUE(result); *def=&error_messages.data[indx]; success=true; } object_free(MORPHO_GETOBJECT(key)); return success; } /** @brief Writes an error message to an error structure * @param err The error structure * @param id The error id. * @param line The line at which the error occurred, if identifiable. * @param posn The position in the line at which the error occurred, if identifiable. * @param args Additional parameters (the data for the printf commands in the message) */ void morpho_writeerrorwithidvalist(error *err, errorid id, char *file, int line, int posn, va_list args) { error_init(err); errordefinition *def; err->id=id; if (morpho_getdefinitionfromid(id, &def)) { /* Print the message with requested args */ error_printf(err, def->cat, file, line, posn, def->msg, args); } else { UNREACHABLE("Undefined error generated."); } } /** @brief Writes an error message to an error structure * @param err The error structure * @param id The error id. * @param file The file in which the error ocdured, if relevant. * @param line The line at which the error occurred, if identifiable. * @param posn The position in the line at which the error occurred, if identifiable. * @param ... Additional parameters (the data for the printf commands in the message) */ void morpho_writeerrorwithid(error *err, errorid id, char *file, int line, int posn, ...) { va_list args; va_start(args, posn); morpho_writeerrorwithidvalist(err, id, file, line, posn, args); va_end(args); } /** @brief Writes an error message to an error structure without position information * @param err The error structure * @param id The error id. * @param ... Additional parameters (the data for the printf commands in the message) */ void error_writewithid(error *err, errorid id, ... ) { va_list args; va_start(args, id); morpho_writeerrorwithidvalist(err, id, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE, args); va_end(args); } /** @brief Writes a user error to an error structure * @param err The error structure * @param id The error id. * @param message Additional parameters (the data for the printf commands in the message) */ void morpho_writeusererror(error *err, errorid id, char *message) { err->line=ERROR_POSNUNIDENTIFIABLE; err->posn=ERROR_POSNUNIDENTIFIABLE; err->cat=ERROR_USER; err->id=id; size_t length = strlen(message); if (length>MORPHO_ERRORSTRINGSIZE-1) length = MORPHO_ERRORSTRINGSIZE-1; memcpy(err->msg, message, length); err->msg[length]='\0'; // Ensure null termination } /** Defines an error * @param id Error struct to fill out * @param cat The category of error * @param message The message string*/ void morpho_defineerror(errorid id, errorcategory cat, char *message) { errordefinition new = {.cat = cat, .msg=morpho_strdup(message)}; if (new.msg) { value key = object_stringfromcstring(id, strlen(id)); int indx = varray_errordefinitionwrite(&error_messages, new); if (dictionary_get(&error_table, key, NULL)) { UNREACHABLE("Duplicate error.\n"); } dictionary_insert(&error_table, key, MORPHO_INTEGER(indx)); } } /** Gets the id of an error */ errorid morpho_geterrorid(error *err) { return err->id; } /** Tests if an error struct is showing error id * @returns true if the match succeeds and false otherwise */ bool morpho_matcherror(error *err, errorid id) { if (err->cat==ERROR_NONE) return false; return (strcmp(err->id, id)==0); } /** Tests if an error block is showing an error */ bool morpho_checkerror(error *err) { return (err->cat!=ERROR_NONE); } /* ********************************************************************** * Unreachable code * ********************************************************************** */ #ifdef MORPHO_DEBUG void morpho_unreachable(const char *explanation) { fprintf(stderr, "Internal consistency error: Please contact developer. [Explanation: %s].\n", explanation); exit(BSD_EX_SOFTWARE); } #endif /* ********************************************************************** * Initialization/Finalization * ********************************************************************** */ /** Initializes the error handling system */ void error_initialize(void) { dictionary_init(&error_table); varray_errordefinitioninit(&error_messages); morpho_addfinalizefn(error_finalize); } /** Finalizes the error handling system */ void error_finalize(void) { dictionary_freecontents(&error_table, true, false); dictionary_clear(&error_table); for (unsigned int i=0; i #include "build.h" #include "varray.h" /* ------------------------------------------------------- * Error type definitions * ------------------------------------------------------- */ /** @brief Identifier for errors. */ typedef char * errorid; /* -------------------------------- * Categories of error * -------------------------------- */ /** Identifies the category of error that has occurred */ typedef enum { ERROR_NONE, /** No error. */ /* Execution should continue */ ERROR_WARNING, /** Warnings generated. */ /* Execution should not continue */ ERROR_HALT, /** or has occured. Should return to the user as fast as possible. */ ERROR_USER, /** Generated by the user */ ERROR_EXIT, /** Morpho should exit. */ /* Other kinds of error */ ERROR_LEX, /** or generated by the lexer */ ERROR_PARSE, /** or generated by the parser */ ERROR_COMPILE, /** or generated by the compiler */ ERROR_DEBUGGER /** or generated by the debugger */ } errorcategory; /* -------------------------------- * Macros to scrutinize these types * -------------------------------- */ /** Did an operation succeed without errors? */ #define ERROR_SUCCEEDED(err) ((err).cat == ERROR_NONE) /** Did an operation fail? */ #define ERROR_FAILED(err) ((err).cat != ERROR_NONE) /** Is this a runtime error? */ #define ERROR_ISRUNTIMEERROR(err) ((err).cat <= ERROR_EXIT) /* ---------------------------------------------------- * Error struct, containing information about the error * ---------------------------------------------------- */ /** @brief A type used by public-facing morpho functions */ typedef errorcategory morphoerror; /** @brief A static container for error messages. */ typedef struct { errorcategory cat; errorid id; char *file; int line, posn; char msg[MORPHO_ERRORSTRINGSIZE]; } error; /** Set line and posn to this value if they can't be determined */ #define ERROR_POSNUNIDENTIFIABLE -1 /* -------------------------------- * Error definitions * -------------------------------- */ /** @brief Definition of an error message. */ typedef struct { errorcategory cat; char *msg; } errordefinition; /** A varray of errordefinitions */ DECLARE_VARRAY(errordefinition, errordefinition) /* ------------------------------------------------------- * Error related macros * ------------------------------------------------------- */ /** Macro to place in code that should be unreachable */ #ifdef MORPHO_DEBUG void morpho_unreachable(const char *explanation); #define UNREACHABLE(x) morpho_unreachable(x) #else #define UNREACHABLE(x) #endif /* -------------------------------- * Exit codes * -------------------------------- */ /** Exit codes from BSD standard */ #define BSD_EX_SOFTWARE 70 /* ------------------------------------------------------- * General error codes * ------------------------------------------------------- */ #define ERROR_ALLOCATIONFAILED "Alloc" #define ERROR_ALLOCATIONFAILED_MSG "Memory allocation failed." #define ERROR_INTERNALERROR "Intrnl" #define ERROR_INTERNALERROR_MSG "Internal error (contact developer)." #define ERROR_ERROR "Err" #define ERROR_ERROR_MSG "Error." /* ------------------------------------------------------- * VM error messages * ------------------------------------------------------- */ #define VM_EXIT "Exit" #define VM_EXIT_MSG "VM halted." #define VM_STCKOVFLW "StckOvflw" #define VM_STCKOVFLW_MSG "Stack overflow." #define VM_ERRSTCKOVFLW "ErrStckOvflw" #define VM_ERRSTCKOVFLW_MSG "Error handler stack overflow." #define VM_INVLDOP "InvldOp" #define VM_INVLDOP_MSG "Invalid operands. Failed to %s %s and %s" #define VM_CNCTFLD "CnctFld" #define VM_CNCTFLD_MSG "Concatenation failed." #define VM_UNCALLABLE "Uncallable" #define VM_UNCALLABLE_MSG "Can only call a function or method." #define VM_GLBLRTRN "GlblRtrn" #define VM_GLBLRTRN_MSG "Return encountered outside a function or method." #define VM_INSTANTIATEFAILED "InstFail" #define VM_INSTANTIATEFAILED_MSG "Could not instantiate object." #define VM_NOTANOBJECT "NotAnObj" #define VM_NOTANOBJECT_MSG "Not an object." #define VM_OBJECTLACKSPROPERTY "ObjLcksPrp" #define VM_OBJECTLACKSPROPERTY_MSG "Object lacks property or method '%s'." #define VM_NOINITIALIZER "NoInit" #define VM_NOINITIALIZER_MSG "Cannot instantiate with arguments because class '%s' does not provide an initializer." #define VM_NOTANINSTANCE "NotAnInst" #define VM_NOTANINSTANCE_MSG "Can only invoke methods on objects." #define VM_CLASSLACKSPROPERTY "ClssLcksMthd" #define VM_CLASSLACKSPROPERTY_MSG "Class lacks method '%s'." #define VM_INVALIDARGS "InvldArgs" #define VM_INVALIDARGS_MSG "Expected %u arguments but got %u." #define VM_NOOPTARG "NoOptArg" #define VM_NOOPTARG_MSG "Function doens't expect optional arguments." #define VM_UNKNWNOPTARG "UnkwnOptArg" #define VM_UNKNWNOPTARG_MSG "Unknown optional argument '%s'." #define VM_INVALIDARGSDETAIL "InvldArgsBltn" #define VM_INVALIDARGSDETAIL_MSG "Function %s expects %u arguments of type %s." #define VM_NOTINDEXABLE "NotIndxbl" #define VM_NOTINDEXABLE_MSG "Value or object not indexable." #define VM_OUTOFBOUNDS "IndxBnds" #define VM_OUTOFBOUNDS_MSG "Index out of bounds." #define VM_NONNUMINDX "NonNmIndx" #define VM_NONNUMINDX_MSG "Non-numerical array index." #define VM_ARRAYWRONGDIM "ArrayDim" #define VM_ARRAYWRONGDIM_MSG "Incorrect number of dimensions for array." #define VM_DBGQUIT "DbgQuit" #define VM_DBGQUIT_MSG "Program terminated by user in debugger." #define VM_DVZR "DvZr" #define VM_DVZR_MSG "Division by zero." #define VM_GETINDEXARGS "NonintIndex" #define VM_GETINDEXARGS_MSG "Noninteger array index." #define VM_MLTPLDSPTCHFLD "MltplDsptchFld" #define VM_MLTPLDSPTCHFLD_MSG "Multiple dispatch could not find an implementation that matches these arguments." /* ------------------------------------------------------- * Error interface * ------------------------------------------------------- */ void error_init(error *err); void error_clear(error *err); void morpho_writeerrorwithidvalist(error *err, errorid id, char *file, int line, int posn, va_list args); void morpho_writeerrorwithid(error *err, errorid id, char *file, int line, int posn, ...); void error_writewithid(error *err, errorid id, ... ); void morpho_writeusererror(error *err, errorid id, char *message); void morpho_defineerror(errorid id, errorcategory cat, char *message); errorid morpho_geterrorid(error *err); bool morpho_matcherror(error *err, errorid id); bool morpho_checkerror(error *err); void error_initialize(void); void error_finalize(void); #endif /* error_h */ ================================================ FILE: src/datastructures/object.c ================================================ /** @file object.c * @author T J Atherton * * @brief Provide functionality for extended and mutable data types. */ #include #include #include "morpho.h" #include "classes.h" /* ********************************************************************** * Object definitions * ********************************************************************** */ /** Hold the object type definitions as they're created */ objecttypedefn _objectdefns[MORPHO_MAXIMUMOBJECTDEFNS]; objecttype objectdefnnext; /** Type of the next object definition */ /** Adds a new object type with a given definition. @returns: the objecttype identifier to be used henceforth */ objecttype object_addtype(objecttypedefn *def) { if (!def->printfn || !def->sizefn) { UNREACHABLE("Object definition must provide a print and size function."); } if (objectdefnnext>=MORPHO_MAXIMUMOBJECTDEFNS) { UNREACHABLE("Too many object definitions (increase MORPHO_MAXIMUMOBJECTDEFNS)."); } _objectdefns[objectdefnnext]=*def; _objectdefns[objectdefnnext].veneer = NULL; objectdefnnext+=1; return objectdefnnext-1; } /** Gets the appropriate definition given an object */ objecttypedefn *object_getdefn(object *obj) { return &_objectdefns[obj->type]; } /* ********************************************************************** * Objects * ********************************************************************** */ /** @brief Initializes an object to be a certain type * @param obj object to initialize * @param type type to initialize with */ void object_init(object *obj, objecttype type) { obj->next=NULL; obj->hsh=HASH_EMPTY; obj->status=OBJECT_ISUNMANAGED; obj->type=type; } /** Frees an object */ void object_free(object *obj) { #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR if (obj) { fprintf(stderr, "Free object %p of type %d ", (void *) obj, obj->type); morpho_printvalue(NULL, MORPHO_OBJECT(obj)); fprintf(stderr, "\n"); } #endif if (object_getdefn(obj)->freefn) object_getdefn(obj)->freefn(obj); MORPHO_FREE(obj); } /** Free an object if it is unmanaged */ void object_freeifunmanaged(object *obj) { if (obj->status==OBJECT_ISUNMANAGED) object_free(obj); } /** Calls an object's print function */ void object_print(void *v, value val) { object *obj = MORPHO_GETOBJECT(val); object_getdefn(obj)->printfn(obj, v); } /** Gets the total size of an object */ size_t object_size(object *obj) { return object_getdefn(obj)->sizefn(obj); } /** Hash an object, either by calling its hash function or by hashing its pointer */ hash object_hash(object *obj) { objecttypedefn *defn = object_getdefn(obj); if (defn->hashfn) return (defn->hashfn) (obj); return dictionary_hashpointer(obj); } /** Compare two objects */ int object_cmp(object *a, object *b) { objecttypedefn *defn = object_getdefn(a); if (defn->cmpfn) return (defn->cmpfn) (a, b); return (a == b? MORPHO_EQUAL: MORPHO_NOTEQUAL); } /** @brief Allocates an object * @param size size of memory to reserve * @param type type to initialize with */ object *object_new(size_t size, objecttype type) { object *new = MORPHO_MALLOC(size); if (new) object_init(new, type); #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR fprintf(stderr, "Create object %p of size %ld with type %d.\n", (void *) new, size, type); #endif return new; } /** Checks if an object is of a particular type */ bool object_istype(value val, objecttype type) { return (MORPHO_ISOBJECT(val) && MORPHO_GETOBJECTTYPE(val)==type); } /** Free any object that may be contained in a value. */ void morpho_freeobject(value val) { if (MORPHO_ISOBJECT(val)) object_free(MORPHO_GETOBJECT(val)); } /* ********************************************************************** * Veneer classes * ********************************************************************** */ /** @brief Sets the veneer class for a particular object type */ void object_setveneerclass(objecttype type, value class) { if (_objectdefns[type].veneer!=NULL) { UNREACHABLE("Veneer class redefined.\n"); } _objectdefns[type].veneer=(object *) MORPHO_GETCLASS(class); } /** @brief Gets the veneer for a particular object type */ objectclass *object_getveneerclass(objecttype type) { return (objectclass *) _objectdefns[type].veneer; } /** @brief Finds the object type associated with a veneer class; returns false if it is not a veneer class */ bool object_veneerclasstotype(objectclass *clss, objecttype *type) { for (int i=0; i #include "value.h" #include "dictionary.h" /* ------------------------------------------------------- * Fundamental object type * ------------------------------------------------------- */ /** Objects are heap-allocated data structures that have a common header allowing type identification. The header should never be accessed directly, but through macros as provided below */ /** The objecttype identifies the type of an object. */ typedef int objecttype; /** Fundamental object structure */ struct sobject { objecttype type; // Type enum { // Memory management status for the object: OBJECT_ISUNMANAGED, // - UNMANAGED means the object is manually alloc'd/dealloc'd OBJECT_ISBUILTIN, // - BUILTIN means the object was created by the builtin environment OBJECT_ISPROGRAM, // - PROGRAM means the object is bound to the program OBJECT_ISUNMARKED, // - UNMARKED means the object is managed by the GC OBJECT_ISMARKED // - MARKED is used internally by the GC } status; hash hsh; // hash value struct sobject *next; // All objects can be chained together (e.g. to attach to the VM that created them) }; /** These macros access the object structure's fields. */ /** Checks if an object is garbage collected */ #define MORPHO_ISGARBAGECOLLECTED(val) (MORPHO_GETOBJECT(val)->status>=OBJECT_ISUNMARKED) /** Gets the type of the object associated with a value @warning: Do not use this to compare types, use an appropriate macro like MORPHO_ISXXX */ #define MORPHO_GETOBJECTTYPE(val) (MORPHO_GETOBJECT(val)->type) /** Gets an object's key */ #define MORPHO_GETOBJECTHASH(val) (MORPHO_GETOBJECT(val)->hsh) /** Sets an objects key */ #define MORPHO_SETOBJECTHASH(val, newhash) (MORPHO_GETOBJECT(val)->hsh = newhash) /* ------------------------------------------------------- * object definitions * ------------------------------------------------------- */ /** To define a new object type, you must provide several functions that enable the morpho runtime to interact with it. These object definition functions are collected together in an objecttypedefn. The object type is assigned at initialization by calling object_addtype */ /** Called to print a short identifier for the object */ typedef void (*objectprintfn) (object *obj, void *v); /** Called to mark the contents of an object; called by the garbage collector to identify subsidiary objects */ typedef void (*objectmarkfn) (object *obj, void *v); /** Called to free any unmanaged subsidiary data structures for an object */ typedef void (*objectfreefn) (object *obj); /** Called to return the size of an object and attached data (anything NOT stored in a value) */ typedef size_t (*objectsizefn) (object *obj); /** Called to hash an object */ typedef hash (*objecthashfn) (object *obj); /** Called to compare two objects */ typedef int (*objectcmpfn) (object *a, object *b); /** This function should return one of: */ #define MORPHO_EQUAL 0 #define MORPHO_NOTEQUAL 1 #define MORPHO_BIGGER 1 #define MORPHO_SMALLER -1 /** Defines a custom object type. */ typedef struct { object *veneer; // Veneer class objectfreefn freefn; objectmarkfn markfn; objectsizefn sizefn; objectprintfn printfn; objecthashfn hashfn; objectcmpfn cmpfn; } objecttypedefn; /* ------------------------------------------------------- * Object creation and management * ------------------------------------------------------- */ // Define new object types objecttype object_addtype(objecttypedefn *def); objecttypedefn *object_getdefn(object *obj); // Management of object structures void object_init(object *obj, objecttype type); void object_free(object *obj); void object_freeifunmanaged(object *obj); void object_print(void *v, value val); void object_printtobuffer(value v, varray_char *buffer); size_t object_size(object *obj); hash object_hash(object *obj); int object_cmp(object *a, object *b); bool object_istype(value val, objecttype type); // Create a new object with a specified allocation size and type object *object_new(size_t size, objecttype type); // Recommended interface to free an object from a value void morpho_freeobject(value val); // Index type typedef ptrdiff_t indx; // Object initialization and finalization void object_initialize(void); void object_finalize(void); #endif /* object_h */ ================================================ FILE: src/datastructures/program.c ================================================ /** @file program.c * @author T J Atherton * * @brief Data structure for a morpho program */ #define MORPHO_CORE #include "core.h" #include "debug.h" #include "compile.h" #include "morpho.h" /* ********************************************************************** * Programs * ********************************************************************** */ DEFINE_VARRAY(instruction, instruction); DEFINE_VARRAY(globalinfo, globalinfo); /** @brief Initializes a program */ void program_init(program *p) { varray_instructioninit(&p->code); varray_debugannotationinit(&p->annotations); p->global=object_newfunction(MORPHO_PROGRAMSTART, MORPHO_NIL, NULL, 0); p->boundlist=NULL; dictionary_init(&p->symboltable); varray_globalinfoinit(&p->globals); varray_valueinit(&p->classes); } /** @brief Clears a program, freeing associated data structures */ void program_clear(program *p) { if (p->global) object_free((object *) p->global); varray_instructionclear(&p->code); debugannotation_clear(&p->annotations); p->global=NULL; /* Free any objects bound to the program */ #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR fprintf(stderr, "--Freeing objects bound to program.\n"); #endif while (p->boundlist!=NULL) { object *next = p->boundlist->next; object_free(p->boundlist); p->boundlist=next; } #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR fprintf(stderr, "------\n"); #endif /* Note we don't free the contents as they are already interned */ varray_globalinfoclear(&p->globals); dictionary_clear(&p->symboltable); varray_valueclear(&p->classes); } /** @brief Creates and initializes a new program */ program *morpho_newprogram(void) { program *new = MORPHO_MALLOC(sizeof(program)); if (new) program_init(new); return new; } /** @brief Frees a program */ void morpho_freeprogram(program *p) { program_clear(p); MORPHO_FREE(p); } /** Sets the entry point of a program */ void program_setentry(program *p, instructionindx entry) { if (p->global) p->global->entry=entry; } /** Gets the entry point of a program */ instructionindx program_getentry(program *p) { instructionindx out = MORPHO_PROGRAMSTART; if (p->global) out=p->global->entry; return out; } /** @brief Binds an object to a program * @details Objects bound to the program are freed with the program; use for static data (e.g. held in constant tables) */ void program_bindobject(program *p, object *obj) { if (!obj->next && /* Object is not already bound to the program (or something else) */ p->boundlist!=obj && obj->status==OBJECT_ISUNMANAGED) { obj->status=OBJECT_ISPROGRAM; obj->next=p->boundlist; p->boundlist=obj; } } /** @brief Interns a symbol into the programs symbol table. * @details Note that the string is cloned if it does not exist already. * Interning is used to accelerate dynamic lookups as the same string for a symbol will be used universally */ value program_internsymbol(program *p, value symbol) { value new = symbol, out; #ifdef MORPHO_DEBUG_SYMBOLTABLE fprintf(stderr, "Interning symbol '"); morpho_printvalue(NULL, symbol); #endif if (builtin_checksymbol(symbol)) { // Check if this is part of the built in symbol table already return builtin_internsymbol(symbol); } if (!dictionary_get(&p->symboltable, symbol, NULL)) { new = object_clonestring(symbol); } out = dictionary_intern(&p->symboltable, new); #ifdef MORPHO_DEBUG_SYMBOLTABLE fprintf(stderr, "' at %p\n", (void *) MORPHO_GETOBJECT(out)); #endif program_bindobject(p, MORPHO_GETOBJECT(out)); return out; } /** @brief Adds a global to the program */ globalindx program_addglobal(program *p, value symbol) { globalinfo info = { .symbol = program_internsymbol(p, symbol), .type=MORPHO_NIL }; return (globalindx) varray_globalinfowrite(&p->globals, info); } /** @brief Sets the type associated with a global variable */ void program_globalsettype(program *p, globalindx indx, value type) { if (indx<0 || indx>p->globals.count) return; p->globals.data[indx].type=type; } /** @brief Gets the type associated with a global variable */ bool program_globaltype(program *p, globalindx indx, value *type) { if (indx<0 || indx>p->globals.count) return false; *type = p->globals.data[indx].type; return true; } /** @brief Gets the symbol associated with a global variable */ bool program_globalsymbol(program *p, globalindx indx, value *symbol) { if (indx<0 || indx>p->globals.count) return false; *symbol = p->globals.data[indx].symbol; return true; } /** @brief Returns the number of globals allocated in the program */ int program_countglobals(program *p) { return p->globals.count; } /** @brief Adds a class to the program's class list */ int program_addclass(program *p, value klass) { return varray_valuewrite(&p->classes, klass); } /** @brief Returns the number of classes allocated in the program */ int program_countclasses(program *p, value klass) { return p->classes.count; } ================================================ FILE: src/datastructures/program.h ================================================ /** @file program.h * @author T J Atherton * * @brief Morpho program data structure */ #ifndef program_h #define program_h #include "varray.h" #include "object.h" #include "classes.h" #include "debugannotation.h" #ifdef MORPHO_CORE /* ------------------------------------------------------- * Instructions are the basic unit of execution * ------------------------------------------------------- */ typedef unsigned int instruction; DECLARE_VARRAY(instruction, instruction); /** @brief Index into instructions */ typedef indx instructionindx; /* ------------------------------------------------------- * Global variables * ------------------------------------------------------- */ /** @brief Index of a global */ typedef int globalindx; /** @brief Record information about each global variable */ typedef struct { value symbol; value type; } globalinfo; DECLARE_VARRAY(globalinfo, globalinfo) /* ------------------------------------------------------- * Programs comprise instructions and debugging information * ------------------------------------------------------- */ /** @brief Morpho code program and associated data */ typedef struct { varray_instruction code; /** Compiled instructions */ varray_debugannotation annotations; /** Information about how the code connects to the source */ objectfunction *global; /** Pseudofunction containing global data */ varray_globalinfo globals; /** Global variables */ varray_value classes; /** Classes defined by this program */ object *boundlist; /** Linked list of static objects bound to this program */ dictionary symboltable; /** The symbol table */ } program; #define MORPHO_PROGRAMSTART 0 void program_setentry(program *p, instructionindx entry); instructionindx program_getentry(program *p); varray_value *program_getconstanttable(program *p); void program_bindobject(program *p, object *obj); value program_internsymbol(program *p, value symbol); globalindx program_addglobal(program *p, value symbol); void program_globalsettype(program *p, globalindx indx, value type); bool program_globaltype(program *p, globalindx indx, value *type); bool program_globalsymbol(program *p, globalindx indx, value *symbol); int program_countglobals(program *p); int program_addclass(program *p, value klass); int program_countclasses(program *p, value klass); #endif /* MORPHO_CORE */ #endif /* error_h */ ================================================ FILE: src/datastructures/signature.c ================================================ /** @file signature.c * @author T J Atherton * * @brief Function signatures and their declarations */ #include "classes.h" #include "signature.h" #include "parse.h" /* ********************************************************************** * Manage signature structures * ********************************************************************** */ void signature_init(signature *s) { varray_valueinit(&s->types); s->ret=MORPHO_NIL; s->varg=false; } void signature_clear(signature *s) { varray_valueclear(&s->types); } /** @brief Sets the contents of a signature * @param[in] s - the signature structure * @param[in] nparam - number of fixed parameters * @param[in] types - list of types of each parameter */ void signature_set(signature *s, int nparam, value *types) { s->types.count=0; // Reset varray_valueadd(&s->types, types, nparam); } /** @brief Sets whether a signature contains variadic arguments */ void signature_setvarg(signature *s, bool varg) { s->varg=varg; } /** @brief Sets whether a signature contains variadic arguments */ bool signature_isvarg(signature *s) { return s->varg; } /** @brief Returns true if any entries in the signature are typed*/ bool signature_istyped(signature *s) { for (int i=0; itypes.count; i++) if (!MORPHO_ISNIL(s->types.data[i])) return true; return false; } /** @brief Check if two signatures are equal */ bool signature_isequal(signature *a, signature *b) { if (a->types.count!=b->types.count) return false; for (int i=0; itypes.count; i++) if (!MORPHO_ISEQUAL(a->types.data[i], b->types.data[i])) return false; return true; } /** @brief Return list of types */ bool signature_paramlist(signature *s, int *nparams, value **ptypes) { if (nparams) *nparams = s->types.count; if (ptypes) *ptypes = s->types.data; return s->types.data; } /** @brief Returns the type of the i'th parameter, if it exists */ bool signature_getparamtype(signature *s, int i, value *type) { if (i>=s->types.count) return false; if (type) *type = s->types.data[i]; return true; } /** @brief Returns the return type from the signature if defined */ value signature_getreturntype(signature *s) { return s->ret; } /** @brief Count the number of parameters in a signature */ int signature_countparams(signature *s) { return s->types.count; } /* ********************************************************************** * Parse signatures * ********************************************************************** */ enum { SIGNATURE_LEFTBRACE, SIGNATURE_RIGHTBRACE, SIGNATURE_COMMA, SIGNATURE_DOTDOTDOT, SIGNATURE_SYMBOL, SIGNATURE_EOF }; tokendefn sigtokens[] = { { "(", SIGNATURE_LEFTBRACE , NULL }, { ")", SIGNATURE_RIGHTBRACE , NULL }, { ",", SIGNATURE_COMMA , NULL }, { "...", SIGNATURE_DOTDOTDOT , NULL }, { "", SIGNATURE_EOF , NULL } }; /** @brief Initializes a lexer for parsing signatures */ void signature_initializelexer(lexer *l, char *signature) { lex_init(l, signature, 0); lex_settokendefns(l, sigtokens); lex_seteof(l, SIGNATURE_EOF); lex_setsymboltype(l, SIGNATURE_SYMBOL); } /** @brief Parses a type name held in the parser's previous type */ bool signature_parsetype(parser *p, value *type) { value symbol; if (!parse_stringfromtoken(p, 0, p->previous.length, &symbol)) return false; value klass = builtin_findclass(symbol); morpho_freeobject(symbol); if (MORPHO_ISCLASS(klass)) *type=klass; return MORPHO_ISCLASS(klass); } /** @brief Parser function to process a symbol held in p->previous */ bool signature_parsesymbol(parser *p, void *out) { signature *sig = (signature *) out; bool success=false; if (p->previous.length==1 && *p->previous.start=='_') { value blank = MORPHO_NIL; success=varray_valueadd(&sig->types, &blank, 1); } else { value type; if (signature_parsetype(p, &type)) success=varray_valueadd(&sig->types, &type, 1); } return success; } /** @brief Parser function to process varg */ bool signature_parsevarg(parser *p, void *out) { signature *sig = (signature *) out; value blank = MORPHO_NIL; bool success=varray_valueadd(&sig->types, &blank, 1); sig->varg=true; return success; } /** @brief Main parser function for signatures */ bool signature_parsesignature(parser *p, void *out) { signature *sig = (signature *) out; if (parse_checktokenadvance(p, SIGNATURE_SYMBOL)) { value type; if (signature_parsetype(p, &type)) sig->ret=type; } if (!parse_checktokenadvance(p, SIGNATURE_LEFTBRACE)) return false; while (!parse_checktoken(p, SIGNATURE_RIGHTBRACE) && !parse_checktoken(p, SIGNATURE_EOF)) { if (parse_checktokenadvance(p, SIGNATURE_SYMBOL)) { if (!signature_parsesymbol(p, out)) return false; } else if (parse_checktokenadvance(p, SIGNATURE_DOTDOTDOT)) { if (!signature_parsevarg(p, out)) return false; } else return false; parse_checktokenadvance(p, SIGNATURE_COMMA); } if (!parse_checktokenadvance(p, SIGNATURE_RIGHTBRACE)) return false; return true; } /** Parses a signature */ bool signature_parse(char *sig, signature *out) { error err; error_init(&err); lexer l; signature_initializelexer(&l, sig); parser p; parse_init(&p, &l, &err, out); parse_setbaseparsefn(&p, signature_parsesignature); parse_setskipnewline(&p, false, TOKEN_NONE); bool success=parse(&p); parse_clear(&p); lex_clear(&l); return success; } /** Print a signature for debugging purposes */ void signature_print(signature *s) { printf("("); for (int i=0; itypes.count; i++) { value type=s->types.data[i]; if (s->varg && i==s->types.count-1) printf("..."); else if (MORPHO_ISNIL(type)) printf("_"); else if (MORPHO_ISCLASS(type)) morpho_printvalue(NULL, MORPHO_GETCLASS(type)->name); if (itypes.count-1) printf(","); } printf(")"); } ================================================ FILE: src/datastructures/signature.h ================================================ /** @file signature.h * @author T J Atherton * * @brief Function signatures and their declarations */ #ifndef signature_h #define signature_h #include #include "varray.h" typedef struct { varray_value types; /** Signature of parameters */ value ret; /** Return type */ bool varg; /** Is the function variadic? */ } signature; void signature_init(signature *s); void signature_clear(signature *s); void signature_setvarg(signature *s, bool varg); bool signature_isvarg(signature *s); bool signature_istyped(signature *s); bool signature_isequal(signature *a, signature *b); bool signature_paramlist(signature *s, int *nparams, value **ptypes); bool signature_getparamtype(signature *s, int i, value *type); value signature_getreturntype(signature *s); int signature_countparams(signature *s); void signature_set(signature *s, int nparam, value *types); bool signature_parse(char *sig, signature *out); void signature_print(signature *s); #endif ================================================ FILE: src/datastructures/syntaxtree.c ================================================ /** @file syntaxtree.c * @author T J Atherton * * @brief Syntax tree data structure for morpho */ #include #include "syntaxtree.h" #include "common.h" DEFINE_VARRAY(syntaxtreenode, syntaxtreenode); DEFINE_VARRAY(syntaxtreeindx, syntaxtreeindx); /** @brief Initialize a syntax tree */ void syntaxtree_init(syntaxtree *tree) { varray_syntaxtreenodeinit(&tree->tree); tree->entry=0; } /** @brief Wipe a syntax tree, not freeing attached objects */ void syntaxtree_wipe(syntaxtree *tree) { tree->tree.count=0; } /** @brief Finalize a syntax tree */ void syntaxtree_clear(syntaxtree *tree) { #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR fprintf(stderr, "--Freeing syntax tree %p.\n",(void *) tree); #endif /** Free attached objects */ for (unsigned int i=0; itree.count; i++) { syntaxtreenode *node = &tree->tree.data[i]; if (MORPHO_ISOBJECT(node->content)) { object_free(MORPHO_GETOBJECT(node->content)); } } varray_syntaxtreenodeclear(&tree->tree); #ifdef MORPHO_DEBUG_LOGGARBAGECOLLECTOR fprintf(stderr, "------\n"); #endif } #ifdef MORPHO_DEBUG static char * nodedisplay[] = { "", // NODE_BASE "", // NODE_NIL "", // NODE_BOOL "", // NODE_FLOAT, "", // NODE_INTEGER, "", // NODE_STRING, "", // NODE_SYMBOL, "self", // NODE_SELF, "super", // NODE_SUPER, "im", // NODE_IMAG, "", // NODE_LEAF, /* ^ All leafs should be above this enum value */ "-", // NODE_NEGATE, "!", // NODE_NOT, "", // NODE_UNARY, /* ^ All unary operators should be above this enum value */ "+", // NODE_ADD, "-", // NODE_SUBTRACT, "*", // NODE_MULTIPLY, "/", // NODE_DIVIDE, "^", // NODE_POW, "=", // NODE_ASSIGN, "==", // NODE_EQ, "!=", // NODE_NEQ, "<", // NODE_LT, ">", // NODE_GT, "<=", // NODE_LTEQ, ">=", // NODE_GTEQ, "and", // NODE_AND "or", // NODE_OR "?", // NODE_TERNARY ".", // NODE_DOT "..", // NODE_INCLUSIVERANGE "...", // NODE_RANGE "", // NODE_OPERATOR, /* ^ All operators should be above this enum value */ "print", // NODE_PRINT ":=", // NODE_DECLARATION "type", // NODE_TYPE "fn", // NODE_FUNCTION "", // NODE_METHOD "class", // NODE_CLASS "return", // NODE_RETURN "if", // NODE_IF "else", // NODE_THEN "while", // NODE_WHILE "for", // NODE_FOR "do", // NODE_DO "in", // NODE_IN "break", // NODE_BREAK "continue",// NODE_CONTINUE "try", // NODE_TRY "", // NODE_STATEMENT "()", // NODE_GROUPING ";", // NODE_SEQUENCE "dict", // NODE_DICTIONARY "keyval", // NODE_DICTENTRY "\"", // NODE_INTERPOLATION "arglist", // NODE_ARGLIST "{}", // NODE_SCOPE "call", // NODE_CALL "index", // NODE_INDEX "list", // NODE_LIST "tuple", // NODE_TUPLE "import", // NODE_IMPORT "as", // NODE_AS "@", // NODE_BREAKPOINT "", // NODE_STRUCTURAL }; /** @brief Prints a single node of a syntax tree with indentation * @param base pointer to head node * @param i index of this node * @param indent indentation level */ void syntaxtree_printnode(syntaxtreenode *base, syntaxtreeindx i, unsigned int indent) { syntaxtreenode *node = base + i; for (unsigned int i=0; itype)) { char *display = nodedisplay[node->type]; if (display) printf("%s",display); else UNREACHABLE("print syntax tree node type [Operator print rule not implemented]"); } if (SYNTAXTREE_ISLEAF(node->type)) { if (node->type==NODE_SELF || node->type==NODE_SUPER) { char *display = nodedisplay[node->type]; if (display) printf("%s\n",display); } else { morpho_printvalue(NULL, node->content); printf("\n"); } } else { if (node->type==NODE_FUNCTION || node->type==NODE_CLASS) { printf(" "); morpho_printvalue(NULL, node->content); } else if (node->type==NODE_INTERPOLATION) { printf("\" '"); morpho_printvalue(NULL, node->content); printf("'"); } printf("\n"); if (node->left!=SYNTAXTREE_UNCONNECTED) syntaxtree_printnode(base, node->left,indent+1); if (!SYNTAXTREE_ISUNARY(node->type)) { if (node->right!=SYNTAXTREE_UNCONNECTED) { syntaxtree_printnode(base, node->right,indent+1); } else { for (unsigned int i=0; itree.count && tree->entry!=SYNTAXTREE_UNCONNECTED) syntaxtree_printnode(tree->tree.data, tree->entry, 0); } #endif /** @brief Adds a node to the syntax tree * @param tree tree to add to. * @param type type of node to add * @param content a value to add * @param left } left ... * @param right } ...and right branches of the node. */ bool syntaxtree_addnode(syntaxtree *tree, syntaxtreenodetype type, value content, int line, int posn, syntaxtreeindx left, syntaxtreeindx right, syntaxtreeindx *out) { syntaxtreenode new = {.content=content, .left = left, .right = right, .type = type, .line=line, .posn=posn}; if (!varray_syntaxtreenodeadd(&tree->tree, &new, 1)) return false; *out = tree->tree.count-1; return true; } /** Gets a syntaxtree node from its index */ syntaxtreenode *syntaxtree_nodefromindx(syntaxtree *tree, syntaxtreeindx indx) { return tree->tree.data+indx; } /* @brief Flattens a tree into a list of node indices * @param[in] tree - the syntaxtree to traverse * @param[in] node - the starting node * @param[in] ntypes - number of node types to match (these will be flattened) * @param[in] types - list of node types to flatten * @param[in/out] list - list of nodes flattened from this node */ void syntaxtree_flatten(syntaxtree *tree, syntaxtreeindx indx, unsigned int ntypes, syntaxtreenodetype *types, varray_syntaxtreeindx *list) { if (indx==SYNTAXTREE_UNCONNECTED) return; syntaxtreenode *node = syntaxtree_nodefromindx(tree, indx); if (!node) return; bool isdesiredtype=false; for (unsigned int i=0; itype==types[i]) isdesiredtype=true; } if (isdesiredtype) { if (node->left!=SYNTAXTREE_UNCONNECTED) syntaxtree_flatten(tree, node->left, ntypes, types, list); if (node->right!=SYNTAXTREE_UNCONNECTED) syntaxtree_flatten(tree, node->right, ntypes, types, list); } else { varray_syntaxtreeindxadd(list, &indx, 1); } } ================================================ FILE: src/datastructures/syntaxtree.h ================================================ /** @file syntaxtree.h * @author T J Atherton * * @brief Syntax tree data structure for morpho */ #ifndef syntaxtree_h #define syntaxtree_h #include #include "value.h" #include "varray.h" /** Syntax trees in morpho are binary trees: they consist of nodes, which may contain a value and reference two child elements */ /* ------------------------------------------------------- * Nodes * ------------------------------------------------------- */ /** Syntax tree node types are left as a generic int to facilitate reprogrammability */ typedef int syntaxtreenodetype; /** @brief A reference to an element of the tree is by an index */ typedef ptrdiff_t syntaxtreeindx; /** @brief A node on the syntax tree */ typedef struct _syntaxtreenode { syntaxtreenodetype type; /** Type of node */ value content; /** A value that represents the node contents */ syntaxtreeindx left; /** left child element */ syntaxtreeindx right; /** right child element */ int line; /** line in the source that the element was created from. */ int posn; /** column in the source that the element was created from. */ } syntaxtreenode; /** Macro used to represent unconnected nodes */ #define SYNTAXTREE_UNCONNECTED -1 /* ------------------------------------------------------- * Syntax tree * ------------------------------------------------------- */ DECLARE_VARRAY(syntaxtreenode, syntaxtreenode); DECLARE_VARRAY(syntaxtreeindx, syntaxtreeindx); /** @brief A syntax tree data structure */ typedef struct { varray_syntaxtreenode tree; /** List of nodes */ syntaxtreeindx entry; /** Entry point into the syntax tree */ } syntaxtree; /* ------------------------------------------------------- * Node types * ------------------------------------------------------- */ /** @brief Type of node */ enum { NODE_BASE, NODE_NIL, NODE_BOOL, NODE_FLOAT, NODE_INTEGER, NODE_STRING, NODE_SYMBOL, NODE_SELF, NODE_SUPER, NODE_IMAG, NODE_LEAF, /* ^ All leafs should be above this enum value */ NODE_NEGATE, NODE_NOT, NODE_UNARY, /* ^ All unary operators should be above this enum value */ NODE_ADD, NODE_SUBTRACT, NODE_MULTIPLY, NODE_DIVIDE, NODE_POW, NODE_ASSIGN, NODE_EQ, NODE_NEQ, NODE_LT, NODE_GT, NODE_LTEQ, NODE_GTEQ, NODE_AND, NODE_OR, NODE_TERNARY, NODE_DOT, NODE_RANGE, NODE_INCLUSIVERANGE, NODE_OPERATOR, /* ^ All operators should be above this enum value */ NODE_PRINT, NODE_DECLARATION, NODE_TYPE, NODE_FUNCTION, NODE_METHOD, NODE_CLASS, NODE_RETURN, NODE_IF, NODE_THEN, NODE_WHILE, NODE_FOR, NODE_DO, NODE_IN, NODE_BREAK, NODE_CONTINUE, NODE_TRY, NODE_STATEMENT, /* ^ All statements should be above this enum value */ NODE_GROUPING, NODE_SEQUENCE, NODE_DICTIONARY, NODE_DICTENTRY, NODE_INTERPOLATION, NODE_ARGLIST, NODE_SCOPE, NODE_CALL, NODE_INDEX, NODE_LIST, NODE_TUPLE, NODE_IMPORT, NODE_AS, NODE_BREAKPOINT }; /* ------------------------------------------------------- * Macros to check node types * ------------------------------------------------------- */ /** @brief Check if a node type lies between two values */ static inline bool syntaxtree_istype(syntaxtreenodetype type, syntaxtreenodetype lower, syntaxtreenodetype upper) { return ((type > lower) && (type < upper)); } /** Check if a node is a leaf, unary operator, binary operator or a statement */ #define SYNTAXTREE_ISLEAF(x) syntaxtree_istype(x, NODE_BASE, NODE_LEAF) #define SYNTAXTREE_ISUNARY(x) syntaxtree_istype(x, NODE_LEAF, NODE_UNARY) #define SYNTAXTREE_ISOPERATOR(x) syntaxtree_istype(x, NODE_UNARY, NODE_OPERATOR) #define SYNTAXTREE_ISSTATEMENT(x) syntaxtree_istype(x, NODE_OPERATOR, NODE_STATEMENT) /* ------------------------------------------------------- * Interface * ------------------------------------------------------- */ void syntaxtree_init(syntaxtree *tree); void syntaxtree_wipe(syntaxtree *tree); void syntaxtree_clear(syntaxtree *tree); void syntaxtree_print(syntaxtree *tree); bool syntaxtree_addnode(syntaxtree *tree, syntaxtreenodetype type, value content, int line, int posn, syntaxtreeindx left, syntaxtreeindx right, syntaxtreeindx *out); syntaxtreenode *syntaxtree_nodefromindx(syntaxtree *tree, syntaxtreeindx indx); void syntaxtree_flatten(syntaxtree *tree, syntaxtreeindx indx, unsigned int ntypes, syntaxtreenodetype *types, varray_syntaxtreeindx *list); #endif /* syntaxtree_h */ ================================================ FILE: src/datastructures/value.c ================================================ /** @file value.c * @author T J Atherton * * @brief Fundamental data type for morpho */ #include #include "value.h" #include "common.h" /* ********************************************************************** * Comparison of values * ********************************************************************** */ /** @brief Compares where two values are the same, i.e. are identical or refer to the same object. * @details Faster than morpho_comparevalue * @param a value to compare * @param b value to compare * @returns true if a and b are identical, false otherwise */ bool morpho_issame(value a, value b) { #ifdef MORPHO_NAN_BOXING return (a==b); #else if (a.type!=b.type) return false; switch (a.type) { case VALUE_NIL: return true; /** Nils are always the same */ case VALUE_INTEGER: return (b.as.integer == a.as.integer); case VALUE_DOUBLE: /* The sign bit comparison is required to distinguish between -0 and 0. */ return ((b.as.real == a.as.real) && (signbit(b.as.real)==signbit(a.as.real))); case VALUE_BOOL: return (b.as.boolean == a.as.boolean); case VALUE_OBJECT: return MORPHO_GETOBJECT(a) == MORPHO_GETOBJECT(b); default: UNREACHABLE("unhandled value type for comparison [Check morpho_issame]"); } return false; #endif } /** @brief Compare two doubles for equality using both absolute and relative tolerances */ bool morpho_doubleeqtest(double a, double b) { if (a==b) return true; double diff = fabs(a-b); double absa = fabs(a), absb=fabs(b); double absmax = (absa>absb ? absa : absb); return (diff == 0.0) || (absmax > DBL_MIN && diff/absmax <= MORPHO_RELATIVE_EPS); } /** @brief Compares two values * @param a value to compare * @param b value to compare * @returns 0 if a and b are equal, a positive number if b\>a and a negative number if a\aa ? MORPHO_BIGGER : MORPHO_SMALLER); } else { switch (MORPHO_GETTYPE(a)) { case VALUE_NIL: return MORPHO_EQUAL; /** Nones are always the same */ case VALUE_INTEGER: { int aa = MORPHO_GETINTEGERVALUE(a); int bb = MORPHO_GETINTEGERVALUE(b); if (aa==bb) return MORPHO_EQUAL; else return (bb>aa ? MORPHO_BIGGER : MORPHO_SMALLER); } case VALUE_BOOL: return (MORPHO_GETBOOLVALUE(b) != MORPHO_GETBOOLVALUE(a)); case VALUE_OBJECT: if (MORPHO_GETOBJECTTYPE(a)!=MORPHO_GETOBJECTTYPE(b)) { return 1; /* Objects of different type are always different */ } else return object_cmp(MORPHO_GETOBJECT(a), MORPHO_GETOBJECT(b)); default: UNREACHABLE("unhandled value type for comparison [Check morpho_comparevalue]"); } } return MORPHO_NOTEQUAL; } /** @brief Compares two values, even for inequivalent values e.g. int to float * @param a value to compare * @param b value to compare * @returns 0 if a and b are equal, a positive number if b\>a and a negative number if a\0) *max = list[i]; } } return true; } /* ********************************************************************** * Varray_values and utility functions * ********************************************************************** */ DEFINE_VARRAY(value, value); /** @brief Finds a value in an varray using a loose equality test (MORPHO_ISEQUAL) * @param[in] varray the array to search * @param[in] v value to find * @param[out] out index of the match * @returns whether the value was found or not. */ bool varray_valuefind(varray_value *varray, value v, unsigned int *out) { for (unsigned int i=0; icount; i++) { if (MORPHO_ISEQUAL(varray->data[i], v)) { if (out) *out=i; return true; } } return false; } /** @brief Finds a value in an varray using strict equality test (MORPHO_ISSAME) * @param[in] varray the array to search * @param[in] v value to find * @param[out] out index of the match * @returns whether the value was found or not. */ bool varray_valuefindsame(varray_value *varray, value v, unsigned int *out) { for (unsigned int i=0; icount; i++) { if (MORPHO_ISSAME(varray->data[i], v)) { if (out) *out=i; return true; } } return false; } /* ********************************************************************** * Veneer classes * ********************************************************************** */ objectclass *_valueveneers[MORPHO_MAXIMUMVALUETYPES]; /** @brief Sets the veneer class for a particular value type */ void value_setveneerclass(value type, value clss) { if (!MORPHO_ISCLASS(clss)) { UNREACHABLE("Veneer class must be a class."); } if (MORPHO_ISOBJECT(type)) { UNREACHABLE("Cannot define a veneer class for generic objects."); } else if (MORPHO_ISFLOAT(type)) { _valueveneers[0]=MORPHO_GETCLASS(clss); } else { int k = MORPHO_GETORDEREDTYPE(type); _valueveneers[k]=MORPHO_GETCLASS(clss); } } /** @brief Gets the veneer class for a particular value type */ objectclass *value_getveneerclass(value type) { if (MORPHO_ISFLOAT(type)) { return _valueveneers[0]; } else { int k = MORPHO_GETORDEREDTYPE(type); return _valueveneers[k]; } } /** @brief Returns the veneer class given the type index */ objectclass *value_veneerclassfromtype(int type) { if (type #include #include #include "build.h" #include "varray.h" /* Forward declarations of object structures */ typedef struct sobject object; /* ------------------------------------------------------- * Fundamental value type * ------------------------------------------------------- */ /** Values are the basic data type in morpho: each variable declared with 'var' corresponds to one value. Values can contain the following types: VALUE_NIL - nil VALUE_INTEGER - 32 bit integer VALUE_DOUBLE - VALUE_BOOL - boolean type VALUE_OBJECT - pointer to an object The implementation of a value is intentionally opaque and can be NAN boxed into a 64-bit double or left as a struct. This file therefore defines several kinds of macro to: * create values of a given type, e.g. MORPHO_INTEGER. * Test the type of a value, e.g. MORPHO_ISINTEGER * Extract a given type from a value and cast to the relevant C type, e.g. MORPHO_GETINTEGERVALUE */ /** NAN Boxing represents a value as a double, using the values that correspond to NAN to contain the remaining types. */ #ifdef MORPHO_NAN_BOXING /** In this representation, we can extract non-double types from a 64 bit integer */ typedef uint64_t value; /** Define macros that enable us to refer to various bits */ #define SIGN_BIT ((uint64_t) 0x8000000000000000) #define QNAN ((uint64_t) 0x7ffc000000000000) #define LOWER_WORD ((uint64_t) 0x00000000ffffffff) /** Store the type in bits 47-49 */ #define TAG_NIL (1ull<<47) // 001 #define TAG_BOOL (2ull<<47) // 010 #define TAG_INT (3ull<<47) // 011 #define TAG_OBJ SIGN_BIT /** Bool values are stored in the lowest bit */ #define TAG_TRUE 1 #define TAG_FALSE 0 /** Bit mask used to select type bits */ #define TYPE_BITS (TAG_OBJ | TAG_NIL | TAG_BOOL | TAG_INT) /** Map VALUE_XXX macros to type bits */ #define VALUE_NIL (TAG_NIL) #define VALUE_INTEGER (TAG_INT) #define VALUE_DOUBLE () #define VALUE_BOOL (TAG_BOOL) #define VALUE_OBJECT (TAG_OBJ) /** Get the type from a value */ #define MORPHO_GETTYPE(x) ((x) & TYPE_BITS) /** Union to enable conversion of a double to a 64 bit integer */ typedef union { uint64_t bits; double num; } doubleunion; /** Converts a double to a value by type punning */ static inline value doubletovalue(double num) { doubleunion data; data.num = num; return data.bits; } /** Converts a value to a double by type punning */ static inline double valuetodouble(value v) { doubleunion data; data.bits = v; return data.num; } /** Create a literal */ #define MORPHO_NIL ((value) (uint64_t) (QNAN | TAG_NIL)) #define MORPHO_TRUE ((value) (uint64_t) (QNAN | TAG_BOOL | TAG_TRUE)) #define MORPHO_FALSE ((value) (uint64_t) (QNAN | TAG_BOOL | TAG_FALSE)) #define MORPHO_INTEGER(x) ((((uint64_t) (x)) & LOWER_WORD) | QNAN | TAG_INT) #define MORPHO_FLOAT(x) doubletovalue(x) #define MORPHO_BOOL(x) ((x) ? MORPHO_TRUE : MORPHO_FALSE) #define MORPHO_OBJECT(x) ((value) (TAG_OBJ | QNAN | (uint64_t)(uintptr_t)(x))) /** Test for the type of a value */ #define MORPHO_ISNIL(v) ((v) == MORPHO_NIL) #define MORPHO_ISINTEGER(v) (((v) & (QNAN | TYPE_BITS)) == (QNAN | TAG_INT)) #define MORPHO_ISFLOAT(v) (((v) & QNAN) != QNAN) #define MORPHO_ISBOOL(v) (((v) & (QNAN | TYPE_BITS)) == (QNAN | TAG_BOOL)) #define MORPHO_ISOBJECT(v) \ (((v) & (QNAN | TYPE_BITS))== (QNAN | TAG_OBJ)) /** Get a value */ #define MORPHO_GETINTEGERVALUE(v) ((int) ((uint32_t) (v & LOWER_WORD))) #define MORPHO_GETFLOATVALUE(v) valuetodouble(v) #define MORPHO_GETBOOLVALUE(v) ((v) == MORPHO_TRUE) #define MORPHO_GETOBJECT(v) ((object *) (uintptr_t) ((v) & ~(TAG_OBJ | QNAN))) static inline bool morpho_ofsametype(value a, value b) { if (MORPHO_ISFLOAT(a) || MORPHO_ISFLOAT(b)) { return MORPHO_ISFLOAT(a) && MORPHO_ISFLOAT(b); } else { if ((a & TYPE_BITS)==(b & TYPE_BITS)) { return true; } } return false; } /** Get a non-object's type field as an integer */ static inline int _getorderedtype(value x) { return (MORPHO_ISFLOAT(x) ? 0 : (((x) & TYPE_BITS)>>47) & 0x7); } #define MORPHO_GETORDEREDTYPE(x) _getorderedtype(x) /** Alternatively, we represent a value through a struct. */ #else /** @brief A enumerated type defining the different types available in Morpho. */ enum { VALUE_DOUBLE, // Note that the order of these must match the boxed version above VALUE_NIL, VALUE_BOOL, VALUE_INTEGER, VALUE_OBJECT }; typedef int valuetype; /** @brief The unboxed value type. */ typedef struct { valuetype type; union { int integer; double real; bool boolean; struct sobject *obj; } as; } value; /** This macro gets the type of the value. @warning Not intended for broad use. */ #define MORPHO_GETTYPE(v) ((v).type) /** Gets the ordered type of the value @warning Not intended for broad use. */ #define MORPHO_GETORDEREDTYPE(v) ((v).type) /** Test for the type of a value */ #define MORPHO_ISNIL(v) ((v).type==VALUE_NIL) #define MORPHO_ISINTEGER(v) ((v).type==VALUE_INTEGER) #define MORPHO_ISFLOAT(v) ((v).type==VALUE_DOUBLE) #define MORPHO_ISBOOL(v) ((v).type==VALUE_BOOL) #define MORPHO_ISOBJECT(v) ((v).type==VALUE_OBJECT) /** Create a literal */ #define MORPHO_NIL ((value) { VALUE_NIL, .as.integer = (int) 0 }) #define MORPHO_INTEGER(x) ((value) { VALUE_INTEGER, .as.integer = (int) (x) }) #define MORPHO_FLOAT(x) ((value) { VALUE_DOUBLE, .as.real = (double) x }) #define MORPHO_BOOL(x) ((value) { VALUE_BOOL, .as.boolean = (bool) x }) #define MORPHO_OBJECT(x) ((value) { VALUE_OBJECT, .as.obj = (object *) x }) #define MORPHO_TRUE MORPHO_BOOL(true) #define MORPHO_FALSE MORPHO_BOOL(false) /** Get a value */ #define MORPHO_GETINTEGERVALUE(v) ((v).as.integer) #define MORPHO_GETFLOATVALUE(v) ((v).as.real) #define MORPHO_GETBOOLVALUE(v) ((v).as.boolean) #define MORPHO_GETOBJECT(v) ((v).as.obj) static inline bool morpho_ofsametype(value a, value b) { return (a.type == b.type); } #endif /* ------------------------------------------------------- * Comparing values * ------------------------------------------------------- */ /** Check if two values are the same, i.e. identical or refer to the same object */ bool morpho_issame(value a, value b); /** Test if two values are identical, i.e. identical or refer to the same object */ #define MORPHO_ISSAME(a,b) (morpho_issame(a,b)) /** Compare two values, checking contents of objects where supported */ int morpho_comparevalue(value a, value b); /** Compare two values, even if they have inequivalent types e.g. int and float */ int morpho_extendedcomparevalue(value a, value b); /** Macro to test if two values are equal, checking contents of objects where supported */ #define MORPHO_ISEQUAL(a,b) (!morpho_comparevalue(a,b)) /* ------------------------------------------------------- * Type checking and conversion * ------------------------------------------------------- */ /** Detect if a value is a number */ bool morpho_isnumber(value a); /** Define a unified notion of falsity/truthyness */ bool morpho_isfalse(value a); /** Convert a value to an integer */ bool morpho_valuetoint(value v, int *out); /** Convert a value to a float */ bool morpho_valuetofloat(value v, double *out); /** Macro to detect if a value is a number */ #define MORPHO_ISNUMBER(v) (morpho_isnumber(v)) /** Conversion of integer to a float */ #define MORPHO_INTEGERTOFLOAT(x) (MORPHO_FLOAT((double) MORPHO_GETINTEGERVALUE((x)))) /** Conversion of a float to an integer with rounding */ #define MORPHO_FLOATTOINTEGER(x) (MORPHO_INTEGER((int) round(MORPHO_GETFLOATVALUE((x))))) /** Macros to determine if a value is true or false */ #define MORPHO_ISFALSE(x) (morpho_isfalse(x)) #define MORPHO_ISTRUE(x) (!morpho_isfalse(x)) /* ------------------------------------------------------- * Varrays of values * ------------------------------------------------------- */ DECLARE_VARRAY(value, value); bool varray_valuefind(varray_value *varray, value v, unsigned int *out); bool varray_valuefindsame(varray_value *varray, value v, unsigned int *out); /* ------------------------------------------------------- * Other utility functions * ------------------------------------------------------- */ bool value_promotenumberlist(unsigned int nv, value *v); bool value_minmax(unsigned int nval, value *list, value *min, value *max); void value_initialize(void); #endif /* value_h */ ================================================ FILE: src/datastructures/varray.c ================================================ /** @file varray.c * @author T J Atherton * * @brief Dynamically resizing array (varray) data structure */ #include "varray.h" /* ********************************************************************** * Common varray types * ********************************************************************** */ DEFINE_VARRAY(char, char); DEFINE_VARRAY(int, int); DEFINE_VARRAY(double, double); DEFINE_VARRAY(ptrdiff, ptrdiff_t); /** @brief Computes the nearest power of 2 above an integer * @param n An integer * @returns Nearest power of 2 above n See: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float */ unsigned int varray_powerof2ceiling(unsigned int n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n++; return n; } ================================================ FILE: src/datastructures/varray.h ================================================ /** @file varray.h * @author T J Atherton * * @brief Dynamically resizing array (varray) data structure */ #ifndef varray_h #define varray_h #include #include #include #include "memory.h" /* ------------------------------------------------------- * Variable array macros * ------------------------------------------------------- */ /** @brief Creates a generic varray containing a specified type. * * @details Varrays only differ by their contents, and so we use macros to * conveniently define types and functions. * To use these: * First, call DECLARE_VARRAY(NAME,TYPE) in your .h file with a selected * name for your varray and the type of thing you want to store in it. * This will define: * 1. A type called varray_NAME (where NAME is the name you gave). * 2. Functions * varray_NAMEinit(v) - Initializes the varray * varray_NAMEadd(v, data[], count) - Adds elements to the varray * varray_NAMEwrite(v, data) - Writes a single element to the varray, returning the index * varray_NAMEclear(v) - Clears the varray, freeing memory * Then, call DEFINE_VARRAY(NAME,TYPE) in your .c file to define the appropriate functions */ #define DECLARE_VARRAY(name, type) \ typedef struct { \ unsigned int count; \ unsigned int capacity; \ type *data; \ } varray_##name; \ \ void varray_##name##init(varray_##name *v); \ bool varray_##name##add(varray_##name *v, type *data, int count); \ bool varray_##name##resize(varray_##name *v, int count); \ int varray_##name##write(varray_##name *v, type data); \ void varray_##name##clear(varray_##name *v); #define DEFINE_VARRAY(name, type) \ void varray_##name##init(varray_##name *v) { \ v->count = 0; \ v->capacity=0; \ v->data=NULL; \ } \ \ bool varray_##name##add(varray_##name *v, type *data, int count) { \ if (v->capacitycount + count) { \ unsigned int capacity = varray_powerof2ceiling(v->count + count); \ v->data = (type *) morpho_allocate(v->data, v->capacity * sizeof(type), \ capacity * sizeof(type)); \ v->capacity = capacity; \ }; \ \ if (v->data && data) for (unsigned int i = 0; i < count; i++) { \ v->data[v->count++] = data[i]; \ } \ return (v->data!=NULL); \ } \ \ bool varray_##name##resize(varray_##name *v, int count) { \ if (v->capacitycount + count) { \ unsigned int capacity = varray_powerof2ceiling(v->count + count); \ v->data = (type *) morpho_allocate(v->data, v->capacity * sizeof(type), \ capacity * sizeof(type)); \ v->capacity = capacity; \ }; \ return (v->data!=NULL); \ } \ \ int varray_##name##write(varray_##name *v, type data) { \ varray_##name##add(v, &data, 1); \ return v->count-1; \ } \ \ void varray_##name##clear(varray_##name *v) { \ morpho_allocate(v->data, 0, 0); \ varray_##name##init(v); \ } \ bool varray_##name##pop(varray_##name *v, type *dest) { \ if (v->count>0) { \ v->count-=1; \ *dest = v->data[v->count]; \ return true; \ } \ return false; \ } \ /* ------------------------------------------------------- * Common varray types * ------------------------------------------------------- */ DECLARE_VARRAY(char, char); DECLARE_VARRAY(int, int); DECLARE_VARRAY(double, double); DECLARE_VARRAY(ptrdiff, ptrdiff_t); unsigned int varray_powerof2ceiling(unsigned int n); #endif /* varray_h */ ================================================ FILE: src/datastructures/version.c ================================================ /** @file version.c * @author T J Atherton * * @brief Version comparison */ #include #include "build.h" #include "version.h" /** @brief Initialize a version structure * @param[in] v - Version structure to intialize * @param[in] major } * @param[in] minor } version * @param[in] patch } */ void version_init(version *v, int major, int minor, int patch) { v->major=major; v->minor=minor; v->patch=patch; } /** @brief Compare two versions; returns < 0 if a0 if a>b */ int version_cmp(version *a, version *b) { int d[3] = { a->major-b->major, a->minor-b->minor, a->patch-b->patch }; for (int i=0; i<3; i++) if (d[i]!=0) return d[i]; return 0; } /** @brief Test whether a version is compatible with minimum and maximum version constraints * @param[in] v - Version structure to test * @param[in] min - (optional) minimum version number * @param[in] max - (optional) maximum version number * @returns true if min <= v <= max */ bool version_check(version *v, version *min, version *max) { int l = (min ? version_cmp(min, v) : 0); int r = (max ? version_cmp(v, max) : 0); return (l>=0 && r<=0); } /** @brief Convert a version to a string * @param[in] v - Version structure to intialize * @param[in] n - size of output buffer * @param[out] str - output string - recommend creating with VERSION_MAXSTRINGLENGTH */ void version_tostring(version *v, size_t size, char *str) { snprintf(str, size, "%i.%i.%i", v->major, v->minor, v->patch); } /** @brief Sets a version to the current morpho version */ void morpho_version(version *v) { version_init(v, MORPHO_VERSION_MAJOR, MORPHO_VERSION_MINOR, MORPHO_VERSION_PATCH); } ================================================ FILE: src/datastructures/version.h ================================================ /** @file version.h * @author T J Atherton * * @brief Semantic version comparison */ #ifndef version_h #define version_h #include #define VERSION_MAXSTRINGLENGTH 64 typedef struct { int major; // when you make incompatible API changes int minor; // when you add functionality in a backward compatible manner int patch; // when you make backward compatible bug fixes } version; #define VERSION_STATIC(maj, min, ptch) { .major = maj, .minor = min, .patch = ptch } /** @brief Initialize a version structure * @param[in] v - Version structure to intialize * @param[in] major } * @param[in] minor } version * @param[in] patch } */ void version_init(version *v, int major, int minor, int patch); /** @brief Compare two versions; returns < 0 if a0 if a>b */ int version_cmp(version *a, version *b); /** @brief Test whether a version is compatible with minimum and maximum version constraints * @param[in] v - Version structure to test * @param[in] min - (optional) minimum version number * @param[in] max - (optional) maximum version number * @returns true if min <= v <= max */ bool version_check(version *v, version *min, version *max); /** @brief Convert a version to a string * @param[in] v - Version structure to intialize * @param[in] n - size of output buffer * @param[out] str - output string - recommend creating with VERSION_MAXSTRINGLENGTH */ void version_tostring(version *v, size_t n, char *str); /** @brief Sets a version to the current morpho version */ void morpho_version(version *v); #endif ================================================ FILE: src/debug/CMakeLists.txt ================================================ target_sources(morpho PRIVATE debug.c debug.h profile.c profile.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES debug.h profile.h ) ================================================ FILE: src/debug/debug.c ================================================ /** @file debug.c * @author T J Atherton * * @brief Debugging, dissassembly and other tools */ #include #include #include #include "compile.h" #include "vm.h" #include "gc.h" #include "debug.h" #include "morpho.h" #include "strng.h" #include "debugannotation.h" void morpho_runtimeerror(vm *v, errorid id, ...); /* ********************************************************************** * Extract debugging information using annotations * ********************************************************************** */ /** Finds debugging info asssociated with instruction at indx */ bool debug_infofromindx(program *code, instructionindx indx, value *module, int *line, int *posn, objectfunction **func, objectclass **klass) { if (module) *module=MORPHO_NIL; if (func) *func=code->global; if (klass) *klass=NULL; instructionindx i=0; for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &code->annotations.data[j]; switch (ann->type) { case DEBUG_ELEMENT: { if (i+ann->content.element.ninstr>indx) { if (line) *line = ann->content.element.line; if (posn) *posn = ann->content.element.posn; return true; } i+=ann->content.element.ninstr; } break; case DEBUG_FUNCTION: if (func) *func=ann->content.function.function; break; case DEBUG_CLASS: if (klass) *klass=ann->content.klass.klass; break; case DEBUG_MODULE: if (module) *module=ann->content.module.module; break; default: break; } } return false; } /** Finds the instruction indx corresponding to a particular line of code */ bool debug_indxfromline(program *code, value file, int line, instructionindx *out) { instructionindx i=0; value module=MORPHO_NIL; for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &code->annotations.data[j]; switch (ann->type) { case DEBUG_ELEMENT: if (MORPHO_ISEQUAL(file, module) && ann->content.element.line==line) { *out=i; return true; } i+=ann->content.element.ninstr; break; case DEBUG_MODULE: module=ann->content.module.module; break; default: break; } } return false; } /** Finds the instruction index corresponding to the entry point of a function or method */ bool debug_indxfromfunction(program *code, value klassname, value fname, instructionindx *indx) { objectclass *cklass=NULL; objectfunction *cfunc=NULL; for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &code->annotations.data[j]; switch (ann->type) { case DEBUG_FUNCTION: cfunc=ann->content.function.function; if (MORPHO_ISEQUAL(cfunc->name, fname) && (MORPHO_ISNIL(klassname) || MORPHO_ISEQUAL(cklass->name, klassname))) { *indx=cfunc->entry; return true; } break; case DEBUG_CLASS: cklass=ann->content.klass.klass; break; default: break; } } return false; } /** Identifies symbols associated with registers * @param[in] code - a program * @param[in] func - the function of interest * @param[in] indx - (optional) instruction to stop at * @param[out] symbols - array of size func->negs; entries will contain associated register names on exit */ bool debug_symbolsforfunction(program *code, objectfunction *func, instructionindx *indx, value *symbols) { objectfunction *cfunc=code->global; instructionindx i=0; for (unsigned int j=0; jnregs; j++) symbols[j]=MORPHO_NIL; for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &code->annotations.data[j]; switch (ann->type) { case DEBUG_ELEMENT: { if (indx && i+ann->content.element.ninstr>*indx) return true; i+=ann->content.element.ninstr; } break; case DEBUG_FUNCTION: cfunc=ann->content.function.function; break; case DEBUG_REGISTER: { if (cfunc==func) { symbols[ann->content.reg.reg]=ann->content.reg.symbol; } } break; default: break; } } return true; } /** Attempts to find a symbol. * @param[in] v - virtual machine to search * @param[in] matchstr - string to match * @param[out] frame - callframe matched * @param[out] symbol - actual symbol found * @param[out] val - value of the found symbol * @returns true on success */ bool debug_findsymbol(vm *v, value matchstr, callframe **frame, value *symbol, value **val) { /* Check back through callframes */ for (callframe *f=v->fp; f>=v->frame; f--) { value symbols[f->function->nregs]; instructionindx indx = f->pc-v->current->code.data; debug_symbolsforfunction(v->current, f->function, &indx, symbols); for (int i=0; ifunction->nregs; i++) { if (!MORPHO_ISNIL(symbols[i]) && MORPHO_ISEQUAL(symbols[i], matchstr)) { if (frame) *frame = f; if (symbol) *symbol = symbols[i]; if (val) *val = &v->stack.data[f->roffset+i]; return true; } } } /* Otherwise is it a global? */ for (unsigned int j=0; jcurrent->annotations.count; j++) { debugannotation *ann = &v->current->annotations.data[j]; if (ann->type==DEBUG_GLOBAL && MORPHO_ISEQUAL(ann->content.global.symbol, matchstr)) { if (frame) *frame = v->frame; if (symbol) *symbol = ann->content.global.symbol; if (val) *val = &v->globals.data[ann->content.global.gindx]; return true; } } return false; } /** Identifies a symbol for a given global number * @param[in] p - program to search * @param[in] id - global number to find * @param[out] frame - callframe matched*/ bool debug_symbolforglobal(program *p, indx id, value *symbol) { for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &p->annotations.data[j]; if (ann->type==DEBUG_GLOBAL && ann->content.global.gindx==id) { *symbol = ann->content.global.symbol; return true; } } return false; } /* ********************************************************************** * Stack traces * ********************************************************************** */ /** Prints a stacktrace */ void morpho_stacktrace(vm *v) { for (callframe *f = (v->errfp ? v->errfp : v->fp); f!=NULL && f>=v->frame; f--) { instructionindx indx = f->pc-v->current->code.data; if (indx>0) indx--; /* Because the pc always points to the NEXT instr. */ morpho_printf(v, " "); morpho_printf(v, "%s", (f==v->fp ? " in " : "from ")); if (!MORPHO_ISNIL(f->function->name)) morpho_printvalue(v, f->function->name); else morpho_printf(v, "global"); int line=0; value module = MORPHO_NIL; if (debug_infofromindx(v->current, indx, &module, &line, NULL, NULL, NULL)) { morpho_printf(v, " at line %u", line); if (!MORPHO_ISNIL(module)) { morpho_printf(v, " in module '"); morpho_printvalue(v, module); morpho_printf(v, "'"); } } morpho_printf(v, "\n"); } } /* ********************************************************************** * Debugger data structure * ********************************************************************** */ /** Initializes a debugger structure with a specified program */ void debugger_init(debugger *d, program *p) { d->singlestep=false; d->nbreakpoints=0; d->err=NULL; d->currentfunc=NULL; d->currentline=0; d->currentmodule=MORPHO_NIL; varray_charinit(&d->breakpoints); int ninstructions = p->code.count; if (!varray_charresize(&d->breakpoints, ninstructions)) return; memset(d->breakpoints.data, '\0', sizeof(char)*ninstructions); d->breakpoints.count=ninstructions; } /** Clears a debugger structure */ void debugger_clear(debugger *d) { varray_charclear(&d->breakpoints); } /** Returns the current VM */ vm *debugger_currentvm(debugger *d) { return d->currentvm; } /** Returns the current program */ program *debugger_currentprogram(debugger *d) { return d->currentvm->current; } /** Sets the error structure that the debugger will report errors to */ void debugger_seterror(debugger *d, error *err) { d->err=err; } /** @brief Raises a debugger error * @param debug the debugger * @param id error id * @param ... additional data for sprintf. */ void debugger_error(debugger *debug, errorid id, ... ) { if (!debug->err || debug->err->id!=ERROR_NONE) return; // Ensure errors are not overwritten. va_list args; va_start(args, id); morpho_writeerrorwithidvalist(debug->err, id, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE, args); va_end(args); } /** Sets whether single step mode is in operation */ void debugger_setsinglestep(debugger *d, bool singlestep) { d->singlestep=singlestep; } /** Are we in singlestep mode? */ bool debugger_insinglestep(debugger *d) { return d->singlestep; } /** Sets a breakpoint */ bool debugger_setbreakpoint(debugger *d, instructionindx indx) { if (indx>d->breakpoints.count) return false; d->breakpoints.data[indx]='b'; d->nbreakpoints++; return true; } /** Clears a breakpoint */ bool debugger_clearbreakpoint(debugger *d, instructionindx indx) { if (indx>d->breakpoints.count) return false; d->breakpoints.data[indx]='\0'; d->nbreakpoints--; return true; } /** Tests if we should break at a given point */ bool debugger_shouldbreakat(debugger *d, instructionindx indx) { if (indx>d->breakpoints.count) return false; return (d->breakpoints.data[indx]!='\0'); } /** Tests if the debugger is in a mode that could cause breaks at arbitrary instructions */ bool debugger_isactive(debugger *d) { return (d->singlestep || (d->nbreakpoints>0)); } /* ********************************************************************** * Disassembler * ********************************************************************** */ /** Formatting rules for disassembler */ typedef struct { instruction op; /** Opcode */ char *label; /** Label to display in disasembler */ char *display; /** Display code - rX is register, cX is constant X, gX is global X, uX is upvalue, + refers to signed B */ } assemblyrule; /** Define disassembler by how to display each opcode */ assemblyrule assemblyrules[] ={ { OP_NOP, "nop", "" }, { OP_MOV, "mov", "rA, rB" }, { OP_LCT, "lct", "rA, cX" }, { OP_ADD, "add", "rA, rB, rC" }, { OP_SUB, "sub", "rA, rB, rC" }, { OP_MUL, "mul", "rA, rB, rC" }, { OP_DIV, "div", "rA, rB, rC" }, { OP_POW, "pow", "rA, rB, rC" }, { OP_NOT, "not", "rA, rB" }, { OP_EQ, "eq ", "rA, rB, rC" }, { OP_NEQ, "neq", "rA, rB, rC" }, { OP_LT, "lt ", "rA, rB, rC" }, { OP_LE, "le ", "rA, rB, rC" }, { OP_PRINT, "print", "rA" }, { OP_B, "b", "+" }, { OP_BIF, "bif", "rA +" }, { OP_BIFF, "biff", "rA +" }, { OP_CALL, "call", "rA, B, C" }, // b, c literal { OP_INVOKE, "invoke", "rA, B, C" }, // b, c literal { OP_METHOD, "method", "rA, B, C" }, // b, c literal { OP_RETURN, "return", "?rB" }, // Return register B is A nonzero { OP_CLOSURE, "closure", "rA, pB" }, // b prototype { OP_LUP, "lup", "rA, uB" }, // b 'u' { OP_SUP, "sup", "uA, rB" }, // a 'u', b c|r { OP_CLOSEUP, "closeup", "rA" }, { OP_LPR, "lpr", "rA, rB, rC" }, { OP_SPR, "spr", "rA, rB, rC" }, { OP_LIX, "lix", "rA, rB, rC" }, { OP_LIXL, "lixl", "rA, rB, rC" }, { OP_SIX, "six", "rA, rB, rC" }, { OP_LGL, "lgl", "rA, gX" }, // { OP_SGL, "sgl", "rA, gX" }, // label b with 'g' { OP_PUSHERR, "pusherr", "cX" }, { OP_POPERR, "poperr", "+" }, { OP_CAT, "cat", "rA, rB, rC" }, { OP_BREAK, "break", "" }, { OP_END, "end", "" }, { 0, NULL, "" } // Null terminate the list }; assemblyrule *debugger_getassemblyrule(unsigned int op) { for (unsigned int i=0; assemblyrules[i].label!=NULL; i++) if (assemblyrules[i].op==op) return &assemblyrules[i]; return NULL; } typedef enum { DBG_CONTENTS_NONE, DBG_CONTENTS_REG, DBG_CONTENTS_CONST} debugcontents; /** Shows the contents of a register or constant */ bool debugger_showcontents(vm *v, debugcontents b, int i, value *konst, value *reg) { value *table = NULL; switch (b) { case DBG_CONTENTS_CONST: table=konst; break; case DBG_CONTENTS_REG: table = reg; break; default: break; } if (!table) return false; morpho_printf(v, "%s%i=", (b==DBG_CONTENTS_CONST ? "c" : "r"), i); morpho_printvalue(v, table[i]); return true; } /** @brief Disassembles a single instruction, writing the output to the console. * @param v VM to use for output * @param instruction The instruction to disassemble * @param indx Instruction index to display * @param konst current constant table * @param reg current registers */ void debugger_disassembleinstruction(vm *v, instruction instruction, instructionindx indx, value *konst, value *reg) { unsigned int op = DECODE_OP(instruction); debugcontents mode=DBG_CONTENTS_NONE, bm=DBG_CONTENTS_NONE, cm=DBG_CONTENTS_NONE; int nb=0, nc=0; morpho_printf(v, "%4lu : ", indx); int n=0; // Number of characters displayed int width=25; // Width of display assemblyrule *show=debugger_getassemblyrule(op); if (show) { n+=morpho_printf(v, "%s ", show->label); for (char *c=show->display; *c!='\0'; c++) { switch (*c) { case 'A': n+=morpho_printf(v, "%u", DECODE_A(instruction)); break; case 'B': { bm=mode; nb=DECODE_B(instruction); mode=DBG_CONTENTS_NONE; n+=morpho_printf(v, "%u", nb); } break; case 'X': { bm=mode; nb=DECODE_Bx(instruction); mode=DBG_CONTENTS_NONE; n+=morpho_printf(v, "%u", nb); } break; case '+': n+=morpho_printf(v, "%i", DECODE_sBx(instruction)); break; case 'C': { cm=mode; nc=DECODE_C(instruction); n+=morpho_printf(v, "%u", DECODE_C(instruction)); } break; case 'c': mode=DBG_CONTENTS_CONST; n+=morpho_printf(v, "%c", *c); break; case 'r': mode=DBG_CONTENTS_REG; n+=morpho_printf(v, "%c", *c); break; case '?': if (DECODE_A(instruction)==0) return; break; default: n+=morpho_printf(v, "%c", *c); break; } } /* Show contents if any were produced by this instruction */ if ((!konst && !reg) || (bm==DBG_CONTENTS_NONE && cm==DBG_CONTENTS_NONE)) return; for (int k=width-n; k>0; k--) morpho_printf(v, " "); morpho_printf(v, "; "); if (debugger_showcontents(v, bm, nb, konst, reg)) morpho_printf(v, " "); debugger_showcontents(v, cm, nc, konst, reg); } } /** Checks if an instruction matches a label in the current error dictionary, and if so print it. */ void debugger_errorlabel(vm *v, varray_value *errorstack, instructionindx i) { objectdictionary *dict = MORPHO_GETDICTIONARY(errorstack->data[errorstack->count-1]); /* Search the current error handler to see if this line corresponds to a label */ for (unsigned int k=0; kdict.capacity; k++) { value label = dict->dict.contents[k].key; if (!MORPHO_ISNIL(label)) { if (MORPHO_GETINTEGERVALUE(dict->dict.contents[k].val)==i) { morpho_printvalue(v, label); morpho_printf(v, ":\n"); } } } } /** Disassembles a program * @param v - vm to use for output * @param code - program to disassemble * @param matchline - optional line number to match */ void debugger_disassemble(vm *v, program *code, int *matchline) { instructionindx entry = program_getentry(code); // The entry point of the function instructionindx i=0; value *konst=(code->global ? code->global->konst.data : NULL); bool silent = matchline; varray_value errorstack; varray_valueinit(&errorstack); /* Loop over debugging information */ for (unsigned int j=0; jannotations.count; j++) { debugannotation *ann = &code->annotations.data[j]; switch(ann->type) { case DEBUG_ELEMENT: { if (matchline) { if (ann->content.element.line<(*matchline)) { i+=ann->content.element.ninstr; break; } if (ann->content.element.line>(*matchline)) return; } else if (errorstack.count>0) { debugger_errorlabel(v, &errorstack, i); } for (unsigned int k=0; kcontent.element.ninstr; k++, i++) { morpho_printf(v, "%s", (i==entry ? "->" : " ")); debugger_disassembleinstruction(v, code->code.data[i], i, konst, NULL); morpho_printf(v, "\n"); } } break; case DEBUG_FUNCTION: { objectfunction *func=ann->content.function.function; konst=func->konst.data; if (silent) break; if (!MORPHO_ISNIL(func->name)) { morpho_printf(v, "fn "); morpho_printvalue(v, func->name); morpho_printf(v, ":\n"); } else morpho_printf(v, "\n"); } break; case DEBUG_CLASS: { objectclass *klass=ann->content.klass.klass; if (silent) break; if (klass && !MORPHO_ISNIL(klass->name)) { morpho_printf(v, "class "); morpho_printvalue(v, klass->name); morpho_printf(v, ":\n"); } } break; case DEBUG_PUSHERR: { objectdictionary *errdict = ann->content.errorhandler.handler; varray_valuewrite(&errorstack, MORPHO_OBJECT(errdict)); } break; case DEBUG_POPERR: { if (errorstack.count>0) errorstack.count--; } break; default: break; } } varray_valueclear(&errorstack); } /** Public interface to disassembler */ void morpho_disassemble(vm *v, program *code, int *matchline) { debugger_disassemble(NULL, code, matchline); } /* ********************************************************************** * General debugger commands * ********************************************************************** */ void debugger_garbagecollect(debugger *debug) { size_t init = debug->currentvm->bound; vm_collectgarbage(debug->currentvm); morpho_printf(NULL, "Collected %ld bytes (from %zu to %zu). Next collection at %zu bytes.\n", init-debug->currentvm->bound, init, debug->currentvm->bound, debug->currentvm->nextgc); } void debugger_quit(debugger *debug) { morpho_runtimeerror(debug->currentvm, VM_DBGQUIT); } /* ********************************************************************** * Breakpoints * ********************************************************************** */ bool debugger_breakatinstruction(debugger *debug, bool set, instructionindx indx) { bool success=false; if (set) success=debugger_setbreakpoint(debug, indx); else success=debugger_clearbreakpoint(debug, indx); if (!success) debugger_error(debug, DEBUGGER_INVLDINSTR); return success; } /** Break at a particular line */ bool debugger_breakatline(debugger *debug, bool set, value file, int line) { instructionindx indx; return (debug_indxfromline(debugger_currentprogram(debug), file, line, &indx) && debugger_breakatinstruction(debug, set, indx)); } /** Break at a function or method */ bool debugger_breakatfunction(debugger *debug, bool set, value klass, value function) { instructionindx indx; return (debug_indxfromfunction(debugger_currentprogram(debug), klass, function, &indx) && debugger_breakatinstruction(debug, set, indx)); } /* ********************************************************************** * Show commands * ********************************************************************** */ /** Prints the location information for a given instruction */ void debugger_showlocation(debugger *debug, instructionindx indx) { vm *v = debugger_currentvm(debug); value module=MORPHO_NIL; // Find location information int line=0; objectfunction *fn=NULL; objectclass *klass=NULL; debug_infofromindx(debugger_currentprogram(debug), indx, &module, &line, NULL, &fn, &klass); morpho_printf(v, "in "); if (klass) { morpho_printvalue(v, klass->name); morpho_printf(v, "."); } if (!MORPHO_ISNIL(fn->name)) morpho_printvalue(v, fn->name); else if (v->current->global==fn) morpho_printf(v, "global"); else morpho_printf(v, "anonymous fn"); if (!MORPHO_ISNIL(module)) { morpho_printf(v, " in '"); morpho_printvalue(v, module); morpho_printf(v, "'"); } morpho_printf(v, " at line %i [instruction %ti]", line, indx); } /** Shows the address of an object */ bool debugger_showaddress(debugger *debug, indx rindx) { vm *v = debugger_currentvm(debug); bool success=false; if (rindx>=0 && rindxfp->function->nregs) { value *reg = v->stack.data + debug->currentvm->fp->roffset; if (MORPHO_ISOBJECT(reg[rindx])) { morpho_printf(v, "Object in register %i at %p.\n", (int) rindx, (void *) MORPHO_GETOBJECT(reg[rindx])); success=true; } else debugger_error(debug, DEBUGGER_REGISTEROBJ, (int) rindx); } else debugger_error(debug, DEBUGGER_INVLDREGISTER); return success; } /** Shows active breakpoints */ void debugger_showbreakpoints(debugger *debug) { vm *v = debugger_currentvm(debug); morpho_printf(v, "Active breakpoints:\n"); for (instructionindx i=0; ibreakpoints.count; i++) { if (debug->breakpoints.data[i]!='\0') { morpho_printf(v, " Breakpoint "); debugger_showlocation(debug, i); morpho_printf(v, "\n"); } else if (DECODE_OP(debugger_currentprogram(debug)->code.data[i])==OP_BREAK) { morpho_printf(v, " Break "); debugger_showlocation(debug, i); morpho_printf(v, "\n"); } } } /** List a particular global */ bool debugger_showglobal(debugger *debug, indx id) { bool success=false; vm *v = debugger_currentvm(debug); if (id>=0 && idglobals.count) { value symbol; morpho_printf(v, " g%lu:", id); morpho_printvalue(v, v->globals.data[id]); if (debug_symbolforglobal(v->current, id, &symbol)) { morpho_printf(v, " ("); morpho_printvalue(v, symbol); morpho_printf(v, ")"); } morpho_printf(v, "\n"); success=true; } else debugger_error(debug, DEBUGGER_INVLDGLOBAL); return success; } /** Show all globals */ void debugger_showglobals(debugger *debug) { vm *v = debugger_currentvm(debug); morpho_printf(v, "Globals:\n"); for (indx i=0; iglobals.count; i++) debugger_showglobal(debug, i); } /** Show the contents of all registers */ void debugger_showregisters(debugger *debug) { vm *v = debugger_currentvm(debug); callframe *frame = v->fp; unsigned int nreg=frame->function->nregs; value symbols[nreg]; instructionindx cinstr=frame->pc-v->current->code.data; bool sym = debug_symbolsforfunction(v->current, frame->function, &cinstr, symbols); morpho_printf(v, "Register contents:\n"); value *reg = v->stack.data + frame->roffset; for (unsigned int i=0; iframe; f!=v->fp; f++) { fbounds[k]=f->roffset; k++; } fbounds[k]=f->roffset; f=v->frame; k=0; morpho_printf(v, "Stack contents:\n"); for (unsigned int i=0; ifp->roffset+v->fp->function->nregs; i++) { if (i==fbounds[k]) { morpho_printf(v, "---"); if (f->function) morpho_printvalue(v, f->function->name); morpho_printf(v, "\n"); k++; f++; } morpho_printf(v, " s%u: ", i); morpho_printvalue(v, v->stack.data[i]); morpho_printf(v, "\n"); } } /** Show the current value of a symbol */ bool debugger_showsymbol(debugger *debug, value match) { vm *v = debugger_currentvm(debug); value symbol, *val=NULL; if (debug_findsymbol(v, match, NULL, &symbol, &val)) { morpho_printvalue(v, symbol); morpho_printf(v, " = "); morpho_printvalue(v, *val); morpho_printf(v, "\n"); } else debugger_error(debug, DEBUGGER_FINDSYMBOL, MORPHO_GETCSTRING(match)); return val; } /** Shows all symbols currently in view */ void debugger_showsymbols(debugger *debug) { vm *v = debugger_currentvm(debug); for (callframe *f=v->fp; f>=v->frame; f--) { morpho_printf(v, "in %s", (f==v->frame ? "global" : "")); if (!MORPHO_ISNIL(f->function->name)) morpho_printvalue(v, f->function->name); morpho_printf(v, ":\n"); value symbols[f->function->nregs]; instructionindx indx = f->pc-v->current->code.data; debug_symbolsforfunction(v->current, f->function, &indx, symbols); for (int i=0; ifunction->nregs; i++) { if (!MORPHO_ISNIL(symbols[i])) { morpho_printf(v, " "); morpho_printvalue(v, symbols[i]); morpho_printf(v, "="); morpho_printvalue(v, v->stack.data[f->roffset+i]); morpho_printf(v, "\n"); } } } } /** Show the current value of a property */ bool debugger_showproperty(debugger *debug, value matchobj, value matchproperty) { bool success=false; vm *v = debugger_currentvm(debug); callframe *frame; value symbol, *instance=NULL, val; if (debug_findsymbol(v, matchobj, &frame, &symbol, &instance)) { if (MORPHO_ISINSTANCE(*instance)) { objectinstance *obj = MORPHO_GETINSTANCE(*instance); if (objectinstance_getproperty(obj, matchproperty, &val)) { morpho_printvalue(v, symbol); morpho_printf(v, "."); morpho_printvalue(v, matchproperty); morpho_printf(v, " = "); morpho_printvalue(v, val); morpho_printf(v, "\n"); success=true; } else debugger_error(debug, DEBUGGER_SYMBOLPROP, MORPHO_GETCSTRING(matchproperty)); } } return success; } /* ********************************************************************** * Set commands * ********************************************************************** */ /** Sets the contents of a register to a given value */ bool debugger_setregister(debugger *debug, indx reg, value val) { vm *v=debugger_currentvm(debug); bool success=false; if (reg>=0 && regfp->function->nregs) { v->stack.data[v->fp->roffset+reg]=val; success=true; } return success; } /** Sets a symbol to a given value */ bool debugger_setsymbol(debugger *debug, value symbol, value val) { value *dest=NULL; if (debug_findsymbol(debugger_currentvm(debug), symbol, NULL, NULL, &dest)) { *dest=val; } else debugger_error(debug, DEBUGGER_FINDSYMBOL, MORPHO_GETCSTRING(symbol)); return (dest!=NULL); } /** Sets a property to a given value */ bool debugger_setproperty(debugger *debug, value symbol, value property, value val) { value *dest=NULL; bool success=false; if (debug_findsymbol(debugger_currentvm(debug), symbol, NULL, NULL, &dest)) { if (MORPHO_ISINSTANCE(*dest)) { objectinstance *obj = MORPHO_GETINSTANCE(*dest); value key = dictionary_intern(&obj->fields, property); success=objectinstance_setproperty(obj, key, val); } else debugger_error(debug, DEBUGGER_SETPROPERTY); } else debugger_error(debug, DEBUGGER_FINDSYMBOL, MORPHO_GETCSTRING(symbol)); return success; } /* ********************************************************************** * Enter the debugger (called by the VM) * ********************************************************************** */ /** Enters the debugger, if one is active. */ bool debugger_enter(debugger *debug, vm *v) { if (debug && v->debuggerfn) { debug->currentvm = v; // Get instruction index debug->iindx = vm_currentinstruction(v); // Retain previous line and function information int oline=debug->currentline; objectfunction *ofunc=debug->currentfunc; // Fetch info from annotations debug_infofromindx(debugger_currentprogram(debug), debug->iindx, &debug->currentmodule, &debug->currentline, NULL, &debug->currentfunc, NULL); // If we're in single step mode, only stop when we've changed line OR if a breakpoint is explicitly set if (debugger_insinglestep(debug) && oline==debug->currentline && ofunc==debug->currentfunc && !debugger_shouldbreakat(debug, debug->iindx)) return false; (v->debuggerfn) (v, v->debuggerref); } return debug; } /* ********************************************************************** * Run a program with debugging active * ********************************************************************** */ /** Run a program with debugging * @param[in] v - the virtual machine to use * @param[in] p - program to run * @returns true on success, false otherwise */ bool morpho_debug(vm *v, program *p) { debugger debug; debugger_init(&debug, p); v->debug=&debug; bool success=morpho_run(v, p); debugger_clear(&debug); return success; } /* ********************************************************************** * Initialization * ********************************************************************** */ /** Intialize the debugger library */ void debugger_initialize(void) { morpho_defineerror(DEBUGGER_FINDSYMBOL, ERROR_DEBUGGER, DEBUGGER_FINDSYMBOL_MSG); morpho_defineerror(DEBUGGER_SETPROPERTY, ERROR_DEBUGGER, DEBUGGER_SETPROPERTY_MSG); morpho_defineerror(DEBUGGER_INVLDREGISTER, ERROR_DEBUGGER, DEBUGGER_INVLDREGISTER_MSG); morpho_defineerror(DEBUGGER_INVLDGLOBAL, ERROR_DEBUGGER, DEBUGGER_INVLDGLOBAL_MSG); morpho_defineerror(DEBUGGER_INVLDINSTR, ERROR_DEBUGGER, DEBUGGER_INVLDINSTR_MSG); morpho_defineerror(DEBUGGER_REGISTEROBJ, ERROR_DEBUGGER, DEBUGGER_REGISTEROBJ_MSG); morpho_defineerror(DEBUGGER_SYMBOLPROP, ERROR_DEBUGGER, DEBUGGER_SYMBOLPROP_MSG); } ================================================ FILE: src/debug/debug.h ================================================ /** @file debug.h * @author T J Atherton * * @brief Debugger, dissassembly and other tools */ #ifndef debug_h #define debug_h #include #include "syntaxtree.h" #include "object.h" #include "program.h" #include "debugannotation.h" #define DEBUG_ISSINGLESTEP(d) ((d) && (d->singlestep)) /* ------------------------------------------------------- * Debugger error messages * ------------------------------------------------------- */ #define DEBUGGER_FINDSYMBOL "DbgSymbl" #define DEBUGGER_FINDSYMBOL_MSG "Can't find symbol '%s' in current context." #define DEBUGGER_SETPROPERTY "DbgStPrp" #define DEBUGGER_SETPROPERTY_MSG "Object does not support setting properties." #define DEBUGGER_INVLDREGISTER "DbgInvldRg" #define DEBUGGER_INVLDREGISTER_MSG "Invalid register." #define DEBUGGER_INVLDGLOBAL "DbgInvldGlbl" #define DEBUGGER_INVLDGLOBAL_MSG "Invalid global." #define DEBUGGER_INVLDINSTR "DbgInvldInstr" #define DEBUGGER_INVLDINSTR_MSG "Invalid instruction." #define DEBUGGER_REGISTEROBJ "DbgRgObj" #define DEBUGGER_REGISTEROBJ_MSG "Register %i does not contain an object." #define DEBUGGER_SYMBOLPROP "DbgSymblPrpty" #define DEBUGGER_SYMBOLPROP_MSG "Symbol lacks property '%s'." /* ------------------------------------------------------- * Debugger interface * ------------------------------------------------------- */ bool debug_infofromindx(program *code, instructionindx indx, value *module, int *line, int *posn, objectfunction **func, objectclass **klass); void debugger_init(debugger *d, program *p); void debugger_clear(debugger *d); void debugger_seterror(debugger *d, error *err); vm *debugger_currentvm(debugger *d); bool debugger_isactive(debugger *d); void debugger_setsinglestep(debugger *d, bool singlestep); bool debugger_insinglestep(debugger *d); bool debugger_setbreakpoint(debugger *d, instructionindx indx); bool debugger_clearbreakpoint(debugger *d, instructionindx indx); bool debugger_shouldbreakat(debugger *d, instructionindx indx); void debugger_disassembleinstruction(vm *v, instruction instruction, instructionindx indx, value *konst, value *reg); void debugger_disassemble(vm *v, program *code, int *matchline); void debugger_garbagecollect(debugger *debug); void debugger_quit(debugger *debug); bool debugger_breakatinstruction(debugger *debug, bool set, instructionindx indx); bool debugger_breakatline(debugger *debug, bool set, value file, int line); bool debugger_breakatfunction(debugger *debug, bool set, value klass, value function); void debugger_showlocation(debugger *debug, instructionindx indx); bool debugger_showaddress(debugger *debug, indx rindx); void debugger_showbreakpoints(debugger *debug); void debugger_showglobals(debugger *debug); bool debugger_showglobal(debugger *debug, indx g); void debugger_showregisters(debugger *debug); void debugger_showstack(debugger *debug); bool debugger_showsymbol(debugger *debug, value symbol); bool debugger_showproperty(debugger *debug, value obj, value property); void debugger_showsymbols(debugger *debug); bool debugger_setregister(debugger *debug, indx reg, value val); bool debugger_setsymbol(debugger *debug, value symbol, value val); bool debugger_setproperty(debugger *debug, value symbol, value property, value val); bool debugger_enter(debugger *debug, vm *v); void debugger_initialize(void); #endif /* debug_h */ ================================================ FILE: src/debug/profile.c ================================================ /** @file profile.c * @author T J Atherton * * @brief Profiler */ #include "profile.h" #include "strng.h" #include "platform.h" /* ********************************************************************** * Profiler * ********************************************************************** */ #ifdef MORPHO_PROFILER #define PROFILER_NVALUES 2 #define PROFILER_VALUEPOSN 1 #define PROFILER_SAMPLINGINTERVAL (0.0001*CLOCKS_PER_SEC) #define PROFILER_GLOBAL "(global)" #define PROFILER_ANON "(anonymous)" #define PROFILER_GC "(garbage collector)" /** Record a sample */ void profiler_sample(profiler *profile, value func) { value v=MORPHO_INTEGER(1); if (dictionary_get(&profile->profile_dict, func, &v)) { v=MORPHO_INTEGER(MORPHO_GETINTEGERVALUE(v)+1); } dictionary_insert(&profile->profile_dict, func, v); } /** Profiler monitor thread */ MorphoThreadFnReturnType profiler_thread(void *arg) { vm *v = (vm *) arg; profiler *profile = v->profiler; if (!v->profiler) MorphoThread_exit(); clock_t last = clock(); clock_t time = last; while (true) { MorphoMutex_lock(&profile->profile_lock); bool quit=profile->profiler_quit; MorphoMutex_unlock(&profile->profile_lock); if (quit) MorphoThread_exit(); while (time-lastfp->inbuiltinfunction; if (v->status==VM_INGC) { profiler_sample(profile, MORPHO_INTEGER(1)); } else if (infunction) { profiler_sample(profile, MORPHO_OBJECT(infunction)); } else { profiler_sample(profile, MORPHO_OBJECT(v->fp->function)); } } } /** Initialize profiler data structure */ void profiler_init(profiler *profile, program *p) { dictionary_init(&profile->profile_dict); MorphoMutex_init(&profile->profile_lock); profile->profiler_quit=false; profile->program=p; } /** Clear profiler data structure */ void profiler_clear(profiler *profile) { MorphoMutex_clear(&profile->profile_lock); dictionary_clear(&profile->profile_dict); } /** Kills a profiler thread */ void profiler_kill(profiler *profile) { MorphoMutex_lock(&profile->profile_lock); profile->profiler_quit=true; MorphoMutex_unlock(&profile->profile_lock); MorphoThread_join(profile->profiler); MorphoThread_clear(profile->profiler); } /** Sorting function */ int profiler_sort(const void *a, const void *b) { value *aa = (value *) a; value *bb = (value *) b; return morpho_comparevalue(aa[PROFILER_VALUEPOSN], bb[PROFILER_VALUEPOSN]); } /** Returns the function name */ bool profiler_getname(value func, value *name, value *klass) { bool success=false; objectclass *k = NULL; if (MORPHO_ISINTEGER(func)) { *name = MORPHO_NIL; // In the Garbage collector success=true; } else if (MORPHO_ISBUILTINFUNCTION(func)) { *name = MORPHO_GETBUILTINFUNCTION(func)->name; k=MORPHO_GETBUILTINFUNCTION(func)->klass; success=true; } else if (MORPHO_ISFUNCTION(func)) { *name = MORPHO_GETFUNCTION(func)->name; k=MORPHO_GETFUNCTION(func)->klass; success=true; } *klass = (k ? k->name : MORPHO_NIL); return success; } /** Calculates the length to display */ size_t profiler_calculatelength(profiler *p, value func) { value name, klass; size_t length=0; if (profiler_getname(func, &name, &klass)) { if (MORPHO_ISSTRING(name)) { length+=MORPHO_GETSTRINGLENGTH(name); } else if (MORPHO_ISINTEGER(func)) { length+=strlen(PROFILER_GC); } else if (MORPHO_ISNIL(name)) { if (MORPHO_ISSAME(func, MORPHO_OBJECT(p->program->global))) length+=strlen(PROFILER_GLOBAL); else length+=strlen(PROFILER_ANON); } if (MORPHO_ISSTRING(klass)) length+=MORPHO_GETSTRINGLENGTH(klass)+1; // extra length for the '.' } return length; } /** Display the function name and its sampling count */ void profiler_display(profiler *p, value func, vm *v) { value name, klass; if (profiler_getname(func, &name, &klass)) { if (MORPHO_ISSTRING(klass)) { morpho_printvalue(v, klass); morpho_printf(v, "."); } if (MORPHO_ISSTRING(name)) { morpho_printvalue(v, name); } else if (MORPHO_ISINTEGER(func)) { morpho_printf(v, PROFILER_GC); } else if (MORPHO_ISNIL(name)) { if (MORPHO_ISSAME(func, MORPHO_OBJECT(p->program->global))) morpho_printf(v, PROFILER_GLOBAL); else morpho_printf(v, PROFILER_ANON); } } } /** Report the outcome of profiling */ void profiler_report(profiler *profile, vm *v) { varray_value samples; varray_valueinit(&samples); for (unsigned int i=0; iprofile_dict.capacity; i++) { if (!MORPHO_ISNIL(profile->profile_dict.contents[i].key)) { varray_valuewrite(&samples, profile->profile_dict.contents[i].key); varray_valuewrite(&samples, profile->profile_dict.contents[i].val); } } qsort(samples.data, samples.count/PROFILER_NVALUES, sizeof(value)*PROFILER_NVALUES, profiler_sort); /* Calculate the length of the names to display */ long nsamples = 0; size_t maxlength = 0; for (unsigned int i=0; imaxlength) maxlength = length; nsamples += (long) MORPHO_GETINTEGERVALUE(samples.data[i+PROFILER_VALUEPOSN]); } // Now report the output morpho_printf(v, "===Profiler output: Execution took %.3f seconds with %ld samples===\n", ((double) profile->end - profile->start)/((double) CLOCKS_PER_SEC), nsamples); for (unsigned int i=0; iprofiler=&profile; profile.start=clock(); bool success=morpho_run(v, p); profile.end=clock(); profiler_kill(&profile); profiler_report(&profile, v); profiler_clear(&profile); return success; } #else bool morpho_profile(vm *v, program *p) { return morpho_run(v, p); } #endif ================================================ FILE: src/debug/profile.h ================================================ /** @file profile.h * @author T J Atherton * * @brief Profiler */ #ifndef profile_h #define profile_h #include "compile.h" #include "vm.h" #include "morpho.h" bool morpho_profile(vm *v, program *p); #endif /* profile_h */ ================================================ FILE: src/geometry/CMakeLists.txt ================================================ target_sources(morpho PRIVATE fespace.c fespace.h field.c field.h functional.c functional.h geometry.c geometry.h integrate.c integrate.h mesh.c mesh.h selection.c selection.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES fespace.h field.h functional.h geometry.h integrate.h mesh.h selection.h ) ================================================ FILE: src/geometry/fespace.c ================================================ /** @file fespace.c * @author T J Atherton * * @brief Finite element fespaces */ #include "geometry.h" /* ********************************************************************** * Discretization objects * ********************************************************************** */ objecttype objectfespacetype; /** Field object definitions */ void objectfespace_printfn(object *obj, void *v) { objectfespace *disc=(objectfespace *) obj; morpho_printf(v, "", disc->fespace->name); } size_t objectfespace_sizefn(object *obj) { return sizeof(objectfespace); } objecttypedefn objectfespacedefn = { .printfn=objectfespace_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectfespace_sizefn }; /** Creates a new fespace object * @param[in] fespace - fespace definition to use */ objectfespace *objectfespace_new(fespace *disc) { objectfespace *new = (objectfespace *) object_new(sizeof(objectfespace), OBJECT_FESPACE); if (new) new->fespace=disc; return new; } /* ********************************************************************** * Discretization definitions * ********************************************************************** */ #define LINE_OPCODE 1 #define AREA_OPCODE 2 #define QUANTITY_OPCODE 255 #define LINE(id, v1, v2) LINE_OPCODE, id, v1, v2 // Identify a grade 1 subelement given by two vertex indices #define AREA(id, v1, v2, v3) AREA_OPCODE, id, v1, v2, v3 // Identify a grade 2 subelement given by three vertex indices #define QUANTITY(grade, id, qno) QUANTITY_OPCODE, grade, id, qno // Fetch quantity from subelement of grade with id and quantity number #define ENDDEFN -1 /* ------------------------------------------------------- * CG1 element in 1D * ------------------------------------------------------- */ /* * 0 - 1 // One degree of freedom per vertex */ void cg1_1dinterpolate(double *lambda, double *wts) { wts[0]=lambda[0]; wts[1]=lambda[1]; } void cg1_1dgrad(double *lambda, double *grad) { double g[] = { 1, 0, 0, 1 }; memcpy(grad, g, sizeof(g)); } unsigned int cg1_1dshape[] = { 1, 0 }; double cg1_1dnodes[] = { 0.0, 1.0 }; eldefninstruction cg1_1ddefn[] = { QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 ENDDEFN }; fespace cg1_1d = { .name = "CG1", .grade = 1, .shape = cg1_1dshape, .degree = 1, .nnodes = 2, .nsubel = 0, .nodes = cg1_1dnodes, .ifn = cg1_1dinterpolate, .gfn = cg1_1dgrad, .eldefn = cg1_1ddefn, .lower = NULL }; /* ------------------------------------------------------- * CG2 element in 1D * ------------------------------------------------------- */ /* * 0 - 2 - 1 // One degree of freedom per vertex; one at the midpoint */ void cg2_1dinterpolate(double *lambda, double *wts) { double dl = (lambda[0]-lambda[1]); wts[0]=lambda[0]*dl; wts[1]=-lambda[1]*dl; wts[2]=4*lambda[0]*lambda[1]; } void cg2_1dgrad(double *lambda, double *grad) { // Gij = d Xi[i] / d lambda[j] // Note this is in column-major order! double g[] = { 2*lambda[0]-lambda[1], -lambda[1], 4*lambda[1], -lambda[0], 2*lambda[1]-lambda[0], 4*lambda[0] }; memcpy(grad, g, sizeof(g)); } unsigned int cg2_1dshape[] = { 1, 1 }; double cg2_1dnodes[] = { 0.0, 1.0, 0.5 }; eldefninstruction cg2_1ddefn[] = { LINE(0,0,1), // Identify line subelement with vertex indices (0,1) QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(1,0,0), // Fetch quantity from line subelement ENDDEFN }; fespace cg2_1d = { .name = "CG2", .grade = 1, .shape = cg2_1dshape, .degree = 2, .nnodes = 3, .nsubel = 1, .nodes = cg2_1dnodes, .ifn = cg2_1dinterpolate, .gfn = cg2_1dgrad, .eldefn = cg2_1ddefn, .lower = NULL }; /* ------------------------------------------------------- * CG3 element in 1D * ------------------------------------------------------- */ /* * 0 - 2 - 3 - 1 // One degree of freedom per vertex; two on the line */ void cg3_1dinterpolate(double *lambda, double *wts) { double a = (9.0/2.0)*lambda[0]*lambda[1]; wts[0]=lambda[0]*(1-a); wts[1]=lambda[1]*(1-a); wts[2]=a*(2*lambda[0]-lambda[1]); wts[3]=a*(2*lambda[1]-lambda[0]); } unsigned int cg3_1dshape[] = { 1, 2 }; double cg3_1dnodes[] = { 0.0, 1.0, 1.0/3.0, 2.0/3.0 }; eldefninstruction cg3_1ddefn[] = { LINE(0,0,1), // Identify line subelement with vertex indices (0,1) QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(1,0,0), // Fetch quantity from line subelement QUANTITY(1,0,1), // Fetch quantity from line subelement ENDDEFN }; fespace cg3_1d = { .name = "CG3", .grade = 1, .shape = cg3_1dshape, .degree = 3, .nnodes = 4, .nsubel = 1, .nodes = cg3_1dnodes, .ifn = cg3_1dinterpolate, .eldefn = cg3_1ddefn, .lower = NULL }; /* ------------------------------------------------------- * CG1 element in 2D * ------------------------------------------------------- */ /* 2 * |\ * 0-1 // One degree of freedom per vertex */ void cg1_2dinterpolate(double *lambda, double *wts) { wts[0]=lambda[0]; wts[1]=lambda[1]; wts[2]=lambda[2]; } void cg1_2dgrad(double *lambda, double *grad) { double g[] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; memcpy(grad, g, sizeof(g)); } unsigned int cg1_2dshape[] = { 1, 0, 0 }; double cg1_2dnodes[] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 }; eldefninstruction cg1_2deldefn[] = { QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(0,2,0), // Fetch quantity on vertex 2 ENDDEFN }; fespace *cg1_2d_lower[] = { &cg1_1d, NULL }; fespace cg1_2d = { .name = "CG1", .grade = 2, .shape = cg1_2dshape, .degree = 1, .nnodes = 3, .nsubel = 0, .nodes = cg1_2dnodes, .ifn = cg1_2dinterpolate, .gfn = cg1_2dgrad, .eldefn = cg1_2deldefn, .lower = cg1_2d_lower }; /* ------------------------------------------------------- * CG2 element in 2D * ------------------------------------------------------- */ /* 2 * |\ * 5 4 * | \ * 0-3-1 // One degree of freedom per vertex; one at the midpoint */ void cg2_2dinterpolate(double *lambda, double *wts) { wts[0]=lambda[0]*(2*lambda[0]-1); wts[1]=lambda[1]*(2*lambda[1]-1); wts[2]=lambda[2]*(2*lambda[2]-1); wts[3]=4*lambda[0]*lambda[1]; wts[4]=4*lambda[1]*lambda[2]; wts[5]=4*lambda[2]*lambda[0]; } void cg2_2dgrad(double *lambda, double *grad) { // Gij = d Xi[i] / d lambda[j] // Note this is in column-major order! double g[] = { 4*lambda[0]-1, 0, 0, 4*lambda[1], 0, 4*lambda[2], 0, 4*lambda[1]-1, 0, 4*lambda[0], 4*lambda[2], 0, 0, 0, 4*lambda[2]-1, 0, 4*lambda[1], 4*lambda[0] }; memcpy(grad, g, sizeof(g)); } unsigned int cg2_2dshape[] = { 1, 1, 0 }; double cg2_2dnodes[] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 0.5, 0.5, 0.0, 0.5 }; eldefninstruction cg2_2deldefn[] = { LINE(0,0,1), // Identify line subelement with vertex indices (0,1) LINE(1,1,2), // Identify line subelement with vertex indices (1,2) LINE(2,2,0), // Identify line subelement with vertex indices (2,0) QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(0,2,0), // Fetch quantity on vertex 2 QUANTITY(1,0,0), // Fetch quantity from line 0 QUANTITY(1,1,0), // Fetch quantity from line 1 QUANTITY(1,2,0), // Fetch quantity from line 2 ENDDEFN }; fespace *cg2_2d_lower[] = { &cg2_1d, NULL }; fespace cg2_2d = { .name = "CG2", .grade = 2, .shape = cg2_2dshape, .degree = 2, .nnodes = 6, .nsubel = 3, .nodes = cg2_2dnodes, .ifn = cg2_2dinterpolate, .gfn = cg2_2dgrad, .eldefn = cg2_2deldefn, .lower = cg2_2d_lower }; /* ------------------------------------------------------- * CG1 element in 3D * ------------------------------------------------------- */ /* z=0 z=1 * 2 * |\ * 0-1 3 // One degree of freedom per vertex */ void cg1_3dinterpolate(double *lambda, double *wts) { wts[0]=lambda[0]; wts[1]=lambda[1]; wts[2]=lambda[2]; wts[3]=lambda[3]; } void cg1_3dgrad(double *lambda, double *grad) { double g[] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; memcpy(grad, g, sizeof(g)); } unsigned int cg1_3dshape[] = { 1, 0, 0, 0 }; double cg1_3dnodes[] = { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; eldefninstruction cg1_3deldefn[] = { QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(0,2,0), // Fetch quantity on vertex 2 QUANTITY(0,3,0), // Fetch quantity on vertex 3 ENDDEFN }; fespace *cg1_3d_lower[] = { &cg1_2d, &cg1_1d, NULL }; fespace cg1_3d = { .name = "CG1", .grade = 3, .shape = cg1_3dshape, .degree = 1, .nnodes = 4, .nsubel = 0, .nodes = cg1_3dnodes, .ifn = cg1_3dinterpolate, .gfn = cg1_3dgrad, .eldefn = cg1_3deldefn, .lower = cg1_3d_lower }; /* ------------------------------------------------------- * CG2 element in 3D * ------------------------------------------------------- */ /* z=0 z=0.5 z=1 * 2 * |\ * 6 5 9 * | \ | \ * 0-4-1 7--8 3 - i.e. vertices */ void cg2_3dinterpolate(double *lambda, double *wts) { wts[0]=lambda[0]*(2*lambda[0]-1); wts[1]=lambda[1]*(2*lambda[1]-1); wts[2]=lambda[2]*(2*lambda[2]-1); wts[3]=lambda[3]*(2*lambda[3]-1); wts[4]=4*lambda[0]*lambda[1]; wts[5]=4*lambda[1]*lambda[2]; wts[6]=4*lambda[2]*lambda[0]; wts[7]=4*lambda[0]*lambda[3]; wts[8]=4*lambda[1]*lambda[3]; wts[9]=4*lambda[2]*lambda[3]; } void cg2_3dgrad(double *lambda, double *grad) { // TODO: FIX // Gij = d Xi[i] / d lambda[j] // Note this is in column-major order! double g[] = { 4*lambda[0]-1, 0, 0, 0, 4*lambda[1], 0, 4*lambda[2], 4*lambda[3], 0, 0, 0, 4*lambda[1]-1, 0, 0, 4*lambda[0], 4*lambda[2], 0, 0, 4*lambda[3], 0, 0, 0, 4*lambda[2]-1, 0, 0, 4*lambda[1], 4*lambda[0], 0, 0, 4*lambda[3], 0, 0, 0, 4*lambda[3]-1, 0, 0, 0, 4*lambda[0], 4*lambda[1], 4*lambda[2] }; memcpy(grad, g, sizeof(g)); } unsigned int cg2_3dshape[] = { 1, 1, 0, 0 }; double cg2_3dnodes[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0.5, 0, 0, 0, 0.5, 0.5, 0, 0.5, 0, 0.5, 0.5 }; eldefninstruction cg2_3deldefn[] = { LINE(0,0,1), // Identify line subelement with vertex indices (0,1) LINE(1,1,2), // Identify line subelement with vertex indices (1,2) LINE(2,2,0), // Identify line subelement with vertex indices (2,0) LINE(3,0,3), // Identify line subelement with vertex indices (0,3) LINE(4,1,3), // Identify line subelement with vertex indices (1,3) LINE(5,2,3), // Identify line subelement with vertex indices (2,3) QUANTITY(0,0,0), // Fetch quantity on vertex 0 QUANTITY(0,1,0), // Fetch quantity on vertex 1 QUANTITY(0,2,0), // Fetch quantity on vertex 2 QUANTITY(0,3,0), // Fetch quantity on vertex 3 QUANTITY(1,0,0), // Fetch quantity from line 0 QUANTITY(1,1,0), // Fetch quantity from line 1 QUANTITY(1,2,0), // Fetch quantity from line 2 QUANTITY(1,3,0), // Fetch quantity from line 3 QUANTITY(1,4,0), // Fetch quantity from line 4 QUANTITY(1,5,0), // Fetch quantity from line 5 ENDDEFN }; fespace *cg2_3d_lower[] = { &cg2_2d, &cg2_1d, NULL }; fespace cg2_3d = { .name = "CG2", .grade = 3, .shape = cg2_3dshape, .degree = 2, .nnodes = 10, .nsubel = 6, .nodes = cg2_3dnodes, .ifn = cg2_3dinterpolate, .gfn = cg2_3dgrad, .eldefn = cg2_3deldefn, .lower = cg2_3d_lower }; /* ------------------------------------------------------- * List of finite elements * ------------------------------------------------------- */ fespace *fespaces[] = { &cg1_1d, &cg2_1d, &cg1_2d, &cg2_2d, &cg1_3d, &cg2_3d, NULL }; /* ********************************************************************** * Discretization functions * ********************************************************************** */ /** Find a fespace definition based on a name and grade */ fespace *fespace_find(char *name, grade g) { for (int i=0; fespaces[i]!=NULL; i++) { if (strcmp(name, fespaces[i]->name)==0 && g==fespaces[i]->grade) return fespaces[i]; } return NULL; } /** Finds a linear fespace for a given grade */ fespace *fespace_findlinear(grade g) { for (int i=0; fespaces[i]!=NULL; i++) { if (fespaces[i]->grade && fespaces[i]->degree==1) return fespaces[i]; } return NULL; } #define FETCH(instr) (*(instr++)) /** Steps through an element definition, generating subelements and identifying quantities */ bool fespace_doftofieldindx(objectfield *field, fespace *disc, int nv, int *vids, fieldindx *findx) { elementid subel[disc->nsubel+1]; // Element IDs of sub elements int sid, svids[nv], nmatch, k=0; objectsparse *vmatrix[disc->grade+1]; // Vertex->elementid connectivity matrices for (grade g=0; g<=disc->grade; g++) vmatrix[g]=mesh_addconnectivityelement(field->mesh, g, 0); for (eldefninstruction *instr=disc->eldefn; instr!=NULL && *instr!=ENDDEFN; ) { eldefninstruction op=FETCH(instr); switch(op) { case LINE_OPCODE: // Find an element defined by n vertices case AREA_OPCODE: // TODO: Need to cope with (mis) orientation of these subelements { sid = FETCH(instr); for (int i=0; i<=op; i++) svids[i] = vids[FETCH(instr)]; if (!mesh_matchelements(vmatrix[1], op, op+1, svids, 1, &nmatch, &subel[sid])) return false; } break; case QUANTITY_OPCODE: { findx[k].g=FETCH(instr); int sid=FETCH(instr); findx[k].id=(findx[k].g==0 ? vids[sid]: subel[sid]); findx[k].indx=FETCH(instr); k++; } break; default: UNREACHABLE("Error in finite element definition"); } } return true; } /** Searches a fespace's lower list to find a fespace to use on a lower grade */ bool fespace_lower(fespace *disc, grade target, fespace **out) { if (disc->lower) for (int i=0; disc->lower[i]!=NULL; i++) { if (disc->lower[i]->grade==target) { *out = disc->lower[i]; return true; } } return false; } /** Constructs a layout matrix that maps element ids (columns) to degree of freedom indices in a field */ bool fespace_layout(objectfield *field, fespace *disc, objectsparse **out) { objectsparse *conn = mesh_getconnectivityelement(field->mesh, 0, disc->grade); elementid nel=mesh_nelements(conn); objectsparse *new = object_newsparse(NULL, NULL); if (!new) return false; sparseccs_resize(&new->ccs, field->nelements, nel, nel*disc->nnodes, NULL); for (elementid id=0; idccs.cptr[id]=id*disc->nnodes; fieldindx findx[disc->nnodes]; if (!fespace_doftofieldindx(field, disc, nv, vids, findx)) goto fespace_layout_cleanup; for (int i=0; innodes; i++) { if (!field_getindex(field, findx[i].g, findx[i].id, findx[i].indx, new->ccs.rix+new->ccs.cptr[id]+i)) goto fespace_layout_cleanup; } } new->ccs.cptr[nel]=nel*disc->nnodes; // Last column pointer points to next column *out=new; return true; fespace_layout_cleanup: if (new) object_free((object *) new); return false; } /** @brief Calculates the gradient of the basis functions with respect to the reference coordinates. * @param[in] disc - fespace to query * @param[in] lambda - position in barycentric coordinates * @param[out] grad - gradient of basis functions with respect to reference coordinates (disc->nnodes x disc->grade) */ void fespace_gradient(fespace *disc, double *lambda, objectmatrix *grad) { int nbary = disc->grade+1; // Compute gradients of the basis functions with respect to barycentric coordinates double gdata[disc->nnodes*nbary]; (disc->gfn) (lambda, gdata); for (int i=0; igrade; i++) { functional_vecsub(disc->nnodes, gdata+(i+1)*disc->nnodes, gdata, grad->elements+i*disc->nnodes); } } /* ********************************************************************** * FunctionSpace class * ********************************************************************** */ /** Constructs a fespace object */ value fespace_constructor(vm *v, int nargs, value *args) { value grd=MORPHO_INTEGER(1); value out=MORPHO_NIL; int nfixed; if (!builtin_options(v, nargs, args, &nfixed, 1, field_gradeoption, &grd)) morpho_runtimeerror(v, FNSPC_ARGS); if (nfixed==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(grd)) { char *label = MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)); fespace *d=fespace_find(label, MORPHO_GETINTEGERVALUE(grd)); if (d) { objectfespace *obj=objectfespace_new(d); if (obj) { out = MORPHO_OBJECT(obj); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else morpho_runtimeerror(v, FNSPC_NOTFOUND, label, MORPHO_GETINTEGERVALUE(grd)); } else morpho_runtimeerror(v, FNSPC_ARGS); return out; } value FiniteElementSpace_layout(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectfespace *slf = MORPHO_GETFESPACE(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectfield *field = MORPHO_GETFIELD(MORPHO_GETARG(args, 0)); objectsparse *new; if (fespace_layout(field, slf->fespace, &new)) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } return out; } MORPHO_BEGINCLASS(FiniteElementSpace) MORPHO_METHOD(FINITEELEMENTSPACE_LAYOUT_METHOD, FiniteElementSpace_layout, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ void fespace_initialize(void) { objectfespacetype=object_addtype(&objectfespacedefn); builtin_addfunction(FINITEELEMENTSPACE_CLASSNAME, fespace_constructor, BUILTIN_FLAGSEMPTY); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value fespaceclass=builtin_addclass(FINITEELEMENTSPACE_CLASSNAME, MORPHO_GETCLASSDEFINITION(FiniteElementSpace), objclass); object_setveneerclass(OBJECT_FESPACE, fespaceclass); morpho_defineerror(FNSPC_ARGS, ERROR_HALT, FNSPC_ARGS_MSG); morpho_defineerror(FNSPC_NOTFOUND, ERROR_HALT, FNSPC_NOTFOUND_MSG); } ================================================ FILE: src/geometry/fespace.h ================================================ /** @file fespace.h * @author T J Atherton * * @brief Finite element fespaces */ #ifndef fespace_h #define fespace_h #include "geometry.h" /* ------------------------------------------------------- * Discretization type definitions * ------------------------------------------------------- */ /** @brief Interpolation functions are called to assign weights to the nodes given barycentric coordinates */ typedef void (*interpolationfn) (double *, double *); /** @brief Element definitions comprise a sequence of instructions to map field degrees of freedom to local nodes */ typedef int eldefninstruction; /** @brief Discretization definitions */ typedef struct sfespace { char *name; /** Name of the fespace */ grade grade; /** Grade of element this fespace is defined on */ unsigned int *shape; /** Number of degrees of freedom on each grade; must have grade+1 entries */ int degree; /** Highest degree of polynomial represented by this element */ int nnodes; /** Number of nodes for this element type */ int nsubel; /** Number of subelements used by */ double *nodes; /** Node positions */ interpolationfn ifn; /** Interpolation function; receives barycentric coordinates as input and returns weights per node */ interpolationfn gfn; /** Gradient interpolation function */ eldefninstruction *eldefn; /** Element definition */ struct sfespace **lower; /** Discretization to be used for interpolation on lower grades */ } fespace; /* ------------------------------------------------------- * Discretization object type * ------------------------------------------------------- */ extern objecttype objectfespacetype; #define OBJECT_FESPACE objectfespacetype typedef struct { object obj; fespace *fespace; } objectfespace; /** Tests whether an object is a fespace */ #define MORPHO_ISFESPACE(val) object_istype(val, OBJECT_FESPACE) /** Gets the object as a fespace */ #define MORPHO_GETFESPACE(val) ((objectfespace *) MORPHO_GETOBJECT(val)) /* ------------------------------------------------------- * FunctionSpace veneer class * ------------------------------------------------------- */ #define FINITEELEMENTSPACE_CLASSNAME "FiniteElementSpace" #define FINITEELEMENTSPACE_LAYOUT_METHOD "layout" /* ------------------------------------------------------- * Discretization error messages * ------------------------------------------------------- */ #define FNSPC_ARGS "FnSpcArgs" #define FNSPC_ARGS_MSG "Function space must be initialized with a label and a grade." #define FNSPC_NOTFOUND "FnSpcNtFnd" #define FNSPC_NOTFOUND_MSG "Function space '%s' on grade %i not found." /* ------------------------------------------------------- * Discretization interface * ------------------------------------------------------- */ fespace *fespace_find(char *name, grade g); fespace *fespace_findlinear(grade g); bool fespace_doftofieldindx(objectfield *field, fespace *disc, int nv, int *vids, fieldindx *findx); bool fespace_lower(fespace *disc, grade target, fespace **out); bool fespace_layout(objectfield *field, fespace *disc, objectsparse **out); void fespace_gradient(fespace *disc, double *lambda, objectmatrix *grad); void fespace_initialize(void); #endif ================================================ FILE: src/geometry/field.c ================================================ /** @file field.c * @author T J Atherton * * @brief Fields */ #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include "field.h" #include "morpho.h" #include "classes.h" #include "common.h" #include "matrix.h" #include "sparse.h" #include "geometry.h" value field_gradeoption; value field_functionspaceoption; /* ********************************************************************** * Field objects * ********************************************************************** */ objecttype objectfieldtype; /** Field object definitions */ void objectfield_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectfield_markfn(object *obj, void *v) { objectfield *c = (objectfield *) obj; morpho_markvalue(v, c->prototype); morpho_markvalue(v, c->fnspc); morpho_markobject(v, (object *) c->mesh); } void objectfield_freefn(object *obj) { objectfield *f = (objectfield *) obj; if (f->dof) MORPHO_FREE(f->dof); if (f->offset) MORPHO_FREE(f->offset); if (f->pool) MORPHO_FREE(f->pool); } size_t objectfield_sizefn(object *obj) { return sizeof(objectfield)+(((objectfield *) obj)->ngrades * sizeof(int)); } objecttypedefn objectfielddefn = { .printfn=objectfield_printfn, .markfn=objectfield_markfn, .freefn=objectfield_freefn, .sizefn=objectfield_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * Constructors * ********************************************************************** */ /** Checks if a prototype object is acceptable */ bool field_checkprototype(value v) { return (MORPHO_ISNUMBER(v) || MORPHO_ISMATRIX(v) || MORPHO_ISSPARSE(v)); } unsigned int field_sizeprototype(value prototype) { unsigned int size = 1; if (MORPHO_ISMATRIX(prototype)) { objectmatrix *m = (MORPHO_GETMATRIX(prototype)); size = m->ncols*m->nrows; } return size; } /** Determines the overall size of storage required for the field * @param[in] mesh - mesh to use * @param[in] prototype - prototype object * @param[in] ngrades - size of grade array * @param[in] dof - number of degrees of freedom per grade * @param[out] offsets - offsets into the store (ngrades + 1 elements) * @returns the overall size of storage required */ unsigned int field_size(objectmesh *mesh, value prototype, unsigned int ngrades, unsigned int *dof, unsigned int *offsets) { unsigned int size = 0; unsigned int psize = field_sizeprototype(prototype); for (unsigned int i=0; ifespace; for (int i=0; i<=disc->grade; i++) dof[i]=disc->shape[i]; for (int i=disc->grade+1; imesh=mesh; new->prototype=(MORPHO_ISNUMBER(prototype)? MORPHO_NIL : prototype); new->psize=field_sizeprototype(prototype); new->nelements=size/new->psize; new->ngrades=ngrades; new->fnspc=(MORPHO_ISFESPACE(fnspc) ? fnspc : MORPHO_NIL); new->offset=noffset; memcpy(noffset, offset, sizeof(unsigned int)*(ngrades+1)); new->dof=ndof; memcpy(ndof, dof, sizeof(unsigned int)*ngrades); new->pool=NULL; /* Initialize the store */ object_init(&new->data.obj, OBJECT_MATRIX); new->data.ncols=1; new->data.nrows=size; new->data.elements=new->data.matrixdata; if (MORPHO_ISMATRIX(prototype)) { objectmatrix *mat = MORPHO_GETMATRIX(prototype); int mel = mat->ncols*mat->nrows; for (unsigned int i=0; inelements; i++) { memcpy(new->data.elements+i*mel, mat->elements, sizeof(double)*mel); } } else if(MORPHO_ISNUMBER(prototype)){ // if we have a number for our prototype set all the elements equal to it for (elementid i=0; ivert->ncols; i++) { field_setelement(new, MESH_GRADE_VERTEX, i, 0, prototype); } } else memset(new->data.elements, 0, sizeof(double)*size); } else { // Cleanup partially allocated structure if (noffset) MORPHO_FREE(noffset); if (ndof) MORPHO_FREE(ndof); } return new; } /** Applies an initialization function to every vertex */ bool field_applyfunctiontovertices(vm *v, objectmesh *mesh, value fn, objectfield *field) { value coords[mesh->dim]; // Vertex coords value ret=MORPHO_NIL; // Return value int nv = mesh_nvertices(mesh); for (elementid i=0; idim, coords, &ret)) return false; if (!field_setelement(field, MESH_GRADE_VERTEX, i, 0, ret)) { // if we can't set the field value to the ouptut of the function clean up morpho_runtimeerror(v, FIELD_OPRETURN); return false; } } } return true; } /** Applies an initialization function to every DOF in an element */ bool field_applyfunctiontoelements(vm *v, objectmesh *mesh, value fn, value fnspc, objectfield *field) { if (!MORPHO_ISFESPACE(fnspc)) return false; fespace *disc = MORPHO_GETFESPACE(fnspc)->fespace; objectsparse *conn = mesh_getconnectivityelement(mesh, 0, disc->grade); if (!conn) return false; elementid nel = mesh_nelements(conn); for (elementid id=0; idnnodes]; if (!fespace_doftofieldindx(field, disc, nv, vids, findx)) return false; for (int i=0; innodes; i++) { // Loop over nodes int indx; if (!field_getindex(field, findx[i].g, findx[i].id, findx[i].indx, &indx)) return false; double lambda[nv], ll=0.0; // Convert node positions in reference element to barycentric coordinates for (int j=0; jnodes[i*disc->grade+j]; ll+=lambda[j+1]; } lambda[0]=1-ll; double xx[mesh->dim]; // Interpolate position in physical space using barycentric coordinates for (int j=0; jdim; j++) xx[j]=0.0; for (int j=0; jdim, xx, lambda[j], x[j], xx); /*printf("<<"); for (int j=0; jnodes[i*disc->grade+j]); printf(">> "); printf("["); for (int j=0; jdim; j++) printf("%g ", xx[j]); printf(": ");*/ value coords[mesh->dim], ret; for (int j=0; jdim; j++) coords[j]=MORPHO_FLOAT(xx[j]); if (!morpho_call(v, fn, mesh->dim, coords, &ret)) return false; //morpho_printvalue(v, ret); //printf(" -> %i\n", indx[i]); if (!field_setelementwithindex(field, indx, ret)) { morpho_runtimeerror(v, FIELD_OPRETURN); return false; } } } return true; } /** Creates a field by applying a function to the vertices of a mesh * @param[in] v - virtual machine to use for function calls * @param[in] mesh - mesh to use * @param[in] fn - function to call * @returns field object or NULL on failure */ objectfield *field_newwithfunction(vm *v, objectmesh *mesh, value fn, value fnspc) { value ret=MORPHO_NIL; // Return value value coords[mesh->dim]; // Vertex coords objectfield *new = NULL; int handle = -1; /* Use the first element to find a prototype **/ if (mesh_getvertexcoordinatesasvalues(mesh, 0, coords)) { if (!morpho_call(v, fn, mesh->dim, coords, &ret)) goto field_newwithfunction_cleanup; if (MORPHO_ISOBJECT(ret)) handle=morpho_retainobjects(v, 1, &ret); } new=object_newfield(mesh, ret, fnspc, NULL); if (new) { if (MORPHO_ISFESPACE(fnspc)) { if (!field_applyfunctiontoelements(v, mesh, fn, fnspc, new)) goto field_newwithfunction_cleanup; } else { if (!field_applyfunctiontovertices(v, mesh, fn, new)) goto field_newwithfunction_cleanup; } } if (handle>=0) morpho_releaseobjects(v, handle); return new; field_newwithfunction_cleanup: if (new) object_free((object *) new); if (handle>=0) morpho_releaseobjects(v, handle); return NULL; } /** Zeros a field */ void field_zero(objectfield *f) { memset(f->data.elements, 0, sizeof(double)*(f->data.nrows)); } /** Adds the object pool. This is a collection of statically allocated objects */ bool field_addpool(objectfield *f) { unsigned int nel = f->nelements; if (!f->pool && MORPHO_ISMATRIX(f->prototype)) { objectmatrix *prototype=MORPHO_GETMATRIX(f->prototype); f->pool=MORPHO_MALLOC(sizeof(objectmatrix)*nel); if (f->pool) { objectmatrix *m = (objectmatrix *) f->pool; for (unsigned int i=0; idata.elements+i*f->psize; m[i].ncols=prototype->ncols; m[i].nrows=prototype->nrows; } } return true; } return false; } /** Clones a field */ objectfield *field_clone(objectfield *f) { objectfield *new = object_newfield(f->mesh, f->prototype, f->fnspc, f->dof); if (new) memcpy(new->data.elements, f->data.elements, f->data.nrows*sizeof(double)); return new; } /* ********************************************************************** * Field operations * ********************************************************************* */ /** Retrieve a value from a field object * @param[in] field - field to use * @param[in] grade - grade to access * @param[in] el - element id * @param[in] indx - index within the element * @param[out] out - the retrieved value * @return true on success */ bool field_getelement(objectfield *field, grade grade, elementid el, int indx, value *out) { unsigned int ix=field->offset[grade]+field->dof[grade]*el+indx; if (!(ixoffset[grade+1] && indxdof[grade])) return false; if (MORPHO_ISNIL(field->prototype)) { *out=MORPHO_FLOAT(field->data.elements[ix]); return true; } else if (MORPHO_ISMATRIX(field->prototype)) { if (!field->pool) field_addpool(field); if (field->pool) { objectmatrix *mpool = (objectmatrix *) field->pool; *out = MORPHO_OBJECT(&mpool[ix]); return true; } } return false; } /** Retrieve a value from a field object given a single index * @param[in] field - field to use * @param[in] indx - index within the element * @param[out] out - the retrieved value * @return true on success */ bool field_getelementwithindex(objectfield *field, int indx, value *out) { if (MORPHO_ISNIL(field->prototype)) { *out=MORPHO_FLOAT(field->data.elements[indx]); return true; } else if (MORPHO_ISMATRIX(field->prototype)) { if (!field->pool) field_addpool(field); if (field->pool) { objectmatrix *mpool = (objectmatrix *) field->pool; *out = MORPHO_OBJECT(&mpool[indx]); return true; } } return false; } /** Constructs a single index, suitable for use with fieldgetelementwithindex from the grade, element id and quantity number * @param[in] field - field to use * @param[in] grade - grade to access * @param[in] el - element id * @param[in] indx - index within the element * @param[out] out - the retrieved index * @return true on success */ bool field_getindex(objectfield *field, grade grade, elementid el, int indx, int *out) { int ix=field->offset[grade]+field->dof[grade]*el+indx; if (!(ixoffset[grade+1] && indxdof[grade])) return false; *out=ix; return true; } /** Retrieve the list of doubles that represent an entry in a field * @param[in] field - field to use * @param[in] grade - grade to access * @param[in] el - element id * @param[in] indx - index within the element * @param[out] nentries - number of entries * @param[out] out - the retrieved list * @return true on success */ bool field_getelementaslist(objectfield *field, grade grade, elementid el, int indx, unsigned int *nentries, double **out) { bool success=false; unsigned int ix=field->offset[grade]+field->dof[grade]*el+indx; if (!(ixoffset[grade+1] && indxdof[grade])) return false; if (MORPHO_ISNIL(field->prototype)) { *out = &field->data.elements[ix]; *nentries=1; success=true; } else if (MORPHO_ISMATRIX(field->prototype)) { *out = &field->data.elements[ix*(field->psize)]; *nentries=field->psize; success=true; } return success; } /** Sets the value of an entry in a field object * @param[in] field - field to use * @param[in] grade - grade to access * @param[in] el - element id * @param[in] indx - index within the element * @param[in] val - value to set * @return true on success */ bool field_setelement(objectfield *field, grade grade, elementid el, int indx, value val) { unsigned int ix=field->offset[grade]+field->dof[grade]*el+indx; if (!(ixoffset[grade+1] && indxdof[grade])) return false; if (MORPHO_ISNIL(field->prototype)) { if (MORPHO_ISNUMBER(val)) { return morpho_valuetofloat(val, &field->data.elements[ix]); } } else { unsigned int psize = field_sizeprototype(val); if (MORPHO_ISMATRIX(val)) { objectmatrix *m = MORPHO_GETMATRIX(val); if (psize==field->psize) { memcpy(field->data.elements+ix*psize, m->elements, psize*sizeof(double)); return true; } } } return false; } /** Sets the value of an entry in a field object given a single index * @param[in] field - field to use * @param[in] ix - index of the element * @param[in] val - value to set * @return true on success */ bool field_setelementwithindex(objectfield *field, int ix, value val) { if (ix>=field->nelements) return false; if (MORPHO_ISNIL(field->prototype)) { if (MORPHO_ISNUMBER(val)) { return morpho_valuetofloat(val, &field->data.elements[ix]); } } else { unsigned int psize = field_sizeprototype(val); if (MORPHO_ISMATRIX(val)) { objectmatrix *m = MORPHO_GETMATRIX(val); if (psize==field->psize) { memcpy(field->data.elements+ix*psize, m->elements, psize*sizeof(double)); return true; } } } return false; } /** Checks if two fields have the same shape */ bool field_compareshape(objectfield *a, objectfield *b) { if (a->data.nrows==b->data.nrows && a->ngrades==b->ngrades) { for (unsigned int i=0; ingrades; i++) { if (a->dof[i]!=b->dof[i]) return false; } return true; } return false; } /** Returns the number of degrees of freedom in a given grade */ unsigned int field_dofforgrade(objectfield *f, grade g) { return (g<=f->ngrades ? f->dof[g] : 0); } /** Adds two fields together */ bool field_add(objectfield *left, objectfield *right, objectfield *out) { return (matrix_add(&left->data, &right->data, &out->data)==MATRIX_OK); } /** Subtracts one field from another */ bool field_sub(objectfield *left, objectfield *right, objectfield *out) { return (matrix_sub(&left->data, &right->data, &out->data)==MATRIX_OK); } /** Accumulate, i.e. a <- a + lambda*b */ bool field_accumulate(objectfield *left, double lambda, objectfield *right) { return (matrix_accumulate(&left->data, lambda, &right->data)==MATRIX_OK); } bool field_inner(objectfield *left, objectfield *right, double *out) { return (matrix_inner(&left->data, &right->data, out)==MATRIX_OK); } /** Calls a function fn on every element of a field, optionally with other fields as arguments */ bool field_op(vm *v, value fn, objectfield *f, int nargs, objectfield **args, value *out) { unsigned int nel = f->nelements; value ret=MORPHO_NIL; value fargs[nargs+1]; objectfield *fld=NULL; int handle = -1; for (int i=0; imesh, ret, f->fnspc, f->dof); if (!fld) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } else { morpho_runtimeerror(v, FIELD_OPRETURN); return false; } } if (!field_setelementwithindex(fld, i, ret)) return false; } else return false; } if (handle>=0) morpho_releaseobjects(v, handle); if (fld) *out = MORPHO_OBJECT(fld); return true; } /* ********************************************************************** * Field veneer class * ********************************************************************* */ /** Constructs a Field object */ value field_constructor(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectfield *new=NULL; objectmesh *mesh=NULL; // The mesh used by the object value fn = MORPHO_NIL; // A function to call value prototype=MORPHO_NIL; // Prototype object value grd = MORPHO_NIL; value fnspc = MORPHO_NIL; int nfixed; if (!builtin_options(v, nargs, args, &nfixed, 2, field_gradeoption, &grd, field_functionspaceoption, &fnspc)) morpho_runtimeerror(v, FIELD_ARGS); for (unsigned int i=0; ival.count, list->val.data, dof)) return MORPHO_NIL; } if (MORPHO_ISNIL(fn)) { new = object_newfield(mesh, prototype, fnspc, (MORPHO_ISNIL(grd) ? NULL: dof)); } else { new = field_newwithfunction(v, mesh, fn, fnspc); } if (new) { out=morpho_wrapandbind(v, (object *) new); } else if (!morpho_checkerror(morpho_geterror(v))) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } return out; } /** Gets the field element with given indices */ value Field_getindex(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); unsigned int indx[nargs]; value out = MORPHO_NIL; if (array_valuelisttoindices(nargs, args+1, indx)) { grade g = (nargs>1 ? indx[0] : MESH_GRADE_VERTEX); elementid el = (nargs>1 ? indx[1] : indx[0]); int elindx = (nargs>2 ? indx[2] : 0); /* If only one index is specified, increment g to the lowest nonempty grade */ if (nargs==1) while (gngrades && f->dof[g]==0) g++; if (!field_getelement(f, g, el, elindx, &out)) morpho_runtimeerror(v, FIELD_INDICESOUTSIDEBOUNDS); } else morpho_runtimeerror(v, FIELD_INVLDINDICES); return out; } /** Sets the field element with given indices */ value Field_setindex(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); unsigned int indx[nargs]; int nindices = nargs-1; if (array_valuelisttoindices(nindices, args+1, indx)) { grade g = (nindices>1 ? indx[0] : MESH_GRADE_VERTEX); elementid el = (nindices>1 ? indx[1] : indx[0]); int elindx = (nindices>2 ? indx[2] : 0); /* If only one index is specified, treat it as a single index */ if (nindices==1) { if (!field_setelementwithindex(f, indx[0], MORPHO_GETARG(args, nargs-1))) { morpho_runtimeerror(v, FIELD_INCOMPATIBLEVAL); return MORPHO_NIL; } } else if (!field_setelement(f, g, el, elindx, MORPHO_GETARG(args, nargs-1))) { morpho_runtimeerror(v, FIELD_INCOMPATIBLEVAL); return MORPHO_NIL; } } else morpho_runtimeerror(v, FIELD_INVLDINDICES); return MORPHO_NIL; } /** Enumerate protocol */ value Field_enumerate(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (i<0) out=MORPHO_INTEGER(a->nelements); else if (inelements) { if (!field_getelementwithindex(a, i, &out)) UNREACHABLE("Could not get field element."); } /* Note no need to bind as we are an object pool */ } } return out; } /** Number of field elements */ value Field_count(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); return MORPHO_INTEGER(f->nelements); } /** Field assign */ value Field_assign(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectfield *b=MORPHO_GETFIELD(MORPHO_GETARG(args, 0)); if (field_compareshape(a, b)) { matrix_copy(&b->data, &a->data); } else morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (matrix_copy(b, &a->data)!=MATRIX_OK) morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, FIELD_ARITHARGS); return MORPHO_NIL; } /** Field add */ value Field_add(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectfield *b=MORPHO_GETFIELD(MORPHO_GETARG(args, 0)); if (field_compareshape(a, b)) { objectfield *new = object_newfield(a->mesh, a->prototype, a->fnspc, a->dof); if (new) { out=MORPHO_OBJECT(new); field_add(a, b, new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, FIELD_ARITHARGS); return out; } /** Right add */ value Field_addr(vm *v, int nargs, value *args) { value out=MORPHO_NIL; if (nargs==1 && (MORPHO_ISNIL(MORPHO_GETARG(args, 0)) || MORPHO_ISNUMBER(MORPHO_GETARG(args, 0)))) { int i=0; if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) i=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (MORPHO_ISFLOAT(MORPHO_GETARG(args, 0))) i=(fabs(MORPHO_GETFLOATVALUE(MORPHO_GETARG(args, 0)))mesh, a->prototype, a->fnspc, a->dof); if (new) { out=MORPHO_OBJECT(new); field_sub(a, b, new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, FIELD_ARITHARGS); return out; } /** Right subtract */ value Field_subr(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && (MORPHO_ISNIL(MORPHO_GETARG(args, 0)) || MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)))) { int i=(MORPHO_ISNIL(MORPHO_GETARG(args, 0)) ? 0 : MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0))); if (i==0) { objectfield *new=field_clone(a); if (new) { out=MORPHO_OBJECT(new); matrix_scale(&new->data, -1.0); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, VM_INVALIDARGS); } else morpho_runtimeerror(v, VM_INVALIDARGS); return out; } /** Field accumulate */ value Field_acc(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); if (nargs==2 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0)) && MORPHO_ISFIELD(MORPHO_GETARG(args, 1))) { objectfield *b=MORPHO_GETFIELD(MORPHO_GETARG(args, 1)); if (field_compareshape(a, b)) { double lambda=1.0; morpho_valuetofloat(MORPHO_GETARG(args, 0), &lambda); field_accumulate(a, lambda, b); } else morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, FIELD_ARITHARGS); return MORPHO_NIL; } /** Field multiply by a scalar */ value Field_mul(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double scale=1.0; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) { objectfield *new = field_clone(a); if (new) { out=MORPHO_OBJECT(new); matrix_scale(&new->data, scale); morpho_bindobjects(v, 1, &out); } } } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return out; } /** Field multiply by a scalar */ value Field_div(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { /* Division by a scalar */ double scale=1.0; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) { if (fabs(scale)data, 1.0/scale); morpho_bindobjects(v, 1, &out); } } } return out; } /** Frobenius inner product */ value Field_inner(vm *v, int nargs, value *args) { objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectfield *b=MORPHO_GETFIELD(MORPHO_GETARG(args, 0)); double prod=0.0; if (field_inner(a, b, &prod)) { out = MORPHO_FLOAT(prod); } else morpho_runtimeerror(v, FIELD_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, FIELD_ARITHARGS); return out; } /** Generalized operations */ value Field_op(vm *v, int nargs, value *args) { objectfield *slf=MORPHO_GETFIELD(MORPHO_SELF(args)); value out=MORPHO_NIL; value fn=MORPHO_NIL; objectfield *flds[nargs]; if (nargs>0) fn=MORPHO_GETARG(args, 0); if (morpho_iscallable(fn)) { for (unsigned int i=1; i\n"); matrix_print(v, &f->data); return MORPHO_NIL; } /** Clones a field */ value Field_clone(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectfield *a=MORPHO_GETFIELD(MORPHO_SELF(args)); objectfield *new=field_clone(a); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Get the shape (number of dofs per grade) of a field */ value Field_shape(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); value shape[f->ngrades]; for (unsigned int i=0; ingrades; i++) { shape[i]=MORPHO_INTEGER(f->dof[i]); } objectlist *new=object_newlist(f->ngrades, shape); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Get the functionspace used by a field */ value Field_fnspace(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); return f->fnspc; } /** Get a prototype used by the field */ value Field_prototype(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); return f->prototype; } /** Get the mesh associated with a field */ value Field_mesh(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); return MORPHO_OBJECT(f->mesh); } /** Get the matrix that stores the Field */ value Field_linearize(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); value out = MORPHO_NIL; objectmatrix *m=object_clonematrix(&f->data); if (m) { out = MORPHO_OBJECT(m); morpho_bindobjects(v, 1, &out); } return out; } /** Directly the matrix that stores the Field @warning only use when you know what you're doing. */ value Field_unsafelinearize(vm *v, int nargs, value *args) { objectfield *f=MORPHO_GETFIELD(MORPHO_SELF(args)); return MORPHO_OBJECT(&f->data); } MORPHO_BEGINCLASS(Field) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Field_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Field_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Field_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Field_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ASSIGN_METHOD, Field_assign, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Field_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADDR_METHOD, Field_addr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Field_sub, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUBR_METHOD, Field_subr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ACC_METHOD, Field_acc, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MUL_METHOD, Field_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MULR_METHOD, Field_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIV_METHOD, Field_div, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_INNER_METHOD, Field_inner, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_OP_METHOD, Field_op, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Field_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Field_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_SHAPE_METHOD, Field_shape, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_FESPACE_METHOD, Field_fnspace, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_PROTOTYPE_METHOD, Field_prototype, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_MESH_METHOD, Field_mesh, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD_LINEARIZE_METHOD, Field_linearize, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FIELD__LINEARIZE_METHOD, Field_unsafelinearize, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************* */ void field_initialize(void) { objectfieldtype=object_addtype(&objectfielddefn); field_gradeoption=builtin_internsymbolascstring(FIELD_GRADEOPTION); field_functionspaceoption=builtin_internsymbolascstring(FIELD_FESPACEOPTION); builtin_addfunction(FIELD_CLASSNAME, field_constructor, BUILTIN_FLAGSEMPTY); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value fieldclass=builtin_addclass(FIELD_CLASSNAME, MORPHO_GETCLASSDEFINITION(Field), objclass); object_setveneerclass(OBJECT_FIELD, fieldclass); morpho_defineerror(FIELD_INDICESOUTSIDEBOUNDS, ERROR_HALT, FIELD_INDICESOUTSIDEBOUNDS_MSG); morpho_defineerror(FIELD_INVLDINDICES, ERROR_HALT, FIELD_INVLDINDICES_MSG); morpho_defineerror(FIELD_ARITHARGS, ERROR_HALT, FIELD_ARITHARGS_MSG); morpho_defineerror(FIELD_INCOMPATIBLEMATRICES, ERROR_HALT, FIELD_INCOMPATIBLEMATRICES_MSG); morpho_defineerror(FIELD_INCOMPATIBLEVAL, ERROR_HALT, FIELD_INCOMPATIBLEVAL_MSG); morpho_defineerror(FIELD_ARGS, ERROR_HALT, FIELD_ARGS_MSG); morpho_defineerror(FIELD_OP, ERROR_HALT, FIELD_OP_MSG); morpho_defineerror(FIELD_OPRETURN, ERROR_HALT, FIELD_OPRETURN_MSG); morpho_defineerror(FIELD_MESHARG, ERROR_HALT, FIELD_MESHARG_MSG); } #endif ================================================ FILE: src/geometry/field.h ================================================ /** @file field.h * @author T J Atherton * * @brief Fields */ #ifndef field_h #define field_h #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include "object.h" #include "mesh.h" #include "matrix.h" #include /* ------------------------------------------------------- * Field objects * ------------------------------------------------------- */ extern objecttype objectfieldtype; #define OBJECT_FIELD objectfieldtype typedef struct { object obj; objectmesh *mesh; /** The mesh the selection is referring to */ unsigned int ngrades; /** Number of grades */ unsigned int *dof; /** number of degrees of freedom per entry in each grade */ unsigned int *offset; /** Offsets into the store for each grade */ value prototype; /** Prototype object */ unsigned int psize; /** Number of dofs per copy of the prototype */ unsigned int nelements; /** Total number of elements in the fireld */ void *pool; /** Pool of statically allocated objects */ value fnspc; /** Function space used */ objectmatrix data; /** Underlying data store */ } objectfield; /** Tests whether an object is a field */ #define MORPHO_ISFIELD(val) object_istype(val, OBJECT_FIELD) /** Gets the object as a field */ #define MORPHO_GETFIELD(val) ((objectfield *) MORPHO_GETOBJECT(val)) /** Creates an empty field object */ objectfield *object_newfield(objectmesh *mesh, value prototype, value disc, unsigned int *shape); /* ------------------------------------------------------- * Indexing a Field * ------------------------------------------------------- */ typedef struct { grade g; // The grade elementid id; // The element int indx; // Quantity index } fieldindx; /* ------------------------------------------------------- * Field class * ------------------------------------------------------- */ extern value field_gradeoption; #define FIELD_CLASSNAME "Field" #define FIELD_GRADEOPTION "grade" #define FIELD_FESPACEOPTION "finiteelementspace" #define FIELD_OP_METHOD "op" #define FIELD_SHAPE_METHOD "shape" #define FIELD_FESPACE_METHOD "finiteelementspace" #define FIELD_PROTOTYPE_METHOD "prototype" #define FIELD_MESH_METHOD "mesh" #define FIELD_LINEARIZE_METHOD "linearize" #define FIELD__LINEARIZE_METHOD "__linearize" #define FIELD_INDICESOUTSIDEBOUNDS "FldBnds" #define FIELD_INDICESOUTSIDEBOUNDS_MSG "Field index out of bounds." #define FIELD_INVLDINDICES "FldInvldIndx" #define FIELD_INVLDINDICES_MSG "Field indices must be numerical." #define FIELD_ARITHARGS "FldInvldArg" #define FIELD_ARITHARGS_MSG "Field arithmetic methods expect a field or number as their argument." #define FIELD_INCOMPATIBLEMATRICES "FldIncmptbl" #define FIELD_INCOMPATIBLEMATRICES_MSG "Fields have incompatible shape." #define FIELD_INCOMPATIBLEVAL "FldIncmptblVal" #define FIELD_INCOMPATIBLEVAL_MSG "Assignment value has incompatible shape with field elements." #define FIELD_ARGS "FldArgs" #define FIELD_ARGS_MSG "Field allows 'grade' as an optional argument." #define FIELD_OP "FldOp" #define FIELD_OP_MSG "Method 'op' requires a callable object as the first argument; all other arguments must be fields of compatible shape." #define FIELD_OPRETURN "FldOpFn" #define FIELD_OPRETURN_MSG "Could not construct a Field from the return value of the function passed to 'op'." #define FIELD_MESHARG "FldMshArg" #define FIELD_MESHARG_MSG "Field expects a mesh as its first argurment" objectfield *field_clone(objectfield *f); void field_zero(objectfield *field); bool field_addpool(objectfield *field); unsigned int field_sizeprototype(value prototype); unsigned int field_dofforgrade(objectfield *f, grade g); bool field_getelement(objectfield *field, grade grade, elementid el, int indx, value *out); bool field_getelementwithindex(objectfield *field, int indx, value *out); bool field_getindex(objectfield *field, grade grade, elementid el, int indx, int *out); bool field_getelementaslist(objectfield *field, grade grade, elementid el, int indx, unsigned int *nentries, double **out); bool field_setelement(objectfield *field, grade grade, elementid el, int indx, value val); bool field_setelementwithindex(objectfield *field, int ix, value val); void field_initialize(void); #endif #endif /* field_h */ ================================================ FILE: src/geometry/functional.c ================================================ /** @file functional.c * @author T J Atherton * * @brief Functionals */ #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include #include #include "functional.h" #include "morpho.h" #include "classes.h" #include "common.h" #include "threadpool.h" #include "matrix.h" #include "sparse.h" #include "geometry.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif value functional_gradeproperty; value functional_fieldproperty; //static value functional_functionproperty; /* ********************************************************************** * Utility functions * ********************************************************************** */ double fddelta1, // = pow(MORPHO_EPS, 1.0/3.0), fddelta2; // = pow(MORPHO_EPS, 1.0/4.0); // Estimates the correct stepsize for cell centered finite differences double functional_fdstepsize(double x, int order) { double h = fddelta1; if (order==2) h = fddelta2; // h should be multiplied by an estimate of the lengthscale over which f changes, // | f / f''' | ^ (1/3) double absx = fabs(x); if (absx>1) h*=absx; // In the absence of other information, and unless we're near 0, use x as the best estimate. // Ensure stepsize results in a representable number volatile double temp = x+h; // Prevent compiler optimizing this away return temp-x; } static void functional_clearmapinfo(functional_mapinfo *info) { info->mesh=NULL; info->field=NULL; info->sel=NULL; info->g=0; info->id=0; info->integrand=NULL; info->grad=NULL; info->dependencies=NULL; info->cloneref=NULL; info->freeref=NULL; info->ref=NULL; info->sym=SYMMETRY_NONE; } /** Validates the arguments provided to a functional * @param[in] v - vm * @param[in] nargs - number of arguments * @param[in] args - the arguments * @param[out] info - mapinfo block */ bool functional_validateargs(vm *v, int nargs, value *args, functional_mapinfo *info) { functional_clearmapinfo(info); for (unsigned int i=0; imesh = MORPHO_GETMESH(MORPHO_GETARG(args,i)); } else if (MORPHO_ISSELECTION(MORPHO_GETARG(args,i))) { info->sel = MORPHO_GETSELECTION(MORPHO_GETARG(args,i)); } else if (MORPHO_ISFIELD(MORPHO_GETARG(args,i))) { info->field = MORPHO_GETFIELD(MORPHO_GETARG(args,i)); if (info->field) info->mesh = (info->field->mesh); // Retrieve the mesh from the field } else if (MORPHO_ISINTEGER(MORPHO_GETARG(args,i))) { info->id = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args,i)); } } if (info->mesh) return true; morpho_runtimeerror(v, FUNC_INTEGRAND_MESH); return false; } /* ********************************************************************** * Common routines * ********************************************************************** */ /** Internal function to count the number of elements */ static bool functional_countelements(vm *v, objectmesh *mesh, grade g, int *n, objectsparse **s) { /* How many elements? */ if (g==MESH_GRADE_VERTEX) { *n=mesh->vert->ncols; } else { *s=mesh_getconnectivityelement(mesh, 0, g); if (*s) { *n=(*s)->ccs.ncols; // Number of elements } else { morpho_runtimeerror(v, FUNC_ELNTFND, g); return false; } } return true; } static int functional_symmetryimagelistfn(const void *a, const void *b) { elementid i=*(elementid *) a; elementid j=*(elementid *) b; return (int) i-j; } /** Gets a list of all image elements (those that map onto a target element) * @param[in] mesh - the mesh * @param[in] g - grade to look up * @param[in] sort - whether to sort othe results * @param[out] ids - varray is filled with image element ids */ void functional_symmetryimagelist(objectmesh *mesh, grade g, bool sort, varray_elementid *ids) { objectsparse *conn=mesh_getconnectivityelement(mesh, g, g); ids->count=0; // Initialize the varray if (conn) { int i,j; void *ctr=sparsedok_loopstart(&conn->dok); while (sparsedok_loop(&conn->dok, &ctr, &i, &j)) { varray_elementidwrite(ids, j); } if (sort) qsort(ids->data, ids->count, sizeof(elementid), functional_symmetryimagelistfn); } } /** Sums forces on symmetry vertices * @param[in] mesh - mesh object * @param frc - force object; updated if symmetries are present. */ bool functional_symmetrysumforces(objectmesh *mesh, objectmatrix *frc) { objectsparse *s=mesh_getconnectivityelement(mesh, 0, 0); // Checking for vertex symmetries if (s) { int i,j; void *ctr = sparsedok_loopstart(&s->dok); double *fi, *fj, fsum[mesh->dim]; while (sparsedok_loop(&s->dok, &ctr, &i, &j)) { if (matrix_getcolumn(frc, i, &fi) && matrix_getcolumn(frc, j, &fj)) { for (unsigned int k=0; kdim; k++) fsum[k]=fi[k]+fj[k]; matrix_setcolumn(frc, i, fsum); matrix_setcolumn(frc, j, fsum); } } } return s; } bool functional_inlist(varray_elementid *list, elementid id) { for (unsigned int i=0; icount; i++) if (list->data[i]==id) return true; return false; } bool functional_containsvertex(int nv, int *vid, elementid id) { for (unsigned int i=0; imesh; objectselection *sel = info->sel; grade g = info->g; functional_integrand *integrand = info->integrand; void *ref = info->ref; objectsparse *s=NULL; int n=0; if (!functional_countelements(v, mesh, g, &n, &s)) return false; /* Find any image elements so we can skip over them */ varray_elementid imageids; varray_elementidinit(&imageids); functional_symmetryimagelist(mesh, g, true, &imageids); if (n>0) { int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices int sindx=0; // Index into imageids array double sum=0.0, c=0.0, y, t, result; if (sel) { // Loop over selection if (sel->selected[g].count>0) for (unsigned int k=0; kselected[g].capacity; k++) { if (!MORPHO_ISINTEGER(sel->selected[g].contents[k].key)) continue; elementid i = MORPHO_GETINTEGERVALUE(sel->selected[g].contents[k].key); // Skip this element if it's an image element: if ((imageids.count>0) && (sindxccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if ((*integrand) (v, mesh, i, nv, vid, ref, &result)) { y=result-c; t=sum+y; c=(t-sum)-y; sum=t; // Kahan summation } else goto functional_sumintegrand_cleanup; } } } else { // Loop over elements for (elementid i=0; i0) && (sindxccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if ((*integrand) (v, mesh, i, nv, vid, ref, &result)) { y=result-c; t=sum+y; c=(t-sum)-y; sum=t; // Kahan summation } else goto functional_sumintegrand_cleanup; } } } *out=MORPHO_FLOAT(sum); } success=true; functional_sumintegrand_cleanup: varray_elementidclear(&imageids); return success; } /** Calculate an integrand * @param[in] v - virtual machine in use * @param[in] info - map info * @param[out] out - a matrix of integrand values * @returns true on success, false otherwise. Error reporting through VM. */ bool functional_mapintegrandX(vm *v, functional_mapinfo *info, value *out) { objectmesh *mesh = info->mesh; objectselection *sel = info->sel; grade g = info->g; functional_integrand *integrand = info->integrand; void *ref = info->ref; objectsparse *s=NULL; objectmatrix *new=NULL; bool ret=false; int n=0; /* How many elements? */ if (!functional_countelements(v, mesh, g, &n, &s)) return false; /* Find any image elements so we can skip over them */ varray_elementid imageids; varray_elementidinit(&imageids); functional_symmetryimagelist(mesh, g, true, &imageids); /* Create the output matrix */ if (n>0) { new=object_newmatrix(1, n, true); if (!new) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } if (new) { int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices int sindx=0; // Index into imageids array double result; if (sel) { // Loop over selection if (sel->selected[g].count>0) for (unsigned int k=0; kselected[g].capacity; k++) { if (!MORPHO_ISINTEGER(sel->selected[g].contents[k].key)) continue; elementid i = MORPHO_GETINTEGERVALUE(sel->selected[g].contents[k].key); // Skip this element if it's an image element if ((imageids.count>0) && (sindxccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if ((*integrand) (v, mesh, i, nv, vid, ref, &result)) { matrix_setelement(new, 0, i, result); } else goto functional_mapintegrand_cleanup; } } } else { // Loop over elements for (elementid i=0; i0) && (sindxccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if ((*integrand) (v, mesh, i, nv, vid, ref, &result)) { matrix_setelement(new, 0, i, result); } else goto functional_mapintegrand_cleanup; } } } *out = MORPHO_OBJECT(new); ret=true; } varray_elementidclear(&imageids); return ret; functional_mapintegrand_cleanup: object_free((object *) new); varray_elementidclear(&imageids); return false; } /** Calculate gradient * @param[in] v - virtual machine in use * @param[in] info - map info structure * @param[out] out - a matrix of integrand values * @returns true on success, false otherwise. Error reporting through VM. */ bool functional_mapgradientX(vm *v, functional_mapinfo *info, value *out) { objectmesh *mesh = info->mesh; objectselection *sel = info->sel; grade g = info->g; functional_gradient *grad = info->grad; void *ref = info->ref; symmetrybhvr sym = info->sym; objectsparse *s=NULL; objectmatrix *frc=NULL; bool ret=false; int n=0; /* How many elements? */ if (!functional_countelements(v, mesh, g, &n, &s)) return false; /* Create the output matrix */ if (n>0) { frc=object_newmatrix(mesh->vert->nrows, mesh->vert->ncols, true); if (!frc) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } if (frc) { int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices if (sel) { // Loop over selection if (sel->selected[g].count>0) for (unsigned int k=0; kselected[g].capacity; k++) { if (!MORPHO_ISINTEGER(sel->selected[g].contents[k].key)) continue; elementid i = MORPHO_GETINTEGERVALUE(sel->selected[g].contents[k].key); if (s) sparseccs_getrowindices(&s->ccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if (!(*grad) (v, mesh, i, nv, vid, ref, frc)) goto functional_mapgradient_cleanup; } } } else { // Loop over elements for (elementid i=0; iccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if (!(*grad) (v, mesh, i, nv, vid, ref, frc)) goto functional_mapgradient_cleanup; } } } if (sym==SYMMETRY_ADD) functional_symmetrysumforces(mesh, frc); *out = MORPHO_OBJECT(frc); ret=true; } functional_mapgradient_cleanup: if (!ret) object_free((object *) frc); return ret; } /** Calculate field gradient * @param[in] v - virtual machine in use * @param[in] info - map info structure * @param[out] out - a field of integrand values * @returns true on success, false otherwise. Error reporting through VM. */ bool functional_mapfieldgradientX(vm *v, functional_mapinfo *info, value *out) { objectmesh *mesh = info->mesh; objectfield *field = info->field; objectselection *sel = info->sel; objectfield *grad=NULL; grade g = info->g; functional_fieldgradient *fgrad = info->fieldgrad; void *ref = info->ref; objectsparse *s=NULL; bool ret=false; int n=0; /* How many elements? */ if (!functional_countelements(v, mesh, g, &n, &s)) return false; /* Create the output field */ if (n>0) { grad=object_newfield(mesh, field->prototype, field->fnspc, field->dof); if (!grad) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } field_zero(grad); } if (grad) { int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices if (sel) { // Loop over selection if (sel->selected[g].count>0) for (unsigned int k=0; kselected[g].capacity; k++) { if (!MORPHO_ISINTEGER(sel->selected[g].contents[k].key)) continue; elementid i = MORPHO_GETINTEGERVALUE(sel->selected[g].contents[k].key); if (s) sparseccs_getrowindices(&s->ccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if (!(*fgrad) (v, mesh, i, nv, vid, ref, grad)) goto functional_mapfieldgradient_cleanup; } } } else { // Loop over elements for (elementid i=0; iccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if (!(*fgrad) (v, mesh, i, nv, vid, ref, grad)) goto functional_mapfieldgradient_cleanup; } } } *out = MORPHO_OBJECT(grad); ret=true; } functional_mapfieldgradient_cleanup: if (!ret) object_free((object *) grad); return ret; } static bool functional_numericalremotegradient(vm *v, functional_mapinfo *info, objectsparse *conn, elementid remoteid, elementid i, int nv, int *vid, objectmatrix *frc); /* Calculates a numerical gradient */ bool functional_numericalgradient(vm *v, objectmesh *mesh, elementid i, int nv, int *vid, functional_integrand *integrand, void *ref, objectmatrix *frc) { double f0,fp,fm,x0,eps=1e-6; // Loop over vertices in element for (unsigned int j=0; jdim; k++) { matrix_getelement(frc, k, vid[j], &f0); matrix_getelement(mesh->vert, k, vid[j], &x0); eps=functional_fdstepsize(x0, 1); matrix_setelement(mesh->vert, k, vid[j], x0+eps); if (!(*integrand) (v, mesh, i, nv, vid, ref, &fp)) return false; matrix_setelement(mesh->vert, k, vid[j], x0-eps); if (!(*integrand) (v, mesh, i, nv, vid, ref, &fm)) return false; matrix_setelement(mesh->vert, k, vid[j], x0); matrix_setelement(frc, k, vid[j], f0+(fp-fm)/(2*eps)); } } return true; } static bool functional_numericalremotegradient(vm *v, functional_mapinfo *info, objectsparse *conn, elementid remoteid, elementid i, int nv, int *vid, objectmatrix *frc) { objectmesh *mesh = info->mesh; double f0,fp,fm,x0,eps=1e-6; // Loop over coordinates for (unsigned int k=0; kdim; k++) { matrix_getelement(frc, k, remoteid, &f0); matrix_getelement(mesh->vert, k, remoteid, &x0); eps=functional_fdstepsize(x0, 1); matrix_setelement(mesh->vert, k, remoteid, x0+eps); if (!(*info->integrand) (v, mesh, i, nv, vid, info->ref, &fp)) return false; matrix_setelement(mesh->vert, k, remoteid, x0-eps); if (!(*info->integrand) (v, mesh, i, nv, vid, info->ref, &fm)) return false; matrix_setelement(mesh->vert, k, remoteid, x0); matrix_setelement(frc, k, remoteid, f0+(fp-fm)/(2*eps)); } return true; } double functional_sumlist(double *list, unsigned int nel) { double sum=0.0, c=0.0, y,t; for (unsigned int i=0; imesh; objectselection *sel = info->sel; grade g = info->g; functional_integrand *integrand = info->integrand; void *ref = info->ref; symmetrybhvr sym = info->sym; objectsparse *s=NULL; objectmatrix *frc=NULL; bool ret=false; int n=0; varray_elementid dependencies; if (info->dependencies) varray_elementidinit(&dependencies); /* Find any image elements so we can skip over them */ varray_elementid imageids; varray_elementidinit(&imageids); functional_symmetryimagelist(mesh, g, true, &imageids); /* How many elements? */ if (!functional_countelements(v, mesh, g, &n, &s)) return false; /* Create the output matrix */ if (n>0) { frc=object_newmatrix(mesh->vert->nrows, mesh->vert->ncols, true); if (!frc) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } if (frc) { int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices int sindx=0; // Index into imageids array if (sel) { // Loop over selection if (sel->selected[g].count>0) for (unsigned int k=0; kselected[g].capacity; k++) { if (!MORPHO_ISINTEGER(sel->selected[g].contents[k].key)) continue; elementid i = MORPHO_GETINTEGERVALUE(sel->selected[g].contents[k].key); if (s) sparseccs_getrowindices(&s->ccs, i, &nv, &vid); else vertexid=i; // Skip this element if it's an image element if ((imageids.count>0) && (sindx0) { if (!functional_numericalgradient(v, mesh, i, nv, vid, integrand, ref, frc)) goto functional_numericalgradient_cleanup; if (info->dependencies && // Loop over dependencies if there are any (info->dependencies) (info, i, &dependencies)) { for (int j=0; j0) && (sindxccs, i, &nv, &vid); else vertexid=i; if (vid && nv>0) { if (!functional_numericalgradient(v, mesh, i, nv, vid, integrand, ref, frc)) goto functional_numericalgradient_cleanup; if (info->dependencies && // Loop over dependencies if there are any (info->dependencies) (info, i, &dependencies)) { for (int j=0; jdependencies) varray_elementidclear(&dependencies); if (!ret) object_free((object *) frc); return ret; } bool functional_mapnumericalfieldgradientX(vm *v, functional_mapinfo *info, value *out) { objectmesh *mesh = info->mesh; objectselection *sel = info->sel; objectfield *field = info->field; grade grd = info->g; functional_integrand *integrand = info->integrand; void *ref = info->ref; //symmetrybhvr sym = info->sym; double eps=1e-6; bool ret=false; objectsparse *conn=mesh_getconnectivityelement(mesh, 0, grd); // Connectivity for the element /* Create the output field */ objectfield *grad=object_newfield(mesh, field->prototype, field->fnspc, field->dof); if (!grad) return false; field_zero(grad); /* Loop over elements in the field */ for (grade g=0; gngrades; g++) { if (field->dof[g]==0) continue; int nentries=1, *entries, nv, *vid; double fr,fl; objectsparse *rconn=mesh_addconnectivityelement(mesh, grd, g); // Find dependencies for the grade for (elementid id=0; idccs, entries[i], &nv, &vid); } else { if (sel) if (!selection_isselected(sel, grd, id)) continue; nv=1; vid=&id; } /* Loop over dofs in field entry */ for (int j=0; jpsize*field->dof[g]; j++) { int k=field->offset[g]+id*field->psize*field->dof[g]+j; double fld=field->data.elements[k]; eps=functional_fdstepsize(fld, 1); field->data.elements[k]+=eps; if (!(*integrand) (v, mesh, id, nv, vid, ref, &fr)) goto functional_mapnumericalfieldgradient_cleanup; field->data.elements[k]=fld-eps; if (!(*integrand) (v, mesh, id, nv, vid, ref, &fl)) goto functional_mapnumericalfieldgradient_cleanup; field->data.elements[k]=fld; grad->data.elements[k]+=(fr-fl)/(2*eps); } } } } *out = MORPHO_OBJECT(grad); ret=true; } functional_mapnumericalfieldgradient_cleanup: if (!ret) object_free((object *) grad); return ret; } /* ********************************************************************** * Multithreaded map functions * ********************************************************************** */ threadpool functional_pool; bool functional_poolinitialized; /** Gradient function */ typedef bool (functional_mapfn) (vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, void *out); /** Optionally process results from mapfn */ typedef bool (functional_processfn) (void *task); /** Work to be done is divided into "tasks" which are then dispatched to the threadpool for execution. */ typedef struct { elementid start, end; /* Start and end indices for the task */ elementid id; /* Current element id */ elementid nel; /* Current element id */ varray_elementid *skip; /* Sorted list of element ids to skip; set to NULL if not needed */ unsigned int sindx; grade g; /* Grade of element */ objectsparse *conn; /* Connectivity matrix */ functional_mapfn *mapfn; /* Map function */ functional_processfn *processfn; /* Post process results */ vm *v; /* Virtual machine in use */ objectmesh *mesh; /* Mesh in use */ objectfield *field; /* Field in use */ objectselection *selection; /* Selection to use if any */ void *ref; /* Ref as an opaque pointer */ void *result; /* Result of individual element as an opaque pointer */ void *out; /* Overall output as an opaque pointer */ _MORPHO_PADDING; } functional_task; /* Initialize a task structure */ void functionaltask_init(functional_task *task, elementid start, elementid end, functional_mapinfo *info) { task->start=start; task->end=end; task->nel=0; task->id=0; task->g=(info ? info->g : 0); task->skip=NULL; task->sindx=0; task->conn=NULL; task->mapfn=NULL; task->processfn=NULL; task->mesh=(info ? info->mesh : NULL); task->field=(info ? info->field : NULL); task->selection=(info ? info->sel : NULL); task->v=NULL; task->ref=(info ? info->ref : NULL); task->out=NULL; task->result=NULL; } /** Check if we should skip element id */ bool functional_checkskip(functional_task *task) { if ((task->skip) && (task->sindxskip->count) && task->skip->data[task->sindx]==task->id) { task->sindx++; return true; } return false; } /** Worker function to map a function over elements */ bool functional_mapfn_elements(void *arg) { functional_task *task = (functional_task *) arg; dictionary *selected=NULL; elementid *vid=&task->id; /* Will hold element definition */ int nv=1; /* Number of vertices per element; default to 1 */ if (task->selection) { selected=&task->selection->selected[task->g]; if (selected->count==0) return true; } // Loop over required elements for (elementid i=task->start; iend; i++) { if (selected) { // Skip empty dictionary entries if (!MORPHO_ISINTEGER(selected->contents[i].key)) continue; // Fetch the element id from the dictionary task->id = MORPHO_GETINTEGERVALUE(selected->contents[i].key); } else task->id = i; // Skip this element if it's an image element if (functional_checkskip(task)) continue; // Fetch element definition if (task->conn) { if (!sparseccs_getrowindices(&task->conn->ccs, task->id, &nv, &vid)) return false; } // Perform the map function if (!(*task->mapfn) (task->v, task->mesh, task->id, nv, vid, task->ref, task->result)) return false; // Perform post-processing if needed if (task->processfn) if (!(*task->processfn) (task)) return false; // Clean out temporary objects vm_cleansubkernel(task->v); } return true; } /** Dispatches tasks to threadpool */ bool functional_parallelmap(int ntasks, functional_task *tasks) { int nthreads = morpho_threadnumber(); if (!nthreads) nthreads=1; if (!functional_poolinitialized) { functional_poolinitialized=threadpool_init(&functional_pool, nthreads); if (!functional_poolinitialized) return false; } for (int i=0; i0) { for (int i=0; i0; i++) { binsizes[i]++; rem--; } } int bindx=0; for (int i=0; i<=nbins; i++) { binbounds[i]=bindx; bindx+=binsizes[i]; } } /** Prepare tasks for submitting * @param[in] v - Virtual machine to use * @param[in] info - Info structure with functional information * @param[in] ntask - Number of tasks * @param[out] task - Task structures updated * @param[out] imageids - Updated to include symmetry image ids */ int functional_preparetasks(vm *v, functional_mapinfo *info, int ntask, functional_task *task, varray_elementid *imageids) { int nel=0; objectsparse *conn=NULL; // The associated connectivity matrix if any /* Work out the number of elements */ if (!functional_countelements(v, info->mesh, info->g, &nel, &conn)) return false; int cmax=nel; if (info->sel) { cmax=info->sel->selected[info->g].capacity; } int bins[ntask+1]; functional_binbounds(cmax, ntask, bins); /* Ensure all mesh topology matrices have CCS */ int maxgrade=mesh_maxgrade(info->mesh); for (int i=0; i<=maxgrade; i++) { for (int j=0; j<=maxgrade; j++) { objectsparse *s = mesh_getconnectivityelement(info->mesh, i, j); if (s) sparse_checkformat(s, SPARSE_CCS, true, false); } } /* Find any image elements so they can be skipped */ functional_symmetryimagelist(info->mesh, info->g, true, imageids); if (info->field) field_addpool(info->field); vm *subkernels[ntask]; if (!vm_subkernels(v, ntask, subkernels)) return false; /** Initialize task structures */ for (int i=0; icount>0) task[i].skip=imageids; } return true; } /** Cleans up task structures after executing them. */ void functional_cleanuptasks(vm *v, int ntask, functional_task *task) { for (int i=0; iout; double y=ks->result-ks->c; double t=ks->sum+y; ks->c=(t-ks->sum)-y; ks->sum=t; // Kahan summation return true; } /** Sum the integrand, mapping over integrand function */ bool functional_sumintegrand(vm *v, functional_mapinfo *info, value *out) { int ntask=morpho_threadnumber(); if (!ntask) return functional_sumintegrandX(v, info, out); functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); if (!functional_preparetasks(v, info, ntask, task, &imageids)) return false; functional_sumintermediate sums[ntask]; for (int i=0; iintegrand; task[i].processfn=functional_sumintegrandprocessfn; task[i].result=(void *) &sums[i].result; task[i].out=(void *) &sums[i]; sums[i].c=0.0; sums[i].sum=0.0; } functional_parallelmap(ntask, task); // Sum up the results from each task... double sumlist[ntask]; for (int i=0; imesh; grade g = info->g; elementid id = info->id; functional_integrand *integrand = info->integrand; void *ref = info->ref; objectsparse *s=NULL; bool ret=false; int n=0; /* How many elements? */ if (!functional_countelements(v, mesh, g, &n, &s)) return false; // Check if the requested element id is out of range if (id>=n) return false; int vertexid; // Use this if looping over grade 0 int *vid=(g==0 ? &vertexid : NULL), nv=(g==0 ? 1 : 0); // The vertex indices if (s) sparseccs_getrowindices(&s->ccs, id, &nv, &vid); else vertexid=id; double result=0.0; if (vid && nv>0) { if (! (*integrand) (v, mesh, id, nv, vid, ref, &result)) { return false; } } *out = MORPHO_FLOAT(result); ret=true; return ret; } /** Set relevant matrix element to the result of the integrand */ bool functional_mapintegrandprocessfn(void *arg) { functional_task *task = (functional_task *) arg; objectmatrix *new = (objectmatrix *) task->out; matrix_setelement(new, 0, task->id, *(double *) task->result); return true; } /** Map integrand function, storing the results in a matrix */ bool functional_mapintegrand(vm *v, functional_mapinfo *info, value *out) { int ntask=morpho_threadnumber(); if (!ntask) return functional_mapintegrandX(v, info, out); functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); objectmatrix *new = NULL; if (!functional_preparetasks(v, info, ntask, task, &imageids)) return false; /* Create output matrix */ if (task[0].nel>0) { new=object_newmatrix(1, task[0].nel, true); if (!new) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } functional_sumintermediate sums[ntask]; for (int i=0; iintegrand; task[i].processfn=functional_mapintegrandprocessfn; task[i].result=(void *) &sums[i].result; task[i].out=(void *) new; } functional_parallelmap(ntask, task); // ...and return the result *out = MORPHO_OBJECT(new); functional_cleanuptasks(v, ntask, task); varray_elementidclear(&imageids); return true; } /* ---------------------------- * Map gradients * ---------------------------- */ /** Compute the gradient */ bool functional_mapgradient(vm *v, functional_mapinfo *info, value *out) { int success=false; int ntask=morpho_threadnumber(); if (!ntask) return functional_mapgradientX(v, info, out); functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); objectmatrix *new[ntask]; for (int i=0; imesh->vert->nrows, info->mesh->vert->ncols, true); if (!new[i]) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_mapgradient_cleanup; } task[i].mapfn=(functional_mapfn *) info->grad; task[i].result=(void *) new[i]; } functional_parallelmap(ntask, task); /* Then add up all the matrices */ for (int i=1; isym==SYMMETRY_ADD) functional_symmetrysumforces(info->mesh, new[0]); success=true; functional_mapgradient_cleanup: for (int i=1; idim; k++) { matrix_getelement(frc, k, i, &f0); matrix_getelement(mesh->vert, k, i, &x0); eps=functional_fdstepsize(x0, 1); matrix_setelement(mesh->vert, k, i, x0+eps); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fp)) return false; matrix_setelement(mesh->vert, k, i, x0-eps); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fm)) return false; matrix_setelement(mesh->vert, k, i, x0); matrix_setelement(frc, k, i, f0+(fp-fm)/(2*eps)); } return true; } /** Computes the gradient of element id with respect to its constituent vertices and any dependencies */ bool functional_numericalgradientmapfn(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, void *out) { bool success=true; functional_mapinfo *info=(functional_mapinfo *) ref; for (int i=0; iintegrand, info->ref, out)) return false; } // Now handle dependencies if (info->dependencies) { varray_elementid dependencies; varray_elementidinit(&dependencies); // Get list of vertices this element depends on if ((info->dependencies) (info, id, &dependencies)) { for (int j=0; jintegrand, info->ref, out)) success=false; } } varray_elementidclear(&dependencies); } return success; } /** Compute the gradient numerically */ bool functional_mapnumericalgradient(vm *v, functional_mapinfo *info, value *out) { int success=false; int ntask=morpho_threadnumber(); if (!ntask) return functional_mapnumericalgradientX(v, info, out); functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); objectmatrix *new[ntask]; // Create an output matrix for each thread for (int i=0; imesh->vert->nrows, info->mesh->vert->ncols, true); if (!new[i]) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_mapgradient_cleanup; } // Clone the vertex matrix for each thread meshclones[i]=*info->mesh; meshclones[i].vert=object_clonematrix(info->mesh->vert); task[i].mesh=&meshclones[i]; task[i].ref=(void *) info; // Use this to pass the info structure task[i].mapfn=functional_numericalgradientmapfn; task[i].result=(void *) new[i]; } functional_parallelmap(ntask, task); /* Then add up all the matrices */ for (int i=1; isym==SYMMETRY_ADD) functional_symmetrysumforces(info->mesh, new[0]); // ...and return the result *out = MORPHO_OBJECT(new[0]); functional_mapgradient_cleanup: // Free the temporary copies of the vertex matrices for (int i=0; imesh, info->field->prototype, info->field->fnspc, info->field->dof); if (!new[i]) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_mapfieldgradient_cleanup; } field_zero(new[i]); task[i].mapfn=(functional_mapfn *) info->fieldgrad; task[i].result=(void *) new[i]; } functional_parallelmap(ntask, task); /* Then add up all the fields using their underlying data stores */ for (int i=1; idata, &new[1]->data, &new[0]->data); // TODO: Use symmetry actions //if (info->sym==SYMMETRY_ADD) functional_symmetrysumforces(info->mesh, new[0]); success=true; functional_mapfieldgradient_cleanup: for (int i=1; ipsize*field->dof[g]; j++) { int k=(field->offset[g]+i)*field->psize*field->dof[g]+j; double f0=field->data.elements[k]; eps=functional_fdstepsize(f0, 1); field->data.elements[k]+=eps; if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fr)) return false; field->data.elements[k]=f0-eps; if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fl)) return false; field->data.elements[k]=f0; grad->data.elements[k]+=(fr-fl)/(2*eps); } return true; } typedef struct { objectfield *field; functional_integrand *integrand; fespace *disc; void *ref; } functional_numericalfieldgradientref; /** Computes the gradient of element id with respect to its constituent vertices and any dependencies */ bool functional_numericalfieldgradientmapfn(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, void *out) { functional_numericalfieldgradientref *tref=(functional_numericalfieldgradientref *) ref; if (tref->disc) { int nnodes=tref->disc->nnodes; fieldindx findx[nnodes]; if (fespace_doftofieldindx(tref->field, tref->disc, nv, vid, findx)) { for (int k=0; kfield, findx[k].g, findx[k].id, nv, vid, tref->integrand, tref->ref, out)) return false; } } } else { for (elementid k=0; kfield, MESH_GRADE_VERTEX, vid[k], nv, vid, tref->integrand, tref->ref, out)) return false; } } return true; } /** Compute the field gradient numerically */ bool functional_mapnumericalfieldgradient(vm *v, functional_mapinfo *info, value *out) { int success=false; int ntask=morpho_threadnumber(); //if (!ntask) return functional_mapnumericalfieldgradientX(v, info, out); if (ntask<1) ntask=1; functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); objectfield *new[ntask]; // Create an output field for each thread objectfield *fieldclones[ntask]; // Create clones of the field for each thread functional_numericalfieldgradientref tref[ntask]; for (int i=0; imesh, info->field->prototype, info->field->fnspc, info->field->dof); if (!new[i]) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_mapfieldgradient_cleanup; } field_zero(new[i]); // Clone the vertex matrix for each thread fieldclones[i]=field_clone(info->field); tref[i].ref=info->ref; if (info->cloneref) { tref[i].ref=(info->cloneref) (info->ref, info->field, fieldclones[i]); } else UNREACHABLE("Functional calls numericalfieldgradient but doesn't provide cloneref"); tref[i].integrand=info->integrand; tref[i].field=fieldclones[i]; tref[i].disc=NULL; if (MORPHO_ISFESPACE(tref[i].field->fnspc)) { tref[i].disc=MORPHO_GETFESPACE(tref[i].field->fnspc)->fespace; if (info->ggrade) { if (!fespace_lower(tref[i].disc, info->g, &tref[i].disc)) return false; } } task[i].ref=(void *) &tref[i]; // Use this to pass the info structure task[i].mapfn=functional_numericalfieldgradientmapfn; task[i].result=(void *) new[i]; } functional_parallelmap(ntask, task); /* Then add up all the fields */ for (int i=1; idata, &new[i]->data, &new[0]->data); success=true; // ...and return the result *out = MORPHO_OBJECT(new[0]); functional_mapfieldgradient_cleanup: for (int i=0; ifreeref) (info->freeref) (tref[i].ref); else if (info->cloneref) MORPHO_FREE(tref[i].ref); // Free the temporary copies of the fields object_free((object *) fieldclones[i]); } // Free spare output matrices for (int i=1; idok, i, j, &h0)) { if (!morpho_valuetofloat(h0, &f0)) return false; } sparsedok_insert(&A->dok, i, j, MORPHO_FLOAT(f0+val)); return true; } /** Computes the contribution to the hessian of element eid with respect to vertices i and j */ bool functional_numericalhess(vm *v, objectmesh *mesh, elementid eid, elementid i, elementid j, int nv, int *vid, functional_integrand *integrand, void *ref, objectsparse *hess) { double x0,y0,epsx=1e-4,epsy=1e-4; for (unsigned int k=0; kdim; k++) { // Loop over coordinates in vertex i matrix_getelement(mesh->vert, k, i, &x0); epsx=functional_fdstepsize(x0, 2); if (i==j) { // Use a special formula for diagonal elements double fc, fr, fl; if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fc)) return false; matrix_setelement(mesh->vert, k, i, x0+epsx); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fr)) return false; matrix_setelement(mesh->vert, k, i, x0-epsx); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fl)) return false; matrix_setelement(mesh->vert, k, i, x0); // Restore vertex to original position functional_sparseaccumulate(hess, i*mesh->dim+k, i*mesh->dim+k, (fr + fl - 2*fc)/(epsx*epsx)); } // Loop over coordinates in vertex j for (unsigned int l=0; //(i==j? k+1 : k); // Detect whether we're in an off diagonal block ldim; l++) { if (i==j && k==l) continue; double fll,frr,flr,frl; matrix_getelement(mesh->vert, l, j, &y0); epsy=functional_fdstepsize(y0, 2); matrix_setelement(mesh->vert, k, i, x0+epsx); matrix_setelement(mesh->vert, l, j, y0+epsy); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &frr)) return false; matrix_setelement(mesh->vert, l, j, y0-epsy); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &frl)) return false; matrix_setelement(mesh->vert, k, i, x0-epsx); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &fll)) return false; matrix_setelement(mesh->vert, l, j, y0+epsy); if (!(*integrand) (v, mesh, eid, nv, vid, ref, &flr)) return false; matrix_setelement(mesh->vert, k, i, x0); // Restore vertices to original position matrix_setelement(mesh->vert, l, j, y0); functional_sparseaccumulate(hess, i*mesh->dim+k, j*mesh->dim+l, (frr + fll - flr - frl)/(4*epsx*epsy)); //functional_sparseaccumulate(hess, j*mesh->dim+l, i*mesh->dim+k, (frr + fll - flr - frl)/(4*epsx*epsy)); } } return true; } /** Computes the gradient of element id with respect to its constituent vertices and any dependencies */ bool functional_numericalhessianmapfn(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, void *out) { bool success=true; functional_mapinfo *info=(functional_mapinfo *) ref; // TODO: Exploit symmetry of hessian to reduce work for (int i=0; iintegrand, info->ref, out)) return false; } } // Now handle dependencies if (info->dependencies) { varray_elementid dependencies; varray_elementidinit(&dependencies); // Get list of vertices this element depends on if ((info->dependencies) (info, id, &dependencies)) { for (int i=0; iintegrand, info->ref, out)) success=false; } } } varray_elementidclear(&dependencies); } return success; } static int _sparsecmp(const void *a, const void *b) { objectsparse *aa = *(objectsparse **) a; objectsparse *bb = *(objectsparse **) b; return bb->dok.dict.count - aa->dok.dict.count; } /** Compute the hessian numerically */ bool functional_mapnumericalhessian(vm *v, functional_mapinfo *info, value *out) { int success=false; int ntask=morpho_threadnumber(); if (ntask==0) ntask = 1; functional_task task[ntask]; varray_elementid imageids; varray_elementidinit(&imageids); objectsparse *new[ntask]; // Create an output matrix for each thread objectmesh meshclones[ntask]; // Create shallow clones of the mesh with different vertex matrices for (int i=0; imesh->dim*mesh_nvertices(info->mesh); // Create one output matrix per thread new[i]=object_newsparse(&N, &N); if (!new[i]) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_maphessian_cleanup; } // Clone the vertex matrix for each thread meshclones[i]=*info->mesh; meshclones[i].vert=object_clonematrix(info->mesh->vert); if (!meshclones[i].vert) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto functional_maphessian_cleanup; } task[i].mesh=&meshclones[i]; task[i].ref=(void *) info; // Use this to pass the info structure task[i].mapfn=functional_numericalhessianmapfn; task[i].result=(void *) new[i]; } functional_parallelmap(ntask, task); qsort(new, ntask, sizeof(objectsparse *), _sparsecmp); if (!sparse_checkformat(new[0], SPARSE_CCS, true, true)) { morpho_runtimeerror(v, SPARSE_OPFAILEDERR); goto functional_maphessian_cleanup; } /* Then add up all the matrices */ for (int i=1; idok.dict.count) continue; objectsparse out = MORPHO_STATICSPARSE(); sparsedok_init(&out.dok); sparseccs_init(&out.ccs); if (sparse_add(new[0], new[i], 1.0, 1.0, &out)==SPARSE_OK) { sparseccs_clear(&new[0]->ccs); new[0]->ccs = out.ccs; } else { morpho_runtimeerror(v, SPARSE_OPFAILEDERR); goto functional_maphessian_cleanup; } } success=true; // Use symmetry actions //if (info->sym==SYMMETRY_ADD) functional_symmetrysumforces(info->mesh, new[0]); sparsedok_clear(&new[0]->dok); // Remove dok info // ...and return the result *out = MORPHO_OBJECT(new[0]); functional_maphessian_cleanup: // Free the temporary copies of the vertex matrices for (int i=0; idim]; for (int j=0; jvert, vid[j], &x[j]); functional_vecsub(mesh->dim, x[1], x[0], s0); *out=functional_vecnorm(mesh->dim, s0); return true; } /** Calculate scaled gradient */ bool length_gradient_scale(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc, double scale) { double *x[nv], s0[mesh->dim], norm; for (int j=0; jvert, vid[j], &x[j]); functional_vecsub(mesh->dim, x[1], x[0], s0); norm=functional_vecnorm(mesh->dim, s0); if (normdim], normcx; for (int j=0; jvert, vid[j], &x[j]); if (mesh->dim==2) { functional_veccross2d(x[0], x[1], cx); normcx=fabs(cx[0]); } else { functional_veccross(x[0], x[1], cx); normcx=functional_vecnorm(mesh->dim, cx); } *out=0.5*normcx; return true; } /** Calculate gradient */ bool areaenclosed_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { double *x[nv], cx[3], s[3]; double norm; for (int j=0; jvert, vid[j], &x[j]); if (mesh->dim==3) { functional_veccross(x[0], x[1], cx); norm=functional_vecnorm(mesh->dim, cx); if (normdim==2) { functional_veccross2d(x[0], x[1], cx); } return true; } FUNCTIONAL_INIT(AreaEnclosed, MESH_GRADE_LINE) FUNCTIONAL_INTEGRAND(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_integrand) FUNCTIONAL_INTEGRANDFORELEMENT(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_integrand) FUNCTIONAL_NUMERICALGRADIENT(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_integrand, SYMMETRY_ADD) //FUNCTIONAL_GRADIENT(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_gradient, SYMMETRY_ADD) FUNCTIONAL_TOTAL(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_integrand) FUNCTIONAL_HESSIAN(AreaEnclosed, MESH_GRADE_LINE, areaenclosed_integrand) MORPHO_BEGINCLASS(AreaEnclosed) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, AreaEnclosed_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, AreaEnclosed_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRANDFORELEMENT_METHOD, AreaEnclosed_integrandForElement, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, AreaEnclosed_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, AreaEnclosed_hessian, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, AreaEnclosed_total, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Area * ---------------------------------------------- */ /** Calculate area */ bool area_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { if (nv!=3) return false; double *x[nv], s0[3], s1[3], cx[3]; for (int j=0; j<3; j++) { s0[j]=0; s1[j]=0; cx[j]=0; } for (int j=0; jvert, vid[j], &x[j]); functional_vecsub(mesh->dim, x[1], x[0], s0); functional_vecsub(mesh->dim, x[2], x[1], s1); functional_veccross(s0, s1, cx); *out=0.5*functional_vecnorm(3, cx); return true; } /** Calculate scaled gradient */ bool area_gradient_scale(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc, double scale) { double *x[nv], s0[3], s1[3], s01[3], s010[3], s011[3]; double norm; for (int j=0; j<3; j++) { s0[j]=0; s1[j]=0; s01[j]=0; s010[j]=0; s011[j]=0; } for (int j=0; jvert, vid[j], &x[j])) return false; functional_vecsub(mesh->dim, x[1], x[0], s0); functional_vecsub(mesh->dim, x[2], x[1], s1); functional_veccross(s0, s1, s01); norm=functional_vecnorm(3, s01); if (normdim, s010, s011, s0); matrix_addtocolumn(frc, vid[1], -0.5/norm*scale, s0); return true; } /** Calculate gradient */ bool area_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { return area_gradient_scale(v, mesh, id, nv, vid, NULL, frc, 1.0); } FUNCTIONAL_INIT(Area, MESH_GRADE_AREA) FUNCTIONAL_INTEGRAND(Area, MESH_GRADE_AREA, area_integrand) FUNCTIONAL_INTEGRANDFORELEMENT(Area, MESH_GRADE_AREA, area_integrand) FUNCTIONAL_GRADIENT(Area, MESH_GRADE_AREA, area_gradient, SYMMETRY_ADD) FUNCTIONAL_TOTAL(Area, MESH_GRADE_AREA, area_integrand) FUNCTIONAL_HESSIAN(Area, MESH_GRADE_AREA, area_integrand) MORPHO_BEGINCLASS(Area) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, Area_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, Area_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRANDFORELEMENT_METHOD, Area_integrandForElement, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, Area_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, Area_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, Area_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Enclosed volume * ---------------------------------------------- */ /** Calculate enclosed volume */ bool volumeenclosed_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { double *x[nv], cx[mesh->dim]; for (int j=0; jvert, vid[j], &x[j])) return false; functional_veccross(x[0], x[1], cx); *out=fabs(functional_vecdot(mesh->dim, cx, x[2]))/6.0; return true; } /** Calculate gradient */ bool volumeenclosed_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { double *x[nv], cx[mesh->dim], dot; for (int j=0; jvert, vid[j], &x[j]); functional_veccross(x[0], x[1], cx); dot=functional_vecdot(mesh->dim, cx, x[2]); if (fabs(dot)<=DBL_MIN) { morpho_runtimeerror(v, VOLUMEENCLOSED_ZERO); return false; } dot/=fabs(dot); matrix_addtocolumn(frc, vid[2], dot/6.0, cx); functional_veccross(x[1], x[2], cx); matrix_addtocolumn(frc, vid[0], dot/6.0, cx); functional_veccross(x[2], x[0], cx); matrix_addtocolumn(frc, vid[1], dot/6.0, cx); return true; } FUNCTIONAL_INIT(VolumeEnclosed, MESH_GRADE_AREA) FUNCTIONAL_INTEGRAND(VolumeEnclosed, MESH_GRADE_AREA, volumeenclosed_integrand) FUNCTIONAL_GRADIENT(VolumeEnclosed, MESH_GRADE_AREA, volumeenclosed_gradient, SYMMETRY_ADD) FUNCTIONAL_TOTAL(VolumeEnclosed, MESH_GRADE_AREA, volumeenclosed_integrand) FUNCTIONAL_HESSIAN(VolumeEnclosed, MESH_GRADE_AREA, volumeenclosed_integrand) MORPHO_BEGINCLASS(VolumeEnclosed) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, VolumeEnclosed_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, VolumeEnclosed_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, VolumeEnclosed_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, VolumeEnclosed_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, VolumeEnclosed_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Volume * ---------------------------------------------- */ /** Calculate enclosed volume */ bool volume_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { double *x[nv], s10[mesh->dim], s20[mesh->dim], s30[mesh->dim], cx[mesh->dim]; for (int j=0; jvert, vid[j], &x[j]); functional_vecsub(mesh->dim, x[1], x[0], s10); functional_vecsub(mesh->dim, x[2], x[0], s20); functional_vecsub(mesh->dim, x[3], x[0], s30); functional_veccross(s20, s30, cx); *out=fabs(functional_vecdot(mesh->dim, s10, cx))/6.0; return true; } /** Calculate scaled gradient */ bool volume_gradient_scale(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc, double scale) { double *x[nv], s10[mesh->dim], s20[mesh->dim], s30[mesh->dim]; double s31[mesh->dim], s21[mesh->dim], cx[mesh->dim], uu; for (int j=0; jvert, vid[j], &x[j]); functional_vecsub(mesh->dim, x[1], x[0], s10); functional_vecsub(mesh->dim, x[2], x[0], s20); functional_vecsub(mesh->dim, x[3], x[0], s30); functional_vecsub(mesh->dim, x[3], x[1], s31); functional_vecsub(mesh->dim, x[2], x[1], s21); functional_veccross(s20, s30, cx); uu=functional_vecdot(mesh->dim, s10, cx); uu=(uu>0 ? 1.0 : -1.0); matrix_addtocolumn(frc, vid[1], uu/6.0*scale, cx); functional_veccross(s31, s21, cx); matrix_addtocolumn(frc, vid[0], uu/6.0*scale, cx); functional_veccross(s30, s10, cx); matrix_addtocolumn(frc, vid[2], uu/6.0*scale, cx); functional_veccross(s10, s20, cx); matrix_addtocolumn(frc, vid[3], uu/6.0*scale, cx); return true; } /** Calculate gradient */ bool volume_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { return volume_gradient_scale(v, mesh, id, nv, vid, NULL, frc, 1.0); } FUNCTIONAL_INIT(Volume, MESH_GRADE_VOLUME) FUNCTIONAL_INTEGRAND(Volume, MESH_GRADE_VOLUME, volume_integrand) FUNCTIONAL_GRADIENT(Volume, MESH_GRADE_VOLUME, volume_gradient, SYMMETRY_ADD) FUNCTIONAL_TOTAL(Volume, MESH_GRADE_VOLUME, volume_integrand) FUNCTIONAL_HESSIAN(Volume, MESH_GRADE_VOLUME, volume_integrand) MORPHO_BEGINCLASS(Volume) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, Volume_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, Volume_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, Volume_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, Volume_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, Volume_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Scalar potential * ---------------------------------------------- */ static value scalarpotential_functionproperty; static value scalarpotential_gradfunctionproperty; typedef struct { value fn; } scalarpotentialref; bool scalarpotential_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, scalarpotentialref *ref) { ref->fn=MORPHO_NIL; return (objectinstance_getpropertyinterned(self, scalarpotential_functionproperty, &ref->fn) && MORPHO_ISCALLABLE(ref->fn)); } /** Evaluate the scalar potential */ bool scalarpotential_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { double *x; value fn = ((scalarpotentialref *) ref)->fn; value args[mesh->dim]; value ret; matrix_getcolumn(mesh->vert, id, &x); for (int i=0; idim; i++) args[i]=MORPHO_FLOAT(x[i]); if (morpho_call(v, fn, mesh->dim, args, &ret)) { return morpho_valuetofloat(ret, out); } return false; } /** Evaluate the gradient of the scalar potential */ bool scalarpotential_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { double *x; value fn = *(value *) ref; value args[mesh->dim]; value ret; matrix_getcolumn(mesh->vert, id, &x); for (int i=0; idim; i++) args[i]=MORPHO_FLOAT(x[i]); if (morpho_call(v, fn, mesh->dim, args, &ret)) { if (MORPHO_ISMATRIX(ret)) { objectmatrix *vf=MORPHO_GETMATRIX(ret); if (vf->nrows*vf->ncols==frc->nrows) { return matrix_addtocolumn(frc, id, 1.0, vf->elements); } } } return false; } /** Initialize a scalar potential */ value ScalarPotential_init(vm *v, int nargs, value *args) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), functional_gradeproperty, MORPHO_INTEGER(MESH_GRADE_VERTEX)); /* First argument is the potential function */ if (nargs>0) { if (MORPHO_ISCALLABLE(MORPHO_GETARG(args, 0))) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), scalarpotential_functionproperty, MORPHO_GETARG(args, 0)); } else morpho_runtimeerror(v, SCALARPOTENTIAL_FNCLLBL); } /* Second argument is the gradient of the potential function */ if (nargs>1) { if (MORPHO_ISCALLABLE(MORPHO_GETARG(args, 1))) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), scalarpotential_gradfunctionproperty, MORPHO_GETARG(args, 1)); } else morpho_runtimeerror(v, SCALARPOTENTIAL_FNCLLBL); } return MORPHO_NIL; } FUNCTIONAL_METHOD(ScalarPotential, integrand, MESH_GRADE_VERTEX, scalarpotentialref, scalarpotential_prepareref, functional_mapintegrand, scalarpotential_integrand, NULL, SCALARPOTENTIAL_FNCLLBL, SYMMETRY_NONE) FUNCTIONAL_METHOD(ScalarPotential, total, MESH_GRADE_VERTEX, scalarpotentialref, scalarpotential_prepareref, functional_sumintegrand, scalarpotential_integrand, NULL, SCALARPOTENTIAL_FNCLLBL, SYMMETRY_NONE) FUNCTIONAL_METHOD(ScalarPotential, hessian, MESH_GRADE_VERTEX, scalarpotentialref, scalarpotential_prepareref, functional_mapnumericalhessian, scalarpotential_integrand, NULL, SCALARPOTENTIAL_FNCLLBL, SYMMETRY_NONE) /** Evaluate a gradient */ value ScalarPotential_gradient(vm *v, int nargs, value *args) { functional_mapinfo info; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { value fn; // Check if a gradient function is available if (objectinstance_getpropertyinterned(MORPHO_GETINSTANCE(MORPHO_SELF(args)), scalarpotential_gradfunctionproperty, &fn)) { info.g = MESH_GRADE_VERTEX; info.grad = scalarpotential_gradient; info.ref = &fn; if (MORPHO_ISCALLABLE(fn)) { functional_mapgradient(v, &info, &out); } else morpho_runtimeerror(v, SCALARPOTENTIAL_FNCLLBL); } else if (objectinstance_getpropertyinterned(MORPHO_GETINSTANCE(MORPHO_SELF(args)), scalarpotential_functionproperty, &fn)) { // Otherwise try to use the regular scalar function value fn; if (objectinstance_getpropertyinterned(MORPHO_GETINSTANCE(MORPHO_SELF(args)), scalarpotential_functionproperty, &fn)) { info.g = MESH_GRADE_VERTEX; info.integrand = scalarpotential_integrand; info.ref = &fn; if (MORPHO_ISCALLABLE(fn)) { functional_mapnumericalgradient(v, &info, &out); } else morpho_runtimeerror(v, SCALARPOTENTIAL_FNCLLBL); } else morpho_runtimeerror(v, VM_OBJECTLACKSPROPERTY, SCALARPOTENTIAL_FUNCTION_PROPERTY); } else morpho_runtimeerror(v, VM_OBJECTLACKSPROPERTY, SCALARPOTENTIAL_FUNCTION_PROPERTY); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(ScalarPotential) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, ScalarPotential_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, ScalarPotential_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, ScalarPotential_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, ScalarPotential_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, ScalarPotential_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Linear Elasticity * ---------------------------------------------- */ static value linearelasticity_referenceproperty; static value linearelasticity_weightbyreferenceproperty; static value linearelasticity_poissonproperty; typedef struct { objectmesh *refmesh; grade grade; double lambda; // Lamé coefficients double mu; // } linearelasticityref; /** Calculates the Gram matrix */ void linearelasticity_calculategram(objectmatrix *vert, int dim, int nv, int *vid, objectmatrix *gram) { int gdim=nv-1; // Dimension of Gram matrix double *x[nv], // Positions of vertices s[gdim][nv]; // Side vectors for (int j=0; j for (int i=0; ielements[i+j*gdim]=functional_vecdot(dim, s[i], s[j]); } /** Calculate the linear elastic energy */ bool linearelasticity_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { double weight=0.0; linearelasticityref *info = (linearelasticityref *) ref; int gdim=nv-1; // Dimension of Gram matrix /* Construct static matrices */ double gramrefel[gdim*gdim], gramdefel[gdim*gdim], qel[gdim*gdim], rel[gdim*gdim], cgel[gdim*gdim]; objectmatrix gramref = MORPHO_STATICMATRIX(gramrefel, gdim, gdim); // Gram matrices objectmatrix gramdef = MORPHO_STATICMATRIX(gramdefel, gdim, gdim); // objectmatrix q = MORPHO_STATICMATRIX(qel, gdim, gdim); // Inverse of Gram in source domain objectmatrix r = MORPHO_STATICMATRIX(rel, gdim, gdim); // Intermediate calculations objectmatrix cg = MORPHO_STATICMATRIX(cgel, gdim, gdim); // Cauchy-Green strain tensor linearelasticity_calculategram(info->refmesh->vert, mesh->dim, nv, vid, &gramref); linearelasticity_calculategram(mesh->vert, mesh->dim, nv, vid, &gramdef); if (matrix_inverse(&gramref, &q)!=MATRIX_OK) return false; if (matrix_mul(&gramdef, &q, &r)!=MATRIX_OK) return false; matrix_identity(&cg); matrix_scale(&cg, -0.5); matrix_accumulate(&cg, 0.5, &r); double trcg=0.0, trcgcg=0.0; matrix_trace(&cg, &trcg); matrix_mul(&cg, &cg, &r); matrix_trace(&r, &trcgcg); if (!functional_elementsize(v, info->refmesh, info->grade, id, nv, vid, &weight)) return false; *out=weight*(info->mu*trcgcg + 0.5*info->lambda*trcg*trcg); return true; } /** Prepares the reference structure from the LinearElasticity object's properties */ bool linearelasticity_prepareref(objectinstance *self, linearelasticityref *ref) { bool success=false; value refmesh=MORPHO_NIL; value grade=MORPHO_NIL; value poisson=MORPHO_NIL; if (objectinstance_getpropertyinterned(self, linearelasticity_referenceproperty, &refmesh) && objectinstance_getpropertyinterned(self, functional_gradeproperty, &grade) && MORPHO_ISINTEGER(grade) && objectinstance_getpropertyinterned(self, linearelasticity_poissonproperty, &poisson) && MORPHO_ISNUMBER(poisson)) { ref->refmesh=MORPHO_GETMESH(refmesh); ref->grade=MORPHO_GETINTEGERVALUE(grade); double nu = MORPHO_GETFLOATVALUE(poisson); ref->mu=0.5/(1+nu); ref->lambda=nu/(1+nu)/(1-2*nu); success=true; } return success; } value LinearElasticity_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); /* First argument is the reference mesh */ if (nargs>0) { if (MORPHO_ISMESH(MORPHO_GETARG(args, 0))) { objectinstance_setproperty(self, linearelasticity_referenceproperty, MORPHO_GETARG(args, 0)); objectmesh *mesh = MORPHO_GETMESH(MORPHO_GETARG(args, 0)); objectinstance_setproperty(self, functional_gradeproperty, MORPHO_INTEGER(mesh_maxgrade(mesh))); objectinstance_setproperty(self, linearelasticity_poissonproperty, MORPHO_FLOAT(0.3)); } else morpho_runtimeerror(v, LINEARELASTICITY_REF); } else morpho_runtimeerror(v, LINEARELASTICITY_REF); /* Second (optional) argument is the grade to act on */ if (nargs>1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), functional_gradeproperty, MORPHO_GETARG(args, 1)); } } return MORPHO_NIL; } /** Integrand function */ value LinearElasticity_integrand(vm *v, int nargs, value *args) { functional_mapinfo info; linearelasticityref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (linearelasticity_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), &ref)) { info.g = ref.grade; info.integrand = linearelasticity_integrand; info.ref = &ref; functional_mapintegrand(v, &info, &out); } else morpho_runtimeerror(v, LINEARELASTICITY_PRP); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Total function */ value LinearElasticity_total(vm *v, int nargs, value *args) { functional_mapinfo info; linearelasticityref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (linearelasticity_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), &ref)) { info.g = ref.grade; info.integrand = linearelasticity_integrand; info.ref = &ref; functional_sumintegrand(v, &info, &out); } else morpho_runtimeerror(v, LINEARELASTICITY_PRP); } return out; } /** Integrand function */ value LinearElasticity_gradient(vm *v, int nargs, value *args) { functional_mapinfo info; linearelasticityref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (linearelasticity_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), &ref)) { info.g = ref.grade; info.integrand = linearelasticity_integrand; info.ref = &ref; info.sym = SYMMETRY_ADD; functional_mapnumericalgradient(v, &info, &out); } else morpho_runtimeerror(v, LINEARELASTICITY_PRP); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(LinearElasticity) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, LinearElasticity_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, LinearElasticity_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, LinearElasticity_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, LinearElasticity_gradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Hydrogel * ---------------------------------------------- */ static value hydrogel_aproperty; static value hydrogel_bproperty; static value hydrogel_cproperty; static value hydrogel_dproperty; static value hydrogel_phirefproperty; static value hydrogel_phi0property; typedef struct { objectmesh *refmesh; grade grade; double a, b, c, d, phiref; // Hydrogel coefficients value phi0; // Can be a number or a field. (Ensuring flexibility for supplying a phi0 field in the future) } hydrogelref; /** Prepares the reference structure from the object's properties */ bool hydrogel_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, hydrogelref *ref) { bool success=false; value refmesh=MORPHO_NIL, grade=MORPHO_NIL, phi0=MORPHO_NIL; value a=MORPHO_NIL, b=MORPHO_NIL, c=MORPHO_NIL, d=MORPHO_NIL, phiref=MORPHO_NIL; if (objectinstance_getpropertyinterned(self, linearelasticity_referenceproperty, &refmesh) && objectinstance_getpropertyinterned(self, functional_gradeproperty, &grade) && MORPHO_ISINTEGER(grade) && objectinstance_getpropertyinterned(self, hydrogel_aproperty, &a) && MORPHO_ISNUMBER(a) && objectinstance_getpropertyinterned(self, hydrogel_bproperty, &b) && MORPHO_ISNUMBER(b) && objectinstance_getpropertyinterned(self, hydrogel_cproperty, &c) && MORPHO_ISNUMBER(c) && objectinstance_getpropertyinterned(self, hydrogel_dproperty, &d) && MORPHO_ISNUMBER(d) && objectinstance_getpropertyinterned(self, hydrogel_phirefproperty, &phiref) && MORPHO_ISNUMBER(phiref) && objectinstance_getpropertyinterned(self, hydrogel_phi0property, &phi0) && (MORPHO_ISNUMBER(phi0) || MORPHO_ISFIELD(phi0))) { ref->refmesh=MORPHO_GETMESH(refmesh); ref->grade=MORPHO_GETINTEGERVALUE(grade); if (ref->grade<0) ref->grade=mesh_maxgrade(mesh); if (morpho_valuetofloat(a, &ref->a) && morpho_valuetofloat(b, &ref->b) && morpho_valuetofloat(c, &ref->c) && morpho_valuetofloat(d, &ref->d) && morpho_valuetofloat(phiref, &ref->phiref)) { ref->phi0 = phi0; success=true; } } return success; } /** Calculate the Hydrogel energy */ bool hydrogel_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { hydrogelref *info = (hydrogelref *) ref; value vphi0 = info->phi0; double V=0.0, V0=0.0, phi0=0.0; if (!functional_elementsize(v, info->refmesh, info->grade, id, nv, vid, &V0)) return false; if (!functional_elementsize(v, mesh, info->grade, id, nv, vid, &V)) return false; if (V0<1e-8) { morpho_runtimewarning(v, HYDROGEL_ZEEROREFELEMENT, id, V, V0); } if (fabs(V)phi0)) { objectfield *p = MORPHO_GETFIELD(info->phi0); if (!field_getelement(p, info->grade, id, 0, &vphi0)) { morpho_runtimeerror(v, HYDROGEL_FLDGRD, (unsigned int) info->grade); return false; } } if (MORPHO_ISNUMBER(vphi0)) { if (!morpho_valuetofloat(vphi0, &phi0)) return false; } double phi = phi0/(V/V0); double pr = info->phiref; if (phi<0 || 1-phi<0) { morpho_runtimewarning(v, HYDROGEL_BNDS, id, V, V0, phi, 1-phi); } if (phi>1-MORPHO_EPS) phi = 1-MORPHO_EPS; if (phia * phi*log(phi) + info->b * (1-phi)*log(1-phi) + info->c * phi*(1-phi))*V + info->d * (log(pr/phi)/3.0 - pow((pr/phi), (2.0/3)) + 1.0)*V0; if (phi<0 || 1-phi<0) return false; return true; } /** Calculate gradient */ bool hydrogel_gradient(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc) { hydrogelref *info = (hydrogelref *) ref; value vphi0 = info->phi0; double V=0.0, V0=0.0, phi0=0.0; if (!functional_elementsize(v, info->refmesh, info->grade, id, nv, vid, &V0)) return false; if (!functional_elementsize(v, mesh, info->grade, id, nv, vid, &V)) return false; if (V0<1e-8) { morpho_runtimewarning(v, HYDROGEL_ZEEROREFELEMENT, id, V, V0); } if (fabs(V)phi0)) { objectfield *p = MORPHO_GETFIELD(info->phi0); if (!field_getelement(p, info->grade, id, 0, &vphi0)) { morpho_runtimeerror(v, HYDROGEL_FLDGRD, (unsigned int) info->grade); return false; } } if (MORPHO_ISNUMBER(vphi0)) { if (!morpho_valuetofloat(vphi0, &phi0)) return false; } double phi = phi0/(V/V0); double pr = info->phiref; if (phi<0 || 1-phi<0) { morpho_runtimewarning(v, HYDROGEL_BNDS, id, V, V0, phi, 1-phi); } if (phi>1-MORPHO_EPS) phi = 1-MORPHO_EPS; if (phia * phi + info->b * ( phi + log(1-phi) ) + info->c * phi*phi + info->d * (pr/phi0) * ((phi/pr)/3.0 - (2.0/3) * pow((phi/pr), (1.0/3)) ) ); // Compute grad * element gradient if (!functional_elementgradient_scale(v, mesh, info->grade, id, nv, vid, frc, grad)) return false; return true; } /** Evaluate a gradient */ value Hydrogel_gradient(vm *v, int nargs, value *args) { functional_mapinfo info; value out=MORPHO_NIL; hydrogelref ref; if (functional_validateargs(v, nargs, args, &info)) { if (hydrogel_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, -1, info.sel, &ref)) { info.g = ref.grade; info.grad = hydrogel_gradient; info.ref = &ref; info.sym = SYMMETRY_ADD; functional_mapgradient(v, &info, &out); } } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } value Hydrogel_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); int nfixed; value grade=MORPHO_INTEGER(-1); value a=MORPHO_NIL, b=MORPHO_NIL, c=MORPHO_NIL, d=MORPHO_NIL, phiref=MORPHO_NIL, phi0=MORPHO_NIL; if (builtin_options(v, nargs, args, &nfixed, 6, hydrogel_aproperty, &a, hydrogel_bproperty, &b, hydrogel_cproperty, &c, hydrogel_dproperty, &d, hydrogel_phirefproperty, &phiref, hydrogel_phi0property, &phi0, functional_gradeproperty, &grade)) { objectinstance_setproperty(self, hydrogel_aproperty, a); objectinstance_setproperty(self, hydrogel_bproperty, b); objectinstance_setproperty(self, hydrogel_cproperty, c); objectinstance_setproperty(self, hydrogel_dproperty, d); objectinstance_setproperty(self, hydrogel_phirefproperty, phiref); objectinstance_setproperty(self, hydrogel_phi0property, phi0); objectinstance_setproperty(self, functional_gradeproperty, grade); if (nfixed==1 && MORPHO_ISMESH(MORPHO_GETARG(args, 0))) { objectinstance_setproperty(self, linearelasticity_referenceproperty, MORPHO_GETARG(args, 0)); } else morpho_runtimeerror(v, HYDROGEL_ARGS); } else morpho_runtimeerror(v, HYDROGEL_ARGS); return MORPHO_NIL; } FUNCTIONAL_METHOD(Hydrogel, integrand, (ref.grade), hydrogelref, hydrogel_prepareref, functional_mapintegrand, hydrogel_integrand, NULL, HYDROGEL_PRP, SYMMETRY_NONE) FUNCTIONAL_METHOD(Hydrogel, total, (ref.grade), hydrogelref, hydrogel_prepareref, functional_sumintegrand, hydrogel_integrand, NULL, HYDROGEL_PRP, SYMMETRY_NONE) MORPHO_BEGINCLASS(Hydrogel) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, Hydrogel_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, Hydrogel_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, Hydrogel_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, Hydrogel_gradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Equielement * ---------------------------------------------- */ static value equielement_weightproperty; typedef struct { grade grade; objectsparse *vtoel; // Connect vertices to elements objectsparse *eltov; // Connect elements to vertices objectmatrix *weight; // Weight field double mean; } equielementref; /** Prepares the reference structure from the Equielement object's properties */ bool equielement_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, equielementref *ref) { bool success=false; value grade=MORPHO_NIL; value weight=MORPHO_NIL; if (objectinstance_getpropertyinterned(self, functional_gradeproperty, &grade) && MORPHO_ISINTEGER(grade) ) { ref->grade=MORPHO_GETINTEGERVALUE(grade); ref->weight=NULL; int maxgrade=mesh_maxgrade(mesh); if (ref->grade<0 || ref->grade>maxgrade) ref->grade = maxgrade; ref->vtoel=mesh_addconnectivityelement(mesh, ref->grade, 0); ref->eltov=mesh_addconnectivityelement(mesh, 0, ref->grade); if (ref->vtoel && ref->eltov) success=true; } if (objectinstance_getpropertyinterned(self, equielement_weightproperty, &weight) && MORPHO_ISMATRIX(weight) ) { ref->weight=MORPHO_GETMATRIX(weight); if (ref->weight) { ref->mean=matrix_sum(ref->weight); ref->mean/=ref->weight->ncols; } } return success; } bool equielement_contains(varray_elementid *nbrs, elementid id) { for (unsigned int i=0; icount; i++) { if (nbrs->data[i]==id) return true; } return false; } /** Finds the points that a point depends on */ bool equielement_dependencies(functional_mapinfo *info, elementid id, varray_elementid *out) { objectmesh *mesh = info->mesh; equielementref *eref = info->ref; bool success=false; varray_elementid nbrs; varray_elementidinit(&nbrs); // varray_elementidwrite(out, id); // EquiElement is a vertex element, and hence depends on itself if (mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, eref->grade, &nbrs)>0) { for (unsigned int i=0; ieltov->ccs, nbrs.data[i], &nentries, &entries)) goto equieleement_dependencies_cleanup; for (unsigned int j=0; jvtoel->ccs, id, &nconn, &conn)) { if (nconn==1) { *out = 0; return true; } double size[nconn], mean=0.0, total=0.0; for (int i=0; ieltov->ccs, conn[i], &nv, &vid); functional_elementsize(v, mesh, ref->grade, conn[i], nv, vid, &size[i]); mean+=size[i]; } mean /= ((double) nconn); if (fabs(mean)weight || fabs(ref->mean)weight, 0, conn[i], &weight[i]); wmean+=weight[i]; } wmean /= ((double) nconn); if (fabs(wmean)selection=sel; ref->lineel = mesh_getconnectivityelement(mesh, MESH_GRADE_VERTEX, MESH_GRADE_LINE); if (ref->lineel) success=sparse_checkformat(ref->lineel, SPARSE_CCS, true, false); if (success) { objectsparse *s = mesh_getconnectivityelement(mesh, MESH_GRADE_LINE, MESH_GRADE_VERTEX); if (!s) s=mesh_addconnectivityelement(mesh, MESH_GRADE_LINE, MESH_GRADE_VERTEX); success=s; } if (success) { value integrandonly=MORPHO_FALSE; objectinstance_getpropertyinterned(self, curvature_integrandonlyproperty, &integrandonly); ref->integrandonly=MORPHO_ISTRUE(integrandonly); } return success; } /** Finds the points that a point depends on */ bool linecurvsq_dependencies(functional_mapinfo *info, elementid id, varray_elementid *out) { objectmesh *mesh = info->mesh; curvatureref *cref = info->ref; bool success=false; varray_elementid nbrs; varray_elementidinit(&nbrs); varray_elementidwrite(out, id); // LinecurvSq is a vertex element, and hence depends on itself if (mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, MESH_GRADE_LINE, &nbrs)>0) { for (unsigned int i=0; ilineel->ccs, nbrs.data[i], &nentries, &entries)) goto linecurvsq_dependencies_cleanup; for (unsigned int j=0; jdim], s1[mesh->dim], *s[2] = { s0, s1}, sgn=-1.0; if (mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, MESH_GRADE_LINE, &nbrs)>0 && mesh_getsynonyms(mesh, MESH_GRADE_VERTEX, id, &synid)) { if (nbrs.count!=2) goto linecurvsq_integrand_cleanup; for (unsigned int i=0; i<2; i++) { int nentries, *entries; // Get the vertices for this edge if (!sparseccs_getrowindices(&cref->lineel->ccs, nbrs.data[i], &nentries, &entries)) break; double *x0, *x1; if (mesh_getvertexcoordinatesaslist(mesh, entries[0], &x0) && mesh_getvertexcoordinatesaslist(mesh, entries[1], &x1)) { functional_vecsub(mesh->dim, x0, x1, s[i]); } if (!(entries[0]==id || functional_inlist(&synid, entries[0]))) sgn*=-1; } double s0s0=functional_vecdot(mesh->dim, s0, s0), s0s1=functional_vecdot(mesh->dim, s0, s1), s1s1=functional_vecdot(mesh->dim, s1, s1); s0s0=sqrt(s0s0); s1s1=sqrt(s1s1); if (s0s0integrandonly) result /= len; // Get the bare curvature. } linecurvsq_integrand_cleanup: *out = result; varray_elementidclear(&nbrs); varray_elementidclear(&synid); return true; } FUNCTIONAL_INIT(LineCurvatureSq, MESH_GRADE_VERTEX) FUNCTIONAL_METHOD(LineCurvatureSq, integrand, MESH_GRADE_VERTEX, curvatureref, curvature_prepareref, functional_mapintegrand, linecurvsq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(LineCurvatureSq, integrandForElement, MESH_GRADE_VERTEX, curvatureref, curvature_prepareref, functional_mapintegrandforelement, linecurvsq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(LineCurvatureSq, total, MESH_GRADE_VERTEX, curvatureref, curvature_prepareref, functional_sumintegrand, linecurvsq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(LineCurvatureSq, gradient, MESH_GRADE_VERTEX, curvatureref, curvature_prepareref, functional_mapnumericalgradient, linecurvsq_integrand, linecurvsq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) FUNCTIONAL_METHOD(LineCurvatureSq, hessian, MESH_GRADE_VERTEX, curvatureref, curvature_prepareref, functional_mapnumericalhessian, linecurvsq_integrand, linecurvsq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) MORPHO_BEGINCLASS(LineCurvatureSq) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, LineCurvatureSq_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, LineCurvatureSq_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRANDFORELEMENT_METHOD, LineCurvatureSq_integrandForElement, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, LineCurvatureSq_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, LineCurvatureSq_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, LineCurvatureSq_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * LineTorsionSq * ---------------------------------------------- */ /** Return a list of vertices that an element depends on */ bool linetorsionsq_dependencies(functional_mapinfo *info, elementid id, varray_elementid *out) { objectmesh *mesh = info->mesh; curvatureref *cref = info->ref; bool success=false; varray_elementid nbrs; varray_elementid synid; varray_elementidinit(&nbrs); varray_elementidinit(&synid); if (mesh_findneighbors(mesh, MESH_GRADE_LINE, id, MESH_GRADE_LINE, &nbrs)>0) { for (unsigned int i=0; ilineel->ccs, nbrs.data[i], &nentries, &entries)) goto linetorsionsq_dependencies_cleanup; for (unsigned int j=0; j0) { if (nbrs.count<2) { *out = 0; success=true; goto linecurvsq_torsion_cleanup; } for (unsigned int i=0; ilineel->ccs, nbrs.data[i], &nentries, &entries)) goto linecurvsq_torsion_cleanup; for (unsigned int j=0; jvert, vlist[i], &x[i]); double A[3], B[3], C[3], crossAB[3], crossBC[3]; functional_vecsub(3, x[1], x[0], A); functional_vecsub(3, x[3], x[2], B); functional_vecsub(3, x[5], x[4], C); functional_veccross(A, B, crossAB); functional_veccross(B, C, crossBC); double normB=functional_vecnorm(3, B), normAB=functional_vecnorm(3, crossAB), normBC=functional_vecnorm(3, crossBC); double S = functional_vecdot(3, A, crossBC)*normB; if (normAB>MORPHO_EPS) S/=normAB; if (normBC>MORPHO_EPS) S/=normBC; S=asin(S); *out=S*S/normB; success=true; linecurvsq_torsion_cleanup: varray_elementidclear(&nbrs); varray_elementidclear(&synid); return success; } FUNCTIONAL_INIT(LineTorsionSq, MESH_GRADE_LINE) FUNCTIONAL_METHOD(LineTorsionSq, integrand, MESH_GRADE_LINE, curvatureref, curvature_prepareref, functional_mapintegrand, linetorsionsq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(LineTorsionSq, total, MESH_GRADE_LINE, curvatureref, curvature_prepareref, functional_sumintegrand, linetorsionsq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(LineTorsionSq, gradient, MESH_GRADE_LINE, curvatureref, curvature_prepareref, functional_mapnumericalgradient, linetorsionsq_integrand, linetorsionsq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) FUNCTIONAL_METHOD(LineTorsionSq, hessian, MESH_GRADE_LINE, curvatureref, curvature_prepareref, functional_mapnumericalhessian, linetorsionsq_integrand, linetorsionsq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) MORPHO_BEGINCLASS(LineTorsionSq) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, LineTorsionSq_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, LineTorsionSq_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, LineTorsionSq_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, LineTorsionSq_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_HESSIAN_METHOD, LineTorsionSq_hessian, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * MeanCurvatureSq * ---------------------------------------------- */ static value curvature_geodesicproperty; typedef struct { objectsparse *areael; // Areas objectselection *selection; // Selection bool integrandonly; // Output integrated curvature or 'bare' curvature. bool geodesic; // Compute the geodesic curvature instead of the Gauss curvature (see https://cuhkmath.wordpress.com/2016/06/21/the-discrete-gauss-bonnet-theorem/) } areacurvatureref; bool areacurvature_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, areacurvatureref *ref) { bool success = true; ref->selection=sel; ref->areael = mesh_getconnectivityelement(mesh, MESH_GRADE_VERTEX, MESH_GRADE_AREA); if (ref->areael) success=sparse_checkformat(ref->areael, SPARSE_CCS, true, false); if (success) { objectsparse *s = mesh_getconnectivityelement(mesh, MESH_GRADE_AREA, MESH_GRADE_VERTEX); if (!s) s=mesh_addconnectivityelement(mesh, MESH_GRADE_AREA, MESH_GRADE_VERTEX); success=s; } if (success) { value integrandonly=MORPHO_FALSE; objectinstance_getpropertyinterned(self, curvature_integrandonlyproperty, &integrandonly); ref->integrandonly=MORPHO_ISTRUE(integrandonly); value geodesic=MORPHO_FALSE; objectinstance_getpropertyinterned(self, curvature_geodesicproperty, &geodesic); ref->geodesic=MORPHO_ISTRUE(geodesic); } return success; } /** Return a list of vertices that an element depends on */ bool meancurvaturesq_dependencies(functional_mapinfo *info, elementid id, varray_elementid *out) { objectmesh *mesh = info->mesh; areacurvatureref *cref = info->ref; bool success=false; varray_elementid nbrs; varray_elementid synid; varray_elementidinit(&nbrs); varray_elementidinit(&synid); mesh_getsynonyms(mesh, MESH_GRADE_VERTEX, id, &synid); varray_elementidwriteunique(&synid, id); /* Loop over synonyms of the element id */ mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, MESH_GRADE_AREA, &nbrs); for (unsigned int i=0; iareael->ccs, nbrs.data[i], &nvert, &vids)) goto meancurvsq_dependencies_cleanup; for (unsigned int j=0; jcount; k++) if (synid->data[k]==vids[i]) { posn = i; break; } } if (posn>0) { // If the desired vertex isn't in first position, move it there. int tmp=vids[posn]; vids[posn]=vids[0]; vids[0]=tmp; } return (posn>=0); } /** Calculate the integral of the mean curvature squared */ bool meancurvaturesq_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { areacurvatureref *cref = (areacurvatureref *) ref; double areasum = 0; bool success=false; varray_elementid nbrs; varray_elementid synid; varray_elementidinit(&nbrs); varray_elementidinit(&synid); mesh_getsynonyms(mesh, MESH_GRADE_VERTEX, id, &synid); varray_elementidwriteunique(&synid, id); double frc[mesh->dim]; // This will hold the total force due to the triangles present for (unsigned int i=0; idim; i++) frc[i]=0.0; mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, MESH_GRADE_AREA, &nbrs); for (unsigned int i=0; iareael->ccs, nbrs.data[i], &nvert, &ovids)) goto meancurvsq_cleanup; int vids[nvert]; // Copy so we can reorder for (int j=0; jvert, vids[j], &x[j]); /* s0 = x1-x0; s1 = x2-x1 */ functional_vecsub(mesh->dim, x[1], x[0], s0); functional_vecsub(mesh->dim, x[2], x[1], s1); /* F(v0) = (s1 x s0 x s1)/|s0 x x1|/2 */ functional_veccross(s0, s1, s01); norm=functional_vecnorm(mesh->dim, s01); if (normdim, frc, 0.5/norm, s101, frc); } *out = functional_vecdot(mesh->dim, frc, frc)/(areasum/3.0)/4.0; if (cref->integrandonly) *out /= (areasum/3.0); success=true; meancurvsq_cleanup: varray_elementidclear(&nbrs); varray_elementidclear(&synid); return success; } FUNCTIONAL_INIT(MeanCurvatureSq, MESH_GRADE_VERTEX) FUNCTIONAL_METHOD(MeanCurvatureSq, integrand, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_mapintegrand, meancurvaturesq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(MeanCurvatureSq, total, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_sumintegrand, meancurvaturesq_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(MeanCurvatureSq, gradient, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_mapnumericalgradient, meancurvaturesq_integrand, meancurvaturesq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) MORPHO_BEGINCLASS(MeanCurvatureSq) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, MeanCurvatureSq_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, MeanCurvatureSq_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, MeanCurvatureSq_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, MeanCurvatureSq_total, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * GaussCurvature * ---------------------------------------------- */ /** Calculate the integral of the gaussian curvature */ bool gausscurvature_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { areacurvatureref *cref = (areacurvatureref *) ref; double anglesum = 0, areasum = 0; bool success=false; varray_elementid nbrs; varray_elementid synid; varray_elementidinit(&nbrs); varray_elementidinit(&synid); mesh_getsynonyms(mesh, MESH_GRADE_VERTEX, id, &synid); varray_elementidwriteunique(&synid, id); double frc[mesh->dim]; // This will hold the total force due to the triangles present for (unsigned int i=0; idim; i++) frc[i]=0.0; mesh_findneighbors(mesh, MESH_GRADE_VERTEX, id, MESH_GRADE_AREA, &nbrs); for (unsigned int i=0; iareael->ccs, nbrs.data[i], &nvert, &ovids)) goto gausscurv_cleanup; int vids[nvert]; // Copy so we can reorder for (int j=0; jvert, vids[j], &x[j]); /* s0 = x1-x0; s1 = x2-x0 */ functional_vecsub(mesh->dim, x[1], x[0], s0); functional_vecsub(mesh->dim, x[2], x[0], s1); functional_veccross(s0, s1, s01); double area = functional_vecnorm(mesh->dim, s01); anglesum+=atan2(area, functional_vecdot(mesh->dim, s0, s1)); areasum+=area/2; } *out = 2*M_PI-anglesum; if (cref->geodesic) *out = M_PI-anglesum; if (cref->integrandonly) *out /= (areasum/3.0); success=true; gausscurv_cleanup: varray_elementidclear(&nbrs); varray_elementidclear(&synid); return success; } FUNCTIONAL_INIT(GaussCurvature, MESH_GRADE_VERTEX) FUNCTIONAL_METHOD(GaussCurvature, integrand, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_mapintegrand, gausscurvature_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(GaussCurvature, total, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_sumintegrand, gausscurvature_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE) FUNCTIONAL_METHOD(GaussCurvature, gradient, MESH_GRADE_VERTEX, areacurvatureref, areacurvature_prepareref, functional_mapnumericalgradient, gausscurvature_integrand, meancurvaturesq_dependencies, FUNCTIONAL_ARGS, SYMMETRY_ADD) MORPHO_BEGINCLASS(GaussCurvature) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, GaussCurvature_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, GaussCurvature_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, GaussCurvature_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, GaussCurvature_total, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Fields * ********************************************************************** */ typedef struct { objectfield *field; grade grade; } fieldref; /* ---------------------------------------------- * GradSq * ---------------------------------------------- */ bool gradsq_computeperpendicular(unsigned int n, double *s1, double *s2, double *out) { double s1s2, s2s2, sout; /* Compute s1 - (s1.s2) s2 / (s2.2) */ s1s2 = functional_vecdot(n, s1, s2); s2s2 = functional_vecdot(n, s2, s2); if (fabs(s2s2)psize * mesh->dim units of storage */ bool gradsq_evaluategradient(objectmesh *mesh, objectfield *field, int nv, int *vid, double *out) { double *f[nv]; // Field value lists double *x[nv]; // Vertex coordinates unsigned int nentries=0; // Get field values and vertex coordinates for (unsigned int i=0; idim], t[3][mesh->dim]; /* Vector sides */ functional_vecsub(mesh->dim, x[1], x[0], s[0]); functional_vecsub(mesh->dim, x[2], x[1], s[1]); functional_vecsub(mesh->dim, x[0], x[2], s[2]); /* Perpendicular vectors */ gradsq_computeperpendicular(mesh->dim, s[2], s[1], t[0]); gradsq_computeperpendicular(mesh->dim, s[0], s[2], t[1]); gradsq_computeperpendicular(mesh->dim, s[1], s[0], t[2]); /* Compute the gradient */ for (unsigned int i=0; idim*nentries; i++) out[i]=0; for (unsigned int j=0; jdim, &out[i*mesh->dim], f[j][i], t[j], &out[i*mesh->dim]); } } return true; } /** Evaluates the gradient of a field quantity in 1D @param[in] mesh - object to use @param[in] field - field to compute gradient of @param[in] nv - number of vertices @param[in] vid - vertex ids @param[out] out - should be field->psize * mesh->dim units of storage */ bool gradsq_evaluategradient1d(objectmesh *mesh, objectfield *field, int nv, int *vid, double *out) { UNREACHABLE("GradSq in 1D not implemented."); double *f[nv]; // Field value lists double *x[nv]; // Vertex coordinates unsigned int nentries=0; // Get field values and vertex coordinates for (unsigned int i=0; idim]; /* Vector sides */ functional_vecsub(mesh->dim, x[1], x[0], s); /* Compute the gradient */ for (unsigned int i=0; idim*nentries; i++) out[i]=0; for (unsigned int j=0; jdim, &out[i*mesh->dim], f[j][i], t[j], &out[i*mesh->dim]); } } return true; } /** Evaluates the gradient of a field quantity in 3D @param[in] mesh - object to use @param[in] field - field to compute gradient of @param[in] nv - number of vertices @param[in] vid - vertex ids @param[out] out - should be field->psize * mesh->dim units of storage */ bool gradsq_evaluategradient3d(objectmesh *mesh, objectfield *field, int nv, int *vid, double *out) { double *f[nv]; // Field value lists double *x[nv]; // Vertex coordinates double xarray[nv*mesh->dim]; // Vertex coordinates double xtarray[nv*mesh->dim]; // Vertex coordinates unsigned int nentries=0; // Get field values and vertex coordinates for (unsigned int i=0; idim, x[i], x[0], &xarray[(i-1)*mesh->dim]); } for (unsigned int i=0; idim*nentries; i++) out[i]=0; objectmatrix M = MORPHO_STATICMATRIX(xarray, mesh->dim, mesh->dim); objectmatrix Mt = MORPHO_STATICMATRIX(xtarray, mesh->dim, mesh->dim); matrix_transpose(&M, &Mt); double farray[nentries*mesh->dim]; // Field elements objectmatrix frhs = MORPHO_STATICMATRIX(farray, mesh->dim, nentries); objectmatrix grad = MORPHO_STATICMATRIX(out, mesh->dim, nentries); // Loop over elements of the field for (unsigned int i=0; idim; j++) farray[i*mesh->dim+j] = f[j+1][i]-f[0][i]; } // Solve to obtain the gradient of each element matrix_divs(&Mt, &frhs, &grad); return true; } /** Prepares the gradsq reference */ bool gradsq_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, fieldref *ref) { bool success=false, grdset=false; value field=MORPHO_NIL, grd=MORPHO_NIL; if (objectinstance_getpropertyinterned(self, functional_fieldproperty, &field) && MORPHO_ISFIELD(field)) { ref->field=MORPHO_GETFIELD(field); success=true; } if (objectinstance_getpropertyinterned(self, functional_gradeproperty, &grd) && MORPHO_ISINTEGER(grd)) { ref->grade=MORPHO_GETINTEGERVALUE(grd); if (ref->grade>0) grdset=true; } if (!grdset) ref->grade=mesh_maxgrade(mesh); return success; } /** Clones the nematic reference with a given substitute field */ void *gradsq_cloneref(void *ref, objectfield *field, objectfield *sub) { fieldref *nref = (fieldref *) ref; fieldref *clone = MORPHO_MALLOC(sizeof(fieldref)); if (clone) { *clone = *nref; if (clone->field==field) clone->field=sub; } return clone; } /** Calculate the |grad q|^2 energy */ bool gradsq_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { fieldref *eref = ref; double size=0; // Length area or volume of the element double grad[eref->field->psize*mesh->dim]; if (!functional_elementsize(v, mesh, eref->grade, id, nv, vid, &size)) return false; if (eref->grade==2) { if (!gradsq_evaluategradient(mesh, eref->field, nv, vid, grad)) return false; } else if (eref->grade==3) { if (!gradsq_evaluategradient3d(mesh, eref->field, nv, vid, grad)) return false; } else { return false; } double gradnrm=functional_vecnorm(eref->field->psize*mesh->dim, grad); *out = gradnrm*gradnrm*size; return true; } /** Initialize a GradSq object */ value GradSq_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); if (nargs>0 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectinstance_setproperty(self, functional_fieldproperty, MORPHO_GETARG(args, 0)); } else { morpho_runtimeerror(v, VM_INVALIDARGS); return MORPHO_FALSE; } /* Second (optional) argument is the grade to act on */ if (nargs>1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), functional_gradeproperty, MORPHO_GETARG(args, 1)); } } return MORPHO_NIL; } FUNCTIONAL_METHOD(GradSq, integrand, (ref.grade), fieldref, gradsq_prepareref, functional_mapintegrand, gradsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(GradSq, total, (ref.grade), fieldref, gradsq_prepareref, functional_sumintegrand, gradsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(GradSq, gradient, (ref.grade), fieldref, gradsq_prepareref, functional_mapnumericalgradient, gradsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_ADD); value GradSq_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; fieldref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (gradsq_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_AREA, info.sel, &ref)) { info.g = ref.grade; info.field = ref.field; info.integrand = gradsq_integrand; info.cloneref = gradsq_cloneref; info.ref = &ref; functional_mapnumericalfieldgradient(v, &info, &out); //functional_mapfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(GradSq) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, GradSq_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, GradSq_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, GradSq_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, GradSq_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, GradSq_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * Nematic * ---------------------------------------------- */ static value nematic_ksplayproperty; static value nematic_ktwistproperty; static value nematic_kbendproperty; static value nematic_pitchproperty; typedef struct { double ksplay,ktwist,kbend,pitch; bool haspitch; objectfield *field; grade grade; } nematicref; /** Prepares the nematic reference */ bool nematic_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, nematicref *ref) { bool success=false, grdset=false; value field=MORPHO_NIL, grd=MORPHO_NIL; value val=MORPHO_NIL; ref->ksplay=1.0; ref->ktwist=1.0; ref->kbend=1.0; ref->pitch=0.0; ref->haspitch=false; if (objectinstance_getpropertyinterned(self, functional_fieldproperty, &field) && MORPHO_ISFIELD(field)) { ref->field=MORPHO_GETFIELD(field); success=true; } if (objectinstance_getpropertyinterned(self, nematic_ksplayproperty, &val) && MORPHO_ISNUMBER(val)) { morpho_valuetofloat(val, &ref->ksplay); } if (objectinstance_getpropertyinterned(self, nematic_ktwistproperty, &val) && MORPHO_ISNUMBER(val)) { morpho_valuetofloat(val, &ref->ktwist); } if (objectinstance_getpropertyinterned(self, nematic_kbendproperty, &val) && MORPHO_ISNUMBER(val)) { morpho_valuetofloat(val, &ref->kbend); } if (objectinstance_getpropertyinterned(self, nematic_pitchproperty, &val) && MORPHO_ISNUMBER(val)) { morpho_valuetofloat(val, &ref->pitch); ref->haspitch=true; } if (objectinstance_getpropertyinterned(self, functional_gradeproperty, &grd) && MORPHO_ISINTEGER(grd)) { ref->grade=MORPHO_GETINTEGERVALUE(grd); if (ref->grade>0) grdset=true; } if (!grdset) ref->grade=mesh_maxgrade(mesh); return success; } /** Clones the nematic reference with a given substitute field */ void *nematic_cloneref(void *ref, objectfield *field, objectfield *sub) { nematicref *nref = (nematicref *) ref; nematicref *clone = MORPHO_MALLOC(sizeof(nematicref)); if (clone) { *clone = *nref; if (clone->field==field) clone->field=sub; } return clone; } /* Integrates two linear functions with values at vertices f[0]...f[2] and g[0]...g[2] */ double nematic_bcint(double *f, double *g) { return (f[0]*(2*g[0]+g[1]+g[2]) + f[1]*(g[0]+2*g[1]+g[2]) + f[2]*(g[0]+g[1]+2*g[2]))/12; } /* Integrates a linear vector function with values at vertices f[0]...f[2] */ double nematic_bcint1(double *f) { return (f[0] + f[1] + f[2])/3; } /* Integrates a linear vector function with values at vertices f[0]...f[n] Works for dimensions 1-3 at least */ double nematic_bcintf(unsigned int n, double *f) { double sum = 0; for (unsigned int i=0; ifield->psize*3]; double gradnn[eref->field->psize*3]; double divnn, curlnn[3] = { 0.0, 0.0, 0.0 }; for (int i=0; ifield->psize*3; i++) { gradnn[i]=0.0; gradnnraw[i]=0.0; } if (!functional_elementsize(v, mesh, eref->grade, id, nv, vid, &size)) return false; // Get nematic director components double *nn[nv]; // Field value lists unsigned int nentries=0; for (unsigned int i=0; ifield, MESH_GRADE_VERTEX, vid[i], 0, &nentries, &nn[i])) return false; } // Evaluate gradients of the director if (eref->grade==2) { if (!gradsq_evaluategradient(mesh, eref->field, nv, vid, gradnnraw)) return false; } else if (eref->grade==3) { if (!gradsq_evaluategradient3d(mesh, eref->field, nv, vid, gradnnraw)) return false; } // Copy into 3x3 matrix for (int j=0; j<3; j++) for (int i=0; idim; i++) gradnn[3*j+i] = gradnnraw[mesh->dim*j+i]; // Output of this is the matrix: // [ nx,x ny,x nz,x ] [ 0 3 6 ] <- indices // [ nx,y ny,y nz,y ] [ 1 4 7 ] // [ nx,z ny,z nz,z ] [ 2 5 8 ] objectmatrix gradnnmat = MORPHO_STATICMATRIX(gradnn, 3, 3); matrix_trace(&gradnnmat, &divnn); curlnn[0]=gradnn[7]-gradnn[5]; // nz,y - ny,z curlnn[1]=gradnn[2]-gradnn[6]; // nx,z - nz,x curlnn[2]=gradnn[3]-gradnn[1]; // ny,x - nx,y /* From components of the curl, construct the coefficients that go in front of integrals of nx^2, ny^2, nz^2, nx*ny, ny*nz, and nz*nx over the element. */ double ctwst[6] = { curlnn[0]*curlnn[0], curlnn[1]*curlnn[1], curlnn[2]*curlnn[2], 2*curlnn[0]*curlnn[1], 2*curlnn[1]*curlnn[2], 2*curlnn[2]*curlnn[0]}; double cbnd[6] = { ctwst[1] + ctwst[2], ctwst[0] + ctwst[2], ctwst[0] + ctwst[1], -ctwst[3], -ctwst[4], -ctwst[5] }; /* Calculate integrals of nx^2, ny^2, nz^2, nx*ny, ny*nz, and nz*nx over the element */ double nnt[3][nv]; // The transpose of nn for (unsigned int i=0; iksplay*size*divnn*divnn; for (unsigned int i=0; i<6; i++) { twist += ctwst[i]*integrals[i]; bend += cbnd[i]*integrals[i]; } twist *= 0.5*eref->ktwist*size; bend *= 0.5*eref->kbend*size; if (eref->haspitch) { /* Cholesteric terms: 0.5 * k22 * [- 2 q (cx + cy + cz ) + q^2] */ for (unsigned i=0; i<3; i++) { chol += -2*curlnn[i]*nematic_bcintf(nv, nnt[i])*eref->pitch; } chol += (eref->pitch*eref->pitch); chol *= 0.5*eref->ktwist*size; } *out = splay+twist+bend+chol; return true; } /** Initialize a Nematic object */ value Nematic_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); int nfixed=nargs; value ksplay=MORPHO_FLOAT(1.0), ktwist=MORPHO_FLOAT(1.0), kbend=MORPHO_FLOAT(1.0); value pitch=MORPHO_NIL; if (builtin_options(v, nargs, args, &nfixed, 4, nematic_ksplayproperty, &ksplay, nematic_ktwistproperty, &ktwist, nematic_kbendproperty, &kbend, nematic_pitchproperty, &pitch)) { objectinstance_setproperty(self, nematic_ksplayproperty, ksplay); objectinstance_setproperty(self, nematic_ktwistproperty, ktwist); objectinstance_setproperty(self, nematic_kbendproperty, kbend); objectinstance_setproperty(self, nematic_pitchproperty, pitch); } else morpho_runtimeerror(v, NEMATIC_ARGS); if (nfixed==1 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0))) { objectinstance_setproperty(self, functional_fieldproperty, MORPHO_GETARG(args, 0)); } else morpho_runtimeerror(v, NEMATIC_ARGS); return MORPHO_NIL; } FUNCTIONAL_METHOD(Nematic, integrand, (ref.grade), nematicref, nematic_prepareref, functional_mapintegrand, nematic_integrand, NULL, NEMATIC_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(Nematic, total, (ref.grade), nematicref, nematic_prepareref, functional_sumintegrand, nematic_integrand, NULL, NEMATIC_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(Nematic, gradient, (ref.grade), nematicref, nematic_prepareref, functional_mapnumericalgradient, nematic_integrand, NULL, NEMATIC_ARGS, SYMMETRY_NONE); value Nematic_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; nematicref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (nematic_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_AREA, info.sel, &ref)) { info.g=ref.grade; info.integrand=nematic_integrand; info.ref=&ref; info.cloneref=nematic_cloneref; functional_mapnumericalfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(Nematic) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, Nematic_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, Nematic_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, Nematic_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, Nematic_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, Nematic_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * NematicElectric * ---------------------------------------------- */ typedef struct { objectfield *director; value field; grade grade; } nematicelectricref; /** Prepares the nematicelectric reference */ bool nematicelectric_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, nematicelectricref *ref) { bool success=false, grdset=false; ref->field=MORPHO_NIL; value fieldlist=MORPHO_NIL, grd=MORPHO_NIL; if (objectinstance_getpropertyinterned(self, functional_fieldproperty, &fieldlist) && MORPHO_ISLIST(fieldlist)) { objectlist *lst = MORPHO_GETLIST(fieldlist); value director = MORPHO_NIL; list_getelement(lst, 0, &director); list_getelement(lst, 1, &ref->field); if (MORPHO_ISFIELD(director)) ref->director=MORPHO_GETFIELD(director); if (MORPHO_ISFIELD(ref->field) || MORPHO_ISMATRIX(ref->field)) success=true; } if (objectinstance_getpropertyinterned(self, functional_gradeproperty, &grd) && MORPHO_ISINTEGER(grd)) { ref->grade=MORPHO_GETINTEGERVALUE(grd); if (ref->grade>0) grdset=true; } if (!grdset) ref->grade=mesh_maxgrade(mesh); return success; } /** Clones the nematic reference with a given substitute field */ void *nematicelectric_cloneref(void *ref, objectfield *field, objectfield *sub) { nematicelectricref *nref = (nematicelectricref *) ref; nematicelectricref *clone = MORPHO_MALLOC(sizeof(nematicelectricref)); if (clone) { *clone = *nref; if (clone->director==field) clone->director=sub; if (MORPHO_ISFIELD(clone->field) && MORPHO_GETFIELD(clone->field)==field) { clone->field=MORPHO_OBJECT(sub); } } return clone; } /** Calculate the integral (n.E)^2 energy, where E is calculated from the electric potential */ bool nematicelectric_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { nematicelectricref *eref = ref; double size=0; // Length area or volume of the element if (!functional_elementsize(v, mesh, eref->grade, id, nv, vid, &size)) return false; // Get nematic director components double *nn[nv]; // Field value lists unsigned int nentries=0; for (unsigned int i=0; idirector, MESH_GRADE_VERTEX, vid[i], 0, &nentries, &nn[i])) return false; } // The electric field ends up being constant over the element double ee[mesh->dim]; if (MORPHO_ISFIELD(eref->field)) { if (eref->grade==2) { if (!gradsq_evaluategradient(mesh, MORPHO_GETFIELD(eref->field), nv, vid, ee)) return false; } else if (eref->grade==3) { if (!gradsq_evaluategradient3d(mesh, MORPHO_GETFIELD(eref->field), nv, vid, ee)) return false; } } /* Calculate integrals of nx^2, ny^2, nz^2, nx*ny, ny*nz, and nz*nx over the element */ double nnt[mesh->dim][nv]; // The transpose of nn for (unsigned int i=0; idim; j++) nnt[j][i]=nn[i][j]; /* Calculate integral (n.e)^2 using the above results */ double total = ee[0]*ee[0]*nematic_bcintfg(nv, nnt[0], nnt[0])+ ee[1]*ee[1]*nematic_bcintfg(nv, nnt[1], nnt[1])+ ee[2]*ee[2]*nematic_bcintfg(nv, nnt[2], nnt[2])+ 2*ee[0]*ee[1]*nematic_bcintfg(nv, nnt[0], nnt[1])+ 2*ee[1]*ee[2]*nematic_bcintfg(nv, nnt[1], nnt[2])+ 2*ee[2]*ee[0]*nematic_bcintfg(nv, nnt[2], nnt[0]); *out = size*total; return true; } /** Initialize a NematicElectric object */ value NematicElectric_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); if (nargs==2 && MORPHO_ISFIELD(MORPHO_GETARG(args, 0)) && MORPHO_ISFIELD(MORPHO_GETARG(args, 1))) { objectlist *new = object_newlist(2, &MORPHO_GETARG(args, 0)); if (new) { value lst = MORPHO_OBJECT(new); objectinstance_setproperty(self, functional_fieldproperty, lst); morpho_bindobjects(v, 1, &lst); } } else morpho_runtimeerror(v, NEMATICELECTRIC_ARGS); return MORPHO_NIL; } FUNCTIONAL_METHOD(NematicElectric, integrand, (ref.grade), nematicelectricref, nematicelectric_prepareref, functional_mapintegrand, nematicelectric_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(NematicElectric, total, (ref.grade), nematicelectricref, nematicelectric_prepareref, functional_sumintegrand, nematicelectric_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(NematicElectric, gradient, (ref.grade), nematicelectricref, nematicelectric_prepareref, functional_mapnumericalgradient, nematicelectric_integrand, NULL, FUNCTIONAL_ARGS, SYMMETRY_NONE); value NematicElectric_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; nematicelectricref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (nematicelectric_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_AREA, info.sel, &ref)) { info.g=ref.grade; info.integrand=nematicelectric_integrand; info.cloneref=nematicelectric_cloneref; info.ref=&ref; functional_mapnumericalfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(NematicElectric) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, NematicElectric_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, NematicElectric_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, NematicElectric_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, NematicElectric_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, NematicElectric_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * NormSq * ---------------------------------------------- */ /** Calculate the norm squared of a field quantity */ bool normsq_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { fieldref *eref = ref; unsigned int nentries; double *entries; if (field_getelementaslist(eref->field, MESH_GRADE_VERTEX, id, 0, &nentries, &entries)) { *out = functional_vecdot(nentries, entries, entries); return true; } return false; } FUNCTIONAL_METHOD(NormSq, integrand, MESH_GRADE_VERTEX, fieldref, gradsq_prepareref, functional_mapintegrand, normsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(NormSq, total, MESH_GRADE_VERTEX, fieldref, gradsq_prepareref, functional_sumintegrand, normsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(NormSq, gradient, MESH_GRADE_VERTEX, fieldref, gradsq_prepareref, functional_mapnumericalgradient, normsq_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); value NormSq_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; fieldref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { if (gradsq_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_VERTEX, info.sel, &ref)) { info.g=MESH_GRADE_VERTEX; info.ref=&ref; info.field=ref.field; info.integrand=normsq_integrand; info.cloneref=gradsq_cloneref; functional_mapnumericalfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(NormSq) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, GradSq_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, NormSq_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, NormSq_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, NormSq_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, NormSq_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Integrals * ********************************************************************** */ /** Integral references @brief Used to pass through the functional element mapping system. A thread local copy is made with cloned fields */ typedef struct { value integrand; int nfields; value *fields; value *originalfields; // Original fields value method; // Method dictionary objectmesh *mref; // Reference mesh vm *v; bool weightbyref; // Use reference mesh for the element } integralref; /* ---------------------------------------------- * Integrand functions * ---------------------------------------------- */ /** Integral element references @brief used to store information about the current element in thread-local storage. We wrap them in an object so that they can be safely stored in a value. Guaranteed to be thread local */ typedef struct { object obj; objectmesh *mesh; // The current mesh object integralref *iref; // The current integral ref structure // Information about the element grade g; // Current grade elementid id; // Current element int nv; // Number of vertices int *vid; // Vertex ids double **vertexposn; // List of vertex positions double elementsize; // Size of the element // Interpolated quantities: double *lambda; // Barycentric coordinates double *posn; // Position in physical space quantity *quantities; // Original quantities obtained for the element objectmatrix *invj; // Inverse jacobian for the element value *qgrad; // Gradients value *qinterpolated; // List of interpolated quantities (this allows us to identify operators on fields } objectintegralelementref; size_t objectintegralelementref_sizefn(object *obj) { return sizeof(objectintegralelementref); } void objectintegralelementref_printfn(object *obj, void *v) { morpho_printf(v, ""); } objecttypedefn objectintegralelementrefdefn = { .printfn=objectintegralelementref_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectintegralelementref_sizefn, .hashfn=NULL, .cmpfn=NULL }; objecttype objectintegralelementreftype; #define OBJECT_INTEGRALELEMENTREF objectintegralelementreftype /** Tests whether an object is an element ref */ #define MORPHO_ISINTEGRALELEMENTREF(val) object_istype(val, OBJECT_INTEGRALELEMENTREF) /** Gets the object as an element ref */ #define MORPHO_GETINTEGRALELEMENTREF(val) ((objectintegralelementref *) MORPHO_GETOBJECT(val)) /** Static element ref */ #define MORPHO_STATICINTEGRALELEMENTREF(mesh, grade, id, nv, vid) { .obj.type=OBJECT_INTEGRALELEMENTREF, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .g=grade, .mesh=mesh, .id=id, .nv=nv, .vid=vid, .qinterpolated=NULL } int elementhandle; /** Get the current element ref from thread-local storage in the VM */ objectintegralelementref *integral_getelementref(vm *v) { value elref=MORPHO_NIL; vm_gettlvar(v, elementhandle, &elref); if (MORPHO_ISINTEGRALELEMENTREF(elref)) return MORPHO_GETINTEGRALELEMENTREF(elref); return NULL; } /* -------- * Tangent * -------- */ int tangenthandle; // TL storage handle for tangent vectors /** Evaluate the tangent vector */ void integral_evaluatetangent(vm *v, value *out) { objectintegralelementref *elref = integral_getelementref(v); if (!elref) { morpho_runtimeerror(v, INTEGRAL_SPCLFN, TANGENT_FUNCTION); return; } int dim = elref->mesh->dim; objectmatrix *mtangent = object_newmatrix(dim, 1, false); if (!mtangent) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return; } functional_vecsub(dim, elref->vertexposn[1], elref->vertexposn[0], mtangent->elements); double tnorm=functional_vecnorm(dim, mtangent->elements); if (fabs(tnorm)>MORPHO_EPS) functional_vecscale(dim, 1.0/tnorm, mtangent->elements, mtangent->elements); vm_settlvar(v, tangenthandle, MORPHO_OBJECT(mtangent)); *out = MORPHO_OBJECT(mtangent); } static value integral_tangent(vm *v, int nargs, value *args) { value out=MORPHO_NIL; vm_gettlvar(v, tangenthandle, &out); if (MORPHO_ISNIL(out)) integral_evaluatetangent(v, &out); return out; } /* -------- * Normal * -------- */ int normlhandle; // TL storage handle for normal vectors /** Evaluates the normal vector */ void integral_evaluatenormal(vm *v, value *out) { objectintegralelementref *elref = integral_getelementref(v); if (!elref) { morpho_runtimeerror(v, INTEGRAL_SPCLFN, NORMAL_FUNCTION); return; } int dim = elref->mesh->dim; double s0[dim], s1[dim]; objectmatrix *mnormal = object_newmatrix(dim, 1, false); if (!mnormal) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return; } functional_vecsub(dim, elref->vertexposn[1], elref->vertexposn[0], s0); functional_vecsub(dim, elref->vertexposn[2], elref->vertexposn[1], s1); functional_veccross(s0, s1, mnormal->elements); double nnorm=functional_vecnorm(dim, mnormal->elements); if (fabs(nnorm)>MORPHO_EPS) functional_vecscale(dim, 1.0/nnorm, mnormal->elements, mnormal->elements); vm_settlvar(v, normlhandle, MORPHO_OBJECT(mnormal)); *out = MORPHO_OBJECT(mnormal); } static value integral_normal(vm *v, int nargs, value *args) { value out=MORPHO_NIL; vm_gettlvar(v, normlhandle, &out); if (MORPHO_ISNIL(out)) integral_evaluatenormal(v, &out); return out; } /* -------- * Gradient * -------- */ bool integrator_sumquantityweighted(int n, double *wts, value *q, value *out); /** @brief Prepares an inverse jacobian matrix. @param[in] dim - dimension of physical space @param[in] g - grade of the object @param[in] x - list of vertex positions (grade+1 entries, each of length dim) @param[out] invj - inverse jacobian for the transformation (dim*g entries) */ bool integral_prepareinvjacobian(unsigned int dim, grade g, double **x, objectmatrix *invj) { bool success=false; // Construct the (dim x g) matrix of edge vectors double s[dim*g]; for (int i=0; i0) { functional_vecscale(dim, 1.0/s01norm, s, invj->elements); success=true; } } else if (g==2 && dim==3) { double *s0 = s, *s1 = s+dim, s0xs1[dim], u[dim], v[dim*g]; functional_veccross(s0, s1, s0xs1); double s0xs1norm = functional_vecnorm(dim, s0xs1); if (s0xs1norm>0) { double invs0xs1norm = 1/(s0xs1norm*s0xs1norm); functional_veccross(s1, s0xs1, u); functional_vecscale(dim, invs0xs1norm, u, v); functional_veccross(s0xs1, s0, u); functional_vecscale(dim, invs0xs1norm, u, v+dim); objectmatrix invjt = MORPHO_STATICMATRIX(v, dim, g); matrix_transpose(&invjt, invj); success=true; } } return success; } /** Allocate suitable storage for the gradient */ bool integral_gradalloc(int dim, value prototype, value *out) { if (MORPHO_ISNIL(prototype)) { // Scalar objectmatrix *mgrad=object_newmatrix(dim, 1, false); if (mgrad) *out = MORPHO_OBJECT(mgrad); return mgrad; } else if (MORPHO_ISMATRIX(prototype)) { objectlist *mlst = object_newlist(0, NULL); if (mlst) *out = MORPHO_OBJECT(mlst); return mlst; } else UNREACHABLE("Field type not supported in grad"); return false; } /** Prepares the gradient sum to hold the component of the gradient */ bool integral_gradsuminit(int i, value prototype, value dest, value *sum) { if (MORPHO_ISLIST(dest)) { objectlist *lst = MORPHO_GETLIST(dest); if (i>=list_length(lst)) { objectmatrix *prmat = MORPHO_GETMATRIX(prototype); objectmatrix *new = object_newmatrix(prmat->nrows, prmat->ncols, true); if (!new) return false; *sum = MORPHO_OBJECT(new); list_append(lst, *sum); } else { matrix_zero(MORPHO_GETMATRIX(lst->val.data[i])); *sum = lst->val.data[i]; } } return true; } /** Copies the component of the gradient into the relevant destination if needed */ bool integral_gradsumcopy(int i, value sum, value dest) { if (MORPHO_ISMATRIX(dest)) { return morpho_valuetofloat(sum, &MORPHO_GETMATRIX(dest)->elements[i]); } else return true; } /** Copies the component of the gradient into the relevant destination */ bool integral_oldgradcopy(int dim, int ndof, double *grad, value prototype, value dest) { bool success=false; if (MORPHO_ISMATRIX(dest)) { objectmatrix *mdest = MORPHO_GETMATRIX(dest); memcpy(mdest->elements, grad, sizeof(double)*dim); success=true; } else if (MORPHO_ISLIST(dest)) { objectlist *lst = MORPHO_GETLIST(dest); objectmatrix *proto = MORPHO_GETMATRIX(prototype); for (int i=0; i=list_length(lst)) { mgrad=object_newmatrix(proto->nrows, proto->ncols, false); // Should copy prototype dimensions! if (mgrad) { for (int k=0; kelements[k]=grad[k*dim+i]; list_append(lst, MORPHO_OBJECT(mgrad)); success=true; } } } } return success; } /** Evaluates the gradient of a field */ bool integral_evaluategradient(vm *v, value q, value *out) { objectintegralelementref *elref = integral_getelementref(v); if (!elref) { morpho_runtimeerror(v, INTEGRAL_SPCLFN, GRAD_FUNCTION); return false; } /* Identify the field being referred to */ int ifld, xfld=-1; for (ifld=0; ifldiref->nfields; ifld++) { if (MORPHO_ISFIELD(q) && MORPHO_ISSAME(elref->iref->originalfields[ifld], q)) break; else if (MORPHO_ISSAME(elref->qinterpolated[ifld], q)) { if (xfld>=0) { morpho_runtimeerror(v, INTEGRAL_AMBGSFLD); return false; } // @warning: This will fail if two fields happen to have the same value(!) xfld=ifld; } } if (xfld>=0) ifld = xfld; // Raise an error if we couldn't find it if (ifld>=elref->iref->nfields) { morpho_runtimeerror(v, INTEGRAL_FLD); return false; } // Extract information from the field objectfield *fld = MORPHO_GETFIELD(elref->iref->fields[ifld]); int dim = elref->mesh->dim; // Allocate objects if need be. Don't bind these; these will be freed when the elref is cleared. if (!MORPHO_ISOBJECT(elref->qgrad[ifld])) { if (!integral_gradalloc(dim, fld->prototype, &elref->qgrad[ifld])) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return false; } } bool success=false; // Evaluate gradient if (MORPHO_ISFESPACE(fld->fnspc)) { if (!elref->invj) { elref->invj=object_newmatrix(elref->g, elref->mesh->dim, false); if (elref->invj) { integral_prepareinvjacobian(elref->mesh->dim, elref->g, elref->vertexposn, elref->invj); } else { morpho_runtimeerror(v, INTEGRAL_GRDEVL); return false; } } int nnodes = MORPHO_GETFESPACE(fld->fnspc)->fespace->nnodes; double gdata[nnodes * elref->g]; objectmatrix gmat = MORPHO_STATICMATRIX(gdata, nnodes, elref->g); // Compute gradient in reference frame fespace_gradient(MORPHO_GETFESPACE(fld->fnspc)->fespace, elref->lambda, &gmat); // Compute matrix double fmatdata[nnodes * dim]; objectmatrix fmat = MORPHO_STATICMATRIX(fmatdata, nnodes, dim); if (matrix_mul(&gmat, elref->invj, &fmat)!=MATRIX_OK) { morpho_runtimeerror(v, INTEGRAL_GRDEVL); return false; } for (int i=0; iprototype, elref->qgrad[ifld], &sum) && integrator_sumquantityweighted(nnodes, fmat.elements+i*nnodes, elref->quantities[ifld].vals, &sum)) { integral_gradsumcopy(i, sum, elref->qgrad[ifld]); } else { morpho_runtimeerror(v, INTEGRAL_GRDEVL); return false; } } success=true; } else { // Old gradient calculation int ndof = fld->psize; // Number of degrees of freedom per element double grad[ndof*dim]; // Storage for gradient // Evaluate correct gradient if (elref->g==2) success=gradsq_evaluategradient(elref->mesh, fld, elref->nv, elref->vid, grad); else if (elref->g==3) success=gradsq_evaluategradient3d(elref->mesh, fld, elref->nv, elref->vid, grad); integral_oldgradcopy(dim, ndof, grad, fld->prototype, elref->qgrad[ifld]); success=true; } // Store for further use if (success) *out=elref->qgrad[ifld]; return success; } static value integral_gradfn(vm *v, int nargs, value *args) { value out=MORPHO_NIL; if (nargs==1) { integral_evaluategradient(v, MORPHO_GETARG(args, 0), &out); } else morpho_runtimeerror(v, INTEGRAL_FLD); return out; } /* ------------------- * Cauchy green strain * ------------------- */ int cauchygreenhandle; // TL storage handle for CG tensor /** Evaluates the cg strain tensor */ void integral_evaluatecg(vm *v, value *out) { objectintegralelementref *elref = integral_getelementref(v); if (!elref || !elref->iref->mref) { morpho_runtimeerror(v, INTEGRAL_SPCLFN, CGTENSOR_FUNCTION); return; } int gdim=elref->nv-1; // Dimension of Gram matrix objectmatrix *cg=object_newmatrix(gdim, gdim, true); if (!cg) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return; } double gramrefel[gdim*gdim], gramdefel[gdim*gdim], qel[gdim*gdim], rel[gdim*gdim]; objectmatrix gramref = MORPHO_STATICMATRIX(gramrefel, gdim, gdim); // Gram matrices objectmatrix gramdef = MORPHO_STATICMATRIX(gramdefel, gdim, gdim); // objectmatrix q = MORPHO_STATICMATRIX(qel, gdim, gdim); // Inverse of Gram in source domain objectmatrix r = MORPHO_STATICMATRIX(rel, gdim, gdim); // Intermediate calculations linearelasticity_calculategram(elref->iref->mref->vert, elref->mesh->dim, elref->nv, elref->vid, &gramref); linearelasticity_calculategram(elref->mesh->vert, elref->mesh->dim, elref->nv, elref->vid, &gramdef); if (matrix_inverse(&gramref, &q)!=MATRIX_OK) return; if (matrix_mul(&gramdef, &q, &r)!=MATRIX_OK) return; matrix_identity(cg); matrix_scale(cg, -0.5); matrix_accumulate(cg, 0.5, &r); vm_settlvar(v, cauchygreenhandle, MORPHO_OBJECT(cg)); *out = MORPHO_OBJECT(cg); } static value integral_cgfn(vm *v, int nargs, value *args) { value out=MORPHO_NIL; vm_gettlvar(v, cauchygreenhandle, &out); if (MORPHO_ISNIL(out)) integral_evaluatecg(v, &out); return out; } /* ---------------------- * General initialization * ---------------------- */ /** Clears threadlocal storage */ void integral_cleartlvars(vm *v) { int handles[] = { elementhandle, normlhandle, tangenthandle, cauchygreenhandle, -1 }; for (int i=0; handles[i]>=0; i++) { vm_settlvar(v, handles[i], MORPHO_NIL); } } void integral_freetlvars(vm *v) { int handles[] = { normlhandle, tangenthandle, cauchygreenhandle, -1 }; for (int i=0; handles[i]>=0; i++) { value val; vm_gettlvar(v, handles[i], &val); if (MORPHO_ISOBJECT(val)) morpho_freeobject(val); } integral_cleartlvars(v); } /* ---------------------------------------------- * Generic integral support functions * ---------------------------------------------- */ value functional_methodproperty; /** Prepares an integral reference */ bool integral_prepareref(objectinstance *self, objectmesh *mesh, grade g, objectselection *sel, integralref *ref) { bool success=false; value func=MORPHO_NIL; value mref=MORPHO_NIL; value wtbyref=MORPHO_NIL; value field=MORPHO_NIL; value method=MORPHO_NIL; ref->v=NULL; ref->nfields=0; ref->method=MORPHO_NIL; ref->mref=NULL; ref->weightbyref=false; if (objectinstance_getpropertyinterned(self, scalarpotential_functionproperty, &func) && MORPHO_ISCALLABLE(func)) { ref->integrand=func; success=true; } if (objectinstance_getpropertyinterned(self, linearelasticity_referenceproperty, &mref) && MORPHO_ISMESH(mref)) { ref->mref=MORPHO_GETMESH(mref); } if (objectinstance_getpropertyinterned(self, linearelasticity_weightbyreferenceproperty, &wtbyref)) { ref->weightbyref=!morpho_isfalse(wtbyref); } if (objectinstance_getpropertyinterned(self, functional_methodproperty, &method)) { ref->method=method; } if (objectinstance_getpropertyinterned(self, functional_fieldproperty, &field) && MORPHO_ISLIST(field)) { objectlist *list = MORPHO_GETLIST(field); ref->nfields=list->val.count; ref->fields=list->val.data; ref->originalfields=list->val.data; for (int i=0; infields; i++) { if (MORPHO_ISFIELD(ref->fields[i])) { objectfield *fld = MORPHO_GETFIELD(ref->fields[i]); field_addpool(fld); } } } return success; } /** Clones the integral reference with a given substitute field */ void *integral_cloneref(void *ref, objectfield *field, objectfield *sub) { integralref *nref = (integralref *) ref; integralref *clone = MORPHO_MALLOC(sizeof(integralref)); if (clone) { *clone = *nref; clone->originalfields=nref->originalfields; clone->fields=MORPHO_MALLOC(sizeof(value)*clone->nfields); if (!clone->fields) { MORPHO_FREE(clone); return NULL; } for (int i=0; infields; i++) { clone->fields[i]=nref->fields[i]; if (MORPHO_ISFIELD(nref->fields[i]) && MORPHO_GETFIELD(nref->fields[i])==field) { clone->fields[i]=MORPHO_OBJECT(sub); } } } return clone; } /** Frees a reference */ void integral_freeref(void *ref) { integralref *nref = (integralref *) ref; MORPHO_FREE(nref->fields); MORPHO_FREE(ref); } /** Clears any data in an element ref */ void integral_clearelref(objectintegralelementref *elref) { if (elref->invj) object_free((object *) elref->invj); } /** Prepares quantity list */ bool integral_preparequantities(integralref *iref, int nv, int *vid, quantity *quantities) { bool success=false; for (int k=0; knfields; k++) { objectfield *f=MORPHO_GETFIELD(iref->fields[k]); if (MORPHO_ISFESPACE(f->fnspc)) { fespace *disc=MORPHO_GETFESPACE(f->fnspc)->fespace; if (nv-1grade) { if (!fespace_lower(disc, nv-1, &disc)) return false; } quantities[k].nnodes=disc->nnodes; quantities[k].ifn=disc->ifn; fieldindx findx[disc->nnodes]; fespace_doftofieldindx(f, disc, nv, vid, findx); quantities[k].vals=MORPHO_MALLOC(sizeof(value)*disc->nnodes); for (int i=0; innodes; i++) { int dof; field_getindex(f, findx[i].g, findx[i].id, findx[i].indx, &dof); field_getelementwithindex(f, dof, &quantities[k].vals[i]); } success=true; } else { quantities[k].nnodes=nv; quantities[k].ifn=NULL; quantities[k].vals=MORPHO_MALLOC(sizeof(value)*nv); for (unsigned int i=0; iv); if (elref) { elref->lambda=t; elref->posn=x; elref->qinterpolated=quantity; } if (morpho_call(iref->v, iref->integrand, nquantity+1, args, &out)) { morpho_valuetofloat(out, fout); return true; } return false; } /* ---------------------------------------------- * LineIntegral * ---------------------------------------------- */ /** Integrate a function over a line */ bool lineintegral_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { integralref iref = *(integralref *) ref; double *x[nv]; bool success; value qgrad[iref.nfields+1]; for (int i=0; idim, MESH_GRADE_LINE, x, iref.nfields, quantities, &iref, out, &err); integral_clearquantities(iref.nfields, quantities); integral_clearelref(&elref); } else { // Old integrator value q0[iref.nfields+1], q1[iref.nfields+1]; value *q[2] = { q0, q1 }; for (unsigned int k=0; kdim, MESH_GRADE_LINE, x, iref.nfields, q, &iref, out); } if (success) *out *=elref.elementsize; integral_freetlvars(v); // Free gradient information for (int i=0; ival.count; j++) morpho_freeobject(l->val.data[j]); } morpho_freeobject(qgrad[i]); } return success; } FUNCTIONAL_METHOD(LineIntegral, integrand, MESH_GRADE_LINE, integralref, integral_prepareref, functional_mapintegrand, lineintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(LineIntegral, total, MESH_GRADE_LINE, integralref, integral_prepareref, functional_sumintegrand, lineintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(LineIntegral, gradient, MESH_GRADE_LINE, integralref, integral_prepareref, functional_mapnumericalgradient, lineintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(LineIntegral, hessian, MESH_GRADE_LINE, integralref, integral_prepareref, functional_mapnumericalhessian, lineintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE) /** Initialize a LineIntegral object */ value LineIntegral_init(vm *v, int nargs, value *args) { objectinstance *self = MORPHO_GETINSTANCE(MORPHO_SELF(args)); int nparams = -1; int nfixed; value method=MORPHO_NIL; value mref=MORPHO_NIL; value wtbyref=MORPHO_NIL; if (builtin_options(v, nargs, args, &nfixed, 3, functional_methodproperty, &method, linearelasticity_referenceproperty, &mref, linearelasticity_weightbyreferenceproperty, &wtbyref)) { if (MORPHO_ISDICTIONARY(method)) { objectinstance_setproperty(self, functional_methodproperty, method); } else if (!MORPHO_ISNIL(method)) { morpho_runtimeerror(v, INTEGRAL_MTHDDCT); } if (MORPHO_ISMESH(mref)) objectinstance_setproperty(self, linearelasticity_referenceproperty, mref); if (MORPHO_ISBOOL(wtbyref)) objectinstance_setproperty(self, linearelasticity_weightbyreferenceproperty, wtbyref); } else { morpho_runtimeerror(v, INTEGRAL_ARGS); return MORPHO_NIL; } if (nfixed>0) { value f = MORPHO_GETARG(args, 0); if (morpho_countparameters(f, &nparams)) { objectinstance_setproperty(self, scalarpotential_functionproperty, MORPHO_GETARG(args, 0)); } else { morpho_runtimeerror(v, INTEGRAL_ARGS); return MORPHO_NIL; } } if (nparams!=nfixed) { morpho_runtimeerror(v, INTEGRAL_NFLDS); return MORPHO_NIL; } if (nfixed>1) { /* Remaining arguments should be fields */ objectlist *list = object_newlist(nfixed-1, & MORPHO_GETARG(args, 1)); if (!list) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return MORPHO_NIL; } for (unsigned int i=1; idim, MESH_GRADE_AREA, x, iref.nfields, quantities, &iref, out, &err); integral_clearquantities(iref.nfields, quantities); integral_clearelref(&elref); } else { value q0[iref.nfields+1], q1[iref.nfields+1], q2[iref.nfields+1]; value *q[3] = { q0, q1, q2 }; for (unsigned int k=0; kdim, MESH_GRADE_AREA, x, iref.nfields, q, &iref, out); } if (success) *out *= elref.elementsize; integral_freetlvars(v); // Free gradient information for (int i=0; ival.count; j++) morpho_freeobject(l->val.data[j]); } morpho_freeobject(qgrad[i]); } return success; } FUNCTIONAL_METHOD(AreaIntegral, integrand, MESH_GRADE_AREA, integralref, integral_prepareref, functional_mapintegrand, areaintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(AreaIntegral, total, MESH_GRADE_AREA, integralref, integral_prepareref, functional_sumintegrand, areaintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(AreaIntegral, gradient, MESH_GRADE_AREA, integralref, integral_prepareref, functional_mapnumericalgradient, areaintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); /** Field gradients for Area Integrals */ value AreaIntegral_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; integralref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { // Should check whether the field is known about here... if (integral_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_AREA, info.sel, &ref)) { info.g=MESH_GRADE_AREA; info.integrand=areaintegral_integrand; info.cloneref=integral_cloneref; info.freeref=integral_freeref; info.ref=&ref; functional_mapnumericalfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(AreaIntegral) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, LineIntegral_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, AreaIntegral_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, AreaIntegral_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, AreaIntegral_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, AreaIntegral_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ---------------------------------------------- * VolumeIntegral * ---------------------------------------------- */ /** Integrate a function over a volume */ bool volumeintegral_integrand(vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out) { integralref iref = *(integralref *) ref; double *x[nv]; bool success; value qgrad[iref.nfields+1]; for (int i=0; idim, MESH_GRADE_VOLUME, x, iref.nfields, quantities, &iref, out, &err); integral_clearquantities(iref.nfields, quantities); integral_clearelref(&elref); } else { value q0[iref.nfields+1], q1[iref.nfields+1], q2[iref.nfields+1], q3[iref.nfields+1]; value *q[4] = { q0, q1, q2, q3 }; for (unsigned int k=0; kdim, MESH_GRADE_VOLUME, x, iref.nfields, q, &iref, out); } if (success) *out *=elref.elementsize; integral_freetlvars(v); for (int i=0; ival.count; j++) morpho_freeobject(l->val.data[j]); } morpho_freeobject(qgrad[i]); } return success; } FUNCTIONAL_METHOD(VolumeIntegral, integrand, MESH_GRADE_VOLUME, integralref, integral_prepareref, functional_mapintegrand, volumeintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(VolumeIntegral, total, MESH_GRADE_VOLUME, integralref, integral_prepareref, functional_sumintegrand, volumeintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); FUNCTIONAL_METHOD(VolumeIntegral, gradient, MESH_GRADE_VOLUME, integralref, integral_prepareref, functional_mapnumericalgradient, volumeintegral_integrand, NULL, GRADSQ_ARGS, SYMMETRY_NONE); /** Field gradients for Volume Integrals */ value VolumeIntegral_fieldgradient(vm *v, int nargs, value *args) { functional_mapinfo info; integralref ref; value out=MORPHO_NIL; if (functional_validateargs(v, nargs, args, &info)) { // Should check whether the field is known about here... if (integral_prepareref(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, MESH_GRADE_VOLUME, info.sel, &ref)) { info.g=MESH_GRADE_VOLUME; info.integrand=volumeintegral_integrand; info.cloneref=integral_cloneref; info.freeref=integral_freeref; info.ref=&ref; functional_mapnumericalfieldgradient(v, &info, &out); } else morpho_runtimeerror(v, GRADSQ_ARGS); } if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } MORPHO_BEGINCLASS(VolumeIntegral) MORPHO_METHOD(MORPHO_INITIALIZER_METHOD, LineIntegral_init, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_INTEGRAND_METHOD, VolumeIntegral_integrand, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_TOTAL_METHOD, VolumeIntegral_total, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_GRADIENT_METHOD, VolumeIntegral_gradient, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(FUNCTIONAL_FIELDGRADIENT_METHOD, VolumeIntegral_fieldgradient, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ void functional_initialize(void) { fddelta1 = pow(MORPHO_EPS, 1.0/3.0); fddelta2 = pow(MORPHO_EPS, 1.0/4.0); functional_gradeproperty=builtin_internsymbolascstring(FUNCTIONAL_GRADE_PROPERTY); functional_fieldproperty=builtin_internsymbolascstring(FUNCTIONAL_FIELD_PROPERTY); scalarpotential_functionproperty=builtin_internsymbolascstring(SCALARPOTENTIAL_FUNCTION_PROPERTY); scalarpotential_gradfunctionproperty=builtin_internsymbolascstring(SCALARPOTENTIAL_GRADFUNCTION_PROPERTY); linearelasticity_referenceproperty=builtin_internsymbolascstring(LINEARELASTICITY_REFERENCE_PROPERTY); linearelasticity_weightbyreferenceproperty=builtin_internsymbolascstring(LINEARELASTICITY_WTBYREF_PROPERTY); linearelasticity_poissonproperty=builtin_internsymbolascstring(LINEARELASTICITY_POISSON_PROPERTY); hydrogel_aproperty=builtin_internsymbolascstring(HYDROGEL_A_PROPERTY); hydrogel_bproperty=builtin_internsymbolascstring(HYDROGEL_B_PROPERTY); hydrogel_cproperty=builtin_internsymbolascstring(HYDROGEL_C_PROPERTY); hydrogel_dproperty=builtin_internsymbolascstring(HYDROGEL_D_PROPERTY); hydrogel_phirefproperty=builtin_internsymbolascstring(HYDROGEL_PHIREF_PROPERTY); hydrogel_phi0property=builtin_internsymbolascstring(HYDROGEL_PHI0_PROPERTY); equielement_weightproperty=builtin_internsymbolascstring(EQUIELEMENT_WEIGHT_PROPERTY); nematic_ksplayproperty=builtin_internsymbolascstring(NEMATIC_KSPLAY_PROPERTY); nematic_ktwistproperty=builtin_internsymbolascstring(NEMATIC_KTWIST_PROPERTY); nematic_kbendproperty=builtin_internsymbolascstring(NEMATIC_KBEND_PROPERTY); nematic_pitchproperty=builtin_internsymbolascstring(NEMATIC_PITCH_PROPERTY); functional_methodproperty=builtin_internsymbolascstring(INTEGRAL_METHOD_PROPERTY); curvature_integrandonlyproperty=builtin_internsymbolascstring(CURVATURE_INTEGRANDONLY_PROPERTY); curvature_geodesicproperty=builtin_internsymbolascstring(CURVATURE_GEODESIC_PROPERTY); objectstring objclassname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objclassname)); builtin_addclass(LENGTH_CLASSNAME, MORPHO_GETCLASSDEFINITION(Length), objclass); builtin_addclass(AREA_CLASSNAME, MORPHO_GETCLASSDEFINITION(Area), objclass); builtin_addclass(AREAENCLOSED_CLASSNAME, MORPHO_GETCLASSDEFINITION(AreaEnclosed), objclass); builtin_addclass(VOLUMEENCLOSED_CLASSNAME, MORPHO_GETCLASSDEFINITION(VolumeEnclosed), objclass); builtin_addclass(VOLUME_CLASSNAME, MORPHO_GETCLASSDEFINITION(Volume), objclass); builtin_addclass(SCALARPOTENTIAL_CLASSNAME, MORPHO_GETCLASSDEFINITION(ScalarPotential), objclass); builtin_addclass(LINEARELASTICITY_CLASSNAME, MORPHO_GETCLASSDEFINITION(LinearElasticity), objclass); builtin_addclass(HYDROGEL_CLASSNAME, MORPHO_GETCLASSDEFINITION(Hydrogel), objclass); builtin_addclass(EQUIELEMENT_CLASSNAME, MORPHO_GETCLASSDEFINITION(EquiElement), objclass); builtin_addclass(LINECURVATURESQ_CLASSNAME, MORPHO_GETCLASSDEFINITION(LineCurvatureSq), objclass); builtin_addclass(LINETORSIONSQ_CLASSNAME, MORPHO_GETCLASSDEFINITION(LineTorsionSq), objclass); builtin_addclass(MEANCURVATURESQ_CLASSNAME, MORPHO_GETCLASSDEFINITION(MeanCurvatureSq), objclass); builtin_addclass(GAUSSCURVATURE_CLASSNAME, MORPHO_GETCLASSDEFINITION(GaussCurvature), objclass); builtin_addclass(GRADSQ_CLASSNAME, MORPHO_GETCLASSDEFINITION(GradSq), objclass); builtin_addclass(NORMSQ_CLASSNAME, MORPHO_GETCLASSDEFINITION(NormSq), objclass); builtin_addclass(LINEINTEGRAL_CLASSNAME, MORPHO_GETCLASSDEFINITION(LineIntegral), objclass); builtin_addclass(AREAINTEGRAL_CLASSNAME, MORPHO_GETCLASSDEFINITION(AreaIntegral), objclass); builtin_addclass(VOLUMEINTEGRAL_CLASSNAME, MORPHO_GETCLASSDEFINITION(VolumeIntegral), objclass); builtin_addclass(NEMATIC_CLASSNAME, MORPHO_GETCLASSDEFINITION(Nematic), objclass); builtin_addclass(NEMATICELECTRIC_CLASSNAME, MORPHO_GETCLASSDEFINITION(NematicElectric), objclass); builtin_addfunction(TANGENT_FUNCTION, integral_tangent, BUILTIN_FLAGSEMPTY); builtin_addfunction(NORMAL_FUNCTION, integral_normal, BUILTIN_FLAGSEMPTY); builtin_addfunction(GRAD_FUNCTION, integral_gradfn, BUILTIN_FLAGSEMPTY); builtin_addfunction(CGTENSOR_FUNCTION, integral_cgfn, BUILTIN_FLAGSEMPTY); morpho_defineerror(VOLUMEENCLOSED_ZERO, ERROR_HALT, VOLUMEENCLOSED_ZERO_MSG); morpho_defineerror(FUNC_INTEGRAND_MESH, ERROR_HALT, FUNC_INTEGRAND_MESH_MSG); morpho_defineerror(FUNC_ELNTFND, ERROR_HALT, FUNC_ELNTFND_MSG); morpho_defineerror(SCALARPOTENTIAL_FNCLLBL, ERROR_HALT, SCALARPOTENTIAL_FNCLLBL_MSG); morpho_defineerror(LINEARELASTICITY_REF, ERROR_HALT, LINEARELASTICITY_REF_MSG); morpho_defineerror(LINEARELASTICITY_PRP, ERROR_HALT, LINEARELASTICITY_PRP_MSG); morpho_defineerror(HYDROGEL_ARGS, ERROR_HALT, HYDROGEL_ARGS_MSG); morpho_defineerror(HYDROGEL_PRP, ERROR_HALT, HYDROGEL_PRP_MSG); morpho_defineerror(HYDROGEL_FLDGRD, ERROR_HALT, HYDROGEL_FLDGRD_MSG); morpho_defineerror(HYDROGEL_ZEEROREFELEMENT, ERROR_WARNING, HYDROGEL_ZEEROREFELEMENT_MSG); morpho_defineerror(HYDROGEL_BNDS, ERROR_WARNING, HYDROGEL_BNDS_MSG); morpho_defineerror(EQUIELEMENT_ARGS, ERROR_HALT, EQUIELEMENT_ARGS_MSG); morpho_defineerror(GRADSQ_ARGS, ERROR_HALT, GRADSQ_ARGS_MSG); morpho_defineerror(NEMATIC_ARGS, ERROR_HALT, NEMATIC_ARGS_MSG); morpho_defineerror(NEMATICELECTRIC_ARGS, ERROR_HALT, NEMATICELECTRIC_ARGS_MSG); morpho_defineerror(FUNCTIONAL_ARGS, ERROR_HALT, FUNCTIONAL_ARGS_MSG); morpho_defineerror(INTEGRAL_ARGS, ERROR_HALT, INTEGRAL_ARGS_MSG); morpho_defineerror(INTEGRAL_NFLDS, ERROR_HALT, INTEGRAL_NFLDS_MSG); morpho_defineerror(INTEGRAL_MTHDDCT, ERROR_HALT, INTEGRAL_MTHDDCT_MSG); morpho_defineerror(INTEGRAL_FLD, ERROR_HALT, INTEGRAL_FLD_MSG); morpho_defineerror(INTEGRAL_AMBGSFLD, ERROR_HALT, INTEGRAL_AMBGSFLD_MSG); morpho_defineerror(INTEGRAL_SPCLFN, ERROR_HALT, INTEGRAL_SPCLFN_MSG); morpho_defineerror(INTEGRAL_GRDEVL, ERROR_HALT, INTEGRAL_GRDEVL_MSG); functional_poolinitialized = false; objectintegralelementreftype=object_addtype(&objectintegralelementrefdefn); elementhandle=vm_addtlvar(); tangenthandle=vm_addtlvar(); normlhandle=vm_addtlvar(); cauchygreenhandle=vm_addtlvar(); morpho_addfinalizefn(functional_finalize); } void functional_finalize(void) { if (functional_poolinitialized) threadpool_clear(&functional_pool); } #endif ================================================ FILE: src/geometry/functional.h ================================================ /** @file functional.h * @author T J Atherton * * @brief Functionals */ #ifndef functional_h #define functional_h #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include #include "morpho.h" #include "mesh.h" #include "field.h" #include "selection.h" /* ------------------------------------------------------- * Functionals * ------------------------------------------------------- */ /* Functional properties */ #define FUNCTIONAL_GRADE_PROPERTY "grade" #define FUNCTIONAL_FIELD_PROPERTY "field" #define SCALARPOTENTIAL_FUNCTION_PROPERTY "function" #define SCALARPOTENTIAL_GRADFUNCTION_PROPERTY "gradfunction" #define LINEARELASTICITY_REFERENCE_PROPERTY "reference" #define LINEARELASTICITY_WTBYREF_PROPERTY "weightByReference" #define LINEARELASTICITY_POISSON_PROPERTY "poissonratio" #define HYDROGEL_A_PROPERTY "a" #define HYDROGEL_B_PROPERTY "b" #define HYDROGEL_C_PROPERTY "c" #define HYDROGEL_D_PROPERTY "d" #define HYDROGEL_PHIREF_PROPERTY "phiref" #define HYDROGEL_PHI0_PROPERTY "phi0" #define EQUIELEMENT_WEIGHT_PROPERTY "weight" #define NEMATIC_KSPLAY_PROPERTY "ksplay" #define NEMATIC_KTWIST_PROPERTY "ktwist" #define NEMATIC_KBEND_PROPERTY "kbend" #define NEMATIC_PITCH_PROPERTY "pitch" #define NEMATIC_DIRECTOR_PROPERTY "director" #define CURVATURE_INTEGRANDONLY_PROPERTY "integrandonly" #define CURVATURE_GEODESIC_PROPERTY "geodesic" #define INTEGRAL_METHOD_PROPERTY "method" /* Functional methods */ #define FUNCTIONAL_INTEGRAND_METHOD "integrand" #define FUNCTIONAL_TOTAL_METHOD "total" #define FUNCTIONAL_GRADIENT_METHOD "gradient" #define FUNCTIONAL_FIELDGRADIENT_METHOD "fieldgradient" #define FUNCTIONAL_HESSIAN_METHOD "hessian" #define FUNCTIONAL_INTEGRANDFORELEMENT_METHOD "integrandForElement" /* Special functions that can be used in integrands */ #define TANGENT_FUNCTION "tangent" #define NORMAL_FUNCTION "normal" #define GRAD_FUNCTION "grad" #define CGTENSOR_FUNCTION "cgtensor" /* Functional names */ #define LENGTH_CLASSNAME "Length" #define AREA_CLASSNAME "Area" #define AREAENCLOSED_CLASSNAME "AreaEnclosed" #define VOLUME_CLASSNAME "Volume" #define VOLUMEENCLOSED_CLASSNAME "VolumeEnclosed" #define SCALARPOTENTIAL_CLASSNAME "ScalarPotential" #define LINEARELASTICITY_CLASSNAME "LinearElasticity" #define HYDROGEL_CLASSNAME "Hydrogel" #define EQUIELEMENT_CLASSNAME "EquiElement" #define LINECURVATURESQ_CLASSNAME "LineCurvatureSq" #define LINETORSIONSQ_CLASSNAME "LineTorsionSq" #define MEANCURVATURESQ_CLASSNAME "MeanCurvatureSq" #define GAUSSCURVATURE_CLASSNAME "GaussCurvature" #define GRADSQ_CLASSNAME "GradSq" #define NORMSQ_CLASSNAME "NormSq" #define LINEINTEGRAL_CLASSNAME "LineIntegral" #define AREAINTEGRAL_CLASSNAME "AreaIntegral" #define VOLUMEINTEGRAL_CLASSNAME "VolumeIntegral" #define NEMATIC_CLASSNAME "Nematic" #define NEMATICELECTRIC_CLASSNAME "NematicElectric" /* Errors */ #define FUNC_INTEGRAND_MESH "FnctlIntMsh" #define FUNC_INTEGRAND_MESH_MSG "Method 'integrand' requires a mesh as the argument." #define FUNC_INTEGRAND_MESH "FnctlIntMsh" #define FUNC_INTEGRAND_MESH_MSG "Method 'integrand' requires a mesh as the argument." #define FUNC_ELNTFND "FnctlELNtFnd" #define FUNC_ELNTFND_MSG "Mesh does not provide elements of grade %u." #define SCALARPOTENTIAL_FNCLLBL "SclrPtFnCllbl" #define SCALARPOTENTIAL_FNCLLBL_MSG "ScalarPotential function is not callable." #define INTEGRAL_ARGS "IntgrlArgs" #define INTEGRAL_ARGS_MSG "Integral functionals require a callable argument, followed by zero or more Fields." #define INTEGRAL_MTHDDCT "IntgrlMthdDct" #define INTEGRAL_MTHDDCT_MSG "The method argument requires a Dictionary containing configuration settings." #define INTEGRAL_FLD "IntgrlFld" #define INTEGRAL_FLD_MSG "Can't identify field." #define INTEGRAL_GRDEVL "IntgrlGrdEvl" #define INTEGRAL_GRDEVL_MSG "Gradient evaluation failed." #define INTEGRAL_AMBGSFLD "IntgrlAmbgsFld" #define INTEGRAL_AMBGSFLD_MSG "Field reference is ambigious: call with a Field object." #define INTEGRAL_SPCLFN "IntgrlSpclFn" #define INTEGRAL_SPCLFN_MSG "Special function '%s' must not be called outside of an Integral." #define INTEGRAL_NFLDS "IntgrlNFlds" #define INTEGRAL_NFLDS_MSG "Incorrect number of Fields provided for integrand function." #define VOLUMEENCLOSED_ZERO "VolEnclZero" #define VOLUMEENCLOSED_ZERO_MSG "VolumeEnclosed detected an element of zero size. Check that a mesh point is not coincident with the origin." #define LINEARELASTICITY_REF "LnElstctyRef" #define LINEARELASTICITY_REF_MSG "LinearElasticity requires a mesh as the argument." #define LINEARELASTICITY_PRP "LnElstctyPrp" #define LINEARELASTICITY_PRP_MSG "LinearElasticity requires properties 'reference' to be a mesh, 'grade' to be an integer grade and 'poissonratio' to be a number." #define EQUIELEMENT_ARGS "EquiElArgs" #define EQUIELEMENT_ARGS_MSG "EquiElement allows 'grade' and 'weight' as optional arguments." #define HYDROGEL_ARGS "HydrglArgs" #define HYDROGEL_ARGS_MSG "Hydrogel requires a reference mesh and allows 'grade', 'a', 'b', 'c', 'd', 'phi0' and 'phiref' as optional arguments." #define HYDROGEL_PRP "HydrglPrp" #define HYDROGEL_PRP_MSG "Hydrogel requires the first argument to be a mesh, 'grade' to be an integer grade, 'a', 'b', 'c' 'd', 'phiref' to be numbers and 'phi0' to be a number or a Field." #define HYDROGEL_FLDGRD "HydrglFldGrd" #define HYDROGEL_FLDGRD_MSG "Hydrogel has been given phi0 as a Field that lacks scalar elements in grade %u." #define HYDROGEL_ZEEROREFELEMENT "HydrglZrRfVl" #define HYDROGEL_ZEEROREFELEMENT_MSG "Reference element %u has tiny volume V=%g, V0=%g\n" #define HYDROGEL_BNDS "HydrglBnds" #define HYDROGEL_BNDS_MSG "Phi outside bounds at element %u V=%g, V0=%g, phi=%g, 1-phi=%g\n" #define GRADSQ_ARGS "GradSqArgs" #define GRADSQ_ARGS_MSG "GradSq requires a field as the argument." #define NEMATIC_ARGS "NmtcArgs" #define NEMATIC_ARGS_MSG "Nematic requires a field as the argument." #define NEMATICELECTRIC_ARGS "NmtcElArgs" #define NEMATICELECTRIC_ARGS_MSG "NematicElectric requires the director and electric field or potential as arguments (in that order)." #define FUNCTIONAL_ARGS "FnctlArgs" #define FUNCTIONAL_ARGS_MSG "Invalid args passed to method." /* ------------------------------------------------------- * Functional types * ------------------------------------------------------- */ extern value functional_gradeproperty; extern value functional_fieldproperty; /** Symmetry behaviors */ typedef enum { SYMMETRY_NONE, SYMMETRY_ADD } symmetrybhvr; /** Integrand function */ typedef bool (functional_integrand) (vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, double *out); /** Gradient function */ typedef bool (functional_gradient) (vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectmatrix *frc); /** Field gradient function */ typedef bool (functional_fieldgradient) (vm *v, objectmesh *mesh, elementid id, int nv, int *vid, void *ref, objectfield *frc); struct s_functional_mapinfo; // Resolve circular typedef dependency /** Clone reference function */ typedef void * (functional_cloneref) (void *ref, objectfield *field, objectfield *sub); /** Free reference function */ typedef void (functional_freeref) (void *ref); /** Dependencies function */ typedef bool (functional_dependencies) (struct s_functional_mapinfo *info, elementid id, varray_elementid *out); typedef struct s_functional_mapinfo { objectmesh *mesh; // Mesh to use objectselection *sel; // Selection, if any objectfield *field; // Field, if any grade g; // Grade to use elementid id; // Element id at which to evaluate the integrand functional_integrand *integrand; // Integrand function functional_gradient *grad; // Gradient functional_fieldgradient *fieldgrad; // Field gradient functional_dependencies *dependencies; // Dependencies functional_cloneref *cloneref; // Clone a reference with a given field substituted functional_freeref *freeref; // Free a reference symmetrybhvr sym; // Symmetry behavior void *ref; // Reference to pass on } functional_mapinfo; bool functional_validateargs(vm *v, int nargs, value *args, functional_mapinfo *info); void functional_symmetryimagelist(objectmesh *mesh, grade g, bool sort, varray_elementid *ids); bool functional_symmetrysumforces(objectmesh *mesh, objectmatrix *frc); bool functional_inlist(varray_elementid *list, elementid id); bool functional_containsvertex(int nv, int *vid, elementid id); bool functional_sumintegrand(vm *v, functional_mapinfo *info, value *out); bool functional_mapintegrand(vm *v, functional_mapinfo *info, value *out); bool functional_mapintegrandat(vm *v, functional_mapinfo *info, value *out); bool functional_mapgradient(vm *v, functional_mapinfo *info, value *out); bool functional_mapfieldgradient(vm *v, functional_mapinfo *info, value *out); bool functional_mapnumericalgradient(vm *v, functional_mapinfo *info, value *out); bool functional_mapnumericalfieldgradient(vm *v, functional_mapinfo *info, value *out); void functional_vecadd(unsigned int n, double *a, double *b, double *out); void functional_vecaddscale(unsigned int n, double *a, double lambda, double *b, double *out); void functional_vecsub(unsigned int n, double *a, double *b, double *out); void functional_vecscale(unsigned int n, double lambda, double *a, double *out); double functional_vecnorm(unsigned int n, double *a); double functional_vecdot(unsigned int n, double *a, double *b); void functional_veccross(double *a, double *b, double *out); void functional_veccross2d(double *a, double *b, double *out); bool functional_elementsize(vm *v, objectmesh *mesh, grade g, elementid id, int nv, int *vid, double *out); bool functional_elementgradient_scale(vm *v, objectmesh *mesh, grade g, elementid id, int nv, int *vid, objectmatrix *frc, double scale); bool functional_elementgradient(vm *v, objectmesh *mesh, grade g, elementid id, int nv, int *vid, objectmatrix *frc); /* ------------------------------------------------------- * Functional method macros * ------------------------------------------------------- */ /** Initialize a functional */ #define FUNCTIONAL_INIT(name, grade) value name##_init(vm *v, int nargs, value *args) { \ objectinstance_setproperty(MORPHO_GETINSTANCE(MORPHO_SELF(args)), functional_gradeproperty, MORPHO_INTEGER(grade)); \ return MORPHO_NIL; \ } /** Evaluate an integrand */ #define FUNCTIONAL_INTEGRAND(name, grade, integrandfn) value name##_integrand(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.integrand = integrandfn; \ functional_mapintegrand(v, &info, &out); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ return out; \ } /** Evaluate an integrand at an element */ #define FUNCTIONAL_INTEGRANDFORELEMENT(name, grade, integrandfn) value name##_integrandForElement(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.integrand = integrandfn; \ functional_mapintegrandforelement(v, &info, &out); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ return out; \ } /** Evaluate a gradient */ #define FUNCTIONAL_GRADIENT(name, grade, gradientfn, symbhvr) \ value name##_gradient(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.grad = gradientfn; info.sym = symbhvr; \ functional_mapgradient(v, &info, &out); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ \ return out; \ } /** Evaluate a gradient */ #define FUNCTIONAL_NUMERICALGRADIENT(name, grade, integrandfn, symbhvr) \ value name##_gradient(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.integrand = integrandfn; info.sym = symbhvr; \ functional_mapnumericalgradient(v, &info, &out); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ \ return out; \ } /** Total an integrand */ #define FUNCTIONAL_TOTAL(name, grade, totalfn) \ value name##_total(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.integrand = totalfn; \ functional_sumintegrand(v, &info, &out); \ } \ \ return out; \ } /** Hessian */ #define FUNCTIONAL_HESSIAN(name, grade, totalfn) \ value name##_hessian(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ info.g = grade; info.integrand = totalfn; \ functional_mapnumericalhessian(v, &info, &out); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ \ return out; \ } /* Alternative way of defining methods that use a reference */ #define FUNCTIONAL_METHOD(class, name, grade, reftype, prepare, integrandfn, integrandmapfn, deps, err, symbhvr) value class##_##name(vm *v, int nargs, value *args) { \ functional_mapinfo info; \ reftype ref; \ value out=MORPHO_NIL; \ \ if (functional_validateargs(v, nargs, args, &info)) { \ if (prepare(MORPHO_GETINSTANCE(MORPHO_SELF(args)), info.mesh, grade, info.sel, &ref)) { \ info.integrand = integrandmapfn; \ info.dependencies = deps, \ info.sym = symbhvr; \ info.g = grade; \ info.ref = &ref; \ integrandfn(v, &info, &out); \ } else morpho_runtimeerror(v, err); \ } \ if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); \ return out; \ } /* ------------------------------------------------------- * Initialization * ------------------------------------------------------- */ void functional_initialize(void); void functional_finalize(void); #endif #endif /* functional_h */ ================================================ FILE: src/geometry/geometry.c ================================================ /** @file geometry.c * @author T J Atherton * * @brief Geometry wrapper */ #include "geometry.h" void geometry_initialize(void) { mesh_initialize(); integrate_initialize(); field_initialize(); functional_initialize(); fespace_initialize(); selection_initialize(); } ================================================ FILE: src/geometry/geometry.h ================================================ /** @file geometry.h * @author T J Atherton * * @brief Geometry wrapper */ #ifndef geometry_h #define geometry_h #include "mesh.h" #include "field.h" #include "selection.h" #include "functional.h" #include "fespace.h" #include "integrate.h" void geometry_initialize(void); #endif ================================================ FILE: src/geometry/integrate.c ================================================ /** @file integrate.c * @author T J Atherton * * @brief Numerical integration */ #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include #include #include "integrate.h" #include "morpho.h" #include "classes.h" #include "matrix.h" #include "sparse.h" #include "geometry.h" bool integrate_recognizequantities(unsigned int nquantity, value *quantity, value *out) { if (nquantity>0) { for (unsigned int i=0; incols*m0->nrows; i++) { out->elements[i] = lambda[0]*m0->elements[i]+lambda[1]*m1->elements[i]; } } } } /** Integrate over a line element * @param[in] function - function to integrate * @param[in] dim - Dimension of the vertices * @param[in] x - vertices of the line x[0] = {x,y,z} etc. * @param[in] nquantity - number of quantities per vertex * @param[in] quantity - List of quantities for each vertex. * @param[in] ref - a pointer to any data required by the function * @param[in] ge - Global estimate of the integral (used for recursion). * @param[out] out - estimate of the integral * @returns True on success */ bool integrate_lineint(integrandfunction *function, unsigned int dim, double *x[2], unsigned int nquantity, value *quantity[2], value *q, void *ref, unsigned int recursiondepth, double ge, double *out) { double r[gknpts], r1=0.0, r2=0.0, eps; double xx[dim], gest=ge; double af=pow(0.5, (double) recursiondepth); // Length of whole line from recursion depth unsigned int i; bool success=false; double fout = 0; /* Try low order method for rapid results on low order functions */ for (unsigned int i=0; iMORPHO_EPS) eps/=gest; // Globally relative estimate using area factor //printf("Recursion depth %u: %g %g - %g\n",recursiondepth, r1, r2, eps); if (fabs(eps)INTEGRATE_MAXRECURSION) { *out=r2; return false; } /* Bisect: */ double *xn[2]; /* Will hold the vertices. */ double xm[dim]; double est; value qm[nquantity+1], *qn[2]; /* New vertices s*/ for (unsigned int i=0; incols*m0->nrows; i++) { out->elements[i] = lambda[0]*m0->elements[i]+lambda[1]*m1->elements[i]+lambda[2]*m2->elements[i]; } } } } /** Integrate over an area element * @param[in] function - function to integrate * @param[in] dim - Dimension of the vertices * @param[in] x - vertices of the line x[0] = {x,y,z} etc. * @param[in] nquantity - number of quantities per vertex * @param[in] quantity - List of quantities for each vertex. * @param[in] ref - a pointer to any data required by the function * @param[in] ge - Global estimate of the integral (used for recursion). * @param[out] out - estimate of the integral * @returns True on success */ bool integrate_areaint(integrandfunction *function, unsigned int dim, double *x[3], unsigned int nquantity, value *quantity[3], value *q, void *ref, unsigned int recursiondepth, double ge, double *out) { double r[npts2], r1, rr, r2, rr2, r3, rr3, eps; double xx[dim], gest=ge; double af=pow(0.25, (double) recursiondepth); // Area of total triangle covered from recursion depth bool success=false; double fout = 0; /* Try low order method for rapid results on low order functions */ for (unsigned int i=0; iMORPHO_EPS) eps/=gest; // Globally relative estimate using area factor if (fabs(eps)MORPHO_EPS) eps/=gest; // Globally relative estimate //printf("Estimates %lg %lg %lg, err=%g af=%g\n", r1,r2,r3, eps, af); if (fabs(eps)INTEGRATE_MAXRECURSION) { *out=2*r3; return false; } /* Quadrasect: * 2 * / \ * x20 - x12 * / \ / \ * 0 - x01 - 1 */ double *xn[3]; /* Will hold the vertices. */ double x01[dim], x12[dim], x20[dim]; /* Vertices from midpoints */ double sub; value q01[nquantity+1], q12[nquantity+1], q20[nquantity+1], *qn[3]; r3=0.0; /* New vertices s*/ for (unsigned int i=0; incols*m0->nrows; i++) { out->elements[i] = lambda[0]*m0->elements[i]+lambda[1]*m1->elements[i]+lambda[2]*m2->elements[i]+lambda[3]*m3->elements[i]; } } } } int nf = 0; /** Integrate over an volume element given a specified integration rule * @param[in] function - function to integrate * @param[in] nsamples - number of sampling pts * @param[in] integrationrule - integration rule data * @param[in] dim - Dimension of the vertices * @param[in] x - vertices of the line x[0] = {x,y,z} etc. * @param[in] nquantity - number of quantities per vertex * @param[in] quantity - List of quantities for each vertex. * @param[in] ref - a pointer to any data required by the function * @param[out] out - estimate of the integral * @returns True on success */ bool integrate_integratevol(integrandfunction *function, unsigned int nsamples, double *integrationrule, unsigned int dim, double *x[4], unsigned int nquantity, value *quantity[3], value *q, void *ref, double *out) { double xx[dim]; double r[nsamples], rout=0; double fout = 0; for (unsigned int i=0; iMORPHO_EPS) eps/=gest; // Globally relative estimate using volume factor if (fabs(eps)integrand=NULL; integrate->dim=0; integrate->nbary=0; integrate->nquantity=0; integrate->adapt=true; integrate->rule = NULL; integrate->errrule = NULL; integrate->subdivide = NULL; varray_quadratureworkiteminit(&integrate->worklist); varray_doubleinit(&integrate->vertexstack); varray_intinit(&integrate->elementstack); integrate->ztol = INTEGRATE_ZEROCHECK; integrate->tol = INTEGRATE_ACCURACYGOAL; integrate->maxiterations = INTEGRATE_MAXITERATIONS; integrate->niterations = 0; integrate->val = 0; integrate->err = 0; integrate->ref = NULL; } /** Free data associated with an integrator */ void integrator_clear(integrator *integrate) { varray_quadratureworkitemclear(&integrate->worklist); varray_intclear(&integrate->elementstack); varray_doubleclear(&integrate->vertexstack); } /** Adds a vertex to the integrators vertex stack, returning the id */ int integrator_addvertex(integrator *integrate, int ndof, double *v) { int vid = integrate->vertexstack.count; varray_doubleadd(&integrate->vertexstack, v, ndof); return vid; } /** Adds an element to the element stack, returning the id. Elements are specified by their coordinates in the reference element */ int integrator_addelement(integrator *integrate, int *vids) { int elid=integrate->elementstack.count; varray_intadd(&integrate->elementstack, vids, integrate->nbary); return elid; } /** Process the list of quantities given */ void integrator_initializequantities(integrator *integrate, int nq, quantity *quantity) { integrate->nquantity=nq; integrate->quantity=quantity; for (int i=0; iqval[i]=q; } else if (MORPHO_ISMATRIX(q)) { objectmatrix *m = MORPHO_GETMATRIX(q); quantity[i].ndof=matrix_countdof(m); objectmatrix *new = object_clonematrix(m); // Use a copy of the matrix integrate->qval[i]=MORPHO_OBJECT(new); } else return; } } /** Frees up any objects used in the quantities list */ void integrator_finalizequantities(integrator *integrate) { for (int i=0; inquantity; i++) morpho_freeobject(integrate->qval[i]); } /** Retrieves the vertex pointers given an elementid. @warning: The pointers returned become invalid after a subsequent call to integrator_addvertex . */ void integrator_getvertices(integrator *integrate, int elementid, double **vert) { for (int i=0; inbary; i++) { int vid=integrate->elementstack.data[elementid+i]; vert[i]=&(integrate->vertexstack.data[vid]); } } /** Retrieves an element with elementid */ void integrator_getelement(integrator *integrate, int elementid, int *vid) { for (int i=0; inbary; i++) { vid[i]=integrate->elementstack.data[elementid+i]; } } /** Adds a work item to the integrator's work list. Uses a binary queue data structure to facilitate ln(N) push and pop - https://en.wikipedia.org/wiki/Binary_heap */ bool integrator_pushworkitem(integrator *integrate, quadratureworkitem *work) { varray_quadratureworkitemadd(&integrate->worklist, work, 1); for (int i=integrate->worklist.count-1, p; i>0; i=p) { p=floor((i-1)/2); // Parent if (integrate->worklist.data[i].err>integrate->worklist.data[p].err) { quadratureworkitem swp=integrate->worklist.data[i]; integrate->worklist.data[i]=integrate->worklist.data[p]; integrate->worklist.data[p]=swp; } else break; } return true; } /** Pops the work item with the largest error */ bool integrator_popworkitem(integrator *integrate, quadratureworkitem *work) { *work = integrate->worklist.data[0]; // Move the last element into first place and pop int n=integrate->worklist.count-1; if (n>0) integrate->worklist.data[0]=integrate->worklist.data[n]; integrate->worklist.count--; // Go down the heap, ensuring that the heap property is maintained for (int i=0, p, q; iworklist.data[q].err>integrate->worklist.data[p].err) { p=q; } // If the child element is larger, swap it up if (pworklist.data[p].err>integrate->worklist.data[i].err) { quadratureworkitem swp=integrate->worklist.data[i]; integrate->worklist.data[i]=integrate->worklist.data[p]; integrate->worklist.data[p]=swp; } else break; } return true; } /** Estimate the value and error of the integrand given a worklist */ void integrator_estimate(integrator *integrate) { double sumval=0.0, cval=0.0, yval, tval, sumerr=0.0, cerr=0.0, yerr, terr; // Sum in reverse as smallest entries should be nearer the end for (int i=integrate->worklist.count-1; i>=0; i--) { yval=integrate->worklist.data[i].val-cval; yerr=integrate->worklist.data[i].err-cerr; tval=sumval+yval; terr=sumerr+yerr; cval=(tval-sumval)-yval; cerr=(terr-sumerr)-yerr; sumval=tval; sumerr=terr; } integrate->val = sumval; integrate->errest = sumerr; } /* -------------------------------- * Linear interpolation * -------------------------------- */ /** Construct vertex transformation matrices @param[in] integrate - the integrator @param[in] vref - vertices specified in reference element (length integrate->nbary) @param[out] r - matrix mapping local node coordinates to ref. el coordinates [r has nbary rows and nbary columns] @param[out] v - matrix mapping ref. el coordinates to physical coordinates [v has dim rows and nbary columns] */ void integrator_preparevertices(integrator *integrate, double **vref, double *r, double *v) { int l=0; if (r) for (int i=0; inbary; i++) { // Loop over vertices [defined rel. to ref. element] for (int k=0; knbary; k++) { // Sum over barycentric coordinates r[l]=vref[i][k]; l++; } } l=0; if (v) for (int i=0; inbary; i++) { // Loop over vertices [defined rel. to ref. element] for (int j=0; jdim; j++) { // Loop over dimensions v[l]=integrate->x[i][j]; l++; } } } /** Sets up interpolation matrix */ void integrator_prepareinterpolation(integrator *integrate, int elementid, double *rmat, double *vmat) { double *vert[integrate->nbary]; // Vertex information integrator_getvertices(integrate, elementid, vert); integrator_preparevertices(integrate, vert, rmat, vmat); } /** Weighted sum of a list */ double integrator_sumlistweighted(unsigned int nel, double *list, double *wts) { return cblas_ddot(nel, list, 1, wts, 1); } /** Transforms local element coordinates to reference element coordinates */ void integrator_transformtorefelement(integrator *integrate, double *rmat, double *local, double *bary) { // Multiply nbary x nbary (rmat) with nbary x 1 (local) to get nbary x 1 (bary) // [1/13/25] Manual matrix multiply is faster on macOS/Intel. TODO: Check on other platforms int nbary=integrate->nbary; for (int j=0; jnbary, 1, integrate->nbary, 1.0, rmat, integrate->nbary, local, integrate->nbary, 0.0, bary, integrate->nbary); } /** Transform from reference element barycentric coordinates to physical coordinates */ void integrator_interpolatecoordinates(integrator *integrate, double *lambda, double *vmat, double *x) { // Multiply dim x nbary (vmat) with nbary x 1 (lambda) to get dim x 1 (x) int dim=integrate->dim, nbary=integrate->nbary; for (int j=0; jdim, 1, integrate->nbary, 1.0, vmat, integrate->dim, lambda, integrate->nbary, 0.0, x, integrate->dim); } /** Sums a weighted list of quantities */ bool integrator_sumquantityweighted(int n, double *wts, value *q, value *out) { bool success=false; if (MORPHO_ISFLOAT(q[0])) { double qval[n]; for (int j=0; jnquantity; i++) { int nnodes = integrate->quantity[i].nnodes; double wts[nnodes]; if (integrate->quantity[i].ifn) { (integrate->quantity[i].ifn) (bary, wts); } else { for (int k=0; kquantity[i].vals, &integrate->qval[i]); } } /* -------------------------------- * Function to perform quadrature * -------------------------------- */ /** Evaluates the integrand at specified places */ bool integrator_evalfn(integrator *integrate, quadraturerule *rule, int imin, int imax, double *rmat, double *vmat, double *x, double *f) { double node[integrate->nbary]; for (int i=imin; inodes[integrate->nbary*i], node); integrator_interpolatecoordinates(integrate, node, vmat, x); if (integrate->nquantity) integrator_interpolatequantities(integrate, node); // Evaluate function if (!(*integrate->integrand) (integrate->dim, node, x, integrate->nquantity, integrate->qval, integrate->ref, &f[i])) return false; } return true; } /** Integrates a function over an element specified in work, filling out the integral and error estimate if provided */ bool integrator_quadrature(integrator *integrate, quadraturerule *rule, quadratureworkitem *work) { int n = rule->nnodes; int nmax = rule->nnodes; int np = 0; // Number of levels of p-refinement for (quadraturerule *q = rule->ext; q!=NULL; q=q->ext) { // Find maximum number of pts nmax = q->nnodes; np++; } double rmat[integrate->nbary*integrate->nbary]; // Transform local element coordinates to ref. el. double vmat[integrate->nbary*integrate->dim]; // Transform barycentric coordinates in ref. el. to physical coordinates integrator_prepareinterpolation(integrate, work->elementid, rmat, vmat); // Evaluate function at quadrature points double x[integrate->dim], f[nmax]; if (!integrator_evalfn(integrate, rule, 0, rule->nnodes, rmat, vmat, x, f)) return false; double r[np+1]; double eps[np+1]; eps[0]=0.0; // Obtain estimate r[0]=integrator_sumlistweighted(rule->nnodes, f, rule->weights); work->lval = work->val = work->weight*r[0]; // Estimate error if (rule->ext!=NULL) { // Evaluate extension rule int nmin = rule->nnodes, ip=0; // Attempt p-refinement if available for (quadraturerule *q=rule->ext; q!=NULL; q=q->ext) { ip++; if (!integrator_evalfn(integrate, q, nmin, q->nnodes, rmat, vmat, x, f)) return false; r[ip]=integrator_sumlistweighted(q->nnodes, f, q->weights); eps[ip]=fabs(r[ip]-r[ip-1]); nmin = q->nnodes; if (fabs(r[ip])ztol || fabs(eps[ip]/r[ip])tol) break; } work->lval = work->weight*r[ip-1]; work->val = work->weight*r[ip]; // Record better estimate work->err = work->weight*eps[ip]; // Use the difference as the error estimator } else if (integrate->errrule) { // Otherwise, use the error rule to obtain the estimate if (rule==integrate->errrule) return true; // We already are using the error rule double temp = work->val; // Retain the lower order estimate if (!integrator_quadrature(integrate, integrate->errrule, work)) return false; work->lval=temp; work->err=fabs(work->val-temp); // Estimate error from difference of rules } else { UNREACHABLE("Integrator definition inconsistent."); } return true; } /* -------------------------------- * Subdivision * -------------------------------- */ bool integrator_subdivide(integrator *integrate, quadratureworkitem *work, int *nels, quadratureworkitem *newitems) { subdivisionrule *rule = integrate->subdivide; // Fetch the element data int vid[integrate->nbary+rule->npts]; integrator_getelement(integrate, work->elementid, vid); // Get ready for interpolation double rmat[integrate->nbary*integrate->nbary]; // Vertex information integrator_prepareinterpolation(integrate, work->elementid, rmat, NULL); // Interpolate vertices double lambda[integrate->nbary]; for (int j=0; jnpts; j++) { integrator_transformtorefelement(integrate, rmat, &rule->pts[j*integrate->nbary], lambda); vid[integrate->nbary+j]=integrator_addvertex(integrate, integrate->nbary, lambda); } // Create elements for (int i=0; inels; i++) { newitems[i].val=0.0; newitems[i].err=0.0; newitems[i].weight=work->weight*rule->weights[i]; if (newitems[i].weightval*DBL_EPSILON) { error_writewithid(integrate->err, INTEGRATE_SBDVSNS); return false; } // Construct new element from the vertex ids int vids[integrate->nbary]; for (int k=0; knbary; k++) { vids[k]=vid[rule->newels[integrate->nbary*i+k]]; } // Define the new element newitems[i].elementid=integrator_addelement(integrate, vids); } *nels = rule->nels; return true; } /* -------------------------------- * Laurie's sharper error estimate * -------------------------------- */ /** Laurie's sharper error estimator: BIT 23 (1983), 258-261 The norm of the difference between two rules |A-B| is usually too pessimistic; this attempts to extrapolate a sharper estimate if convergence looks good */ void integrator_sharpenerrorestimate(integrator *integrate, quadratureworkitem *work, int nels, quadratureworkitem *newitems) { double a1=work->val, b1=work->lval, a2=0, b2=0; for (int k=0; kval-=work->val; integrate->errest-=work->err; for (int k=0; kval+=dval; integrate->errest+=derr; } /* -------------------------------- * Integrator configuration * -------------------------------- */ /** Finds a rule by name */ bool integrator_matchrulebyname(int grade, char *name, quadraturerule **out) { for (int i=0; quadrules[i]!=NULL; i++) { if (quadrules[i]->grade!=grade) continue; if (name && quadrules[i]->name && (strcmp(name, quadrules[i]->name)==0)) { // Match a rule by name *out = quadrules[i]; return true; } } return false; } /** Attempts to find a quadrature rule that uses rule as an extension. */ bool integrator_matchrulebyextension(quadraturerule *rule, quadraturerule **out) { for (int i=0; quadrules[i]!=NULL; i++) { if (quadrules[i]->ext==rule) { *out = quadrules[i]; return true; } } return false; } /** Finds the [highest/lowest] rule with order such that minorder <= order <= maxorder */ bool integrator_matchrulebyorder(int grade, int minorder, int maxorder, bool highest, quadraturerule **out) { int best=-1, bestorder=(highest ? -1 : INT_MAX); for (int i=0; quadrules[i]!=NULL; i++) { if (quadrules[i]->grade!=grade) continue; if (quadrules[i]->order>=minorder && quadrules[i]->order<=maxorder && ( (highest && quadrules[i]->order>bestorder) || (!highest && quadrules[i]->orderorder; } } if (best>=0) *out = quadrules[best]; return (best>=0); } /** Returns a default rule for each grade */ bool integrator_matchrulebygrade(int grade, quadraturerule **out) { for (int i=0; defaultquadrule[i]!=NULL; i++) { if (defaultquadrule[i]->grade==grade) { *out = defaultquadrule[i]; return true; } } return false; } /** Configures an integrator based on the grade to integrate and hints for order and rule type * @param[in] integrate - integrator structure to be configured * @param[in] err - error structure to report errors to * @param[in] adapt - enable adaptive refinement * @param[in] grade - Dimension of the vertices * @param[in] order - Requested order of quadrature rule * @param[in] name - Alternatively, supply the name of a known rule * @returns true if the configuration was successful */ bool integrator_configure(integrator *integrate, error *err, bool adapt, int grade, int order, char *name) { integrate->rule=NULL; integrate->errrule=NULL; integrate->adapt=adapt; integrate->err=err; integrate->nbary=grade+1; // Number of barycentric coordinates if (name) { if (!integrator_matchrulebyname(grade, name, &integrate->rule)) { error_writewithid(err, INTEGRATE_RLNTFND, name); return false; } } else if (order>=0) { integrator_matchrulebyorder(grade, order, INT_MAX, false, &integrate->rule); } else { integrator_matchrulebygrade(grade, &integrate->rule); } // Check we succeeded in finding a rule if (!integrate->rule) { error_writewithid(err, INTEGRATE_RLUNAVLB); return false; } // Do we need to find an extension rule? if (adapt && integrate->rule->ext==NULL) { // Find if the rule obtained is an extension of another rule if (integrator_matchrulebyextension(integrate->rule, &integrate->rule)) { } else if (!integrator_matchrulebyorder(grade, integrate->rule->order+1, INT_MAX, false, &integrate->errrule)) { // Otherwise attempt to find a rule of higher order // but if there wasn't one, find the next lowest one... if (!integrator_matchrulebyorder(grade, 0, integrate->rule->order-1, true, &integrate->errrule)) return false; } // Ensure that the error rule is higher than the integration rule if (integrate->errrule && integrate->rule->order>integrate->errrule->order) { quadraturerule *swp=integrate->rule; integrate->rule=integrate->errrule; integrate->errrule=swp; } } // Select subdivision rule for (int i=0; subdivisionrules[i]!=NULL; i++) { if (subdivisionrules[i]->grade==grade) { integrate->subdivide = subdivisionrules[i]; break; } } return true; } /** Configures the integrator based on the contents of a dictionary */ bool integrator_configurewithdictionary(integrator *integrate, error *err, grade g, objectdictionary *dict) { char *name=NULL; bool adapt=true; int order=-1; value val; objectstring rulelabel = MORPHO_STATICSTRING(INTEGRATE_RULELABEL); objectstring degreelabel = MORPHO_STATICSTRING(INTEGRATE_DEGREELABEL); objectstring adaptlabel = MORPHO_STATICSTRING(INTEGRATE_ADAPTLABEL); if (dictionary_get(&dict->dict, MORPHO_OBJECT(&rulelabel), &val)) { if (MORPHO_ISSTRING(val)) { name = MORPHO_GETCSTRING(val); } else { error_writewithid(err, INTEGRATE_MTHDTYP, INTEGRATE_RULELABEL, STRING_CLASSNAME); return false; } } if (dictionary_get(&dict->dict, MORPHO_OBJECT(°reelabel), &val)) { if (MORPHO_ISINTEGER(val)) { order = MORPHO_GETINTEGERVALUE(val); } else { error_writewithid(err, INTEGRATE_MTHDTYP, INTEGRATE_DEGREELABEL, INT_CLASSNAME); return false; } } if (dictionary_get(&dict->dict, MORPHO_OBJECT(&adaptlabel), &val)) { if (MORPHO_ISBOOL(val)) { adapt = MORPHO_GETBOOLVALUE(val); } else { error_writewithid(err, INTEGRATE_MTHDTYP, INTEGRATE_ADAPTLABEL, BOOL_CLASSNAME); return false; } } return integrator_configure(integrate, err, adapt, g, order, name); } /* -------------------------------- * Driver routine * -------------------------------- */ /** Integrates over a function * @param[in] integrate - integrator structure, that has been configured with integrator_configure * @param[in] integrand - function to integrate * @param[in] dim - Dimension of the vertices * @param[in] x - vertices of the line x[0] = {x,y,z} etc. * @param[in] nquantity - number of quantities per vertex * @param[in] quantity - List of quantities for each vertex. * @param[in] ref - a pointer to any data required by the function * @returns True on success */ bool integrator_integrate(integrator *integrate, integrandfunction *integrand, int dim, double **x, unsigned int nquantity, quantity *quantity, void *ref) { bool success=false; integrate->integrand=integrand; // Integrand function integrate->ref=ref; integrate->x=x; // Vertices integrate->dim=dim; integrate->worklist.count=0; // Reset these integrate->vertexstack.count=0; integrate->elementstack.count=0; // Quantities value qval[nquantity+1]; integrate->qval=qval; integrator_initializequantities(integrate, nquantity, quantity); // Create first element, which corresponds to the reference element int vids[integrate->nbary]; double xref[integrate->nbary]; for (int i=0; inbary; i++) xref[i]=0.0; for (int i=0; inbary; i++) { xref[i]=1.0; vids[i]=integrator_addvertex(integrate, integrate->nbary, xref); xref[i]=0.0; } int elid = integrator_addelement(integrate, vids); // Add it to the work list quadratureworkitem work; work.weight = 1.0; work.elementid = elid; integrator_quadrature(integrate, integrate->rule, &work); // Perform initial quadrature integrator_pushworkitem(integrate, &work); integrator_estimate(integrate); // Initial estimate if (integrate->adapt) for (integrate->niterations=0; integrate->niterations<=integrate->maxiterations; integrate->niterations++) { // Convergence check if (fabs(integrate->val)ztol || fabs(integrate->errest/integrate->val)tol) break; // Get worst interval integrator_popworkitem(integrate, &work); // Subdivide int nels; // Number of elements created quadratureworkitem newitems[integrate->subdivide->nels]; if (!integrator_subdivide(integrate, &work, &nels, newitems)) goto integrator_integrate_error; for (int k=0; krule, &newitems[k]); // Error estimate integrator_sharpenerrorestimate(integrate, &work, nels, newitems); // Add new items to heap and update error estimates integrator_update(integrate, &work, nels, newitems); } // Final estimate by Kahan summing heap integrator_estimate(integrate); success=true; integrator_integrate_error: integrator_finalizequantities(integrate); return success; } /* --------------------------------------- * Public interface resembling old version * --------------------------------------- */ /** Integrate over an element - public interface for one off integrals. * @param[in] integrand - integrand * @param[in] method - Dictionary with method selection (optional) * @param[in] err - Error structure to report errors (optional) * @param[in] dim - Dimension of the vertices * @param[in] grade - Grade to integrate over * @param[in] x - vertices of the triangle x[0] = {x,y,z} etc. * @param[in] nquantity - number of quantities per vertex * @param[in] quantity - List of quantities * @param[in] ref - a pointer to any data required by the function * @param[out] out - value of the integral * @param[out] errest - an estimate of the error * @returns true on success. */ bool integrate(integrandfunction *integrand, objectdictionary *method, error *err, unsigned int dim, unsigned int grade, double **x, unsigned int nquantity, quantity *quantity, void *ref, double *out, double *errest) { bool success=false; integrator integrate; integrator_init(&integrate); if (method) { if (!integrator_configurewithdictionary(&integrate, err, grade, method)) return false; } else if (!integrator_configure(&integrate, err, true, grade, -1, NULL)) return false; success=integrator_integrate(&integrate, integrand, dim, x, nquantity, quantity, ref); *out = integrate.val; if (errest) *errest = integrate.errest; integrator_clear(&integrate); return success; } /* ------------------------------------- * Public interface matching old version * ------------------------------------- */ void integrate_initialize(void) { morpho_defineerror(INTEGRATE_SBDVSNS, ERROR_HALT, INTEGRATE_SBDVSNS_MSG); morpho_defineerror(INTEGRATE_RLNTFND, ERROR_HALT, INTEGRATE_RLNTFND_MSG); morpho_defineerror(INTEGRATE_RLUNAVLB, ERROR_HALT, INTEGRATE_RLUNAVLB_MSG); morpho_defineerror(INTEGRATE_MTHDTYP, ERROR_HALT, INTEGRATE_MTHDTYP_MSG); } #endif ================================================ FILE: src/geometry/integrate.h ================================================ /** @file integrate.h * @author T J Atherton * * @brief Numerical integration */ #ifndef integration_h #define integration_h #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include #include "morpho.h" #include "dict.h" #include "fespace.h" #define INTEGRATE_RULELABEL "rule" #define INTEGRATE_DEGREELABEL "degree" #define INTEGRATE_ADAPTLABEL "adapt" #define INTEGRATE_ACCURACYGOAL 1e-6 #define INTEGRATE_ZEROCHECK 1e-15 #define INTEGRATE_MAXRECURSION 100 #define INTEGRATE_MAXITERATIONS 1000 /* ------------------------------------------------------- * Integrator type definitions * ------------------------------------------------------- */ /* ---------------------------------- * Integrands * ---------------------------------- */ /** Generic specification for an integrand. * @param[in] dim - The dimension of the space * @param[in] lambda - Barycentric coordinates for the element * @param[in] x - Coordinates of the point calculated from interpolation * @param[in] nquantity - Number of quantities * @param[in] quantity - List of quantities evaluated for the point, calculated from interpolation * @param[in] ref - A reference passed by the caller (typically things constant over the domain * @returns value of the integrand at the appropriate point with interpolated quantities. */ typedef bool (integrandfunction) (unsigned int dim, double *lambda, double *x, unsigned int nquantity, value *quantity, void *ref, double *fout); /* ---------------------------------- * Quadrature rules define wts/nodes * ---------------------------------- */ typedef struct quadraturerule_s quadraturerule; /** @details A quadrature rule is defined by: - a set of nodes, given in barycentric coordinates (d+1 values per node) - and a set of weights - metadata The integrator is designed to work with rules which provide a higher order extension. */ struct quadraturerule_s { char *name; /** Identifier for the rule */ int grade; /** Dimensionality of element the rule operates on */ int order; /** Order of integrator */ int nnodes; /** Number of nodes */ double *nodes; /** Nodes */ double *weights; /** Weights */ quadraturerule *ext; /** Extension rule that uses same points */ }; /* ---------------------------------- * Subdivision rules * ---------------------------------- */ /** @details A subdivision rule is defined by: - a set of new nodes to be created in the original element, given as barycentric coordinates - a list of vertex ids defining the new element (the original vertices are labelled 0...grade-1 - a list of weights for the new elements (the fraction of the total d-volume of the original element) N.B. weights should sum to 1 (NOT the volume of the element - metadata */ typedef struct subdivisionrule_struct subdivisionrule; struct subdivisionrule_struct { int grade; /** Appropriate grade for the strategy */ int npts; /** Number of new pts created */ double *pts; /** New barycentric coordinates */ int nels; /** Number of new elements created */ int *newels; /** Indices of new elements */ double *weights; /** Weights of new elements */ subdivisionrule *alt; /** Alternative subdivision rule */ } ; /* -------------------------------- * Quadrature work items * -------------------------------- */ typedef struct { double weight; /** Overall element weight */ int elementid; /** Id of element on the element stack */ //value **quantity; double val; /** Value of work item */ double lval; /** Value of work item from lower order estimate */ double err; /** Error estimate of work item */ } quadratureworkitem; DECLARE_VARRAY(quadratureworkitem, quadratureworkitem) /* ---------------------------------- * Quantities * ---------------------------------- */ typedef struct { int nnodes; /** Number of quantity values per element */ value *vals; /** List of quantity values */ interpolationfn ifn; /** Interpolation function */ int ndof; /** Number of degrees of freedom (this will be filled out by the integrator) */ } quantity; /* ---------------------------------- * Integrator * ---------------------------------- */ typedef struct { integrandfunction *integrand; /** Function to integrate */ void *ref; /** Reference to pass to integrand function */ int dim; /** Dimension of points in embedded space */ double **x; /** Vertices defining the element */ int nbary; /** Number of barycentric coordinates */ int nquantity; /** Number of quantities to interpolate */ quantity *quantity; /** Quantity list */ value *qval; /** Interpolated quantity values */ quadraturerule *rule; /** Quadrature rule to use */ quadraturerule *errrule; /** Additional rule for error estimation */ bool adapt; /** Enable adaptive integration */ subdivisionrule *subdivide; /** Subdivision rule to use */ varray_quadratureworkitem worklist; /** Work list */ varray_double vertexstack; /** Stack of vertices */ varray_int elementstack; /** Stack of elements */ double ztol; /** Tolerance for zero detection */ double tol; /** Tolerance for relative error */ int maxiterations; /** Maximum number of subdivisions to perform */ int niterations; /** Number of iterations performed */ double val; /** Estimated value of the integral */ double errest; /** Estimated error of the integral */ error *err; /** Error structure to report errors */ } integrator; /* ------------------------------------------------------- * Integrator errors * ------------------------------------------------------- */ #define INTEGRATE_SBDVSNS "IntgrtrSbdvns" #define INTEGRATE_SBDVSNS_MSG "Too many subdivisions in evaluating integral; possible singularity detected." #define INTEGRATE_RLNTFND "IntgrtrRlNtFnd" #define INTEGRATE_RLNTFND_MSG "Integrator quadrature rule '%s' not found." #define INTEGRATE_RLUNAVLB "IntgrtrRlUnavlb" #define INTEGRATE_RLUNAVLB_MSG "No quadrature rule is available that matches the provided integrator method dictionary." #define INTEGRATE_MTHDTYP "IntgrtrMthdTyp" #define INTEGRATE_MTHDTYP_MSG "Integrator method dictionary option '%s' must be a %s." /* ------------------------------------------------------- * Integrator interface * ------------------------------------------------------- */ bool integrate_integrate(integrandfunction *integrand, unsigned int dim, unsigned int grade, double **x, unsigned int nquantity, value **quantity, void *ref, double *out); bool integrate(integrandfunction *integrand, objectdictionary *method, error *err, unsigned int dim, unsigned int grade, double **x, unsigned int nquantity, quantity *quantity, void *ref, double *out, double *errest); void integrate_initialize(void); #endif #endif /* integration_h */ ================================================ FILE: src/geometry/mesh.c ================================================ /** @file mesh.c * @author T J Atherton * * @brief Mesh class and associated functionality */ #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include "morpho.h" #include "classes.h" #include "mesh.h" #include "file.h" #include "parse.h" #include "sparse.h" #include "matrix.h" #include "selection.h" // Temporary include #include "integrate.h" #include void mesh_link(objectmesh *mesh, object *obj); DEFINE_VARRAY(elementid, elementid); /* ********************************************************************** * Mesh object definitions * ********************************************************************** */ objecttype objectmeshtype; /** Mesh object definitions */ void objectmesh_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectmesh_markfn(object *obj, void *v) { objectmesh *c = (objectmesh *) obj; if (c->vert) morpho_markobject(v, (object *) c->vert); if (c->conn) morpho_searchunmanagedobject(v, (object *) c->conn); } void objectmesh_freefn(object *obj) { objectmesh *m = (objectmesh *) obj; if (m->link) { object *next=NULL; for (object *obj=m->link; obj!=NULL; obj=next) { next=obj->next; object_free(obj); } } if (m->conn) object_free((object *) m->conn); } size_t objectmesh_sizefn(object *obj) { return sizeof(objectmesh); } objecttypedefn objectmeshdefn = { .printfn=objectmesh_printfn, .markfn=objectmesh_markfn, .freefn=objectmesh_freefn, .sizefn=objectmesh_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * Create mesh objects * ********************************************************************** */ objectmesh *object_newmesh(unsigned int dim, unsigned int nv, double *v) { objectmesh *new = MORPHO_MALLOC(sizeof(objectmesh)); if (new) { object_init((object *) new, OBJECT_MESH); new->dim=dim; new->conn=NULL; new->vert=object_newmatrix(dim, nv, false); new->link=NULL; if (new->vert) { mesh_link(new, (object *) new->vert); if (dim>0){ memcpy(new->vert->elements, v, sizeof(double)*dim*nv); } } } return new; } /** Links an object to the mesh; used to keep track of unbound child objects */ void mesh_link(objectmesh *mesh, object *obj) { for (object *e = mesh->link; e!=NULL; e=e->next) if (e==obj) return; if (obj->status==OBJECT_ISUNMANAGED && obj->next==NULL) { obj->next=mesh->link; mesh->link=obj; } } /** Delinks an object from the mesh; used to keep track of unbound child objects */ void mesh_delink(objectmesh *mesh, object *obj) { if (mesh->link==obj) { // If the first, simply delink mesh->link=obj->next; return; } // Otherwise, search and delink once the object is found for (object *e = mesh->link; e!=NULL; e=e->next) { if (e->next==obj) { e->next=obj->next; break; } } } /* ********************************************************************** * Manipulate mesh objects * ********************************************************************** */ /* ------------------------------------- * Vertices * ------------------------------------- */ /** Gets vertex coordinates */ bool mesh_getvertexcoordinates(objectmesh *mesh, elementid id, double *out) { double *coords; if (matrix_getcolumn(mesh->vert, id, &coords)) { for (unsigned int i=0; idim; i++) out[i]=coords[i]; return true; } return false; } /** Gets vertex coordinates as a list */ bool mesh_getvertexcoordinatesaslist(objectmesh *mesh, elementid id, double **out) { double *coords=NULL; if (matrix_getcolumn(mesh->vert, id, &coords)) { *out=coords; } return coords; } /** Gets vertex coordinates */ bool mesh_setvertexcoordinates(objectmesh *mesh, elementid id, double *x) {; return matrix_setcolumn(mesh->vert, id, x); } /** Gets vertex coordinates as a value list */ bool mesh_getvertexcoordinatesasvalues(objectmesh *mesh, elementid id, value *val) { double *x=NULL; // The vertex positions bool success=matrix_getcolumn(mesh->vert, id, &x); if (success) { for (unsigned int i=0; idim; i++) val[i]=MORPHO_FLOAT(x[i]); } return success; } /** Finds the nearest vertex to a point * @param[in] mesh - mesh to search * @param[in] x - position [should be of mesh->dim * @param[out] id - closest vertex * @param[out] separation (optional) * @returns true on success. */ bool mesh_nearestvertex(objectmesh *mesh, double *x, elementid *id, double *separation) { double *vx; double best=0, sep=0; elementid bestid=0; for (elementid i=0; ivert, i, &vx)) return false; sep=0; for (int k=0; kdim; k++) sep+=(vx[k]-x[k])*(vx[k]-x[k]); if (i==0 || sepconn) return true; unsigned int dim[2]={mesh->dim+1, mesh->dim+1}; mesh->conn=object_newarray(2, dim); return (mesh->conn); } /** Freezes mesh connectivity, converting subsidiary data structures to fixed but efficient versions */ void mesh_freezeconnectivity(objectmesh *mesh) { if (!mesh_checkconnectivity(mesh)) return; for (unsigned int i=0; idim+1; i++) { for (unsigned int j=0; jdim+1; j++) { objectsparse *s=mesh_getconnectivityelement(mesh, i, j); if (s) sparse_checkformat(s, SPARSE_CCS, true, false); } } } /** Creates a new blank connectivity element */ objectsparse *mesh_newconnectivityelement(objectmesh *mesh, unsigned int row, unsigned int col) { objectsparse *out=NULL; unsigned int indx[2] = {row, col}; out=object_newsparse(NULL, NULL); if (out) array_setelement(mesh->conn, 2, indx, MORPHO_OBJECT(out)); if (out) mesh_link(mesh, (object *) out); return out; } /** Sets a connectivity element */ bool mesh_setconnectivityelement(objectmesh *mesh, unsigned int row, unsigned int col, objectsparse *el) { if (row==col) return false; unsigned int indx[2]={row,col}; if (mesh_checkconnectivity(mesh)) { value old = MORPHO_NIL; if ((array_getelement(mesh->conn, 2, indx, &old)==ARRAY_OK) && MORPHO_ISOBJECT(old)) { object *oel = MORPHO_GETOBJECT(old); mesh_delink(mesh, oel); if (oel->status==OBJECT_ISUNMANAGED) object_free(oel); } value val = MORPHO_NIL; if (el) val = MORPHO_OBJECT(el); if (array_setelement(mesh->conn, 2, indx, val) == ARRAY_OK) { if (el && el->obj.status==OBJECT_ISUNMANAGED) mesh_link(mesh, (object *) el); } } return false; } /** Gets the connectivity matrix corresponding to (row, col) */ objectsparse *mesh_getconnectivityelement(objectmesh *mesh, unsigned int row, unsigned int col) { objectsparse *out=NULL; unsigned int indx[2]={row,col}; value matrix=MORPHO_NIL; if (mesh->conn) array_getelement(mesh->conn, 2, indx, &matrix); if (MORPHO_ISSPARSE(matrix)) { out=MORPHO_GETSPARSE(matrix); } return out; } /** How many vertices are in a matrix? */ elementid mesh_nvertices(objectmesh *mesh) { elementid out = 0; if (mesh->vert) out = mesh->vert->ncols; return out; } /** How many elements are in a connectivity matrix? */ elementid mesh_nelements(objectsparse *conn) { return conn->ccs.ncols; } /** How many elements exist in a given grade? */ elementid mesh_nelementsforgrade(objectmesh *mesh, grade g) { elementid count=0; if (g==MESH_GRADE_VERTEX) { count=mesh_nvertices(mesh); } else { objectsparse *conn = mesh_getconnectivityelement(mesh, 0, g); if (conn) count=mesh_nelements(conn); } return count; } /** Maximum grade in the mesh */ grade mesh_maxgrade(objectmesh *mesh) { for (grade g=mesh->dim; g>0; g--) { if (mesh_getconnectivityelement(mesh, 0, g)) return g; } return 0; } /** Gets connectivitiy infornation for a given element * @param[in] conn - the connectivity matrix for the element * @param[in] id - the element id * @param[out] nentries - number of entries * @param[out] entries - a list of entries * @returns true on success, false otherwise */ bool mesh_getconnectivity(objectsparse *conn, elementid id, int *nentries, int **entries) { sparse_checkformat(conn, SPARSE_CCS, true, false); if (conn) { return sparseccs_getrowindices(&conn->ccs, id, nentries, entries); } return false; } /* ------------------------------------------ * Functions to modify the connectivity array * ------------------------------------------ */ /* Data structure to store ntuples of elementids and determine if a particular ntuple is already present. */ typedef struct { int n; // number of elements in a tuple elementid maxval; // Maximum elementid that will be used int *elementoffset; // Offset to tuples based on first element of the tuple varray_elementid tuples; // data store } ntuplelist; /* Initialize an ntuplelist data structure */ void ntuplelist_init(ntuplelist *list, int n, elementid maxval) { list->n=n; list->maxval=maxval; list->elementoffset=MORPHO_MALLOC(sizeof(int)*(maxval+1)); if (list->elementoffset) for (unsigned int i=0; i<=maxval; i++) list->elementoffset[i]=-1; varray_elementidinit(&list->tuples); } /* Clear ntuplelist data structure */ void ntuplelist_clear(ntuplelist *list) { if (list->elementoffset) MORPHO_FREE(list->elementoffset); varray_elementidclear(&list->tuples); } /* Add a tuple */ void ntuplelist_add(ntuplelist *list, elementid *tuple) { unsigned int posn = list->tuples.count; if (posn>INT_MAX) UNREACHABLE("Overflow in ntuplelist_add"); varray_elementidadd(&list->tuples, tuple, list->n); if (list->elementoffset) { // Store an offset to the first varray_elementidwrite(&list->tuples, list->elementoffset[tuple[0]]); list->elementoffset[tuple[0]]=posn; } } /* Compare two tuples */ bool ntuplelist_compare(int n, elementid *t1, elementid *t2) { for (unsigned int i=0; ielementoffset) { for (int i=list->elementoffset[tuple[0]]; i>=0; ) { if (ntuplelist_compare(list->n, &list->tuples.data[i], tuple)) return true; i=list->tuples.data[i+list->n]; } } else { for (unsigned int i=0; ituples.count; i+=list->n) { if (ntuplelist_compare(list->n, &list->tuples.data[i], tuple)) return true; } } return false; } objectsparse *mesh_addgrade(objectmesh *mesh, grade g) { /* Does the grade already exist? */ objectsparse *el=mesh_getconnectivityelement(mesh, 0, g); if (el) return el; grade maxG = mesh_maxgrade(mesh); grade h; /* Otherwise, find the next available grade above it */ for (h=g+1; (h<=maxG) && (!el); h++) { el=mesh_getconnectivityelement(mesh, 0, h); } /* if this grade doesn't exist and we can't find the next available grade above it return NULL */ if (!el) return NULL; /* Create a new sparse matrix */ objectsparse *new=object_newsparse(NULL, NULL); if (!new) return NULL; /* Create an ntuplelist to keep track of elements already created */ ntuplelist list; int n = g+1; // Number of elements in the tuple elementid maxvid = mesh_nvertices(mesh)-1; // Highest vertexid ntuplelist_init(&list, n, maxvid); int nel, *entries; elementid newid = 0; /* Loop over elements in the higher grade */ for (elementid id=0; idccs.ncols; id++) { /* Get the associated connectivity */ if (!mesh_getconnectivity(el, id, &nel, &entries)) break; // Initialize n-tuple and counters with [0,1,2...] elementid tuple[n]; // Store the tuple int counter[n], cmax[n]; // Counters for (unsigned int i=0; idok, tuple[i], newid, MORPHO_NIL); newid++; } /* Generate tuples */ int k; while (counter[0]=0 && counter[k]>cmax[k]; k--) counter[k-1]++; // Carry if (kdok, tuple[i], newid, MORPHO_NIL); newid++; } } } ntuplelist_clear(&list); mesh_setconnectivityelement(mesh, 0, g, new); mesh_link(mesh, (object *) new); mesh_freezeconnectivity(mesh); return new; } void mesh_removegrade(objectmesh *mesh, grade g) { /* Does the grade already exist? */ objectsparse *el=mesh_getconnectivityelement(mesh, 0, g); grade maxg = mesh_maxgrade(mesh); if (el && g<=maxg) { mesh_setconnectivityelement(mesh, 0, g, NULL); mesh_resetconnectivity(mesh); } } /** Adds a missing grade */ objectsparse *mesh_addgradeold(objectmesh *mesh, grade g) { if (g>1) UNREACHABLE("mesh_addgrade only supports adding grade 1."); /* Does the grade already exist? */ objectsparse *el=mesh_getconnectivityelement(mesh, 0, g); if (el) return el; grade maxG = mesh_maxgrade(mesh); grade h; /* Otherwise, find the next available grade above it */ for (h=g+1; (h<=maxG) && (!el); h++) { el=mesh_getconnectivityelement(mesh, 0, h); } /* if this grade doesn't exist and we can't find the next available grade above it return NULL */ if (!el){ return NULL; } /* Create a new sparse matrix */ objectsparse *new=object_newsparse(NULL, NULL); if (!new) return NULL; /* Create a temporary sparse matrix to hold connectivity information */ objectsparse *temp=object_newsparse(NULL, NULL); if (!temp) { object_free((object *) new); return NULL; } int nentries, *entries; elementid newid=0; /* Loop over elements in the higher grade */ if (temp) for (elementid id=0; idccs.ncols; id++) { /* Get the associated connectivity */ if (mesh_getconnectivity(el, id, &nentries, &entries)) { /* Now loop over pairs of vertices in the element */ /* Should be n-tuples */ for (unsigned int j=0; jdok, l, newid, MORPHO_NIL); sparsedok_insert(&new->dok, m, newid, MORPHO_NIL); /* Keep track of elements made */ sparsedok_insert(&temp->dok, l, m, MORPHO_NIL); newid++; } } } } } if (temp) object_free((object *) temp); mesh_setconnectivityelement(mesh, 0, g, new); mesh_link(mesh, (object *) new); mesh_freezeconnectivity(mesh); return new; } /** Internal function used for sorting ids */ static int mesh_compareid(const void *a, const void *b) { return *((int *) a) - *((int *) b); } /** Find elements that match grade g in a connectivity matrix . * @param[in] vmatrix - the (g, 0) connectivity matrix (i.e. the vertex raising matrix for grade g) * @param[in] g - the grade of interest * @param[in] nids - number of vertex ids to match * @param[in] ids - list of vertex ids to match * @param[in] maxmatches - maximum number of matches to find * @param[out] nmatches - the number of matches found * @param[out] matches - matched vertex ids * @returns true on success, false otherwise */ bool mesh_matchelements(objectsparse *vmatrix, grade g, int nids, int *ids, int maxmatches, int *nmatches, int *matches) { int nentries[nids], *entries[nids], length=0, k=0; /* Obtain connectivity information from the columns of vertex connectivity matrix */ for (unsigned int i=0; iccs.ncols; rid++) { /* Get the associated connectivity */ if (mesh_getconnectivity(tlower, rid, &nentries, &entries)) { if (mesh_matchelements(traise, row, nentries, entries, maxmatches, &nmatches, matches)) { if (nmatches>=maxmatches) { UNREACHABLE("Too many connections."); } for (unsigned int i=0; idok, matches[i], rid, MORPHO_NIL); } } } } mesh_setconnectivityelement(mesh, row, col, new); mesh_freezeconnectivity(mesh); } return new; } /** Fill in a missing connectivity element */ objectsparse *mesh_addconnectivityelement(objectmesh *mesh, unsigned int row, unsigned int col) { /** Does the element already exist? */ objectsparse *el=mesh_getconnectivityelement(mesh, row, col); if (el) return el; /* If not, what kind of element is it? */ if (row==0) { /* First row */ /* Can't add a missing grade; use addgrade instead*/ } else if (rowdok.ncols; // The new element is one after the last element bool success=true; for (unsigned int i=0; idok, (int) v[i], eid, MORPHO_NIL)) success=false; } return success; } /** Resets connectivity elements other than the first row */ void mesh_resetconnectivity(objectmesh *m) { grade max = mesh_maxgrade(m); for (grade i=1; i<=max; i++) { for (grade j=0; j<=max; j++) { mesh_setconnectivityelement(m, i, j, NULL); } } } /* ********************************************************************** * Clone * ********************************************************************** */ /** Clones a mesh object */ objectmesh *mesh_clone(objectmesh *mesh) { objectmesh *new = object_newmesh(mesh->dim, mesh->vert->ncols, mesh->vert->elements); if (new) { if (mesh->conn && mesh_checkconnectivity(new)) { grade max = mesh_maxgrade(mesh); for (grade i=0; i<=max; i++) { for (grade j=0; j<=max; j++) { objectsparse *conn=mesh_getconnectivityelement(mesh, i, j); if (conn) { objectsparse *cl=sparse_clone(conn); if (cl) mesh_setconnectivityelement(new, i, j, cl); } } } } } return new; } /* ********************************************************************** * Symmetries * ********************************************************************** */ /** Adds a symmetry to a mesh. */ bool mesh_addsymmetry(vm *v, objectmesh *mesh, value symmetry, objectselection *sel) { value method=MORPHO_NIL; objectstring s = MORPHO_STATICSTRING(MESH_TRANSFORM_METHOD); objectsparse *sym=mesh_getconnectivityelement(mesh, MESH_GRADE_VERTEX, MESH_GRADE_VERTEX); double x[mesh->dim]; objectmatrix posn = MORPHO_STATICMATRIX(x, mesh->dim, 1); elementid nv = mesh_nvertices(mesh); value arg = MORPHO_OBJECT(&posn); value ret = MORPHO_NIL; if (morpho_lookupmethod(symmetry, MORPHO_OBJECT(&s), &method)) { /* Loop over vertices */ for (elementid i=0; ielements, &nearest, &sep)) return false; if (sepdok, nv, nv); } if (!sym) return false; sparse_setelement(sym, i, nearest, symmetry); } } } } else morpho_runtimeerror(v, MESH_ADDSYMMSNGTRNSFRM); return false; } /* Get a list of synonymous elements for a given element */ bool mesh_getsynonyms(objectmesh *mesh, grade g, elementid id, varray_elementid *synonymids) { objectsparse *sym = mesh_getconnectivityelement(mesh, g, g); if (sym) { synonymids->count=0; void *ctr=sparsedok_loopstart(&sym->dok); int row, col; while (sparsedok_loop(&sym->dok, &ctr, &row, &col)) { if (id==row) varray_elementidwriteunique(synonymids, col); if (id==col) varray_elementidwriteunique(synonymids, row); } } return true; } void varray_elementidwriteunique(varray_elementid *list, elementid id) { for (unsigned int i=0; icount; i++) if (list->data[i]==id) return; varray_elementidwrite(list, id); } /** Insert ids for a given element * @param[in] conn - Connectivity matrix * @param[in] id - id to insert * @param[in] ignoreid - whether or not to include id if it is found * @param[out] out - varray to hold output */ void mesh_insertidsforelement(objectsparse *conn, elementid id, bool ignore, elementid ignoreid, varray_elementid *out) { int nids, *entries; if (sparseccs_getrowindices(&conn->ccs, id, &nids, &entries)) { for (unsigned int i=0; i0) { objectsparse *down = mesh_getconnectivityelement(mesh, 0, g); sparseccs_getrowindices(&down->ccs, id, &nvert, &vids); } else { nvert = 1; vids=&vvid; } objectsparse *conn = mesh_getconnectivityelement(mesh, target, 0); // Now find the neighboring elements if (conn && sparse_checkformat(conn, SPARSE_CCS, true, false)) { for (unsigned int k=0; kccs, vids[k], &nsymids, &symids)) { for (unsigned int k=0; kccs, id, MAX_NEIGHBORS, &nrids, rids)) { for (unsigned int k=0; k=MAX_NEIGHBORS) UNREACHABLE("Too many neighbors."); } } return (neighbors->count); } /* ********************************************************************** * Mesh loader * ********************************************************************** */ static unsigned int mesh_nsections = 4; static char *mesh_sections[] = {MESH_VERTSECTION, MESH_EDGESECTION, MESH_FACESECTION, MESH_VOLSECTION}; static size_t mesh_slength[4]; /** Checks whether line matches a section marker. * @param[in] line line to match * @param[out] g if line matches, grade is updated * @returns true if line matched a section marker; false otherwise */ static bool mesh_checksection(char *line, grade *g) { /* Check if the line starts with a section marker */ unsigned int i; for (i=0; i0 && g==0) { /* Check dimensionality */ if (ndim<0) ndim=n-1; else if (n-1!=ndim) { morpho_runtimeerror(v, MESH_LOADVERTEXDIM, fline); goto meshload_cleanup; } /* Add the vertex */ for (unsigned int k=0; k0 && !mesh_checksection(line.data, &g)) { /* Convert the string to an array of values */ if (parse_stringtovaluearray(line.data, 5, val, &n, &err)) { if (n>0) { elementid vid[g+1]; /* Check number of vertices is consistent with the grade */ if (n-1!=g+1) { morpho_runtimeerror(v, MESH_LOADVERTEXNUM, fline); goto meshload_cleanup; } for (unsigned int i=0; ivert->nrows; j++) { double x; if (matrix_getelement(m->vert, j, i, &x)) { fprintf(f, "%g ", x); } } fprintf(f, "\n"); } fprintf(f, "\n"); for (grade g=1; g<=m->dim; g++) { objectsparse *conn=mesh_getconnectivityelement(m, 0, g); if (conn) { fprintf(f, "%s\n\n", mesh_sections[g]); int nentries=0, *entries=NULL; int nel = mesh_nelements(conn); for (elementid id=0; idvert) morpho_printf(v, " %u vertices", mesh_nvertices(m)); morpho_printf(v, ">"); return MORPHO_NIL; } /** Get the vertex matrix */ value Mesh_vertexmatrix(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value out=MORPHO_NIL; if (m->vert) out=MORPHO_OBJECT(m->vert); mesh_delink(m, (object *) m->vert); morpho_bindobjects(v, 1, &out); return out; } /** Set the vertex matrix */ value Mesh_setvertexmatrix(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *mat = MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (m->dim>0 && (mesh_nvertices(m)!=mat->ncols || m->vert->nrows!=mat->nrows)) { morpho_runtimeerror(v, MESH_VERTMTRXDIM); } else { if (m->dim==0) m->dim=mat->nrows; m->vert=mat; } } return MORPHO_NIL; } /** Get position of a vertex */ value Mesh_vertexposition(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { unsigned int id=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); double *vals; if (matrix_getcolumn(m->vert, id, &vals)) { objectmatrix *new=object_newmatrix(m->dim, 1, true); if (new) { matrix_setcolumn(new, 0, vals); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, MESH_INVLDID); } else morpho_runtimeerror(v, MESH_VRTPSNARGS); return out; } /** Set position of a vertex */ value Mesh_setvertexposition(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISMATRIX(MORPHO_GETARG(args, 1))) { unsigned int id=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); objectmatrix *mat = MORPHO_GETMATRIX(MORPHO_GETARG(args, 1)); if (!matrix_setcolumn(m->vert, id, mat->elements)) morpho_runtimeerror(v, MESH_INVLDID); } else morpho_runtimeerror(v, MESH_STVRTPSNARGS); return MORPHO_NIL; } /** Gets a connectivity matrix */ value Mesh_connectivitymatrix(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { unsigned int row=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); unsigned int col=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)); objectsparse *s=mesh_getconnectivityelement(m, row, col); if (!s && row>0 && row!=col) s=mesh_addconnectivityelement(m, row, col); if (s) { mesh_delink(m, (object *) s); out=MORPHO_OBJECT(s); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, MESH_CNNMTXARGS); return out; } /** Clears any connectivity matrices */ value Mesh_resetconnectivity(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); mesh_resetconnectivity(m); return MORPHO_NIL; } /** Adds a grade to a mesh */ value Mesh_addgrade(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { unsigned int g=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (g==0) return MORPHO_NIL; objectsparse *s=mesh_getconnectivityelement(m, 0, g); if (!s) { s=mesh_addgrade(m, g); if(!s){ morpho_runtimeerror(v, MESH_ADDGRDOOB,g,mesh_maxgrade(m)); return MORPHO_NIL; } } } else if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISSPARSE(MORPHO_GETARG(args, 1))) { unsigned int grade=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); objectsparse *s=MORPHO_GETSPARSE(MORPHO_GETARG(args, 1)); if (s && grade>0) mesh_setconnectivityelement(m, 0, grade, s); mesh_freezeconnectivity(m); } else morpho_runtimeerror(v, MESH_ADDGRDARGS); return out; } /** Removes a grade from a mesh */ value Mesh_removegrade(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { unsigned int g=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (g==0) return MORPHO_NIL; mesh_removegrade(m, g); } else morpho_runtimeerror(v, MESH_ADDGRDARGS); return out; } /** Adds a symmetry to a mesh */ value Mesh_addsymmetry(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); value obj = MORPHO_NIL; objectselection *sel = NULL; if (nargs>0 && MORPHO_ISOBJECT(MORPHO_GETARG(args, 0))) { obj=MORPHO_GETARG(args, 0); } else morpho_runtimeerror(v, MESH_ADDSYMARGS); if (nargs>1) { if (MORPHO_ISSELECTION(MORPHO_GETARG(args, 1))) { sel = MORPHO_GETSELECTION(MORPHO_GETARG(args, 1)); } else morpho_runtimeerror(v, MESH_ADDSYMARGS); } if (!MORPHO_ISNIL(obj)) { mesh_addsymmetry(v, m, obj, sel); } return MORPHO_NIL; } /* Returns the highest grade present */ value Mesh_maxgrade(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); return MORPHO_INTEGER(mesh_maxgrade(m)); } /** Counts the number of elements for a given grade, or returns the number of vertices if no argument is supplied. */ value Mesh_count(vm *v, int nargs, value *args) { objectmesh *m=MORPHO_GETMESH(MORPHO_SELF(args)); grade g=0; value out=MORPHO_INTEGER(0); if (nargs>0 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { g = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); } if (g==0) { out = MORPHO_INTEGER(m->vert->ncols); } else { objectsparse *s = mesh_getconnectivityelement(m, 0, g); if (s) out = MORPHO_INTEGER(s->ccs.ncols); } return out; } /** Clones a mesh */ value Mesh_clone(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectmesh *a=MORPHO_GETMESH(MORPHO_SELF(args)); objectmesh *new=mesh_clone(a); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } MORPHO_BEGINCLASS(Mesh) MORPHO_METHOD(MORPHO_PRINT_METHOD, Mesh_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SAVE_METHOD, Mesh_save, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_VERTEXMATRIX_METHOD, Mesh_vertexmatrix, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_SETVERTEXMATRIX_METHOD, Mesh_setvertexmatrix, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_VERTEXPOSITION_METHOD, Mesh_vertexposition, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_SETVERTEXPOSITION_METHOD, Mesh_setvertexposition, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_RESETCONNECTIVITY_METHOD, Mesh_resetconnectivity, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_CONNECTIVITYMATRIX_METHOD, Mesh_connectivitymatrix, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_ADDGRADE_METHOD, Mesh_addgrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_REMOVEGRADE_METHOD, Mesh_removegrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_ADDSYMMETRY_METHOD, Mesh_addsymmetry, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MESH_MAXGRADE_METHOD, Mesh_maxgrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Mesh_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Mesh_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ void mesh_initialize(void) { // integrate_test(); objectmeshtype=object_addtype(&objectmeshdefn); for (unsigned int i=0; i"); } void objectselection_freefn(object *obj) { objectselection *s = (objectselection *) obj; selection_clear(s); } size_t objectselection_sizefn(object *obj) { return sizeof(objectselection)+sizeof(objectsparse *)*((objectselection *) obj)->ngrades; } objecttypedefn objectselectiondefn = { .printfn=objectselection_printfn, .markfn=NULL, .freefn=objectselection_freefn, .sizefn=objectselection_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* ********************************************************************** * Selection object constructor * ********************************************************************** */ /** Create a new empty selection object */ objectselection *object_newselection(objectmesh *mesh) { unsigned int ngrades = mesh->dim+1; objectselection *new=(objectselection *) object_new(sizeof(objectselection)+sizeof(dictionary)*ngrades, OBJECT_SELECTION); if (new) { new->mesh=mesh; new->ngrades=ngrades; new->mode=SELECT_NONE; for (unsigned int i=0; iselected[i]); } return new; } /** Clones a selection */ objectselection *selection_clone(objectselection *sel) { objectselection *new=object_newselection(sel->mesh); if (new) { new->mode=sel->mode; for (unsigned int i=0; ingrades; i++) dictionary_copy(&sel->selected[i], &new->selected[i]); } return new; } /** Clears all data structures associated with a selection */ void selection_clear(objectselection *s) { for (grade i=0; ingrades; i++) { dictionary_clear(&s->selected[i]); } } /** Removes a grade from a selection */ void selection_removegrade(objectselection *sel, grade g) { dictionary_clear(&sel->selected[g]); } /** Selects an element */ bool selection_selectelement(objectselection *sel, grade g, elementid id) { if (sel->mode==SELECT_ALL) return true; /* No need to store info in the selectall scenario */ sel->mode=SELECT_SOME; // Ensure that we change modes return dictionary_insert(&sel->selected[g], MORPHO_INTEGER(id), MORPHO_NIL); } /** Attempts to change the grade of a selection by raising * @param[in] sel - selection to change * @param[in] g - grade to add * @param[in] includepartials - whether to include partially selected elements or not */ bool selection_addgraderaise(objectselection *sel, grade g, bool includepartials) { // Get the corresponding grade from the mesh objectsparse *conn=mesh_getconnectivityelement(sel->mesh, 0, g); if (!conn) return false; int nentries, *entries=NULL; for (elementid id=0; idccs.ncols; id++) { if (mesh_getconnectivity(conn, id, &nentries, &entries)) { int k=0; for (int j=0; jselected[0], MORPHO_INTEGER(entries[j]), NULL)) k++; } if ((includepartials && k>0) || (k==nentries)) { dictionary_insert(&sel->selected[g], MORPHO_INTEGER(id), MORPHO_NIL); } } } return true; } /** Attempts to change the grade of a selection by lowering * @param[in] sel - selection to change * @param[in] g - grade to add */ bool selection_addgradelower(objectselection *sel, grade g) { //dictionary *dest = &sel->selected[g]; for (grade i=sel->ngrades; i>g; i--) { // Loop over grades higher than g objectsparse *conn = mesh_addconnectivityelement(sel->mesh, g, i); if (!conn) continue; // Look through the selected elements in grade i for (unsigned int k=0; kselected[i].capacity; k++) { value el = sel->selected[i].contents[k].key; if (MORPHO_ISINTEGER(el)) { int nentries, *entries; elementid id = MORPHO_GETINTEGERVALUE(el); // Get the element ids if (mesh_getconnectivity(conn, id, &nentries, &entries)) { for (int j=0; jselected[g], MORPHO_INTEGER(entries[j]), MORPHO_NIL); } } } } } return true; } /** Selects an element * @param v - the virtual machine to use * @param sel - selection object * @param fn - function to call */ void selection_selectwithfunction(vm *v, objectselection *sel, value fn) { if (!sel->mesh || !sel->mesh->vert) UNREACHABLE("Selection on mesh with invalid vertex structure."); objectmatrix *vert=sel->mesh->vert; int nv = vert->ncols; value ret=MORPHO_NIL; // Return value value coords[sel->mesh->dim+1]; // Vertex coords for (elementid i=0; imesh, i, coords)) { if (!morpho_call(v, fn, sel->mesh->dim, coords, &ret)) break; if (MORPHO_ISTRUE(ret)) selection_selectelement(sel, MESH_GRADE_VERTEX, i); } } } /** Selects elements by mapping a function over a matrix. * @param v - the virtual machine to use * @param sel - selection object * @param fn - function to call * @param matrix - matrix to map over */ void selection_selectwithmatrix(vm *v, objectselection *sel, value fn, objectmatrix *matrix) { if (!sel->mesh || !sel->mesh->vert) UNREACHABLE("Selection on mesh with invalid vertex structure."); objectmatrix *vert=sel->mesh->vert; int nv = vert->ncols; if (matrix->ncols!=nv) { morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); return; } int nargs = matrix->nrows; // Number of args to pass to function value args[nargs]; // Vertex coords double *x; // Matrix column value ret=MORPHO_NIL; // Return value for (elementid i=0; imesh); if (max<1) { morpho_runtimeerror(v, SELECTION_BND); return; } grade bnd = max-1; objectsparse *conn=mesh_addconnectivityelement(sel->mesh, max, bnd); if (conn) { int nentries, *entries; for (elementid i=0; imode==SELECT_NONE || sel->mode==SELECT_SOME)) { if (gngrades) { dictionary_insert(&sel->selected[g], MORPHO_INTEGER(id), MORPHO_NIL); } sel->mode=SELECT_SOME; } else if (!selected && (sel->mode==SELECT_SOME)) { if (gngrades) { dictionary_remove(&sel->selected[g], MORPHO_INTEGER(id)); } sel->mode=SELECT_SOME; } if (sel->mode==SELECT_ALL) { UNREACHABLE("Unimplemented modification to SELECTALL not implemented."); } } /** Tests if an element is selected */ bool selection_isselected(objectselection *sel, grade g, elementid id) { switch (sel->mode) { case SELECT_NONE: return false; case SELECT_ALL: return true; case SELECT_SOME: { return dictionary_get(&sel->selected[g], MORPHO_INTEGER(id), NULL); } } } /** Finds the maximum nonempty grade in a selection */ grade selection_maxgrade(objectselection *sel) { switch (sel->mode) { case SELECT_NONE: return 0; case SELECT_ALL: return mesh_maxgrade(sel->mesh); case SELECT_SOME: for (grade g=sel->ngrades-1; g>0; g--) { if (sel->selected[g].count>0) return g; } } return 0; } /** Gets the element ids for a given grade as a list */ objectlist *selection_idlistforgrade(objectselection *sel, grade g) { objectlist *new = object_newlist(0, NULL); dictionary *dict = &sel->selected[g]; if (new) switch(sel->mode) { case SELECT_NONE: break; case SELECT_ALL: { UNREACHABLE("ID list for select all not implemented."); } break; case SELECT_SOME: { list_resize(new, dict->count); for (unsigned int i=0; icapacity; i++) { if (MORPHO_ISINTEGER(dict->contents[i].key)) { list_append(new, dict->contents[i].key); } } } break; } return new; } /* ********************************************************************** * Selection set operations * ********************************************************************** */ /* Computes the union of selections a & b */ objectselection *selection_union(objectselection *a, objectselection *b) { objectselection *new = object_newselection(a->mesh); if (a->mode==SELECT_ALL || b->mode==SELECT_ALL) { // No need to copy a select all element new->mode=SELECT_ALL; } else { for (grade g=0; gngrades && gngrades; g++) { dictionary_union(&a->selected[g], &b->selected[g], &new->selected[g]); if (new->selected[g].count>0) new->mode=SELECT_SOME; } } return new; } /* Computes the union of selections a & b */ objectselection *selection_intersection(objectselection *a, objectselection *b) { objectselection *new = object_newselection(a->mesh); if (a->mode==SELECT_ALL && b->mode==SELECT_ALL) { // No need to copy a select all element new->mode=SELECT_ALL; } else if (a->mode!=SELECT_NONE && b->mode!=SELECT_NONE) { for (grade g=0; gngrades && gngrades; g++) { dictionary_intersection(&a->selected[g], &b->selected[g], &new->selected[g]); if (new->selected[g].count>0) new->mode=SELECT_SOME; } } return new; } /* Computes the union of selections a & b */ objectselection *selection_difference(objectselection *a, objectselection *b) { objectselection *new = object_newselection(a->mesh); if (a->mode==SELECT_ALL) { if (b->mode==SELECT_NONE) { new->mode=SELECT_ALL; } else UNREACHABLE("Selectall difference not implemented."); } else if (a->mode!=SELECT_NONE) { for (grade g=0; gngrades && gngrades; g++) { dictionary_difference(&a->selected[g], &b->selected[g], &new->selected[g]); if (new->selected[g].count>0) new->mode=SELECT_SOME; } } return new; } /* ********************************************************************** * Selection veneer class * ********************************************************************** */ static value selection_boundaryoption; static value selection_partialsoption; /** Constructs a Selection object */ value selection_constructor(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectmesh *mesh=NULL; objectselection *new=NULL; value fn=MORPHO_NIL; value fnargs=MORPHO_NIL; value boundary=MORPHO_FALSE; int nfixed=nargs; builtin_options(v, nargs, args, &nfixed, 1, selection_boundaryoption, &boundary); /* Get mesh as first argument */ if (nfixed>0) { if (MORPHO_ISMESH(MORPHO_GETARG(args, 0))) mesh=MORPHO_GETMESH(MORPHO_GETARG(args, 0)); } /* Selection function as optional second argument */ if (nfixed>1) fn = MORPHO_GETARG(args, 1); /* Selection function arguments as optional second argument */ if (nfixed>2) fnargs = MORPHO_GETARG(args, 2); if (mesh) { new=object_newselection(mesh); } else { morpho_runtimeerror(v, SELECTION_NOMESH); return out; } if (new) { if (!MORPHO_ISNIL(fn)) { if (MORPHO_ISNIL(fnargs)) { selection_selectwithfunction(v, new, fn); } else if (MORPHO_ISMATRIX(fnargs)) { selection_selectwithmatrix(v, new, fn, MORPHO_GETMATRIX(fnargs)); } } else if (MORPHO_ISTRUE(boundary)) { selection_selectboundary(v, new); } out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Select an element by id */ value Selection_setindex(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); if (nargs==3 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { selection_selectwithid(sel, MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)), MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)), MORPHO_ISTRUE(MORPHO_GETARG(args, 2))); } else morpho_runtimeerror(v, SELECTION_ISSLCTDARG); return MORPHO_NIL; } /** Tests if something is selected */ value Selection_isselected(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); value out = MORPHO_FALSE; if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { out = MORPHO_BOOL(selection_isselected(sel, MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)), MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)))); } else morpho_runtimeerror(v, SELECTION_ISSLCTDARG); return out; } /** Get the id list for a given grade */ value Selection_idlistforgrade(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { objectlist *lst=selection_idlistforgrade(sel, MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0))); if (lst) { out = MORPHO_OBJECT(lst); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else morpho_runtimeerror(v, SELECTION_GRADEARG); return out; } /** Adds a grade to a selection */ value Selection_addgrade(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); value partials = MORPHO_FALSE; int nfixed; builtin_options(v, nargs, args, &nfixed, 1, selection_partialsoption, &partials); if (nargs>0 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { grade g = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); grade max = selection_maxgrade(sel); if (g>max) { selection_addgraderaise(sel, g, MORPHO_ISTRUE(partials)); } else { selection_addgradelower(sel, g); } } else morpho_runtimeerror(v, SELECTION_GRADEARG); return MORPHO_NIL; } /** Removes a grade from a selection */ value Selection_removegrade(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { selection_removegrade(sel, MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0))); } else morpho_runtimeerror(v, SELECTION_GRADEARG); return MORPHO_NIL; } /** Print the selection */ value Selection_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISSELECTION(self)) return Object_print(v, nargs, args); morpho_printf(v, ""); return MORPHO_NIL; } /** Counts number of elements selected in each grade */ value Selection_count(vm *v, int nargs, value *args) { objectselection *sel = MORPHO_GETSELECTION(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { grade g = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); out = MORPHO_INTEGER(sel->selected[g].count); } else morpho_runtimeerror(v, SELECTION_GRADEARG); return out; } /** Clones a selection */ value Selection_clone(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectselection *a=MORPHO_GETSELECTION(MORPHO_SELF(args)); objectselection *new=selection_clone(a); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } #define SELECTION_SETOP(op) \ value Selection_##op(vm *v, int nargs, value *args) { \ objectselection *slf = MORPHO_GETSELECTION(MORPHO_SELF(args)); \ value out=MORPHO_NIL; \ \ if (nargs>0 && MORPHO_ISSELECTION(MORPHO_GETARG(args, 0))) { \ objectselection *new = selection_##op(slf, MORPHO_GETSELECTION(MORPHO_GETARG(args, 0))); \ \ if (new) { \ out=MORPHO_OBJECT(new); \ morpho_bindobjects(v, 1, &out); \ } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); \ } else morpho_runtimeerror(v, SELECTION_STARG); \ \ return out; \ } SELECTION_SETOP(union) SELECTION_SETOP(intersection) SELECTION_SETOP(difference) MORPHO_BEGINCLASS(Selection) MORPHO_METHOD(SELECTION_ISSELECTEDMETHOD, Selection_isselected, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Selection_isselected, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Selection_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SELECTION_IDLISTFORGRADEMETHOD, Selection_idlistforgrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Selection_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Selection_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_UNION_METHOD, Selection_union, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_INTERSECTION_METHOD, Selection_intersection, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIFFERENCE_METHOD, Selection_difference, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Selection_union, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Selection_difference, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SELECTION_ADDGRADEMETHOD, Selection_addgrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SELECTION_REMOVEGRADEMETHOD, Selection_removegrade, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Selection_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************** */ void selection_initialize(void) { objectselectiontype=object_addtype(&objectselectiondefn); selection_boundaryoption=builtin_internsymbolascstring(SELECTION_BOUNDARYOPTION); selection_partialsoption=builtin_internsymbolascstring(SELECTION_PARTIALSOPTION); builtin_addfunction(SELECTION_CLASSNAME, selection_constructor, BUILTIN_FLAGSEMPTY); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value selectionclass=builtin_addclass(SELECTION_CLASSNAME, MORPHO_GETCLASSDEFINITION(Selection), objclass); object_setveneerclass(OBJECT_SELECTION, selectionclass); morpho_defineerror(SELECTION_NOMESH, ERROR_HALT, SELECTION_NOMESH_MSG); morpho_defineerror(SELECTION_ISSLCTDARG, ERROR_HALT, SELECTION_ISSLCTDARG_MSG); morpho_defineerror(SELECTION_GRADEARG, ERROR_HALT, SELECTION_GRADEARG_MSG); morpho_defineerror(SELECTION_STARG, ERROR_HALT, SELECTION_STARG_MSG); morpho_defineerror(SELECTION_BND, ERROR_HALT, SELECTION_BND_MSG); } #endif ================================================ FILE: src/geometry/selection.h ================================================ /** @file selection.c * @author T J Atherton * * @brief Selections */ #ifndef selection_h #define selection_h #include "build.h" #ifdef MORPHO_INCLUDE_GEOMETRY #include "mesh.h" /* ------------------------------------------------------- * Selection objects * ------------------------------------------------------- */ extern objecttype objectselectiontype; #define OBJECT_SELECTION objectselectiontype typedef struct { object obj; objectmesh *mesh; /** The mesh the selection is referring to */ enum { SELECT_ALL, SELECT_NONE, SELECT_SOME } mode; /** What is selected? */ unsigned int ngrades; /** Number of grades */ dictionary selected[]; /** Selections */ } objectselection; /** Tests whether an object is a selection */ #define MORPHO_ISSELECTION(val) object_istype(val, OBJECT_SELECTION) /** Gets the object as a selection */ #define MORPHO_GETSELECTION(val) ((objectselection *) MORPHO_GETOBJECT(val)) /** Creates an empty selection object */ objectselection *object_newselection(objectmesh *mesh); /* ------------------------------------------------------- * Selection class * ------------------------------------------------------- */ #define SELECTION_CLASSNAME "Selection" #define SELECTION_ISSELECTEDMETHOD "isselected" #define SELECTION_IDLISTFORGRADEMETHOD "idlistforgrade" #define SELECTION_ADDGRADEMETHOD "addgrade" #define SELECTION_REMOVEGRADEMETHOD "removegrade" #define SELECTION_COMPLEMENTMETHOD "complement" #define SELECTION_BOUNDARYOPTION "boundary" #define SELECTION_PARTIALSOPTION "partials" #define SELECTION_NOMESH "SlNoMsh" #define SELECTION_NOMESH_MSG "Selection requires a Mesh object as an argument." #define SELECTION_ISSLCTDARG "SlIsSlArg" #define SELECTION_ISSLCTDARG_MSG "Selection.isselected requires a grade and element id as arguments." #define SELECTION_GRADEARG "SlGrdArg" #define SELECTION_GRADEARG_MSG "Method requires a grade as the argument." #define SELECTION_STARG "SlStArg" #define SELECTION_STARG_MSG "Selection set methods require a selection as the argument." #define SELECTION_BND "SlBnd" #define SELECTION_BND_MSG "Mesh has no boundary elements." void selection_clear(objectselection *s); bool selection_isselected(objectselection *sel, grade g, elementid id); void selection_initialize(void); #endif #endif /* selection_h */ ================================================ FILE: src/linalg/CMakeLists.txt ================================================ target_sources(morpho PRIVATE matrix.c matrix.h sparse.c sparse.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES matrix.h sparse.h ) ================================================ FILE: src/linalg/matrix.c ================================================ /** @file matrix.c * @author T J Atherton * * @brief Veneer class over the objectmatrix type that interfaces with blas and lapack */ #include "build.h" #ifdef MORPHO_INCLUDE_LINALG #include #include "morpho.h" #include "classes.h" #include "matrix.h" #include "sparse.h" #include "format.h" /* ********************************************************************** * Matrix objects * ********************************************************************** */ objecttype objectmatrixtype; /** Function object definitions */ size_t objectmatrix_sizefn(object *obj) { return sizeof(objectmatrix)+sizeof(double) * ((objectmatrix *) obj)->ncols * ((objectmatrix *) obj)->nrows; } void objectmatrix_printfn(object *obj, void *v) { morpho_printf(v, ""); } objecttypedefn objectmatrixdefn = { .printfn=objectmatrix_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectmatrix_sizefn, .hashfn=NULL, .cmpfn=NULL }; /** Creates a matrix object */ objectmatrix *object_newmatrix(unsigned int nrows, unsigned int ncols, bool zero) { unsigned int nel = nrows*ncols; objectmatrix *new = (objectmatrix *) object_new(sizeof(objectmatrix)+nel*sizeof(double), OBJECT_MATRIX); if (new) { new->ncols=ncols; new->nrows=nrows; new->elements=new->matrixdata; if (zero) { memset(new->elements, 0, sizeof(double)*nel); } } return new; } /* ********************************************************************** * Other constructors * ********************************************************************** */ /* * Create matrices from array objects */ void matrix_raiseerror(vm *v, objectmatrixerror err) { switch(err) { case MATRIX_OK: break; case MATRIX_INCMPTBLDIM: morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); break; case MATRIX_SING: morpho_runtimeerror(v, MATRIX_SINGULAR); break; case MATRIX_INVLD: morpho_runtimeerror(v, MATRIX_INVLDARRAYINIT); break; case MATRIX_BNDS: morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); break; case MATRIX_NSQ: morpho_runtimeerror(v, MATRIX_NOTSQ); break; case MATRIX_FAILED: morpho_runtimeerror(v, MATRIX_OPFAILED); break; case MATRIX_ALLOC: morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); break; } } /** Recurses into an objectarray to find the dimensions of the array and all child arrays * @param[in] array - to search * @param[out] dim - array of dimensions to be filled out (must be zero'd before initial call) * @param[in] maxdim - maximum number of dimensions * @param[out] ndim - number of dimensions of the array */ bool matrix_getarraydimensions(objectarray *array, unsigned int dim[], unsigned int maxdim, unsigned int *ndim) { unsigned int n=0, m=0; for (n=0; nndim; n++) { int k=MORPHO_GETINTEGERVALUE(array->data[n]); if (k>dim[n]) dim[n]=k; } if (maxdimndim) return false; for (unsigned int i=array->ndim; indim+array->nelements; i++) { if (MORPHO_ISARRAY(array->data[i])) { if (!matrix_getarraydimensions(MORPHO_GETARRAY(array->data[i]), dim+n, maxdim-n, &m)) return false; } } *ndim=n+m; return true; } /** Looks up an array element recursively if necessary */ value matrix_getarrayelement(objectarray *array, unsigned int ndim, unsigned int *indx) { unsigned int na=array->ndim; value out; if (array_getelement(array, na, indx, &out)==ARRAY_OK) { if (ndim==na) return out; if (MORPHO_ISARRAY(out)) { return matrix_getarrayelement(MORPHO_GETARRAY(out), ndim-na, indx+na); } } return MORPHO_NIL; } /** Creates a new array from a list of values */ objectmatrix *object_matrixfromarray(objectarray *array) { unsigned int dim[2]={0,1}; // The 1 is to allow for vector arrays. unsigned int ndim=0; objectmatrix *ret=NULL; if (matrix_getarraydimensions(array, dim, 2, &ndim)) { ret=object_newmatrix(dim[0], dim[1], true); } unsigned int indx[2]; if (ret) for (unsigned int i=0; ielements[j*dim[0]+i]); } else if (!MORPHO_ISNIL(f)) { object_free((object *) ret); return NULL; } } } return ret; } /* * Create matrices from lists */ /** Recurses into an objectlist to find the dimensions of the array and all child arrays * @param[in] list - to search * @param[out] dim - array of dimensions to be filled out (must be zero'd before initial call) * @param[in] maxdim - maximum number of dimensions * @param[out] ndim - number of dimensions of the array */ bool matrix_getlistdimensions(objectlist *list, unsigned int dim[], unsigned int maxdim, unsigned int *ndim) { unsigned int m=0; if (maxdim==0) return false; /* Store the length */ if (list->val.count>dim[0]) dim[0]=list->val.count; for (unsigned int i=0; ival.count; i++) { if (MORPHO_ISLIST(list->val.data[i]) && maxdim>0) { matrix_getlistdimensions(MORPHO_GETLIST(list->val.data[i]), dim+1, maxdim-1, &m); } } *ndim=m+1; return true; } /** Gets a matrix element from a (potentially nested) list. */ bool matrix_getlistelement(objectlist *list, unsigned int ndim, unsigned int *indx, value *val) { value out=MORPHO_NIL; objectlist *l=list; for (unsigned int i=0; ival.count) { out=l->val.data[indx[i]]; if (i2) return false; unsigned int indx[2]; if (ret) for (unsigned int i=0; ielements[j*dim[0]+i]); } else { object_free((object *) ret); return NULL; } } } return ret; } /** Creates a matrix from a list of floats */ objectmatrix *object_matrixfromfloats(unsigned int nrows, unsigned int ncols, double *list) { objectmatrix *ret=NULL; ret=object_newmatrix(nrows, ncols, true); if (ret) cblas_dcopy(ncols*nrows, list, 1, ret->elements, 1); return ret; } /* * Clone matrices */ /** Clone a matrix */ objectmatrix *object_clonematrix(objectmatrix *in) { objectmatrix *new = object_newmatrix(in->nrows, in->ncols, false); if (new) { cblas_dcopy(in->ncols * in->nrows, in->elements, 1, new->elements, 1); } return new; } /* ********************************************************************** * Matrix operations * ********************************************************************* */ /** @brief Sets a matrix element. @returns true if the element is in the range of the matrix, false otherwise */ bool matrix_setelement(objectmatrix *matrix, unsigned int row, unsigned int col, double value) { if (colncols && rownrows) { matrix->elements[col*matrix->nrows+row]=value; return true; } return false; } /** @brief Gets a matrix element * @returns true if the element is in the range of the matrix, false otherwise */ bool matrix_getelement(objectmatrix *matrix, unsigned int row, unsigned int col, double *value) { if (colncols && rownrows) { if (value) *value=matrix->elements[col*matrix->nrows+row]; return true; } return false; } /** @brief Gets a column's entries * @param[in] matrix - the matrix * @param[in] col - column number * @param[out] v - column entries (matrix->nrows in number) * @returns true if the element is in the range of the matrix, false otherwise */ bool matrix_getcolumn(objectmatrix *matrix, unsigned int col, double **v) { if (colncols) { *v=&matrix->elements[col*matrix->nrows]; return true; } return false; } /** @brief Sets a column's entries * @param[in] matrix - the matrix * @param[in] col - column number * @param[in] v - column entries (matrix->nrows in number) * @returns true if the element is in the range of the matrix, false otherwise */ bool matrix_setcolumn(objectmatrix *matrix, unsigned int col, double *v) { if (colncols) { cblas_dcopy(matrix->nrows, v, 1, &matrix->elements[col*matrix->nrows], 1); return true; } return false; } /** @brief Add a vector to a column in a matrix * @param[in] m - the matrix * @param[in] col - column number * @param[in] alpha - scale * @param[out] v - column entries (matrix->nrows in number) [should have m->nrows entries] * @returns true on success */ bool matrix_addtocolumn(objectmatrix *m, unsigned int col, double alpha, double *v) { if (colncols) { cblas_daxpy(m->nrows, alpha, v, 1, &m->elements[col*m->nrows], 1); return true; } return false; } /* ********************************************************************** * Matrix arithmetic * ********************************************************************* */ /** Copies one matrix to another */ unsigned int matrix_countdof(objectmatrix *a) { return a->ncols*a->nrows; } /** Copies one matrix to another */ objectmatrixerror matrix_copy(objectmatrix *a, objectmatrix *out) { if (a->ncols==out->ncols && a->nrows==out->nrows) { cblas_dcopy(a->ncols * a->nrows, a->elements, 1, out->elements, 1); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Copies a matrix to another at an arbitrary point */ objectmatrixerror matrix_copyat(objectmatrix *a, objectmatrix *out, int row0, int col0) { if (col0+a->ncols<=out->ncols && row0+a->nrows<=out->nrows) { for (int j=0; jncols; j++) { for (int i=0; inrows; i++) { double value; if (!matrix_getelement(a, i, j, &value)) return MATRIX_BNDS; if (!matrix_setelement(out, row0+i, col0+j, value)) return MATRIX_BNDS; } } return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Performs a + b -> out. */ objectmatrixerror matrix_add(objectmatrix *a, objectmatrix *b, objectmatrix *out) { if (a->ncols==b->ncols && a->ncols==out->ncols && a->nrows==b->nrows && a->nrows==out->nrows) { if (a!=out) cblas_dcopy(a->ncols * a->nrows, a->elements, 1, out->elements, 1); cblas_daxpy(a->ncols * a->nrows, 1.0, b->elements, 1, out->elements, 1); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Performs lambda*a + beta -> out. */ objectmatrixerror matrix_addscalar(objectmatrix *a, double lambda, double beta, objectmatrix *out) { if (a->ncols==out->ncols && a->nrows==out->nrows) { for (unsigned int i=0; inrows*out->ncols; i++) { out->elements[i]=lambda*a->elements[i]+beta; } return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Performs a + lambda*b -> a. */ objectmatrixerror matrix_accumulate(objectmatrix *a, double lambda, objectmatrix *b) { if (a->ncols==b->ncols && a->nrows==b->nrows ) { cblas_daxpy(a->ncols * a->nrows, lambda, b->elements, 1, a->elements, 1); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Performs a - b -> out */ objectmatrixerror matrix_sub(objectmatrix *a, objectmatrix *b, objectmatrix *out) { if (a->ncols==b->ncols && a->ncols==out->ncols && a->nrows==b->nrows && a->nrows==out->nrows) { if (a!=out) cblas_dcopy(a->ncols * a->nrows, a->elements, 1, out->elements, 1); cblas_daxpy(a->ncols * a->nrows, -1.0, b->elements, 1, out->elements, 1); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Performs a * b -> out */ objectmatrixerror matrix_mul(objectmatrix *a, objectmatrix *b, objectmatrix *out) { if (a->ncols==b->nrows && a->nrows==out->nrows && b->ncols==out->ncols) { cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, a->nrows, b->ncols, a->ncols, 1.0, a->elements, a->nrows, b->elements, b->nrows, 0.0, out->elements, out->nrows); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Finds the Frobenius inner product of two matrices */ objectmatrixerror matrix_inner(objectmatrix *a, objectmatrix *b, double *out) { if (a->ncols==b->ncols && a->nrows==b->nrows) { *out=cblas_ddot(a->ncols*a->nrows, a->elements, 1, b->elements, 1); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Computes the outer product of two matrices */ objectmatrixerror matrix_outer(objectmatrix *a, objectmatrix *b, objectmatrix *out) { int m=a->nrows*a->ncols, n=b->nrows*b->ncols; if (m==out->nrows && n==out->ncols) { cblas_dger(CblasColMajor, m, n, 1, a->elements, 1, b->elements, 1, out->elements, out->nrows); return MATRIX_OK; } return MATRIX_INCMPTBLDIM; } /** Solves the system a.x = b * @param[in] a lhs * @param[in] b rhs * @param[in] out - the solution x * @param[out] lu - LU decomposition of a; you must provide an array the same size as a. * @param[out] pivot - you must provide an array with the same number of rows as a. * @returns objectmatrixerror indicating the status; MATRIX_OK indicates success. * */ static objectmatrixerror matrix_div(objectmatrix *a, objectmatrix *b, objectmatrix *out, double *lu, int *pivot) { int n=a->nrows, nrhs = b->ncols, info; cblas_dcopy(a->ncols * a->nrows, a->elements, 1, lu, 1); if (b!=out) cblas_dcopy(b->ncols * b->nrows, b->elements, 1, out->elements, 1); #ifdef MORPHO_LINALG_USE_LAPACKE info=LAPACKE_dgesv(LAPACK_COL_MAJOR, n, nrhs, lu, n, pivot, out->elements, n); #else dgesv_(&n, &nrhs, lu, &n, pivot, out->elements, &n, &info); #endif return (info==0 ? MATRIX_OK : (info>0 ? MATRIX_SING : MATRIX_INVLD)); } /** Solves the system a.x = b for small matrices (test with MATRIX_ISSMALL) * @warning Uses the C stack for storage, which avoids malloc but can cause stack overflow */ objectmatrixerror matrix_divs(objectmatrix *a, objectmatrix *b, objectmatrix *out) { if (a->ncols==b->nrows && a->ncols == out->nrows) { int pivot[a->nrows]; double lu[a->nrows*a->ncols]; return matrix_div(a, b, out, lu, pivot); } return MATRIX_INCMPTBLDIM; } /** Solves the system a.x = b for large matrices (test with MATRIX_ISSMALL) */ objectmatrixerror matrix_divl(objectmatrix *a, objectmatrix *b, objectmatrix *out) { objectmatrixerror ret = MATRIX_ALLOC; // Returned if allocation fails if (!(a->ncols==b->nrows && a->ncols == out->nrows)) return MATRIX_INCMPTBLDIM; int *pivot=MORPHO_MALLOC(sizeof(int)*a->nrows); double *lu=MORPHO_MALLOC(sizeof(double)*a->nrows*a->ncols); if (pivot && lu) ret=matrix_div(a, b, out, lu, pivot); if (pivot) MORPHO_FREE(pivot); if (lu) MORPHO_FREE(lu); return ret; } /** Inverts the matrix a * @param[in] a lhs * @param[in] out - the solution x * @returns objectmatrixerror indicating the status; MATRIX_OK indicates success. * */ objectmatrixerror matrix_inverse(objectmatrix *a, objectmatrix *out) { int nrows=a->nrows, ncols=a->ncols, info; if (!(a->ncols==out->nrows && a->ncols == out->nrows)) return MATRIX_INCMPTBLDIM; int pivot[nrows]; cblas_dcopy(a->ncols * a->nrows, a->elements, 1, out->elements, 1); #ifdef MORPHO_LINALG_USE_LAPACKE info=LAPACKE_dgetrf(LAPACK_COL_MAJOR, nrows, ncols, out->elements, nrows, pivot); #else dgetrf_(&nrows, &ncols, out->elements, &nrows, pivot, &info); #endif if (info!=0) return (info>0 ? MATRIX_SING : MATRIX_INVLD); #ifdef MORPHO_LINALG_USE_LAPACKE info=LAPACKE_dgetri(LAPACK_COL_MAJOR, nrows, out->elements, nrows, pivot); #else int lwork=nrows*ncols; double work[nrows*ncols]; dgetri_(&nrows, out->elements, &nrows, pivot, work, &lwork, &info); #endif return (info==0 ? MATRIX_OK : (info>0 ? MATRIX_SING : MATRIX_INVLD)); } /** Compute eigenvalues and eigenvectors of a matrix * @param[in] a - an objectmatrix to diagonalize of size n * @param[out] wr - a buffer of size n will hold the real part of the eigenvalues on exit * @param[out] wi - a buffer of size n will hold the imag part of the eigenvalues on exit * @param[out] vec - (optional) will be filled out with eigenvectors as columns (should be of size n) * @returns an error code or MATRIX_OK on success */ objectmatrixerror matrix_eigensystem(objectmatrix *a, double *wr, double *wi, objectmatrix *vec) { int info, n=a->nrows; if (a->nrows!=a->ncols) return MATRIX_NSQ; if (vec && ((a->nrows!=vec->nrows) || (a->nrows!=vec->ncols))) return MATRIX_INCMPTBLDIM; // Copy a to prevent destruction size_t size = ((size_t) n) * ((size_t) n) * sizeof(double); double *acopy=MORPHO_MALLOC(size); if (!acopy) return MATRIX_ALLOC; cblas_dcopy(n*n, a->elements, 1, acopy, 1); #ifdef MORPHO_LINALG_USE_LAPACKE info=LAPACKE_dgeev(LAPACK_COL_MAJOR, 'N', (vec ? 'V' : 'N'), n, acopy, n, wr, wi, NULL, n, (vec ? vec->elements : NULL), n); #else int lwork=4*n; double work[4*n]; dgeev_("N", (vec ? "V" : "N"), &n, acopy, &n, wr, wi, NULL, &n, (vec ? vec->elements : NULL), &n, work, &lwork, &info); #endif if (acopy) MORPHO_FREE(acopy); // Free up buffer if (info!=0) return (info>0 ? MATRIX_FAILED : MATRIX_INVLD); return MATRIX_OK; } /** Sums all elements of a matrix using Kahan summation */ double matrix_sum(objectmatrix *a) { unsigned int nel=a->ncols*a->nrows; double sum=0.0, c=0.0, y,t; for (unsigned int i=0; ielements[i]-c; t=sum+y; c=(t-sum)-y; sum=t; } return sum; } /** Norms */ /** Computes the Frobenius norm of a matrix */ double matrix_norm(objectmatrix *a) { double nrm2=cblas_dnrm2(a->ncols*a->nrows, a->elements, 1); return nrm2; } /** Computes the L1 norm of a matrix */ double matrix_L1norm(objectmatrix *a) { unsigned int nel=a->ncols*a->nrows; double sum=0.0, c=0.0, y,t; for (unsigned int i=0; ielements[i])-c; t=sum+y; c=(t-sum)-y; sum=t; } return sum; } /** Computes the Ln norm of a matrix */ double matrix_Lnnorm(objectmatrix *a, double n) { unsigned int nel=a->ncols*a->nrows; double sum=0.0, c=0.0, y,t; for (unsigned int i=0; ielements[i],n)-c; t=sum+y; c=(t-sum)-y; sum=t; } return pow(sum,1.0/n); } /** Computes the Linf norm of a matrix */ double matrix_Linfnorm(objectmatrix *a) { unsigned int nel=a->ncols*a->nrows; double max=0.0; for (unsigned int i=0; ielements[i]); if (y>max) max=y; } return max; } /** Transpose a matrix */ objectmatrixerror matrix_transpose(objectmatrix *a, objectmatrix *out) { if (!(a->ncols==out->nrows && a->nrows == out->ncols)) return MATRIX_INCMPTBLDIM; /* Copy elements a column at a time */ for (unsigned int i=0; incols; i++) { cblas_dcopy(a->nrows, a->elements+(i*a->nrows), 1, out->elements+i, a->ncols); } return MATRIX_OK; } /** Calculate the trace of a matrix */ objectmatrixerror matrix_trace(objectmatrix *a, double *out) { if (a->nrows!=a->ncols) return MATRIX_NSQ; *out=1.0; *out=cblas_ddot(a->nrows, a->elements, a->ncols+1, out, 0); return MATRIX_OK; } /** Scale a matrix */ objectmatrixerror matrix_scale(objectmatrix *a, double scale) { cblas_dscal(a->ncols*a->nrows, scale, a->elements, 1); return MATRIX_OK; } /** Load the indentity matrix*/ objectmatrixerror matrix_identity(objectmatrix *a) { if (a->ncols!=a->nrows) return MATRIX_NSQ; memset(a->elements, 0, sizeof(double)*a->nrows*a->ncols); for (int i=0; inrows; i++) a->elements[i+a->nrows*i]=1.0; return MATRIX_OK; } /** Sets a matrix to zero */ objectmatrixerror matrix_zero(objectmatrix *a) { memset(a->elements, 0, sizeof(double)*a->nrows*a->ncols); return MATRIX_OK; } /** Prints a matrix */ void matrix_print(vm *v, objectmatrix *m) { for (int i=0; inrows; i++) { // Rows run from 0...m morpho_printf(v, "[ "); for (int j=0; jncols; j++) { // Columns run from 0...k double val; matrix_getelement(m, i, j, &val); morpho_printf(v, "%g ", (fabs(val)nrows-1 ? "\n" : "")); } } /** Prints a matrix to a buffer */ bool matrix_printtobuffer(objectmatrix *m, char *format, varray_char *out) { for (int i=0; inrows; i++) { // Rows run from 0...m varray_charadd(out, "[ ", 2); for (int j=0; jncols; j++) { // Columns run from 0...k double val; matrix_getelement(m, i, j, &val); if (!format_printtobuffer(MORPHO_FLOAT(val), format, out)) return false; varray_charadd(out, " ", 1); } varray_charadd(out, "]", 1); if (inrows-1) varray_charadd(out, "\n", 1); } return true; } /** Matrix eigensystem */ bool matrix_eigen(vm *v, objectmatrix *a, value *evals, value *evecs) { double *ev = MORPHO_MALLOC(sizeof(double)*a->nrows*2); // Allocate temporary memory for eigenvalues double *er=ev, *ei=ev+a->nrows; objectmatrix *vecs=NULL; // A new matrix for eigenvectors objectlist *vallist = object_newlist(0, NULL); // List to hold eigenvalues bool success=false; if (evecs) vecs=object_clonematrix(a); // Clones a to hold eigenvectors // Check that everything was allocated correctly if (!(ev && vallist && (!evecs || vecs))) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); goto matrix_eigen_cleanup; }; objectmatrixerror err=matrix_eigensystem(a, er, ei, vecs); if (err!=MATRIX_OK) { matrix_raiseerror(v, err); goto matrix_eigen_cleanup; } // Now process the eigenvalues for (int i=0; inrows; i++) { if (fabs(ei[i])val.count; i++) { if (MORPHO_ISOBJECT(vallist->val.data[i])) object_free(MORPHO_GETOBJECT(vallist->val.data[i])); } object_free((object *) vallist); } if (vecs) object_free((object *) vecs); } return success; } /* ********************************************************************** * Matrix veneer class * ********************************************************************* */ /** Constructs a Matrix object */ value matrix_constructor(vm *v, int nargs, value *args) { unsigned int nrows, ncols; objectmatrix *new=NULL; value out=MORPHO_NIL; if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1)) ) { nrows = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); ncols = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)); new=object_newmatrix(nrows, ncols, true); } else if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { nrows = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); ncols = 1; new=object_newmatrix(nrows, ncols, true); } else if (nargs==1 && MORPHO_ISARRAY(MORPHO_GETARG(args, 0))) { new=object_matrixfromarray(MORPHO_GETARRAY(MORPHO_GETARG(args, 0))); if (!new) morpho_runtimeerror(v, MATRIX_INVLDARRAYINIT); #ifdef MORPHO_INCLUDE_SPARSE } else if (nargs==1 && MORPHO_ISLIST(MORPHO_GETARG(args, 0))) { new=object_matrixfromlist(MORPHO_GETLIST(MORPHO_GETARG(args, 0))); if (!new) { /** Could this be a concatenation operation? */ objectsparseerror err = sparse_catmatrix(MORPHO_GETLIST(MORPHO_GETARG(args, 0)), &new); if (err==SPARSE_INVLDINIT) { morpho_runtimeerror(v, MATRIX_INVLDARRAYINIT); } else if (err!=SPARSE_OK) sparse_raiseerror(v, err); } #endif } else if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { new=object_clonematrix(MORPHO_GETMATRIX(MORPHO_GETARG(args, 0))); if (!new) morpho_runtimeerror(v, MATRIX_INVLDARRAYINIT); #ifdef MORPHO_INCLUDE_SPARSE } else if (nargs==1 && MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { objectsparseerror err=sparse_tomatrix(MORPHO_GETSPARSE(MORPHO_GETARG(args, 0)), &new); if (err!=SPARSE_OK) morpho_runtimeerror(v, MATRIX_INVLDARRAYINIT); #endif } else morpho_runtimeerror(v, MATRIX_CONSTRUCTOR); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Creates an identity matrix */ value matrix_identityconstructor(vm *v, int nargs, value *args) { int n; objectmatrix *new=NULL; value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { n = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); new=object_newmatrix(n, n, false); if (new) { matrix_identity(new); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else morpho_runtimeerror(v, MATRIX_IDENTCONSTRUCTOR); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Checks that a matrix is indexed with 2 indices with a generic interface */ bool matrix_slicedim(value * a, unsigned int ndim){ if (ndim>2||ndim<0) return false; return true; } /** Constucts a new matrix with a generic interface */ void matrix_sliceconstructor(unsigned int *slicesize,unsigned int ndim,value* out){ unsigned int numcol = 1; if (ndim == 2) { numcol = slicesize[1]; } *out = MORPHO_OBJECT(object_newmatrix(slicesize[0],numcol,false)); } /** Copies data from a at indx to out at newindx with a generic interface */ objectarrayerror matrix_slicecopy(value * a,value * out, unsigned int ndim, unsigned int *indx,unsigned int *newindx){ double num; // matrices store doubles; unsigned int colindx = 0; unsigned int colnewindx = 0; if (ndim == 2) { colindx = indx[1]; colnewindx = newindx[1]; } if (!(matrix_getelement(MORPHO_GETMATRIX(*a),indx[0],colindx,&num)&& matrix_setelement(MORPHO_GETMATRIX(*out),newindx[0],colnewindx,num))){ return ARRAY_OUTOFBOUNDS; } return ARRAY_OK; } /** Rolls the matrix list */ void matrix_rollflat(objectmatrix *a, objectmatrix *b, int nplaces) { unsigned int N = a->nrows*a->ncols; int n = abs(nplaces); if (n>N) n = n % N; unsigned int Np = N - n; // Number of elements to roll if (nplaces<0) { memcpy(b->matrixdata, a->matrixdata+n, sizeof(double)*Np); memcpy(b->matrixdata+Np, a->matrixdata, sizeof(double)*n); } else { memcpy(b->matrixdata+n, a->matrixdata, sizeof(double)*Np); if (n>0) memcpy(b->matrixdata, a->matrixdata+Np, sizeof(double)*n); } } /** Copies arow from matrix a into brow for matrix b */ void matrix_copyrow(objectmatrix *a, int arow, objectmatrix *b, int brow) { cblas_dcopy(a->ncols, a->elements+arow, a->nrows, b->elements+brow, a->nrows); } /** Rolls a list by a number of elements */ objectmatrix *matrix_roll(objectmatrix *a, int nplaces, int axis) { objectmatrix *new=object_newmatrix(a->nrows, a->ncols, false); if (new) { switch(axis) { case 0: { // TODO: Could probably be faster for (int i=0; inrows; i++) { int j = (i+nplaces); if (j<0) j+=a->nrows; matrix_copyrow(a, i, new, j % a->nrows); } } break; case 1: matrix_rollflat(a, new, nplaces*a->nrows); break; } } return new; } /** Gets the matrix element with given indices */ value Matrix_getindex(vm *v, int nargs, value *args) { objectmatrix *m=MORPHO_GETMATRIX(MORPHO_SELF(args)); unsigned int indx[2]={0,0}; value out = MORPHO_NIL; if (nargs>2){ morpho_runtimeerror(v, MATRIX_INVLDNUMINDICES); return out; } if (array_valuelisttoindices(nargs, args+1, indx)) { double outval; if (!matrix_getelement(m, indx[0], indx[1], &outval)) { morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } else { out = MORPHO_FLOAT(outval); } } else { // now try to get a slice objectarrayerror err = getslice(&MORPHO_SELF(args), &matrix_slicedim, &matrix_sliceconstructor, &matrix_slicecopy, nargs, &MORPHO_GETARG(args,0), &out); if (err!=ARRAY_OK) MORPHO_RAISE(v, array_to_matrix_error(err) ); if (MORPHO_ISOBJECT(out)){ morpho_bindobjects(v,1,&out); } else morpho_runtimeerror(v, MATRIX_INVLDINDICES); } return out; } /** Sets the matrix element with given indices */ value Matrix_setindex(vm *v, int nargs, value *args) { objectmatrix *m=MORPHO_GETMATRIX(MORPHO_SELF(args)); unsigned int indx[2]={0,0}; if (array_valuelisttoindices(nargs-1, args+1, indx)) { double value=0.0; if (MORPHO_ISFLOAT(args[nargs])) value=MORPHO_GETFLOATVALUE(args[nargs]); if (MORPHO_ISINTEGER(args[nargs])) value=(double) MORPHO_GETINTEGERVALUE(args[nargs]); if (!matrix_setelement(m, indx[0], indx[1], value)) { morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } } else morpho_runtimeerror(v, MATRIX_INVLDINDICES); return MORPHO_NIL; } /** Sets the column of a matrix */ value Matrix_setcolumn(vm *v, int nargs, value *args) { objectmatrix *m=MORPHO_GETMATRIX(MORPHO_SELF(args)); if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISMATRIX(MORPHO_GETARG(args, 1))) { unsigned int col = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); objectmatrix *src = MORPHO_GETMATRIX(MORPHO_GETARG(args, 1)); if (colncols) { if (src && src->ncols*src->nrows==m->nrows) { matrix_setcolumn(m, col, src->elements); } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } else morpho_runtimeerror(v, MATRIX_SETCOLARGS); return MORPHO_NIL; } /** Gets a column of a matrix */ value Matrix_getcolumn(vm *v, int nargs, value *args) { objectmatrix *m=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { unsigned int col = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (colncols) { double *vals; if (matrix_getcolumn(m, col, &vals)) { objectmatrix *new=object_matrixfromfloats(m->nrows, 1, vals); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } } else morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } else morpho_runtimeerror(v, MATRIX_SETCOLARGS); return out; } /** Prints a matrix */ value Matrix_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISMATRIX(self)) return Object_print(v, nargs, args); objectmatrix *m=MORPHO_GETMATRIX(MORPHO_SELF(args)); matrix_print(v, m); return MORPHO_NIL; } /** Formatted conversion to a string */ value Matrix_format(vm *v, int nargs, value *args) { value out = MORPHO_NIL; if (nargs==1 && MORPHO_ISSTRING(MORPHO_GETARG(args, 0))) { varray_char str; varray_charinit(&str); if (matrix_printtobuffer(MORPHO_GETMATRIX(MORPHO_SELF(args)), MORPHO_GETCSTRING(MORPHO_GETARG(args, 0)), &str)) { out = object_stringfromvarraychar(&str); if (MORPHO_ISOBJECT(out)) morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); varray_charclear(&str); } else { morpho_runtimeerror(v, VALUE_FRMTARG); } return out; } /** Matrix add */ value Matrix_assign(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (a->ncols==b->ncols && a->nrows==b->nrows) { matrix_copy(b, a); } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } return MORPHO_NIL; } /** Matrix add */ value Matrix_add(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (a->ncols==b->ncols && a->nrows==b->nrows) { objectmatrix *new = object_newmatrix(a->nrows, a->ncols, false); if (new) { out=MORPHO_OBJECT(new); matrix_add(a, b, new); } } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectmatrix *new = object_newmatrix(a->nrows, a->ncols, false); if (new) { out=MORPHO_OBJECT(new); matrix_addscalar(a, 1.0, val, new); } } } else morpho_runtimeerror(v, MATRIX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Right add */ value Matrix_addr(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && (MORPHO_ISNIL(MORPHO_GETARG(args, 0)) || MORPHO_ISNUMBER(MORPHO_GETARG(args, 0)))) { int i=0; if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) i=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (MORPHO_ISFLOAT(MORPHO_GETARG(args, 0))) i=(fabs(MORPHO_GETFLOATVALUE(MORPHO_GETARG(args, 0)))ncols==b->ncols && a->nrows==b->nrows) { objectmatrix *new = object_newmatrix(a->nrows, a->ncols, false); if (new) { out=MORPHO_OBJECT(new); matrix_sub(a, b, new); } } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double val; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &val)) { objectmatrix *new = object_newmatrix(a->nrows, a->ncols, false); if (new) { out=MORPHO_OBJECT(new); matrix_addscalar(a, 1.0, -val, new); } } } else morpho_runtimeerror(v, MATRIX_ARITHARGS); if (!MORPHO_ISNIL(out)) morpho_bindobjects(v, 1, &out); return out; } /** Right subtract */ value Matrix_subr(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && (MORPHO_ISNIL(MORPHO_GETARG(args, 0)) || MORPHO_ISNUMBER(MORPHO_GETARG(args, 0)))) { int i=(MORPHO_ISNIL(MORPHO_GETARG(args, 0)) ? 0 : MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0))); if (MORPHO_ISFLOAT(MORPHO_GETARG(args, 0))) i=(fabs(MORPHO_GETFLOATVALUE(MORPHO_GETARG(args, 0)))nrows, a->ncols, false); if (new) { matrix_addscalar(a, 1.0, -val, new); // now that did self - arg[0] and we want arg[0] - self so scale the whole thing by -1 matrix_scale(new, -1.0); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } } else morpho_runtimeerror(v, VM_INVALIDARGS); } else morpho_runtimeerror(v, VM_INVALIDARGS); return out; } /** Matrix multiply */ value Matrix_mul(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (a->ncols==b->nrows) { objectmatrix *new = object_newmatrix(a->nrows, b->ncols, false); if (new) { out=MORPHO_OBJECT(new); matrix_mul(a, b, new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double scale=1.0; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) { objectmatrix *new = object_clonematrix(a); if (new) { out=MORPHO_OBJECT(new); matrix_scale(new, scale); morpho_bindobjects(v, 1, &out); } } #ifdef MORPHO_INCLUDE_SPARSE } else if (nargs==1 && MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { // Returns nil to ensure it gets passed to mulr on Sparse #endif } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return out; } /** Called when multiplying on the right */ value Matrix_mulr(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double scale=1.0; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) { objectmatrix *new = object_clonematrix(a); if (new) { out=MORPHO_OBJECT(new); matrix_scale(new, scale); morpho_bindobjects(v, 1, &out); } } } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return out; } /** Solution of linear system a.x = b (i.e. x = b/a) */ value Matrix_div(vm *v, int nargs, value *args) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); if (a->ncols==b->nrows) { objectmatrix *new = object_newmatrix(b->nrows, b->ncols, false); if (new) { objectmatrixerror err; if (MATRIX_ISSMALL(a)) { err=matrix_divs(a, b, new); } else { err=matrix_divl(a, b, new); } if (err==MATRIX_SING) { morpho_runtimeerror(v, MATRIX_SINGULAR); object_free((object *) new); } else { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); #ifdef MORPHO_INCLUDE_SPARSE } else if (nargs==1 && MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { /* Division by a sparse matrix: redirect to the divr selector of Sparse. */ value vargs[2]={args[1],args[0]}; return Sparse_divr(v, nargs, vargs); #endif } else if (nargs==1 && MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { /* Division by a scalar */ double scale=1.0; if (morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) { if (fabs(scale)ncols==b->ncols && a->nrows==b->nrows) { out=MORPHO_SELF(args); double lambda=1.0; morpho_valuetofloat(MORPHO_GETARG(args, 0), &lambda); matrix_accumulate(a, lambda, b); } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return MORPHO_NIL; } /** Frobenius inner product */ value Matrix_inner(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); double prod=0.0; if (matrix_inner(a, b, &prod)==MATRIX_OK) { out = MORPHO_FLOAT(prod); } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return out; } /** Outer product */ value Matrix_outer(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); objectmatrix *new=object_newmatrix(a->nrows*a->ncols, b->nrows*b->ncols, true); if (new && matrix_outer(a, b, new)==MATRIX_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, MATRIX_ARITHARGS); return out; } /** Matrix sum */ value Matrix_sum(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); return MORPHO_FLOAT(matrix_sum(a)); } /** Roll a matrix */ value Matrix_roll(vm *v, int nargs, value *args) { objectmatrix *slf = MORPHO_GETMATRIX(MORPHO_SELF(args)); value out = MORPHO_NIL; int roll, axis=0; if (nargs>0 && morpho_valuetoint(MORPHO_GETARG(args, 0), &roll)) { if (nargs==2 && !morpho_valuetoint(MORPHO_GETARG(args, 1), &axis)) return out; objectmatrix *new = matrix_roll(slf, roll, axis); if (new) { out = MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } else morpho_runtimeerror(v, LIST_ADDARGS); return out; } /** Matrix norm */ value Matrix_norm(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out = MORPHO_NIL; if (nargs==1) { value arg = MORPHO_GETARG(args, 0); if (MORPHO_ISNUMBER(arg)) { double n; if (morpho_valuetofloat(arg, &n)) { if (fabs(n-1.0)val.count, new->val.data); new->val.count--; // And pop it back off } return evals; } /** Matrix eigensystem */ value Matrix_eigensystem(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value evals=MORPHO_NIL, evecs=MORPHO_NIL, out=MORPHO_NIL; objectlist *resultlist = object_newlist(0, NULL); if (!resultlist) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return MORPHO_NIL; } if (matrix_eigen(v, a, &evals, &evecs)) { objectlist *evallist = MORPHO_GETLIST(evals); list_append(resultlist, evals); // Create the output list list_append(resultlist, evecs); out=MORPHO_OBJECT(resultlist); list_append(evallist, evals); // Ensure we bind all objects at once list_append(evallist, evecs); // by popping them onto the evallist. list_append(evallist, out); // morpho_bindobjects(v, evallist->val.count, evallist->val.data); evallist->val.count-=3; // and then popping them back off. } return out; } /** Inverts a matrix */ value Matrix_inverse(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; // The inverse will have the number of rows and number of columns // swapped. objectmatrix *new = object_newmatrix(a->ncols, a->nrows, false); if (new) { objectmatrixerror mi = matrix_inverse(a, new); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); if (mi!=MATRIX_OK) matrix_raiseerror(v, mi); } return out; } /** Transpose of a matrix */ value Matrix_transpose(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; objectmatrix *new = object_newmatrix(a->ncols, a->nrows, false); if (new) { matrix_transpose(a, new); out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Reshape a matrix */ value Matrix_reshape(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); if (nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1))) { int nrows = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); int ncols = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)); if (nrows*ncols==a->nrows*a->ncols) { a->nrows=nrows; a->ncols=ncols; } else morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } else morpho_runtimeerror(v, MATRIX_RESHAPEARGS); return MORPHO_NIL; } /** Trace of a matrix */ value Matrix_trace(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (a->nrows==a->ncols) { double tr; if (matrix_trace(a, &tr)==MATRIX_OK) out=MORPHO_FLOAT(tr); } else { morpho_runtimeerror(v, MATRIX_NOTSQ); } return out; } /** Enumerate protocol */ value Matrix_enumerate(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (i<0) out=MORPHO_INTEGER(a->ncols*a->nrows); else if (incols*a->nrows) out=MORPHO_FLOAT(a->elements[i]); } } return out; } /** Number of matrix elements */ value Matrix_count(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); return MORPHO_INTEGER(a->ncols*a->nrows); } /** Matrix dimensions */ value Matrix_dimensions(vm *v, int nargs, value *args) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); value dim[2]; value out=MORPHO_NIL; dim[0]=MORPHO_INTEGER(a->nrows); dim[1]=MORPHO_INTEGER(a->ncols); objectlist *new=object_newlist(2, dim); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Clones a matrix */ value Matrix_clone(vm *v, int nargs, value *args) { value out=MORPHO_NIL; objectmatrix *a=MORPHO_GETMATRIX(MORPHO_SELF(args)); objectmatrix *new=object_clonematrix(a); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } MORPHO_BEGINCLASS(Matrix) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Matrix_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Matrix_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_GETCOLUMN_METHOD, Matrix_getcolumn, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_SETCOLUMN_METHOD, Matrix_setcolumn, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Matrix_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_FORMAT_METHOD, Matrix_format, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ASSIGN_METHOD, Matrix_assign, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Matrix_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADDR_METHOD, Matrix_addr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Matrix_sub, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUBR_METHOD, Matrix_subr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MUL_METHOD, Matrix_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MULR_METHOD, Matrix_mulr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIV_METHOD, Matrix_div, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ACC_METHOD, Matrix_acc, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_INNER_METHOD, Matrix_inner, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_OUTER_METHOD, Matrix_outer, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUM_METHOD, Matrix_sum, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_NORM_METHOD, Matrix_norm, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_INVERSE_METHOD, Matrix_inverse, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_TRANSPOSE_METHOD, Matrix_transpose, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_RESHAPE_METHOD, Matrix_reshape, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_EIGENVALUES_METHOD, Matrix_eigenvalues, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_EIGENSYSTEM_METHOD, Matrix_eigensystem, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_TRACE_METHOD, Matrix_trace, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Matrix_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Matrix_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_DIMENSIONS_METHOD, Matrix_dimensions, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ROLL_METHOD, Matrix_roll, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Matrix_clone, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* ********************************************************************** * Initialization * ********************************************************************* */ void matrix_initialize(void) { objectmatrixtype=object_addtype(&objectmatrixdefn); builtin_addfunction(MATRIX_CLASSNAME, matrix_constructor, MORPHO_FN_CONSTRUCTOR); builtin_addfunction(MATRIX_IDENTITYCONSTRUCTOR, matrix_identityconstructor, BUILTIN_FLAGSEMPTY); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value matrixclass=builtin_addclass(MATRIX_CLASSNAME, MORPHO_GETCLASSDEFINITION(Matrix), objclass); object_setveneerclass(OBJECT_MATRIX, matrixclass); morpho_defineerror(MATRIX_INDICESOUTSIDEBOUNDS, ERROR_HALT, MATRIX_INDICESOUTSIDEBOUNDS_MSG); morpho_defineerror(MATRIX_INVLDINDICES, ERROR_HALT, MATRIX_INVLDINDICES_MSG); morpho_defineerror(MATRIX_INVLDNUMINDICES, ERROR_HALT, MATRIX_INVLDNUMINDICES_MSG); morpho_defineerror(MATRIX_CONSTRUCTOR, ERROR_HALT, MATRIX_CONSTRUCTOR_MSG); morpho_defineerror(MATRIX_INVLDARRAYINIT, ERROR_HALT, MATRIX_INVLDARRAYINIT_MSG); morpho_defineerror(MATRIX_ARITHARGS, ERROR_HALT, MATRIX_ARITHARGS_MSG); morpho_defineerror(MATRIX_RESHAPEARGS, ERROR_HALT, MATRIX_RESHAPEARGS_MSG); morpho_defineerror(MATRIX_INCOMPATIBLEMATRICES, ERROR_HALT, MATRIX_INCOMPATIBLEMATRICES_MSG); morpho_defineerror(MATRIX_SINGULAR, ERROR_HALT, MATRIX_SINGULAR_MSG); morpho_defineerror(MATRIX_NOTSQ, ERROR_HALT, MATRIX_NOTSQ_MSG); morpho_defineerror(MATRIX_OPFAILED, ERROR_HALT, MATRIX_OPFAILED_MSG); morpho_defineerror(MATRIX_SETCOLARGS, ERROR_HALT, MATRIX_SETCOLARGS_MSG); morpho_defineerror(MATRIX_NORMARGS, ERROR_HALT, MATRIX_NORMARGS_MSG); morpho_defineerror(MATRIX_IDENTCONSTRUCTOR, ERROR_HALT, MATRIX_IDENTCONSTRUCTOR_MSG); } #endif ================================================ FILE: src/linalg/matrix.h ================================================ /** @file matrix.h * @author T J Atherton * * @brief Veneer class over the objectmatrix type that interfaces with blas and lapack */ #ifndef matrix_h #define matrix_h #include "build.h" #ifdef MORPHO_INCLUDE_LINALG #include #include "classes.h" /** Use Apple's Accelerate library for LAPACK and BLAS */ #ifdef __APPLE__ #ifdef MORPHO_LINALG_USE_ACCELERATE #define ACCELERATE_NEW_LAPACK #include #define MATRIX_LAPACK_PRESENT #endif #endif /** Otherwise, use LAPACKE */ #ifndef MATRIX_LAPACK_PRESENT #include #include #define MORPHO_LINALG_USE_LAPACKE #define MATRIX_LAPACK_PRESENT #endif #include "cmplx.h" #include "list.h" /* ------------------------------------------------------- * Matrix objects * ------------------------------------------------------- */ extern objecttype objectmatrixtype; #define OBJECT_MATRIX objectmatrixtype /** Matrices are a purely numerical collection type oriented toward linear algebra. Elements are stored in column-major format, i.e. [ 1 2 ] [ 3 4 ] is stored ( 1, 3, 2, 4 ) in memory. This is for compatibility with standard linear algebra packages */ typedef struct { object obj; unsigned int nrows; unsigned int ncols; double *elements; double matrixdata[]; } objectmatrix; /** Tests whether an object is a matrix */ #define MORPHO_ISMATRIX(val) object_istype(val, OBJECT_MATRIX) /** Gets the object as an matrix */ #define MORPHO_GETMATRIX(val) ((objectmatrix *) MORPHO_GETOBJECT(val)) /** Creates a matrix object */ objectmatrix *object_newmatrix(unsigned int nrows, unsigned int ncols, bool zero); /** Creates a new matrix from an array */ objectmatrix *object_matrixfromarray(objectarray *array); /** Creates a new matrix from an existing matrix */ objectmatrix *object_clonematrix(objectmatrix *array); /** @brief Use to create static matrices on the C stack @details Intended for small matrices; Caller needs to supply a double array of size nr*nc. */ #define MORPHO_STATICMATRIX(darray, nr, nc) { .obj.type=OBJECT_MATRIX, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .elements=darray, .nrows=nr, .ncols=nc } /** Macro to decide if a matrix is 'small' or 'large' and hence static or dynamic allocation should be used. */ #define MATRIX_ISSMALL(m) (m->nrows*m->ncols #include #include "morpho.h" #include "classes.h" #include "sparse.h" #include "matrix.h" /* *************************************** * Compatibility with Sparse libraries * *************************************** */ /** ---- CSparse --- */ #ifdef MORPHO_LINALG_USE_CSPARSE #include /* Convert a CCS structure into a CSPARSE structure */ void sparse_ccstocsparse(sparseccs *s, cs *out) { out->nzmax=s->nentries; out->m=s->nrows; out->n=s->ncols; out->p=s->cptr; out->i=s->rix; out->x=s->values; out->nz=-1; } /* Convert a CCS structure into a CSPARSE structure */ void sparse_csparsetoccs(cs *in, sparseccs *out) { out->nentries=in->nzmax; out->nrows=in->m; out->ncols=in->n; out->cptr=in->p; out->rix=in->i; out->values=in->x; } #endif /* *************************************** * Dictionary of keys format * *************************************** */ objecttype objectdokkeytype; /** DOK key object definitions */ void objectdokkey_printfn(object *obj, void *v) { morpho_printf(v, ""); } size_t objectdokkey_sizefn(object *obj) { return sizeof(objectdokkey); } /** Fibonacci hash function for pairs of integers. */ hash objectdokkey_hashfn(object *obj) { objectdokkey *key = (objectdokkey *) obj; uint64_t i1 = MORPHO_GETDOKKEYROW(key); uint64_t i2 = MORPHO_GETDOKKEYCOL(key); return ((i1<<32 | i2) * 11400714819323198485llu)>> 32; } int objectdokkey_cmpfn(object *a, object *b) { objectdokkey *akey = (objectdokkey *) a; objectdokkey *bkey = (objectdokkey *) b; return ((MORPHO_GETDOKKEYCOL(akey)==MORPHO_GETDOKKEYCOL(bkey) && MORPHO_GETDOKKEYROW(akey)==MORPHO_GETDOKKEYROW(bkey)) ? MORPHO_EQUAL : MORPHO_NOTEQUAL); } objecttypedefn objectdokkeydefn = { .printfn=objectdokkey_printfn, .markfn=NULL, .freefn=NULL, .sizefn=objectdokkey_sizefn, .hashfn=objectdokkey_hashfn, .cmpfn=objectdokkey_cmpfn }; DEFINE_VARRAY(dokkey, objectdokkey); /** Initializes a sparsedok structure */ void sparsedok_init(sparsedok *dok) { dok->nrows=0; dok->ncols=0; dictionary_init(&dok->dict); dok->keys=NULL; } /** Clears a sparsedok structure */ void sparsedok_clear(sparsedok *dok) { objectdokkey *next=NULL; for (objectdokkey *key=dok->keys; key!=NULL; key=next) { next=(objectdokkey *) key->obj.next; MORPHO_FREE(key); } dictionary_clear(&dok->dict); sparsedok_init(dok); } /** Create a new key from a pair of indices */ static objectdokkey *sparsedok_newkey(sparsedok *dok, int i, int j) { objectdokkey *key = MORPHO_MALLOC(sizeof(objectdokkey)); if (key) { object_init((object *) key, OBJECT_DOKKEY); key->row=i; key->col=j; } return key; } /** Adds a key to a dok structure. * @returns the key added. */ static objectdokkey *sparsedok_addkey(sparsedok *dok, int i, int j) { objectdokkey *out=sparsedok_newkey(dok, i, j); if (out) { out->obj.next=(object *) dok->keys; dok->keys=out; } return out; } /** Inserts a matrix element (i,j) -> val into a sparsedok structure * @returns true on success. */ bool sparsedok_insert(sparsedok *dok, int i, int j, value val) { objectdokkey key=MORPHO_STATICDOKKEY(i, j); if (!dictionary_get(&dok->dict, MORPHO_OBJECT(&key), NULL)) { objectdokkey *newkey=sparsedok_addkey(dok, i, j); if (newkey) { if (dok->nrows==0 || i>=dok->nrows) dok->nrows=i+1; if (dok->ncols==0 || j>=dok->ncols) dok->ncols=j+1; return dictionary_insert(&dok->dict, MORPHO_OBJECT(newkey), val); } } else { return dictionary_insert(&dok->dict, MORPHO_OBJECT(&key), val); } return false; } /** Retrieves a matrix element (i,j) from a sparsedok structure * @returns true on success. */ bool sparsedok_get(sparsedok *dok, int i, int j, value *val) { objectdokkey key=MORPHO_STATICDOKKEY(i, j); return dictionary_get(&dok->dict, MORPHO_OBJECT(&key), val); } /** Removes a matrix element (i,j) from a sparsedok * @returns true on success. * @warning Use sparingly as the deleted key is not recovered. */ bool sparsedok_remove(sparsedok *dok, int i, int j, value *val) { objectdokkey key=MORPHO_STATICDOKKEY(i, j); return dictionary_remove(&dok->dict, MORPHO_OBJECT(&key)); } /** Sets the dimensions of the matrix * @returns true if successful, or false if the dimensions are incompatible with existing matrix entries * @details This function is intended for use in constructing matrix. */ bool sparsedok_setdimensions(sparsedok *dok, int nrows, int ncols) { if (nrowsnrows || ncolsncols) return false; dok->nrows=nrows; dok->ncols=ncols; return true; } /** Expands the dimensions of a matrix * @returns true if successful, or false if the dimensions are incompatible with existing matrix entries * @details This function is intended for use in constructing matrix. */ bool sparsedok_expanddimensions(sparsedok *dok, int nrows, int ncols) { if (nrows>dok->nrows) dok->nrows=nrows; if (nrows>dok->ncols) dok->ncols=ncols; return true; } /** Prints a sparsedok matrix */ void sparsedok_print(vm *v, sparsedok *dok) { value out; for (int i=0; inrows; i++) { morpho_printf(v, "[ "); for (int j=0; jncols; j++) { if (sparsedok_get(dok, i, j, &out)) { morpho_printvalue(v, out); morpho_printf(v, " "); } else { morpho_printf(v, "0 "); } } morpho_printf(v, "]%s", (inrows-1 ? "\n" : "")); } } /** Number of entries in a sparsedok */ unsigned int sparsedok_count(sparsedok *dok) { return dok->dict.count; } /** Loop over dok keys - initializer * @param[in] dok - the dictionary of keys to loop over * @returns Initial value for the loop counter */ void *sparsedok_loopstart(sparsedok *dok) { return dok->keys; } /** Loop over dok keys * @param[in] dok - the dictionary of keys to loop over * @param[in] cntr - Pointer to loop counter of type (void *) * @param[out] i - row index. * @param[out] j - column index * @returns true if i, j contain valid data; cntri is updated */ bool sparsedok_loop(sparsedok *dok, void **cntr, int *i, int *j) { objectdokkey *key = *cntr; if (key) { *i = key->row; *j = key->col; *cntr=key->obj.next; } return key; } /* *************** * Copy operations * *************** */ /* Copies a sparsedok object */ bool sparsedok_copy(sparsedok *src, sparsedok *dest) { int i, j; void *ctr = sparsedok_loopstart(src); value entry; if (!sparsedok_setdimensions(dest, src->nrows, src->ncols)) return false; while (sparsedok_loop(src, &ctr, &i, &j)) { if (sparsedok_get(src, i, j, &entry)) { if (!sparsedok_insert(dest, i, j, entry)) return false; } } return true; } /* Copies a sparsedok object with a particular destination */ bool sparsedok_copyat(sparsedok *src, sparsedok *dest, int row0, int col0) { int i, j; void *ctr = sparsedok_loopstart(src); value entry; while (sparsedok_loop(src, &ctr, &i, &j)) { if (sparsedok_get(src, i, j, &entry)) { if (!sparsedok_insert(dest, i+row0, j+col0, entry)) return false; } } return true; } /** Copies a dense matrix to a sparse dok */ bool sparsedok_copymatrixat(objectmatrix *src, sparsedok *dest, int row0, int col0) { double val; for (int j=0; jncols; j++) { for (int i=0; inrows; i++) { if (!(matrix_getelement(src, i, j, &val) && sparsedok_insert(dest, i+row0, j+col0, MORPHO_FLOAT(val)))) return false; } } return true; } /* Copies a sparsedok object to a dense matrix */ bool sparsedok_copytomatrix(sparsedok *src, objectmatrix *dest, int row0, int col0) { int i, j; void *ctr = sparsedok_loopstart(src); value entry; while (sparsedok_loop(src, &ctr, &i, &j)) { if (sparsedok_get(src, i, j, &entry)) { double val=0.0; if (!morpho_valuetofloat(entry, &val)) return false; if (!matrix_setelement(dest, i+row0, j+col0, val)) return false; } } return true; } /* *************************************** * Compressed Column Storage Format * *************************************** */ /** Initializes an empty sparseccs */ void sparseccs_init(sparseccs *ccs) { ccs->nentries=0; ccs->nrows=0; ccs->ncols=0; ccs->cptr=NULL; ccs->rix=NULL; ccs->values=NULL; } /** These wrappers enable us to ensure that we're using the same allocator as CSparse */ static void _ccsfree(void *p) { #ifdef MORPHO_LINALG_USE_CSPARSE cs_free(p); #else MORPHO_FREE(p); #endif } static void *_ccsrealloc(void *p, size_t newsize) { #ifdef MORPHO_LINALG_USE_CSPARSE CS_INT ok; return cs_realloc(p, 1, newsize, &ok); #else return MORPHO_REALLOC(P, newsize); #endif } /** Clears all data structures associated with a sparseccs */ void sparseccs_clear(sparseccs *ccs) { if (ccs->cptr) _ccsfree(ccs->cptr); if (ccs->rix) _ccsfree(ccs->rix); if (ccs->values) _ccsfree(ccs->values); sparseccs_init(ccs); } /** Resizes a sparseccs */ bool sparseccs_resize(sparseccs *ccs, int nrows, int ncols, unsigned int nentries, bool values) { if (ncols>ccs->ncols) { ccs->cptr=_ccsrealloc(ccs->cptr, sizeof(int)*(ncols+1)); if (ccs->values || values) { ccs->values=_ccsrealloc(ccs->values, sizeof(double)*nentries); if (!ccs->values) goto sparseccs_resize_error; } } ccs->rix=_ccsrealloc(ccs->rix, sizeof(int)*nentries); if (!(ccs->cptr && ccs->rix)) goto sparseccs_resize_error; ccs->ncols=ncols; ccs->nrows=nrows; ccs->nentries=nentries; return true; sparseccs_resize_error: sparseccs_clear(ccs); return false; } /** Retrieves the row indices given a column * @param[in] ccs the matrix * @param[in] col column index * @param[out] nentries the number of entries * @param[out] entries the entries themselves * @param[out] values (optional) the values */ bool sparseccs_getrowindiceswithvalues(sparseccs *ccs, int col, int *nentries, int **entries, double **values) { if (col>=ccs->ncols) return false; *nentries=ccs->cptr[col+1]-ccs->cptr[col]; *entries=ccs->rix+ccs->cptr[col]; if (values) *values=ccs->values+ccs->cptr[col]; return true; } /** Wrapper to sparseccs_getrowindiceswithvalues */ bool sparseccs_getrowindices(sparseccs *ccs, int col, int *nentries, int **entries) { return sparseccs_getrowindiceswithvalues(ccs,col,nentries,entries,NULL); } /** Sets the row indices given a column * @param[in] ccs the matrix * @param[in] col column index * @param[out] nentries the number of entries * @param[out] entries the entries themselves * @warning Use with caution */ bool sparseccs_setrowindices(sparseccs *ccs, int col, int nentries, int *entries) { if (col>=ccs->ncols) return false; if (nentries!=ccs->cptr[col+1]-ccs->cptr[col]) return false; int *e=ccs->rix+ccs->cptr[col]; for (unsigned int i=0; incols; i++) { if (ccs->cptr[i+1]!=ccs->cptr[i]) { if (entries && knentries; i++) { while (ccs->cptr[col+1]<=i) col++; if (ccs->rix[i]==row) { if (entries && kcptr[j]; kcptr[j+1]; k++) { if (ccs->rix[k]==i) { if (ccs->values) ccs->values[k]=val; return true; } } return false; } /** Retrieves a matrix element (i,j) from a sparseccs structure * @returns true on success. */ bool sparseccs_get(sparseccs *ccs, int i, int j, double *val) { int k; for (k=ccs->cptr[j]; kcptr[j+1]; k++) { if (ccs->rix[k]==i) { if (val) *val=(ccs->values ? ccs->values[k] : 1.0); return true; } } return false; } /** Helper function to compare unsigned integers */ static int sparseccs_compareuint(const void * a, const void * b) { long int i=*(int*)a, j=*(int*)b; return (int) (i-j); } /** Converts a DOK matrix to a CCS matrix */ bool sparseccs_doktoccs(sparsedok *in, sparseccs *out, bool copyvals) { int nentries=in->dict.count; sparseccs_init(out); if (!sparseccs_resize(out, in->nrows, in->ncols, nentries, copyvals)) return false; /* Clear the column pointer array */ for (int i=0; incols+1; i++) out->cptr[i]=0; /* Count number of entries per column */ for (unsigned int i=0; idict.capacity; i++) { value key=in->dict.contents[i].key; if (MORPHO_ISDOKKEY(key)) out->cptr[MORPHO_GETDOKCOLWVAL(key)]++; } /* Construct the column pointer array */ unsigned int ptr=0; for (int i=0; incols+1; i++) { int p=ptr; ptr+=out->cptr[i]; out->cptr[i]=p; } /* Clear the row index array */ for (int i=0; irix[i]=-1; /* Copy entries into appropriate rowindex */ for (unsigned int i=0; idict.capacity; i++) { value key=in->dict.contents[i].key; if (MORPHO_ISDOKKEY(key)) { int k=out->cptr[MORPHO_GETDOKCOLWVAL(key)]; while (out->rix[k]!=-1) k++; out->rix[k]=MORPHO_GETDOKROWWVAL(key); } } /* Sort columns */ for (int i=0; incols; i++) { int len=out->cptr[i+1]-out->cptr[i]; if (len>1) { qsort(out->rix+out->cptr[i], len, sizeof(int), sparseccs_compareuint); } } /* Copy over values */ if (copyvals) { for (int i=0; ivalues[i]=0.0; for (int j=0; jncols; j++) { int len=out->cptr[j+1]-out->cptr[j]; for (int i=0; irix[out->cptr[j]+i], j, &val)) { if (MORPHO_ISFLOAT(val)) out->values[out->cptr[j]+i]=MORPHO_GETFLOATVALUE(val); else if (MORPHO_ISINTEGER(val)) out->values[out->cptr[j]+i]=MORPHO_GETINTEGERVALUE(val); } } } } return true; } /** Prints a sparsedok matrix */ void sparseccs_print(vm *v, sparseccs *ccs) { double val; for (int i=0; inrows; i++) { morpho_printf(v, "[ "); for (int j=0; jncols; j++) { if (sparseccs_get(ccs, i, j, &val)) morpho_printf(v, "%g ", val); else morpho_printf(v, "0 "); } morpho_printf(v, "]%s", (inrows-1 ? "\n" : "")); } } /** Number of entries in a sparseccs */ unsigned int sparseccs_count(sparseccs *ccs) { return ccs->nentries; } /** Copies one sparseccs matrix to another, reallocating as necessary */ bool sparseccs_copy(sparseccs *src, sparseccs *dest) { bool success=false; if (sparseccs_resize(dest, src->nrows, src->ncols, src->nentries, src->values)) { memcpy(dest->cptr, src->cptr, sizeof(int)*(src->ncols+1)); memcpy(dest->rix, src->rix, sizeof(int)*(src->nentries)); if (src->values) memcpy(dest->values, src->values, sizeof(double)*src->nentries); success=true; } return success; } /** Copies a sparseccs matrix into a dok matrix at offset i0, j0 */ bool sparseccs_copytodok(sparseccs *src, sparsedok *dest, int row0, int col0) { for (int i=0, k=0; incols; i++) { // Loop over columns int nentries, *entries; if (!sparseccs_getrowindices(src, i, &nentries, &entries)) return false; for (int j=0; jvalues[k]))) return false; k++; } } return true; } /** Copies a sparseccs matrix into a dense matrix at offset i0, j0 */ bool sparseccs_copytomatrix(sparseccs *src, objectmatrix *dest, int row0, int col0) { for (int i=0, k=0; incols; i++) { // Loop over columns int nentries, *entries; if (!sparseccs_getrowindices(src, i, &nentries, &entries)) return false; for (int j=0; jvalues[k])) return false; k++; } } return true; } /* *************************************** * Object sparse interface * *************************************** */ /** Checks whether a format is available. * @param sparse the matrix to check * @param format format to check * @param force if format is unavailable, try to make it available * @param copyvals copy values across * @returns true if the format is available */ bool sparse_checkformat(objectsparse *sparse, objectsparseformat format, bool force, bool copyvals) { bool available=false; switch (format) { case SPARSE_DOK: available=(sparse->dok.ncols>0 && sparse->dok.nrows>0)||(sparse->dok.dict.count>0); break; case SPARSE_CCS: if (force && !sparse->ccs.cptr) { available=sparseccs_doktoccs(&sparse->dok, &sparse->ccs, copyvals); } else available=(sparse->ccs.cptr); } return available; } /** Removes data structures for a given format */ void sparse_removeformat(objectsparse *s, objectsparseformat format) { if (format==SPARSE_DOK) { sparsedok_clear(&s->dok); } else { sparseccs_clear(&s->ccs); } } /* *************************************** * objectsparse definition * *************************************** */ objecttype objectsparsetype; /** Sparse object definitions */ void objectsparse_printfn(object *obj, void *v) { morpho_printf(v, ""); } void objectsparse_markfn(object *obj, void *v) { objectsparse *c = (objectsparse *) obj; morpho_markdictionary(v, &c->dok.dict);} void objectsparse_freefn(object *obj) { objectsparse *s = (objectsparse *) obj; sparse_clear(s); } size_t objectsparse_sizefn(object *obj) { return sparse_size((objectsparse *) obj); } objecttypedefn objectsparsedefn = { .printfn=objectsparse_printfn, .markfn=objectsparse_markfn, .freefn=objectsparse_freefn, .sizefn=objectsparse_sizefn, .hashfn=NULL, .cmpfn=NULL }; /* *************************************** * objectsparse objects * *************************************** */ /** Creates a sparse matrix object * @param[in] nrows } Optional number of rows and columns * @param[in] ncols } */ objectsparse *object_newsparse(int *nrows, int *ncols) { objectsparse *new = (objectsparse *) object_new(sizeof(objectsparse), OBJECT_SPARSE); if (new) { sparsedok_init(&new->dok); sparseccs_init(&new->ccs); if (nrows) sparsedok_setdimensions(&new->dok, *nrows, *ncols); } return new; } /* ******************************* * Concatenate matrices * ******************************* */ /** Checks if the contents of dim match check; if *dim hasn't been set it is updated to match check */ bool sparse_checkupdatedimension(int check, int *dim) { if (*dim<0) *dim=check; if (*dim!=check) return false; return true; } /** Checks the dimensions of a matrix of matrices to be concatenated */ objectsparseerror sparse_catcheckdimensions(objectlist *in, int ndim, unsigned int *dim, int *ncols, int *nrows) { for (unsigned int i=0; inrows, &nrows[i]) && sparse_checkupdatedimension(matrix->ncols, &ncols[j]))) return SPARSE_INCMPTBLDIM; } else if (!MORPHO_ISINTEGER(val)) { return SPARSE_INVLDINIT; } } } } return SPARSE_OK; } typedef bool (*sparse_catcopyfn) (void *out, value val, int irow, int icol); /* Copy sparse matrix entries across */ bool sparse_catcopysparsetosparseat(objectsparse *src, int row0, int col0, objectsparse *dest) { if (sparse_checkformat(src, SPARSE_CCS, false, false)) { return sparseccs_copytodok(&src->ccs, &dest->dok, row0, col0); } else { return sparsedok_copyat(&src->dok, &dest->dok, row0, col0); } return false; } /* Copy sparse matrix entries across */ bool sparse_catcopysparsetomatrixat(objectsparse *src, int row0, int col0, objectmatrix *dest) { if (sparse_checkformat(src, SPARSE_CCS, false, false)) { return sparseccs_copytomatrix(&src->ccs, dest, row0, col0); } else { return sparsedok_copytomatrix(&src->dok, dest, row0, col0); } return false; } /* Copies a single entry in the cat matrix */ bool sparse_catcopyentry(void *out, value val, int irow, int icol) { objectsparse *dest = out; if (MORPHO_ISSPARSE(val)) { objectsparse *sparse = MORPHO_GETSPARSE(val); sparse_catcopysparsetosparseat(sparse, irow, icol, dest); } else if (MORPHO_ISMATRIX(val)) { objectmatrix *matrix = MORPHO_GETMATRIX(val); sparsedok_copymatrixat(matrix, &dest->dok, irow, icol); } else if (MORPHO_ISINTEGER(val)) { } return true; } /* Copies a single entry in the cat matrix */ bool matrix_catcopyentry(void *out, value val, int irow, int icol) { objectmatrix *dest = out; if (MORPHO_ISSPARSE(val)) { objectsparse *sparse = MORPHO_GETSPARSE(val); if (sparse_catcopysparsetomatrixat(sparse, irow, icol, dest)!=SPARSE_OK) return false; } else if (MORPHO_ISMATRIX(val)) { objectmatrix *matrix = MORPHO_GETMATRIX(val); if (matrix_copyat(matrix, dest, irow, icol)!=MATRIX_OK) return false; } else if (MORPHO_ISINTEGER(val)) { } return true; } /** Sparse matrix concatenation Call with dest=NULL to get size information in outrows and outcols */ objectsparseerror sparse_docat(objectlist *in, void *dest, sparse_catcopyfn copyfn, int *outrows, int *outcols) { unsigned int dim[2] = {0,0}, ndim; if (!matrix_getlistdimensions(in, dim, 2, &ndim) || ndim!=2) return SPARSE_INVLDINIT; /* Keep track of rows and columns of the matrix */ int nrows[dim[0]], ncols[dim[1]]; objectsparseerror err = sparse_catcheckdimensions(in, ndim, dim, ncols, nrows); if (err!=SPARSE_OK) return err; if (outrows) { *outrows=0; for (int i=0; i0) icol+=ncols[j]; } irow+=nrows[i]; } return SPARSE_OK; } /** Veneer onto sparse_docat for sparse matrices */ objectsparseerror sparse_cat(objectlist *in, objectsparse *dest) { return sparse_docat(in, dest, sparse_catcopyentry, &dest->dok.nrows, &dest->dok.ncols); } /** Veneer onto sparse_docat for dense matrices. Allocates a dense matrix of the correct size */ objectsparseerror sparse_catmatrix(objectlist *in, objectmatrix **out) { int nrows, ncols; objectmatrix *new = NULL; objectsparseerror err=sparse_docat(in, NULL, matrix_catcopyentry, &nrows, &ncols); if (err!=SPARSE_OK) goto sparse_catmatrix_error; new = object_newmatrix(nrows, ncols, true); err=sparse_docat(in, new, matrix_catcopyentry, NULL, NULL); if (err==SPARSE_OK) *out = new; return err; sparse_catmatrix_error: if (new) object_free((object *) new); return err; } /* ******************************* * Construct sparse matrices * ******************************* */ /** Create a sparse array from an array */ objectsparse *object_sparsefromarray(objectarray *array) { unsigned int dim[2] = {0,0}, ndim; if (!matrix_getarraydimensions(array, dim, 2, &ndim)) return NULL; objectsparse *new=object_newsparse(NULL, NULL); for (unsigned int i=0; idok, MORPHO_GETINTEGERVALUE(v[0]), MORPHO_GETINTEGERVALUE(v[1]), v[2]); } else { sparse_clear(new); MORPHO_FREE(new); return false; } } return new; } /** Create a sparse array from a list */ objectsparseerror object_sparsefromlist(objectlist *list, objectsparse **out) { unsigned int dim[2] = {0,0}, ndim; objectsparseerror err=SPARSE_OK; if (!matrix_getlistdimensions(list, dim, 2, &ndim)) return SPARSE_INVLDINIT; objectsparse *new=object_newsparse(NULL, NULL); if (dim[0]>0 && dim[1]!=3) { // If this isn't a list of entries, it may be a concatenation operation err=sparse_cat(list, new); if (err==SPARSE_OK) goto object_sparsefromlist_succeeded; goto object_sparsefromlist_cleanup; } for (unsigned int i=0; idok, MORPHO_GETINTEGERVALUE(v[0]), MORPHO_GETINTEGERVALUE(v[1]), v[2]); } else { sparse_clear(new); err=sparse_cat(list, new); if (err==SPARSE_OK) goto object_sparsefromlist_succeeded; goto object_sparsefromlist_cleanup; } } object_sparsefromlist_succeeded: *out = new; return err; object_sparsefromlist_cleanup: if (new) { sparse_clear(new); MORPHO_FREE(new); } return err; } /** Convert a sparse matrix to a dense matrix */ objectsparseerror sparse_tomatrix(objectsparse *in, objectmatrix **out) { objectsparseerror err = SPARSE_FAILED; objectmatrix *new = NULL; if (sparse_checkformat(in, SPARSE_CCS, false, false)) { new=object_newmatrix(in->ccs.nrows, in->ccs.ncols, true); if (!new) return SPARSE_FAILED; if (sparseccs_copytomatrix(&in->ccs, new, 0, 0)) err=SPARSE_OK; } else if (sparse_checkformat(in, SPARSE_DOK, false, false)) { new=object_newmatrix(in->dok.nrows, in->dok.ncols, true); if (!new) return SPARSE_FAILED; if (sparsedok_copytomatrix(&in->dok, new, 0, 0)) err=SPARSE_OK; } // Clean up and return if (err==SPARSE_OK) { *out=new; } else if (new) object_free((object *) new); return err; } /** Clones a sparse matrix */ objectsparse *sparse_clone(objectsparse *s) { objectsparse *new = object_newsparse(NULL, NULL); if (new) { sparsedok_copy(&s->dok, &new->dok); sparseccs_copy(&s->ccs, &new->ccs); } return new; } /** Gets the dimension sof a sparse matrix */ void sparse_getdimensions(objectsparse *s, int *nrows, int *ncols) { if (s->ccs.ncols>0) { if (nrows) *nrows=s->ccs.nrows; if (ncols) *ncols=s->ccs.ncols; } else { if (nrows) *nrows=s->dok.nrows; if (ncols) *ncols=s->dok.ncols; } } /** Set an element */ bool sparse_setelement(objectsparse *s, int row, int col, value val) { if (sparse_checkformat(s, SPARSE_CCS, false, false)) { if (!sparseccs_copytodok(&s->ccs, &s->dok, 0, 0)) return false; sparse_removeformat(s, SPARSE_CCS); } if (sparsedok_insert(&s->dok, row, col, val)) return true; return false; } /** Get an element * @param[in] s the sparse object * @param[in] row the row * @param[in] col the column * @param[out] val the value; pass NULL to check if an element exists */ bool sparse_getelement(objectsparse *s, int row, int col, value *val) { if (sparse_checkformat(s, SPARSE_DOK, false, false)) { return sparsedok_get(&s->dok, row, col, val); } else if (sparse_checkformat(s, SPARSE_CCS, false, false)) { double v; if (sparseccs_get(&s->ccs, row, col, &v)) { if (val) *val = MORPHO_FLOAT(v); } } return false; } /** Enumerate values in a sparse matrix */ bool sparse_enumerate(objectsparse *s, int i, value *out) { if (sparse_checkformat(s, SPARSE_CCS, false, false)) { if (i<0) { *out=MORPHO_INTEGER(s->ccs.nentries); return true; } if (iccs.nentries) { *out=MORPHO_FLOAT(s->ccs.values[i]); return true; } } else if (sparse_checkformat(s, SPARSE_DOK, false, false)) { if (i<0) { *out=MORPHO_INTEGER(s->dok.dict.count); return true; } if (idok.dict.count) { objectdokkey *key = s->dok.keys; for (int k=0; kobj.next; if (key) return dictionary_get(&s->dok.dict, MORPHO_OBJECT(key), out); } } return false; } /** Add two matrices * @param[in] a - sparse matrix * @param[in] b - sparse matrix * @param[in] alpha - scale for a * @param[in] beta - scale for b * @param[out] out - alpha*a+beta*b. */ objectsparseerror sparse_add(objectsparse *a, objectsparse *b, double alpha, double beta, objectsparse *out) { if (!(sparse_checkformat(a, SPARSE_CCS, true, true) && sparse_checkformat(b, SPARSE_CCS, true, true)) ) return SPARSE_CONVFAILED; if (a->ccs.ncols!=b->ccs.ncols || a->ccs.nrows != b->ccs.nrows) return SPARSE_INCMPTBLDIM; sparsedok_clear(&out->dok); sparseccs_clear(&out->ccs); #ifdef MORPHO_LINALG_USE_CSPARSE cs A, B; sparse_ccstocsparse(&a->ccs, &A); sparse_ccstocsparse(&b->ccs, &B); cs *ret=cs_add(&A, &B, alpha, beta); if (ret) { sparse_csparsetoccs(ret, &out->ccs); cs_free(ret); return SPARSE_OK; } #endif return SPARSE_FAILED; } /** Multiply two matrices * @param[in] a - sparse matrix * @param[in] b - sparse matrix * @param[out] out - a*b. */ objectsparseerror sparse_mul(objectsparse *a, objectsparse *b, objectsparse *out) { if (!(sparse_checkformat(a, SPARSE_CCS, true, true) && sparse_checkformat(b, SPARSE_CCS, true, true)) ) return SPARSE_CONVFAILED; if (a->ccs.ncols!=b->ccs.nrows) return SPARSE_INCMPTBLDIM; sparsedok_clear(&out->dok); sparseccs_clear(&out->ccs); #ifdef MORPHO_LINALG_USE_CSPARSE cs A, B; sparse_ccstocsparse(&a->ccs, &A); sparse_ccstocsparse(&b->ccs, &B); cs *ret=cs_multiply(&A, &B); if (ret) { sparse_csparsetoccs(ret, &out->ccs); cs_free(ret); return SPARSE_OK; } #endif return SPARSE_FAILED; } /** Multiply a sparse matrix a by a dense matrix b: out -> out + a*b * @param[in] a - sparse matrix * @param[in] b - dense matrix * @param[out] out - out + a*b. */ objectsparseerror sparse_mulsxd(objectsparse *a, objectmatrix *b, objectmatrix *out) { if (a->ccs.ncols!=b->nrows) return SPARSE_INCMPTBLDIM; #ifdef MORPHO_LINALG_USE_CSPARSE cs A; sparse_ccstocsparse(&a->ccs, &A); for (int i=0; incols; i++) { cs_gaxpy(&A, b->elements+i*b->nrows, out->elements+i*b->nrows); } return SPARSE_OK; #endif return SPARSE_FAILED; } /** Multiply a dense matrix a by a sparse matrix b: out -> out + a*b * @param[in] a - dense matrix * @param[in] b - sparse matrix * @param[out] out - out + a*b. */ objectsparseerror sparse_muldxs(objectmatrix *a, objectsparse *b, objectmatrix *out) { if (!(sparse_checkformat(b, SPARSE_CCS, true, true))) return SPARSE_CONVFAILED; if (a->ncols!=b->ccs.nrows) return SPARSE_INCMPTBLDIM; for (unsigned int row=0; rownrows; row++) { for (unsigned int col=0; colccs.nrows; col++) { double val, *svalues; int nentries, *entries; matrix_getelement(out, row, col, &val); sparseccs_getrowindiceswithvalues(&b->ccs, col, &nentries, &entries, &svalues); for (int i=0; idok); sparseccs_clear(&out->ccs); if (!sparseccs_copy(&src->ccs, &out->ccs)) return SPARSE_FAILED; cblas_dscal(out->ccs.nentries, scale, out->ccs.values, 1); return SPARSE_OK; } /** Solve a linear system a.x = b * @param[in] a - sparse matrix * @param[in] b - dense rhs (may have more than one column) * @param[out] out - Solution to a.x = b. */ objectsparseerror sparse_div(objectsparse *a, objectmatrix *b, objectmatrix *out) { if (!(sparse_checkformat(a, SPARSE_CCS, true, true))) return SPARSE_CONVFAILED; if (a->ccs.ncols!=b->nrows || b->nrows!=out->nrows || b->ncols!=out->ncols) return SPARSE_INCMPTBLDIM; if (b!=out) cblas_dcopy(b->ncols * b->nrows, b->elements, 1, out->elements, 1); #ifdef MORPHO_LINALG_USE_CSPARSE cs A; sparse_ccstocsparse(&a->ccs, &A); int ret=false; if (a->ccs.ncols==a->ccs.nrows) { ret=cs_lusol(0, &A, out->elements, MORPHO_EPS); } else { ret=cs_qrsol(0, &A, out->elements); } if (ret) return SPARSE_OK; #endif return SPARSE_FAILED; } /** Transpose a sparse matrix * @param[in] a - sparse matrix * @param[out] out - transpose(A). */ objectsparseerror sparse_transpose(objectsparse *a, objectsparse *out) { if (!(sparse_checkformat(a, SPARSE_CCS, true, true)) ) return SPARSE_CONVFAILED; sparsedok_clear(&out->dok); sparseccs_clear(&out->ccs); #ifdef MORPHO_LINALG_USE_CSPARSE cs A; sparse_ccstocsparse(&a->ccs, &A); cs *ret=cs_transpose(&A, true); if (ret) { sparse_csparsetoccs(ret, &out->ccs); cs_free(ret); return SPARSE_OK; } #endif return SPARSE_FAILED; } /** Clears any data attached to a sparse matrix */ void sparse_clear(objectsparse *a) { sparsedok_clear(&a->dok); sparseccs_clear(&a->ccs); } /** Calculate the size of a sparse matrix structure */ size_t sparse_size(objectsparse *a) { return sizeof(objectsparse)+ a->dok.dict.capacity*sizeof(dictionaryentry) + sizeof(int)*(a->ccs.ncols+1) + sizeof(int)*(a->ccs.nentries) + ( a->ccs.values ? sizeof(double)*(a->ccs.nentries) : 0); } /* *************************************** * Sparse builtin class * *************************************** */ void sparse_raiseerror(vm *v, objectsparseerror err) { switch(err) { case SPARSE_OK: break; case SPARSE_INCMPTBLDIM: morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); break; case SPARSE_CONVFAILED: morpho_runtimeerror(v, SPARSE_CONVFAILEDERR); break; case SPARSE_FAILED: morpho_runtimeerror(v, SPARSE_OPFAILEDERR); break; case SPARSE_INVLDINIT: morpho_runtimeerror(v, SPARSE_INVLDARRAYINIT); break; } } /** Constructs a Sparse object */ value sparse_constructor(vm *v, int nargs, value *args) { int nrows, ncols; objectsparse *new=NULL; value out=MORPHO_NIL; if ( nargs==2 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0)) && MORPHO_ISINTEGER(MORPHO_GETARG(args, 1)) ) { nrows = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); ncols = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 1)); new=object_newsparse(&nrows, &ncols); } else if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { nrows = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); ncols = 1; new=object_newsparse(&nrows, &ncols); } else if (nargs==1 && MORPHO_ISARRAY(MORPHO_GETARG(args, 0))) { new=object_sparsefromarray(MORPHO_GETARRAY(MORPHO_GETARG(args, 0))); if (!new) morpho_runtimeerror(v, SPARSE_INVLDARRAYINIT); } else if (nargs==1 && MORPHO_ISLIST(MORPHO_GETARG(args, 0))) { objectsparseerror err = object_sparsefromlist(MORPHO_GETLIST(MORPHO_GETARG(args, 0)), &new); if (!new) sparse_raiseerror(v, err); } else if (nargs==0) { new = object_newsparse(NULL, NULL); } else { morpho_runtimeerror(v, SPARSE_CONSTRUCTOR); } if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Retrieve a matrix element */ value Sparse_getindex(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); unsigned int indx[2]={0,0}; value out = MORPHO_FLOAT(0.0); if (array_valuelisttoindices(nargs, args+1, indx)) { sparse_getelement(s, indx[0], indx[1], &out); } else morpho_runtimeerror(v, MATRIX_INVLDINDICES); return out; } /** Set a matrix element */ value Sparse_setindex(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); unsigned int indx[2]={0,0}; if (array_valuelisttoindices(nargs-1, args+1, indx)) { size_t osize = sparse_size(s); if (!sparse_setelement(s, indx[0], indx[1], args[nargs])) { morpho_runtimeerror(v, SPARSE_SETFAILED); } size_t nsize = sparse_size(s); if (osize!=nsize) { morpho_resizeobject(v, (object *) s, osize, nsize); } } else morpho_runtimeerror(v, MATRIX_INVLDINDICES); return MORPHO_NIL; } /** Enumerate protocol */ value Sparse_enumerate(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1) { if (MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { int i=MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); sparse_enumerate(s, i, &out); } } return out; } /** Print a sparse matrix */ value Sparse_print(vm *v, int nargs, value *args) { value self = MORPHO_SELF(args); if (!MORPHO_ISSPARSE(self)) return Object_print(v, nargs, args); objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); if (sparse_checkformat(s, SPARSE_CCS, false, false)) { sparseccs_print(v, &s->ccs); } else if (sparse_checkformat(s, SPARSE_DOK, false, false)) { sparsedok_print(v, &s->dok); } return MORPHO_NIL; } /** Add two sparse matrices */ value Sparse_add(vm *v, int nargs, value *args) { objectsparse *a=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { objectsparse *b=MORPHO_GETSPARSE(MORPHO_GETARG(args, 0)); objectsparse *new = object_newsparse(NULL, NULL); if (new) { size_t asize=sparse_size(a), bsize=sparse_size(b); objectsparseerror err =sparse_add(a, b, 1.0, 1.0, new); morpho_resizeobject(v, (object *) a, asize, sparse_size(a)); morpho_resizeobject(v, (object *) b, bsize, sparse_size(b)); if (err==SPARSE_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { morpho_freeobject(MORPHO_OBJECT(new)); sparse_raiseerror(v, err); } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } return out; } /** Subtract sparse matrices */ value Sparse_sub(vm *v, int nargs, value *args) { objectsparse *a=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { objectsparse *b=MORPHO_GETSPARSE(MORPHO_GETARG(args, 0)); objectsparse *new = object_newsparse(NULL, NULL); if (new) { size_t asize=sparse_size(a), bsize=sparse_size(b); objectsparseerror err =sparse_add(a, b, 1.0, -1.0, new); morpho_resizeobject(v, (object *) a, asize, sparse_size(a)); morpho_resizeobject(v, (object *) b, bsize, sparse_size(b)); if (err==SPARSE_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { sparse_raiseerror(v, err); } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } return out; } /** Multiply sparse matrices */ value Sparse_mul(vm *v, int nargs, value *args) { objectsparse *a=MORPHO_GETSPARSE(MORPHO_SELF(args)); size_t asize = sparse_size(a); objectsparse *new = NULL; value out=MORPHO_NIL; objectsparseerror err = SPARSE_OK; if (nargs==1) { if (MORPHO_ISSPARSE(MORPHO_GETARG(args, 0))) { objectsparse *b=MORPHO_GETSPARSE(MORPHO_GETARG(args, 0)); size_t bsize=sparse_size(b); new = object_newsparse(NULL, NULL); if (new) { err=sparse_mul(a, b, new); morpho_resizeobject(v, (object *) b, bsize, sparse_size(b)); // Check for size change } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else if (MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { if (sparse_checkformat(a, SPARSE_CCS, true, true)) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); objectmatrix *out=object_newmatrix(a->ccs.nrows, b->ncols, true); new = (objectsparse *) out; // Munge type to ensure binding/deallocation if (out) { err=sparse_mulsxd(a, b, out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else err=SPARSE_CONVFAILED; } else if (MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { double scale; if (!morpho_valuetofloat(MORPHO_GETARG(args, 0), &scale)) return MORPHO_NIL; new = object_newsparse(NULL, NULL); if (new) { err=sparse_scale(a, scale, new); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } } morpho_resizeobject(v, (object *) a, asize, sparse_size(a)); // In case we caused a size change if (err==SPARSE_OK && new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { sparse_raiseerror(v, err); if (new) object_free((object *) new); } return out; } /** Multiplication on the right */ value Sparse_mulr(vm *v, int nargs, value *args) { objectsparse *b=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; objectsparseerror err = SPARSE_OK; if (nargs==1) { if (MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *a=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); int ncols; sparse_getdimensions(b, NULL, &ncols); objectmatrix *new=object_newmatrix(a->nrows, ncols, true); if (new) { err=sparse_muldxs(a, b, new); if (err==SPARSE_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { sparse_raiseerror(v, err); if (new) object_free((object *) new); } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else if (MORPHO_ISNUMBER(MORPHO_GETARG(args, 0))) { return Sparse_mul(v, nargs, args); // Redirect to regular multiplication } } return out; } /** Sparse rhs not implemented */ value Sparse_div(vm *v, int nargs, value *args) { return MORPHO_NIL; } /** Solve a linear system b/ A where A is sparse */ value Sparse_divr(vm *v, int nargs, value *args) { objectsparse *a=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISMATRIX(MORPHO_GETARG(args, 0))) { objectmatrix *b=MORPHO_GETMATRIX(MORPHO_GETARG(args, 0)); objectmatrix *new = object_newmatrix(b->nrows, b->ncols, false); if (new) { size_t asize=sparse_size(a); objectsparseerror err =sparse_div(a, b, new); morpho_resizeobject(v, (object *) a, asize, sparse_size(a)); if (err==SPARSE_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { sparse_raiseerror(v, err); } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } return out; } /** Multiply sparse matrices */ value Sparse_transpose(vm *v, int nargs, value *args) { objectsparse *a=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; objectsparse *new = object_newsparse(NULL, NULL); if (new) { size_t asize=sparse_size(a); objectsparseerror err = sparse_transpose(a, new); morpho_resizeobject(v, (object *) a, asize, sparse_size(a)); if (err==SPARSE_OK) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else { sparse_raiseerror(v, err); } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Clone a sparse matrix */ value Sparse_clone(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out = MORPHO_NIL; objectsparse *new=sparse_clone(s); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } return out; } /** Count number of elements */ value Sparse_count(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out = MORPHO_INTEGER(0); if (sparse_checkformat(s, SPARSE_DOK, false, false)) { out=MORPHO_INTEGER(sparsedok_count(&s->dok)); } else if (sparse_checkformat(s, SPARSE_CCS, false, false)) { out=MORPHO_INTEGER(sparseccs_count(&s->ccs)); } return out; } /** Sparse dimensions */ value Sparse_dimensions(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value dim[2]; value out=MORPHO_NIL; int nrows, ncols; sparse_getdimensions(s, &nrows, &ncols); dim[0]=MORPHO_INTEGER(nrows); dim[1]=MORPHO_INTEGER(ncols); objectlist *new=object_newlist(2, dim); if (new) { out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return out; } /** Gets a column of a Sparse matrix */ value Sparse_getcolumn(vm *v, int nargs, value *args) { value out = MORPHO_NIL; objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { unsigned int col = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); if (!sparse_checkformat(s, SPARSE_CCS, true, true)) { morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); return MORPHO_NIL; } if (colccs.ncols) { int ncols=1, nentries=0, *entries=NULL; double *values; objectsparse *new=object_newsparse(&s->ccs.nrows, &ncols); if (new) { sparseccs_getrowindiceswithvalues(&s->ccs, col, &nentries, &entries, &values); if (nentries>0) { if (sparseccs_resize(&new->ccs, s->ccs.nrows, 1, nentries, true)) { new->ccs.cptr[0]=0; new->ccs.cptr[1]=nentries; for (int i=0; iccs.rix[i]=entries[i]; new->ccs.values[i]=values[i]; } } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } else morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } else morpho_runtimeerror(v, MATRIX_SETCOLARGS); return out; } /** Get the row indices given a column */ value Sparse_rowindices(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; if (nargs==1 && MORPHO_ISINTEGER(MORPHO_GETARG(args, 0))) { if (sparse_checkformat(s, SPARSE_CCS, true, true)) { int col = MORPHO_GETINTEGERVALUE(MORPHO_GETARG(args, 0)); int nentries=0, *entries=NULL; if (colccs.ncols) { if (sparseccs_getrowindices(&s->ccs, col, &nentries, &entries)) { objectlist *new = object_newlist(nentries, NULL); if (new) { for (int i=0; iccs.ncols) { for (int i=0; iccs, col, nentries, entries)) { morpho_runtimeerror(v, MATRIX_INCOMPATIBLEMATRICES); } } else morpho_runtimeerror(v, MATRIX_INDICESOUTSIDEBOUNDS); } } return out; } /** Get the column indices */ value Sparse_colindices(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; size_t ssize=sparse_size(s); if (sparse_checkformat(s, SPARSE_CCS, true, true)) { morpho_resizeobject(v, (object *) s, ssize, sparse_size(s)); int ncols=0; varray_int cols; varray_intinit(&cols); varray_intresize(&cols, s->ccs.ncols); if (sparseccs_getcolindices(&s->ccs, s->ccs.ncols, &ncols, cols.data)) { objectlist *new=object_newlist(ncols, NULL); if (new) { for (int i=0; ival.data[i]=MORPHO_INTEGER(cols.data[i]); new->val.count=ncols; out=MORPHO_OBJECT(new); morpho_bindobjects(v, 1, &out); } } varray_intclear(&cols); } return out; } /** Get a list of indices */ value Sparse_indices(vm *v, int nargs, value *args) { objectsparse *s=MORPHO_GETSPARSE(MORPHO_SELF(args)); value out=MORPHO_NIL; size_t ssize=sparse_size(s); if (sparse_checkformat(s, SPARSE_DOK, true, true)) { morpho_resizeobject(v, (object *) s, ssize, sparse_size(s)); objectlist *list=object_newlist(s->dok.dict.count, NULL); if (list) { for (objectdokkey *key=s->dok.keys; key!=NULL; key=(objectdokkey *) key->obj.next) { objectlist *entry=object_newlist(2, NULL); if (entry) { list_append(entry, MORPHO_INTEGER(key->row)); list_append(entry, MORPHO_INTEGER(key->col)); list_append(list, MORPHO_OBJECT(entry)); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } /* Temporarily append a self reference so everything is in one place to bind... */ list_append(list, MORPHO_OBJECT(list)); morpho_bindobjects(v, list->val.count, list->val.data); list->val.count--; // And pop it back off out = MORPHO_OBJECT(list); } else morpho_runtimeerror(v, ERROR_ALLOCATIONFAILED); } return out; } MORPHO_BEGINCLASS(Sparse) MORPHO_METHOD(MORPHO_GETINDEX_METHOD, Sparse_getindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SETINDEX_METHOD, Sparse_setindex, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ENUMERATE_METHOD, Sparse_enumerate, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_PRINT_METHOD, Sparse_print, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_ADD_METHOD, Sparse_add, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_SUB_METHOD, Sparse_sub, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MUL_METHOD, Sparse_mul, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_MULR_METHOD, Sparse_mulr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_DIVR_METHOD, Sparse_divr, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_TRANSPOSE_METHOD, Sparse_transpose, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_COUNT_METHOD, Sparse_count, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_DIMENSIONS_METHOD, Sparse_dimensions, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SPARSE_ROWINDICES_METHOD, Sparse_rowindices, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SPARSE_SETROWINDICES_METHOD, Sparse_setrowindices, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MATRIX_GETCOLUMN_METHOD, Sparse_getcolumn, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SPARSE_COLINDICES_METHOD, Sparse_colindices, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(MORPHO_CLONE_METHOD, Sparse_clone, BUILTIN_FLAGSEMPTY), MORPHO_METHOD(SPARSE_INDICES_METHOD, Sparse_indices, BUILTIN_FLAGSEMPTY) MORPHO_ENDCLASS /* *************************************** * Initialization * *************************************** */ void sparse_initialize(void) { objectdokkeytype=object_addtype(&objectdokkeydefn); objectsparsetype=object_addtype(&objectsparsedefn); builtin_addfunction(SPARSE_CLASSNAME, sparse_constructor, MORPHO_FN_CONSTRUCTOR); objectstring objname = MORPHO_STATICSTRING(OBJECT_CLASSNAME); value objclass = builtin_findclass(MORPHO_OBJECT(&objname)); value sparseclass=builtin_addclass(SPARSE_CLASSNAME, MORPHO_GETCLASSDEFINITION(Sparse), objclass); object_setveneerclass(OBJECT_SPARSE, sparseclass); morpho_defineerror(SPARSE_CONSTRUCTOR, ERROR_HALT, SPARSE_CONSTRUCTOR_MSG); morpho_defineerror(SPARSE_SETFAILED, ERROR_HALT, SPARSE_SETFAILED_MSG); morpho_defineerror(SPARSE_INVLDARRAYINIT, ERROR_HALT, SPARSE_INVLDARRAYINIT_MSG); morpho_defineerror(SPARSE_CONVFAILEDERR, ERROR_HALT, SPARSE_CONVFAILEDERR_MSG); morpho_defineerror(SPARSE_OPFAILEDERR, ERROR_HALT, SPARSE_OPFAILEDERR_MSG); //sparse_test(); } #endif ================================================ FILE: src/linalg/sparse.h ================================================ /** @file sparse.h * @author T J Atherton * * @brief Veneer class over the objectsparse type that provides sparse matrices */ #ifndef sparse_h #define sparse_h #include "build.h" #ifdef MORPHO_INCLUDE_SPARSE #include #include "object.h" #include "morpho.h" #include "matrix.h" /* ------------------------------------------------------- * Sparse objects * ------------------------------------------------------- */ extern objecttype objectdokkeytype; #define OBJECT_DOKKEY objectdokkeytype /** The dictionary of keys format uses this special object type to store indices, enabling use of the existing dictionary type. @warning These are for internal use only and should never be returned to user code */ typedef struct { object obj; unsigned int row; unsigned int col; } objectdokkey; /** Create */ #define MORPHO_STATICDOKKEY(i,j) { .obj.type=OBJECT_DOKKEY, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL, .row=i, .col=j } /** Tests whether an object is a dok key */ #define MORPHO_ISDOKKEY(val) object_istype(val, OBJECT_DOKKEY) /** Gets the object as a dok key */ #define MORPHO_GETDOKKEY(val) ((objectdokkey *) MORPHO_GETOBJECT(val)) /** Gets the row and column from a objectdokkey */ #define MORPHO_GETDOKKEYROW(objptr) ((unsigned int) (objptr)->row) #define MORPHO_GETDOKKEYCOL(objptr) ((unsigned int) (objptr)->col) #define MORPHO_GETDOKROWWVAL(val) ((unsigned int) (MORPHO_GETDOKKEY(val)->row)) #define MORPHO_GETDOKCOLWVAL(val) ((unsigned int) (MORPHO_GETDOKKEY(val)->col)) DECLARE_VARRAY(dokkey, objectdokkey); typedef struct { int nrows; int ncols; dictionary dict; objectdokkey *keys; } sparsedok; typedef struct { int nentries; int nrows; int ncols; int *cptr; // Pointers to column entries int *rix; // Row indices double *values; // Values } sparseccs; extern objecttype objectsparsetype; #define OBJECT_SPARSE objectsparsetype typedef struct { object obj; sparsedok dok; sparseccs ccs; } objectsparse; /** Tests whether an object is a sparse matrix */ #define MORPHO_ISSPARSE(val) object_istype(val, OBJECT_SPARSE) /** Gets the object as a sparse matrix */ #define MORPHO_GETSPARSE(val) ((objectsparse *) MORPHO_GETOBJECT(val)) /** @brief Use to create static sparse matrices on the C stack. Note that the entries should be initialized */ #define MORPHO_STATICSPARSE() { .obj.type=OBJECT_SPARSE, .obj.status=OBJECT_ISUNMANAGED, .obj.next=NULL } objectsparse *object_newsparse(int *nrows, int *ncols); objectsparse *sparse_sparsefromarray(objectarray *array); /* ------------------------------------------------------- * Sparse veneer class * ------------------------------------------------------- */ #define SPARSE_CLASSNAME "Sparse" #define SPARSE_ROWINDICES_METHOD "rowindices" #define SPARSE_SETROWINDICES_METHOD "setrowindices" #define SPARSE_COLINDICES_METHOD "colindices" #define SPARSE_INDICES_METHOD "indices" /* ------------------------------------------------------- * Sparse errors * ------------------------------------------------------- */ #define SPARSE_CONSTRUCTOR "SprsCns" #define SPARSE_CONSTRUCTOR_MSG "Sparse() should be called either with dimensions or an array initializer." #define SPARSE_SETFAILED "SprsSt" #define SPARSE_SETFAILED_MSG "Attempt to set sparse matrix element failed." #define SPARSE_INVLDARRAYINIT "SprsInvldInit" #define SPARSE_INVLDARRAYINIT_MSG "Invalid initializer passed to Sparse()." #define SPARSE_CONVFAILEDERR "SprsCnvFld" #define SPARSE_CONVFAILEDERR_MSG "Sparse format conversion failed." #define SPARSE_OPFAILEDERR "SprsOpFld" #define SPARSE_OPFAILEDERR_MSG "Sparse matrix operation failed." /* ------------------------------------------------------- * Sparse interface * ------------------------------------------------------- */ /* *************************************** * Dictionary of keys format * *************************************** */ void sparsedok_init(sparsedok *dok); void sparsedok_clear(sparsedok *dok); bool sparsedok_insert(sparsedok *dok, int i, int j, value val); bool sparsedok_get(sparsedok *dok, int i, int j, value *val); bool sparsedok_remove(sparsedok *dok, int i, int j, value *val); bool sparsedok_setdimensions(sparsedok *dok, int nrows, int ncols); unsigned int sparsedok_count(sparsedok *dok); void *sparsedok_loopstart(sparsedok *dok); bool sparsedok_loop(sparsedok *dok, void **cntr, int *i, int *j); bool sparsedok_copy(sparsedok *src, sparsedok *dest); bool sparsedok_copyat(sparsedok *src, sparsedok *dest, int row0, int col0); bool sparsedok_copymatrixat(objectmatrix *src, sparsedok *dest, int row0, int col0); bool sparsedok_copytomatrix(sparsedok *src, objectmatrix *dest, int row0, int col0); void sparsedok_print(vm *v, sparsedok *dok); /* *************************************** * Compressed Column Storage Format * *************************************** */ void sparseccs_init(sparseccs *ccs); void sparseccs_clear(sparseccs *ccs); bool sparseccs_resize(sparseccs *ccs, int nrows, int ncols, unsigned int nentries, bool values); bool sparseccs_get(sparseccs *ccs, int i, int j, double *val); bool sparseccs_getrowindices(sparseccs *ccs, int col, int *nentries, int **entries); bool sparseccs_getrowindiceswithvalues(sparseccs *ccs, int col, int *nentries, int **entries, double **vals); bool sparseccs_setrowindices(sparseccs *ccs, int col, int nentries, int *entries); bool sparseccs_getcolindices(sparseccs *ccs, int maxentries, int *nentries, int *entries); bool sparseccs_getcolindicesforrow(sparseccs *ccs, int row, int maxentries, int *nentries, int *entries); bool sparseccs_doktoccs(sparsedok *in, sparseccs *out, bool copyvals); bool sparseccs_copy(sparseccs *src, sparseccs *dest); bool sparseccs_copytodok(sparseccs *src, sparsedok *dest, int row0, int col0); bool sparseccs_copytomatrix(sparseccs *src, objectmatrix *dest, int row0, int col0); void sparseccs_print(vm *v, sparseccs *ccs); typedef enum { SPARSE_DOK, SPARSE_CCS } objectsparseformat; typedef enum { SPARSE_OK, SPARSE_INCMPTBLDIM, SPARSE_INVLDINIT, SPARSE_CONVFAILED, SPARSE_FAILED } objectsparseerror; /* *************************************** * Generic sparse functions * *************************************** */ void sparse_raiseerror(vm *v, objectsparseerror err); bool sparse_checkformat(objectsparse *sparse, objectsparseformat format, bool force, bool copyvals); objectsparseerror sparse_tomatrix(objectsparse *in, objectmatrix **out); objectsparse *sparse_clone(objectsparse *s); bool sparse_setelement(objectsparse *matrix, int row, int col, value value); bool sparse_getelement(objectsparse *matrix, int row, int col, value *value); void sparse_getdimensions(objectsparse *s, int *nrows, int *ncols); objectsparseerror sparse_add(objectsparse *a, objectsparse *b, double alpha, double beta, objectsparse *out); objectsparseerror sparse_mul(objectsparse *a, objectsparse *b, objectsparse *out); objectsparseerror sparse_mulsxd(objectsparse *a, objectmatrix *b, objectmatrix *out); objectsparseerror sparse_muldxs(objectmatrix *a, objectsparse *b, objectmatrix *out); objectsparseerror sparse_transpose(objectsparse *a, objectsparse *out); void sparse_clear(objectsparse *a); size_t sparse_size(objectsparse *a); objectsparseerror sparse_cat(objectlist *in, objectsparse *dest); objectsparseerror sparse_catmatrix(objectlist *in, objectmatrix **out); /* *************************************** * Sparse class methods * *************************************** */ value Sparse_divr(vm *v, int nargs, value *args); /** Intialization */ void sparse_initialize(void); #endif #endif /* sparse_h */ ================================================ FILE: src/morpho.h ================================================ /** @file morpho.h * @author T J Atherton * * @brief Define public interface to Morpho */ #ifndef morpho_h #define morpho_h #include "build.h" #include "value.h" #include "error.h" #include "dictionary.h" #include "version.h" /* ********************************************************************** * VM types * ********************************************************************** */ #ifndef MORPHO_CORE typedef void vm; typedef void program; typedef void compiler; #endif /* ********************************************************************** * Standard methods * ********************************************************************** */ #define MORPHO_INITIALIZER_METHOD "init" #define MORPHO_GETINDEX_METHOD "index" #define MORPHO_SETINDEX_METHOD "setindex" #define MORPHO_TOSTRING_METHOD "tostring" #define MORPHO_FORMAT_METHOD "format" #define MORPHO_ASSIGN_METHOD "assign" #define MORPHO_ADD_METHOD "add" #define MORPHO_ADDR_METHOD "addr" #define MORPHO_SUB_METHOD "sub" #define MORPHO_SUBR_METHOD "subr" #define MORPHO_MUL_METHOD "mul" #define MORPHO_MULR_METHOD "mulr" #define MORPHO_DIV_METHOD "div" #define MORPHO_DIVR_METHOD "divr" #define MORPHO_POW_METHOD "pow" #define MORPHO_POWR_METHOD "powr" #define MORPHO_ACC_METHOD "acc" #define MORPHO_SUM_METHOD "sum" #define MORPHO_CONTAINS_METHOD "contains" #define MORPHO_UNION_METHOD "union" #define MORPHO_INTERSECTION_METHOD "intersection" #define MORPHO_DIFFERENCE_METHOD "difference" #define MORPHO_ROLL_METHOD "roll" #define MORPHO_JOIN_METHOD "join" #define MORPHO_CLASS_METHOD "clss" #define MORPHO_SUPER_METHOD "superclass" #define MORPHO_SERIALIZE_METHOD "serialize" #define MORPHO_HAS_METHOD "has" #define MORPHO_RESPONDSTO_METHOD "respondsto" #define MORPHO_INVOKE_METHOD "invoke" #define MORPHO_CLONE_METHOD "clone" #define MORPHO_ENUMERATE_METHOD "enumerate" #define MORPHO_COUNT_METHOD "count" #define MORPHO_CLONE_METHOD "clone" #define MORPHO_PRINT_METHOD "prnt" #define MORPHO_SAVE_METHOD "save" /* Non-standard methods */ #define MORPHO_APPEND_METHOD "append" #define MORPHO_LINEARIZATION_METHOD "linearization" #define MORPHO_THROW_METHOD "throw" #define MORPHO_WARNING_METHOD "warning" extern value initselector; extern value indexselector; extern value setindexselector; extern value addselector; extern value subselector; extern value mulselector; extern value divselector; extern value printselector; extern value enumerateselector; extern value countselector; extern value cloneselector; /* ********************************************************************** * Public interfaces * ********************************************************************** */ /* Version checking */ void morpho_version(version *v); /* Error handling */ void morpho_writeerrorwithid(error *err, errorid id, char *file, int line, int posn, ...); void morpho_defineerror(errorid id, errorcategory cat, char *message); errorid morpho_geterrorid(error *err); /* Programs */ program *morpho_newprogram(void); void morpho_freeprogram(program *p); /* Optimizers */ typedef bool (optimizerfn) (program *in); void morpho_setoptimizer(optimizerfn *optimizer); /* Virtual machine */ vm *morpho_newvm(void); void morpho_freevm(vm *v); /* Bind new objects to the virtual machine */ void morpho_bindobjects(vm *v, int nobj, value *obj); value morpho_wrapandbind(vm *v, object *obj); /* Interact with the garbage collector in an object definition */ void morpho_markobject(void *v, object *obj); void morpho_markvalue(void *v, value val); void morpho_markvarrayvalue(void *v, varray_value *array); void morpho_markdictionary(void *v, dictionary *dict); void morpho_searchunmanagedobject(void *v, object *obj); bool morpho_ismanagedobject(object *obj); /* Tell the VM that the size of an object has changed */ void morpho_resizeobject(vm *v, object *obj, size_t oldsize, size_t newsize); /* Temporarily retain objects across multiple calls into the VM */ int morpho_retainobjects(vm *v, int nobj, value *obj); void morpho_releaseobjects(vm *v, int handle); /* Raise runtime errors and warnings */ void morpho_warning(vm *v, error *err); void morpho_error(vm *v, error *err); void morpho_runtimeerror(vm *v, errorid id, ...); void morpho_runtimewarning(vm *v, errorid id, ...); /* Compilation */ compiler *morpho_newcompiler(program *out); void morpho_freecompiler(compiler *c); bool morpho_compile(char *in, compiler *c, bool optimize, error *err); const char *morpho_compilerrestartpoint(compiler *c); void morpho_resetentry(program *p); /* Interpreting */ bool morpho_run(vm *v, program *p); bool morpho_profile(vm *v, program *p); bool morpho_debug(vm *v, program *p); bool morpho_lookupmethod(value obj, value label, value *method); bool morpho_countparameters(value f, int *nparams); bool morpho_call(vm *v, value fn, int nargs, value *args, value *ret); bool morpho_invoke(vm *v, value obj, value method, int nargs, value *args, value *ret); error *morpho_geterror(vm *v); /* I/O */ int morpho_printf(vm *v, char *format, ...); void morpho_printvalue(vm *v, value val); int morpho_readline(vm *v, varray_char *buffer); /* Stack trace */ void morpho_stacktrace(vm *v); /* Disassembler */ void morpho_disassemble(vm *v, program *code, int *matchline); /* Multithreading */ void morpho_setthreadnumber(int nthreads); int morpho_threadnumber(void); /* Initialization and finalization */ typedef void (*morpho_finalizefn) (void); void morpho_addfinalizefn(morpho_finalizefn finalizefn); void morpho_setbaseclass(value clss); void morpho_initialize(void); void morpho_finalize(void); void morpho_setargs(int argc, const char * argv[]); // Pass arguments to morpho /* Obtain and use subkernels [for internal use only] */ bool vm_subkernels(vm *v, int nkernels, vm **subkernels); void vm_releasesubkernel(vm *subkernel); void vm_cleansubkernel(vm *subkernel); /* Thread local storage [for internal use only] */ int vm_addtlvar(void); bool vm_settlvar(vm *v, int handle, value val); bool vm_gettlvar(vm *v, int handle, value *out); #endif /* morpho_h */ ================================================ FILE: src/support/CMakeLists.txt ================================================ target_sources(morpho PRIVATE common.c common.h extensions.c extensions.h format.c format.h lex.c lex.h memory.c memory.h parse.c parse.h platform.c platform.h random.c random.h resources.c resources.h threadpool.c threadpool.h ) target_sources(morpho INTERFACE FILE_SET public_headers TYPE HEADERS FILES common.h extensions.h format.h lex.h memory.h parse.h random.h resources.h threadpool.h ) ================================================ FILE: src/support/common.c ================================================ /** @file common.c * @author T J Atherton * * @brief Utility functions for the Morpho VM */ #include #include #include #include #include #include "common.h" /* ********************************************************************** * Printing * ********************************************************************** */ /** @brief Prints a value * @param v The value to print */ void morpho_printvalue(vm *v, value val) { if (MORPHO_ISFLOAT(val)) { morpho_printf(v, "%g", MORPHO_GETFLOATVALUE(val)); return; } else { switch (MORPHO_GETTYPE(val)) { case VALUE_NIL: morpho_printf(v, MORPHO_NILSTRING); return; case VALUE_BOOL: morpho_printf(v, "%s", (MORPHO_GETBOOLVALUE(val) ? MORPHO_TRUESTRING : MORPHO_FALSESTRING)); return; case VALUE_INTEGER: morpho_printf(v, "%i", MORPHO_GETINTEGERVALUE(val)); return; case VALUE_OBJECT: object_print(v, val); return; default: return; } } } /** @brief Prints a value to a buffer */ #define MORPHO_TOSTRINGTMPBUFFERSIZE 64 bool morpho_printtobuffer(vm *v, value val, varray_char *buffer) { bool success=false; char tmp[MORPHO_TOSTRINGTMPBUFFERSIZE]; int nv; if (MORPHO_ISSTRING(val)) { objectstring *s = MORPHO_GETSTRING(val); success=varray_charadd(buffer, s->string, (int) s->length); } else if (MORPHO_ISCLASS(val)) { objectclass *klass = MORPHO_GETCLASS(val); varray_charwrite(buffer, '@'); success=morpho_printtobuffer(v, klass->name, buffer); } else if (MORPHO_ISOBJECT(val)) { objectclass *klass = morpho_lookupclass(val); if (klass) { objectstring str = MORPHO_STATICSTRING(MORPHO_TOSTRING_METHOD); value label = MORPHO_OBJECT(&str); value method, ret; if (morpho_lookupmethod(val, label, &method) && morpho_invoke(v, val, method, 0, NULL, &ret)) { if (MORPHO_ISSTRING(ret)) { success=varray_charadd(buffer, MORPHO_GETCSTRING(ret), (int) MORPHO_GETSTRINGLENGTH(ret)); } } else { varray_charwrite(buffer, '<'); success=morpho_printtobuffer(v, klass->name, buffer); varray_charwrite(buffer, '>'); } } else if (MORPHO_ISBUILTINFUNCTION(val)) { objectbuiltinfunction *fn = MORPHO_GETBUILTINFUNCTION(val); varray_charadd(buffer, "name, buffer); varray_charwrite(buffer, '>'); } } else if (MORPHO_ISFLOAT(val)) { nv=snprintf(tmp, MORPHO_TOSTRINGTMPBUFFERSIZE, "%g", MORPHO_GETFLOATVALUE(val)); success=varray_charadd(buffer, tmp, nv); } else if (MORPHO_ISINTEGER(val)) { nv=snprintf(tmp, MORPHO_TOSTRINGTMPBUFFERSIZE, "%i", MORPHO_GETINTEGERVALUE(val)); success=varray_charadd(buffer, tmp, nv); } else if (MORPHO_ISBOOL(val)) { nv=snprintf(tmp, MORPHO_TOSTRINGTMPBUFFERSIZE, "%s", (MORPHO_ISTRUE(val) ? MORPHO_TRUESTRING : MORPHO_FALSESTRING)); success=varray_charadd(buffer, tmp, nv); } else if (MORPHO_ISNIL(val)) { nv=snprintf(tmp, MORPHO_TOSTRINGTMPBUFFERSIZE, "%s", MORPHO_NILSTRING); success=varray_charadd(buffer, tmp, nv); } return success; } /** @brief Concatenates a sequence of values as a string */ value morpho_concatenate(vm *v, int nval, value *val) { varray_char buffer; varray_charinit(&buffer); for (unsigned int i=0; i ascii out[0]=c; // b 0XXXXXXX return 1; } else if (c<=0x07ff) { // 2 byte out[0]=(char) (((c >> 6) & 0x1f) | 0xc0); // b 110XXXXX out[1]=(char) (((c >> 0) & 0x3f) | 0x80); // b 10XXXXXX return 2; } else if (c<=0xffff) { // 3 byte out[0]=(char) (((c >> 12) & 0x0f) | 0xe0); // b 1110XXXX out[1]=(char) (((c >> 6) & 0x3f) | 0x80); // b 10XXXXXX out[2]=(char) (((c >> 0) & 0x3f) | 0x80); // b 10XXXXXX return 3; } else if (c<=0x10ffff) { out[0]=(char) (((c >> 18) & 0x07) | 0xf0); // b 11110XXX out[1]=(char) (((c >> 12) & 0x3f) | 0x80); // b 10XXXXXX out[2]=(char) (((c >> 6) & 0x3f) | 0x80); // b 10XXXXXX out[3]=(char) (((c >> 0) & 0x3f) | 0x80); // b 10XXXXXX return 4; } return 0; } /* ********************************************************************** * Other utility functions * ********************************************************************** */ /** @brief Computes the nearest power of 2 above an integer * @param n An integer * @returns Nearest power of 2 above n * See: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float */ unsigned int morpho_powerof2ceiling(unsigned int n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n++; return n; } /** Count the number of fixed parameters in a callable object * @param[in] f - the function or callable object * @param[out] nparams - number of parameters; -1 if unknown * @returns true on success, false if f is not callable*/ bool morpho_countparameters(value f, int *nparams) { value g = f; bool success=false; if (MORPHO_ISINVOCATION(g)) { // Unpack invocation objectinvocation *inv = MORPHO_GETINVOCATION(g); g=inv->method; } if (MORPHO_ISCLOSURE(g)) { // Unpack closure objectclosure *cl = MORPHO_GETCLOSURE(g); g=MORPHO_OBJECT(cl->func); } if (MORPHO_ISFUNCTION(g)) { objectfunction *fun = MORPHO_GETFUNCTION(g); *nparams=fun->nargs; success=true; } else if (MORPHO_ISBUILTINFUNCTION(g)) { *nparams = -1; success=true; } return success; } /** Initialize tuple generator @param[in] nval - number of values @param[in] n - n-tuples to generate @param[in] c - workspace: supply an unsigned integer array of size 2xn */ void morpho_tuplesinit(unsigned int nval, unsigned int n, unsigned int *c, tuplemode mode) { unsigned int *counter=c, *cmax=c+n; // Counters for (unsigned int i=0; icmax[0]) return false; // Done // Generate tuple from counter for (unsigned int i=0; i0 && counter[k]>cmax[k]; k--) counter[k-1]++; // Carry if (k #include #include #include "value.h" #include "object.h" #include "classes.h" #define MORPHO_NILSTRING "nil" #define MORPHO_TRUESTRING "true" #define MORPHO_FALSESTRING "false" /* ----------------------------------------- * VM Callback functions * ----------------------------------------- */ typedef enum { MORPHO_INPUT_KEYPRESS, MORPHO_INPUT_LINE } morphoinputmode; /* Callback function used to obtain input from stdin */ typedef void (*morphoinputfn) (vm *v, void *ref, morphoinputmode mode, varray_char *str); /* Callback function used to print text to stdout */ typedef void (*morphoprintfn) (vm *v, void *ref, char *str); /* Callback function used to output a warning */ typedef void (*morphowarningfn) (vm *v, void *ref, error *warning); /* Callback function used to enter debugger */ typedef void (*morphodebuggerfn) (vm *v, void *ref); void morpho_setwarningfn(vm *v, morphowarningfn warningfn, void *ref); void morpho_setprintfn(vm *v, morphoprintfn printfn, void *ref); void morpho_setinputfn(vm *v, morphoinputfn inputfn, void *ref); void morpho_setdebuggerfn(vm *v, morphodebuggerfn debuggerfn, void *ref); /* ----------------------------------------- * Functions and macros for comparing values * ----------------------------------------- */ /** @brief Promotes l and r to types that can be compared * @param l value to compare * @param r value to compare */ /*#define MORPHO_CMPPROMOTETYPE(l, r) \ if (!morpho_ofsametype(l, r)) { \ if (MORPHO_ISINTEGER(l) && MORPHO_ISFLOAT(r)) { \ l = MORPHO_INTEGERTOFLOAT(l); \ } else if (MORPHO_ISFLOAT(l) && MORPHO_ISINTEGER(r)) { \ r = MORPHO_INTEGERTOFLOAT(r); \ } \ }*/ /** Check if a value is callable */ static inline bool morpho_iscallable(value a) { return (MORPHO_ISFUNCTION(a) || MORPHO_ISBUILTINFUNCTION(a) || MORPHO_ISMETAFUNCTION(a) || MORPHO_ISINVOCATION(a) || MORPHO_ISCLOSURE(a) || MORPHO_ISCLASS(a)); } #define MORPHO_ISCALLABLE(x) (morpho_iscallable(x)) bool morpho_printtobuffer(vm *v, value val, varray_char *buffer); value morpho_concatenate(vm *v, int nval, value *val); char *morpho_strdup(char *string); int morpho_utf8numberofbytes(const char *string); int morpho_utf8toint(const char *c); int morpho_encodeutf8(int c, char *out); unsigned int morpho_powerof2ceiling(unsigned int n); #ifdef MORPHO_DEBUG void morpho_unreachable(const char *explanation); #endif typedef enum { MORPHO_TUPLEMODE, // Generates tuples (all combinations of n elements) MORPHO_SETMODE // Generates sets (unique elements and indep of order) } tuplemode; void morpho_tuplesinit(unsigned int nval, unsigned int n, unsigned int *c, tuplemode mode); bool morpho_tuples(unsigned int nval, value *list, unsigned int n, unsigned int *c, tuplemode mode, value *tuple); #endif /* common_h */ ================================================ FILE: src/support/extensions.c ================================================ /** @file extensions.c * @author T J Atherton * * @brief Morpho extensions */ /* ********************************************************************** * Extensions * ********************************************************************** */ #include #include "varray.h" #include "value.h" #include "common.h" #include "object.h" #include "builtin.h" #include "resources.h" #include "extensions.h" #include "platform.h" /* ------------------------------------------------------- * Extension structure * ------------------------------------------------------- */ typedef struct { value name; value path; value functiontable; value classtable; MorphoDLHandle handle; } extension; DECLARE_VARRAY(extension, extension) DEFINE_VARRAY(extension, extension) varray_extension extensionlist; // List of loaded extensions /* ------------------------------------------------------- * Extension interface * ------------------------------------------------------- */ /** Open the dynamic library associated with an extension */ bool extension_dlopen(extension *e) { if (e->handle) return true; // Prevent multiple loads if (MORPHO_ISSTRING(e->path)) e->handle=platform_dlopen(MORPHO_GETCSTRING(e->path)); return e->handle; } /** Close the dynamic library associated with an extension */ void extension_dlclose(extension *e) { if (e->handle) platform_dlclose(e->handle); e->handle=NULL; } /** Initializes an extension structure with empty values */ void extension_init(extension *e) { e->name=MORPHO_NIL; e->path=MORPHO_NIL; e->functiontable=MORPHO_NIL; e->classtable=MORPHO_NIL; e->handle=NULL; } /** Clears an extension structure */ void extension_clear(extension *e) { if (MORPHO_ISOBJECT(e->name)) morpho_freeobject(e->name); if (MORPHO_ISOBJECT(e->path)) morpho_freeobject(e->path); // The functions and classes are freed from the builtin_objects list. if (MORPHO_ISOBJECT(e->functiontable)) morpho_freeobject(e->functiontable); if (MORPHO_ISOBJECT(e->classtable)) morpho_freeobject(e->classtable); extension_dlclose(e); extension_init(e); } /** Initializes an extension structure with a name and path, creating associated data structures */ bool extension_initwithname(extension *e, char *name, char *path) { extension_init(e); e->name=object_stringfromcstring(name, strlen(name)); e->path=object_stringfromcstring(path, strlen(path)); objectdictionary *functiontable = object_newdictionary(), *classtable = object_newdictionary(); if (functiontable) e->functiontable=MORPHO_OBJECT(functiontable); if (classtable) e->classtable=MORPHO_OBJECT(classtable); e->handle=NULL; if (!MORPHO_ISSTRING(e->name) || !MORPHO_ISSTRING(e->path) || !MORPHO_ISDICTIONARY(e->functiontable) || !MORPHO_ISDICTIONARY(e->classtable)) { extension_clear(e); return false; } return true; } /** Trys to locate a function with NAME_FN in extension e, and calls it if found */ bool extension_call(extension *e, const char *name, const char *fn) { void (*fptr) (void); size_t size = strlen(name) + strlen(fn) + 2; char fnname[size]; strncpy(fnname, name, size); strncat(fnname, "_", size); strncat(fnname, fn, size); fptr = platform_dlsym(e->handle, fnname); if (fptr) (*fptr) (); return fptr; } /** Finds the path for an extension using the resource finder */ bool extension_find(char *name, value *path) { return morpho_findresource(MORPHO_RESOURCE_EXTENSION, name, path); } /** Checks if an extension is already loaded; returns it in out if found */ bool extension_isloaded(value path, extension *out) { for (int i=0; ipath)) { if (out) *out = *e; return true; } } return false; } /** Call the extension's initializer */ bool extension_initialize(extension *e) { dictionary *ofunc=builtin_getfunctiontable(), *oclss=builtin_getclasstable(); builtin_setfunctiontable(MORPHO_GETDICTIONARYSTRUCT(e->functiontable)); builtin_setclasstable(MORPHO_GETDICTIONARYSTRUCT(e->classtable)); bool success=extension_call(e, MORPHO_GETCSTRING(e->name), MORPHO_EXTENSIONINITIALIZE); builtin_setfunctiontable(ofunc); builtin_setclasstable(oclss); return success; } /** Call the extension's finalizer */ bool extension_finalize(extension *e) { return extension_call(e, MORPHO_GETCSTRING(e->name), MORPHO_EXTENSIONFINALIZE); } /** Load an extension, optionally returning a dictionary of functions and classes defined by the extension */ bool extension_load(char *name, dictionary **functiontable, dictionary **classtable) { value path; if (!extension_find(name, &path)) return false; bool success=false; extension e; extension_init(&e); if (extension_isloaded(path, &e)) { success=true; } else if (extension_initwithname(&e, name, MORPHO_GETCSTRING(path)) && extension_dlopen(&e)) { success=extension_initialize(&e); if (success) varray_extensionwrite(&extensionlist, e); } if (success) { if (functiontable) *functiontable = MORPHO_GETDICTIONARYSTRUCT(e.functiontable); if (classtable) *classtable = MORPHO_GETDICTIONARYSTRUCT(e.classtable); } else extension_clear(&e); morpho_freeobject(path); return success; } /* ------------------------------------------------------- * Extensions initialization/finalization * ------------------------------------------------------- */ void extensions_initialize(void) { varray_extensioninit(&extensionlist); morpho_addfinalizefn(extensions_finalize); } void extensions_finalize(void) { for (int i=0; i #define MORPHO_EXTENSIONINITIALIZE "initialize" // Function to call upon initialization #define MORPHO_EXTENSIONFINALIZE "finalize" // Function to call upon finalization bool extension_load(char *name, dictionary **functiontable, dictionary **classtable); void extensions_initialize(void); void extensions_finalize(void); #endif /* extensions_h */ ================================================ FILE: src/support/format.c ================================================ /** @file format.c * @author T J Atherton * * @brief Formatting of values */ #include #include #include #include "value.h" #include "format.h" #include "varray.h" #define SPRINTFBUFFER 255 #define ERROR_CHECK(f) if (!(f)) return false; /* ********************************************************************** * Format utility functions * ********************************************************************** */ #define UNSET -1 typedef struct fformat { int width; int precision; char type; } format; /** Parses a format string @param[in] formatstring - format string to parse @param[in] valid - valid type characters */ bool _format_parse(char *formatstring, char *validtypes, char **endptr, format *f) { f->width=UNSET; f->precision=UNSET; f->type=' '; char *c = formatstring; if (*c=='%') c++; else return false; if (isdigit(*c)) { // Field width f->width = (int) strtol(c, &c, 10); } if (*c=='.') { // Precision c++; if (isdigit(*c)) { f->precision = (int) strtol(c, &c, 10); } else return false; } if (!strchr(validtypes, *c)) return false; // Check the type is valid f->type=*c; c++; if (endptr) *endptr = c; return true; } /** Prints a value to a buffer using a format specifier */ bool _format_printtobuffer(value v, format *f, varray_char *out) { if (!(MORPHO_ISFLOAT(v) || MORPHO_ISINTEGER(v))) return false; char format[SPRINTFBUFFER]; char buffer[SPRINTFBUFFER]; char *c = format; *c='%'; c++; if (f->width>=0) c+=snprintf(c, format+SPRINTFBUFFER-c, "%i", f->width); if (f->precision>=0) c+=snprintf(c, format+SPRINTFBUFFER-c, ".%i", f->precision); *c=f->type; c++; *c='\0'; int nchars=0; if (MORPHO_ISFLOAT(v)) nchars=snprintf(buffer, SPRINTFBUFFER, format, MORPHO_GETFLOATVALUE(v)); else nchars=snprintf(buffer, SPRINTFBUFFER, format, MORPHO_GETINTEGERVALUE(v)); varray_charadd(out, buffer, nchars); return true; } /* ********************************************************************** * Format public functions * ********************************************************************** */ /** Prints a quantity to a buffer */ bool format_printtobuffer(value v, char *formatstring, varray_char *out) { char *validtypes=FORMAT_INTTYPES; if (MORPHO_ISFLOAT(v)) validtypes=FORMAT_FLOATTYPES; for (char *c = formatstring; *c!='\0'; ) { // Loop over format string if (*c!='%') { // Output any characters unconnected to the format varray_charwrite(out, *c); c++; } else { format f; ERROR_CHECK(_format_parse(c, validtypes, &c, &f)); ERROR_CHECK(_format_printtobuffer(v, &f, out)); } } return true; } ================================================ FILE: src/support/format.h ================================================ /** @file format.h * @author T J Atherton * * @brief Formatting of values */ #ifndef format_h #define format_h #define FORMAT_FLOATTYPES "efgEG" #define FORMAT_INTTYPES "ioxX" bool format_printtobuffer(value v, char *format, varray_char *out); #endif /* format_h */ ================================================ FILE: src/support/lex.c ================================================ /** @file lex.c * @author T J Atherton * * @brief Lexer */ #include #include #include "lex.h" extern tokendefn standardtokens[]; extern int nstandardtokens; /* ********************************************************************** * Lexer library functions * ********************************************************************** */ /* ------------------------------------------------------- * Comparison functions for token definitions * ------------------------------------------------------- */ /** Compare two token definitions */ int _lex_tokndefncmp(const void *ldefn, const void *rdefn) { tokendefn *a = (tokendefn *) ldefn; tokendefn *b = (tokendefn *) rdefn; return strcmp(a->string, b->string); } /** Compare a string with the contents of a token definition */ int _lex_tokendefnwithstringcmp(const void *lstr, const void *rdefn) { char *a = (char *) lstr; tokendefn *b = (tokendefn *) rdefn; return strcmp(a, b->string); } /** Compare the contents of a token with the contents of a token definition */ int _lex_tokendefnwithtokencmp(const void *ltok, const void *rdefn) { token *tok = (token *) ltok; tokendefn *b = (tokendefn *) rdefn; // Compare token with token definition int cmp = strncmp(tok->start, b->string, tok->length); // If we see a match, ensure that we're not simply matching the initial part of the definition. if (cmp==0 && b->string[tok->length]!='\0') cmp = -b->string[tok->length]; // Mimic behavior of strcmp return cmp; } /** Compare a character with the first character of a token definition */ int _lex_tokendefnwithtokenfirstcharcmp(const void *l, const void *rdefn) { char *c = (char *) l; tokendefn *b = (tokendefn *) rdefn; return *c - b->string[0]; } DEFINE_VARRAY(tokendefn, tokendefn); /* ------------------------------------------------------- * Library functions to support writing custom lexers * ------------------------------------------------------- */ /** @brief Records a token * @param[in] l The lexer in use * @param[in] type Type of token to record * @param[out] tok Token structure to fill out */ void lex_recordtoken(lexer *l, tokentype type, token *tok) { tok->type=type; tok->start=l->start; tok->length=(int) (l->current - l->start); tok->line=l->line; tok->posn=l->posn - tok->length; } /** @brief Advances the lexer by one character, returning the character */ char lex_advance(lexer *l) { char c = *(l->current); l->current++; l->posn++; return c; } /** @brief Advances the lexer by n characters, returning the last character */ char lex_advanceby(lexer *l, size_t n) { l->current+=n; l->posn+=n; return *(l->current-1); } /** @brief Reverses the current character in the lexer by one. */ bool lex_back(lexer *l) { if (l->current==l->start) return false; l->current--; l->posn--; return true; } /** @brief Checks if we're at the end of the string. Doesn't advance. */ bool lex_isatend(lexer *l) { return (*(l->current) == '\0'); } /** @brief Checks if a character is alphanumeric or underscore. */ bool lex_isalpha(char c) { return (isalpha(c) || (c=='_')); } /** @brief Checks if a character is a digit. */ bool lex_isdigit(char c) { return isdigit(c); } /** @brief Checks if a character is whitespace. @warning: The morpho lexer does not consider newlines to be whitespace. */ bool lex_isspace(char c) { return (c==' ') || (c=='\t') || (c=='\n') || (c=='\r'); } /** @brief Returns the next character */ char lex_peek(lexer *l) { return *(l->current); } /** @brief Returns n characters ahead. Caller should check that this is meaningfull. */ char lex_peekahead(lexer *l, int n) { return *(l->current + n); } /** @brief Returns the previous character */ char lex_peekprevious(lexer *l) { if (l->current==l->start) return '\0'; return *(l->current - 1); } /** @brief Advance line counter */ void lex_newline(lexer *l) { l->line++; l->posn=0; } /** @brief Attempts to find a matching token for the current token. * @param[in] l The lexer in use * @param[out] defn Type of token, if found * @returns true if the token matched, false if not */ bool lex_matchtoken(lexer *l, tokendefn **defn) { token tok = { .start = l->start, .length = (int) (l->current - l->start) }; tokendefn *def = bsearch(&tok, l->defns, l->ndefns, sizeof(tokendefn), _lex_tokendefnwithtokencmp); if (def && defn) *defn = def; return def; } /** @brief Attempts to identify a token from the current point, advances if it finds one. * @param[in] l The lexer in use * @param[out] defn Type of token, if found * @returns true if the token matched, false if not */ bool lex_identifytoken(lexer *l, tokendefn **defn) { char c = lex_peek(l); // Match first character tokendefn *def = bsearch(&c, l->defns, l->ndefns, sizeof(tokendefn), _lex_tokendefnwithtokenfirstcharcmp); if (!def) return false; tokendefn *last = l->defns+l->ndefns-1; // Now find the last definition that matches this character while (defstring[0]==c) def++; // Test each in turn, working backwards to match the longest token we can for (; def->string[0]==c && def>=l->defns; def--) { size_t len=strlen(def->string); if (strncmp(def->string, l->current, len)==0) { lex_advanceby(l, len); if (defn) *defn = def; return true; } } return false; } /* ********************************************************************** * Morpho lexer * ********************************************************************** */ // Process functions we'll be using bool lex_string(lexer *l, token *tok, error *err); bool lex_processnewline(lexer *l, token *tok, error *err); bool lex_processinterpolation(lexer *l, token *tok, error *err); /* ------------------------------------------------------- * Morpho token definitions * ------------------------------------------------------- */ tokendefn standardtokens[] = { { "(", TOKEN_LEFTPAREN , NULL }, { ")", TOKEN_RIGHTPAREN , NULL }, { "[", TOKEN_LEFTSQBRACKET , NULL }, { "]", TOKEN_RIGHTSQBRACKET , NULL }, { "{", TOKEN_LEFTCURLYBRACKET , NULL }, { "}", TOKEN_RIGHTCURLYBRACKET , lex_processinterpolation }, { ";", TOKEN_SEMICOLON , NULL }, { ":", TOKEN_COLON , NULL }, { ",", TOKEN_COMMA , NULL }, { "^", TOKEN_CIRCUMFLEX , NULL }, { "?", TOKEN_QUESTION , NULL }, { "@", TOKEN_AT , NULL }, { "#", TOKEN_HASH , NULL }, { ".", TOKEN_DOT , NULL }, { "..", TOKEN_DOTDOT , NULL }, { "...", TOKEN_DOTDOTDOT , NULL }, { "+", TOKEN_PLUS , NULL }, { "+=", TOKEN_PLUSEQ , NULL }, { "-", TOKEN_MINUS , NULL }, { "-=", TOKEN_MINUSEQ , NULL }, { "*", TOKEN_STAR , NULL }, { "*=", TOKEN_STAREQ , NULL }, { "/", TOKEN_SLASH , NULL }, { "/=", TOKEN_SLASHEQ , NULL }, { "==", TOKEN_EQ , NULL }, { "=", TOKEN_EQUAL , NULL }, { "!", TOKEN_EXCLAMATION , NULL }, { "!=", TOKEN_NEQ , NULL }, { "<", TOKEN_LT , NULL }, { "<=", TOKEN_LTEQ , NULL }, { ">", TOKEN_GT , NULL }, { ">=", TOKEN_GTEQ , NULL }, { "&", TOKEN_AMP , NULL }, { "&&", TOKEN_DBLAMP , NULL }, { "|", TOKEN_VBAR , NULL }, { "||", TOKEN_DBLVBAR , NULL }, { "\"", TOKEN_QUOTE , lex_string }, { "\n", TOKEN_NEWLINE , lex_processnewline }, { "and", TOKEN_DBLAMP , NULL }, { "as", TOKEN_AS , NULL }, { "break", TOKEN_BREAK , NULL }, { "class", TOKEN_CLASS , NULL }, { "continue", TOKEN_CONTINUE , NULL }, { "catch", TOKEN_CATCH , NULL }, { "do", TOKEN_DO , NULL }, { "else", TOKEN_ELSE , NULL }, { "false", TOKEN_FALSE , NULL }, { "for", TOKEN_FOR , NULL }, { "fn", TOKEN_FUNCTION , NULL }, { "help", TOKEN_QUESTION , NULL }, { "if", TOKEN_IF , NULL }, { "in", TOKEN_IN , NULL }, { "is", TOKEN_IS , NULL }, { "import", TOKEN_IMPORT , NULL }, { "im", TOKEN_IMAG , NULL }, { "nil", TOKEN_NIL , NULL }, { "or", TOKEN_DBLVBAR , NULL }, { "print", TOKEN_PRINT , NULL }, { "return", TOKEN_RETURN , NULL }, { "self", TOKEN_SELF , NULL }, { "super", TOKEN_SUPER , NULL }, { "true", TOKEN_TRUE , NULL }, { "try", TOKEN_TRY , NULL }, { "var", TOKEN_VAR , NULL }, { "while", TOKEN_WHILE , NULL }, { "with", TOKEN_WITH , NULL }, { "", TOKEN_NONE , NULL } // Token list should be terminated by an empty token }; int nstandardtokens; /* ------------------------------------------------------- * Morpho lexing functions * ------------------------------------------------------- */ /** @brief Skips multiline comments * @param[in] l the lexer * @param[out] tok token record to fill out (if necessary) * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_skipmultilinecomment(lexer *l, token *tok, error *err) { unsigned int level=0; unsigned int startline = l->line, startpsn = l->posn; do { char c = lex_peek(l); switch (c) { case '\0': /* If we come to the end of the file, the token is marked as incomplete. */ morpho_writeerrorwithid(err, LEXER_UNTERMINATEDCOMMENT, NULL, startline, startpsn); lex_recordtoken(l, TOKEN_INCOMPLETE, tok); return false; case '\n': /* Advance the line counter. */ lex_newline(l); break; case '/': if (lex_peekahead(l, 1)=='*') { level++; lex_advance(l); } break; case '*': if (lex_peekahead(l, 1)=='/') { level--; lex_advance(l); } break; default: break; } /* Now advance the counter */ lex_advance(l); } while (level>0); return true; } /** @brief Skips comments * @param[in] l the lexer * @param[out] tok token record to fill out (if necessary) * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_skipcomment(lexer *l, token *tok, error *err) { char c = lex_peekahead(l, 1); if (c == '/') { while (lex_peek(l) != '\n' && !lex_isatend(l)) lex_advance(l); return true; } else if (c == '*') { return lex_skipmultilinecomment(l, tok, err); } return false; } /** @brief Detect and skip a shebang line * @param[in] l the lexer * @param[out] tok token record to fill out (if necessary) * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_skipshebang(lexer *l) { if (lex_peek(l)=='#' && lex_peekahead(l, 1)=='!') { while (lex_peek(l) != '\n' && !lex_isatend(l)) lex_advance(l); } return true; } /** @brief Skips whitespace * @param[in] l the lexer * @param[out] tok token record to fill out (if necessary) * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_skipwhitespace(lexer *l, token *tok, error *err) { do { switch (lex_peek(l)) { case ' ': case '\t': case '\r': lex_advance(l); break; case '/': if (!lex_skipcomment(l, tok, err)) return true; break; default: return true; } } while (true); return true; } /** @brief Lex strings * @param[in] l the lexer * @param[out] tok token record to fill out * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_string(lexer *l, token *tok, error *err) { unsigned int startline = l->line, startpsn = l->posn; char first = lex_peekprevious(l); while (lex_peek(l) != '"' && !lex_isatend(l)) { if (lex_peek(l) == '\n') lex_newline(l); /* Detect string interpolation */ if (l->stringinterpolation && lex_peek(l) == '$' && lex_peekahead(l, 1) == '{') { lex_advance(l); lex_advance(l); lex_recordtoken(l, TOKEN_INTERPOLATION, tok); if (first=='"') l->interpolationlevel++; return true; } /* Detect an escaped character */ if (lex_peek(l)=='\\') { lex_advance(l); } lex_advance(l); } if (lex_isatend(l)) { /* Unterminated string */ morpho_writeerrorwithid(err, LEXER_UNTERMINATEDSTRING, NULL, startline, startpsn); lex_recordtoken(l, TOKEN_INCOMPLETE, tok); return false; } lex_advance(l); /* Closing quote */ if (l->stringinterpolation && l->interpolationlevel>0 && first=='}') l->interpolationlevel--; lex_recordtoken(l, TOKEN_STRING, tok); return true; } /** @brief Lex numbers * @param[in] l the lexer * @param[out] tok token record to fill out * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_number(lexer *l, token *tok, error *err) { tokentype type=l->inttype; while (lex_isdigit(lex_peek(l))) lex_advance(l); /* Fractional part */ char next = '\0'; if (lex_peek(l)!='\0') next=lex_peekahead(l, 1); // Prevent looking beyond buffer if (lex_peek(l) == '.' && (lex_isdigit(next) || lex_isspace(next) || next=='\0') ) { type=l->flttype; lex_advance(l); /* Consume the '.' */ while (lex_isdigit(lex_peek(l))) lex_advance(l); } /* Exponent */ if (lex_peek(l) == 'e' || lex_peek(l) == 'E') { type=l->flttype; lex_advance(l); /* Consume the 'e' */ /* Optional sign */ if (lex_peek(l) == '+' || lex_peek(l) == '-') lex_advance(l); /* Exponent digits */ while (lex_isdigit(lex_peek(l))) lex_advance(l); } /* Imaginary Numbers */ if (lex_peek(l) =='i' && lex_peekahead(l, 1) == 'm'){ /* mark this as an imaginary number*/ type = l->imagtype; lex_advance(l); /* Consume the 'i' */ lex_advance(l); /* Consume the 'm' */ } lex_recordtoken(l, type, tok); return true; } /** @brief Checks whether a symbol matches a given string, and if so records a token * @param[in] l the lexer * @param[in] start offset to start comparing from * @param[in] length length to compare * @param[in] match string to match with * @param[in] type token type to use if the match is successful * @returns type or symboltype if the match was not successful */ tokentype lex_checksymbol(lexer *l, int start, int length, char *match, tokentype type) { int toklength = (int) (l->current - l->start); int expectedlength = start + length; /* Compare, but don't bother calling memcmp if the lengths are different */ if ((toklength == expectedlength) && (memcmp(l->start+start, match, length) == 0)) return type; return l->symboltype; } tokentype lex_typeforsymboltoken(lexer *l) { tokentype t = l->symboltype; tokendefn *def; if (lex_matchtoken(l, &def)) t = def->type; return t; } /** @brief Lex symbols * @param[in] l the lexer * @param[out] tok token record to fill out * @param[out] err error struct to fill out on errors * @returns true on success, false if an error occurs */ bool lex_symbol(lexer *l, token *tok, error *err) { while (lex_isalpha(lex_peek(l)) || lex_isdigit(lex_peek(l))) lex_advance(l); tokentype typ = l->symboltype; if (l->matchkeywords) typ = lex_typeforsymboltoken(l); lex_recordtoken(l, typ, tok); return true; } /* ------------------------------------------------------- * Morpho preprocessing functions * ------------------------------------------------------- */ /** @brief Process function for newline tokens */ bool lex_preprocess(lexer *l, token *tok, error *err) { char c = lex_peek(l); if (lex_isalpha(c)) return lex_symbol(l, tok, err); if (lex_isdigit(c)) return lex_number(l, tok, err); return false; } /** @brief Process function for newline tokens */ bool lex_processnewline(lexer *l, token *tok, error *err) { lex_newline(l); return true; } /** @brief Process function for interpolation tokens */ bool lex_processinterpolation(lexer *l, token *tok, error *err) { if (l->stringinterpolation && l->interpolationlevel>0) { return lex_string(l, tok, err); } return true; } /* ********************************************************************** * Initialize/clear a lexer * ********************************************************************** */ /** @brief Initializes a lexer with a given starting point * @param l The lexer to initialize * @param start Starting point to lex from * @param line The current line number */ void lex_init(lexer *l, const char *start, int line) { l->current=start; l->start=start; l->line=line; l->posn=0; l->matchkeywords=true; l->stringinterpolation=true; l->interpolationlevel=0; l->prefn=lex_preprocess; l->whitespacefn=lex_skipwhitespace; l->eoftype=TOKEN_EOF; l->inttype=TOKEN_INTEGER; l->flttype=TOKEN_NUMBER; l->imagtype=TOKEN_IMAG; l->symboltype=TOKEN_SYMBOL; l->defns=standardtokens; // Use the standard morpho tokens by default l->ndefns=nstandardtokens; varray_tokendefninit(&l->defnstore); // Alternative definitions will be held here } /** @brief Clears a lexer */ void lex_clear(lexer *l) { l->current=NULL; l->start=NULL; l->posn=0; l->interpolationlevel=0; varray_tokendefnclear(&l->defnstore); } /* ********************************************************************** * Configure the lexer * ********************************************************************** */ /** Sets the lexer to use a specific set of token definitions * @param[in] l the lexer * @param[out] defns List of token definitons, terminated by a null or null length string * @warning: The lexer does not duplicate the token definition strings, so these should be preserved. */ void lex_settokendefns(lexer *l, tokendefn *defns) { int n; for (n=0; ; n++) if (defns[n].string == NULL || strlen(defns[n].string)==0) break; l->defnstore.count=0; varray_tokendefnadd(&l->defnstore, defns, n); l->defns=l->defnstore.data; l->ndefns=n; qsort(l->defns, l->ndefns, sizeof(tokendefn), _lex_tokndefncmp); } /** @brief Sets the token type representing End Of File */ void lex_seteof(lexer *l, tokentype eoftype) { l->eoftype = eoftype; } /** @brief Gets the token type representing End Of File */ tokentype lex_eof(lexer *l) { return l->eoftype; } /** @brief Sets the token type representing integers, floats and complex */ void lex_setnumbertype(lexer *l, tokentype inttype, tokentype flttype, tokentype imagtype) { l->inttype=inttype; l->flttype=flttype; l->imagtype=imagtype; } /** @brief Sets the token type representing symbols */ void lex_setsymboltype(lexer *l, tokentype symboltype) { l->symboltype=symboltype; } /** @brief Gets the token type representing symbols */ tokentype lex_symboltype(lexer *l) { return l->symboltype; } /** @brief Choose whether the lexer should perform string interpolation. */ void lex_setstringinterpolation(lexer *l, bool interpolation) { l->stringinterpolation=interpolation; } /** @brief Choose whether the lexer should attempt to match keywords or simply return them as symbols. */ void lex_setmatchkeywords(lexer *l, bool match) { l->matchkeywords=match; } /** @brief Choose whether the lexer should attempt to match keywords or simply return them as symbols. */ bool lex_matchkeywords(lexer *l) { return l->matchkeywords; }; /** @brief Provide a processing function to skip whitespace and comments. */ void lex_setwhitespacefn(lexer *l, processtokenfn whitespacefn) { l->whitespacefn = whitespacefn; } /** @brief Provide a processing function to identify tokens prior to matching. */ void lex_setprefn(lexer *l, processtokenfn prefn) { l->prefn = prefn; } /* ********************************************************************** * Lexer public interface * ********************************************************************** */ /** @brief Checks if a token contains a keyword */ bool lex_tokeniskeyword(lexer *l, token *tok) { if (tok->type==TOKEN_SYMBOL) return false; return lex_isalpha(tok->start[0]); } /** @brief Identifies the next token * @param[in] l The lexer in use * @param[out] err An error block to fill out on an error * @param[out] tok Token structure to fill out * @returns true on success or false on failure */ bool lex(lexer *l, token *tok, error *err) { bool success=false; // Handle leading whitespace if (l->whitespacefn) { if (!((l->whitespacefn) (l, tok, err))) return false; } // Set beginning of the token l->start=l->current; // Check whether we're at the end of the source string if (lex_isatend(l)) { lex_recordtoken(l, l->eoftype, tok); return true; } // If the lexer has a prefn, call that and check whether it handled the token. if (l->prefn) { success=(l->prefn) (l, tok, err); if (err->cat!=ERROR_NONE) return false; // It raised an error, so should return if (success) return true; } tokendefn *defn=NULL; if (lex_identifytoken(l, &defn)) { lex_recordtoken(l, defn->type, tok); } else { morpho_writeerrorwithid(err, LEXER_UNRECOGNIZEDTOKEN, NULL, l->line, l->posn); return false; } // If the token type provides a process function, call it if (defn->processfn) return (defn->processfn) (l, tok, err); return true; } /* ********************************************************************** * Initialization/finalization * ********************************************************************** */ /** @brief Initialization/finalization */ void lex_initialize(void) { // Ensure standardtokens is sorted; this is then used by default to reduce cost of initializing a lexer. int n; for (n=0; ; n++) if (standardtokens[n].string == NULL || strlen(standardtokens[n].string)==0) break; qsort(standardtokens, n, sizeof(tokendefn), _lex_tokndefncmp); // Retain the number of standardtokens nstandardtokens = n; /* Lexer errors */ morpho_defineerror(LEXER_UNRECOGNIZEDTOKEN, ERROR_LEX, LEXER_UNRECOGNIZEDTOKEN_MSG); morpho_defineerror(LEXER_UNTERMINATEDCOMMENT, ERROR_LEX, LEXER_UNTERMINATEDCOMMENT_MSG); morpho_defineerror(LEXER_UNTERMINATEDSTRING, ERROR_LEX, LEXER_UNTERMINATEDSTRING_MSG); } ================================================ FILE: src/support/lex.h ================================================ /** @file lex.h * @author T J Atherton and others (see below) * * @brief Lexer */ #ifndef lex_h #define lex_h #include #include "varray.h" #include "error.h" /** The lexer breaks an input stream into tokens, classifying them as it goes. */ typedef struct slexer lexer; /* ------------------------------------------------------- * Tokens * ------------------------------------------------------- */ #define TOKEN_NONE -1 /** Token types are left as a generic int to facilitate reprogrammability in the future */ typedef int tokentype; /** A token */ typedef struct { tokentype type; /** Type of the token */ const char *start; /** Start of the token */ unsigned int length; /** Its length */ /* Position of the token in the source */ int line; /** Source line */ int posn; /** Character position of the end of the token */ } token; /** Literal for a blank token */ #define TOKEN_BLANK ((token) {.type=TOKEN_NONE, .start=NULL, .length=0, .line=0, .posn=0} ) /* ------------------------------------------------------- * Token processing functions * ------------------------------------------------------- */ /** Token processing functions are called after recording it. */ typedef bool (* processtokenfn) (lexer *l, token *tok, error *err); /* ------------------------------------------------------- * Token definitions * ------------------------------------------------------- */ typedef struct { char *string; // String defining the token tokentype type; // Token type processtokenfn processfn; // Optional processfunction to call } tokendefn; DECLARE_VARRAY(tokendefn, tokendefn); /* ------------------------------------------------------- * Lexer data structure * ------------------------------------------------------- */ /** @brief Store the current configuration of a lexer */ struct slexer { const char* start; /** Starting point to lex */ const char* current; /** Current point */ int line; /** Line number */ int posn; /** Character position in line */ bool matchkeywords; /** Whether to match keywords or not; default is true */ bool stringinterpolation; /** Whether to perform string interpolation */ processtokenfn whitespacefn; /** Called to skip whitespace */ processtokenfn prefn; /** Called before attempting to match the token list */ tokentype eoftype; /** End of file marker */ tokentype inttype; /** Integers */ tokentype flttype; /** Floats */ tokentype imagtype; /** Imaginary numbers */ tokentype symboltype; /** Symbol numbers */ int interpolationlevel; /** Level of string interpolation */ tokendefn *defns; /** Pointer to token defintions in use */ int ndefns; /** Number of token defintions in use */ varray_tokendefn defnstore; /** Used to hold custom tokens */ } ; /* ------------------------------------------------------- * Morpho token types * ------------------------------------------------------- */ /** Enum listing standard token types. Each token will be mapped to a parserule in the parser */ enum { /* New line */ TOKEN_NEWLINE, /* Question mark */ TOKEN_QUESTION, /* Literals */ TOKEN_STRING, TOKEN_INTERPOLATION, TOKEN_INTEGER, TOKEN_NUMBER, TOKEN_SYMBOL, /* Brackets */ TOKEN_LEFTPAREN, TOKEN_RIGHTPAREN, TOKEN_LEFTSQBRACKET, TOKEN_RIGHTSQBRACKET, TOKEN_LEFTCURLYBRACKET, TOKEN_RIGHTCURLYBRACKET, /* Delimiters */ TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_COMMA, /* Operators */ TOKEN_PLUS, TOKEN_MINUS, TOKEN_STAR, TOKEN_SLASH, TOKEN_CIRCUMFLEX, TOKEN_PLUSPLUS, TOKEN_MINUSMINUS, TOKEN_PLUSEQ, TOKEN_MINUSEQ, TOKEN_STAREQ, TOKEN_SLASHEQ, TOKEN_HASH, TOKEN_AT, /* Other symbols */ TOKEN_QUOTE, TOKEN_DOT, TOKEN_DOTDOT, TOKEN_DOTDOTDOT, TOKEN_EXCLAMATION, TOKEN_AMP, TOKEN_VBAR, TOKEN_DBLAMP, TOKEN_DBLVBAR, TOKEN_EQUAL, TOKEN_EQ, TOKEN_NEQ, TOKEN_LT, TOKEN_GT, TOKEN_LTEQ, TOKEN_GTEQ, /* Keywords */ TOKEN_TRUE, TOKEN_FALSE, TOKEN_NIL, TOKEN_SELF, TOKEN_SUPER, TOKEN_IMAG, TOKEN_PRINT, TOKEN_VAR, TOKEN_IF, TOKEN_ELSE, TOKEN_IN, TOKEN_WHILE, TOKEN_FOR, TOKEN_DO, TOKEN_BREAK, TOKEN_CONTINUE, TOKEN_FUNCTION, TOKEN_RETURN, TOKEN_CLASS, TOKEN_IMPORT, TOKEN_AS, TOKEN_IS, TOKEN_WITH, TOKEN_TRY, TOKEN_CATCH, /* Shebangs at start of script */ TOKEN_SHEBANG, /* Errors and other statuses */ TOKEN_INCOMPLETE, TOKEN_EOF }; /* ------------------------------------------------------- * Lex error messages * ------------------------------------------------------- */ #define LEXER_UNRECOGNIZEDTOKEN "UnrgnzdTkn" #define LEXER_UNRECOGNIZEDTOKEN_MSG "Unrecognized token." #define LEXER_UNTERMINATEDCOMMENT "UntrmComm" #define LEXER_UNTERMINATEDCOMMENT_MSG "Unterminated multiline comment '/*'." #define LEXER_UNTERMINATEDSTRING "UntrmStrng" #define LEXER_UNTERMINATEDSTRING_MSG "Unterminated string." /* ------------------------------------------------------- * Library functions to support customizable lexers * ------------------------------------------------------- */ bool lex_findtoken(lexer *l, tokendefn **defn); bool lex_matchtoken(lexer *l, tokendefn **defn); void lex_recordtoken(lexer *l, tokentype type, token *tok); char lex_advance(lexer *l); bool lex_back(lexer *l); bool lex_isatend(lexer *l); bool lex_isalpha(char c); bool lex_isdigit(char c); bool lex_isspace(char c); char lex_peek(lexer *l); char lex_peekahead(lexer *l, int n); char lex_peekprevious(lexer *l); void lex_newline(lexer *l); bool lex_skipshebang(lexer *l); /* ------------------------------------------------------- * Lex interface * ------------------------------------------------------- */ // Initialize and clear a lexer structure void lex_init(lexer *l, const char *start, int line); void lex_clear(lexer *l); // Configure lexer void lex_settokendefns(lexer *l, tokendefn *defns); void lex_seteof(lexer *l, tokentype eoftype); tokentype lex_eof(lexer *l); void lex_setnumbertype(lexer *l, tokentype inttype, tokentype flttype, tokentype imagtype); void lex_setsymboltype(lexer *l, tokentype symboltype); tokentype lex_symboltype(lexer *l); void lex_setstringinterpolation(lexer *l, bool interpolation); void lex_setmatchkeywords(lexer *l, bool match); bool lex_matchkeywords(lexer *l); void lex_setwhitespacefn(lexer *l, processtokenfn whitespacefn); void lex_setprefn(lexer *l, processtokenfn prefn); // Get information about a token bool lex_tokeniskeyword(lexer *l, token *tok); // Obtain the next token bool lex(lexer *l, token *tok, error *err); // Initialization/finalization void lex_initialize(void); void lex_finalize(void); #endif /* lex_h */ ================================================ FILE: src/support/memory.c ================================================ /** @file memory.c * @author T J Atherton * * @brief Morpho memory allocator */ #include "memory.h" /** @brief Generic allocator function * @param old A previously allocated pointer, or NULL to allocate new memory * @param oldsize The previously allocated size * @param newsize New size to allocate * @returns A pointer to allocated memory, or NULL on failure. */ void *morpho_allocate(void *old, size_t oldsize, size_t newsize) { if (newsize == 0) { free(old); return NULL; } return realloc(old, newsize); } ================================================ FILE: src/support/memory.h ================================================ /** @file memory.h * @author T J Atherton * * @brief Morpho memory allocator */ #ifndef memory_h #define memory_h #include /** Macro to redirect malloc through our memory management */ #define MORPHO_MALLOC(size) morpho_allocate(NULL, 0, size) /** Macro to redirect free through our memory management */ #define MORPHO_FREE(x) morpho_allocate(x, 0, 0) /** Macro to redirect realloc through our memory management */ #define MORPHO_REALLOC(x, size) morpho_allocate(x, 0, size) void *morpho_allocate(void *old, size_t oldsize, size_t newsize); #endif /* memory_h */ ================================================ FILE: src/support/parse.c ================================================ /** @file parse.c * @author T J Atherton * * @brief Parser */ #include #include #include #include #include #include "parse.h" #include "object.h" #include "common.h" #include "cmplx.h" #include "syntaxtree.h" /** Varrays of parse rules */ DEFINE_VARRAY(parserule, parserule) /** Macro to check return of a bool function */ #define PARSE_CHECK(f) if (!(f)) return false; /* ********************************************************************** * Parser utility functions * ********************************************************************** */ /** @brief Fills out the error record * @param p the parser * @param use_prev use the previous token? [this is the more typical usage] * @param id error id * @param ... additional data for sprintf. */ void parse_error(parser *p, bool use_prev, errorid id, ... ) { va_list args; token *tok = (use_prev ? &p->previous : &p->current); /** Only return the first error that occurs */ if (ERROR_FAILED(*p->err)) return; va_start(args, id); morpho_writeerrorwithid(p->err, id, NULL, tok->line, tok->posn, args); va_end(args); } /** @brief Advance the parser by one token * @param p the parser in use. * @returns true on success, false otherwise */ bool parse_advance(parser *p) { lexer *l = p->lex; p->previous=p->current; p->nl=false; for (;;) { if (!lex(l, &p->current, p->err)) return false; /* Skip any newlines encountered */ if (p->skipnewline && p->current.type==p->toknewline) { p->nl=true; continue; } else break; } return ERROR_SUCCEEDED(*p->err); } /** Saves the state of the parser and attached lexer */ void parse_savestate(parser *p, parser *op, lexer *ol) { *ol = *p->lex; // Save the state of the parser and lexer *op = *p; } /** Restores the parser from a saved position. @warning: You must take care to ensure no new objects have been allocated prior to calling this. */ void parse_restorestate(parser *op, lexer *ol, parser *out) { *out = *op; *out->lex = *ol; } /** @brief Continues parsing while tokens have a lower or equal precendece than a specified value. * @param p the parser in use * @param precendence precedence value to keep below or equal to * @returns syntaxtreeindx for the expression parsed */ bool parse_precedence(parser *p, precedence prec, void *out) { parsefunction prefixrule=NULL, infixrule=NULL; PARSE_CHECK(parse_advance(p)); parserule *rule = parse_getrule(p, p->previous.type); if (rule) prefixrule = rule->prefix; if (!rule || !prefixrule) { parse_error(p, true, PARSE_EXPECTEXPRESSION); return false; } PARSE_CHECK(prefixrule(p, out)); /* Now keep parsing while the tokens have lower precedence */ rule=parse_getrule(p, p->current.type); while (rule!=NULL && prec <= rule->precedence) { /* Break if a newline is encountered before a function call */ if (p->current.type==TOKEN_LEFTPAREN && p->nl) break; PARSE_CHECK(parse_advance(p)); infixrule = parse_getrule(p, p->previous.type)->infix; if (infixrule) { PARSE_CHECK(infixrule(p, out)) } else UNREACHABLE("No infix rule defined for this token type [check parser definition table]."); rule=parse_getrule(p, p->current.type); } return true; } /** Checks whether the current token matches a specified tokentype */ bool parse_checktoken(parser *p, tokentype type) { return p->current.type==type; } /** Checks whether the current token matches any of the specified tokentypes */ bool parse_checktokenmulti(parser *p, int n, tokentype *type) { for (int i=0; icurrent.type==type[i]) return true; } return false; } /** Checks whether the current token matches a given type and advances if so. */ bool parse_checktokenadvance(parser *p, tokentype type) { PARSE_CHECK(parse_checktoken(p, type)); PARSE_CHECK(parse_advance(p)); return true; } /** Checks whether the current token is a keyword */ bool parse_checktokeniskeywordadvance(parser *p) { PARSE_CHECK(lex_tokeniskeyword(p->lex, &p->current)); PARSE_CHECK(parse_advance(p)); return true; } /** @brief Checks if the next token has the required type and advance if it does, otherwise generates an error. * @param p the parser in use * @param type type to check * @param id error id to generate if the token doesn't match * @returns true on success */ bool parse_checkrequiredtoken(parser *p, tokentype type, errorid id) { if (parse_checktoken(p, type)) { PARSE_CHECK(parse_advance(p)); return true; } if (id!=ERROR_NONE) parse_error(p, false, id); return false; } /** @brief Checks if the next token has a specific type and if it does generates an error. * @param p the parser in use * @param type type to check * @param id error id to generate if the token is found doesn't match * @returns true if the disallowed token was found */ bool parse_checkdisallowedtoken(parser *p, tokentype type, errorid id) { if (parse_checktoken(p, type)) { parse_error(p, true, id); return true; } return false; } /** Converts a hex string to a character code, outputting it into a varray @param[in] p - the current parser @param[in] codestr - code string to parse @param[in] nhex - number of hex characters to parse @param[in] raw - returns a raw ascii byte, rather than the utf8 encoded character @param[out] out - characters are added @returns true on success, false on failure */ bool parse_codepointfromhex(parser *p, const char *codestr, int nhex, bool raw, varray_char *out) { char in[nhex+1]; for (int j=0; jprevious.start; varray_char str; varray_charinit(&str); for (unsigned int i=start, nbytes; i1) { // Unicode literals varray_charadd(&str, (char *) &input[i], nbytes); } else if (input[i]=='\n') { // Newlines are ok varray_charwrite(&str, input[i]); } else if (iscntrl((unsigned char) input[i])) { // Unescaped control codes are not parse_error(p, true, PARSE_UNESCPDCTRL); goto parse_stringfromtokencleanup; } else if (nbytes==1 && input[i]=='\\') { // Escape sequence i++; switch (input[i]) { case 'b': varray_charwrite(&str, '\b'); break; case 'f': varray_charwrite(&str, '\f'); break; case 'n': varray_charwrite(&str, '\n'); break; case 'r': varray_charwrite(&str, '\r'); break; case 't': varray_charwrite(&str, '\t'); break; case 'u': if (!parse_codepointfromhex(p, &input[i+1], 4, false, &str)) goto parse_stringfromtokencleanup; i+=4; break; case 'U': if (!parse_codepointfromhex(p, &input[i+1], 8, false, &str)) goto parse_stringfromtokencleanup; i+=8; break; case 'x': if (!parse_codepointfromhex(p, &input[i+1], 2, true, &str)) goto parse_stringfromtokencleanup; i+=2; break; default: varray_charwrite(&str, input[i]); break; } } else varray_charwrite(&str, input[i]); // Any other single character } success=true; if (out) { *out = object_stringfromvarraychar(&str); if (!(MORPHO_ISSTRING(*out))) parse_error(p, true, ERROR_ALLOCATIONFAILED); } parse_stringfromtokencleanup: varray_charclear(&str); return success; } /** Parses the previous token into a value with no processing. */ value parse_tokenasstring(parser *p) { value s = object_stringfromcstring(p->previous.start, p->previous.length); if (MORPHO_ISNIL(s)) parse_error(p, true, ERROR_ALLOCATIONFAILED, OBJECT_SYMBOLLABEL); return s; } /** Parses the next token as a symbol regardless of whether it is a keyword; returns true on success */ bool parse_tokenassymbol(parser *p) { bool oldmatch = lex_matchkeywords(p->lex); lex_setmatchkeywords(p->lex, false); bool success=parse_checktokenadvance(p, lex_symboltype(p->lex)); lex_setmatchkeywords(p->lex, oldmatch); // Restore state of lexer return success; } /** Adds a node to the syntax tree. */ bool parse_addnode(parser *p, syntaxtreenodetype type, value content, token *tok, syntaxtreeindx left, syntaxtreeindx right, syntaxtreeindx *out) { syntaxtree *tree = (syntaxtree *) p->out; if (!syntaxtree_addnode(tree, type, content, tok->line, tok->posn, left, right, out)) { parse_error(p, true, ERROR_ALLOCATIONFAILED); return false; } p->left=*out; /* Record this for a future infix operator to catch */ return true; } /** Retrieve a syntaxtree node from an index. */ syntaxtreenode *parse_lookupnode(parser *p, syntaxtreeindx i) { return syntaxtree_nodefromindx((syntaxtree *) p->out, i); } /** Checks whether a long created by strtol is within range. Raises a parse error if not and returns false. */ bool parse_validatestrtol(parser *p, long f) { if ( ((f==LONG_MAX || f==LONG_MIN) && errno==ERANGE) || // Check for underflow or overflow f>INT_MAX || fprevious.start, NULL, 10); return parse_validatestrtol(p, *i); } /** Converts an token to a double, returning true on success */ bool parse_tokentodouble(parser *p, double *x) { *x = strtod(p->previous.start, NULL); return parse_validatestrtod(p, *x); } /** Increments the recursion depth counter. If it exceeds PARSE_RECURSIONLIMIT an error is generated */ bool parse_incrementrecursiondepth(parser *p) { if (!(p->recursiondepthmaxrecursiondepth)) { parse_error(p, false, PARSE_RCRSNLMT); return false; } p->recursiondepth++; return true; } /** Decrements the recursion depth counter. */ bool parse_decrementrecursiondepth(parser *p) { if (p->recursiondepth>0) p->recursiondepth--; return false; } /** Adds an object to the parser */ void parse_addobject(parser *p, value obj) { varray_valuewrite(&p->objects, obj); } /** Frees objects generated by the parser */ void parse_freeobjects(parser *p) { for (unsigned int i=0; iobjects.count; i++) morpho_freeobject(p->objects.data[i]); } /** Clears the object list */ void parse_clearobjects(parser *p) { varray_valueclear(&p->objects); } /* ------------------------------------------ * Parser implementation functions (parselets) * ------------------------------------------- */ bool parse_arglist(parser *p, tokentype rightdelimiter, unsigned int *nargs, void *out); bool parse_variable(parser *p, errorid id, void *out); bool parse_statementterminator(parser *p); bool parse_checkstatementterminator(parser *p); bool parse_synchronize(parser *p); /* ------------------------------------------ * Utility functions for this parser * ------------------------------------------- */ /** @brief Parses a list of expressions * @param[in] p the parser * @param[in] rightdelimiter token type that denotes the end of the arguments list * @param[out] nel the number of elements * @details Note that the arguments are output in reverse order, i.e. the * first argument is deepest in the tree. */ bool parse_expressionlist(parser *p, tokentype rightdelimiter, unsigned int *nel, void *out) { syntaxtreeindx prev=SYNTAXTREE_UNCONNECTED, current=SYNTAXTREE_UNCONNECTED; token start = p->current; unsigned int n=0; if (!parse_checktoken(p, rightdelimiter)) { do { PARSE_CHECK(parse_pseudoexpression(p, ¤t)); PARSE_CHECK(parse_addnode(p, NODE_ARGLIST, MORPHO_NIL, &start, prev, current, ¤t)); prev = current; n++; } while (parse_checktokenadvance(p, TOKEN_COMMA)); } /* Output the number of args */ if (nel) *nel=n; *((syntaxtreeindx *) out)=current; return true; } /** @brief Parses an argument list * @param[in] p the parser * @param[in] rightdelimiter token type that denotes the end of the arguments list * @param[out] nargs the number of arguments * @param[out] out the syntaxtreeindex, updated * @returns true on success * @details Note that the arguments are output in reverse order, i.e. the * first argument is deepest in the tree. */ bool parse_arglist(parser *p, tokentype rightdelimiter, unsigned int *nargs, void *out) { syntaxtreeindx prev=SYNTAXTREE_UNCONNECTED, current=SYNTAXTREE_UNCONNECTED; token start = p->current; unsigned int n=0; bool varg=false; if (!parse_checktoken(p, rightdelimiter)) { do { bool vargthis = false; if (parse_checktokenadvance(p, TOKEN_DOTDOTDOT)) { // If we are trying to index something // then ... represents an open range if (rightdelimiter == TOKEN_RIGHTSQBRACKET) { } else if (varg) { parse_error(p, true, PARSE_ONEVARPR); return false; } varg = true; vargthis = true; } if (parse_checktokenadvance(p, TOKEN_SYMBOL)) { PARSE_CHECK(parse_symbol(p, ¤t)); if (parse_checktokenadvance(p, TOKEN_SYMBOL)) { // If two symbols in a row, then the first is the type syntaxtreeindx label; PARSE_CHECK(parse_symbol(p, &label)); PARSE_CHECK(parse_addnode(p, NODE_TYPE, MORPHO_NIL, &start, current, label, ¤t)); } else if (parse_checktokenadvance(p, TOKEN_DOT)) { // Symbol followed by dot is a type in a namespace syntaxtreeindx type, label; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_SYMBOL, PARSE_SYMBLEXPECTED)); PARSE_CHECK(parse_symbol(p, &type)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_SYMBOL, PARSE_SYMBLEXPECTED)); PARSE_CHECK(parse_symbol(p, &label)); PARSE_CHECK(parse_addnode(p, NODE_DOT, MORPHO_NIL, &start, current, type, ¤t)); PARSE_CHECK(parse_addnode(p, NODE_TYPE, MORPHO_NIL, &start, current, label, ¤t)); } else if (parse_checktokenadvance(p, TOKEN_EQUAL)) { // Symbol followed by equals is an optional argument syntaxtreeindx val; PARSE_CHECK(parse_pseudoexpression(p, &val)); PARSE_CHECK(parse_addnode(p, NODE_ASSIGN, MORPHO_NIL, &start, current, val, ¤t)); } } else PARSE_CHECK(parse_pseudoexpression(p, ¤t)); if (vargthis) PARSE_CHECK(parse_addnode(p, NODE_RANGE, MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, current, ¤t)); n++; PARSE_CHECK(parse_addnode(p, NODE_ARGLIST, MORPHO_NIL, &start, prev, current, ¤t)); prev = current; } while (parse_checktokenadvance(p, TOKEN_COMMA)); } /* Output the number of args */ if (nargs) *nargs=n; *((syntaxtreeindx *) out)=current; return true; } /** Parses a variable name, or raises and error if a symbol isn't found */ bool parse_variable(parser *p, errorid id, void *out) { PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_SYMBOL, id)); return parse_symbol(p, out); } /** Parses a reference that could be a symbol or a namespace.symbol reference */ bool parse_reference(parser *p, errorid errid, void *out) { PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_SYMBOL, errid)); syntaxtreeindx symbol, selector=SYNTAXTREE_UNCONNECTED; PARSE_CHECK(parse_symbol(p, &symbol)); if (parse_checktokenadvance(p, TOKEN_DOT)) { PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_SYMBOL, errid)); PARSE_CHECK(parse_symbol(p, &selector)); } if (selector!=SYNTAXTREE_UNCONNECTED) { PARSE_CHECK(parse_addnode(p, NODE_DOT, MORPHO_NIL, &p->previous, symbol, selector, &symbol)); } *((syntaxtreeindx *) out)=symbol; return true; } /** Parse a statement terminator */ bool parse_statementterminator(parser *p) { if (parse_checktoken(p, TOKEN_SEMICOLON)) { PARSE_CHECK(parse_advance(p)); } else if (p->nl || parse_checktoken(p, TOKEN_EOF) || parse_checktoken(p, TOKEN_RIGHTCURLYBRACKET)) { } else if (parse_checktoken(p, TOKEN_IN) || parse_checktoken(p, TOKEN_ELSE)) { } else { parse_error(p, true, PARSE_MISSINGSEMICOLONEXP); return false; } return true; } /** Checks whether a possible statement terminator is next */ bool parse_checkstatementterminator(parser *p) { return (parse_checktoken(p, TOKEN_SEMICOLON) || (p->nl) || parse_checktoken(p, TOKEN_EOF) || parse_checktoken(p, TOKEN_RIGHTCURLYBRACKET) || parse_checktoken(p, TOKEN_IN) ) ; } /** @brief Keep parsing til the end of a statement boundary. */ bool parse_synchronize(parser *p) { while (p->current.type!=TOKEN_EOF) { /** Align */ if (p->previous.type == TOKEN_SEMICOLON) return true; switch (p->current.type) { case TOKEN_PRINT: case TOKEN_IF: case TOKEN_WHILE: case TOKEN_FOR: case TOKEN_DO: case TOKEN_BREAK: case TOKEN_CONTINUE: case TOKEN_RETURN: case TOKEN_TRY: case TOKEN_CLASS: case TOKEN_FUNCTION: case TOKEN_VAR: return true; default: ; } PARSE_CHECK(parse_advance(p)); } return true; } /* ------------------------------------------ * Basic literals * ------------------------------------------- */ /** Parses nil */ bool parse_nil(parser *p, void *out) { return parse_addnode(p, NODE_NIL, MORPHO_NIL, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses an integer */ bool parse_integer(parser *p, void *out) { long f; PARSE_CHECK(parse_tokentointeger(p, &f)); return parse_addnode(p, NODE_INTEGER, MORPHO_INTEGER(f), &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a number */ bool parse_number(parser *p, void *out) { double f; PARSE_CHECK(parse_tokentodouble(p, &f)); return parse_addnode(p, NODE_FLOAT, MORPHO_FLOAT(f), &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parse a complex number */ bool parse_complex(parser *p, void *out) { double f; if (p->previous.length==2) { // just a bare im symbol f = 1; } else { PARSE_CHECK(parse_tokentodouble(p, &f)); } value c = MORPHO_OBJECT(object_newcomplex(0,f)); parse_addobject(p, c); return parse_addnode(p, NODE_IMAG, c, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a bool */ bool parse_bool(parser *p, void *out) { return parse_addnode(p, NODE_BOOL, MORPHO_BOOL((p->previous.type==TOKEN_TRUE ? true : false)), &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a self token */ bool parse_self(parser *p, void *out) { return parse_addnode(p, NODE_SELF, MORPHO_NIL, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a super token */ bool parse_super(parser *p, void *out) { if (!parse_checktoken(p, TOKEN_DOT)) { parse_error(p, false, PARSE_EXPECTDOTAFTERSUPER); return false; } return parse_addnode(p, NODE_SUPER, MORPHO_NIL, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a symbol */ bool parse_symbol(parser *p, void *out) { value s = object_stringfromcstring(p->previous.start, p->previous.length); parse_addobject(p, s); if (MORPHO_ISNIL(s)) { parse_error(p, true, ERROR_ALLOCATIONFAILED, OBJECT_SYMBOLLABEL); return false; } return parse_addnode(p, NODE_SYMBOL, s, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parses a string */ bool parse_string(parser *p, void *out) { value s; PARSE_CHECK(parse_stringfromtoken(p, 1, p->previous.length-1, &s)); parse_addobject(p, s); return parse_addnode(p, NODE_STRING, s, &p->previous, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** @brief: Parses a dictionary. * @details Dictionaries are a list of key/value pairs, { key : value, key: value } */ bool parse_dictionary(parser *p, void *out) { syntaxtreeindx last=SYNTAXTREE_UNCONNECTED; parse_addnode(p, NODE_DICTIONARY, MORPHO_NIL, &p->current, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, &last); while (!parse_checktoken(p, TOKEN_RIGHTCURLYBRACKET) && !parse_checktoken(p, TOKEN_EOF)) { syntaxtreeindx key, val, pair; token tok=p->current; // Keep track of the token that corresponds to each key/value pair /* Parse the key/value pair */ PARSE_CHECK(parse_expression(p, &key)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_COLON, PARSE_DCTSPRTR)); PARSE_CHECK(parse_expression(p, &val)); /* Create an entry node */ PARSE_CHECK(parse_addnode(p, NODE_DICTENTRY, MORPHO_NIL, &tok, key, val, &pair)); /* These are linked into a chain of dictionary nodes */ PARSE_CHECK(parse_addnode(p, NODE_DICTIONARY, MORPHO_NIL, &tok, last, pair, &last)); if (!parse_checktoken(p, TOKEN_RIGHTCURLYBRACKET)) { PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_COMMA, PARSE_MSSNGCOMMA)); } }; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTCURLYBRACKET, PARSE_DCTTRMNTR)); *((syntaxtreeindx *) out) = last; return true; } /** Parses a string interpolation. */ bool parse_interpolation(parser *p, void *out) { token tok = p->previous; /* First copy the string */ value s; PARSE_CHECK(parse_stringfromtoken(p, 1, tok.length-2, &s)); parse_addobject(p, s); syntaxtreeindx left=SYNTAXTREE_UNCONNECTED, right=SYNTAXTREE_UNCONNECTED; if (!(parse_checktoken(p, TOKEN_STRING) && *p->current.start=='}')) { PARSE_CHECK(parse_expression(p, &left)); } if (parse_checktokenadvance(p, TOKEN_STRING)) { if (!parse_string(p, &right)) return false; } else if (parse_checktokenadvance(p, TOKEN_INTERPOLATION)) { if (!parse_interpolation(p, &right)) return false; } else { parse_error(p, false, PARSE_INCOMPLETESTRINGINT); return false; } return parse_addnode(p, NODE_INTERPOLATION, s, &tok, left, right, (syntaxtreeindx *) out); } /** Helper function to parse a tuple @param[in] p - current parser @param[in] start - token for starting '(' of the tuple @param[in] first - syntax tree index of first expression, already parsed @param[out] out - syntax tree entry of the output tuple node @returns true on success */ bool parse_tuple(parser *p, token *start, syntaxtreeindx first, void *out) { syntaxtreeindx prev=first, current=SYNTAXTREE_UNCONNECTED; // First entry was already parsed by calling function PARSE_CHECK(parse_addnode(p, NODE_ARGLIST, MORPHO_NIL, &p->previous, prev, current, ¤t)); if (!parse_checktoken(p, TOKEN_RIGHTPAREN)) { do { PARSE_CHECK(parse_pseudoexpression(p, ¤t)); PARSE_CHECK(parse_addnode(p, NODE_ARGLIST, MORPHO_NIL, &p->previous, prev, current, ¤t)); prev = current; } while (parse_checktokenadvance(p, TOKEN_COMMA)); } PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_MSSNGSQBRC)); return parse_addnode(p, NODE_TUPLE, MORPHO_NIL, start, SYNTAXTREE_UNCONNECTED, current, out); } /** Parses an expression in parentheses */ bool parse_grouping(parser *p, void *out) { token start = p->previous; syntaxtreeindx new; PARSE_CHECK(parse_pseudoexpression(p, &new)); syntaxtreenode *node = parse_lookupnode(p, new); // Detect a tuple from a comma after the first expression or if the // grouping encloses a tuple. if (parse_checktokenadvance(p, TOKEN_COMMA) || node->type==NODE_TUPLE) { return parse_tuple(p, &start, new, out); } PARSE_CHECK(parse_addnode(p, NODE_GROUPING, MORPHO_NIL, &p->previous, new, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_MISSINGPARENTHESIS)); return true; } /** Parse a unary operator */ bool parse_unary(parser *p, void *out) { token start = p->previous; syntaxtreenodetype nodetype=NODE_LEAF; /* Determine which operator */ switch (start.type) { case TOKEN_MINUS: nodetype = NODE_NEGATE; break; case TOKEN_EXCLAMATION: nodetype = NODE_NOT; break; case TOKEN_AT: nodetype = NODE_BREAKPOINT; break; default: UNREACHABLE("unhandled unary operator [Check parser definition table]"); } /* Now add this node */ syntaxtreeindx right; PARSE_CHECK(parse_precedence(p, PREC_UNARY, &right)); return parse_addnode(p, nodetype, MORPHO_NIL, &start, right, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parse a binary operator */ bool parse_binary(parser *p, void *out) { token start = p->previous; syntaxtreenodetype nodetype=NODE_LEAF; enum {LEFT, RIGHT} assoc = LEFT; /* for left associative operators */ /* Determine which operator */ switch (start.type) { case TOKEN_EQUAL: nodetype = NODE_ASSIGN; assoc = RIGHT; break; case TOKEN_PLUS: nodetype = NODE_ADD; break; case TOKEN_MINUS: nodetype = NODE_SUBTRACT; break; case TOKEN_STAR: nodetype = NODE_MULTIPLY; break; case TOKEN_SLASH: nodetype = NODE_DIVIDE; break; case TOKEN_CIRCUMFLEX: nodetype = NODE_POW; assoc = RIGHT; break; case TOKEN_EQ: nodetype = NODE_EQ; break; case TOKEN_NEQ: nodetype = NODE_NEQ; break; case TOKEN_LT: nodetype = NODE_LT; break; case TOKEN_GT: nodetype = NODE_GT; break; case TOKEN_LTEQ: nodetype = NODE_LTEQ; break; case TOKEN_GTEQ: nodetype = NODE_GTEQ; break; case TOKEN_DOT: nodetype = NODE_DOT; break; case TOKEN_DBLAMP: nodetype = NODE_AND; break; case TOKEN_DBLVBAR: nodetype = NODE_OR; break; default: UNREACHABLE("unhandled binary operator [Check parser definition table]"); } parserule *rule=parse_getrule(p, start.type); syntaxtreeindx left=p->left; syntaxtreeindx right=SYNTAXTREE_UNCONNECTED; /* Check if we have a right hand side. */ if (parse_checktoken(p, TOKEN_EOF)) { parse_error(p, true, PARSE_INCOMPLETEEXPRESSION); return false; } else { if (nodetype==NODE_ASSIGN && parse_checktokenadvance(p, TOKEN_FUNCTION)) { PARSE_CHECK(parse_anonymousfunction(p, &right)); } else if (nodetype==NODE_DOT && parse_checktokeniskeywordadvance(p)) { PARSE_CHECK(parse_symbol(p, &right)); } else { PARSE_CHECK(parse_precedence(p, rule->precedence + (assoc == LEFT ? 1 : 0), &right)); } } /* Now add this node */ return parse_addnode(p, nodetype, MORPHO_NIL, &start, left, right, (syntaxtreeindx *) out); } /** Parse ternary operator */ bool parse_ternary(parser *p, void *out) { token start = p->previous; syntaxtreeindx cond=p->left; syntaxtreeindx left, right, outcomes; PARSE_CHECK(parse_expression(p, &left)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_COLON, PARSE_TRNRYMSSNGCOLON)); PARSE_CHECK(parse_expression(p, &right)); PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, left, right, &outcomes)); return parse_addnode(p, NODE_TERNARY, MORPHO_NIL, &start, cond, outcomes, (syntaxtreeindx *) out); } /** Parse operators like +=, -=, *= etc. */ bool parse_assignby(parser *p, void *out) { token start = p->previous; syntaxtreenodetype nodetype=NODE_LEAF; /* Determine which operator */ switch (start.type) { case TOKEN_PLUSEQ: nodetype = NODE_ADD; break; case TOKEN_MINUSEQ: nodetype = NODE_SUBTRACT; break; case TOKEN_STAREQ: nodetype = NODE_MULTIPLY; break; case TOKEN_SLASHEQ: nodetype = NODE_DIVIDE; break; default: UNREACHABLE("unhandled assignment operator [Check parser definition table]"); } parserule *rule=parse_getrule(p, start.type); syntaxtreeindx left=p->left; syntaxtreeindx right=SYNTAXTREE_UNCONNECTED; /* Check if we have a right hand side. */ if (parse_checktoken(p, TOKEN_EOF)) { parse_error(p, true, PARSE_INCOMPLETEEXPRESSION); return false; } else { PARSE_CHECK(parse_precedence(p, rule->precedence, &right)); } if (!parse_addnode(p, nodetype, MORPHO_NIL, &start, left, right, &right)) return false; /* Now add this node */ return parse_addnode(p, NODE_ASSIGN, MORPHO_NIL, &start, left, right, (syntaxtreeindx *) out); } /** Parses a range */ bool parse_range(parser *p, void *out) { token start = p->previous; bool inclusive = (start.type==TOKEN_DOTDOT); syntaxtreeindx left=p->left; syntaxtreeindx right; PARSE_CHECK(parse_expression(p, &right)); syntaxtreeindx new; PARSE_CHECK(parse_addnode(p, (inclusive ? NODE_INCLUSIVERANGE : NODE_RANGE), MORPHO_NIL, &start, left, right, &new)); if (parse_checktokenadvance(p, TOKEN_COLON)) { // Wrap in an outer NODE_RANGE syntaxtreeindx step; PARSE_CHECK(parse_expression(p, &step)); PARSE_CHECK(parse_addnode(p, NODE_RANGE, MORPHO_NIL, &start, new, step, &new)); } *((syntaxtreeindx *) out) = new; return true; } /** Parse a function call */ bool parse_call(parser *p, void *out) { token start = p->previous; syntaxtreeindx left=p->left; syntaxtreeindx right; unsigned int nargs; PARSE_CHECK(parse_expressionlist(p, TOKEN_RIGHTPAREN, &nargs, &right)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_CALLRGHTPARENMISSING)); return parse_addnode(p, NODE_CALL, MORPHO_NIL, &start, left, right, out); } /** Parse index index / \ symbol indices */ bool parse_index(parser *p, void *out) { token start = p->previous; syntaxtreeindx left=p->left; syntaxtreeindx right; unsigned int nindx; PARSE_CHECK(parse_expressionlist(p, TOKEN_RIGHTSQBRACKET, &nindx, &right)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTSQBRACKET, PARSE_CALLRGHTPARENMISSING)); return parse_addnode(p, NODE_INDEX, MORPHO_NIL, &start, left, right, (syntaxtreeindx *) out); } /** Parse list */ bool parse_list(parser *p, void *out) { token start = p->previous; syntaxtreeindx right; PARSE_CHECK(parse_expressionlist(p, TOKEN_RIGHTSQBRACKET, NULL, &right)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTSQBRACKET, PARSE_MSSNGSQBRC)); return parse_addnode(p, NODE_LIST, MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, right, out); } /** Parses an anonymous function */ bool parse_anonymousfunction(parser *p, void *out) { token start = p->previous; syntaxtreeindx args=SYNTAXTREE_UNCONNECTED, body=SYNTAXTREE_UNCONNECTED; /* Parameter list */ PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_FNLEFTPARENMISSING)); PARSE_CHECK(parse_arglist(p, TOKEN_RIGHTPAREN, NULL, &args)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_FNRGHTPARENMISSING)); /* Function body */ if (parse_checktokenadvance(p, TOKEN_LEFTCURLYBRACKET)) { // fn (x) { ... } PARSE_CHECK(parse_blockstatement(p, &body)); } else { PARSE_CHECK(parse_expression(p, &body)); // Short form: fn (x) x PARSE_CHECK(parse_addnode(p, NODE_RETURN, MORPHO_NIL, &start, body, SYNTAXTREE_UNCONNECTED, &body)); } return parse_addnode(p, NODE_FUNCTION, MORPHO_NIL, &start, args, body, out); } /** @brief: Parses a switch block * @details Switch blocks are key/statement pairs. Each pair is stored in a NODE_DICTIONARY list */ bool parse_switch(parser *p, void *out) { syntaxtreeindx last=SYNTAXTREE_UNCONNECTED; while(!parse_checktokenadvance(p, TOKEN_RIGHTCURLYBRACKET) && !parse_checktoken(p, TOKEN_EOF)) { syntaxtreeindx key, statements, pair; token tok=p->current; // Keep track of the token that corresponds to each key/value pair /* Parse the key/value pair */ PARSE_CHECK(parse_expression(p, &key)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_COLON, PARSE_SWTCHSPRTR)); tokentype terminators[] = { TOKEN_STRING, TOKEN_INTEGER, TOKEN_NUMBER, TOKEN_TRUE, TOKEN_FALSE, TOKEN_NIL, TOKEN_RIGHTCURLYBRACKET }; PARSE_CHECK(parse_declarationmulti(p, 7, terminators, &statements)); /* Create an entry node */ PARSE_CHECK(parse_addnode(p, NODE_DICTENTRY, MORPHO_NIL, &tok, key, statements, &pair)); /* These are linked into a chain of dictionary nodes */ PARSE_CHECK(parse_addnode(p, NODE_DICTIONARY, MORPHO_NIL, &tok, last, pair, &last)); }; *((syntaxtreeindx *) out) = last; return true; } /* ------------------------------- * Declarations * ------------------------------- */ /** Parses a variable declaration */ bool parse_vardeclaration(parser *p, void *out) { syntaxtreeindx symbol, initializer, new=SYNTAXTREE_UNCONNECTED, last=SYNTAXTREE_UNCONNECTED; do { token start = p->previous; PARSE_CHECK(parse_variable(p, PARSE_VAREXPECTED, &symbol)); if (parse_checktokenadvance(p, TOKEN_LEFTSQBRACKET)) { if (!parse_index(p, &symbol)) return false; } if (parse_checktokenadvance(p, TOKEN_EQUAL)) { PARSE_CHECK(parse_pseudoexpression(p, &initializer)); } else initializer=SYNTAXTREE_UNCONNECTED; PARSE_CHECK(parse_addnode(p, NODE_DECLARATION, MORPHO_NIL, &start, symbol, initializer, &new)); if (last!=SYNTAXTREE_UNCONNECTED) { PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, last, new, &new)); } last=new; } while (parse_checktokenadvance(p, TOKEN_COMMA)); PARSE_CHECK(parse_statementterminator(p)); *((syntaxtreeindx *) out) = new; return true; } /** Parses a possible typed var declaration */ bool parse_typedvardeclaration(parser *p, void *out) { syntaxtreeindx new=SYNTAXTREE_UNCONNECTED; token start = p->previous; lexer ol; // Store the state of the parser parser op; parse_savestate(p, &op, &ol); parse_advance(p); if (parse_checktoken(p, TOKEN_SYMBOL)) { // It is a typed variable declaration syntaxtreeindx type=SYNTAXTREE_UNCONNECTED, var=SYNTAXTREE_UNCONNECTED; PARSE_CHECK(parse_symbol(p, &type)); PARSE_CHECK(parse_vardeclaration(p, &var)); PARSE_CHECK(parse_addnode(p, NODE_TYPE, MORPHO_NIL, &start, type, var, &new)); } else if (parse_checktokenadvance(p, TOKEN_DOT) && // Check that we actually match a var declaration parse_checktokenadvance(p, TOKEN_SYMBOL) && parse_checktokenadvance(p, TOKEN_SYMBOL)) { parse_restorestate(&op, &ol, p); // Match successful, so rewind the parser parse_advance(p); syntaxtreeindx namespace=SYNTAXTREE_UNCONNECTED, type=SYNTAXTREE_UNCONNECTED, var=SYNTAXTREE_UNCONNECTED; PARSE_CHECK(parse_symbol(p, &namespace)); parse_advance(p); parse_advance(p); // Advance over TOKEN_DOT PARSE_CHECK(parse_symbol(p, &type)); PARSE_CHECK(parse_vardeclaration(p, &var)); PARSE_CHECK(parse_addnode(p, NODE_DOT, MORPHO_NIL, &start, namespace, type, &type)); PARSE_CHECK(parse_addnode(p, NODE_TYPE, MORPHO_NIL, &start, type, var, &new)); } else { // Perhaps it was really an expression statement parse_restorestate(&op, &ol, p); PARSE_CHECK(parse_statement(p, &new)); } *((syntaxtreeindx *) out) = new; return true; } /** Parses a function declaration */ bool parse_functiondeclaration(parser *p, void *out) { value name=MORPHO_NIL; token start = p->previous; syntaxtreeindx args=SYNTAXTREE_UNCONNECTED, body=SYNTAXTREE_UNCONNECTED; /* Function name */ if (parse_checktokenadvance(p, TOKEN_SYMBOL) || parse_checktokeniskeywordadvance(p)) { name=parse_tokenasstring(p); parse_addobject(p, name); } else { parse_error(p, false, PARSE_FNNAMEMISSING); return false; } /* Parameter list */ PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_FNLEFTPARENMISSING)); PARSE_CHECK(parse_arglist(p, TOKEN_RIGHTPAREN, NULL, &args)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_FNRGHTPARENMISSING)); /* Function body */ PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTCURLYBRACKET, PARSE_FNLEFTCURLYMISSING)); PARSE_CHECK(parse_blockstatement(p, &body)); return parse_addnode(p, NODE_FUNCTION, name, &start, args, body, (syntaxtreeindx *) out); } /* Parses a class declaration */ bool parse_classdeclaration(parser *p, void *out) { value name=MORPHO_NIL; syntaxtreeindx sclass=SYNTAXTREE_UNCONNECTED; token start = p->previous; /* Class name */ if (parse_checktokenadvance(p, TOKEN_SYMBOL)) { name=parse_tokenasstring(p); parse_addobject(p, name); } else { parse_error(p, false, PARSE_EXPECTCLASSNAME); return false; } /* Extract a superclass name */ if (parse_checktokenadvance(p, TOKEN_LT) || parse_checktokenadvance(p, TOKEN_IS)) { PARSE_CHECK(parse_reference(p, PARSE_EXPECTSUPER, &sclass)); } if (parse_checktokenadvance(p, TOKEN_WITH)) { do { syntaxtreeindx smixin; PARSE_CHECK(parse_reference(p, PARSE_EXPECTMIXIN, &smixin)); PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &p->previous, smixin, sclass, &sclass)); // Mixins end up being recorded in reverse order } while (parse_checktokenadvance(p, TOKEN_COMMA)); } PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTCURLYBRACKET, PARSE_CLASSLEFTCURLYMISSING)); /* Method declarations */ syntaxtreeindx last=SYNTAXTREE_UNCONNECTED, current=SYNTAXTREE_UNCONNECTED; while (!parse_checktoken(p, TOKEN_RIGHTCURLYBRACKET) && !parse_checktoken(p, TOKEN_EOF)) { PARSE_CHECK(parse_functiondeclaration(p, ¤t)); /* If we now have more than one node, insert a sequence node */ if (last!=SYNTAXTREE_UNCONNECTED) { PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, last, current, ¤t)); } last = current; } PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTCURLYBRACKET, PARSE_CLASSRGHTCURLYMISSING)); return parse_addnode(p, NODE_CLASS, name, &start, sclass, current, (syntaxtreeindx *) out); } /** Parse an import qualifier list. This list consists of elements introduced by for and as in any order. */ bool parse_importqualifierlist(parser *p, void *out) { syntaxtreeindx list=SYNTAXTREE_UNCONNECTED; bool asincluded=false; // Only one as is allowed while (!parse_checkstatementterminator(p) && !parse_checktoken(p, TOKEN_COMMA)) { if (parse_checktokenadvance(p, TOKEN_AS)) { if (asincluded) { // Only one 'as' allowed per import parse_error(p, true, PARSE_IMPORTMLTPLAS); return false; } if (parse_checktokenadvance(p, TOKEN_SYMBOL)) { syntaxtreeindx symbl; PARSE_CHECK(parse_symbol(p, &symbl)); PARSE_CHECK(parse_addnode(p, NODE_AS, MORPHO_NIL, &p->previous, symbl, list, &list)); } else { parse_error(p, true, PARSE_IMPORTASSYMBL); return false; } asincluded=true; } else if (parse_checktokenadvance(p, TOKEN_FOR)) { do { if (parse_checktokenadvance(p, TOKEN_SYMBOL)) { syntaxtreeindx symbl; PARSE_CHECK(parse_symbol(p, &symbl)); PARSE_CHECK(parse_addnode(p, NODE_FOR, MORPHO_NIL, &p->previous, symbl, list, &list)); } else { parse_error(p, true, PARSE_IMPORTFORSYMBL); return false; } } while (parse_checktokenadvance(p, TOKEN_COMMA)); } else { parse_error(p, true, PARSE_IMPORTUNEXPCTDTOK); return false; } } *((syntaxtreeindx *) out)=list; return true; } /** Parse an import declaration. Each import has the following structure: * IMPORT * / \ * module import qualifier list * These are chained together as sequence nodes if there's more than one */ bool parse_importdeclaration(parser *p, void *out) { syntaxtreeindx prev=SYNTAXTREE_UNCONNECTED, // Use to construct list current=SYNTAXTREE_UNCONNECTED; do { syntaxtreeindx modulename=SYNTAXTREE_UNCONNECTED, qualifier=SYNTAXTREE_UNCONNECTED; token start = p->previous; if (parse_checktokenadvance(p, TOKEN_STRING)) { PARSE_CHECK(parse_string(p, &modulename)); } else if (parse_checktokenadvance(p, TOKEN_SYMBOL)){ PARSE_CHECK(parse_symbol(p, &modulename)); } else { parse_error(p, true, PARSE_IMPORTMISSINGNAME); return false; } PARSE_CHECK(parse_importqualifierlist(p, &qualifier)); PARSE_CHECK(parse_addnode(p, NODE_IMPORT, MORPHO_NIL, &start, modulename, qualifier, ¤t)); PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, prev, current, ¤t)); prev = current; } while (parse_checktokenadvance(p, TOKEN_COMMA)); PARSE_CHECK(parse_statementterminator(p)); *((syntaxtreeindx *) out)=current; return true; } /* ------------------------------- * Statements * ------------------------------- */ /** Parse a print statement */ bool parse_printstatement(parser *p, void *out) { token start = p->previous; syntaxtreeindx left; PARSE_CHECK(parse_pseudoexpression(p, &left)); PARSE_CHECK(parse_statementterminator(p)); return parse_addnode(p, NODE_PRINT, MORPHO_NIL, &start, left, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parse an expression statement */ bool parse_expressionstatement(parser *p, void *out) { syntaxtreeindx new; PARSE_CHECK(parse_expression(p, &new)); PARSE_CHECK(parse_statementterminator(p)); *((syntaxtreeindx *) out) = new; return true; } /** @brief Parse a block statement. * @details This wraps up a sequence of statements in a SCOPE node: * SCOPE * / \ * - body **/ bool parse_blockstatement(parser *p, void *out) { syntaxtreeindx body = SYNTAXTREE_UNCONNECTED; token start = p->previous; tokentype terminator[] = { TOKEN_RIGHTCURLYBRACKET }; PARSE_CHECK(parse_declarationmulti(p, 1, terminator, &body)); if (parse_checktoken(p, TOKEN_EOF)) { parse_error(p, false, PARSE_INCOMPLETEEXPRESSION); return false; } else { PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTCURLYBRACKET, PARSE_MISSINGSEMICOLONEXP)); } return parse_addnode(p, NODE_SCOPE, MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, body, out); } /** Parse an if statement */ bool parse_ifstatement(parser *p, void *out) { syntaxtreeindx cond=SYNTAXTREE_UNCONNECTED, then=SYNTAXTREE_UNCONNECTED, els=SYNTAXTREE_UNCONNECTED; token start = p->previous; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_IFLFTPARENMISSING)); PARSE_CHECK(parse_expression(p, &cond)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_IFRGHTPARENMISSING)); token thentok = p->current; PARSE_CHECK(parse_statement(p, &then)); if (parse_checktoken(p, TOKEN_ELSE)) { PARSE_CHECK(parse_advance(p)); PARSE_CHECK(parse_statement(p, &els)); /* Create an additional node that contains both statements */ PARSE_CHECK(parse_addnode(p, NODE_THEN, MORPHO_NIL, &thentok, then, els, &then)); } return parse_addnode(p, NODE_IF, MORPHO_NIL, &start, cond, then, out); } /** Parse a while statement */ bool parse_whilestatement(parser *p, void *out) { syntaxtreeindx cond=SYNTAXTREE_UNCONNECTED, body=SYNTAXTREE_UNCONNECTED; token start = p->previous; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_WHILELFTPARENMISSING)); PARSE_CHECK(parse_expression(p, &cond)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_IFRGHTPARENMISSING)); PARSE_CHECK(parse_statement(p, &body)); return parse_addnode(p, NODE_WHILE, MORPHO_NIL, &start, cond, body, out); } /** Parse a for statement. */ bool parse_forstatement(parser *p, void *out) { syntaxtreeindx init=SYNTAXTREE_UNCONNECTED, // Initializer cond=SYNTAXTREE_UNCONNECTED, // Condition body=SYNTAXTREE_UNCONNECTED, // Loop body final=SYNTAXTREE_UNCONNECTED; // Final statement token start = p->current; bool forin=false, ret=false; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_FORLFTPARENMISSING)); if (parse_checktokenadvance(p, TOKEN_SEMICOLON)) { } else if (parse_checktokenadvance(p, TOKEN_VAR)) { PARSE_CHECK(parse_vardeclaration(p, &init)); } else { PARSE_CHECK(parse_expression(p, &init)); while (parse_checktokenadvance(p, TOKEN_COMMA)) { syntaxtreeindx new; PARSE_CHECK(parse_expressionstatement(p, &new)); parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &p->current, init, new, &init); } parse_checktokenadvance(p, TOKEN_SEMICOLON); } if (parse_checktokenadvance(p, TOKEN_IN)) { /* If its an for..in loop, parse the collection */ PARSE_CHECK(parse_expression(p, &cond)); forin=true; } else { /* Otherwise, parse the condition and final clause in a traditional for loop. */ if (!parse_checktokenadvance(p, TOKEN_SEMICOLON)) { PARSE_CHECK(parse_expressionstatement(p, &cond)); } if (!parse_checktoken(p, TOKEN_RIGHTPAREN)) { PARSE_CHECK(parse_expression(p, &final)); } } PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_FORRGHTPARENMISSING)); if (!parse_checkstatementterminator(p)) { PARSE_CHECK(parse_statement(p, &body)); } if (forin) { /* A for..in loop is parsed into the syntax tree as follows: * * forin * / \ * in body * / \ * init collection */ syntaxtreeindx innode; PARSE_CHECK(parse_addnode(p, NODE_IN, MORPHO_NIL, &start, init, cond, &innode)); ret=parse_addnode(p, NODE_FOR, MORPHO_NIL, &start, innode, body, (syntaxtreeindx *) out); } else { /* A traditional for loop is parsed into an equivalent while loop: * -> for (init; cond; inc) body; * * becomes * scope * \ * ; * / \ * init while * / \ * cond ; // The presence of the seq. indicates a for loop * / \ * body inc * */ syntaxtreeindx loop,whil,seq; PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, body, final, &loop)); PARSE_CHECK(parse_addnode(p, NODE_WHILE, MORPHO_NIL, &start, cond, loop, &whil)); PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, init, whil, &seq)); ret=parse_addnode(p, NODE_SCOPE, MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, seq, (syntaxtreeindx *) out); } return ret; } /** Parses a do...while loop */ bool parse_dostatement(parser *p, void *out) { syntaxtreeindx body=SYNTAXTREE_UNCONNECTED, // Loop body cond=SYNTAXTREE_UNCONNECTED; // Condition token start = p->current; if (!parse_statement(p, &body)) return false; PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_WHILE, PARSE_EXPCTWHL)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTPAREN, PARSE_WHILELFTPARENMISSING)); PARSE_CHECK(parse_expression(p, &cond)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_RIGHTPAREN, PARSE_IFRGHTPARENMISSING)); /* Optional statement terminator */ if (parse_checkstatementterminator(p)) { PARSE_CHECK(parse_statementterminator(p)); } return parse_addnode(p, NODE_DO, MORPHO_NIL, &start, body, cond, (syntaxtreeindx *) out); } /** Parses a break or continue statement */ bool parse_breakstatement(parser *p, void *out) { token start = p->previous; PARSE_CHECK(parse_statementterminator(p)); return parse_addnode(p, (start.type==TOKEN_BREAK ? NODE_BREAK: NODE_CONTINUE), MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parse a return statement */ bool parse_returnstatement(parser *p, void *out) { token start = p->previous; syntaxtreeindx left = SYNTAXTREE_UNCONNECTED; if (!parse_checkstatementterminator(p)) { PARSE_CHECK(parse_pseudoexpression(p, &left)); } PARSE_CHECK(parse_statementterminator(p)); return parse_addnode(p, NODE_RETURN, MORPHO_NIL, &start, left, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /** Parse a try/catch statement try / \ body catch block */ bool parse_trystatement(parser *p, void *out) { syntaxtreeindx try=SYNTAXTREE_UNCONNECTED, // Try block catch=SYNTAXTREE_UNCONNECTED; // Catch dictionary token start = p->current; PARSE_CHECK(parse_statement(p, &try)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_CATCH, PARSE_EXPCTCTCH)); PARSE_CHECK(parse_checkrequiredtoken(p, TOKEN_LEFTCURLYBRACKET, PARSE_CATCHLEFTCURLYMISSING)); PARSE_CHECK(parse_switch(p, &catch)); /* Optional statement terminator */ if (parse_checkstatementterminator(p)) { PARSE_CHECK(parse_statementterminator(p)); } return parse_addnode(p, NODE_TRY, MORPHO_NIL, &start, try, catch, (syntaxtreeindx *) out); } /** Parse a breakpoint statement */ bool parse_breakpointstatement(parser *p, void *out) { token start = p->previous; if (parse_checkstatementterminator(p)) { PARSE_CHECK(parse_statementterminator(p)); } return parse_addnode(p, NODE_BREAKPOINT, MORPHO_NIL, &start, SYNTAXTREE_UNCONNECTED, SYNTAXTREE_UNCONNECTED, (syntaxtreeindx *) out); } /* ------------------------------------------------------- * Parsers for different statement types * ------------------------------------------------------- */ /** Parses an expression */ bool parse_expression(parser *p, void *out) { return parse_precedence(p, PREC_ASSIGN, out); } /** Parses an expression that may include an anonymous function */ bool parse_pseudoexpression(parser *p, void *out) { if (parse_checktokenadvance(p, TOKEN_FUNCTION)) { return parse_anonymousfunction(p, out); } else { return parse_expression(p, out); } } /** @brief Parse statements * @details Statements are things that are allowed inside control flow statements */ bool parse_statement(parser *p, void *out) { if (parse_checktokenadvance(p, TOKEN_PRINT)) { return parse_printstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_IF)) { return parse_ifstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_WHILE)) { return parse_whilestatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_FOR)) { return parse_forstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_DO)) { return parse_dostatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_BREAK)) { return parse_breakstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_CONTINUE)) { return parse_breakstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_RETURN)) { return parse_returnstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_TRY)) { return parse_trystatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_LEFTCURLYBRACKET)) { return parse_blockstatement(p, out); } else if (parse_checktokenadvance(p, TOKEN_AT)) { return parse_breakpointstatement(p, out); } else { return parse_expressionstatement(p, out); } return false; } /** @brief Parse declarations * @details Declarations define something (e.g. a variable or a function) OR * a regular statement. They are *not* allowed in control flow statements. */ bool parse_declaration(parser *p, void *out) { bool success=false; if (parse_checktokenadvance(p, TOKEN_FUNCTION)) { success=parse_functiondeclaration(p, out); } else if (parse_checktokenadvance(p, TOKEN_VAR)) { success=parse_vardeclaration(p, out); } else if (parse_checktokenadvance(p, TOKEN_CLASS)) { success=parse_classdeclaration(p, out); } else if (parse_checktokenadvance(p, TOKEN_IMPORT)) { success=parse_importdeclaration(p, out); } else if (parse_checktoken(p, TOKEN_SYMBOL)) { // Typed var declaration ? success=parse_typedvardeclaration(p, out); } else { success=parse_statement(p, out); } //if (!success) parse_synchronize(p); return success; } /** Parses multiple declarations, separated by ; separators * @param p the parser * @param end token type to terminate on [use TOKEN_EOF if no special terminator] * @returns the syntaxtreeindx of the parsed expression */ bool parse_declarationmulti(parser *p, int n, tokentype *end, void *out) { syntaxtreeindx last=SYNTAXTREE_UNCONNECTED, current=SYNTAXTREE_UNCONNECTED; token start = p->current; while (!parse_checktokenmulti(p, n, end) && !parse_checktoken(p, TOKEN_EOF)) { PARSE_CHECK(parse_declaration(p, ¤t)); /* If we now have more than one node, insert a sequence node */ if (last!=SYNTAXTREE_UNCONNECTED) { PARSE_CHECK(parse_addnode(p, NODE_SEQUENCE, MORPHO_NIL, &start, last, current, ¤t)); } last = current; } *((syntaxtreeindx *) out) = current; return true; } /** Parse a program as a sequence of declarations and statements */ bool parse_program(parser *p, void *out) { tokentype terminator[] = { TOKEN_EOF }; parse_declarationmulti(p, 1, terminator, &((syntaxtree *) p->out)->entry); return ERROR_SUCCEEDED(*p->err); } /* ********************************************************************** * Parser definition table for morpho grammar * ********************************************************************** */ parserule rules[] = { PARSERULE_UNUSED(TOKEN_NEWLINE), PARSERULE_INFIX(TOKEN_QUESTION, parse_ternary, PREC_TERNARY), PARSERULE_PREFIX(TOKEN_STRING, parse_string), PARSERULE_PREFIX(TOKEN_INTERPOLATION, parse_interpolation), PARSERULE_PREFIX(TOKEN_INTEGER, parse_integer), PARSERULE_PREFIX(TOKEN_NUMBER, parse_number), PARSERULE_PREFIX(TOKEN_SYMBOL, parse_symbol), PARSERULE_MIXFIX(TOKEN_LEFTPAREN, parse_grouping, parse_call, PREC_CALL), PARSERULE_UNUSED(TOKEN_RIGHTPAREN), PARSERULE_MIXFIX(TOKEN_LEFTSQBRACKET, parse_list, parse_index, PREC_CALL), PARSERULE_UNUSED(TOKEN_RIGHTSQBRACKET), PARSERULE_PREFIX(TOKEN_LEFTCURLYBRACKET, parse_dictionary), PARSERULE_UNUSED(TOKEN_RIGHTCURLYBRACKET), PARSERULE_UNUSED(TOKEN_COLON), PARSERULE_UNUSED(TOKEN_SEMICOLON), PARSERULE_UNUSED(TOKEN_COMMA), PARSERULE_INFIX(TOKEN_PLUS, parse_binary, PREC_TERM), PARSERULE_MIXFIX(TOKEN_MINUS, parse_unary, parse_binary, PREC_TERM), PARSERULE_INFIX(TOKEN_STAR, parse_binary, PREC_FACTOR), PARSERULE_INFIX(TOKEN_SLASH, parse_binary, PREC_FACTOR), PARSERULE_INFIX(TOKEN_CIRCUMFLEX, parse_binary, PREC_POW), PARSERULE_UNUSED(TOKEN_PLUSPLUS), PARSERULE_UNUSED(TOKEN_MINUSMINUS), PARSERULE_INFIX(TOKEN_PLUSEQ, parse_assignby, PREC_ASSIGN), PARSERULE_INFIX(TOKEN_MINUSEQ, parse_assignby, PREC_ASSIGN), PARSERULE_INFIX(TOKEN_STAREQ, parse_assignby, PREC_ASSIGN), PARSERULE_INFIX(TOKEN_SLASHEQ, parse_assignby, PREC_ASSIGN), PARSERULE_UNUSED(TOKEN_HASH), PARSERULE_PREFIX(TOKEN_AT, parse_unary), PARSERULE_INFIX(TOKEN_DOT, parse_binary, PREC_CALL), PARSERULE_INFIX(TOKEN_DOTDOT, parse_range, PREC_RANGE), PARSERULE_INFIX(TOKEN_DOTDOTDOT, parse_range, PREC_RANGE), PARSERULE_PREFIX(TOKEN_EXCLAMATION, parse_unary), PARSERULE_UNUSED(TOKEN_AMP), PARSERULE_UNUSED(TOKEN_VBAR), PARSERULE_INFIX(TOKEN_DBLAMP, parse_binary, PREC_AND), PARSERULE_INFIX(TOKEN_DBLVBAR, parse_binary, PREC_OR), PARSERULE_INFIX(TOKEN_EQUAL, parse_binary, PREC_ASSIGN), PARSERULE_INFIX(TOKEN_EQ, parse_binary, PREC_EQUALITY), PARSERULE_INFIX(TOKEN_NEQ, parse_binary, PREC_EQUALITY), PARSERULE_INFIX(TOKEN_LT, parse_binary, PREC_COMPARISON), PARSERULE_INFIX(TOKEN_GT, parse_binary, PREC_COMPARISON), PARSERULE_INFIX(TOKEN_LTEQ, parse_binary, PREC_COMPARISON), PARSERULE_INFIX(TOKEN_GTEQ, parse_binary, PREC_COMPARISON), PARSERULE_PREFIX(TOKEN_TRUE, parse_bool), PARSERULE_PREFIX(TOKEN_FALSE, parse_bool), PARSERULE_PREFIX(TOKEN_NIL, parse_nil), PARSERULE_PREFIX(TOKEN_SELF, parse_self), PARSERULE_PREFIX(TOKEN_SUPER, parse_super), PARSERULE_PREFIX(TOKEN_IMAG, parse_complex), PARSERULE_UNUSED(TOKEN_PRINT), PARSERULE_UNUSED(TOKEN_VAR), PARSERULE_UNUSED(TOKEN_IF), PARSERULE_UNUSED(TOKEN_ELSE), PARSERULE_UNUSED(TOKEN_IN), PARSERULE_UNUSED(TOKEN_WHILE), PARSERULE_UNUSED(TOKEN_FOR), PARSERULE_UNUSED(TOKEN_DO), PARSERULE_UNUSED(TOKEN_BREAK), PARSERULE_UNUSED(TOKEN_CONTINUE), PARSERULE_UNUSED(TOKEN_FUNCTION), PARSERULE_UNUSED(TOKEN_RETURN), PARSERULE_UNUSED(TOKEN_CLASS), PARSERULE_UNUSED(TOKEN_IMPORT), PARSERULE_UNUSED(TOKEN_AS), PARSERULE_UNUSED(TOKEN_IS), PARSERULE_UNUSED(TOKEN_WITH), PARSERULE_UNUSED(TOKEN_TRY), PARSERULE_UNUSED(TOKEN_CATCH), PARSERULE_UNUSED(TOKEN_INCOMPLETE), PARSERULE_UNUSED(TOKEN_EOF), PARSERULE_UNUSED(TOKEN_NONE) }; /* ********************************************************************** * Obtain parse rules * ********************************************************************** */ /** Compares two parse rules */ int _parse_parserulecmp(const void *l, const void *r) { parserule *a = (parserule *) l; parserule *b = (parserule *) r; return ((int) a->type) - ((int) b->type); } /** Get the rule to parse an element of type tokentype. */ parserule *parse_getrule(parser *p, tokentype type) { if (p->parsetable.count==0) return &rules[type]; parserule key = { .type = type }; return bsearch(&key, p->parsetable.data, p->parsetable.count, sizeof(parserule), _parse_parserulecmp); } /* ********************************************************************** * Customize parsers * ********************************************************************** */ /** Defines the parse table. */ bool parse_setparsetable(parser *p, parserule *rules) { varray_parseruleclear(&p->parsetable); for (int i=0; rules[i].type!=TOKEN_NONE; i++) { if (rules[i].prefix!=NULL || rules[i].infix!=NULL) { if (!varray_parseruleadd(&p->parsetable, &rules[i], 1)) return false; } } qsort(p->parsetable.data, p->parsetable.count, sizeof(parserule), _parse_parserulecmp); return true; } /** Sets the parse function to be called to start parsing */ void parse_setbaseparsefn(parser *p, parsefunction fn) { p->baseparsefn = fn; } /** Sets whether to skip new line tokens, and define the token type if so. */ void parse_setskipnewline(parser *p, bool skip, tokentype toknewline) { p->skipnewline = skip; p->toknewline = toknewline; } /** Set maximum recursion depth @warning: It is not guaranteed that values above PARSE_MAXRECURSIONDEPTH are achievable */ void parse_setmaxrecursiondepth(parser *p, int maxdepth) { p->maxrecursiondepth = maxdepth; } /* ********************************************************************** * Interface * ********************************************************************** */ /** @brief Initialize a parser * @param p the parser to initialize * @param lex lexer to use * @param err an error structure to fill out if necessary * @param tree Pointer to the output */ void parse_init(parser *p, lexer *lex, error *err, void *out) { p->current = TOKEN_BLANK; p->previous = TOKEN_BLANK; p->left = SYNTAXTREE_UNCONNECTED; p->lex=lex; p->err=err; p->out=out; p->nl=false; p->maxrecursiondepth=PARSE_MAXRECURSIONDEPTH; p->recursiondepth=0; varray_parseruleinit(&p->parsetable); varray_valueinit(&p->objects); // Configure parser to parse morpho by default parse_setbaseparsefn(p, parse_program); parse_setskipnewline(p, true, TOKEN_NEWLINE); parse_setparsetable(p, rules); } /** @brief Clear a parser */ void parse_clear(parser *p) { p->current = TOKEN_BLANK; p->previous = TOKEN_BLANK; p->left = SYNTAXTREE_UNCONNECTED; varray_parseruleclear(&p->parsetable); parse_clearobjects(p); } /** Generic entry point into the parser; call this from your own parser wrapper */ bool parse(parser *p) { PARSE_CHECK(parse_advance(p)); bool success=(p->baseparsefn) (p, p->out); if (!success) parse_freeobjects(p); parse_clearobjects(p); return success; } /** Parse morpho source */ bool morpho_parse(parser *p) { lex_skipshebang(p->lex); bool success=parse(p); if (!success) syntaxtree_wipe((syntaxtree *) p->out); return success; } /* ********************************************************************** * Other useful parsers * ********************************************************************** */ /** Convenience function to parse a string into an array of values * @param[in] string - string to parse * @param[in] nmax - maximum number of values to read * @param[in] v - value array, filled out on return * @param[out] n - number of values read * @param[out] err - error structure filled out if an error occurs * @returns true if successful, false otherwise. */ bool parse_stringtovaluearray(char *string, unsigned int nmax, value *v, unsigned int *n, error *err) { lexer l; token tok; unsigned int k=0; bool minus=false; lex_init(&l, string, 0); do { if (!lex(&l, &tok, err)) return false; switch(tok.type) { case TOKEN_INTEGER: { long f = strtol(tok.start, NULL, 10); v[k]=MORPHO_INTEGER((minus ? -f : f)); k++; minus=false; } break; case TOKEN_NUMBER: { double f = strtod(tok.start, NULL); v[k]=MORPHO_FLOAT((minus ? -f : f)); k++; minus=false; } break; case TOKEN_MINUS: minus=true; break; case TOKEN_COMMA: case TOKEN_EOF: break; default: morpho_writeerrorwithid(err, PARSE_UNRECGNZEDTOK, NULL, ERROR_POSNUNIDENTIFIABLE, ERROR_POSNUNIDENTIFIABLE); return false; break; } } while (tok.type!=TOKEN_EOF && k0) { syntaxtreenode node = tree.tree.data[tree.entry]; if (SYNTAXTREE_ISLEAF(node.type)) { if (MORPHO_ISSTRING(node.content)) { *out = object_clonestring(node.content); } else *out = node.content; success=true; } } syntaxtree_clear(&tree); return success; } void parse_initialize(void) { /* Parse errors */ morpho_defineerror(PARSE_INCOMPLETEEXPRESSION, ERROR_PARSE, PARSE_INCOMPLETEEXPRESSION_MSG); morpho_defineerror(PARSE_MISSINGPARENTHESIS, ERROR_PARSE, PARSE_MISSINGPARENTHESIS_MSG); morpho_defineerror(PARSE_EXPECTEXPRESSION, ERROR_PARSE, PARSE_EXPECTEXPRESSION_MSG); morpho_defineerror(PARSE_MISSINGSEMICOLON, ERROR_PARSE, PARSE_MISSINGSEMICOLON_MSG); morpho_defineerror(PARSE_MISSINGSEMICOLONEXP, ERROR_PARSE, PARSE_MISSINGSEMICOLONEXP_MSG); morpho_defineerror(PARSE_MISSINGSEMICOLONVAR, ERROR_PARSE, PARSE_MISSINGSEMICOLONVAR_MSG); morpho_defineerror(PARSE_VAREXPECTED, ERROR_PARSE, PARSE_VAREXPECTED_MSG); morpho_defineerror(PARSE_SYMBLEXPECTED, ERROR_PARSE, PARSE_SYMBLEXPECTED_MSG); morpho_defineerror(PARSE_BLOCKTERMINATOREXP, ERROR_PARSE, PARSE_BLOCKTERMINATOREXP_MSG); morpho_defineerror(PARSE_MSSNGSQBRC, ERROR_PARSE, PARSE_MSSNGSQBRC_MSG); morpho_defineerror(PARSE_MSSNGCOMMA, ERROR_PARSE, PARSE_MSSNGCOMMA_MSG); morpho_defineerror(PARSE_TRNRYMSSNGCOLON, ERROR_PARSE, PARSE_TRNRYMSSNGCOLON_MSG); morpho_defineerror(PARSE_IFLFTPARENMISSING, ERROR_PARSE, PARSE_IFLFTPARENMISSING_MSG); morpho_defineerror(PARSE_IFRGHTPARENMISSING, ERROR_PARSE, PARSE_IFRGHTPARENMISSING_MSG); morpho_defineerror(PARSE_WHILELFTPARENMISSING, ERROR_PARSE, PARSE_WHILELFTPARENMISSING_MSG); morpho_defineerror(PARSE_FORLFTPARENMISSING, ERROR_PARSE, PARSE_FORLFTPARENMISSING_MSG); morpho_defineerror(PARSE_FORSEMICOLONMISSING, ERROR_PARSE, PARSE_FORSEMICOLONMISSING_MSG); morpho_defineerror(PARSE_FORRGHTPARENMISSING, ERROR_PARSE, PARSE_FORRGHTPARENMISSING_MSG); morpho_defineerror(PARSE_FNNAMEMISSING, ERROR_PARSE, PARSE_FNNAMEMISSING_MSG); morpho_defineerror(PARSE_FNLEFTPARENMISSING, ERROR_PARSE, PARSE_FNLEFTPARENMISSING_MSG); morpho_defineerror(PARSE_FNRGHTPARENMISSING, ERROR_PARSE, PARSE_FNRGHTPARENMISSING_MSG); morpho_defineerror(PARSE_FNLEFTCURLYMISSING, ERROR_PARSE, PARSE_FNLEFTCURLYMISSING_MSG); morpho_defineerror(PARSE_CALLRGHTPARENMISSING, ERROR_PARSE, PARSE_CALLRGHTPARENMISSING_MSG); morpho_defineerror(PARSE_EXPECTCLASSNAME, ERROR_PARSE, PARSE_EXPECTCLASSNAME_MSG); morpho_defineerror(PARSE_CLASSLEFTCURLYMISSING, ERROR_PARSE, PARSE_CLASSLEFTCURLYMISSING_MSG); morpho_defineerror(PARSE_CLASSRGHTCURLYMISSING, ERROR_PARSE, PARSE_CLASSRGHTCURLYMISSING_MSG); morpho_defineerror(PARSE_EXPECTDOTAFTERSUPER, ERROR_PARSE, PARSE_EXPECTDOTAFTERSUPER_MSG); morpho_defineerror(PARSE_INCOMPLETESTRINGINT, ERROR_PARSE, PARSE_INCOMPLETESTRINGINT_MSG); morpho_defineerror(PARSE_VARBLANKINDEX, ERROR_COMPILE, PARSE_VARBLANKINDEX_MSG); morpho_defineerror(PARSE_IMPORTMISSINGNAME, ERROR_PARSE, PARSE_IMPORTMISSINGNAME_MSG); morpho_defineerror(PARSE_IMPORTMLTPLAS, ERROR_PARSE, PARSE_IMPORTMLTPLAS_MSG); morpho_defineerror(PARSE_IMPORTUNEXPCTDTOK, ERROR_PARSE, PARSE_IMPORTUNEXPCTDTOK_MSG); morpho_defineerror(PARSE_IMPORTASSYMBL, ERROR_PARSE, PARSE_IMPORTASSYMBL_MSG); morpho_defineerror(PARSE_IMPORTFORSYMBL, ERROR_PARSE, PARSE_IMPORTFORSYMBL_MSG); morpho_defineerror(PARSE_EXPECTSUPER, ERROR_COMPILE, PARSE_EXPECTSUPER_MSG); morpho_defineerror(PARSE_EXPECTMIXIN, ERROR_COMPILE, PARSE_EXPECTMIXIN_MSG); morpho_defineerror(PARSE_UNRECGNZEDTOK, ERROR_PARSE, PARSE_UNRECGNZEDTOK_MSG); morpho_defineerror(PARSE_DCTSPRTR, ERROR_PARSE, PARSE_DCTSPRTR_MSG); morpho_defineerror(PARSE_DCTTRMNTR, ERROR_PARSE, PARSE_DCTTRMNTR_MSG); morpho_defineerror(PARSE_SWTCHSPRTR, ERROR_PARSE, PARSE_SWTCHSPRTR_MSG); morpho_defineerror(PARSE_DCTENTRYSPRTR, ERROR_PARSE, PARSE_DCTENTRYSPRTR_MSG); morpho_defineerror(PARSE_EXPCTWHL, ERROR_PARSE, PARSE_EXPCTWHL_MSG); morpho_defineerror(PARSE_EXPCTCTCH, ERROR_PARSE, PARSE_EXPCTCTCH_MSG); morpho_defineerror(PARSE_ONEVARPR, ERROR_PARSE, PARSE_ONEVARPR_MSG); morpho_defineerror(PARSE_CATCHLEFTCURLYMISSING, ERROR_PARSE, PARSE_CATCHLEFTCURLYMISSING_MSG); morpho_defineerror(PARSE_VALRANGE, ERROR_PARSE, PARSE_VALRANGE_MSG); morpho_defineerror(PARSE_STRESC, ERROR_PARSE, PARSE_STRESC_MSG); morpho_defineerror(PARSE_RCRSNLMT, ERROR_PARSE, PARSE_RCRSNLMT_MSG); morpho_defineerror(PARSE_UNESCPDCTRL, ERROR_PARSE, PARSE_UNESCPDCTRL_MSG); morpho_defineerror(PARSE_INVLDUNCD, ERROR_PARSE, PARSE_INVLDUNCD_MSG); } ================================================ FILE: src/support/parse.h ================================================ /** @file parse.h * @author T J Atherton and others (see below) * * @brief Parser */ #ifndef parse_h #define parse_h #include #include "error.h" #include "lex.h" #include "syntaxtree.h" /* ------------------------------------------------------- * Parser data types * ------------------------------------------------------- */ /** Parser type defined below */ typedef struct sparser parser; /* ------------------------------------------------------- * The parser is defined by parserules that respond to * various token types * ------------------------------------------------------- */ /** @brief an enumerated type that defines precedence order. */ enum { PREC_NONE, PREC_LOWEST, PREC_ASSIGN, PREC_TERNARY, PREC_COMMA, PREC_OR, PREC_AND, PREC_EQUALITY, PREC_COMPARISON, PREC_RANGE, PREC_TERM, PREC_FACTOR, PREC_UNARY, PREC_POW, PREC_CALL, PREC_HIGHEST }; /** Precedence order */ typedef int precedence; /** @brief Definition of a parse function. */ typedef bool (*parsefunction) (parser *c, void *out); /** @brief A parse rule will be defined for each token, * providing functions to parse the token if it is encountered in the * prefix or infix positions. The parse rule also defines the precedence. */ typedef struct { tokentype type; parsefunction prefix; parsefunction infix; precedence precedence; } parserule; /** @brief Macros used to build a parser definition table * Each line in the table defines the parserule(s) for a specific token type. */ #define PARSERULE_UNUSED(tok) { tok, NULL, NULL, PREC_NONE } #define PARSERULE_PREFIX(tok, fn) { tok, fn, NULL, PREC_NONE } #define PARSERULE_INFIX(tok, fn, prec) { tok, NULL, fn, prec } #define PARSERULE_MIXFIX(tok, unaryfn, infixfn, prec) { tok, unaryfn, infixfn, prec } /** Varrays of parse rules */ DECLARE_VARRAY(parserule, parserule) /* ------------------------------------------------------- * Define a Parser * ------------------------------------------------------- */ /** Maximum depth of recursion for a parser */ #define PARSE_MAXRECURSIONDEPTH 1000 /** @brief A structure that defines the state of a parser */ struct sparser { token current; /** The current token */ token previous; /** The previous token */ syntaxtreeindx left; int maxrecursiondepth; /** Maximum recursion depth */ int recursiondepth; /** Recursion depth */ lexer *lex; /** Lexer to use */ void *out; /** Output */ error *err; /** Error structure to output errors to */ bool nl; /** Was a newline encountered before the current token? */ // Customize parser bool skipnewline; /** Whether to advance past newline tokens or return them */ tokentype toknewline; /** Newline token type */ parsefunction baseparsefn; /** Base parse function */ varray_parserule parsetable; /** Table of parse rules */ varray_value objects; /** Hold objects generated while parsing */ }; /* ------------------------------------------------------- * Parser error messages * ------------------------------------------------------- */ #define PARSE_INCOMPLETEEXPRESSION "IncExp" #define PARSE_INCOMPLETEEXPRESSION_MSG "Incomplete expression." #define PARSE_MISSINGPARENTHESIS "MssngParen" #define PARSE_MISSINGPARENTHESIS_MSG "Expect ')' after expression." #define PARSE_EXPECTEXPRESSION "ExpExpr" #define PARSE_EXPECTEXPRESSION_MSG "Expected expression." #define PARSE_MISSINGSEMICOLON "MssngSemiVal" #define PARSE_MISSINGSEMICOLON_MSG "Expect ; after value." #define PARSE_MISSINGSEMICOLONEXP "MssngExpTerm" #define PARSE_MISSINGSEMICOLONEXP_MSG "Expect expression terminator (; or newline) after expression." #define PARSE_MISSINGSEMICOLONVAR "MssngSemiVar" #define PARSE_MISSINGSEMICOLONVAR_MSG "Expect ; after variable declaration." #define PARSE_VAREXPECTED "VarExpct" #define PARSE_VAREXPECTED_MSG "Variable name expected after var." #define PARSE_SYMBLEXPECTED "SymblExpct" #define PARSE_SYMBLEXPECTED_MSG "Symbol expected." #define PARSE_BLOCKTERMINATOREXP "MssngBrc" #define PARSE_BLOCKTERMINATOREXP_MSG "Expected '}' to finish block." #define PARSE_MSSNGSQBRC "MssngSqBrc" #define PARSE_MSSNGSQBRC_MSG "Expected ']' to finish list." #define PARSE_MSSNGCOMMA "MssngComma" #define PARSE_MSSNGCOMMA_MSG "Expected ','." #define PARSE_TRNRYMSSNGCOLON "TrnryMssngColon" #define PARSE_TRNRYMSSNGCOLON_MSG "Expected ':' after expression in ternary operator." #define PARSE_IFLFTPARENMISSING "IfMssngLftPrn" #define PARSE_IFLFTPARENMISSING_MSG "Expected '(' after if." #define PARSE_IFRGHTPARENMISSING "IfMssngRgtPrn" #define PARSE_IFRGHTPARENMISSING_MSG "Expected ')' after condition." #define PARSE_WHILELFTPARENMISSING "WhlMssngLftPrn" #define PARSE_WHILELFTPARENMISSING_MSG "Expected '(' after while." #define PARSE_FORLFTPARENMISSING "ForMssngLftPrn" #define PARSE_FORLFTPARENMISSING_MSG "Expected '(' after for." #define PARSE_FORSEMICOLONMISSING "ForMssngSemi" #define PARSE_FORSEMICOLONMISSING_MSG "Expected ';'." #define PARSE_FORRGHTPARENMISSING "ForMssngRgtPrn" #define PARSE_FORRGHTPARENMISSING_MSG "Expected ')' after for clauses." #define PARSE_FNNAMEMISSING "FnNoName" #define PARSE_FNNAMEMISSING_MSG "Expected function or method name." #define PARSE_FNLEFTPARENMISSING "FnMssngLftPrn" #define PARSE_FNLEFTPARENMISSING_MSG "Expect '(' after name." #define PARSE_FNRGHTPARENMISSING "FnMssngRgtPrn" #define PARSE_FNRGHTPARENMISSING_MSG "Expect ')' after parameters." #define PARSE_FNLEFTCURLYMISSING "FnMssngLftBrc" #define PARSE_FNLEFTCURLYMISSING_MSG "Expect '{' before body." #define PARSE_CALLRGHTPARENMISSING "CllMssngRgtPrn" #define PARSE_CALLRGHTPARENMISSING_MSG "Expect ')' after arguments." #define PARSE_EXPECTCLASSNAME "ClsNmMssng" #define PARSE_EXPECTCLASSNAME_MSG "Expect class name." #define PARSE_CLASSLEFTCURLYMISSING "ClsMssngLftBrc" #define PARSE_CLASSLEFTCURLYMISSING_MSG "Expect '{' before class body." #define PARSE_CLASSRGHTCURLYMISSING "ClsMssngRgtBrc" #define PARSE_CLASSRGHTCURLYMISSING_MSG "Expect '}' after class body." #define PARSE_EXPECTDOTAFTERSUPER "ExpctDtSpr" #define PARSE_EXPECTDOTAFTERSUPER_MSG "Expect '.' after 'super'" #define PARSE_INCOMPLETESTRINGINT "IntrpIncmp" #define PARSE_INCOMPLETESTRINGINT_MSG "Incomplete string after interpolation." #define PARSE_VARBLANKINDEX "EmptyIndx" #define PARSE_VARBLANKINDEX_MSG "Empty capacity in variable declaration." #define PARSE_IMPORTMISSINGNAME "ImprtMssngNm" #define PARSE_IMPORTMISSINGNAME_MSG "Import expects a module or file name." #define PARSE_IMPORTMLTPLAS "ImprtMltplAs" #define PARSE_IMPORTMLTPLAS_MSG "Import statement can only include one 'as' clause." #define PARSE_IMPORTUNEXPCTDTOK "ImprtExpctFrAs" #define PARSE_IMPORTUNEXPCTDTOK_MSG "Import expects a module or file name followed by for or as." #define PARSE_IMPORTASSYMBL "ExpctSymblAftrAs" #define PARSE_IMPORTASSYMBL_MSG "Expect symbol after as in import." #define PARSE_IMPORTFORSYMBL "ExpctSymblAftrFr" #define PARSE_IMPORTFORSYMBL_MSG "Expect symbol(s) after for in import." #define PARSE_EXPECTSUPER "SprNmMssng" #define PARSE_EXPECTSUPER_MSG "Expect superclass name." #define PARSE_EXPECTMIXIN "MxnNmMssng" #define PARSE_EXPECTMIXIN_MSG "Expect mixin class name." #define PARSE_UNRECGNZEDTOK "UnrcgnzdTok" #define PARSE_UNRECGNZEDTOK_MSG "Encountered an unrecognized token." #define PARSE_DCTSPRTR "DctSprtr" #define PARSE_DCTSPRTR_MSG "Expected a colon separating a key/value pair in dictionary." #define PARSE_SWTCHSPRTR "SwtchSprtr" #define PARSE_SWTCHSPRTR_MSG "Expected a colon after label." #define PARSE_DCTENTRYSPRTR "DctEntrySprtr" #define PARSE_DCTENTRYSPRTR_MSG "Expected a comma separating each dictionary entry." #define PARSE_DCTTRMNTR "DctTrmntr" #define PARSE_DCTTRMNTR_MSG "Expected a '}' to end the dictionary." #define PARSE_EXPCTWHL "ExpctWhl" #define PARSE_EXPCTWHL_MSG "Expected while after loop body." #define PARSE_EXPCTCTCH "ExpctCtch" #define PARSE_EXPCTCTCH_MSG "Expected catch after try statement." #define PARSE_CATCHLEFTCURLYMISSING "ExpctHndlr" #define PARSE_CATCHLEFTCURLYMISSING_MSG "Expected block of error handlers after catch." #define PARSE_ONEVARPR "OneVarPr" #define PARSE_ONEVARPR_MSG "Functions can have only one variadic parameter." #define PARSE_VALRANGE "ValRng" #define PARSE_VALRANGE_MSG "Value out of range." #define PARSE_STRESC "StrEsc" #define PARSE_STRESC_MSG "Unrecognized escape sequence in string." #define PARSE_RCRSNLMT "RcrsnLmt" #define PARSE_RCRSNLMT_MSG "Recursion depth exceeded." #define PARSE_UNESCPDCTRL "UnescpdCtrl" #define PARSE_UNESCPDCTRL_MSG "Unescaped control character in string literal." #define PARSE_INVLDUNCD "InvldUncd" #define PARSE_INVLDUNCD_MSG "Invalid unicode escape sequence." /* ------------------------------------------------------- * Interface for writing a custom parser * ------------------------------------------------------- */ // Library functions void parse_error(parser *p, bool use_prev, errorid id, ... ); bool parse_advance(parser *p); bool parse_precedence(parser *p, precedence prec, void *out); bool parse_checktoken(parser *p, tokentype type); bool parse_checktokenmulti(parser *p, int n, tokentype *type); bool parse_checktokenadvance(parser *p, tokentype type); bool parse_checkrequiredtoken(parser *p, tokentype type, errorid id); bool parse_checkdisallowedtoken(parser *p, tokentype type, errorid id); bool parse_codepointfromhex(parser *p, const char *codestr, int nhex, bool raw, varray_char *out); bool parse_stringfromtoken(parser *p, unsigned int start, unsigned int length, value *out); value parse_tokenasstring(parser *p); bool parse_tokenassymbol(parser *p); // Functions for use when out is a syntaxtree bool parse_addnode(parser *p, syntaxtreenodetype type, value content, token *tok, syntaxtreeindx left, syntaxtreeindx right, syntaxtreeindx *out); syntaxtreenode *parse_lookupnode(parser *p, syntaxtreeindx i); // Find a parserule for a given tokentype parserule *parse_getrule(parser *p, tokentype type); // Validate output bool parse_validatestrtol(parser *p, long f); bool parse_validatestrtod(parser *p, double f); // Convert tokens to C types bool parse_tokentointeger(parser *p, long *i); bool parse_tokentodouble(parser *p, double *x); // Recursion depth checking bool parse_incrementrecursiondepth(parser *p); bool parse_decrementrecursiondepth(parser *p); /* ------------------------------------------------------- * Morpho parse rules * ------------------------------------------------------- */ // Fundamental parse rules bool parse_expression(parser *p, void *out); bool parse_pseudoexpression(parser *p, void *out); bool parse_statement(parser *p, void *out); bool parse_declaration(parser *p, void *out); bool parse_declarationmulti(parser *p, int n, tokentype *end, void *out); bool parse_program(parser *p, void *out); // Declarations bool parse_vardeclaration(parser *p, void *out); bool parse_functiondeclaration(parser *p, void *out); bool parse_classdeclaration(parser *p, void *out); bool parse_importdeclaration(parser *p, void *out); // Statements bool parse_printstatement(parser *p, void *out); bool parse_expressionstatement(parser *p, void *out); bool parse_blockstatement(parser *p, void *out); bool parse_ifstatement(parser *p, void *out); bool parse_whilestatement(parser *p, void *out); bool parse_forstatement(parser *p, void *out); bool parse_dostatement(parser *p, void *out); bool parse_breakstatement(parser *p, void *out); bool parse_returnstatement(parser *p, void *out); bool parse_trystatement(parser *p, void *out); bool parse_breakpointstatement(parser *p, void *out); // Simple literals bool parse_nil(parser *p, void *out); bool parse_integer(parser *p, void *out); bool parse_number(parser *p, void *out); bool parse_complex(parser *p, void *out); bool parse_bool(parser *p, void *out); bool parse_self(parser *p, void *out); bool parse_super(parser *p, void *out); bool parse_symbol(parser *p, void *out); // Compound objects bool parse_string(parser *p, void *out); bool parse_dictionary(parser *p, void *out); bool parse_interpolation(parser *p, void *out); bool parse_grouping(parser *p, void *out); bool parse_unary(parser *p, void *out); bool parse_binary(parser *p, void *out); bool parse_assignby(parser *p, void *out); bool parse_call(parser *p, void *out); bool parse_index(parser *p, void *out); bool parse_list(parser *p, void *out); bool parse_anonymousfunction(parser *p, void *out); bool parse_switch(parser *p, void *out); /* ------------------------------------------------------- * Parser interface * ------------------------------------------------------- */ void parse_init(parser *p, lexer *lex, error *err, void *out); void parse_clear(parser *p); bool parse_setparsetable(parser *p, parserule *rules); void parse_setbaseparsefn(parser *p, parsefunction fn); void parse_setskipnewline(parser *p, bool skip, tokentype toknewline); void parse_setmaxrecursiondepth(parser *p, int maxdepth); bool parse(parser *p); bool morpho_parse(parser *p); bool parse_stringtovaluearray(char *string, unsigned int nmax, value *v, unsigned int *n, error *err); bool parse_value(const char *in, value *out); // Initialization/finalization void parse_initialize(void); void parse_finalize(void); #endif /* parse_h */ ================================================ FILE: src/support/platform.c ================================================ /** @file platform.c * @author T J Atherton * * @brief Isolates platform dependent code in morpho */ /** Platform-dependent code in morpho arises in several ways: * - Complex numbers for platforms that do not fully implement C99 * - Navigating the file system * - APIs for opening dynamic libraries * - APIs for using threads * - Functions that involve time */ #include #include #include #include #include "build.h" #include "platform.h" #include "error.h" #ifdef _WIN32 #include #include #else #define _POSIX_C_SOURCE 199309L #include #include #include #include #include #include #include #include #endif /* ********************************************************************** * Platform name * ********************************************************************** */ const char *platform_name(void) { #if __APPLE__ return MORPHO_PLATFORM_MACOS; #elif __linux__ return MORPHO_PLATFORM_LINUX; #elif __UNIX__ return MORPHO_PLATFORM_UNIX; #elif defined(_WIN32) return MORPHO_PLATFORM_WINDOWS; #endif return NULL; // Unrecognized platform } /* ********************************************************************** * Random numbers * ********************************************************************** */ /** Obtain a number of random bytes from the host platform */ bool platform_randombytes(char *buffer, size_t nbytes) { bool success=false; #ifdef _WIN32 HCRYPTPROV hProvider = 0; if (CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) && CryptGenRandom(hProvider, nbytes, buffer)) { CryptReleaseContext(hProvider, 0); success=true; } #else FILE *urandom; /* Initialize from OS random bits */ urandom=fopen("/dev/urandom", "r"); if (urandom) { for(int i=0; iabsb ? absa : absb); return (diff == 0.0) || (absmax > DBL_MIN && diff/absmax <= MORPHO_RELATIVE_EPS); } /* ********************************************************************** * File system functions * ********************************************************************** */ /* Tells if an object at path corresponds to a directory */ bool platform_isdirectory(const char *path) { #ifdef _WIN32 DWORD attributes = GetFileAttributes(path); if (attributes==INVALID_FILE_ATTRIBUTES) return false; return (attributes & FILE_ATTRIBUTE_DIRECTORY); #else struct stat statbuf; if (stat(path, &statbuf) != 0) return 0; return (bool) S_ISDIR(statbuf.st_mode); #endif } /** Returns the maximum size of a file path */ size_t platform_maxpathsize(void) { #ifdef _WIN32 return (size_t) MAX_PATH; #else return pathconf("/", _PC_PATH_MAX); #endif } /** Sets the current working directory to path */ bool platform_setcurrentdirectory(const char *path) { #ifdef _WIN32 return SetCurrentDirectory(path); #else return chdir(path)==0; #endif } /** Gets the path for the current working directory */ bool platform_getcurrentdirectory(char *buffer, size_t size) { #ifdef _WIN32 return GetCurrentDirectory(size, buffer); #else return getcwd(buffer, size); #endif } /** Gets a path containing the user's home directory */ bool platform_gethomedirectory(char *buffer, size_t size) { #ifdef _WIN32 DWORD length = GetEnvironmentVariable("USERPROFILE", buffer, size); return (length!=0 && lengthpw_dir; } if (homedir) strncpy(buffer, homedir, size); return homedir; #endif } /** Initializes a MorphoDirContents structure with a given path */ bool platform_directorycontentsinit(MorphoDirContents *contents, const char *path) { #ifdef _WIN32 size_t len = strlen(path)+3; char srch[len]; strcpy(srch,path); strcat(srch, "/*"); // Add required wildcard bool success=false; contents->handle = FindFirstFile(srch, &contents->finddata); if (contents->handle != INVALID_HANDLE_VALUE) { contents->isvalid=true; // Used by platform_directorcontentsnext to check that finddata contains valid information success=true; } return success; #else contents->dir=opendir(path); return contents->dir; #endif } /** Clears the contents of a MorphoDirContents structure */ void platform_directorycontentsclear(MorphoDirContents *contents) { #ifdef _WIN32 FindClose(contents->handle); #else closedir(contents->dir); #endif } /** Call this function repeatedly to extract the next file in the directory. Returns true if a file is found; the filename is in the buffer. */ bool platform_directorycontents(MorphoDirContents *contents, char *buffer, size_t size) { #ifdef _WIN32 if (!contents->isvalid) return false; while (strcmp(contents->finddata.cFileName, ".")==0 || strcmp(contents->finddata.cFileName, "..")==0) { if (FindNextFile(contents->handle, &contents->finddata)==0) return false; } strncpy(buffer, contents->finddata.cFileName, size); // Fetch next file and record whether it succeeded contents->isvalid=(FindNextFile(contents->handle, &contents->finddata)!=0); return true; #else struct dirent *entry; do { entry = readdir(contents->dir); if (!entry) return false; } while (strcmp(entry->d_name, ".")==0 || strcmp(entry->d_name, "..")==0); // Skip links to this and parent folder strncpy(buffer, entry->d_name, size); return true; #endif } /* ********************************************************************** * Dynamic libraries * ********************************************************************** */ /** Opens a dynamic library, returning a handle for future use */ MorphoDLHandle platform_dlopen(const char *path) { #ifdef _WIN32 return LoadLibrary((LPCSTR) path); #else return dlopen(path, RTLD_LAZY); #endif } /** Closes a dynamic libary */ void platform_dlclose(MorphoDLHandle handle) { #ifdef _WIN32 FreeLibrary(handle); #else dlclose(handle); #endif } /** Looks up a symbol in a dynamic library */ void *platform_dlsym(MorphoDLHandle handle, const char *symbol) { #ifdef _WIN32 return (void *) GetProcAddress(handle, symbol); #else return dlsym(handle, symbol); #endif } /* ********************************************************************** * Threads * ********************************************************************** */ DEFINE_VARRAY(MorphoThread, MorphoThread); /** Creates a thread */ bool MorphoThread_create(MorphoThread *thread, MorphoThreadFn threadfn, void *ref) { #ifdef _WIN32 DWORD threadId; *thread = CreateThread(NULL, // Default security attributes 0, // Default stack size threadfn, ref, 0, // Default creation flags &threadId); return (*thread!=NULL); #else return (pthread_create(thread, NULL, threadfn, ref)==0); #endif } /** Waits for a thread to finish */ void MorphoThread_join(MorphoThread thread) { #ifdef _WIN32 WaitForSingleObject(thread, INFINITE); #else pthread_join(thread, NULL); #endif } /** Clears a thread, releasing any resources used */ void MorphoThread_clear(MorphoThread thread) { #ifdef _WIN32 CloseHandle(thread); #endif } /** Exits a thread */ void MorphoThread_exit(void) { #ifdef _WIN32 ExitThread(0); #else pthread_exit(NULL); #endif } /** Initializes a mutex */ bool MorphoMutex_init(MorphoMutex *mutex) { #ifdef _WIN32 InitializeCriticalSection(mutex); return true; #else return pthread_mutex_init(mutex, NULL)==0; #endif } /** Clears a mutex */ void MorphoMutex_clear(MorphoMutex *mutex) { #ifdef _WIN32 DeleteCriticalSection(mutex); #else pthread_mutex_destroy(mutex); #endif } /** Locks a mutex */ void MorphoMutex_lock(MorphoMutex *mutex) { #ifdef _WIN32 EnterCriticalSection(mutex); #else pthread_mutex_lock(mutex); #endif } /** Unlocks a mutex */ void MorphoMutex_unlock(MorphoMutex *mutex) { #ifdef _WIN32 LeaveCriticalSection(mutex); #else pthread_mutex_unlock(mutex); #endif } /** Initializes a condition variable */ bool MorphoCond_init(MorphoCond *cond) { #ifdef _WIN32 InitializeConditionVariable(cond); return true; #else return (pthread_cond_init(cond, NULL)==0); #endif } /** Clears a condition variable */ void MorphoCond_clear(MorphoCond *cond) { #ifdef _WIN32 /* Condition variables don't require cleanup on windows */ #else pthread_cond_destroy(cond); #endif } /** Signals condition variable, waking up a waiting thread */ void MorphoCond_signal(MorphoCond *cond) { #ifdef _WIN32 WakeConditionVariable(cond); #else pthread_cond_signal(cond); #endif } /** Wake all threads waiting on a condition variable */ void MorphoCond_broadcast(MorphoCond *cond) { #ifdef _WIN32 WakeAllConditionVariable(cond); #else pthread_cond_broadcast(cond); #endif } /** Waits for the condition variable to be signalled */ void MorphoCond_wait(MorphoCond *cond, MorphoMutex *mutex) { #ifdef _WIN32 SleepConditionVariableCS(cond, mutex, INFINITE); #else pthread_cond_wait(cond, mutex); #endif } /* ********************************************************************** * Time * ********************************************************************** */ /** Returns the system clock time in seconds. */ double platform_clock(void) { #ifdef _WIN32 SYSTEMTIME st; GetSystemTime(&st); double seconds = st.wMilliseconds*1e-3 + st.wSecond + st.wMinute*60 + st.wHour*3600; return seconds; #else struct timeval tv; gettimeofday(&tv, NULL); return ((double) tv.tv_sec) + tv.tv_usec * 1e-6; #endif } /** Sleep for a specified number of milliseconds */ void platform_sleep(int msecs) { #ifdef _WIN32 Sleep(msecs); #else struct timespec t; t.tv_sec = msecs / 1000; t.tv_nsec = (msecs % 1000) * 1000000; nanosleep(&t, NULL); #endif } ================================================ FILE: src/support/platform.h ================================================ /** @file platform.h * @author T J Atherton * * @brief Isolates platform dependent code in morpho */ #ifndef platform_h #define platform_h #include #include #include #include "varray.h" #ifdef _WIN32 #include #else #include #include #endif /* ------------------------------------------------------- * Detecting platform name * ------------------------------------------------------- */ #define MORPHO_PLATFORM_MACOS "macos" #define MORPHO_PLATFORM_LINUX "linux" #define MORPHO_PLATFORM_UNIX "unix" #define MORPHO_PLATFORM_WINDOWS "windows" const char *platform_name(void); /* ------------------------------------------------------- * Random numbers * ------------------------------------------------------- */ bool platform_randombytes(char *buffer, size_t nbytes); /* ------------------------------------------------------- * Complex numbers * ------------------------------------------------------- */ /** The windows C library has only partial support for C99 complex numbers, * which are implemented as a struct rather than a native type. While * C99 complex functions are provided, basic arithmetic operations don't * work. Hence we provide a type, as well as several macros and functions * to fill in missing functionality: */ #ifdef _WIN32 typedef _Dcomplex MorphoComplex; #define MCBuild(re,im) _Cbuild(re, im) MorphoComplex MCAdd(MorphoComplex a, MorphoComplex b); MorphoComplex MCSub(MorphoComplex a, MorphoComplex b); #define MCMul(a,b) (_Cmulcc(a,b)) #define MCScale(a,b) (_Cmulcr(a,b)) MorphoComplex MCDiv(MorphoComplex a, MorphoComplex b); bool MCSame(MorphoComplex a, MorphoComplex b); #else typedef double complex MorphoComplex; #define MCBuild(re,im) (re + I * im) #define MCAdd(a, b) (a + b) #define MCSub(a, b) (a - b) #define MCMul(a, b) (a * b) #define MCScale(a, b) (a * b) #define MCDiv(a, b) (a / b) #define MCSame(a, b) (a == b) #endif bool MCEq(MorphoComplex a, MorphoComplex b); /* ------------------------------------------------------- * Navigating the file system * ------------------------------------------------------- */ size_t platform_maxpathsize(void); bool platform_setcurrentdirectory(const char *path); bool platform_getcurrentdirectory(char *buffer, size_t size); bool platform_gethomedirectory(char *buffer, size_t size); bool platform_isdirectory(const char *path); typedef struct { #ifdef _WIN32 bool isvalid; WIN32_FIND_DATA finddata; HANDLE handle; #else DIR *dir; #endif } MorphoDirContents; bool platform_directorycontentsinit(MorphoDirContents *contents, const char *path); void platform_directorycontentsclear(MorphoDirContents *contents); bool platform_directorycontents(MorphoDirContents *contents, char *buffer, size_t size); /* ------------------------------------------------------- * Dynamic libraries * ------------------------------------------------------- */ #ifdef _WIN32 typedef HMODULE MorphoDLHandle; #else typedef void* MorphoDLHandle; #endif MorphoDLHandle platform_dlopen(const char *path); void platform_dlclose(MorphoDLHandle handle); void *platform_dlsym(MorphoDLHandle handle, const char *symbol); bool morpho_isdirectory(const char *path); /* ------------------------------------------------------- * Threads * ------------------------------------------------------- */ #ifdef _WIN32 typedef HANDLE MorphoThread; typedef CRITICAL_SECTION MorphoMutex; typedef CONDITION_VARIABLE MorphoCond; typedef DWORD MorphoThreadFnReturnType; typedef DWORD (*MorphoThreadFn)(void *); #else typedef pthread_t MorphoThread; typedef pthread_mutex_t MorphoMutex; typedef pthread_cond_t MorphoCond; typedef void* MorphoThreadFnReturnType; typedef void* (*MorphoThreadFn)(void *); #endif DECLARE_VARRAY(MorphoThread, MorphoThread); bool MorphoThread_create(MorphoThread *thread, MorphoThreadFn threadfn, void *ref); void MorphoThread_join(MorphoThread thread); void MorphoThread_clear(MorphoThread thread); void MorphoThread_exit(void); bool MorphoMutex_init(MorphoMutex *mutex); void MorphoMutex_clear(MorphoMutex *mutex); void MorphoMutex_lock(MorphoMutex *mutex); void MorphoMutex_unlock(MorphoMutex *mutex); bool MorphoCond_init(MorphoCond *cond); void MorphoCond_clear(MorphoCond *cond); void MorphoCond_signal(MorphoCond *cond); void MorphoCond_broadcast(MorphoCond *cond); void MorphoCond_wait(MorphoCond *cond, MorphoMutex *mutex); /* ------------------------------------------------------- * Time * ------------------------------------------------------- */ double platform_clock(void); void platform_sleep(int msecs); #endif ================================================ FILE: src/support/random.c ================================================ /** @file random.c * @author T J Atherton and others (see below) * * @brief Random number generation */ #include "random.h" #include "platform.h" /* ********************************************************************** * Splitmix64 (used for initialization purposes only) * ********************************************************************** */ /* Written in 2015 by Sebastiano Vigna (vigna@acm.org) To the extent possible under law, the author has dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See . This is a fixed-increment version of Java 8's SplittableRandom generator See http://dx.doi.org/10.1145/2714064.2660195 and http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html It is a very fast generator passing BigCrush, and it can be useful if for some reason you absolutely want 64 bits of state; otherwise, we rather suggest to use a xoroshiro128+ (for moderately parallel computations) or xorshift1024* (for massively parallel computations) generator. */ uint64_t splitmix64_state; /* The state can be seeded with any value. */ /** Get the next random number generated by splitmix64 */ static inline uint64_t splitmix64_next(void) { uint64_t z = (splitmix64_state += UINT64_C(0x9E3779B97F4A7C15)); z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); return z ^ (z >> 31); } /** Set the seed for splitmix64 */ void splitmix64_seed(uint64_t seed) { splitmix64_state=seed; } /* ********************************************************************** * xoshiro256++ * ********************************************************************** */ /* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org) To the extent possible under law, the author has dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See . This is xoshiro256++ 1.0, one of our all-purpose, rock-solid generators. It has excellent (sub-ns) speed, a state (256 bits) that is large enough for any parallel application, and it passes all tests we are aware of. For generating just floating-point numbers, xoshiro256+ is even faster. The state must be seeded so that it is not everywhere zero. If you have a 64-bit seed, we suggest to seed a splitmix64 generator and use its output to fill s. */ static inline uint64_t xoshiro256pp_rotl(const uint64_t x, int k) { return (x << k) | (x >> (64 - k)); } static uint64_t xoshiro256pp_state[4]; static inline uint64_t next(void) { const uint64_t result = xoshiro256pp_rotl(xoshiro256pp_state[0] + xoshiro256pp_state[3], 23) + xoshiro256pp_state[0]; const uint64_t t = xoshiro256pp_state[1] << 17; xoshiro256pp_state[2] ^= xoshiro256pp_state[0]; xoshiro256pp_state[3] ^= xoshiro256pp_state[1]; xoshiro256pp_state[1] ^= xoshiro256pp_state[2]; xoshiro256pp_state[0] ^= xoshiro256pp_state[3]; xoshiro256pp_state[2] ^= t; xoshiro256pp_state[3] = xoshiro256pp_rotl(xoshiro256pp_state[3], 45); return result; } /* This is the jump function for the generator. It is equivalent to 2^128 calls to next(); it can be used to generate 2^128 non-overlapping subsequences for parallel computations. */ void xoshiro256pp_jump(void) { static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; uint64_t s0 = 0; uint64_t s1 = 0; uint64_t s2 = 0; uint64_t s3 = 0; for(int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) for(int b = 0; b < 64; b++) { if (JUMP[i] & UINT64_C(1) << b) { s0 ^= xoshiro256pp_state[0]; s1 ^= xoshiro256pp_state[1]; s2 ^= xoshiro256pp_state[2]; s3 ^= xoshiro256pp_state[3]; } next(); } xoshiro256pp_state[0] = s0; xoshiro256pp_state[1] = s1; xoshiro256pp_state[2] = s2; xoshiro256pp_state[3] = s3; } /* This is the long-jump function for the generator. It is equivalent to 2^192 calls to next(); it can be used to generate 2^64 starting points, from each of which jump() will generate 2^64 non-overlapping subsequences for parallel distributed computations. */ void xoshiro256pp_longjump(void) { static const uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; uint64_t s0 = 0; uint64_t s1 = 0; uint64_t s2 = 0; uint64_t s3 = 0; for(int i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++) for(int b = 0; b < 64; b++) { if (LONG_JUMP[i] & UINT64_C(1) << b) { s0 ^= xoshiro256pp_state[0]; s1 ^= xoshiro256pp_state[1]; s2 ^= xoshiro256pp_state[2]; s3 ^= xoshiro256pp_state[3]; } next(); } xoshiro256pp_state[0] = s0; xoshiro256pp_state[1] = s1; xoshiro256pp_state[2] = s2; xoshiro256pp_state[3] = s3; } /* ********************************************************************** * xoshiro256+ * ********************************************************************** */ /* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) To the extent possible under law, the author has dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See This is xoshiro256+ 1.0, our best and fastest generator for floating-point numbers. We suggest to use its upper bits for floating-point generation, as it is slightly faster than xoshiro256++/xoshiro256**. It passes all tests we are aware of except for the lowest three bits, which might fail linearity tests (and just those), so if low linear complexity is not considered an issue (as it is usually the case) it can be used to generate 64-bit outputs, too. We suggest to use a sign test to extract a random Boolean value, and right shifts to extract subsets of bits. The state must be seeded so that it is not everywhere zero. If you have a 64-bit seed, we suggest to seed a splitmix64 generator and use its output to fill s. */ static inline uint64_t xoshiro256p_rotl(const uint64_t x, int k) { return (x << k) | (x >> (64 - k)); } static uint64_t xoshiro256p_state[4]; static inline uint64_t xoshiro256p_next(void) { const uint64_t result = xoshiro256p_state[0] + xoshiro256p_state[3]; const uint64_t t = xoshiro256p_state[1] << 17; xoshiro256p_state[2] ^= xoshiro256p_state[0]; xoshiro256p_state[3] ^= xoshiro256p_state[1]; xoshiro256p_state[1] ^= xoshiro256p_state[2]; xoshiro256p_state[0] ^= xoshiro256p_state[3]; xoshiro256p_state[2] ^= t; xoshiro256p_state[3] = xoshiro256p_rotl(xoshiro256p_state[3], 45); return result; } /* This is the jump function for the generator. It is equivalent to 2^128 calls to next(); it can be used to generate 2^128 non-overlapping subsequences for parallel computations. */ void xoshiro256p_jump(void) { static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; uint64_t s0 = 0; uint64_t s1 = 0; uint64_t s2 = 0; uint64_t s3 = 0; for(int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) for(int b = 0; b < 64; b++) { if (JUMP[i] & UINT64_C(1) << b) { s0 ^= xoshiro256p_state[0]; s1 ^= xoshiro256p_state[1]; s2 ^= xoshiro256p_state[2]; s3 ^= xoshiro256p_state[3]; } next(); } xoshiro256p_state[0] = s0; xoshiro256p_state[1] = s1; xoshiro256p_state[2] = s2; xoshiro256p_state[3] = s3; } /* This is the long-jump function for the generator. It is equivalent to 2^192 calls to next(); it can be used to generate 2^64 starting points, from each of which jump() will generate 2^64 non-overlapping subsequences for parallel distributed computations. */ void xoshiro256p_longjump(void) { static const uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; uint64_t s0 = 0; uint64_t s1 = 0; uint64_t s2 = 0; uint64_t s3 = 0; for(int i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++) for(int b = 0; b < 64; b++) { if (LONG_JUMP[i] & UINT64_C(1) << b) { s0 ^= xoshiro256p_state[0]; s1 ^= xoshiro256p_state[1]; s2 ^= xoshiro256p_state[2]; s3 ^= xoshiro256p_state[3]; } next(); } xoshiro256p_state[0] = s0; xoshiro256p_state[1] = s1; xoshiro256p_state[2] = s2; xoshiro256p_state[3] = s3; } /* ********************************************************************** * Public interface * ********************************************************************** */ /** Generate a random double on the interval [0.0,1.0] */ double random_double(void) { uint64_t x = xoshiro256p_next(); return (double) (x >> 11) * 0x1.0p-53; } /** Generate a random 32 bit unsigned int */ unsigned int random_int(void) { uint64_t x = xoshiro256p_next(); return (unsigned int) (x>>32); } /** Initialize the random number generator */ void random_initialize(void) { uint64_t seed = (uint64_t) time(NULL); char bytes[sizeof(uint64_t)]; if (platform_randombytes(bytes, sizeof(uint64_t))) { seed = *((uint64_t *) bytes); } else { fprintf(stderr, "Warning: initializing random number generator using time-not recommended for production runs.\n"); } /* Use this to initialize splitmix64 */ splitmix64_seed(seed); /* Then initialize xoshiro256pp */ xoshiro256pp_state[0]=splitmix64_next(); xoshiro256pp_state[1]=splitmix64_next(); xoshiro256pp_state[2]=splitmix64_next(); xoshiro256pp_state[3]=splitmix64_next(); /* ... and xoshiro256p */ xoshiro256p_state[0]=splitmix64_next(); xoshiro256p_state[1]=splitmix64_next(); xoshiro256p_state[2]=splitmix64_next(); xoshiro256p_state[3]=splitmix64_next(); } ================================================ FILE: src/support/random.h ================================================ /** @file random.c * @author T J Atherton and others (see below) * * @brief Random number generation */ #ifndef random_h #define random_h #include #include #include double random_double(void); unsigned int random_int(void); void random_initialize(void); #endif /* random_h */ ================================================ FILE: src/support/resources.c ================================================ /** @file resources.c * @author T J Atherton * * @brief Locates resources from installation folders and packages */ #include #include "common.h" #include "resources.h" #include "platform.h" #include "file.h" /* ********************************************************************** * Resource enumerator structure * ********************************************************************** */ /** A resource enumerator contains state information to enable the resources system to recursively search various resource locations (e.g. /usr/local/share/morpho/ ) for a specified query. You initialize the resourceenumerator with a query, then call morpho_enumerateresources until no further resources are found, which is indicated by it returning false. */ typedef struct { char *folder; // folder specification to scan char *fname; // filename to match char **ext; // list of possible extensions, terminated by an empty string bool recurse; // whether to search recursively varray_value resources; } resourceenumerator; /* ********************************************************************** * Resource types * ********************************************************************** */ /** Map morphoresourcetypes to package subfolders. @warning: These must match the order of the morphoresourcetypeenum */ static char *_dir[] = { MORPHO_HELPDIR, MORPHO_MODULEDIR, MORPHO_EXTENSIONDIR }; /* Map morphoresourcetypes to extensions */ static char *_helpext[] = { MORPHO_HELPEXTENSION, "" }; static char *_moduleext[] = { MORPHO_EXTENSION, "" }; static char *_extensionext[] = { MORPHO_DYLIBEXTENSION, "dylib", "so", "" }; static char **_ext[] = { _helpext, _moduleext, _extensionext }; /* Map morphoresourcetypes to base folders */ static char *_basedir[] = { MORPHO_HELP_BASEDIR, MORPHO_MODULE_BASEDIR, NULL }; char *_folderfortype(morphoresourcetype type) { return _dir[type]; } char **_extfortype(morphoresourcetype type) { return _ext[type]; } char *_basedirfortype(morphoresourcetype type) { return _basedir[type]; } /* ********************************************************************** * Resources * ********************************************************************** */ varray_value resourcelocations; /** Identifies a base folder emanating from path and consistent with resourceenumerator */ void resources_matchbasefolder(resourceenumerator *en, char *path) { varray_char fname; varray_charinit(&fname); varray_charadd(&fname, path, (int) strlen(path)); varray_charwrite(&fname, MORPHO_DIRSEPARATOR); if (en->folder) varray_charadd(&fname, en->folder, (int) strlen(en->folder)); varray_charwrite(&fname, '\0'); if (platform_isdirectory(fname.data)) { value v = object_stringfromcstring(fname.data, fname.count); if (MORPHO_ISSTRING(v)) varray_valuewrite(&en->resources, v); } varray_charclear(&fname); } /** Locates all possible base folders consistent with the current folder specification @param[in] en - initialized enumerator */ void resources_basefolders(resourceenumerator *en) { for (int i=0; i=f; c--) { if (*c=='.') return c; } return NULL; } /** Checks if a filename matches all criteria in a resourceenumerator @param[in] en - initialized enumerator */ bool resources_matchfile(resourceenumerator *en, char *file) { // Skip extension char *ext = resources_findextension(file); if (!ext) ext = file+strlen(file); // If no extension found, just go to the end of the filename if (en->fname) { // Match filename if requested char *f = ext; while (f>=file && *f!=MORPHO_DIRSEPARATOR) f--; // Find last separator if (*f==MORPHO_DIRSEPARATOR) f++; // If we stopped at a separator, skip it size_t len = strlen(en->fname); if (strncmp(en->fname, f, len)!=0) return false; // Compare string if (!(f[len]=='.' || f[len]=='\0')) return false; // Ensure filename is terminated by null byte or file extension separator. } if (!en->ext) return true; // Match extension only if requested if (*ext!='.') return false; for (int k=0; *en->ext[k]!='\0'; k++) { // Check extension against possible extensions if (strcmp(ext+1, en->ext[k])==0) return true; // We have a match } return false; } /** Searches a given folder, adding all resources to the enumerator @param[in] en - initialized enumerator */ void resources_searchfolder(resourceenumerator *en, char *path) { MorphoDirContents contents; size_t size = platform_maxpathsize(); char buffer[size]; char sep[2] = { MORPHO_DIRSEPARATOR, '\0' }; if (platform_directorycontentsinit(&contents, path)) { while (platform_directorycontents(&contents, buffer, size)) { /* Construct the file name */ size_t len = strlen(path)+strlen(buffer)+2; char file[len]; strcpy(file, path); strcat(file, sep); strcat(file, buffer); if (platform_isdirectory(file)) { if (!en->recurse) continue; } else { if (!resources_matchfile(en, file)) continue; } /* Add the file or folder to the work list */ value v = object_stringfromcstring(file, len); if (MORPHO_ISSTRING(v)) varray_valuewrite(&en->resources, v); } platform_directorycontentsclear(&contents); } } /** Initialize a resource enumerator @param[in] en - enumerator to initialize @param[in] folder - folder specification to scan @param[in] fname - filename to match @param[in] ext - list of possible extensions, terminated by an empty string @param[in] recurse - search recursively */ void resourceenumerator_init(resourceenumerator *en, char *folder, char *fname, char *ext[], bool recurse) { en->folder = folder; en->fname = fname; en->ext = ext; en->recurse = recurse; varray_valueinit(&en->resources); resources_basefolders(en); } /** Clears a resource enumerator @param[in] en - enumerator to clear */ void resourceenumerator_clear(resourceenumerator *en) { for (int i=0; iresources.count; i++) morpho_freeobject(en->resources.data[i]); varray_valueclear(&en->resources); } /** Enumerates resources @param[in] en - enumerator to use @param[out] out - next resource */ bool resourceenumerator_enumerate(resourceenumerator *en, value *out) { if (en->resources.count==0) return false; value next = en->resources.data[--en->resources.count]; while (platform_isdirectory(MORPHO_GETCSTRING(next))) { resources_searchfolder(en, MORPHO_GETCSTRING(next)); morpho_freeobject(next); if (en->resources.count==0) return false; next = en->resources.data[--en->resources.count]; } *out = next; return true; } /** Adds the default folder for a given resource type */ void resourceenumerator_defaultfolder(resourceenumerator *en, morphoresourcetype type) { char *basedir = _basedirfortype(type); if (basedir) { value v = object_stringfromcstring(basedir, strlen(basedir)); if (MORPHO_ISSTRING(v)) varray_valuewrite(&en->resources, v); } } /** Locates a resource @param[in] type - type of resource to locate @param[in] fname - filename to match @param[out] out - an objectstring that contains the resource file location */ bool morpho_findresource(morphoresourcetype type, char *fname, value *out) { char *folder = _folderfortype(type); char **ext = _extfortype(type); bool success=false; resourceenumerator en; resourceenumerator_init(&en, folder, fname, ext, true); resourceenumerator_defaultfolder(&en, type); success=resourceenumerator_enumerate(&en, out); resourceenumerator_clear(&en); return success; } /** Locates all resources of a given type @param[in] type - type of resource to locate @param[out] out - a varray_value that contains the resource file locations */ bool morpho_listresources(morphoresourcetype type, varray_value *out) { char *folder = _folderfortype(type); char **ext = _extfortype(type); resourceenumerator en; resourceenumerator_init(&en, folder, NULL, ext, true); resourceenumerator_defaultfolder(&en, type); value file; while (resourceenumerator_enumerate(&en, &file)) { varray_valuewrite(out, file); } resourceenumerator_clear(&en); return (out->count>0); } /** Loads a list of packages in ~/.morphopackages */ void resources_loadpackagelist(void) { varray_char line; varray_charinit(&line); size_t len = platform_maxpathsize(); char home[len]; if (platform_gethomedirectory(home, len)) { varray_charadd(&line, home, (int) strlen(home)); } varray_charwrite(&line, MORPHO_DIRSEPARATOR); varray_charadd(&line, MORPHO_PACKAGELIST, (int) strlen(MORPHO_PACKAGELIST)); varray_charwrite(&line, '\0'); FILE *f = fopen(line.data, "r"); if (f) { while (!feof(f)) { line.count=0; if (file_readlineintovarray(f, &line) && line.count>0) { value str = object_stringfromvarraychar(&line); varray_valuewrite(&resourcelocations, str); } } fclose(f); } varray_charclear(&line); } void resources_initialize(void) { varray_valueinit(&resourcelocations); resources_loadpackagelist(); morpho_addfinalizefn(resources_finalize); } void resources_finalize(void) { for (int i=0; i #include "build.h" #include "threadpool.h" /* ********************************************************************** * Thread pools * ********************************************************************** */ int threadpool_nthreads = MORPHO_DEFAULTTHREADNUMBER; /** Sets the number of worker threads to use */ void morpho_setthreadnumber(int nthreads) { threadpool_nthreads = nthreads; } /** Returns the number of worker threads to use */ int morpho_threadnumber(void) { return threadpool_nthreads; } DEFINE_VARRAY(task, task); /* Worker thread */ MorphoThreadFnReturnType threadpool_worker(void *ref) { threadpool *pool = (threadpool *) ref; task t = { .func = NULL, .arg = NULL }; while (true) { /* Await a task */ MorphoMutex_lock(&pool->lock_mutex); while (pool->queue.count == 0 && !pool->stop) MorphoCond_wait(&pool->work_available_cond, &pool->lock_mutex); if (pool->stop) break; /* Terminate if asked to do so */ varray_taskpop(&pool->queue, &t); /* Get the task */ pool->nprocessing++; MorphoMutex_unlock(&pool->lock_mutex); if (t.func) { (t.func) (t.arg); }; /* Perform the assigned task */ MorphoMutex_lock(&pool->lock_mutex); pool->nprocessing--; if (!pool->stop && pool->nprocessing == 0 && pool->queue.count == 0) MorphoCond_signal(&pool->work_halted_cond); MorphoMutex_unlock(&pool->lock_mutex); } /* No need to lock here as lock was already obtained */ pool->nthreads--; MorphoCond_signal(&pool->work_halted_cond); MorphoMutex_unlock(&pool->lock_mutex); return (MorphoThreadFnReturnType) NULL; } /* Interface */ /** Initialize a threadpool with n worker threads. */ bool threadpool_init(threadpool *pool, int nworkers) { if (nworkers<1) return false; varray_taskinit(&pool->queue); varray_MorphoThreadinit(&pool->threads); MorphoMutex_init(&pool->lock_mutex); MorphoCond_init(&pool->work_available_cond); MorphoCond_init(&pool->work_halted_cond); pool->nthreads=nworkers; pool->stop=false; pool->nprocessing=0; for (int i=0; inthreads; i++) { MorphoThread thread; MorphoThread_create(&thread, threadpool_worker, pool); varray_MorphoThreadadd(&pool->threads, &thread, 1); } return true; } /** Clears a threadpool. */ void threadpool_clear(threadpool *pool) { MorphoMutex_lock(&pool->lock_mutex); varray_taskclear(&pool->queue); /* Erase any remaining tasks */ pool->stop = true; /* Tell workers to stop */ MorphoCond_broadcast(&pool->work_available_cond); /* Signal to workers to wake up */ MorphoMutex_unlock(&pool->lock_mutex); for (int i=0; ithreads.count; i++) MorphoThread_join(pool->threads.data[i]); MorphoMutex_clear(&pool->lock_mutex); MorphoCond_clear(&pool->work_available_cond); MorphoCond_clear(&pool->work_halted_cond); for (int i=0; ithreads.count; i++) MorphoThread_clear(pool->threads.data[i]); varray_MorphoThreadclear(&pool->threads); } /** Adds a task to the threadpool */ bool threadpool_add_task(threadpool *pool, workfn func, void *arg) { bool success=true; MorphoMutex_lock(&pool->lock_mutex); task t = { .func = func, .arg=arg }; if (!varray_taskadd(&pool->queue, &t, 1)) success=false; /* Add the task to the queue */ MorphoCond_broadcast(&pool->work_available_cond); /* Signal there is work to be done */ MorphoMutex_unlock(&pool->lock_mutex); return success; } /** Blocks until all tasks in the thread pool are complete */ void threadpool_fence(threadpool *pool) { MorphoMutex_lock(&pool->lock_mutex); while (true) { if ((!pool->stop && (pool->queue.count > 0 || pool->nprocessing>0)) || // If we are simply waiting for tasks to finish (pool->stop && pool->nthreads > 0)) { // Or if we have been told to stop MorphoCond_wait(&pool->work_halted_cond, &pool->lock_mutex); // Block until working_cond is set } else break; } MorphoMutex_unlock(&pool->lock_mutex); } ================================================ FILE: src/support/threadpool.h ================================================ /** @file threadpool.h * @author T J Atherton * * @brief Thread pool */ #ifndef threadpool_h #define threadpool_h #include #include "varray.h" #include "platform.h" /* ----------------------------------------- * Tasks * ----------------------------------------- */ /** A task is the basic unit of work that is allocated to the thread pool; it comprises a work function to perform the task, and a single argument. */ /** A workfn will be called by the threadpool once a thread is available. You must supply all relevant information for both input and output in a single structure passed as an opaque reference. */ typedef bool (* workfn) (void *arg); typedef struct { workfn func; // Function to call that performs the task void *arg; // Opaque pointer passed as an argument to the work function } task; DECLARE_VARRAY(task, task); /* ----------------------------------------- * Thread pools * ----------------------------------------- */ typedef struct { MorphoMutex lock_mutex; /* Lock for access to threadpool structure. */ MorphoCond work_available_cond; /* Signals that work is available. */ MorphoCond work_halted_cond; /* Signals when no threads are processing. */ int nprocessing; /* Number of threads actively processing work */ int nthreads; /* Number of active threads. */ bool stop; /* Indicates threads should terminate */ varray_task queue; /* Queue of tasks lined up */ varray_MorphoThread threads; /* Threads created by this pool */ } threadpool; bool threadpool_init(threadpool *pool, int nworkers); void threadpool_clear(threadpool *pool); bool threadpool_add_task(threadpool *pool, workfn func, void *arg); void threadpool_fence(threadpool *pool); void threadpool_wait(threadpool *pool); #endif /* threadpool_h */ ================================================ FILE: test/apply/apply.morpho ================================================ // Apply a function to arguments fn f(x) { return 1-x^2 } print apply(f, 0.5) // expect: 0.75 print apply(f, 0.1) // expect: 0.99 ================================================ FILE: test/apply/apply_args.morpho ================================================ // Apply a function to arguments fn f(x,y,z) { return x*x+y*y+z*z; } print apply(f, 0.5, 0.2, 0.1) // expect: 0.3 print apply(f, 0.5) // expect: Error 'InvldArgs' ================================================ FILE: test/apply/apply_builtin.morpho ================================================ // Apply a function to arguments var a = sin(0.1) var b = apply(sin, 0.1) print a-b // expect: 0 ================================================ FILE: test/apply/apply_closure.morpho ================================================ // Apply a closure to arguments fn f(x) { fn g(y) { return x+y } return g } print apply(apply(f, 2), 3) // expect: 5 ================================================ FILE: test/apply/apply_list.morpho ================================================ // Apply a function to a list of arguments fn f(x,y) { return x^2+y^2 } print apply(f, [0.5,0.5]) // expect: 0.5 var a = [0.1, 0.1] print apply(f, a) // expect: 0.02 fn g(x) { print x } apply(g, [[1,2,3]]) // expect: [ 1, 2, 3 ] ================================================ FILE: test/apply/apply_method.morpho ================================================ // Apply a method class Blob { init(x) { self.a = x } blip(x) { return self.a+x^2 } } var a = Blob(0.5) print a.blip(0.5) // expect: 0.75 print apply(a.blip, 0.1) // expect: 0.51 var p = a.blip print apply(p, 0.1) // expect: 0.51 ================================================ FILE: test/apply/apply_recursion.morpho ================================================ // Apply a function to arguments fn f(x) { if (x==0) return 0 return x+apply(f, x-1) } print f(60) // expect: 1830 ================================================ FILE: test/arithmetic/power.morpho ================================================ // Test power operator print 2^2 // expect: 4 print 2^3^2 // expect: 512 // Check both integer and float operands work print 2.0^3.0 // expect: 8 print 2.0^3 // expect: 8 print 2^3.0 // expect: 8 // Check precedence relative to unary operator print -1^2 // expect: -1 var x = -1 print x^2 // expect: 1 print (-1)^2 // expect: 1 ================================================ FILE: test/arithmetic/redirection.morpho ================================================ class Redirect { add (x) { print "Adding ${x}" } addr (x) { print "Right adding ${x}" } sub (x) { print "Subtracting ${x}" } subr (x) { print "Right subtracting ${x}" } mul (x) { print "Multiplying ${x}" } mulr (x) { print "Right multiplying ${x}" } div (x) { print "Dividing ${x}" } divr (x) { print "Right dividing ${x}" } } var r = Redirect() r + 1 // expect: Adding 1 1 + r // expect: Right adding 1 r - 2 // expect: Subtracting 2 2 - r // expect: Right subtracting 2 r * 3 // expect: Multiplying 3 3 * r // expect: Right multiplying 3 r / 3 // expect: Dividing 3 3 / r // expect: Right dividing 3 (-r) // expect: Right subtracting 0 ================================================ FILE: test/arithmetic/unary_div.morpho ================================================ // Ensuring unary minus and operations are compatible var a = 1 var b = 4 print -a/b // expect: -0.25 print -a/b // expect: -0.25 ================================================ FILE: test/array/array.morpho ================================================ /* * Arrays */ // Initialize variable with a list var a = [5, 3] print a[0] // expect: 5 print a[1] // expect: 3 // Uninitialized array var b[1] print b[0] // expect: 0 // Two dimensional array var c[2,2] print c[1,1] // expect: 0 //= [[1,0],[0,1]] // Set array element a[0] = 20 print a[0] // expect: 20 // Set array element 2D c[0,0]=3 c[1,1]=4 print c[0,0] // expect: 3 print c[1,0] // expect: 0 print c[1,1] // expect: 4 print c[0,1] // expect: 0 // Assign beyond bounds a[4] = 2 // expect Error 'IndxBnds' ================================================ FILE: test/array/array_assign_beyond_bounds.morpho ================================================ var a = [1,2,3] a[4] = 2 // expect: Error 'IndxBnds' Array index out of bounds. ================================================ FILE: test/array/array_dim_with_initializer.morpho ================================================ // Initialize array var a[2,2] = [ [ 1, 0 ], [ 2, 0 ] ] print a // expect: [ [ 1, 0 ], [ 2, 0 ] ] fn f() { for (i in 1..2) { var a[2,2] = [ [ 1, 2 ], [ 3, 4 ] ] print a } } f() // expect: [ [ 1, 2 ], [ 3, 4 ] ] // expect: [ [ 1, 2 ], [ 3, 4 ] ] ================================================ FILE: test/array/array_enumerate.morpho ================================================ /* Enumerate the contents of an array */ // Two dimensional array var a[2,2] a[0,0]=1 a[1,1]=2 for (x in a) print x // expect: 1 // expect: 0 // expect: 0 // expect: 2 ================================================ FILE: test/array/array_garbage_collect.morpho ================================================ // Creates nested arrays to stress garbage collector // Recursively make arrays fn makearray(size, depth) { if (depth<1) return nil; var a[size,size] for (var i=0; i, nil ] ] ================================================ FILE: test/array/array_read_beyond_bounds.morpho ================================================ var a = [1,2,3] print a[4] // expect: Error 'IndxBnds' Array index out of bounds. ================================================ FILE: test/array/array_three_dim.morpho ================================================ // Three dimensional arrays var N=2 var a[N,N,N] var k=0 for (var i=0; i3) break print i } // expect: 1 // expect: 2 // expect: 3 // Conditionally skip early elements for (var i=0;i<6;i+=1) { if (i<3) continue print i } // expect: 3 // expect: 4 // expect: 5 ================================================ FILE: test/break/in_forin.morpho ================================================ // For in loops with break and continue // Immediately stop a loop for (i in 1..5) break // Conditionally terminate a loop early for (i in 1..5) { if (i>3) break print i } // expect: 1 // expect: 2 // expect: 3 // Conditionally skip early elements for (i in 1..5) { if (i<3) continue print i } // expect: 3 // expect: 4 // expect: 5 ================================================ FILE: test/break/in_while.morpho ================================================ // While loops with break and continue // Immediately stop a loop while (true) break // Use break and continue var k=0 while (k<10) { if (k==2) { k+=1; continue } if (k>4) break print k k+=1 continue print "You'll never see this!" } // expect: 0 // expect: 1 // expect: 3 // expect: 4 // A loop that shouldn't execute while (false) continue ================================================ FILE: test/builtin/apply.morpho ================================================ // Check whether apply works correctly fn f(x,y,z) { print x+y+z } apply(f, (1,2,3)) // expect: 6 apply(f, [1,2,3]) // expect: 6 apply(f, 1,2,3) // expect: 6 apply([1,2,3], f) // expect error 'ApplyNtCllble' ================================================ FILE: test/builtin/bounds.morpho ================================================ // Calculate bounds of enumerable objects var a = [ 4, 5, 3, 1, 2 ] print bounds(a) // expect: [ 1, 5 ] print min(a) // expect: 1 print max(a) // expect: 5 var b = Matrix([[-1,0],[0, 1]]) print min(b) // expect: -1 print bounds() // expect Error 'MnMxArgs' ================================================ FILE: test/builtin/boundsvargs.morpho ================================================ // Calculate bounds of enumerable objects var a = [ 4, 5, 3, 1, 2 ] var b = [ 2, -1, 0.3 ] print bounds(a, 7, b) // expect: [ -1, 7 ] print bounds() // expect error 'MnMxArgs' ================================================ FILE: test/builtin/iscallable.morpho ================================================ // Check whether iscallable works correctly class Foo { bar() { } } var a[2,2] var f = Foo() fn g(x) { return x } fn h(t) { fn q() { return t } return q } var s = h(1) var vals = [ f, // expect: false g, // expect: true f.bar, // expect: true 9, // expect: false 9.0, // expect: false "Hi", // expect: false a, // expect: false sin, // expect: true s, // expect: true h(2) // expect: true ] for (x in vals) { print iscallable(x) } ================================================ FILE: test/builtin/maxargs.morpho ================================================ print max([]) // expect: nil print max(-120) // expect: -120 ================================================ FILE: test/builtin/minargs.morpho ================================================ print min([]) // expect: nil print min(-120) // expect: -120 ================================================ FILE: test/builtin/minnumbers.morpho ================================================ var list = [1,0.1,0.2,0.5,0.3] print min(list) // expect: 0.1 print max(list) // expect: 1 list.sort() print list // expect: [ 0.1, 0.2, 0.3, 0.5, 1 ] list.sort(fn (a, b) b-a) print list // expect: [ 1, 0.5, 0.3, 0.2, 0.1 ] ================================================ FILE: test/builtin/minvargs.morpho ================================================ // Use min/max on varargs print min(0.1, 0.5, 1, 0.001, 2) // expect: 0.001 print min(0.1, [0.5, 1], [2, 0.003]) // expect: 0.003 print max(0.1, 0.5, 1, 0.001, 2) // expect: 2 print max(0.1, [0.5, 3], [2, 0.003]) // expect: 3 ================================================ FILE: test/builtin/mod.morpho ================================================ // Mod function print mod(5, 3) // expect: 2 print mod(5.3, 3) // expect: 2.3 print mod(4, 2.5) // expect: 1.5 print mod(3.4, 1.1) // expect: 0.1 print mod(1,2,3) // expect Error 'InvldArgs' ================================================ FILE: test/builtin/typecheck.morpho ================================================ // Check that type checking functions work correctly class Foo { init(x) { self.bar = x } } var a[2,2] var vals = [ nil, 1, 0.2, true, false, Object(), "Hi", Foo, 1..2, { "a": 1, "b": 2}, [], a, Matrix(2,2), Sparse([[1,1,1]]) ] var check = [ isnil, isint, isfloat, isbool, isbool, isobject, isstring, isclass, isrange, isdictionary, islist, isarray, ismatrix, issparse ] var tst=Matrix(check.count(),vals.count()) for (i in 0..vals.count()-1) { for (j in 0..vals.count()-1) { if (apply(check[i], [].append(vals[j]))) tst[i,j]=1 } } print tst // expect: [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 1 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 1 1 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 1 1 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 1 1 1 1 1 1 1 1 1 ] // expect: [ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 1 0 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ] ================================================ FILE: test/call/bool.morpho ================================================ // Check that calling a bool generates an err. true() // expect error: 'Uncallable' ================================================ FILE: test/call/call.morpho ================================================ // Test calling functions // Built in function print exp(0) // expect: 1 // A function fn sqr(x) { return x*x } print sqr(2) // expect: 4 ================================================ FILE: test/call/nil.morpho ================================================ // Check that calling nil generates an err nil() // expect error: 'Uncallable' ================================================ FILE: test/call/num.morpho ================================================ // Check that calling a number generates an err 123() // expect error: 'Uncallable' ================================================ FILE: test/call/object.morpho ================================================ // Check that calling an object generates an err class Foo {} var foo = Foo() foo() // expect error: 'Uncallable' ================================================ FILE: test/call/string.morpho ================================================ // Check that calling a string generates an err. "str"() // expect error: 'Uncallable' ================================================ FILE: test/class/apply_class.morpho ================================================ // Ensure a property can store a class and be callable class A { init(a) { self.a = a } } var a = apply(A, "foo") print a // expect:
print a.a // expect: foo ================================================ FILE: test/class/call_class_in_property.morpho ================================================ // Ensure a property can store a class and be callable class A { init(a) { self.a = a } make() { return self.a("foo") } } var a = A(A) print a.make() // expect: ================================================ FILE: test/class/call_on_class.morpho ================================================ // Call on a class class Foo { prnt() { print "Hello" } } Foo.prnt() // expect: Hello System.prnt("Foo\n") // expect: Foo ================================================ FILE: test/class/empty.morpho ================================================ class Foo {} print Foo // expect: @Foo ================================================ FILE: test/class/forward_ref_in_method.morpho ================================================ // Forward reference in method class A { foo() { print boo() } } fn boo() { return "woo!" } var a = A() a.foo() // expect error 'UnrslvdFrwdRf' ================================================ FILE: test/class/inherit_self.morpho ================================================ // Ensure that a class cannot inherit itself class Foo < Foo {} // expect error 'ClssCrcRf' ================================================ FILE: test/class/inherited_method.morpho ================================================ // Check inheritance class Foo { inFoo() { print "in foo" } } class Bar < Foo { inBar() { print "in bar" } } class Baz < Bar { inBaz() { print "in baz" } } var baz = Baz() baz.inFoo() // expect: in foo baz.inBar() // expect: in bar baz.inBaz() // expect: in baz ================================================ FILE: test/class/is.morpho ================================================ // Test 'is' class A { a() { return "Foo" } } class B is A { } var b = B() print b.a() // expect: Foo ================================================ FILE: test/class/keyword_method.morpho ================================================ // Ensure a class can use keywords for method labels class A { init(a) { self.a = a } print() { print self.a } } var a = A("foo") a.print() // expect: foo ================================================ FILE: test/class/keyword_property.morpho ================================================ // Ensure a class can use keywords for property labels class A { init(a) { self.while = a } } var a = A("foo") print a // expect: print a.while // expect: foo ================================================ FILE: test/class/linearization.morpho ================================================ // Class linearization class O { } class F is O { } class E is O { } class D is O { } class C is D with F { } class B is D with E { } class A is B with C { } print O.linearization() // expect: [ @O ] print F.linearization() // expect: [ @F, @O ] print E.linearization() // expect: [ @E, @O ] print D.linearization() // expect: [ @D, @O ] print C.linearization() // expect: [ @C, @D, @F, @O ] print B.linearization() // expect: [ @B, @D, @E, @O ] print A.linearization() // expect: [ @A, @B, @C, @D, @E, @F, @O ] ================================================ FILE: test/class/local_fn_supersedes_method_call.morpho ================================================ // A local function supercedes a method call class A { foo() { fn goo(x) { print "Boo" } goo("Hoo") } goo(x) { print x } } var a = A() a.foo() // expect: Boo a.goo("Woo") // expect: Woo ================================================ FILE: test/class/local_inherit_other.morpho ================================================ // Check that we can return a local class class A {} fn f() { class B < A {} return B } print f() // expect: @B ================================================ FILE: test/class/local_inherit_self.morpho ================================================ // Ensure classes cannot inherit from themselves { class Foo < Foo {} // expect error 'ClssCrcRf' } ================================================ FILE: test/class/local_reference_self.morpho ================================================ // Check a method can return the class. { class Foo { returnSelf() { return Foo } } print Foo().returnSelf() // expect: @Foo } ================================================ FILE: test/class/method_call_supersedes_global_fn.morpho ================================================ // A method reference supersedes a global function definition fn goo(x) { print "Boo" } class A { foo() { goo("Hoo") } goo(x) { print x } } var a = A() a.foo() // expect: Hoo a.goo("Woo") // expect: Woo ================================================ FILE: test/class/mixins.morpho ================================================ // Test mixins class A { a() { return "Foo" } b() { return "Ooo Noo!" } } class B { b() { return "Boo" } c() { return "Coo" } } class Q { b() { return "Woo" } q() { return "Qoo" } z() { return self.a() } } class C is A with B, Q { } var x = C() print x.a() // expect: Foo print x.b() // expect: Woo print x.c() // expect: Coo print x.q() // expect: Qoo print x.z() // expect: Foo ================================================ FILE: test/class/nested_class.morpho ================================================ class Foo { foo() { class Boo { } return Boo() } } print Foo() // expect error 'NstdClss' ================================================ FILE: test/class/no_self_for_method_call.morpho ================================================ // Avoid needing self in a method call class A { foo() { self.goo("Foo") goo("Hoo") } goo(x) { print x } } var a = A() a.foo() // expect: Foo // expect: Hoo a.goo("Woo") // expect: Woo ================================================ FILE: test/class/prefer_local.morpho ================================================ // Ensure that variables shadow globals in method definitions var m = "Still here!" class Foo { bar(f) { var m = Matrix(2,2) for (i in 0...2) { for (j in 0...2) { m[i,j]=i*j } } print m } } var a = Foo() a.bar(1) // expect: [ 0 0 ] // expect: [ 0 1 ] print m // expect: Still here! ================================================ FILE: test/class/reference_self.morpho ================================================ class Foo { returnSelf() { return Foo } } print Foo().returnSelf() // expect: @Foo ================================================ FILE: test/class/syntax_error_in_constructor.morpho ================================================ // Syntax err. in call to constructor class A { init(m, a=1, b=1, c=1) { } } class B {} var b = B() var a = A(b., a=1.0, b=1.0, c=1.0) // expect error 'ExpExpr' ================================================ FILE: test/class/unlinearizable.morpho ================================================ // Unlinearizable classes class A { } class B { } class C is A with B { } class D is B with A { } class E is C with D { } // expect error 'ClssLnrz' ================================================ FILE: test/closure/assign_to_closure.morpho ================================================ // Check that assignment works within closures var f var g { var local = "local" // A local variable fn f_() { // Set the value of the local variable print local local = "after f" print local } f = f_ fn g_() { print local local = "after g" print local } g = g_ } f() // expect: local // expect: after f g() // expect: after f // expect: after g ================================================ FILE: test/closure/assign_to_shadowed_later.morpho ================================================ var a = "global" { fn assign() { a = "assigned" } var a = "inner" assign() print a // expect: inner } print a // expect: assigned ================================================ FILE: test/closure/close_over_function_parameter.morpho ================================================ var f fn foo(param) { fn f_() { print param } f = f_ } foo("param") f() // expect: param ================================================ FILE: test/closure/close_over_later_variable.morpho ================================================ // This is a regression test. There was a bug where if an upvalue for an // earlier local (here "a") was captured *after* a later one ("b"), then it // would crash because it walked to the end of the upvalue list (correct), but // then didn't handle not finding the variable. fn f() { var a = "a" var b = "b" fn g() { print b // expect: b print a // expect: a } g() } f() ================================================ FILE: test/closure/closed_closure_in_function.morpho ================================================ var f { var local = "local" fn f_() { print local } f = f_ } f() // expect: local ================================================ FILE: test/closure/closures_in_loop.morpho ================================================ var mesh = Mesh() while (true) { var L = 1 var a=Selection(mesh, fn (x,y,z) x+L<0.01) var b=LineIntegral(fn (x) x) break } print "ok" // expect: ok ================================================ FILE: test/closure/nested_closure.morpho ================================================ var f fn f1() { var a = "a" fn f2() { var b = "b" fn f3() { var c = "c" fn f4() { print a print b print c } f = f4 } f3() } f2() } f1() f() // expect: a // expect: b // expect: c ================================================ FILE: test/closure/open_closure_in_function.morpho ================================================ { var local = "local" fn f() { print local // expect: local } f() } ================================================ FILE: test/closure/prioritize_upvalues_over_globals.morpho ================================================ // Checks that upvalues are searched before globals // Create a global var a = "Hello" // This is a reference to an upvalue fn h(a) { fn q() { return a } return q } // Hence the returned value is a closure print h(1) // expect: <> print h(1)() // expect: 1 // This is a reference to a global fn g() { fn q() { return a } return q } print g() // expect: print g()() // expect: Hello ================================================ FILE: test/closure/reference_closure_multiple_times.morpho ================================================ var f { var a = "a" fn f_() { print a print a } f = f_ } f() // expect: a // expect: a ================================================ FILE: test/closure/reuse_closure_slot.morpho ================================================ { var f { var a = "a" fn f_() { print a } f = f_ } { // Since a is out of scope, the local slot will be reused by b. Make sure // that f still closes over a. var b = "b" f() // expect: a } } ================================================ FILE: test/closure/shadow_closure_with_local.morpho ================================================ { var foo = "closure" fn f() { { print foo // expect: closure var foo = "shadow" print foo // expect: shadow } print foo // expect: closure } f() } ================================================ FILE: test/closure/unused_closure.morpho ================================================ // Ensure closures that are never created don't cause problems { var a = "a" if (false) { fn foo() { a } } } // If we get here, we didn't segfault when a went out of scope. print "ok" // expect: ok ================================================ FILE: test/closure/unused_later_closure.morpho ================================================ // This is a regression test. When closing upvalues for discarded locals, it // wouldn't make sure it discarded the upvalue for the correct stack slot. // // Here we create two locals that can be closed over, but only the first one // actually is. When "b" goes out of scope, we need to make sure we don't // prematurely close "a". var closure { var a = "a" { var b = "b" fn returnA() { return a } closure = returnA if (false) { fn returnB() { return b } } } print closure() // expect: a } ================================================ FILE: test/closure/veneer.morpho ================================================ // Test veneer class fn func(x) { fn g() { return x } return g } var a = func(1) print a() // expect: 1 print a // expect: <> print a.clss() // expect: @Closure print a.clone() // expect error 'ObjCantClone' ================================================ FILE: test/comments/line_at_eof.morpho ================================================ // Comment at end of file print "ok" // expect: ok // comment ================================================ FILE: test/comments/multline.morpho ================================================ /* This * is * a * multiline * comment * print "Hello" */ ================================================ FILE: test/comments/nested.morpho ================================================ /* /* Nesting /* Comments /* Like */ */ this */ */ ================================================ FILE: test/comments/only_line_comment.morpho ================================================ // comment ================================================ FILE: test/comments/only_line_comment_and_line.morpho ================================================ // comment ================================================ FILE: test/comments/unicode.morpho ================================================ // Unicode characters are allowed in comments. // // Latin 1 Supplement: £§¶ÜÞ // Latin Extended-A: ĐĦŋœ // Latin Extended-B: ƂƢƩǁ // Other stuff: ឃᢆ᯽₪ℜ↩⊗┺░ // Emoji: ☃☺♣ print "ok" // expect: ok ================================================ FILE: test/comments/unterminated.morpho ================================================ // expect error 'UntrmComm' /* Unterminated comment print "Hello" ================================================ FILE: test/complex/ComplexBuiltin.morpho ================================================ //Complex builtin test import constants var A = Complex(1,2) var B = Complex(2.2,-3.1) var C = Complex(3,4) var compList = [A,B,C] var tol = 1e-15 print A == 1+2im // expect: true print 1+2im == 1+2*im // expect: true print 1-2im == 1+2/im // expect: true print B == 2.2-3.1im // expect: true for (Z in compList){ print real(Z) } // expect: 1 // expect: 2.2 // expect: 3 // imag for (Z in compList){ print imag(Z) } // expect: 2 // expect: -3.1 // expect: 4 // abs print abs(C) // expect: 5 // exp print (imag(exp(Complex(2,Pi/2)))==exp(2)) // expect: true print real(exp(Complex(4,Pi)))==-exp(4) // expect: true // log print log((E*im))==(1+Pi/2*im) // expect: true print abs(log(abs(A))+angle(A)*im - log(A))<1e-15 // expect: true // log10 print abs(log10(A)-(log(abs(A))/log(10)+angle(A)*im/log(10))) ================================================ FILE: test/constructor/call_init_explicitly.morpho ================================================ class Foo { init(arg) { print "Foo.init(" + arg + ")" self.field = "init" } } var foo = Foo("one") // expect: Foo.init(one) foo.field = "field" var foo2 = foo.init("two") // expect: Foo.init(two) print foo2 // expect: // Make sure init() doesn't create a fresh instance. print foo.field // expect: init ================================================ FILE: test/constructor/default.morpho ================================================ class Foo {} var foo = Foo() print foo // expect: ================================================ FILE: test/constructor/default_arguments.morpho ================================================ class Foo {} var foo = Foo(1, 2, 3) // expect error 'NoInit' ================================================ FILE: test/constructor/early_return.morpho ================================================ class Foo { init() { print "init" return print "nope" } } var foo = Foo() // expect: init print foo // expect: ================================================ FILE: test/constructor/extra_arguments.morpho ================================================ class Foo { init(a, b) { self.a = a self.b = b } } var foo = Foo(1, 2, 3, 4) // expect error 'InvldArgs' ================================================ FILE: test/constructor/init_not_method.morpho ================================================ class Foo { init(arg) { print "Foo.init(" + arg + ")" self.field = "init" } } fn init() { print "not an initializer" } init() // expect: not an initializer ================================================ FILE: test/constructor/missing_arguments.morpho ================================================ class Foo { init(a, b) {} } var foo = Foo(1) // expect error 'InvldArgs' ================================================ FILE: test/constructor/return_in_nested_function.morpho ================================================ class Foo { init() { fn init() { return "bar" } print init() // expect: bar } } print Foo() // expect: ================================================ FILE: test/constructor/return_value.morpho ================================================ class Foo { init() { return "result" // expect error 'InitRtn' } } ================================================ FILE: test/dictionary/clear.morpho ================================================ // Test Dictionary clear method var mass = { "Capital" : "Boston", "Population" : 6892503 } mass.clear() mass["Abbreviation"] = "MA" print mass // expect: { Abbreviation : MA } ================================================ FILE: test/dictionary/contains.morpho ================================================ // Test Dictionary contains method var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass.contains("Capital") // expect: true print mass.contains("State") // expect: false ================================================ FILE: test/dictionary/empty_literal.morpho ================================================ // Empty dictionary literal var a = { } print isdictionary(a) // expect: true ================================================ FILE: test/dictionary/inherited.morpho ================================================ // Test Dictionary methods inherited from Object var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass.respondsto("contains") // expect: true print islist(mass.respondsto()) // expect: true print mass.clss() // expect: @Dictionary print mass.superclass() // expect: @Object print islist(mass.invoke("respondsto")) // expect: true print mass.has("a") // expect: false print mass.count() // expect: 2 ================================================ FILE: test/dictionary/key_not_found.morpho ================================================ // Test Dictionary standard methods var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass["Capital"] // expect: Boston print mass["Area"] // expect error 'DctKyNtFnd' ================================================ FILE: test/dictionary/literal_from_vars.morpho ================================================ // Dictionary literal in a loop constucted from variables (with operations!) // Checks register allocation and instruction count var t = "Type" for (i in 1..2) { var p = { t: "Cruller", "Calories": 400*i+50 } print p["Type"] print p["Calories"] } // expect: Cruller // expect: 450 // expect: Cruller // expect: 850 ================================================ FILE: test/dictionary/literal_in_function.morpho ================================================ // Dictionary literal in a function fn state(cap, pop) { return { "Capital" : cap, "Population" : 6892503 } } var mass=state("Boston", 6892503) print mass["Capital"] // expect: Boston print mass["Population"] // expect: 6892503 ================================================ FILE: test/dictionary/literal_in_loop.morpho ================================================ // Dictionary literal in a loop // (Checks the compiler returned the right instruction count) for (i in 1..5) { var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass["Capital"] } // expect: Boston // expect: Boston // expect: Boston // expect: Boston // expect: Boston ================================================ FILE: test/dictionary/methods.morpho ================================================ // Test Dictionary standard methods var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass // expect: { Population : 6892503 , Capital : Boston } print mass.count() // expect: 2 for (key in mass) print key // expect: Population // expect: Capital var mass2 = mass.clone() print mass2["Capital"] // expect: Boston ================================================ FILE: test/dictionary/missing_comma.morpho ================================================ // Missing comma between key/value pairs var a = { "a" : "b" "c" : "d" } // expect error 'MssngComma' ================================================ FILE: test/dictionary/missing_separator.morpho ================================================ // Missing separator between key/value pair var a = { "a" "b" } // expect error 'DctSprtr' ================================================ FILE: test/dictionary/remove.morpho ================================================ // Test Dictionary remove method var mass = { "Capital" : "Boston", "Population" : 6892503 } mass.remove("Capital") print mass // expect: { Population : 6892503 } ================================================ FILE: test/dictionary/set_operations.morpho ================================================ // Dictionary set operations var a = { 1: 0, 2: 0, 3: 0 } var b = { 2: 0, 3: 0, 4: 0 } var k = a.union(b).keys() k.sort() print k // expect: [ 1, 2, 3, 4 ] k=a.intersection(b).keys() k.sort() print k // expect: [ 2, 3 ] k=a.difference(b).keys() k.sort() print k // expect: [ 1 ] // Addition and subtraction operators map onto difference and union print { "foo": 0, "bar": 0} - { "bar": 0} // expect: { foo : 0 } k=({ "f": 0, "g": 0} + { "h": 0, "i": 0}).keys() k.sort() print k // expect: [ f, g, h, i ] ================================================ FILE: test/dictionary/syntax.morpho ================================================ // Dictionary literals var mass = { "Capital" : "Boston", "Population" : 6892503 } print mass // expect: { Population : 6892503 , Capital : Boston } print mass["Capital"] // expect: Boston print mass["Population"] // expect: 6892503 var shft = { 1: 2, 2: 3, 3: 4} print shft[1] // expect: 2 print shft[2] // expect: 3 print shft[3] // expect: 4 ================================================ FILE: test/dictionary/tombstone.morpho ================================================ // This test makes sure that dictionary insertion happens even if there // are only tombstones and no blank entries left. var d = Dictionary() var N = 16 // This is chosen because it's the minimum and defualt capacity of the Morpho dictionary. var n = floor(0.75*N) // This is the maximum number of entries that can be added to the dictionary without triggering a resize to 2*N // Add n items var key for (i in 1..n) { key = "${i}" d[key] = i } // Now, remove a single key, followed by addition of another key, for // all these keys. This triggers a situation at i=n-1 that all the blank // entries have been replaced with tombstones. The old // implementation of dictionary_find returns a NULL pointer for entry, // and _dictionary_insert in turn doesn't handle that scenario. Hence, // the last assignment of d[newkey] fails silently. The current // implementation should handle this scenario. var newkey for (i in 1..n) { key = "${i}" d.remove(key) newkey = "new_${i}" d[newkey] = i } print d[newkey] // expect: 12 ================================================ FILE: test/dictionary/unterminated.morpho ================================================ // Empty dictionary literal var a = { // expect error 'DctTrmntr' ================================================ FILE: test/do/do_in_fn.morpho ================================================ // Do..while loop in a function fn f(N) { var i=0 do { print i i+=1 } while (i var f = Field(m) var g = Field(m) // Create a convenient function for output fn write(f) { print "[${f[0]}, ${f[1]}, ${f[2]}, ${f[3]}]" } // Set elements for (i in 0...4) f[i]=i write(f) // expect: [0, 1, 2, 3] write(g) // expect: [0, 0, 0, 0] g.assign(f) write(g) // expect: [0, 1, 2, 3] ================================================ FILE: test/field/assign_matrix.morpho ================================================ // Scalar field assign with matrix var m = Mesh("square.mesh") print m // expect: var f = Field(m) // Create a convenient function for output fn write(f) { print "[${f[0]}, ${f[1]}, ${f[2]}, ${f[3]}]" } var a = f.linearize().clone() // Set elements for (i in 0...4) a[i]=i // Show fresh field write(f) // expect: [0, 0, 0, 0] // Assign matrix to field f.assign(a) // Show assigned field write(f) // expect: [0, 1, 2, 3] var b = Matrix([1,2]) f.assign(b) // expect error 'FldIncmptbl' ================================================ FILE: test/field/badfnin.morpho ================================================ var m = Mesh("square.mesh") var f = Field(m, fn(x,y,z) [1,2]) // expect error: 'FldOpFn' ================================================ FILE: test/field/bounds.morpho ================================================ // Calculate bounds of enumerable objects var m = Mesh("square.mesh") print m // expect: var f = Field(m) // Set elements for (i in 0...4) f[i]=i print bounds(f) // expect: [ 0, 3 ] print min(f) // expect: 0 print max(f) // expect: 3 ================================================ FILE: test/field/discretizations/boundary_line_integrals.morpho ================================================ // Gradient of CG1 field import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() m.addgrade(1) fn integrand(x, q) { return q } var f = Field(m, fn(x,y) 3 + 4*x - 2*y, finiteelementspace=FiniteElementSpace("CG1", grade=2)) print abs(LineIntegral(integrand, f, method={ }).total(m) - (7 + 4*sqrt(2))) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg1_area_in_2d_grad.morpho ================================================ // Gradient of CG1 field import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([2,0]) mb.addvertex([0,2]) mb.addvertex([2,2]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) var l = FiniteElementSpace("CG1", grade=2) fn integrand(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y) 3 - 4*x + 2*y, finiteelementspace=l) print abs(AreaIntegral(integrand, f, method={ }).total(m) - 80) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg1_area_in_2d_grad_old.morpho ================================================ // Gradient of CG1 field import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([2,0]) mb.addvertex([0,2]) mb.addvertex([2,2]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) fn integrand(x, q) { var g = grad(q) return g[0].norm()^2 + g[1].norm()^2 } fn q0(x,y) { return Matrix([3 - 4*x + 2*y, 3 + 4*x - 2*y]) } var f = Field(m, q0) print abs(AreaIntegral(integrand, f, method={ }).total(m) - 160) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg1_area_in_2d_old.morpho ================================================ // Gradient of CG1 field import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([2,0]) mb.addvertex([0,2]) mb.addvertex([2,2]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) fn integrand(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y) 3 - 4*x + 2*y) print abs(AreaIntegral(integrand, f, method={ }).total(m) - 80) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg1_line_in_3d_grad.morpho ================================================ // Gradient of CG1 field on a line import meshtools var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([2,2,1]) mb.addedge([0,1]) var m = mb.build() fn integrand(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y,z) x + y + 0.5*z, finiteelementspace=FiniteElementSpace("CG1", grade=1)) print abs(LineIntegral(integrand, f, method={ }).total(m) - 6.75) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_area_in_2d.morpho ================================================ // CG2 FunctionSpace in two dimensions import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addvertex([1,1]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) var l = FiniteElementSpace("CG2", grade=2) var f = Field(m, fn (x,y) Matrix([[x,y],[-y, x]]), finiteelementspace=l) print f.shape() // expect: [ 1, 1, 0 ] print abs(AreaIntegral(fn (x) sqrt(x[0]*x[1]), method={ "rule" : "cubtri7" }).total(m)-4/9) < 1e-6 // expect: true print abs(AreaIntegral(fn (x, q) q.trace()^2, f, method={ }).total(m)-4/3) < 1e-6 // expect: true ================================================ FILE: test/field/discretizations/cg2_area_in_2d_grad.morpho ================================================ // Compute the gradient of a quantity import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([2,0]) mb.addvertex([0,2]) mb.addvertex([2,2]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) var l = FiniteElementSpace("CG2", grade=2) fn integrand(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y) 3 - 2*x + 4*y, finiteelementspace=l) print abs(AreaIntegral(integrand, f, method={ }).total(m) - 80) < 1e-8 // expect: true var g = Field(m, fn (x,y) x^2 + y^2, finiteelementspace=l) print abs(AreaIntegral(integrand, g, method={ }).total(m) - 128/3) < 1e-8 // expect: true var h = Field(m, fn (x,y) 3 - 2*x + 4*y) print abs(AreaIntegral(integrand, h, method={ }).total(m) - 80) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_area_in_2d_tensor_grad.morpho ================================================ // Compute the gradient of a tensor import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([2,0]) mb.addvertex([0,2]) mb.addvertex([2,2]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) fn f0(x,y) { return Matrix([[x+y, x^2+y^2], [-(x^2+y^2),x-y]]) } var f = Field(m, f0, finiteelementspace=FiniteElementSpace("CG2", grade=2)) print abs(AreaIntegral(fn (x, q) q[0,0], f, method={ }).total(m) - 8) < 1e-8 // expect: true fn integrand(x, q) { var g = grad(q) var sum=0 for (dg in g) sum+=dg.norm()^2 return sum } print abs(AreaIntegral(integrand, f, method={ }).total(m) - 304/3) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_area_in_3d_grad.morpho ================================================ // Compute the gradient of a quantity import meshtools var mb = MeshBuilder() mb.addvertex([0,0,1]) mb.addvertex([2,0,1]) mb.addvertex([0,2,1]) mb.addvertex([2,2,1]) mb.addface([0,1,2]) mb.addface([1,3,2]) var m = mb.build() m.addgrade(1) var l = FiniteElementSpace("CG2", grade=2) fn integrand(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y,z) 3 - 2*x + 4*y, finiteelementspace=l) print abs(AreaIntegral(integrand, f, method={ }).total(m) - 80) < 1e-8 // expect: true var g = Field(m, fn (x,y,z) x^2 + y^2, finiteelementspace=l) print abs(AreaIntegral(integrand, g, method={ }).total(m) - 128/3) < 1e-8 // expect: true var h = Field(m, fn (x,y,z) 3 - 2*x + 4*y) print abs(AreaIntegral(integrand, h, method={ }).total(m) - 80) < 1e-8 // expect: true var p = Field(m, fn (x,y,z) 3 - 2*x + 4*y) print abs(AreaIntegral(fn (x, q) grad(q)[0], p, method={}).total(m) - (-8)) < 1e-8 // expect: true print abs(AreaIntegral(fn (x, q) grad(q)[1], p, method={}).total(m) - 16) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_line_in_1d.morpho ================================================ // CG2 FunctionSpace in one dimension import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.5) var l = FiniteElementSpace("CG2", grade=1) var f = Field(m, fn (x,y,z) x^2, finiteelementspace=l) print l.layout(f) // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 0 1 ] // expect: [ 1 0 ] // expect: [ 0 1 ] print f.shape() // expect: [ 1, 1 ] print (LineIntegral(fn (x, u) u, f, method={ }).total(m) - 1/3) < 1e-10 // expect: true ================================================ FILE: test/field/discretizations/cg2_line_in_2d_grad.morpho ================================================ // CG2 FunctionSpace in one dimension import meshtools var m = LineMesh(fn (t) [t,t], 0..1:0.5) var l = FiniteElementSpace("CG2", grade=1) var f = Field(m, fn (x,y) x^2+y^2, finiteelementspace=l) fn integrand(x, u) { var g = grad(u) return g.norm()^2 } print abs(LineIntegral(integrand, f, method={ }).total(m) - 8*sqrt(2)/3) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_line_in_3d_integrate.morpho ================================================ import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1) var cg2 = FiniteElementSpace("CG2", grade=1) var f = Field(m, fn (x,y,z) x^2, finiteelementspace=cg2) var ans = 20/21 print (LineIntegral(fn (x) 1-x[0]^20, method={ }).total(m) - ans) < 1e-8 // expect: true print (LineIntegral(fn (x, g) 1-x[0]^20, f, method={ }).total(m) - ans) < 1e-8 // expect: true print (LineIntegral(fn (x, g) 1-g^10, f, method={ }).total(m) - ans) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_vol_in_3d.morpho ================================================ // CG2 FunctionSpace in three dimensions import meshtools var cube = [ [0,0,0], [1,0,0], [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1] ] var pts = [] for (x in cube) pts.append(Matrix(x)) var m = DelaunayMesh(pts) m.addgrade(1) var fe = FiniteElementSpace("CG2", grade=3) var f = Field(m, fn (x,y,z) x*y*z, finiteelementspace=fe) print f.shape() // expect: [ 1, 1, 0, 0 ] print abs(VolumeIntegral(fn (x, q) q, f, method={}).total(m) - 0.125) < 1e-8 // expect: true var g = Field(m, fn (x,y,z) x^2*(y+z), finiteelementspace=fe) print abs(VolumeIntegral(fn (x, q) q, g, method={}).total(m) - 1/3) < 1e-8 // expect: true var h = Field(m, fn (x,y,z) z^3-x^2-y, finiteelementspace=fe) print abs(VolumeIntegral(fn (x, q) q, h, method={}).total(m) - (-7/12)) < 1e-8 // expect: true ================================================ FILE: test/field/discretizations/cg2_vol_in_3d_grad.morpho ================================================ // CG2 FunctionSpace in three dimensions import meshtools var cube = [ [0,0,0], [1,0,0], [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1] ] var pts = [] for (x in cube) pts.append(Matrix(x)) var m = DelaunayMesh(pts) m.addgrade(1) var fe = FiniteElementSpace("CG2", grade=3) fn normsq(x, q) { var g = grad(q) return g.norm()^2 } var f = Field(m, fn (x,y,z) x*y + y*z + z*x, finiteelementspace=fe) print abs(VolumeIntegral(normsq, f, method={}).total(m) - 7/2) < 1e-6 // expect: true f = Field(m, fn (x,y,z) x^2 - y^2 + 2*x*y, finiteelementspace=fe) print abs(VolumeIntegral(normsq, f, method={}).total(m) - 16/3) < 1e-6 // expect: true f = Field(m, fn (x,y,z) x + y + z, finiteelementspace=fe) print abs(VolumeIntegral(normsq, f, method={}).total(m) - 3) < 1e-6 // expect: true ================================================ FILE: test/field/enumerate.morpho ================================================ // Operations on Field elements var m = Mesh("square.mesh") var f = Field(m, Matrix([1,0]), grade = 2) print f.count() // expect: 2 for (v in f) print v // expect: [ 1 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 0 ] var g = f.op(fn (x) x.norm()) print g.count() // expect: 2 for (nrm in g) print nrm // expect: 1 // expect: 1 ================================================ FILE: test/field/grade.morpho ================================================ // Higher grades var m = Mesh("square.mesh") print m // expect: var f = Field(m, grade=2) f[2, 0]=1 f[1]=2 print f[2, 0] // expect: 1 print f[1] // expect: 2 var g = Field(m, Matrix(2,2), grade=2) g[1]=Matrix([[1,2],[3,4]]) print g[1] // expect: [ 1 2 ] // expect: [ 3 4 ] ================================================ FILE: test/field/inherited.morpho ================================================ // Test Matrix methods inherited from Object var m = Mesh() var a = Field(m) print a.respondsto("respondsto") // expect: true print islist(a.respondsto()) // expect: true print a.clss() // expect: @Field print a.superclass() // expect: @Object print islist(a.invoke("respondsto")) // expect: true print a.has("a") // expect: false ================================================ FILE: test/field/inner.morpho ================================================ // Inner product var m = Mesh("square.mesh") print m // expect: var f = Field(m) // Set elements for (i in 0...4) f[i]=i print f.inner(f) // expect: 14 ================================================ FILE: test/field/linearize.morpho ================================================ // Access the underlying linearized store for the Field var m = Mesh("square.mesh") print m // expect: var f = Field(m) var a = f.linearize() var b = f.__linearize() print a // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] for (i in 0...4) a[i] = i print f // expect: // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] for (i in 0...4) b[i] = i print f // expect: // expect: [ 0 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] var f2 = Field(m, Matrix([1,0])) print f2.linearize() // expect: [ 1 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 0 ] ================================================ FILE: test/field/matrix.morpho ================================================ // Matrix field var m = Mesh("square.mesh") print m // expect: var f = Field(m, Matrix(2,2)) print f[0] // expect: [ 0 0 ] // expect: [ 0 0 ] f[1]=f[0]=Matrix([[1,0],[0,1]]) var g = f+f print g[1] // expect: [ 2 0 ] // expect: [ 0 2 ] var h = Field(m, fn (x,y,z) Matrix([[cos(x),0],[0,sin(x)]])) print h[1] // expect: [ 0.540302 0 ] // expect: [ 0 0.841471 ] ================================================ FILE: test/field/methods.morpho ================================================ // Scalar field var m = Mesh("square.mesh") var f = Field(m, Matrix([1,2]), grade = [1, 0, 1]) print f.shape() // expect: [ 1, 0, 1 ] print f.mesh() // expect: ================================================ FILE: test/field/multigrade.morpho ================================================ // Multiple grades var m = Mesh("square.mesh") print m // expect: var f = Field(m, Matrix([1,2]), grade = [1,0,1]) for (i in 0...4) f[0,i]=Matrix([i,-1]) for (i in 0...2) f[2,i]=Matrix([1/2,-2]) print f[0,1] // expect: [ 1 ] // expect: [ -1 ] print f[2,1] // expect: [ 0.5 ] // expect: [ -2 ] ================================================ FILE: test/field/neg_nilMesh.morpho ================================================ Field(nil,Matrix([1,0,0])) //expect error 'FldMshArg' ================================================ FILE: test/field/negate.morpho ================================================ // Negate a field var m = Mesh("square.mesh") print m // expect: var f = Field(m, Matrix([[1,0],[0,1]])) var g = -f print g[0] // expect: [ -1 0 ] // expect: [ 0 -1 ] ================================================ FILE: test/field/op.morpho ================================================ // Operations on Field elements var m = Mesh("square.mesh") print m // expect: var f = Field(m, Matrix([2,0,0]), grade = [1,0,1]) var g = f.op(fn (x) x.norm()) print g.inner(g) // expect: 24 var h = f.op(fn (x,y) x.inner(y), f) print h.inner(h) // expect: 96 ================================================ FILE: test/field/op_in_loop.morpho ================================================ // Operations on Field elements in a loop import meshtools var m = AreaMesh(fn(u,v) [u,v,0], -1..1:0.1, -1..1:0.1) var f = Field(m, Matrix([1,0,0])) var d_n for (i in 1..1000) { d_n = f.op(fn (x) x.inner(Matrix([1/2, sqrt(3)/2, 0]))) } print "ok" // expect: ok ================================================ FILE: test/field/scalar.morpho ================================================ // Scalar field var m = Mesh("square.mesh") print m // expect: // Create a convenience function for output fn write(f) { print "[${f[0]}, ${f[1]}, ${f[2]}, ${f[3]}]" } var f = Field(m) var g = Field(m) write(f) // expect: [0, 0, 0, 0] print f[0] // expect: 0 // Set elements for (i in 0...4) f[i]=i for (i in 0...4) g[i]=i/2 write(f) // expect: [0, 1, 2, 3] // Arithmetic operations write(f + g) // expect: [0, 1.5, 3, 4.5] write(f - g) // expect: [0, 0.5, 1, 1.5] // Clone var h = f.clone() // Accumulate f.acc(-1, f) write(f) // expect: [0, 0, 0, 0] write(h) // expect: [0, 1, 2, 3] // Construct by mapping a function over the vertices var q = Field(m, fn (x,y,z) x ) write(q) // expect: [0, 1, 0, 1] // f.op(fn (a,b) a.inner(b), g) // f.op(fn (a) a.norm()) import meshtools var sm = LineMesh(fn (t) [t,0,0], 0..1:0.2) var sf = Field(sm, 2.0) print sf // expect: // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] sf = Field(sm, 2) print sf // expect: // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 2 ] ================================================ FILE: test/field/scalarmul.morpho ================================================ // Scalar multiplication of a field var m = Mesh("square.mesh") print m // expect: var f = Field(m) var g = Field(m) // Create a convenient function for output fn write(f) { print "[${f[0]}, ${f[1]}, ${f[2]}, ${f[3]}]" } // Set elements for (i in 0...4) f[i]=i write(f*2) // expect: [0, 2, 4, 6] write(2*f) // expect: [0, 2, 4, 6] write(f/2) // expect: [0, 0.5, 1, 1.5] f*=2 write(f) // expect: [0, 2, 4, 6] ================================================ FILE: test/field/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/file/file.morpho ================================================ /* * File class */ // Read in characters var f = File("test.txt") for (var i=0; i<5; i=i+1) print f.readchar() f.close() // expect: L // expect: o // expect: r // expect: e // expect: m // Verify we read nil after the file is closed for (var i=0; i<2; i=i+1) print f.readchar() // expect: nil // expect: nil f=File("test.txt") var p=f.lines() f.close() print p[3] // expect: Curabitur gravida neque in neque sodales molestie. print "---" // expect: --- // Read lines in individually var g = File("test.txt") while (!g.eof()) { var line = g.readline(); if (line) print line; } g.close() // expect: Lorem ipsum dolor sit amet, consectetur adipiscing elit. // expect: Phasellus sit amet ligula in ante porta mattis. // expect: Duis fringilla dui nec tristique rhoncus. // expect: Curabitur gravida neque in neque sodales molestie. // expect: Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. // expect: Ut tristique leo quis ullamcorper ornare. // expect: Integer cursus nunc nibh, eu interdum nisl mattis quis. // expect: Phasellus in lectus id dolor rhoncus cursus. // expect: Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. // expect: Curabitur venenatis sem sed velit varius tristique. ================================================ FILE: test/file/file_lines.morpho ================================================ /* * File class */ var f=File("test.txt") var p=f.lines() f.close() for (var i=0; i<12; i=i+1) print p[i] // expect: Lorem ipsum dolor sit amet, consectetur adipiscing elit. // expect: Phasellus sit amet ligula in ante porta mattis. // expect: Duis fringilla dui nec tristique rhoncus. // expect: Curabitur gravida neque in neque sodales molestie. // expect: Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. // expect: Ut tristique leo quis ullamcorper ornare. // expect: Integer cursus nunc nibh, eu interdum nisl mattis quis. // expect: Phasellus in lectus id dolor rhoncus cursus. // expect: Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. // expect: Curabitur venenatis sem sed velit varius tristique. // expect error 'IndxBnds' ================================================ FILE: test/file/file_not_found.morpho ================================================ /* * File class */ // Read in characters var f = File("randomcrazyfile.txt") // expect error: 'FlOpnFld' ================================================ FILE: test/file/file_readall.morpho ================================================ // Test the readall method, that reads the whole file into a string. var f=File("test.txt") var p=f.readall() f.close() print isstring(p) // expect: true var words = [] for (word in p.split(" \n")) if (word.count()>0) words.append(word) print words.count() // expect: 77 var f2=File("string.txt") var p2=f2.readall() f2.close() print p2[p2.count()-1] // expect: ] ================================================ FILE: test/file/file_write.morpho ================================================ /* * File class */ var file = "testout.txt" var f = File(file, "w") f.close() // Write some lines f = File(file, "w") for (var i=0; i<3; i=i+1) f.write("I have ${i} apples.") f.close() // Now read them back var g = File(file, "r") while (!g.eof()) { var line = g.readline(); if (line) print line; } // expect: I have 0 apples. // expect: I have 1 apples. // expect: I have 2 apples. g.close() f = File(file, "append") for (var i=3; i<6; i=i+1) f.write("I have ${i} apples.") f.close() g = File(file) while (!g.eof()) { var line = g.readline(); if (line) print line; } // expect: I have 0 apples. // expect: I have 1 apples. // expect: I have 2 apples. // expect: I have 3 apples. // expect: I have 4 apples. // expect: I have 5 apples. ================================================ FILE: test/file/filename_missing.morpho ================================================ /* * File class */ // Read in characters var f = File() // expect error: 'FlNmMssng' ================================================ FILE: test/file/filename_not_string.morpho ================================================ /* * File class */ // Read in characters var f = File(1) // expect error: 'FlNmArgs' ================================================ FILE: test/file/folder/file1.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sit amet ligula in ante porta mattis. Duis fringilla dui nec tristique rhoncus. Curabitur gravida neque in neque sodales molestie. Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. Ut tristique leo quis ullamcorper ornare. Integer cursus nunc nibh, eu interdum nisl mattis quis. Phasellus in lectus id dolor rhoncus cursus. Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. Curabitur venenatis sem sed velit varius tristique. ================================================ FILE: test/file/folder/file2.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sit amet ligula in ante porta mattis. Duis fringilla dui nec tristique rhoncus. Curabitur gravida neque in neque sodales molestie. Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. Ut tristique leo quis ullamcorper ornare. Integer cursus nunc nibh, eu interdum nisl mattis quis. Phasellus in lectus id dolor rhoncus cursus. Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. Curabitur venenatis sem sed velit varius tristique. ================================================ FILE: test/file/folder/file3.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sit amet ligula in ante porta mattis. Duis fringilla dui nec tristique rhoncus. Curabitur gravida neque in neque sodales molestie. Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. Ut tristique leo quis ullamcorper ornare. Integer cursus nunc nibh, eu interdum nisl mattis quis. Phasellus in lectus id dolor rhoncus cursus. Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. Curabitur venenatis sem sed velit varius tristique. ================================================ FILE: test/file/folder.morpho ================================================ /* * Folder class */ // Check if a resource is a folder print Folder.isfolder("folder") // expect: true // Find the contents of a folder var files = Folder.contents("folder") print files.count() // expect: 3 ================================================ FILE: test/file/remote.morpho ================================================ var f = File("test.txt", "read") print f.filename() // expect: test.txt var rp = f.relativepath() print rp // expect: file/test.txt print "${rp} etc" // expect: file/test.txt etc f.close() ================================================ FILE: test/file/string.txt ================================================ [ abcedfghijklmnopqrstuvwxyz ] ================================================ FILE: test/file/test.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sit amet ligula in ante porta mattis. Duis fringilla dui nec tristique rhoncus. Curabitur gravida neque in neque sodales molestie. Fusce ut velit pellentesque lectus porttitor malesuada a pellentesque libero. Ut tristique leo quis ullamcorper ornare. Integer cursus nunc nibh, eu interdum nisl mattis quis. Phasellus in lectus id dolor rhoncus cursus. Maecenas dapibus tincidunt turpis, nec fermentum augue ultrices sed. Curabitur venenatis sem sed velit varius tristique. ================================================ FILE: test/file/testout.txt ================================================ I have 0 apples. I have 1 apples. I have 2 apples. I have 3 apples. I have 4 apples. I have 5 apples. ================================================ FILE: test/for/class_in_body.morpho ================================================ // Body must be a statement for (;;) class Foo {} // expect error 'ExpExpr' ================================================ FILE: test/for/closure_in_body.morpho ================================================ var f1 var f2 var f3 for (var i = 1; i < 4; i+=1) { var j = i fn f() { print i print j } if (j == 1) f1 = f else if (j == 2) f2 = f else f3 = f } f1() // expect: 4 // expect: 1 f2() // expect: 4 // expect: 2 f3() // expect: 4 // expect: 3 ================================================ FILE: test/for/fn_in_body.morpho ================================================ // Body must be a statement for (;;) fn foo() {} // expect error 'ExpExpr' ================================================ FILE: test/for/return_closure.morpho ================================================ // Return a closure from within a loop fn f() { for (;;) { var i = "i" fn g() { print i } return g } } var h = f() h() // expect: i ================================================ FILE: test/for/return_inside.morpho ================================================ // Return a value inside a loop fn f() { for (;;) { var i = "i" return i } } print f() // expect: i ================================================ FILE: test/for/scope.morpho ================================================ // Check scoping rules { var i = "before" // New variable is in inner scope. for (var i = 0; i < 1; i+=1) { print i // expect: 0 // Loop body is in second inner scope. var i = -1 print i // expect: -1 } print i // expect: before } { // New variable shadows outer variable. for (var i = 0; i > 0; i+=1) {} // Goes out of scope after loop. var i = "after" print i // expect: after // Can reuse an existing variable. for (i = 0; i < 1; i+=1) { print i // expect: 0 } print i // expect: 1 } ================================================ FILE: test/for/statement_condition.morpho ================================================ // Loop condition can't be a statement for (var a = 1; var b = 2; a+=1) {} // expect error 'ExpExpr' ================================================ FILE: test/for/statement_increment.morpho ================================================ // Increment can't be a statement for (var a = 1; a < 2; print "a") {} // expect error 'ExpExpr' ================================================ FILE: test/for/statement_initializer.morpho ================================================ // Initializer must be a statement for (while (true); a < 2; a = a + 1) {} // expect error 'ExpExpr' ================================================ FILE: test/for/syntax.morpho ================================================ // Single-expression body. for (var c = 0; c < 3;) print c+=1 // expect: 1 // expect: 2 // expect: 3 // Block body. for (var a = 0; a < 3; a+=1) { print a } // expect: 0 // expect: 1 // expect: 2 // No clauses. fn foo() { for (;;) return "done" } print foo() // expect: done // No variable. var i = 0 for (; i < 2; i = i + 1) print i // expect: 0 // expect: 1 // No condition. fn bar() { for (var i = 0;; i = i + 1) { print i if (i >= 2) return } } bar() // expect: 0 // expect: 1 // expect: 2 // No increment. for (var i = 0; i < 2;) { print i i+=1 } // expect: 0 // expect: 1 // Statement bodies. for (; false;) if (true) 1; else 2 for (; false;) while (true) 1 for (; false;) for (;;) 1 ================================================ FILE: test/for/var_in_body.morpho ================================================ // Body must be a statement for (;;) var foo; // expect error 'ExpExpr' ================================================ FILE: test/for_in/forin.morpho ================================================ // For in loops var a = Matrix([1,2,3,4]) for (var x in a) { print x } // expect: 1 // expect: 2 // expect: 3 // expect: 4 for (var x in a) print 2*x // expect: 2 // expect: 4 // expect: 6 // expect: 8 ================================================ FILE: test/for_in/forin_custom.morpho ================================================ // For in loops with a custom collection class Collection { init (n) { self.count=n } enumerate(k) { if (k>=0) return (k+1)^2 else return self.count } } var a=Collection(4) for (var x in a) { print x } // expect: 1 // expect: 4 // expect: 9 // expect: 16 ================================================ FILE: test/for_in/forin_in_function.morpho ================================================ // For in loops fn f(u) { for (var x in u) print x } var a = Matrix([1,2,3,4]) f(a) // expect: 1 // expect: 2 // expect: 3 // expect: 4 ================================================ FILE: test/for_in/forin_index.morpho ================================================ // For in loops with index parameter var a = Matrix([1,2,3,4]) for (var x, i in a) { print "${i}: ${x}" } // expect: 0: 1 // expect: 1: 2 // expect: 2: 3 // expect: 3: 4 // Optional var for (x, i in a) { print "${i}: ${x}" } // expect: 0: 1 // expect: 1: 2 // expect: 2: 3 // expect: 3: 4 ================================================ FILE: test/for_in/forin_string.morpho ================================================ // For in loops with strings var a = "Hello" for (var x in a) { print x } // expect: H // expect: e // expect: l // expect: l // expect: o ================================================ FILE: test/function/anonymous.morpho ================================================ // Anonymous functions print apply(fn (x) x^2, 0.5) // expect: 0.25 var b = fn (x) 1-x^2 print b(0.5) // expect: 0.75 ================================================ FILE: test/function/anonymous_closure.morpho ================================================ // Anonymous closure var a = 0.5 print apply(fn (x) x+a, 0.2) // expect: 0.7 fn f(x) { return fn (y) x+y } print f(0.4) // expect: <> print f(0.4)(0.4) // expect: 0.8 ================================================ FILE: test/function/anonymous_closure_assign.morpho ================================================ // Anonymous closure with assignment var a = 0.5 var f = fn (x) a+=x print a // expect: 0.5 f(0.5) print a // expect: 1 f(0.5) print a // expect: 1.5 ================================================ FILE: test/function/anonymous_closure_noparams.morpho ================================================ // Anonymous closure var a = 0.5 print apply(fn () a, []) // expect: 0.5 fn f(x) { return fn () x } print f(0.4) // expect: <> print f(0.4)() // expect: 0.4 for (i in 1..5) { print apply(fn () i, []) } // expect: 1 // expect: 2 // expect: 3 // expect: 4 // expect: 5 ================================================ FILE: test/function/anonymous_in_args.morpho ================================================ // Anonymous fn in the arguments of a function fn f(g, x) { print g(x) } f(fn (x) x, "Foo") // expect: Foo f(fn (x) "Boo", "Foo") // expect: Boo f(fn (x) { return x[0] }, "Foo") // expect: F ================================================ FILE: test/function/anonymous_in_optional_args.morpho ================================================ // Anonymous fn in the optional arguments of a function fn f(x, g=nil) { print g(x) } f("Foo", g=fn (x) x) // expect: Foo f("Foo", g=fn (x) "Boo") // expect: Boo f("Foo", g=fn (x) { return x[0] }) // expect: F ================================================ FILE: test/function/body_must_be_block.morpho ================================================ fn f() 123 // expect error 'FnMssngLftBrc' ================================================ FILE: test/function/constant_arg_optional.morpho ================================================ // Ensure regular args aren't checked for interned literals fn f(name, size, id=nil) { print name } f("Helvetica", 1) // expect: Helvetica ================================================ FILE: test/function/empty_body.morpho ================================================ fn f() {} print f() // expect: nil ================================================ FILE: test/function/extra_arguments.morpho ================================================ fn f(a, b) { print a print b } f(1, 2, 3, 4) // expect error 'InvldArgs' ================================================ FILE: test/function/index_in_arguments.morpho ================================================ // Check that you can call a function with index access in arguments fn f(a,b) { return "${a}, ${b}" } var a = [ 1, 2 ] print f(a[0],a[1]) // expect: 1, 2 ================================================ FILE: test/function/local_recursion.morpho ================================================ { fn fib(n) { if (n < 2) return n return fib(n - 1) + fib(n - 2) } print fib(8) // expect: 21 } ================================================ FILE: test/function/missing_arguments.morpho ================================================ fn f(a, b) {} f(1) // expect error 'InvldArgs' ================================================ FILE: test/function/missing_comma_in_parameters.morpho ================================================ fn foo(a, b c, d, e, f) {} // expect error 'UnknwnType' ================================================ FILE: test/function/mutual_recursion/duplicate_local_mutual_recursion.morpho ================================================ // Two forward references to the same function { fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } fn isEvenEven(n) { if (n == 0) return true return isOdd(n - 1) } fn isOdd(n) { if (n == 0) return false return isEven(n - 1) } print isEvenEven(4) // expect: true } ================================================ FILE: test/function/mutual_recursion/local_mutual_recursion.morpho ================================================ { fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } fn isOdd(n) { if (n == 0) return false return isEven(n - 1) } print isEven(4) // expect: true } ================================================ FILE: test/function/mutual_recursion/local_mutual_recursion_out_of_scope.morpho ================================================ { fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } } fn isOdd(n) { if (n == 0) return false return isEven(n - 1) } isEven(4); // expect error 'UnrslvdFrwdRf' ================================================ FILE: test/function/mutual_recursion/mutual_recursion.morpho ================================================ fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } fn isOdd(n) { if (n == 0) return false return isEven(n - 1) } print isEven(4) // expect: true print isOdd(3) // expect: true ================================================ FILE: test/function/mutual_recursion/mutual_recursion_in_closure.morpho ================================================ fn g() { fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } fn isOdd(n) { if (n == 0) return false return isEven(n - 1) } return isEven } var isEv=g() print isEv(3) // expect: false print isEv(4) // expect: true ================================================ FILE: test/function/mutual_recursion/undefined_call_in_global.morpho ================================================ // Try to call an undefined function in global scope print isOdd(4) // expect error 'SymblUndf' ================================================ FILE: test/function/mutual_recursion/unresolved_forward_ref_in_func.morpho ================================================ // Forward references must be resolved in the same scope fn f() { return fn (u) func(u) } fn func(u) { return u } var x = f() print x(1) // expect error 'UnrslvdFrwdRf' ================================================ FILE: test/function/mutual_recursion/unresolved_forward_reference.morpho ================================================ // A forward reference that is never supplied fn isEven(n) { if (n == 0) return true return isOdd(n - 1) } // expect error 'UnrslvdFrwdRf' ================================================ FILE: test/function/optional.morpho ================================================ // Optional arguments fn func(x, y=true, z=2) { print x print y print z } func(1) // expect: 1 // expect: true // expect: 2 func(1, y=false) // expect: 1 // expect: false // expect: 2 func(1, z=3) // expect: 1 // expect: true // expect: 3 func(1, z=3, y=false) // expect: 1 // expect: false // expect: 3 func(1, 2, y=false) // expect error 'InvldArgs' ================================================ FILE: test/function/optional_invalid.morpho ================================================ // Function that doesn't support optional arguments called with them fn f(x,y,z,a) { return -1 } print f(1,z=3) // expect error 'InvldArgs' ================================================ FILE: test/function/optional_non_constant_default.morpho ================================================ // Optional arguments fn func(x, y=2*5) { } // expect error 'OptPrmDflt' ================================================ FILE: test/function/optional_too_few_fixed.morpho ================================================ // Optional arguments fn func(x, y=true, z=2) { print x print y print z } func(y=false) // expect error 'InvldArgs' ================================================ FILE: test/function/parameters.morpho ================================================ fn f0() { return 0; } print f0() // expect: 0 fn f1(a) { return a } print f1(1) // expect: 1 fn f2(a, b) { return a + b } print f2(1, 2) // expect: 3 fn f3(a, b, c) { return a + b + c } print f3(1, 2, 3) // expect: 6 fn f4(a, b, c, d) { return a + b + c + d } print f4(1, 2, 3, 4) // expect: 10 fn f5(a, b, c, d, e) { return a + b + c + d + e } print f5(1, 2, 3, 4, 5) // expect: 15 fn f6(a, b, c, d, e, f) { return a + b + c + d + e + f } print f6(1, 2, 3, 4, 5, 6) // expect: 21 fn f7(a, b, c, d, e, f, g) { return a + b + c + d + e + f + g } print f7(1, 2, 3, 4, 5, 6, 7) // expect: 28 fn f8(a, b, c, d, e, f, g, h) { return a + b + c + d + e + f + g + h } print f8(1, 2, 3, 4, 5, 6, 7, 8) // expect: 36 ================================================ FILE: test/function/print.morpho ================================================ // Print functions fn foo() {} print foo // expect: print clock // expect: ================================================ FILE: test/function/recursion.morpho ================================================ fn fib(n) { if (n < 2) return n return fib(n - 1) + fib(n - 2) } print fib(8) // expect: 21 ================================================ FILE: test/function/stack_overflow.morpho ================================================ // Cause stack overflow with a recursive function fn f(n) { return f(n + 1) } print f(1) // expect error 'StckOvflw' ================================================ FILE: test/function/too_many_arguments.morpho ================================================ fn foo() {} { var a = 1 foo( a, // 1 a, // 2 a, // 3 a, // 4 a, // 5 a, // 6 a, // 7 a, // 8 a, // 9 a, // 10 a, // 11 a, // 12 a, // 13 a, // 14 a, // 15 a, // 16 a, // 17 a, // 18 a, // 19 a, // 20 a, // 21 a, // 22 a, // 23 a, // 24 a, // 25 a, // 26 a, // 27 a, // 28 a, // 29 a, // 30 a, // 31 a, // 32 a, // 33 a, // 34 a, // 35 a, // 36 a, // 37 a, // 38 a, // 39 a, // 40 a, // 41 a, // 42 a, // 43 a, // 44 a, // 45 a, // 46 a, // 47 a, // 48 a, // 49 a, // 50 a, // 51 a, // 52 a, // 53 a, // 54 a, // 55 a, // 56 a, // 57 a, // 58 a, // 59 a, // 60 a, // 61 a, // 62 a, // 63 a, // 64 a, // 65 a, // 66 a, // 67 a, // 68 a, // 69 a, // 70 a, // 71 a, // 72 a, // 73 a, // 74 a, // 75 a, // 76 a, // 77 a, // 78 a, // 79 a, // 80 a, // 81 a, // 82 a, // 83 a, // 84 a, // 85 a, // 86 a, // 87 a, // 88 a, // 89 a, // 90 a, // 91 a, // 92 a, // 93 a, // 94 a, // 95 a, // 96 a, // 97 a, // 98 a, // 99 a, // 100 a, // 101 a, // 102 a, // 103 a, // 104 a, // 105 a, // 106 a, // 107 a, // 108 a, // 109 a, // 110 a, // 111 a, // 112 a, // 113 a, // 114 a, // 115 a, // 116 a, // 117 a, // 118 a, // 119 a, // 120 a, // 121 a, // 122 a, // 123 a, // 124 a, // 125 a, // 126 a, // 127 a, // 128 a, // 129 a, // 130 a, // 131 a, // 132 a, // 133 a, // 134 a, // 135 a, // 136 a, // 137 a, // 138 a, // 139 a, // 140 a, // 141 a, // 142 a, // 143 a, // 144 a, // 145 a, // 146 a, // 147 a, // 148 a, // 149 a, // 150 a, // 151 a, // 152 a, // 153 a, // 154 a, // 155 a, // 156 a, // 157 a, // 158 a, // 159 a, // 160 a, // 161 a, // 162 a, // 163 a, // 164 a, // 165 a, // 166 a, // 167 a, // 168 a, // 169 a, // 170 a, // 171 a, // 172 a, // 173 a, // 174 a, // 175 a, // 176 a, // 177 a, // 178 a, // 179 a, // 180 a, // 181 a, // 182 a, // 183 a, // 184 a, // 185 a, // 186 a, // 187 a, // 188 a, // 189 a, // 190 a, // 191 a, // 192 a, // 193 a, // 194 a, // 195 a, // 196 a, // 197 a, // 198 a, // 199 a, // 200 a, // 201 a, // 202 a, // 203 a, // 204 a, // 205 a, // 206 a, // 207 a, // 208 a, // 209 a, // 210 a, // 211 a, // 212 a, // 213 a, // 214 a, // 215 a, // 216 a, // 217 a, // 218 a, // 219 a, // 220 a, // 221 a, // 222 a, // 223 a, // 224 a, // 225 a, // 226 a, // 227 a, // 228 a, // 229 a, // 230 a, // 231 a, // 232 a, // 233 a, // 234 a, // 235 a, // 236 a, // 237 a, // 238 a, // 239 a, // 240 a, // 241 a, // 242 a, // 243 a, // 244 a, // 245 a, // 246 a, // 247 a, // 248 a, // 249 a, // 250 a, // 251 a, // 252 a, // 253 a, // 254 a, // 255 a) // expect error 'TooMnyArg' } ================================================ FILE: test/function/too_many_parameters.morpho ================================================ // 256 parameters. fn f( a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255, a) {} // expect error 'TooMnyPrm' ================================================ FILE: test/function/unknown_optional.morpho ================================================ // Unknown optional argument fn func(x, foo=nil) { return foo } func(1, boo="hoo") // expect error 'UnkwnOptArg' ================================================ FILE: test/function/variadic.morpho ================================================ // Variadic arguments fn func(x, y, ...varg) { for (a in varg) print a } func(1, 2) func(1, 2, 3) // expect: 3 func(1, 2, 3, 4) // expect: 3 // expect: 4 func(1, 2, 3, 4, 5) // expect: 3 // expect: 4 // expect: 5 func(1) // expect error 'InvldArgs' ================================================ FILE: test/function/variadic_append.morpho ================================================ // Can append to variadic argument list fn func(x, y, ...varg) { for (a in varg) print a for (i in 1..3) varg.append(i) print varg } func(1, 2, 3) // expect: 3 // expect: [ 3, 1, 2, 3 ] ================================================ FILE: test/function/variadic_apply.morpho ================================================ // Variadic args with apply fn f(...x) { for (i in x) print i } f(1,2,3) // expect: 1 // expect: 2 // expect: 3 apply(f, [1,2,3]) // expect: 1 // expect: 2 // expect: 3 ================================================ FILE: test/function/variadic_in_method.morpho ================================================ // Variadic args in a method class Foo { foo(...u) { print u } boo() { var a = [1,2,3] apply(self.foo, a) } } var a = Foo() a.foo(1,2,3) // expect: [ 1, 2, 3 ] a.boo() // expect: [ 1, 2, 3 ] apply(a.foo, [1,2,3]) // expect: [ 1, 2, 3 ] ================================================ FILE: test/function/variadic_last.morpho ================================================ // Variadic parameter must be last fn bad(...varg, x, y) { } // expect Error: 'VarPrLst' ================================================ FILE: test/function/variadic_only.morpho ================================================ // Variadic args fn func(...a) { for (x in a) print x } func(1) // expect: 1 func(1, 2) // expect: 1 // expect: 2 func(1, 2, 3) // expect: 1 // expect: 2 // expect: 3 ================================================ FILE: test/function/variadic_optional.morpho ================================================ // Variadic arguments fn func(x, y, ...varg, q=0) { print "q=${q}" for (a in varg) print a } func(1, 2) // expect: q=0 func(1, 2, 3, 4) // expect: q=0 // expect: 3 // expect: 4 func(1, 2, 3, 4, 5, q="Hi") // expect: q=Hi // expect: 3 // expect: 4 // expect: 5 func(1, q=4) // expect error 'InvldArgs' ================================================ FILE: test/function/variadic_too_many.morpho ================================================ // Can only have one variadic parameter fn bad(x, y, ...varg, ...warg) { } // expect Error: 'OneVarPr' ================================================ FILE: test/function/veneer.morpho ================================================ // Test veneer class fn func(x) { return x } print func // expect: print func.clss() // expect: @Function print func.clone() // expect error 'ObjCantClone' ================================================ FILE: test/functionals/area/area.morpho ================================================ import "../numericalderivatives.morpho" var m = Mesh("triangle.mesh") var a = Area() print m // expect: print (a.integrand(m) - Matrix([ 1.29904 ])).norm() < 1e-5 // expect: true print (a.gradient(m)-numericalgradient(a, m)).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/area/area2.morpho ================================================ import "../numericalderivatives.morpho" var m = Mesh("square.mesh") var a = Area() print m // expect: print (a.total(m) - 1) < 1e-5 // expect: true print (a.integrand(m) - Matrix([[ 0.5, 0.5 ]]) ).norm() < 1e-5 // expect: true print (a.gradient(m)-numericalgradient(a, m)).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/area/area2d.morpho ================================================ import meshtools import "../numericalderivatives.morpho" var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() var a = Area() print m // expect: print (a.integrand(m) - Matrix([ 0.5 ])).norm() < 1e-5 // expect: true print (a.gradient(m) - numericalgradient(a, m)).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/area/area_hessian.morpho ================================================ import meshtools import "../numericalderivatives.morpho" var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() var a = Area() var h = a.hessian(m) var h2 = numericalhessian(a, m) print (Matrix(h) - h2).norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/area/area_integrandForElement.morpho ================================================ import meshtools var m = Mesh("square.mesh") var a = Area() print a.integrandForElement(m, 1) // expect: 0.5 ================================================ FILE: test/functionals/area/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/area/triangle.mesh ================================================ vertices 1 1 0 0 2 -0.5 0.866025 0 3 -0.5 -0.866025 0 faces 1 1 2 3 ================================================ FILE: test/functionals/areaenclosed/areaenclosed.morpho ================================================ import meshtools var np = 40 var m=LineMesh(fn (t) [cos(t), sin(t), 0], 0...2*Pi:2*Pi/np, closed=true) var a = AreaEnclosed() print m // expect: print a.integrand(m) // expect: [ 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 0.0782172 ] print a.total(m) // expect: 3.12869 var grad = a.gradient(m) var value = Matrix([[ 0.156434, 0.154508, 0.148778, 0.139384, 0.126558, 0.110616, 0.0919499, 0.0710198, 0.0483409, 0.0244717, 0, -0.0244717, -0.0483409, -0.0710198, -0.0919499, -0.110616, -0.126558, -0.139384, -0.148778, -0.154508, -0.156434, -0.154508, -0.148778, -0.139384, -0.126558, -0.110616, -0.0919499, -0.0710198, -0.0483409, -0.0244717, -1.03037e-16, 0.0244717, 0.0483409, 0.0710198, 0.0919499, 0.110616, 0.126558, 0.139384, 0.148778, 0.154508 ],[ -1.21667e-16, 0.0244717, 0.0483409, 0.0710198, 0.0919499, 0.110616, 0.126558, 0.139384, 0.148778, 0.154508, 0.156434, 0.154508, 0.148778, 0.139384, 0.126558, 0.110616, 0.0919499, 0.0710198, 0.0483409, 0.0244717, 1.03037e-16, -0.0244717, -0.0483409, -0.0710198, -0.0919499, -0.110616, -0.126558, -0.139384, -0.148778, -0.154508, -0.156434, -0.154508, -0.148778, -0.139384, -0.126558, -0.110616, -0.0919499, -0.0710198, -0.0483409, -0.0244717 ],[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]]) var diff = grad-value print diff.norm()<5e-6 // expect: true ================================================ FILE: test/functionals/areaenclosed/areaenclosed_hessian.morpho ================================================ import meshtools var m = LineMesh(fn (t) [cos(t), sin(t)], -Pi...Pi:Pi/2, closed=true) var a = AreaEnclosed() print a.total(m) // expect: 2 print a.hessian(m) // expect: [ 0 0 0 0.5 0 0 0 -0.5 ] // expect: [ 0 0 -0.5 0 0 0 0.5 0 ] // expect: [ 0 -0.5 0 0 0 0.5 0 0 ] // expect: [ 0.5 0 0 0 -0.5 0 0 0 ] // expect: [ 0 0 0 -0.5 0 0 0 0.5 ] // expect: [ 0 0 0.5 0 0 0 -0.5 0 ] // expect: [ 0 0.5 0 0 0 -0.5 0 0 ] // expect: [ -0.5 0 0 0 0.5 0 0 0 ] ================================================ FILE: test/functionals/areaenclosed/areaenclosed_integrandForElement.morpho ================================================ import meshtools var m = LineMesh(fn(t) [cos(t), sin(t), 0], 0..3*Pi/2:Pi/2, closed=true) m.addgrade(1) var a = AreaEnclosed() print a.integrandForElement(m, 1) // expect: 0.5 ================================================ FILE: test/functionals/areaintegral/areaintegral.morpho ================================================ import constants import meshtools var m = AreaMesh(fn (x,y) [x,y,0], 0..1:0.5, 0..1:0.5) var f = Field(m, fn (x,y,z) Matrix([x,y,z])) // A line integral with only spatial dependence print AreaIntegral(fn (x) x[0]*x[1]).total(m) // expect: 0.25 print AreaIntegral(fn (x) x[0]^2*(1-x[1]^2)).total(m) // expect: 0.222222 print AreaIntegral(fn (x) (x[0]-1/2)^4*(x[1]-1/2)^4).total(m) // expect: 0.00015625 print AreaIntegral(fn (x) x[0]^6*x[1]^6).total(m) // expect: 0.0204082 print AreaIntegral(fn (x, n) n[0]*n[1], f).total(m) // expect: 0.25 print AreaIntegral(fn (x, n) n[0]^2*(1-n[1]^2), f).total(m) // expect: 0.222222 ================================================ FILE: test/functionals/areaintegral/cg2fieldgradient.morpho ================================================ // Calculate field gradient for a field with higher grades import meshtools import "../numericalderivatives.morpho" var mb=MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() m.addgrade(1) var f = Field(m, fn (x,y) Matrix([x^2,y^2]), finiteelementspace=FiniteElementSpace("CG2", grade=2)) fn elasticity(x, f) { var g = grad(f) return g[0].inner(g[0])+g[1].inner(g[1]) } var lel = AreaIntegral(elasticity, f, method={}) print (lel.fieldgradient(m, f) - numericalfieldgradient(lel, m, f)).__linearize().norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/areaintegral/cgtensor.morpho ================================================ // Cauchy-Green tensor in integrals import constants import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() var mref = m.clone() m.setvertexmatrix(2*m.vertexmatrix()) fn integrand(x) { var cg = cgtensor() return cg.trace() } var a = AreaIntegral(integrand, reference=mref) print a.total(m) // expect: 6 var b = AreaIntegral(integrand, reference=mref, weightByReference=true) print b.total(m) // expect: 1.5 // Ensure equivalence of LinearElasticity and AreaIntegral formulations var nu = 0.3 var mu = 1/2/(1+nu) var lambda = nu/(1+nu)/(1-2*nu) fn elasticity(x) { var cg = cgtensor() var trCG=cg.trace() var trCGCG = (cg * cg).trace() return mu*trCGCG + lambda*trCG^2/2 } print (LinearElasticity(mref).total(m) - AreaIntegral(elasticity, reference=mref, weightByReference=true).total(m)) < 1e-8 // expect: true ================================================ FILE: test/functionals/areaintegral/grad.morpho ================================================ import constants import meshtools var m = AreaMesh(fn (x,y) [x,y,0], 0..1:0.5, 0..1:0.5) var f = Field(m, fn (x,y,z) 2*x) print AreaIntegral(fn (x, f) grad(f).norm(), f).total(m) // expect: 2 ================================================ FILE: test/functionals/areaintegral/grad2.morpho ================================================ import constants import meshtools var m = AreaMesh(fn (x,y) [x,y,0], 0..1:0.5, 0..1:0.5) var f = Field(m, fn (x,y,z) x) var g = Field(m, fn (x,y,z) 2*y) var g2 = Field(m, fn (x,y,z) 2*y+0.001*x) var h = Field(m, fn (x,y,z) x+4*y) fn integrand(x, n) { var g = grad(n) return g.inner(g) } print AreaIntegral(integrand, f).total(m) // expect: 1 print AreaIntegral(fn (x, fl, gl) grad(g).norm(), f, g).total(m) // expect: 2 print AreaIntegral(fn (x, fl, gl) grad(f).inner(grad(g2)), f, g2).total(m) // expect: 0.001 print AreaIntegral(fn (x, g, h) grad(g2).inner(grad(h)), g2, h).total(m) // expect: 8.001 print AreaIntegral(fn (x, fl, gl) grad(fl).inner(grad(g)), f, g).total(m) // expect error 'IntgrlAmbgsFld' ================================================ FILE: test/functionals/areaintegral/gradvector.morpho ================================================ import constants import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() var f = Field(m, fn (x,y) Matrix([x,2*y])) var r = [ Matrix([[1],[0]]), Matrix([[0],[2]]) ] var out = true fn integrand(x, n) { var g = grad(n) for (gg, k in g) if ((gg-r[k]).norm()>1e-4) out = false return 0 } print AreaIntegral(integrand, f).total(m) // expect: 0 print out // expect: true ================================================ FILE: test/functionals/areaintegral/normal.morpho ================================================ import constants import meshtools var m = AreaMesh(fn (x,y) [x,y,0], 0..1:0.5, 0..1:0.5) var f = Field(m, fn (x,y,z) Matrix([0,0,1])) print AreaIntegral(fn (x, n) n.inner(normal())^2 , f).total(m) // expect: 1 ================================================ FILE: test/functionals/cube.mesh ================================================ vertices 1 -0.5 -0.5 -0.5 2 0.5 -0.5 -0.5 3 -0.5 0.5 -0.5 4 0.5 0.5 -0.5 5 -0.5 -0.5 0.5 6 0.5 -0.5 0.5 7 -0.5 0.5 0.5 8 0.5 0.5 0.5 9 0 0 -0.5 10 0 0 0.5 11 0 -0.5 0 12 0 0.5 0 13 -0.5 0 0 14 0.5 0 0 edges 1 1 2 2 3 4 3 1 3 4 2 4 5 5 6 6 7 8 7 5 7 8 6 8 9 1 5 10 2 6 11 3 7 12 4 8 13 9 1 14 9 2 15 9 4 16 9 3 17 10 5 18 10 6 19 10 8 20 10 7 21 11 1 22 11 2 23 11 6 24 11 5 25 12 3 26 12 4 27 12 8 28 12 7 29 13 1 30 13 3 31 13 7 32 13 5 33 14 2 34 14 4 35 14 8 36 14 6 faces 1 9 2 1 2 9 4 2 3 9 3 4 4 9 1 3 5 10 5 6 6 10 6 8 7 10 8 7 8 10 7 5 9 11 1 2 10 11 2 6 11 11 6 5 12 11 5 1 13 12 4 3 14 12 8 4 15 12 7 8 16 12 3 7 17 13 3 1 18 13 7 3 19 13 5 7 20 13 1 5 21 14 2 4 22 14 4 8 23 14 8 6 24 14 6 2 ================================================ FILE: test/functionals/cubeout.mesh ================================================ vertices 1 -0.430974 -0.430974 -0.430974 2 0.430974 -0.430974 -0.430974 3 -0.430974 0.430974 -0.430974 4 0.430974 0.430974 -0.430974 5 -0.430974 -0.430974 0.430974 6 0.430974 -0.430974 0.430974 7 -0.430974 0.430974 0.430974 8 0.430974 0.430974 0.430974 9 1.99469e-17 -1.08442e-17 -0.672989 10 3.28745e-17 5.96567e-17 0.672989 11 3.67119e-18 -0.672989 2.74001e-17 12 -1.984e-17 0.672989 6.29326e-18 13 -0.672989 -3.10151e-17 -2.05836e-17 14 0.672989 2.60876e-17 1.15108e-17 edges 1 1 2 2 3 4 3 1 3 4 2 4 5 5 6 6 7 8 7 5 7 8 6 8 9 1 5 10 2 6 11 3 7 12 4 8 13 1 9 14 2 9 15 4 9 16 3 9 17 5 10 18 6 10 19 8 10 20 7 10 21 1 11 22 2 11 23 6 11 24 5 11 25 3 12 26 4 12 27 8 12 28 7 12 29 1 13 30 3 13 31 7 13 32 5 13 33 2 14 34 4 14 35 8 14 36 6 14 faces 1 1 2 9 2 2 4 9 3 3 4 9 4 1 3 9 5 5 6 10 6 6 8 10 7 7 8 10 8 5 7 10 9 1 2 11 10 2 6 11 11 5 6 11 12 1 5 11 13 3 4 12 14 4 8 12 15 7 8 12 16 3 7 12 17 1 3 13 18 3 7 13 19 5 7 13 20 1 5 13 21 2 4 14 22 4 8 14 23 6 8 14 24 2 6 14 ================================================ FILE: test/functionals/equielement/equielement.morpho ================================================ // Test equielements import meshtools import optimize fn numericalgrad(func, m, eps=1e-8) { var x=m.vertexmatrix() var grad=x.clone() var dim = x.dimensions()[0] for (i in 0...m.count()) { for (j in 0...dim) { var temp = x[j,i] x[j,i]=temp+eps var fr=func.total(m) x[j,i]=temp-eps var fl=func.total(m) x[j,i]=temp grad[j,i]=(fr-fl)/(2*eps) } } return grad } var m = LineMesh(fn (t) [t, 0, 0], 0..1:0.5) var le = EquiElement() var v = m.vertexmatrix() v.setcolumn(1, Matrix([0.3, 0, 0])) print le.integrand(m) // expect: [ 0 0.32 0 ] print le.total(m) // expect: 0.32 print (le.gradient(m)-numericalgrad(le, m)).norm() < 1e-4 // expect: true var problem = OptimizationProblem(m) problem.addenergy(le) var opt=ShapeOptimizer(problem, m) opt.quiet=true opt.relax(20) print abs(opt.totalenergy())<1e-8 // expect: true ================================================ FILE: test/functionals/equielement/equielement_gradient.morpho ================================================ import meshgen import plot import meshtools import "../numericalderivatives.morpho" // Some random points var pts = [ [ 0.118625, 0.966611 ], [ 0.868882, 0.854463 ], [ 0.237799, 0.284121 ], [ 0.17586, 0.534928 ], [ 0.864168, 0.820575 ], [ 0.436567, 0.0629146 ], [ 0.770178, 0.529049 ], [ 0.554193, 0.844503 ], [ 0.418181, 0.414727 ], [ 0.652704, 0.682881 ] ] var x = [] for (p in pts) x.append(Matrix(p)) var mesh = DelaunayMesh(x) //mesh.addgrade(1) var leq = EquiElement() var ngrad=numericalgradient(leq, mesh) var grad=leq.gradient(mesh) print (ngrad-grad).norm()<1e-4 // expect: true ================================================ FILE: test/functionals/equielement/equielement_hessian.morpho ================================================ import meshtools import "../numericalderivatives.morpho" /* var m = LineMesh(fn (t) [cos(t^2), sin(t^2)], 0...sqrt(2*Pi):sqrt(2*Pi)/10, closed=true) var a = EquiElement() print a.total(m) print a.hessian(m) print "" print numericalhessian(a, m, eps=1e-3) */ ================================================ FILE: test/functionals/equielement/hex.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0.5 0.866025 0 4 -0.5 0.866025 0 5 -1 0 0 6 -0.5 -0.866025 0 7 0.5 -0.866025 0 faces 1 1 2 3 2 1 3 4 3 1 4 5 4 1 5 6 5 1 6 7 6 1 7 2 ================================================ FILE: test/functionals/equielement/weight.morpho ================================================ // Test equielements with weights import meshtools import optimize import plot var m = Mesh("hex.mesh") m.addgrade(1) m.setvertexposition(0, Matrix([0, 0.2, 0])) var bnd = Selection(m, boundary=true) bnd.addgrade(0) // Boundary vertices var w = Field(m, grade=2) for (i in 0...w.count()) w[2,i]=1 var le = EquiElement(grade=2, weight=w) var la = Area() var problem = OptimizationProblem(m) problem.addenergy(le) var opt=ShapeOptimizer(problem, m) opt.fix(bnd) opt.quiet=true opt.relax(10) var xc=m.vertexposition(0) print xc.inner(xc)<1e-9 // expect: true ================================================ FILE: test/functionals/err_functional_req_mesh.morpho ================================================ var m = Mesh("triangle.mesh") var a = Area() print a.integrand() // expect error 'FnctlIntMsh' ================================================ FILE: test/functionals/err_integrand.morpho ================================================ import meshtools import plot import functionals var L = 1.0 var dx = 0.1 var m = AreaMesh(fn (u, v) [u, v, 0], -L/2..L/2:dx, -L/2..L/2:dx) m.addgrade(1) fn mhdgrad(x,y,z,x0,y0,varphi){ return Matrix([(y-y0)/((x-x0)^2+(y-y0)^2)/2,-(x-x0)/((x-x0)^2+(y-y0)^2)/2]) } fn phdgrad(x,y,z,x0,y0,varphi){ return Matrix([-(y-y0)/((x-x0)^2+(y-y0)^2)/2,(x-x0)/((x-x0)^2+(y-y0)^2)/2]) } var defectsGrad = Field(m, fn(x,y,z) phdgrad(x, y, z, 0.25, 0, 0)+mhdgrad(x, y, z, -0.25, 0, 0) ) var directorIntegral = LineIntegral(fn (x,n) 1/(2*Pi) * n.inner(tangent()), defectsGrad) var fe = directorIntegral.integrand(m) //expect error 'MtrxIncmptbl' ================================================ FILE: test/functionals/gausscurvature/disk.mesh ================================================ vertices 1 -1. 0. 0 2 -0.951057 -0.309017 0 3 -0.951057 0.309017 0 4 -0.809017 -0.587785 0 5 -0.809017 0.587785 0 6 -0.587785 -0.809017 0 7 -0.587785 0.809017 0 8 -0.5 -0.5 0 9 -0.5 0. 0 10 -0.5 0.5 0 11 -0.309017 -0.951057 0 12 -0.309017 0.951057 0 13 0. -1. 0 14 0. -0.5 0 15 0. 0. 0 16 0. 0.5 0 17 0. 1. 0 18 0.309017 -0.951057 0 19 0.309017 0.951057 0 20 0.5 -0.5 0 21 0.5 0. 0 22 0.5 0.5 0 23 0.587785 -0.809017 0 24 0.587785 0.809017 0 25 0.809017 -0.587785 0 26 0.809017 0.587785 0 27 0.951057 -0.309017 0 28 0.951057 0.309017 0 29 1. 0. 0 edges 1 8 2 2 2 4 3 4 8 4 4 6 5 6 8 6 11 13 7 13 14 8 14 11 9 8 9 10 9 2 11 14 8 12 8 11 13 13 18 14 18 14 15 6 11 16 14 9 17 3 10 18 10 5 19 5 3 20 9 3 21 3 1 22 1 9 23 10 7 24 7 5 25 10 9 26 9 15 27 15 10 28 12 7 29 10 12 30 10 16 31 16 12 32 1 2 33 14 15 34 14 20 35 20 15 36 18 20 37 18 23 38 23 20 39 20 21 40 21 15 41 27 20 42 20 25 43 25 27 44 21 27 45 27 29 46 29 21 47 23 25 48 21 22 49 22 15 50 16 22 51 22 19 52 19 16 53 16 15 54 19 17 55 17 16 56 22 24 57 24 19 58 17 12 59 26 22 60 22 28 61 28 26 62 26 24 63 21 28 64 29 28 faces 1 8 2 4 2 8 4 6 3 11 13 14 4 2 8 9 5 14 8 11 6 13 18 14 7 11 8 6 8 9 8 14 9 3 10 5 10 9 3 1 11 10 7 5 12 10 9 15 13 12 7 10 14 10 16 12 15 10 3 9 16 1 2 9 17 15 9 14 18 14 20 15 19 20 14 18 20 20 18 23 21 15 20 21 22 27 20 25 23 21 27 29 24 21 20 27 25 25 20 23 26 15 21 22 27 16 22 19 28 16 15 22 29 19 17 16 30 22 24 19 31 16 17 12 32 26 22 28 33 24 22 26 34 22 21 28 35 29 28 21 36 15 16 10 ================================================ FILE: test/functionals/gausscurvature/gausscurvature.morpho ================================================ // Gaussian curvature import constants import implicitmesh import plot // Make a sphere var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) var mesh = impl.build(stepsize=0.25) // Gauss curvature var lgc = GaussCurvature() print abs(lgc.total(mesh)-4*Pi)<1e-6 // expect: true ================================================ FILE: test/functionals/gausscurvature/geodesiccurvature.morpho ================================================ // Geodesic curvature import constants import meshtools import meshgen import implicitmesh import plot // Computes the euler characteristic of a mesh fn euler(mesh) { return mesh.count(0) - mesh.count(1) + mesh.count(2) } // For surfaces with zero Gaussian curvature, the geodesic curvature integral will be equal to 2Pi * Euler characteristic of the surface. fn testzerogaussmesh(mesh, lgc) { mesh.addgrade(1) var bnd = Selection(mesh, boundary=true) print abs(lgc.total(mesh, bnd)-2*Pi*euler(mesh))<1e-6 } var lgc = GaussCurvature() lgc.geodesic = true // Disk mesh (Euler characteristic = 1) var mesh = Mesh("disk.mesh") testzerogaussmesh(mesh, lgc) // expect: true // Tube mesh (Euler characteristic = 0) var mesh = AreaMesh(fn (u, v) [v, cos(u), sin(u)], -Pi...Pi:Pi/4,-1..1:0.1, closed=[true, false]) testzerogaussmesh(mesh, lgc) // expect: true // Half-slice of a sphere fn disktohemisphere(mesh) { var vert = mesh.vertexmatrix() var hemisphere = mesh.clone() for (i in 0...mesh.count()) { var pos = vert.column(i) var x = pos[0] var y = pos[1] var z2 = 1 - x^2 - y^2 var z if (z2<0) z = 0 else z = sqrt(z2) var posnew = Matrix([x, y, z]) hemisphere.setvertexposition(i, posnew) } return hemisphere } // Create an approximate hemisphere var mesh = Mesh("disk.mesh") mesh = refinemesh(mesh) mesh.addgrade(1) var hemisphere = disktohemisphere(mesh) hemisphere.addgrade(1) var bnd = Selection(hemisphere, boundary=true) var whole = Selection(hemisphere, fn(x,y,z) true) var interior = whole.difference(bnd) // The hemisphere is an approximate one, so the value is off, but the gauss integral and the geodesic integral add up to 2Pi. var g1 = GaussCurvature().total(hemisphere, interior) var g2 = lgc.total(hemisphere, bnd) var lhs = g1+g2 print abs(lhs-2*Pi*euler(hemisphere))<1e-6 // expect: true ================================================ FILE: test/functionals/gausscurvature/gradient.morpho ================================================ // Gaussian curvature gradient import constants import implicitmesh import plot // Make a sphere var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) var m = impl.build(stepsize=0.25) // Mean Squared curvature var lc = GaussCurvature() var grad = lc.gradient(m) var dim = grad.dimensions() var ngrad = Matrix(dim[0], dim[1]) // Manually calculate the gradient var vert = m.vertexmatrix() var eps = 1e-8 for (i in 0...dim[0]) { for (j in 0...dim[1]) { var v = vert[i, j] vert[i, j] = v + eps var fp = lc.total(m) vert[i, j] = v - eps var fm = lc.total(m) ngrad[i,j] = (fp-fm)/(2*eps) } } print (grad-ngrad).norm()/grad.count() < 1e-6 // expect: true ================================================ FILE: test/functionals/gausscurvature/symm.morpho ================================================ // Gaussian curvature with symmetry import constants import implicitmesh import plot import symmetry var L = 2 var m = AreaMesh(fn (t, x) [x, cos(t), sin(t)], 0...2*Pi:2*Pi/40, -L..L:0.125, closed=[true,false]) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x-L)<1e-10 || abs(x+L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) // Mean Squared curvature var lmc = GaussCurvature() print abs(lmc.total(m))<1e-6 // expect: true ================================================ FILE: test/functionals/gausscurvature/torus.morpho ================================================ // Gaussian curvature with torus import constants import implicitmesh import plot // Make a sphere // Torus var r=1 var a=0.35 var impl = ImplicitMeshBuilder(fn (x,y,z) (x^2+y^2+z^2+r^2-a^2)^2 - 4*r^2*(x^2+y^2)) var mesh = impl.build(start=Matrix([1,0,0.5]), stepsize=0.25, maxiterations=400) // Mean Squared curvature var lmc = GaussCurvature() print abs(lmc.total(mesh))<1e-6 // expect: true ================================================ FILE: test/functionals/gradsq/disk.mesh ================================================ vertices 1 -1 0 0 2 -0.951057 -0.309017 0 3 -0.951057 0.309017 0 4 -0.809017 -0.587785 0 5 -0.809017 0.587785 0 6 -0.587785 -0.809017 0 7 -0.587785 0.809017 0 8 -0.5 -0.5 0 9 -0.5 0 0 10 -0.5 0.5 0 11 -0.309017 -0.951057 0 12 -0.309017 0.951057 0 13 0 -1 0 14 0 -0.5 0 15 0 0 0 16 0 0.5 0 17 0 1 0 18 0.309017 -0.951057 0 19 0.309017 0.951057 0 20 0.5 -0.5 0 21 0.5 0 0 22 0.5 0.5 0 23 0.587785 -0.809017 0 24 0.587785 0.809017 0 25 0.809017 -0.587785 0 26 0.809017 0.587785 0 27 0.951057 -0.309017 0 28 0.951057 0.309017 0 29 1 0 0 30 -0.25 0.5 0 31 0.725529 0.404508 0 32 -0.725529 -0.404508 0 33 -0.154508 0.725529 0 34 0.891007 0.45399 0 35 -0.891007 -0.45399 0 36 -0.987688 -0.156434 0 37 0.707107 0.707107 0 38 -0.654508 -0.543893 0 39 0 -0.25 0 40 0.725529 0.154508 0 41 -0.707107 -0.707107 0 42 0.25 -0.5 0 43 0.987688 0.156434 0 44 -0.543893 -0.654508 0 45 0.25 -0.25 0 46 -0.156434 -0.987688 0 47 0.404508 -0.725529 0 48 0 -0.75 0 49 0.45399 -0.891007 0 50 -0.154508 -0.725529 0 51 0.543893 -0.654508 0 52 -0.5 -0.25 0 53 0.5 -0.25 0 54 -0.725529 -0.154508 0 55 0.25 0 0 56 -0.25 -0.5 0 57 0.725529 -0.404508 0 58 -0.404508 -0.725529 0 59 0.654508 -0.543893 0 60 0.156434 -0.987688 0 61 0.891007 -0.45399 0 62 0.154508 -0.725529 0 63 0.725529 -0.154508 0 64 -0.45399 -0.891007 0 65 0.987688 -0.156434 0 66 -0.25 -0.25 0 67 0.75 0 0 68 -0.725529 0.404508 0 69 0.707107 -0.707107 0 70 -0.654508 0.543893 0 71 0.5 0.25 0 72 -0.891007 0.45399 0 73 0.25 0.25 0 74 -0.725529 0.154508 0 75 0.25 0.5 0 76 -0.987688 0.156434 0 77 0.404508 0.725529 0 78 -0.75 0 0 79 0.154508 0.725529 0 80 -0.543893 0.654508 0 81 0 0.25 0 82 -0.707107 0.707107 0 83 0.156434 0.987688 0 84 -0.5 0.25 0 85 0 0.75 0 86 -0.25 0 0 87 0.543893 0.654508 0 88 -0.25 0.25 0 89 0.45399 0.891007 0 90 -0.45399 0.891007 0 91 -0.156434 0.987688 0 92 -0.404508 0.725529 0 93 0.654508 0.543893 0 94 0.375 -0.375 0 95 0.233919 -0.972256 0 96 -0.972256 0.233919 0 97 -0.233919 0.972256 0 98 0.612764 0.202254 0 99 -0.496147 0.767273 0 100 0.862764 -0.0772542 0 101 -0.375 0.5 0 102 0.125 -0.125 0 103 0.852385 -0.522914 0 104 -0.996955 0.0779731 0 105 -0.452254 0.612764 0 106 -0.327254 -0.612764 0 107 -0.426455 0.802783 0 108 0.737764 -0.0772542 0 109 -0.125 0.5 0 110 -0.233919 -0.972256 0 111 0.924066 -0.382233 0 112 0.452254 0.612764 0 113 -0.356763 0.838293 0 114 -0.279508 -0.725529 0 115 -0.474201 0.690018 0 116 0.612764 -0.202254 0 117 0.612764 0.452254 0 118 -0.0779731 -0.996955 0 119 0.231763 -0.838293 0 120 0.356763 0.838293 0 121 0.731763 0.565839 0 122 -0.202254 -0.612764 0 123 -0.202254 0.612764 0 124 0.725529 -0.279508 0 125 0.838293 0.356763 0 126 0.356763 -0.838293 0 127 0.0772542 -0.612764 0 128 -0.875 0 0 129 0.577254 0.521946 0 130 0.850529 0.154508 0 131 -0.279508 0.725529 0 132 0.612764 -0.327254 0 133 -0.612764 -0.452254 0 134 0.452254 -0.612764 0 135 0.612764 -0.0772542 0 136 -0.625 0 0 137 0.496147 0.767273 0 138 0.862764 0.0772542 0 139 -0.327254 0.612764 0 140 0.676455 -0.621147 0 141 -0.838293 -0.356763 0 142 0 -0.875 0 143 0.838293 -0.231763 0 144 0.231763 0.838293 0 145 0.426455 0.802783 0 146 0.737764 0.0772542 0 147 -0.612764 0.327254 0 148 0.621147 -0.676455 0 149 -0.0772542 0.612764 0 150 0 -0.625 0 151 -0.522914 -0.852385 0 152 0.0772542 0.612764 0 153 0.474201 0.690018 0 154 0.154508 -0.850529 0 155 -0.612764 0.202254 0 156 0.599201 -0.599201 0 157 -0.231763 0.838293 0 158 0.382233 -0.924066 0 159 -0.382233 -0.924066 0 160 -0.521946 0.577254 0 161 -0.802783 -0.426455 0 162 0.0772542 -0.737764 0 163 -0.725529 0.279508 0 164 0.375 0.25 0 165 0.924066 0.382233 0 166 0.522914 -0.852385 0 167 0.972256 -0.233919 0 168 -0.565839 0.731763 0 169 -0.767273 -0.496147 0 170 0.0772542 -0.862764 0 171 -0.850529 -0.154508 0 172 0.25 0.125 0 173 0.852385 0.522914 0 174 -0.0772542 -0.612764 0 175 0.996955 -0.0779731 0 176 0 0.375 0 177 -0.690018 -0.474201 0 178 -0.125 0.25 0 179 -0.862764 -0.0772542 0 180 0.375 0.125 0 181 -0.924066 -0.382233 0 182 -0.231763 -0.838293 0 183 -0.125 -0.375 0 184 0 0.125 0 185 -0.154508 0.850529 0 186 -0.25 0.375 0 187 -0.737764 -0.0772542 0 188 0.327254 0.612764 0 189 -0.852385 -0.522914 0 190 0.565839 -0.731763 0 191 -0.375 -0.125 0 192 -0.649077 0.760723 0 193 -0.0772542 0.737764 0 194 -0.125 0.375 0 195 -0.125 -0.125 0 196 0.279508 0.725529 0 197 -0.996955 -0.0779731 0 198 0.521946 -0.577254 0 199 0.875 0 0 200 -0.760723 0.649077 0 201 -0.0772542 0.862764 0 202 -0.426455 -0.802783 0 203 -0.125 -0.25 0 204 0.202254 0.612764 0 205 -0.972256 -0.233919 0 206 -0.5 -0.375 0 207 0.625 0 0 208 0.233919 0.972256 0 209 -0.621147 -0.676455 0 210 -0.496147 -0.767273 0 211 -0.25 -0.125 0 212 0.125 0.25 0 213 0.760723 0.649077 0 214 -0.5 -0.125 0 215 -0.838293 0.356763 0 216 0.0779731 0.996955 0 217 -0.599201 -0.599201 0 218 -0.474201 -0.690018 0 219 0.25 -0.375 0 220 0.125 0.375 0 221 0.649077 0.760723 0 222 0.5 -0.375 0 223 -0.612764 0.452254 0 224 -0.5 0.375 0 225 -0.676455 -0.621147 0 226 -0.375 -0.25 0 227 0.125 -0.25 0 228 0.25 0.375 0 229 -0.731763 -0.565839 0 230 0.5 -0.125 0 231 0.649077 -0.760723 0 232 -0.5 0.125 0 233 0.690018 0.474201 0 234 -0.25 -0.375 0 235 0.125 -0.375 0 236 0.0772542 0.862764 0 237 -0.577254 -0.521946 0 238 -0.612764 -0.0772542 0 239 0.760723 -0.649077 0 240 0 0.875 0 241 0.802783 0.426455 0 242 -0.375 -0.375 0 243 0.327254 -0.612764 0 244 0.0772542 0.737764 0 245 0 -0.375 0 246 -0.838293 -0.231763 0 247 -0.577254 0.521946 0 248 0 0.625 0 249 0.767273 0.496147 0 250 -0.690018 0.474201 0 251 0.279508 -0.725529 0 252 0.154508 0.850529 0 253 0 -0.125 0 254 0.375 0 0 255 -0.731763 0.565839 0 256 -0.375 0 0 257 -0.0772542 -0.862764 0 258 -0.767273 0.496147 0 259 0.202254 -0.612764 0 260 0.612764 0.0772542 0 261 0.125 0 0 262 0.5 0.125 0 263 -0.125 0 0 264 -0.0772542 -0.737764 0 265 -0.802783 0.426455 0 266 0.496147 -0.767273 0 267 0.838293 0.231763 0 268 -0.125 -0.5 0 269 0.5 0.375 0 270 0.521946 0.577254 0 271 -0.154508 -0.850529 0 272 -0.850529 0.154508 0 273 0.474201 -0.690018 0 274 -0.760723 -0.649077 0 275 -0.375 -0.5 0 276 -0.852385 0.522914 0 277 0.565839 0.731763 0 278 0.621147 0.676455 0 279 -0.862764 0.0772542 0 280 0.426455 -0.802783 0 281 -0.649077 -0.760723 0 282 0.838293 -0.356763 0 283 -0.924066 0.382233 0 284 -0.125 0.125 0 285 0.676455 0.621147 0 286 -0.737764 0.0772542 0 287 0.375 -0.125 0 288 0.125 -0.5 0 289 0.612764 -0.452254 0 290 0.375 0.375 0 291 -0.375 0.375 0 292 0.599201 0.599201 0 293 -0.621147 0.676455 0 294 0.25 -0.125 0 295 0.375 -0.5 0 296 -0.452254 -0.612764 0 297 0.125 0.125 0 298 0.522914 0.852385 0 299 -0.612764 -0.202254 0 300 -0.676455 0.621147 0 301 0.375 -0.25 0 302 0.996955 0.0779731 0 303 -0.356763 -0.838293 0 304 -0.612764 0.0772542 0 305 0.382233 0.924066 0 306 -0.725529 -0.279508 0 307 -0.599201 0.599201 0 308 0.690018 -0.474201 0 309 0.972256 0.233919 0 310 0.577254 -0.521946 0 311 -0.838293 0.231763 0 312 -0.382233 0.924066 0 313 -0.612764 -0.327254 0 314 -0.375 0.125 0 315 0.767273 -0.496147 0 316 -0.565839 -0.731763 0 317 0.731763 -0.565839 0 318 0.125 0.5 0 319 -0.522914 0.852385 0 320 0.725529 0.279508 0 321 -0.25 0.125 0 322 0.802783 -0.426455 0 323 -0.521946 -0.577254 0 324 0.0779731 -0.996955 0 325 0.375 0.5 0 326 -0.0779731 0.996955 0 327 0.612764 0.327254 0 328 -0.375 0.25 0 329 0.850529 -0.154508 0 edges 1 295 222 2 219 301 3 60 95 4 95 18 5 3 96 6 96 76 7 91 97 8 97 12 9 71 98 10 98 40 11 168 115 12 319 107 13 65 100 14 100 67 15 10 101 16 101 30 17 227 294 18 253 261 19 25 103 20 103 61 21 76 104 22 104 1 23 10 105 24 105 92 25 56 106 26 106 58 27 90 107 28 107 92 29 67 108 30 108 63 31 30 109 32 109 16 33 11 110 34 110 46 35 61 111 36 111 27 37 22 112 38 112 77 39 92 113 40 113 12 41 58 114 42 114 50 43 92 115 44 115 80 45 53 116 46 116 63 47 22 117 48 117 31 49 46 118 50 118 13 51 18 119 52 119 62 53 77 120 54 120 19 55 26 121 56 121 93 57 50 122 58 122 56 59 30 123 60 123 33 61 63 124 62 124 57 63 31 125 64 125 28 65 18 126 66 126 47 67 62 127 68 127 14 69 1 128 70 128 78 71 93 129 72 129 22 73 40 130 74 130 43 75 33 131 76 131 92 77 57 132 78 132 53 79 8 133 80 133 32 81 47 134 82 134 20 83 21 135 84 135 63 85 78 136 86 136 9 87 277 153 88 298 145 89 43 138 90 138 67 91 92 139 92 139 30 93 59 140 94 140 69 95 32 141 96 141 2 97 13 142 98 142 48 99 63 143 100 143 27 101 19 144 102 144 79 103 89 145 104 145 77 105 67 146 106 146 40 107 68 147 108 147 84 109 69 148 110 148 51 111 16 149 112 149 33 113 48 150 114 150 14 115 6 151 116 151 64 117 79 152 118 152 16 119 77 153 120 153 87 121 60 154 122 154 62 123 84 155 124 155 74 125 51 156 126 156 59 127 33 157 128 157 12 129 18 158 130 158 49 131 64 159 132 159 11 133 10 160 134 160 80 135 32 161 136 161 35 137 62 162 138 162 48 139 74 163 140 163 68 141 71 164 142 164 73 143 28 165 144 165 34 145 49 166 146 166 23 147 27 167 148 167 65 149 80 168 150 168 7 151 161 189 152 229 177 153 48 170 154 170 60 155 54 171 156 171 36 157 73 172 158 172 55 159 34 173 160 173 26 161 14 174 162 174 50 163 65 175 164 175 29 165 16 176 166 176 81 167 38 177 168 177 32 169 81 178 170 178 88 171 36 179 172 179 78 173 172 254 174 262 164 175 2 181 176 181 35 177 50 182 178 182 11 179 245 268 180 203 234 181 81 184 182 184 15 183 91 185 184 185 33 185 88 186 186 186 30 187 78 187 188 187 54 189 75 188 190 188 77 191 35 189 192 189 4 193 23 190 194 190 51 195 211 226 196 256 214 197 7 192 198 192 82 199 33 193 200 193 85 201 109 186 202 176 178 203 211 263 204 203 253 205 77 196 206 196 79 207 1 197 208 197 36 209 51 198 210 198 20 211 29 199 212 199 67 213 82 200 214 200 5 215 85 201 216 201 91 217 58 202 218 202 64 219 39 203 220 203 66 221 79 204 222 204 75 223 36 205 224 205 2 225 8 206 226 206 52 227 67 207 228 207 21 229 19 208 230 208 83 231 41 209 232 209 44 233 151 202 234 316 218 235 66 211 236 211 86 237 73 212 238 212 81 239 26 213 240 213 37 241 52 214 242 214 9 243 3 215 244 215 68 245 83 216 246 216 17 247 44 217 248 217 38 249 44 218 250 218 58 251 42 219 252 219 45 253 176 212 254 318 228 255 37 221 256 221 24 257 20 222 258 222 53 259 68 223 260 223 10 261 10 224 262 224 84 263 38 225 264 225 41 265 52 226 266 226 66 267 45 227 268 227 39 269 75 228 270 228 73 271 4 229 272 229 38 273 53 230 274 230 21 275 23 231 276 231 69 277 84 232 278 232 9 279 93 233 280 233 31 281 66 234 282 234 56 283 227 245 284 288 219 285 83 236 286 236 85 287 38 237 288 237 8 289 9 238 290 238 54 291 69 239 292 239 25 293 17 240 294 240 85 295 31 241 296 241 34 297 234 275 298 206 226 299 42 243 300 243 47 301 85 244 302 244 79 303 14 245 304 245 39 305 54 246 306 246 2 307 10 247 308 247 70 309 85 248 310 248 16 311 173 241 312 121 233 313 68 250 314 250 70 315 47 251 316 251 62 317 79 252 318 252 83 319 39 253 320 253 15 321 21 254 322 254 55 323 70 255 324 255 5 325 9 256 326 256 86 327 46 257 328 257 48 329 250 255 330 276 265 331 62 259 332 259 42 333 21 260 334 260 40 335 55 261 336 261 15 337 21 262 338 262 71 339 86 263 340 263 15 341 48 264 342 264 50 343 72 265 344 265 68 345 166 280 346 190 273 347 40 267 348 267 28 349 14 268 350 268 56 351 71 269 352 269 22 353 22 270 354 270 87 355 50 271 356 271 46 357 74 272 358 272 76 359 51 273 360 273 47 361 4 274 362 274 41 363 56 275 364 275 8 365 5 276 366 276 72 367 87 277 368 277 24 369 87 278 370 278 37 371 76 279 372 279 78 373 47 280 374 280 49 375 41 281 376 281 6 377 27 282 378 282 57 379 72 283 380 283 3 381 184 263 382 178 321 383 37 285 384 285 93 385 78 286 386 286 74 387 230 301 388 254 294 389 14 288 390 288 42 391 57 289 392 289 20 393 269 325 394 164 228 395 186 328 396 101 224 397 93 292 398 292 87 399 80 293 400 293 82 401 55 294 402 294 45 403 42 295 404 295 20 405 8 296 406 296 58 407 172 212 408 261 184 409 24 298 410 298 89 411 52 299 412 299 54 413 82 300 414 300 70 415 45 301 416 301 53 417 29 302 418 302 43 419 58 303 420 303 11 421 9 304 422 304 74 423 89 305 424 305 19 425 54 306 426 306 32 427 70 307 428 307 80 429 57 308 430 308 59 431 43 309 432 309 28 433 20 310 434 310 59 435 74 311 436 311 3 437 12 312 438 312 90 439 32 313 440 313 52 441 232 328 442 256 321 443 308 317 444 103 322 445 6 316 446 316 44 447 59 317 448 317 25 449 16 318 450 318 75 451 90 319 452 319 7 453 40 320 454 320 31 455 86 321 456 321 88 457 61 322 458 322 57 459 44 323 460 323 8 461 13 324 462 324 60 463 75 325 464 325 22 465 17 326 466 326 91 467 31 327 468 327 71 469 88 328 470 328 84 471 63 329 472 329 65 473 39 183 474 203 183 475 183 245 476 24 137 477 298 137 478 137 277 479 86 191 480 211 191 481 191 256 482 120 305 483 89 120 484 145 120 485 66 195 486 211 195 487 195 203 488 87 112 489 153 112 490 112 270 491 295 94 492 94 219 493 42 94 494 137 145 495 77 137 496 153 137 497 253 102 498 102 227 499 39 102 500 141 181 501 35 141 502 161 141 503 288 235 504 235 245 505 14 235 506 4 169 507 229 169 508 169 189 509 45 235 510 227 235 511 235 219 512 237 133 513 133 177 514 38 133 515 295 134 516 134 243 517 243 295 518 161 169 519 169 177 520 32 169 521 126 251 522 251 119 523 119 126 524 157 97 525 97 185 526 185 157 527 288 259 528 259 127 529 127 288 530 149 193 531 193 248 532 248 149 533 251 259 534 259 243 535 243 251 536 326 201 537 201 240 538 240 326 539 23 266 540 190 266 541 266 166 542 185 193 543 193 201 544 201 185 545 134 198 546 51 134 547 273 134 548 281 316 549 316 209 550 209 281 551 49 126 552 280 126 553 126 158 554 237 323 555 323 217 556 217 237 557 266 273 558 47 266 559 280 266 560 274 225 561 225 229 562 229 274 563 21 287 564 254 287 565 287 230 566 209 217 567 217 225 568 225 209 569 102 261 570 55 102 571 294 102 572 129 117 573 117 233 574 93 117 575 53 94 576 301 94 577 94 222 578 125 165 579 34 125 580 241 125 581 287 294 582 45 287 583 301 287 584 26 249 585 121 249 586 249 173 587 289 310 588 59 289 589 308 289 590 31 249 591 241 249 592 249 233 593 25 315 594 103 315 595 315 317 596 118 142 597 142 257 598 257 118 599 111 282 600 282 322 601 61 282 602 150 174 603 174 264 604 264 150 605 308 315 606 315 322 607 57 315 608 182 110 609 110 271 610 271 182 611 143 167 612 167 329 613 329 143 614 257 264 615 264 271 616 271 257 617 175 199 618 199 100 619 100 175 620 277 221 621 221 278 622 278 277 623 207 135 624 135 108 625 108 207 626 213 285 627 285 121 628 121 213 629 329 100 630 100 108 631 108 329 632 270 292 633 292 129 634 129 270 635 135 116 636 116 230 637 230 135 638 285 292 639 292 278 640 278 285 641 282 124 642 124 143 643 143 282 644 214 238 645 238 299 646 299 214 647 222 132 648 132 289 649 289 222 650 141 246 651 246 306 652 306 141 653 124 132 654 132 116 655 116 124 656 206 313 657 313 133 658 133 206 659 317 239 660 239 140 661 140 317 662 299 306 663 306 313 664 313 299 665 231 148 666 148 190 667 190 231 668 125 267 669 267 320 670 320 125 671 310 156 672 156 198 673 198 310 674 117 327 675 327 269 676 269 117 677 148 156 678 156 140 679 140 148 680 260 98 681 98 262 682 262 260 683 269 290 684 290 164 685 71 290 686 320 327 687 327 98 688 98 320 689 261 297 690 297 172 691 55 297 692 275 296 693 296 106 694 106 275 695 262 180 696 180 254 697 21 180 698 182 303 699 303 114 700 114 182 701 73 180 702 172 180 703 180 164 704 268 122 705 122 174 706 174 268 707 325 112 708 112 188 709 188 325 710 106 114 711 114 122 712 122 106 713 120 144 714 144 196 715 196 120 716 267 309 717 309 130 718 130 267 719 152 318 720 318 204 721 204 152 722 302 138 723 138 199 724 199 302 725 188 196 726 196 204 727 204 188 728 260 146 729 146 207 730 207 260 731 297 184 732 81 297 733 212 297 734 138 146 735 146 130 736 130 138 737 176 220 738 220 318 739 16 220 740 95 119 741 119 154 742 154 95 743 290 228 744 75 290 745 325 290 746 150 127 747 127 162 748 162 150 749 220 228 750 73 220 751 212 220 752 324 170 753 170 142 754 142 324 755 216 240 756 240 236 757 236 216 758 154 162 759 162 170 760 170 154 761 152 248 762 248 244 763 244 152 764 284 178 765 81 284 766 184 284 767 208 252 768 252 144 769 144 208 770 30 291 771 186 291 772 291 101 773 236 244 774 244 252 775 252 236 776 176 194 777 194 109 778 16 194 779 186 194 780 194 178 781 88 194 782 303 159 783 64 303 784 202 303 785 151 210 786 210 316 787 6 210 788 296 218 789 44 296 790 323 296 791 210 218 792 58 210 793 202 210 794 214 191 795 191 226 796 52 191 797 183 234 798 56 183 799 268 183 800 206 242 801 242 275 802 8 242 803 234 242 804 242 226 805 66 242 806 223 247 807 70 223 808 250 223 809 5 258 810 276 258 811 258 255 812 283 215 813 215 265 814 72 215 815 250 258 816 258 265 817 68 258 818 311 96 819 96 272 820 272 311 821 104 128 822 128 279 823 279 104 824 136 304 825 304 286 826 286 136 827 272 279 828 279 286 829 286 272 830 168 192 831 192 293 832 293 168 833 255 200 834 200 300 835 300 255 836 160 307 837 307 247 838 247 160 839 293 300 840 300 307 841 307 293 842 9 314 843 256 314 844 314 232 845 263 284 846 284 321 847 86 284 848 291 224 849 84 291 850 328 291 851 314 321 852 88 314 853 328 314 854 7 99 855 319 99 856 99 168 857 113 312 858 90 113 859 107 113 860 105 115 861 80 105 862 160 105 863 92 99 864 115 99 865 99 107 866 109 149 867 149 123 868 123 109 869 113 157 870 157 131 871 131 113 872 101 139 873 139 105 874 105 101 875 123 131 876 131 139 877 139 123 878 224 147 879 147 223 880 223 224 881 304 155 882 155 232 883 232 304 884 215 163 885 163 311 886 311 215 887 155 163 888 163 147 889 147 155 890 246 205 891 205 171 892 171 246 893 197 179 894 179 128 895 128 197 896 238 187 897 187 136 898 136 238 899 179 187 900 187 171 901 171 179 902 15 195 903 253 195 904 195 263 faces 1 245 39 183 2 203 234 183 3 14 245 268 4 39 203 183 5 277 24 137 6 298 145 137 7 87 277 153 8 24 298 137 9 86 191 256 10 66 211 226 11 191 256 214 12 211 86 191 13 120 19 305 14 89 305 120 15 77 120 145 16 89 120 145 17 66 195 203 18 211 263 86 19 195 203 253 20 66 195 211 21 270 87 112 22 77 153 112 23 22 270 112 24 87 112 153 25 295 20 222 26 219 301 94 27 42 94 219 28 42 295 94 29 298 89 145 30 145 77 137 31 277 153 137 32 77 153 137 33 253 102 261 34 227 294 45 35 39 102 227 36 39 102 253 37 141 2 181 38 181 35 141 39 32 141 161 40 35 141 161 41 288 219 235 42 227 245 235 43 14 235 245 44 14 288 235 45 189 4 169 46 229 177 169 47 161 189 169 48 4 229 169 49 219 45 235 50 227 39 245 51 288 42 219 52 45 227 235 53 237 8 133 54 177 32 133 55 38 133 177 56 38 237 133 57 295 20 134 58 243 47 134 59 42 243 295 60 134 243 295 61 161 35 189 62 229 38 177 63 177 32 169 64 32 161 169 65 126 47 251 66 119 62 251 67 18 126 119 68 126 251 119 69 157 12 97 70 91 97 185 71 33 157 185 72 97 185 157 73 288 42 259 74 62 259 127 75 14 288 127 76 259 127 288 77 33 193 149 78 85 248 193 79 16 149 248 80 193 248 149 81 62 259 251 82 42 243 259 83 47 251 243 84 259 243 251 85 201 91 326 86 85 201 240 87 17 326 240 88 326 201 240 89 166 23 266 90 190 273 266 91 49 166 280 92 23 190 266 93 185 33 193 94 193 85 201 95 201 91 185 96 185 193 201 97 134 20 198 98 51 198 134 99 47 134 273 100 51 134 273 101 281 6 316 102 209 44 316 103 41 281 209 104 281 316 209 105 158 49 126 106 47 280 126 107 18 158 126 108 49 126 280 109 237 8 323 110 44 323 217 111 38 237 217 112 323 217 237 113 190 51 273 114 273 47 266 115 166 280 266 116 47 280 266 117 225 41 274 118 38 225 229 119 4 274 229 120 274 225 229 121 230 21 287 122 254 294 287 123 53 230 301 124 21 254 287 125 209 44 217 126 217 38 225 127 225 41 209 128 209 217 225 129 253 261 15 130 55 261 102 131 102 227 294 132 55 102 294 133 129 22 117 134 233 31 117 135 93 117 233 136 93 129 117 137 222 53 94 138 219 301 45 139 295 222 94 140 53 94 301 141 125 28 165 142 165 34 125 143 31 125 241 144 34 125 241 145 254 55 294 146 294 45 287 147 230 301 287 148 45 301 287 149 173 26 249 150 121 233 249 151 34 173 241 152 26 121 249 153 289 20 310 154 310 59 289 155 57 289 308 156 59 289 308 157 233 31 249 158 173 241 249 159 121 93 233 160 31 241 249 161 317 25 315 162 103 322 315 163 308 317 315 164 25 103 315 165 118 13 142 166 257 48 142 167 46 118 257 168 118 142 257 169 111 27 282 170 322 57 282 171 61 282 322 172 61 111 282 173 150 14 174 174 264 50 174 175 48 150 264 176 150 174 264 177 308 59 317 178 103 61 322 179 322 57 315 180 57 308 315 181 182 11 110 182 271 46 110 183 50 182 271 184 182 110 271 185 143 27 167 186 329 65 167 187 63 143 329 188 143 167 329 189 257 48 264 190 264 50 271 191 271 46 257 192 257 264 271 193 175 29 199 194 100 67 199 195 65 175 100 196 175 199 100 197 277 24 221 198 278 37 221 199 87 278 277 200 221 278 277 201 207 21 135 202 108 63 135 203 67 207 108 204 207 135 108 205 213 37 285 206 121 93 285 207 26 213 121 208 213 285 121 209 329 65 100 210 100 67 108 211 108 63 329 212 329 100 108 213 270 87 292 214 93 292 129 215 22 270 129 216 292 129 270 217 135 63 116 218 53 116 230 219 21 135 230 220 116 230 135 221 93 292 285 222 87 278 292 223 37 285 278 224 292 278 285 225 282 57 124 226 63 124 143 227 27 282 143 228 124 143 282 229 214 9 238 230 299 54 238 231 52 214 299 232 214 238 299 233 222 53 132 234 57 132 289 235 20 222 289 236 132 289 222 237 141 2 246 238 54 246 306 239 32 141 306 240 246 306 141 241 57 132 124 242 53 116 132 243 63 124 116 244 132 116 124 245 313 52 206 246 32 313 133 247 8 206 133 248 206 313 133 249 317 25 239 250 140 69 239 251 59 140 317 252 239 140 317 253 299 54 306 254 306 32 313 255 313 52 299 256 299 306 313 257 231 69 148 258 190 51 148 259 23 231 190 260 231 148 190 261 125 28 267 262 40 267 320 263 31 125 320 264 267 320 125 265 310 59 156 266 51 156 198 267 20 310 198 268 156 198 310 269 31 327 117 270 71 269 327 271 22 117 269 272 327 269 117 273 51 156 148 274 59 140 156 275 69 148 140 276 156 140 148 277 98 40 260 278 71 98 262 279 21 260 262 280 260 98 262 281 269 22 325 282 164 228 290 283 71 290 164 284 71 269 290 285 320 31 327 286 327 71 98 287 98 40 320 288 320 327 98 289 261 297 184 290 172 212 73 291 55 297 172 292 55 297 261 293 275 8 296 294 106 58 296 295 56 275 106 296 275 296 106 297 262 164 180 298 172 254 180 299 21 180 254 300 21 262 180 301 182 11 303 302 58 303 114 303 50 182 114 304 303 114 182 305 164 73 180 306 172 55 254 307 262 71 164 308 73 172 180 309 122 56 268 310 50 122 174 311 14 268 174 312 268 122 174 313 325 22 112 314 188 77 112 315 75 325 188 316 325 112 188 317 106 58 114 318 114 50 122 319 122 56 106 320 106 114 122 321 120 19 144 322 196 79 144 323 77 120 196 324 120 144 196 325 267 28 309 326 130 43 309 327 40 130 267 328 309 130 267 329 152 16 318 330 204 75 318 331 79 152 204 332 152 318 204 333 302 43 138 334 199 67 138 335 29 302 199 336 302 138 199 337 188 77 196 338 196 79 204 339 204 75 188 340 188 196 204 341 260 40 146 342 67 146 207 343 21 260 207 344 146 207 260 345 261 184 15 346 81 184 297 347 297 172 212 348 212 81 297 349 67 146 138 350 40 130 146 351 43 138 130 352 146 130 138 353 176 81 212 354 318 228 220 355 16 220 318 356 16 176 220 357 95 18 119 358 154 62 119 359 60 95 154 360 95 119 154 361 164 228 73 362 75 228 290 363 269 325 290 364 75 290 325 365 150 14 127 366 62 127 162 367 48 150 162 368 127 162 150 369 318 228 75 370 73 220 228 371 176 212 220 372 73 220 212 373 170 60 324 374 48 170 142 375 13 324 142 376 324 170 142 377 216 17 240 378 236 85 240 379 83 216 236 380 216 240 236 381 154 62 162 382 162 48 170 383 170 60 154 384 154 162 170 385 152 16 248 386 85 248 244 387 79 152 244 388 248 244 152 389 284 178 321 390 81 178 284 391 184 263 15 392 81 284 184 393 252 83 208 394 79 252 144 395 19 208 144 396 208 252 144 397 30 291 101 398 88 186 328 399 291 101 224 400 186 30 291 401 236 85 244 402 244 79 252 403 252 83 236 404 236 244 252 405 176 194 178 406 109 186 30 407 16 194 109 408 16 194 176 409 194 109 186 410 176 178 81 411 88 194 178 412 88 194 186 413 303 11 159 414 64 159 303 415 58 202 303 416 202 64 303 417 151 64 202 418 316 218 210 419 6 210 316 420 6 151 210 421 296 58 218 422 44 218 296 423 8 296 323 424 44 296 323 425 316 218 44 426 58 210 218 427 151 202 210 428 58 210 202 429 256 214 9 430 211 191 226 431 52 226 191 432 52 191 214 433 203 66 234 434 234 56 183 435 245 268 183 436 56 183 268 437 206 242 226 438 234 242 275 439 8 242 275 440 8 242 206 441 234 275 56 442 206 226 52 443 66 242 226 444 66 242 234 445 223 10 247 446 247 70 223 447 68 223 250 448 70 223 250 449 255 5 258 450 276 265 258 451 250 255 258 452 5 276 258 453 283 3 215 454 265 68 215 455 72 215 265 456 72 283 215 457 250 70 255 458 276 72 265 459 265 68 258 460 68 250 258 461 311 3 96 462 272 76 96 463 74 311 272 464 311 96 272 465 104 1 128 466 279 78 128 467 76 104 279 468 104 128 279 469 136 9 304 470 286 74 304 471 78 136 286 472 136 304 286 473 272 76 279 474 279 78 286 475 286 74 272 476 272 279 286 477 168 7 192 478 293 82 192 479 80 168 293 480 168 192 293 481 255 5 200 482 82 200 300 483 70 255 300 484 200 300 255 485 307 80 160 486 70 307 247 487 10 160 247 488 160 307 247 489 293 82 300 490 300 70 307 491 307 80 293 492 293 300 307 493 232 9 314 494 256 321 314 495 84 232 328 496 9 256 314 497 184 284 263 498 178 88 321 499 86 284 321 500 86 263 284 501 101 224 10 502 224 84 291 503 186 291 328 504 84 291 328 505 256 86 321 506 321 88 314 507 232 328 314 508 88 328 314 509 7 99 168 510 319 99 107 511 168 115 80 512 319 7 99 513 113 12 312 514 312 90 113 515 107 92 113 516 90 107 113 517 105 92 115 518 115 80 105 519 10 105 160 520 80 105 160 521 92 99 107 522 99 168 115 523 90 319 107 524 92 99 115 525 109 16 149 526 123 33 149 527 30 109 123 528 109 149 123 529 113 12 157 530 33 157 131 531 92 113 131 532 157 131 113 533 139 30 101 534 92 139 105 535 10 101 105 536 101 139 105 537 123 33 131 538 131 92 139 539 139 30 123 540 123 131 139 541 224 84 147 542 68 147 223 543 10 224 223 544 147 223 224 545 304 74 155 546 84 155 232 547 9 304 232 548 155 232 304 549 215 68 163 550 74 163 311 551 3 215 311 552 163 311 215 553 74 163 155 554 68 147 163 555 84 155 147 556 163 147 155 557 246 2 205 558 171 36 205 559 54 171 246 560 205 171 246 561 197 36 179 562 128 78 179 563 1 197 128 564 197 179 128 565 238 54 187 566 78 187 136 567 9 238 136 568 187 136 238 569 78 187 179 570 54 171 187 571 36 179 171 572 187 171 179 573 15 195 263 574 203 253 39 575 211 195 263 576 253 15 195 ================================================ FILE: test/functionals/gradsq/gradsq.morpho ================================================ // Test gradsq on a single triangle import meshtools import shapeopt var m = Mesh("triangle.mesh") var phi = Field(m) phi[1]=1 var lel = GradSq(phi) print lel.integrand(m) // expect: [ 0.57735 ] print lel.total(m) // expect: 0.57735 var grad = lel.gradient(m) var value = Matrix([[ 0.192452, 0.192452, -0.3849 ],[ 0.333333, -0.333335, 5.55112e-07 ],[ 0, 0, 0 ]]) print (grad-value).norm()<1e-5 // expect: true var grd=lel.fieldgradient(m, phi) print abs(grd[0] - (-0.57735))<5e-6 print abs(grd[1] - (1.1547))<5e-6 print abs(grd[2] - (-0.57735))<5e-6 // expect: true // expect: true // expect: true ================================================ FILE: test/functionals/gradsq/gradsq1d.morpho ================================================ // Currently morpho doesn't support GradSq on 1D elements. // This test will in future check that this works once implemented /*import meshtools var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0]) mb.addedge([0,1]) var m = mb.build() var f = Field(m, fn (x,y,z) x) var lgs = GradSq(f) print lgs.total(m) */ ================================================ FILE: test/functionals/gradsq/gradsq3d.morpho ================================================ // Test gradsq on a single tetrahedron import meshtools import shapeopt import plot var tol = 2e-4 var mb = MeshBuilder() mb.addvertex(Matrix([0,0,0])) mb.addvertex(Matrix([2,3,0])) mb.addvertex(Matrix([-1,2,1])) mb.addvertex(Matrix([1,4,5])) mb.addelement(3,[0,1,2,3]) var m = mb.build() m.addgrade(1) var phi = Field(m, Matrix(2)) phi[0]=Matrix([0,1]) phi[1]=Matrix([8,2]) phi[2]=Matrix([7/2,3]) phi[3]=Matrix([23/2,4]) var lel = GradSq(phi) var expected = 29.8611 var tol = 2e-4 print abs(lel.integrand(m)[0]-expected) |grad f|^2 = 4 (x^2+y^2) -> integral over unit disk is 2 pi import meshtools import shapeopt var m = Mesh("disk.mesh") var phi = Field(m, fn (x,y,z) x^2 + y^2) var lel = GradSq(phi) print lel.total(m) // expect: 6.21862 ================================================ FILE: test/functionals/gradsq/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 volumes 1 1 2 3 4 ================================================ FILE: test/functionals/gradsq/triangle.mesh ================================================ vertices 1 1 0 0 2 -0.5 0.866025 0 3 -0.5 -0.866025 0 faces 1 1 2 3 ================================================ FILE: test/functionals/hydrogel/hydrogel.morpho ================================================ // Hydrogel mixing energy (modeled from Flory-Rehner theory) var m0 = Mesh("tetrahedron.mesh") var vol0 = Volume().total(m0) var phi0 = 0.5 var phiref = 0.1 var a = 0.1 var b = 1.0 var c = 0.25 var d = 1.0 var lh = Hydrogel(m0, a = a, b = b, c = c, d = d, phiref=phiref, phi0=phi0) var m = Mesh("tetrahedron.mesh") // Expand m by a linear factor var f = 1.2 var vert = m.vertexmatrix() for (i in 0...m.count()) m.setvertexposition(i, f*vert.column(i)) var phi = phi0/(f^3) // New phi will be inversely proportional to the volume var vol = vol0 * f^3 var e1 = vol * (a*phi*log(phi) + b*(1-phi)*log((1-phi)) + c*phi*(1-phi)) var e2 = vol0 * d * ( log(phiref/phi)/3 - (phiref/phi)^(2/3) + 1 ) var expected = e1 + e2 var tol = 1e-6 print abs(lh.integrand(m)[0]-expected) print (a.total(m) - 2*sqrt(2)) < 1e-5 // expect: true print (a.integrand(m) - Matrix([ 2*sqrt(2) ])).norm() < 1e-5 // expect: true print (a.gradient(m) - numericalgradient(a, m)).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/length/length2d.morpho ================================================ import meshtools import "../numericalderivatives.morpho" var mb = MeshBuilder() mb.addvertex([-1,-1]) mb.addvertex([1,1]) mb.addedge([0,1]) var m = mb.build() var a = Length() print m // expect: print (a.total(m) - 2*sqrt(2)) < 1e-5 // expect: true print (a.integrand(m) - Matrix([ 2*sqrt(2) ])).norm() < 1e-5 // expect: true print (a.gradient(m) - numericalgradient(a, m)).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/length/length_hessian.morpho ================================================ import meshtools import "../numericalderivatives.morpho" var m = LineMesh(fn (t) [cos(t), sin(t)], -Pi...Pi:Pi/2, closed=true) var a = Length() print abs(a.total(m) - 4*sqrt(2)) < 1e-10 // expect: true print (a.gradient(m)-numericalgradient(a, m)).norm()<1e-6 // expect: true print (Matrix(a.hessian(m))-numericalhessian(a, m, eps=1e-4)).norm() < 1e-2 // expect: true ================================================ FILE: test/functionals/length/length_integrandForElement.morpho ================================================ import meshtools var m = LineMesh(fn(t) [t^2, 0, 0], 0..10) var l = Length() print l.integrandForElement(m, 5) // expect: 11 ================================================ FILE: test/functionals/length/line.mesh ================================================ vertices 1 -1 -1 0 2 1 1 0 edges 1 1 2 ================================================ FILE: test/functionals/linearelasticity/linearelasticity.morpho ================================================ var m = Mesh("stretchsquare.mesh") var mshear = Mesh("shearsquare.mesh") var mscale = Mesh("scalesquare.mesh") var mref = Mesh("square.mesh") var e = LinearElasticity(mref) e.poissonratio = 0.2 print e.reference // expect: print e.grade // expect: 2 print e.integrand(m) // expect: [ 0.625 0.625 ] print e.total(m) // expect: 1.25 print e.integrand(mshear) // expect: [ 0.0303819 0.0303819 ] print e.integrand(mscale) // expect: [ 1.5625 1.5625 ] print e.gradient(mscale) // expect: [ -2.08333 2.08333 -2.08333 2.08333 ] // expect: [ -2.08333 -2.08333 2.08333 2.08333 ] // expect: [ 0 0 0 0 ] ================================================ FILE: test/functionals/linearelasticity/relax.morpho ================================================ import shapeopt var mstretch = Mesh("stretchsquare.mesh") var mshear = Mesh("shearsquare.mesh") var mscale = Mesh("scalesquare.mesh") var mref = Mesh("square.mesh") var e = LinearElasticity(mref) e.poissonratio = 0.4 var s = ShapeOptimizer(mscale) s.addenergy(e) s.quiet = true // Relax the scaled square s.relax(100) print abs(e.total(mscale))<1e-8 // expect: true // Relax the sheared square s.mesh = mshear s.relax(200) print abs(e.total(mshear))<1e-8 // expect: true // Relax the stretched square s.mesh=mstretch s.relax(200) print abs(e.total(mstretch))<1e-8 // expect: true ================================================ FILE: test/functionals/linearelasticity/scalesquare.mesh ================================================ vertices 1 0 0 0 2 2 0 0 3 0 2 0 4 2 2 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/linearelasticity/shearsquare.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0.5 1 0 4 1.5 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/linearelasticity/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/linearelasticity/stretchsquare.mesh ================================================ vertices 1 0 0 0 2 2 0 0 3 0 1 0 4 2 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/linecurvaturesq/gradient.morpho ================================================ // Line curvature Sq gradient import constants import meshtools import "../numericalderivatives.morpho" var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),0], 0...2*Pi:2*Pi/np, closed=true) // Create the manifold var lc = LineCurvatureSq() print lc.total(m) // expect: 6.38774 var grad = lc.gradient(m) var ngrad = numericalgradient(lc, m) print (grad-ngrad).norm()/grad.count() < 1e-6 // expect: true ================================================ FILE: test/functionals/linecurvaturesq/gradient_symm.morpho ================================================ // Line curvature Sq gradient import constants import meshtools import plot import symmetry var np=10 var L=1 var m = LineMesh(fn (t) [t, 0.2*cos(2*Pi*t), 0], -L..L:2/np) //Show(plotmesh(m)) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x+L)<1e-10 || abs(x-L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) // Create the manifold var lc = LineCurvatureSq() var grad = lc.gradient(m) var dim = grad.dimensions() var ngrad = Matrix(dim[0], dim[1]) // Manually calculate the gradient var vert = m.vertexmatrix() var eps = 1e-10 for (i in 0...dim[0]) { for (j in 0...dim[1]) { var v = vert[i, j] vert[i, j] = v + eps var fp = lc.total(m) vert[i, j] = v - eps var fm = lc.total(m) ngrad[i,j] = (fp-fm)/(2*eps) } } var sym=ngrad.column(0)+ngrad.column(np) ngrad.setcolumn(0, sym) ngrad.setcolumn(np, sym) //print (grad-ngrad).norm() print (grad-ngrad).norm()/grad.count() < 1e-5 // expect: true ================================================ FILE: test/functionals/linecurvaturesq/hessian.morpho ================================================ // Line curvature Sq hessian import constants import meshtools import "../numericalderivatives.morpho" var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),0], 0...2*Pi:2*Pi/np, closed=true) // Create the manifold var lc = LineCurvatureSq() print abs(lc.total(m) - 6.38774) < 1e-5 // expect: true var h = lc.hessian(m) var h2 = numericalhessian(lc, m) //print Matrix(h).format("%10.4g") //print h2.format("%10.4g") print (Matrix(h) - h2).norm()/h2.count() < 1e-5 // expect: true ================================================ FILE: test/functionals/linecurvaturesq/linecurvaturesq.morpho ================================================ // Line curvature Sq import constants import meshtools var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),0], 0...2*Pi:2*Pi/np, closed=true) // Create the manifold var lc = LineCurvatureSq() //print lc.integrand(m) print lc.total(m) // expect: 6.38774 print 2*Pi*R*(1/R)^2 // expect: 6.28319 ================================================ FILE: test/functionals/linecurvaturesq/linecurvaturesq.xmorpho ================================================ // Line curvature Sq import constants import meshtools var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),0], 0...2*Pi:2*Pi/np, closed=true) // Create the manifold var lc = LineCurvatureSq() //print lc.integrand(m) print lc.total(m) // expect: 6.38774 print 2*Pi*R*(1/R)^2 // expect: 6.28319 ================================================ FILE: test/functionals/linecurvaturesq/linecurvaturesq_integrandForElement.morpho ================================================ // Line curvature Sq import constants import meshtools var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),0], 0...2*Pi:2*Pi/np, closed=true) var lc = LineCurvatureSq() print abs(lc.integrandForElement(m,1) - 0.638774) < 1e-5 // expect: true lc.integrandonly = true print abs(lc.integrandForElement(m,1)-1.03356) < 1e-5 // expect: true ================================================ FILE: test/functionals/linecurvaturesq/linecurvaturesq_symm.morpho ================================================ // Symmetries import constants import meshtools import plot import symmetry var np=50 var L=1 var m = LineMesh(fn (t) [t, 0.2*cos(Pi*t), 0], -L..L:2/np) //Show(plotmesh(m)) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x+L)<1e-10 || abs(x-L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) // Create the manifold var lc = LineCurvatureSq() print lc.total(m) // expect: 3.16856 lc.integrandonly = true print lc.integrand(m) // expect: [ 3.87607 3.74578 3.38492 2.8701 2.29371 1.73455 1.24313 0.841609 0.531919 0.305208 0.149083 0.0520402 0.00569076 0.00569076 0.0520402 0.149083 0.305208 0.531919 0.841609 1.24313 1.73455 2.29371 2.8701 3.38492 3.74578 3.87607 3.74578 3.38492 2.8701 2.29371 1.73455 1.24313 0.841609 0.531919 0.305208 0.149083 0.0520402 0.00569076 0.00569076 0.0520402 0.149083 0.305208 0.531919 0.841609 1.24313 1.73455 2.29371 2.8701 3.38492 3.74578 0 ] ================================================ FILE: test/functionals/linecurvaturesq/linecurvaturesq_symm.xmorpho ================================================ // Symmetries import constants import meshtools import plot import "symmetry.xmorpho" var np=10 var L=1 var m = LineMesh(fn (t) [t, cos(Pi*t), 0], -L..L:2/np) //Show(plotmesh(m)) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x+L)<1e-10 || abs(x-L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) //print m.connectivitymatrix(0,0) // Create the manifold var lc = LineCurvatureSq() print lc.integrand(m) ================================================ FILE: test/functionals/linecurvaturesq/symmetry.xmorpho ================================================ // Symmetries /** Translations by a constant vector */ class Translate { init (vec) { // Store the translation vector self.vec = vec } transform(posn) { // Forward transformation return posn+self.vec } inverse(posn) { // Inverse transformation return posn-self.vec } } ================================================ FILE: test/functionals/lineintegral/fields_incorrect_args.morpho ================================================ // Check what happens if insufficient fields supplied to LineIntegral import constants import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.1) var f = Field(m, fn (x,y,z) x) // A line integral with a field var lcf = LineIntegral(fn (x, f, g) (f*(1-f))^2, f, List()) // expect error 'IntgrlArgs' ================================================ FILE: test/functionals/lineintegral/fields_insufficient_args.morpho ================================================ // Check what happens if insufficient fields supplied to LineIntegral import constants import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.1) var f = Field(m, fn (x,y,z) x) // A line integral with a field var lcf = LineIntegral(fn (x, f, g) (f*(1-f))^2, f) // expect error 'IntgrlNFlds' ================================================ FILE: test/functionals/lineintegral/fields_toomany_args.morpho ================================================ // Check what happens if insufficient fields supplied to LineIntegral import constants import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.1) var f = Field(m, fn (x,y,z) x) // A line integral with a field var lcf = LineIntegral(fn (x, f, g) (f*(1-f))^2, f, f, f) // expect error 'IntgrlNFlds' ================================================ FILE: test/functionals/lineintegral/lineintegral.morpho ================================================ import constants import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.1) var f = Field(m, fn (x,y,z) x) // A line integral with only spatial dependence var lc = LineIntegral(fn (x) (x[0]*(1-x[0]))^2) print lc.integrand(m) // expect: [ 0.000285333 0.00164533 0.00350533 0.00514533 0.00608533 0.00608533 0.00514533 0.00350533 0.00164533 0.000285333 ] print lc.total(m) // expect: 0.0333333 // A line integral with a field var lcf = LineIntegral(fn (x, f) (f*(1-f))^2, f) print lcf.integrand(m) // expect: [ 0.000285333 0.00164533 0.00350533 0.00514533 0.00608533 0.00608533 0.00514533 0.00350533 0.00164533 0.000285333 ] print lcf.total(m) // expect: 0.0333333 var badLiField = LineIntegral(fn (x,f) (f*(1-f))^2, nil) // expect error 'IntgrlArgs' ================================================ FILE: test/functionals/lineintegral/lineintegral_fieldgradient.morpho ================================================ import meshtools var m = LineMesh(fn (u) [u,0,0], 0..1:0.1) var f = Field(m, fn (x,y,z) x) var ll = LineIntegral(fn (x, f) f^2, f) var grad = ll.fieldgradient(f) var ngrad = f.clone() var eps = 1e-8 var fl, fr for (i in 0...f.count()) { var f0 = f[i] f[i]=f0+eps fr=ll.total(m) f[i]=f0-eps fl=ll.total(m) f[i]=f0 ngrad[i]=(fr-fl)/(2*eps) } print (grad-ngrad).linearize().norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/lineintegral/lineintegral_hessian.morpho ================================================ import meshtools import "../numericalderivatives.morpho" var m = LineMesh(fn (t) [t,0,0], 0..1:1) // A line integral with only spatial dependence var lc = LineIntegral(fn (x) (x[0]*(1-x[0]))^2) var h = lc.hessian(m) var h2 = numericalhessian(lc, m) print (Matrix(h) - h2).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/lineintegral/selection.morpho ================================================ // Test selections for fieldgradient import constants import meshtools var m = LineMesh(fn (t) [t,0,0], 0..1:0.1) var s = Selection(m, fn (x,y,z) x<=0.5) s.addgrade(1) var f = Field(m, fn (x,y,z) x) // A line integral with only spatial dependence var lc = LineIntegral(fn (x, f) (f*(1-f))^2, f) var int = lc.integrand(f, s) var exp = Matrix([[ 0.000285333, 0.00164533, 0.00350533, 0.00514533, 0.00608533, 0, 0, 0, 0, 0 ]]) print (int-exp).norm() < 1e-8 // expect: true print abs(lc.total(f, s) - 0.0166667) < 1e-6 // expect: true var grad=lc.fieldgradient(f, s).linearize() var ngrad = Matrix([ 0.00285333, 0.0136, 0.0186, 0.0164, 0.0094, 0.00164666, 0, 0, 0, 0, 0 ]) print (grad-ngrad).norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/lineintegral/tangent.morpho ================================================ import constants import meshtools var m = LineMesh(fn (t) [cos(t),sin(t),0], 0..Pi:Pi/10) var nn = Field(m, fn (x,y,z) Matrix([1,0,0])) // A line integral with a element call var lc = LineIntegral(fn (x, n) (n.inner(tangent()))^2, nn) var ans = Matrix([[ 0.00765645, 0.0644846, 0.156434, 0.248384, 0.305212, 0.305212, 0.248384, 0.156434, 0.0644846, 0.00765645 ]]) print (lc.integrand(m)-ans).norm() < 1e-5 // expect: true print abs(lc.total(m) - 1.56434) <1e-5 // expect: true ================================================ FILE: test/functionals/lineintegral/tangent2d.morpho ================================================ import constants import meshtools var m = LineMesh(fn (t) [cos(t),sin(t)], 0..Pi:Pi/10) var nn = Field(m, fn (x,y) Matrix([1,0])) // A line integral with a element call var lc = LineIntegral(fn (x, n) (n.inner(tangent()))^2, nn) print lc.integrand(m) // expect: [ 0.00765645 0.0644846 0.156434 0.248384 0.305212 0.305212 0.248384 0.156434 0.0644846 0.00765645 ] print lc.total(m) // expect: 1.56434 ================================================ FILE: test/functionals/linetorsionsq/gradient.morpho ================================================ // Line torsion Sq import constants import meshtools import "../numericalderivatives.morpho" var np=20 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t), t], 0..2*Pi:2*Pi/np, closed=false) // Create the manifold var lc = LineTorsionSq() var grad = lc.gradient(m) var ngrad = numericalgradient(lc, m) print (grad-ngrad).norm()/grad.count() < 1e-6 // expect: true ================================================ FILE: test/functionals/linetorsionsq/gradient_symm.morpho ================================================ // Line torsion Sq import constants import meshtools import symmetry var np=10 var R=1 var L=2 var m = LineMesh(fn (t) [R*cos(2*Pi*t), R*sin(2*Pi*t), t], 0..L:L/np, closed=false) // Show(plotmesh(m, grade=1)) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(z)<1e-10 || abs(z-L)<1e-10) var t = Translate(Matrix([0,0,L])) m.addsymmetry(t, s) // Create the manifold var lc = LineTorsionSq() var grad = lc.gradient(m) var dim = grad.dimensions() var ngrad = Matrix(dim[0], dim[1]) // Manually calculate the gradient var vert = m.vertexmatrix() var eps = 1e-8 for (i in 0...dim[0]) { for (j in 0...dim[1]) { var v = vert[i, j] vert[i, j] = v + eps var fp = lc.total(m) vert[i, j] = v - eps var fm = lc.total(m) ngrad[i,j] = (fp-fm)/(2*eps) } } // Account for symmetry var sym=ngrad.column(0)+ngrad.column(np) ngrad.setcolumn(0, sym) ngrad.setcolumn(np, sym) print (grad-ngrad).norm()/grad.count() < 1e-6 // expect: true ================================================ FILE: test/functionals/linetorsionsq/hessian.morpho ================================================ // Line torsion Sq import constants import meshtools import "../numericalderivatives.morpho" var np=4 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t), t], 0..2*Pi:2*Pi/np, closed=false) // Create the manifold var lc = LineTorsionSq() var h = lc.hessian(m) var h2 = numericalhessian(lc, m) print (Matrix(h) - h2).norm()/h2.count() < 1e-5 // expect: true ================================================ FILE: test/functionals/linetorsionsq/linetorsionsq.morpho ================================================ // Line torsion Sq import constants import meshtools var np=20 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t), t], 0..2*Pi:2*Pi/np, closed=false) // Create the manifold var lc = LineTorsionSq() print lc.total(m) // expect: 2.0282 ================================================ FILE: test/functionals/linetorsionsq/linetorsionsq.xmorpho ================================================ // Line torsion Sq import constants import meshtools var np=10 var R=1 var m = LineMesh(fn (t) [R*cos(t), R*sin(t),t], 0...2*Pi:2*Pi/np, closed=true) /*// Create the manifold var lc = LineCurvatureSq() //print lc.integrand(m) print lc.total(m) // expect: 6.38774 print 2*Pi*R*(1/R)^2 // expect: 6.28319 */ ================================================ FILE: test/functionals/linetorsionsq/linetorsionsq_symm.morpho ================================================ // Line torsion Sq import constants import meshtools import symmetry import plot var np=1000 var R=1 var L=2 var m = LineMesh(fn (t) [R*cos(2*Pi*t), R*sin(2*Pi*t), t], 0..L:L/np, closed=false) // Show(plotmesh(m, grade=1)) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(z)<1e-10 || abs(z-L)<1e-10) var t = Translate(Matrix([0,0,L])) m.addsymmetry(t, s) // Create the torsionsq var lc = LineTorsionSq() print abs(lc.total(m) - 0.306601) < 1e-5 // expect: true ================================================ FILE: test/functionals/meancurvaturesq/gradient.morpho ================================================ // Mean Sq curvature import implicitmesh import plot // Make a sphere var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) var m = impl.build(stepsize=0.25) // Mean Squared curvature var lc = MeanCurvatureSq() var grad = lc.gradient(m) var dim = grad.dimensions() var ngrad = Matrix(dim[0], dim[1]) // Manually calculate the gradient var vert = m.vertexmatrix() var eps = 1e-8 for (i in 0...dim[0]) { for (j in 0...dim[1]) { var v = vert[i, j] vert[i, j] = v + eps var fp = lc.total(m) vert[i, j] = v - eps var fm = lc.total(m) ngrad[i,j] = (fp-fm)/(2*eps) } } print (grad-ngrad).norm()/grad.count() < 1e-4 // expect: true ================================================ FILE: test/functionals/meancurvaturesq/gradient_symm.morpho ================================================ // Mean Sq curvature with a translational symmetry import constants import meshtools import plot import symmetry var L = 2 var m = AreaMesh(fn (t, x) [x, cos(t), sin(t)], 0...2*Pi:2*Pi/10, -L..L:0.5, closed=[true,false]) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x-L)<1e-10 || abs(x+L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) // Mean Squared curvature var lc = MeanCurvatureSq() var grad = lc.gradient(m) var dim = grad.dimensions() var ngrad = Matrix(dim[0], dim[1]) // Manually calculate the gradient var vert = m.vertexmatrix() var eps = 1e-8 for (i in 0...dim[0]) { for (j in 0...dim[1]) { var v = vert[i, j] vert[i, j] = v + eps var fp = lc.total(m) vert[i, j] = v - eps var fm = lc.total(m) ngrad[i,j] = (fp-fm)/(2*eps) } } // Correct for symmetry vertices for (i in 0..9) { var sym=ngrad.column(i)+ngrad.column(80+i) ngrad.setcolumn(i, sym) ngrad.setcolumn(80+i, sym) } print (grad-ngrad).norm()/grad.count() < 1e-4 // expect: true ================================================ FILE: test/functionals/meancurvaturesq/meancurvaturesq.morpho ================================================ // Mean Sq curvature import implicitmesh import plot // Make a sphere var impl = ImplicitMeshBuilder(fn (x,y,z) x^2+y^2+z^2-1) var mesh = impl.build(stepsize=0.25) // Mean Squared curvature var lmc = MeanCurvatureSq() print (lmc.total(mesh)-12.477)<1e-4 // expect: true ================================================ FILE: test/functionals/meancurvaturesq/symm.morpho ================================================ // Mean Sq curvature with a translational symmetry import constants import meshtools import plot import symmetry var L = 2 var m = AreaMesh(fn (t, x) [x, cos(t), sin(t)], 0...2*Pi:2*Pi/40, -L..L:0.125, closed=[true,false]) // Add translational symmetry to the exterior vertices var s = Selection(m, fn (x,y,z) abs(x-L)<1e-10 || abs(x+L)<1e-10) var t = Translate(Matrix([2*L,0,0])) m.addsymmetry(t, s) // Mean Squared curvature var lmc = MeanCurvatureSq() print (lmc.total(m)-6.27673)<1e-5 // expect: true ================================================ FILE: test/functionals/nematic/nematic.morpho ================================================ // Test nematic import meshtools import optimize var m = Mesh("square.mesh") var nn = Field(m, Matrix([1,0,0])) //var psi = 0.2 //nn[2]=Matrix([cos(psi),0,sin(psi)]) //nn[3]=Matrix([cos(psi),0,sin(psi)]) nn[0]=Matrix([1/sqrt(2),1/sqrt(2),0]) nn[2]=Matrix([1/sqrt(2),1/sqrt(2),0]) nn[3]=Matrix([0,1,0]) var lnn = Nematic(nn) print lnn.integrand(m) // expect: [ 0.288706 0.288706 ] print lnn.total(m) // expect: 0.577411 var grad = lnn.gradient(m) var value = Matrix([[ -0.288705, -0.288706, 0.288705, 0.288706], [ -0.288705, 0.288706, 0.288705, -0.288706], [ 0, 0, 0, 0]]) var diff = grad-value print diff.norm()<1e-5 // expect: true var fg=lnn.fieldgradient(nn,m) for (x in fg) print x // expect: [ -0.275634 ] // expect: [ -0.275634 ] // expect: [ 0 ] // expect: [ 0.337521 ] // expect: [ -0.676777 ] // expect: [ 0 ] // expect: [ 0.724366 ] // expect: [ 0.724366 ] // expect: [ 0 ] // expect: [ -0.676777 ] // expect: [ 0.337521 ] // expect: [ 0 ] ================================================ FILE: test/functionals/nematic/nematic3d.morpho ================================================ // Test nematic var tol = 1e-6 var m = Mesh("tetrahedron.mesh") var nn = Field(m, Matrix([1,0,0])) nn[0]=Matrix([1/sqrt(2),1/sqrt(2),0]) nn[2]=Matrix([1/sqrt(2),0,1/sqrt(2)]) nn[3]=Matrix([0,1,0]) var lnn = Nematic(nn) var expected = 0.192873 var tol = 1e-5 print abs(lnn.integrand(m)[0]-expected) // expect: [ 0.235702 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0.235702 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0.235702 ] // expect: [ 0 ] // expect: [ 0 ] print lne.fieldgradient(phi) // expect: // expect: [ -1 ] // expect: [ 0.5 ] // expect: [ 0.5 ] ================================================ FILE: test/functionals/normsq/normsq.morpho ================================================ // Test norm sq import meshtools import shapeopt var m = Mesh("triangle.mesh") var nn = Field(m, Matrix([1,1,1/2])) var lnn = NormSq(nn) print lnn.integrand(m) // expect: [ 2.25 2.25 2.25 ] print lnn.total(m) // expect: 6.75 print lnn.gradient(m) // expect: [ 0 0 0 ] // expect: [ 0 0 0 ] // expect: [ 0 0 0 ] var fg = lnn.fieldgradient(m) for (f in fg) print f // expect: [ 2 ] // expect: [ 2 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 2 ] // expect: [ 1 ] ================================================ FILE: test/functionals/normsq/triangle.mesh ================================================ vertices 1 1 0 0 2 -0.5 0.866025 0 3 -0.5 -0.866025 0 faces 1 1 2 3 ================================================ FILE: test/functionals/numericalderivatives.morpho ================================================ // Numerical derivatives to test functionals fn numericalgradient(func, m, eps=1e-8) { var x=m.vertexmatrix() var grad=x.clone() var dim = x.dimensions()[0] for (i in 0...m.count()) { for (j in 0...dim) { var temp = x[j,i] x[j,i]=temp+eps var fr=func.total(m) x[j,i]=temp-eps var fl=func.total(m) x[j,i]=temp grad[j,i]=(fr-fl)/(2*eps) } } return grad } fn numericalfieldgradient(func, m, f, eps=1e-8) { var x=f.__linearize() var grad=f.clone() var g=grad.__linearize() for (i in 0...x.count()) { var temp = x[i] x[i]=temp+eps var fr=func.total(m) x[i]=temp-eps var fl=func.total(m) x[i]=temp g[i]=(fr-fl)/(2*eps) } return grad } fn _hessianelement(func, m, x, i, j, k, l, dim, eps) { var s = i*dim+k, t = j*dim+l if (i==j && k==l) { var temp=x[k,i] var fc=func.total(m) x[k,i]=temp+eps var fr=func.total(m) x[k,i]=temp-eps var fl=func.total(m) x[k,i]=temp return (fr + fl - 2*fc)/(eps^2) } var tempi=x[k,i], tempj=x[l,j] x[k,i]=tempi+eps x[l,j]=tempj+eps var frr=func.total(m) x[k,i]=tempi-eps x[l,j]=tempj-eps var fll=func.total(m) x[k,i]=tempi-eps x[l,j]=tempj+eps var flr=func.total(m) x[k,i]=tempi+eps x[l,j]=tempj-eps var frl=func.total(m) x[k,i]=tempi x[l,j]=tempj return (frr + fll - flr - frl)/(4*eps^2) } fn numericalhessian(func, m, eps=1e-3) { var x=m.vertexmatrix() var dim = x.dimensions() var hess = Matrix(dim[0]*dim[1], dim[0]*dim[1]) for (i in 0...m.count()) { for (j in 0...m.count()) { for (k in 0...dim[0]) { for (l in 0...dim[0]) { hess[i*dim[0]+k, j*dim[0]+l]=_hessianelement(func, m, x, i, j, k, l, dim[0], eps) } } } } return hess } ================================================ FILE: test/functionals/on_selection.morpho ================================================ // Test functional calculations on a selection var m = Mesh("square.mesh") fn f(x,y,z) { return y>0.5 } var s = Selection(m, f) print s // expect: fn phi(x,y,z) { return x+y+z } fn gradphi(x,y,z) { return Matrix([1,1,1]) } var lp = ScalarPotential(phi, gradphi) print lp.integrand(m) // expect: [ 0 1 1 2 ] print lp.integrand(m, s) // expect: [ 0 0 1 2 ] print lp.total(m) // expect: 4 print lp.total(m, s) // expect: 3 print lp.gradient(m) // expect: [ 1 1 1 1 ] // expect: [ 1 1 1 1 ] // expect: [ 1 1 1 1 ] print lp.gradient(m, s) // expect: [ 0 0 1 1 ] // expect: [ 0 0 1 1 ] // expect: [ 0 0 1 1 ] ================================================ FILE: test/functionals/relax.morpho ================================================ var m = Mesh("cube.mesh") var la = Area() var lv = VolumeEnclosed() print m // expect: print "Area: ${la.total(m)} Volume: ${lv.total(m)}" // expect: Area: 6 Volume: 1 var fa = la.gradient(m) var fv = lv.gradient(m) print fa // expect: [ -1 1 -1 1 -1 1 -1 1 0 0 0 0 0 0 ] // expect: [ -1 -1 1 1 -1 -1 1 1 0 0 0 0 0 0 ] // expect: [ -1 -1 -1 -1 1 1 1 1 0 0 0 0 0 0 ] print fv // expect: [ -0.166667 0.166667 -0.166667 0.166667 -0.166667 0.166667 -0.166667 0.166667 0 0 0 0 -0.333333 0.333333 ] // expect: [ -0.166667 -0.166667 0.166667 0.166667 -0.166667 -0.166667 0.166667 0.166667 0 0 -0.333333 0.333333 0 0 ] // expect: [ -0.166667 -0.166667 -0.166667 -0.166667 0.166667 0.166667 0.166667 0.166667 -0.333333 0.333333 0 0 0 0 ] var ainnerv=fa.inner(fv) var vinnerv=fv.inner(fv) print "${ainnerv}, ${vinnerv}" // expect: 4, 1.33333 var ft=fa.clone() ft.acc(-ainnerv/vinnerv, fv) print ft // expect: [ -0.5 0.5 -0.5 0.5 -0.5 0.5 -0.5 0.5 0 0 0 0 1 -1 ] // expect: [ -0.5 -0.5 0.5 0.5 -0.5 -0.5 0.5 0.5 0 0 1 -1 0 0 ] // expect: [ -0.5 -0.5 -0.5 -0.5 0.5 0.5 0.5 0.5 1 -1 0 0 0 0 ] var v = m.vertexmatrix() v.acc(-0.01,ft) print la.total(m) print lv.total(m) // expect: 5.8833 // expect: 0.999702 ================================================ FILE: test/functionals/relax2.morpho ================================================ /* Test relaxation problem */ var m = Mesh("cube.mesh") var la = Area() var lv = VolumeEnclosed() var vtarget = 1 var vtol = 1e-10 var ftol = 1e-10 var etol = 1e-16 var stepsize = 0.1 var v = m.vertexmatrix() var energy = la.total(m) for (i in 1..40) { var fa = la.gradient(m) var fv = lv.gradient(m) // Subtract component of fa parallel to the constraint var lambda=fa.inner(fv)/fv.inner(fv) var ft=fa.clone() ft.acc(-lambda, fv) // Move downhill v.acc(-stepsize, ft) // Reproject onto volume constraint var dv=vtarget-lv.total(m); do { fv=lv.gradient(m) var normfv=fv.inner(fv) v.acc(dv/normfv,fv) dv=vtarget-lv.total(m) } while (abs(dv)>vtol) // Monitor energy, change in energy and the norm of the force var newenergy=la.total(m) var de = abs(newenergy-energy) var fnorm = sqrt(ft.inner(ft)) // print "Iteration ${i}. Area: ${la.total(m)} Volume: ${lv.total(m)}" // print " delta E: ${de} |force|: ${fnorm}" if (de print a.integrand(m) // expect: [ 0.374999 0.375 0.375 0.375 ] print a.total(m) // expect: 1.5 print a.gradient(m) // expect: [ 0 -0.57735 -0.57735 1.1547 ] // expect: [ 0 -1 1 0 ] // expect: [ 1.22474 -0.408248 -0.408248 -0.408248 ] ================================================ FILE: test/functionals/scalarpotential/scalarpotential_hessian.morpho ================================================ import "../numericalderivatives.morpho" var m = Mesh("tetrahedron.mesh") fn phi(x,y,z) { return x^2+y^2+0.5*z^2 + x*y } var a = ScalarPotential(phi) var h = a.hessian(m) var h2 = numericalhessian(a, m) print (Matrix(h) - h2).norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/scalarpotential/scalarpotential_ndiff.morpho ================================================ var m = Mesh("tetrahedron.mesh") fn phi(x,y,z) { return x^2+y^2+z^2 } fn gradphi(x,y,z) { return Matrix([2*x,2*y,2*z]) } var a = ScalarPotential(phi) var mc = Matrix([ 0.374999, 0.375, 0.375, 0.375 ]) var mi = Matrix(4) for (k in 0...m.count()) { var x = m.vertexposition(k) mi[k]=phi(x[0], x[1], x[2]) } print (mi-mc).norm()<1e-5 // expect: true print a.total(m) // expect: 1.5 var ngrad = [[ 0, -0.57735, -0.57735, 1.1547 ], [ 0, -1, 1, 0 ], [ 1.22474, -0.408248, -0.408248, -0.408248 ]] print (a.gradient(m)-Matrix(Array(ngrad))).norm() < 1e-5 // expect: true ================================================ FILE: test/functionals/scalarpotential/scalarpotential_notcallable.morpho ================================================ // Err. when a level set function isn't callable var a = ScalarPotential(0.4) // expect Error 'SclrPtFnCllbl' ================================================ FILE: test/functionals/scalarpotential/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 volumes 1 1 2 3 4 ================================================ FILE: test/functionals/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/functionals/triangle.mesh ================================================ vertices 1 1 0 0 2 -0.5 0.866025 0 3 -0.5 -0.866025 0 faces 1 1 2 3 ================================================ FILE: test/functionals/volume/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 volumes 1 1 2 3 4 ================================================ FILE: test/functionals/volume/volume.morpho ================================================ var m = Mesh("tetrahedron.mesh") var a = Volume() print m // expect: print a.integrand(m) // expect: [ 0.117851 ] print a.total(m) // expect: 0.117851 print a.gradient(m) // expect: [ 0 -0.0680413 -0.0680413 0.136083 ] // expect: [ 0 -0.117851 0.117851 0 ] // expect: [ 0.144338 -0.0481125 -0.0481125 -0.0481125 ] ================================================ FILE: test/functionals/volume/volume_hessian.morpho ================================================ import "../numericalderivatives.morpho" var m = Mesh("tetrahedron.mesh") var a = Volume() var h = a.hessian(m) var h2 = numericalhessian(a, m) print (Matrix(h) - h2).norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/volumeenclosed/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 faces 1 1 2 3 2 2 3 4 3 3 4 1 4 2 1 4 ================================================ FILE: test/functionals/volumeenclosed/volencl.morpho ================================================ var m = Mesh("tetrahedron.mesh") var a = VolumeEnclosed() print m // expect: print a.integrand(m) // expect: [ 0.0294627 0.0294627 0.0294627 0.0294627 ] print a.total(m) // expect: 0.117851 print a.gradient(m) // expect: [ 0 -0.0680413 -0.0680413 0.136083 ] // expect: [ 0 -0.117851 0.117851 0 ] // expect: [ 0.144338 -0.0481125 -0.0481125 -0.0481125 ] ================================================ FILE: test/functionals/volumeenclosed/volencl_hessian.morpho ================================================ import "../numericalderivatives.morpho" var m = Mesh("tetrahedron.mesh") var a = VolumeEnclosed() var h = a.hessian(m) var h2 = numericalhessian(a, m) print (Matrix(h) - h2).norm() < 1e-6 // expect: true ================================================ FILE: test/functionals/volumeenclosed/volencl_zeroelement.morpho ================================================ import meshtools import optimize var vertexlist = [[0.5,0.5,0.5], [0.5,1.5,0.5], [1.5,1.5,0.5], [1.5,0.5,0.5], [0.5,0.5,1.5],[0.5,1.5,1.5], [1.5,1.5,1.5], [1.5,0.5,1.5]] var facelist = [[0,3,2,1], [0,4,5,1], [0,3,7,4], [2,3,7,6], [4,5,6,7], [1,2,6,5]] var m = PolyhedronMesh(vertexlist, facelist) for (id in 0...m.count()) { var x = m.vertexposition(id) x -= Matrix([1,1,1])/2 m.setvertexposition(id, x) } var problem = OptimizationProblem(m) var la = Area() var lv = VolumeEnclosed() problem.addenergy(la) problem.addconstraint(lv) var opt = ShapeOptimizer(problem, m) opt.relax(1) // expect error 'VolEnclZero' ================================================ FILE: test/functionals/volumeintegral/grad.morpho ================================================ import constants import meshtools // Build unit tetrahedron var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0]) mb.addvertex([0,1,0]) mb.addvertex([0,0,1]) mb.addvolume([0,1,2,3]) var m = mb.build() // Construct a field var f = Field(m, fn (x,y,z) x) print abs(VolumeIntegral(fn (x, f) grad(f).norm(), f).total(m) - 1/6) < 1e-6 // expect: true // Construct a field var g = Field(m, fn (x,y,z) x+y+z) print abs(VolumeIntegral(fn (x, f) grad(f).norm(), g).total(m) - sqrt(3)/6) < 1e-6 // expect: true ================================================ FILE: test/functionals/volumeintegral/testintegrals.morpho ================================================ // Test that integrators give correct estimates for simple integrals import meshtools import plot var a = AreaMesh(fn (u,v) [u,v], 0..1:0.1, 0..1:0.1) var eps = 1e-10 print abs(AreaIntegral(fn (x) x[0]).total(a) - 1/2) < eps // expect: true print abs(AreaIntegral(fn (x) x[0]*x[1]).total(a) - 1/4) < eps // expect: true print abs(AreaIntegral(fn (x) (x[0]*x[1])^2).total(a) - 1/9) < eps // expect: true var pts = [] for (x in 0..1:0.25) for (y in 0..1:0.25) for (z in 0..1:0.25) pts.append(Matrix([x,y,z])) var m = DelaunayMesh(pts) print abs(VolumeIntegral(fn (x) x[0]).total(m) - 1/2) < eps // expect: true print abs(VolumeIntegral(fn (x) x[0]*x[1]*x[2]).total(m) - 1/8) < eps // expect: true print abs(VolumeIntegral(fn (x) (x[0]*x[1]*x[2])^2).total(m) - 1/27) < eps // expect: true ================================================ FILE: test/functionals/volumeintegral/volume_integral.morpho ================================================ // Check volume integral import constants import plot // Build unit tetrahedron var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0]) mb.addvertex([0,1,0]) mb.addvertex([0,0,1]) mb.addvolume([0,1,2,3]) var m = mb.build() // Construct a field var f = Field(m, fn (x,y,z) y) var neval = 0 // Integrand fn q(x, f) { neval+=1 return (sin(2*Pi*x[0]*f)^2) } var lv = VolumeIntegral(q, f) var est = lv.total(m) print abs(est-0.02482305536688542)<1e-8 // expect: true ================================================ FILE: test/help.py ================================================ #!/usr/bin/env python3 # Check for undocumented functions # T J Atherton May 2025 import subprocess import re import colored from colored import stylize morphocmd = "morpho6" # Calls the morpho terminal application and perform a help query by piping into stdin def morphoHelp(query): result = subprocess.run(["echo ? "+query+" | "+morphocmd], shell=True, capture_output=True, text=True) return result.stdout # Check if a help query failed def checkResult(result): return re.search("No help found for",result)==None # Identify methods provided by a morpho class by using introspection def morphoMethods(clss): cmd = "print " + clss + ".respondsto()" result = subprocess.run(["echo \""+cmd+"\" | "+morphocmd ], shell=True, capture_output=True, text=True) out = result.stdout if (re.search("Error",out)==None): # Check the query succeeded return [item.strip() for item in out.strip("[]\n").split(",")] # Separate into list return [] classes = [ "Array", "Bool", "Complex", "Dictionary", "Error", "File", "Float", "Function", "Int", "Invocation", "JSON", "List", "Range", "String", "System", "Tuple" ] results = [] print("--Begin testing---------------------") for clss in classes: methods = morphoMethods(clss) for m in methods: query = clss + "."+m success=checkResult(morphoHelp(query)) results.append([query, success]) countSuccess = 0 for r in results: success = ( stylize("Passed",colored.fg("green")) if r[1] else stylize("Failed",colored.fg("red"))) print(r[0] + ":" + success) if r[1]: countSuccess+=1 print("--End testing-----------------------") print(countSuccess, 'out of', len(results), 'tests passed.') ================================================ FILE: test/if/class_in_else.morpho ================================================ if (true) "ok" else class Foo {} // expect error: 'ExpExpr' ================================================ FILE: test/if/class_in_then.morpho ================================================ if (true) class Foo {} // expect error: 'ExpExpr' ================================================ FILE: test/if/dangling_else.morpho ================================================ // A dangling else binds to the right-most if. if (true) if (false) print "bad" else print "good" // expect: good if (false) if (true) print "bad" else print "bad" // prints nothing ================================================ FILE: test/if/else.morpho ================================================ // Evaluate the 'else' expression if the condition is false. if (true) print "good" else print "bad" // expect: good if (false) print "bad" else print "good" // expect: good // Allow block body. if (false) nil else { print "block" } // expect: block ================================================ FILE: test/if/fn_in_else.morpho ================================================ if (true) "ok" else fn foo() {} // expect error: 'ExpExpr' ================================================ FILE: test/if/fn_in_then.morpho ================================================ if (true) fn foo() {} // expect error: 'ExpExpr' ================================================ FILE: test/if/if.morpho ================================================ // Evaluate the 'then' expression if the condition is true. if (true) print "good" // expect: good if (false) print "bad" // Allow block body. if (true) { print "block" } // expect: block // Assignment in if condition. var a = false if (a = true) print a // expect: true ================================================ FILE: test/if/if_in_method.morpho ================================================ // Detect a bug around returns enclosed in conditionals class Foo { init () { self.a = 1 } method() { var df = self.other() if (df //print Red ================================================ FILE: test/import/as_not_imported.morpho ================================================ // Import modules into a namespace import color as col print Red // expect error 'SymblUndf' ================================================ FILE: test/import/file_not_found.morpho ================================================ import "madeupfile.morpho" // expect error: 'FlNtFnd' ================================================ FILE: test/import/for_clause.morpho ================================================ // Selectively import symbols from a module import color for Color, HueMap var gray = Color(0.5,0.5,0.5) print gray // expect: print gray.red(0.1) // expect: 0.5 print gray.green(0.1) // expect: 0.5 print gray.blue(0.1) // expect: 0.5 ================================================ FILE: test/import/for_clause_restrict.morpho ================================================ // Ensure symbols not included in a for clause are NOT imported import color for Color, HueMap print Red // expect error: 'SymblUndf' ================================================ FILE: test/import/for_string_after_for.morpho ================================================ // Ensure whatever is after for is a symbol import color for "Ooops" // expect error: 'ExpctSymblAftrFr' ================================================ FILE: test/import/import_class_extends.morpho ================================================ // Import a class and extend it import color class Beep is Color { } print Beep(1,1,1) // expect: ================================================ FILE: test/import/import_file.morpho ================================================ fn f(x) { print x } // You can import a file import "importtest.m" f(cat("Hello","Goodbye")) // expect: HelloGoodbye ================================================ FILE: test/import/import_for_class.morpho ================================================ // Import for a class import color for Color print Color(1,1,1) // expect: ================================================ FILE: test/import/import_module.morpho ================================================ // Import everything from a distinct module... import color print Red // expect: print Red.red(0.1) // expect: 1 print Red.green(0.1) // expect: 0 print Red.blue(0.1) // expect: 0 var a = GradientMap() print a.red(0.5) // expect: 0.475 ================================================ FILE: test/import/import_underscore.morpho ================================================ // Import a module with protected symbols import "underscoreimporttest.m" print _b // expect error 'SymblUndf' ================================================ FILE: test/import/importtest.m ================================================ // A simple test library to include fn cat(a,b) { return a + b } ================================================ FILE: test/import/module_not_found.morpho ================================================ import unicorn // expect error: 'MdlNtFnd' ================================================ FILE: test/import/multiple.morpho ================================================ // Import multiple libraries per line import color, graphics, plot print Red // expect: print Graphics // expect: @Graphics print plotmesh // expect: ================================================ FILE: test/import/multiple_as.morpho ================================================ // Can only use as once in an import statement import color as col as foo // expect error: 'ImprtMltplAs' ================================================ FILE: test/import/nested_import_in_module.morpho ================================================ // Nested imports import color for Blue var a = Blue import "nestedimporttest.m" print a // expect: print b // expect: ================================================ FILE: test/import/nestedimporttest.m ================================================ // Test module that imports a module import "nestedimporttest2.m" import color var b = Red ================================================ FILE: test/import/nestedimporttest2.m ================================================ // Test module that imports a module import color for Green var c = Green ================================================ FILE: test/import/one_for_per_line.morpho ================================================ // Lists of symbols after for are parsed as one // Hence using for requires one import per line import color as boo for Red, histogram as hist // expect error 'ImprtMltplAs' ================================================ FILE: test/import/optional.morpho ================================================ // Test module that defines an optional argument fn foo(x, beep="Boop") { print x print beep } ================================================ FILE: test/import/optional_in_module.morpho ================================================ import "optional.morpho" foo(1) // expect: 1 // expect: Boop foo(1, beep="BeepBeep") // expect: 1 // expect: BeepBeep ================================================ FILE: test/import/underscoreimporttest.m ================================================ // Test module with underscore var _b = 2 ================================================ FILE: test/inheritance/constructor.morpho ================================================ class A { init(param) { self.field = param } test() { print self.field } } class B < A {} var b = B("value") b.test() // expect: value ================================================ FILE: test/inheritance/inherit_from_function.morpho ================================================ fn foo() {} class Subclass < foo {} // expect error 'SprNtFnd' ================================================ FILE: test/inheritance/inherit_from_nil.morpho ================================================ // This test works a bit differently to lox because classes are not dynamic in morpho // It still fails, because the symbol Nil doesn't denote a class. var Nil = nil class Foo < Nil {} // expect error: 'SprNtFnd' ================================================ FILE: test/inheritance/inherit_from_number.morpho ================================================ // As for nil, this fails because Number is not a superclass var Number = 123 class Foo < Number {} // expect error: 'SprNtFnd' ================================================ FILE: test/inheritance/inherit_methods.morpho ================================================ class Foo { methodOnFoo() { print "foo" } override() { print "foo" } } class Bar is Foo { methodOnBar() { print "bar" } override() { print "bar" } } var bar = Bar() bar.methodOnFoo() // expect: foo bar.methodOnBar() // expect: bar bar.override() // expect: bar ================================================ FILE: test/inheritance/parenthesized_superclass.morpho ================================================ class Foo {} class Bar < (Foo) {} // expect error: 'SprNmMssng' ================================================ FILE: test/inheritance/set_fields_from_base_class.morpho ================================================ class Foo { foo(a, b) { self.field1 = a self.field2 = b } fooPrint() { print self.field1 print self.field2 } } class Bar < Foo { bar(a, b) { self.field1 = a self.field2 = b } barPrint() { print self.field1 print self.field2 } } var bar = Bar() bar.foo("foo 1", "foo 2") bar.fooPrint() // expect: foo 1 // expect: foo 2 bar.bar("bar 1", "bar 2") bar.barPrint() // expect: bar 1 // expect: bar 2 bar.fooPrint() // expect: bar 1 // expect: bar 2 ================================================ FILE: test/integrate/counter.morpho ================================================ // Class to count number of times a function is evaluated // Used by integration testing suite class EvaluationCounter { init(func) { self.func = func self.pts = [] } eval(x) { self.pts.append(x.clone()) var xx = [] for (u in x) xx.append(u) return apply(self.func, xx) } reset() { self.pts = [] } count() { return self.pts.count() } } ================================================ FILE: test/integrate/embedding.morpho ================================================ // Ensure position interpolation works correctly import meshtools var m1 = LineMesh(fn (t) [t], 0..1:0.5) var m2 = LineMesh(fn (t) [t,t], 0..1:0.5) var m3 = LineMesh(fn (t) [t,t,t], 0..1:0.5) print abs(LineIntegral(fn (x) x[0], method={}).total(m1) - 1/2) < 1e-8 // expect: true print abs(LineIntegral(fn (x) x[0] + x[1], method={}).total(m2) - sqrt(2)) < 1e-8 // expect: true print abs(LineIntegral(fn (x) x[0] + x[1] + x[2], method={}).total(m3) - 3*sqrt(3)/2) < 1e-8 // expect: true ================================================ FILE: test/integrate/integrals1d.morpho ================================================ // Test underlying integration routine import meshtools import "counter.morpho" var mb = MeshBuilder() mb.addvertex([0]) mb.addvertex([1]) mb.addedge([0,1]) var m = mb.build() fn step(x) { if (x<0.3) return 1 return 0 } // List of integrals to evaluate and precalculated exact values var tests = [ [ step, 0.3 ], [ fn (x) sqrt(x), 0.66666666666666666667 ], [ fn (x) x*(1-x), 0.16666666666666666667 ], [ fn (x) 1/sqrt(x), 2 ], [ fn (x) exp(-(x-1/2)^2), 0.92256201282558489751 ] ] var out = [] for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = LineIntegral(c.eval, method={ "rule" : "gauss5" } ).total(m) print (abs((r1-t[1])/t[1]))<1e-5 } // expect: true // expect: true // expect: true // expect: true // expect: true /* for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = LineIntegral(c.eval, method={ "rule" : "gauss5" } ).total(m) var c1 = c.count() c.reset() var r2 = LineIntegral(c.eval).total(m) var c2 = c.count() out.append([r1, abs((r1-t[1])/t[1]), c1, r2, abs((r2-t[1])/t[1]), c2]) } for (res in out) print Array(res) var times = [] var ntimes = 1000 print "Timing tests" for (t in tests) { var start = System.clock() for (i in 1..ntimes) { var r1 = LineIntegral(fn (x) apply(t[0], x[0]), method={ } ).total(m) } var time1 = System.clock()-start start = System.clock() for (i in 1..ntimes) { var r2 = LineIntegral(fn (x) apply(t[0], x[0])).total(m) } var time2 = System.clock() - start times.append([time1, time2]) } for (t in times) print Array(t)*/ ================================================ FILE: test/integrate/integrals2d.morpho ================================================ // Test underlying integration routine import meshtools import "counter.morpho" var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() fn circ(x,y) { var rsq = (x-1/2)^2+(y-1/2)^2 if (rsq<=1/4) return 1 return 0 } // List of integrals to evaluate and precalculated exact values // See ACM Transactions on Mathematical Software, Vol 8, No. 2, June 1982,Pages 210-218 var tests = [ [ fn (x, y) (x^2+3*y^2)^(-1/2), 0.93313202062943577716 ], // [ circ, 0.39269908169872415481 ], [ fn (x, y) exp(sin(x)*cos(y)), 0.691810450659522 ], [ fn (x, y) x^2/(1+x^2), 0.061175426882524345093 ], [ fn (x, y) sin(3*x+6*y), 0.031203084128814462056 ], [ fn (x, y) sin(x+y)^(-1/2), 0.691684734029392 ] ] var out = [] for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = AreaIntegral(c.eval, method={ } ).total(m) print (abs((r1-t[1])/t[1])<1e-6) } // expect: true // expect: true // expect: true // expect: true // expect: true /* for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = AreaIntegral(c.eval, method={ } ).total(m) var c1 = c.count() c.reset() var r2 = AreaIntegral(c.eval).total(m) var c2 = c.count() out.append([r1, abs((r1-t[1])/t[1]), c1, r2, abs((r2-t[1])/t[1]), c2]) } for (res in out) print Array(res) var times = [] var ntimes = 10 print "Timing tests" for (t in tests) { var start = System.clock() for (i in 1..ntimes) { var r1 = AreaIntegral(fn (x) apply(t[0], x[0], x[1]), method={ } ).total(m) } var time1 = System.clock()-start start = System.clock() for (i in 1..ntimes) { var r1 = AreaIntegral(fn (x) apply(t[0], x[0], x[1]) ).total(m) } var time2 = System.clock() - start times.append([time1, time2]) } for (t in times) print Array(t) */ ================================================ FILE: test/integrate/integrals2d_quantities.morpho ================================================ // Test integration using quantities import meshtools var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addface([0,1,2]) var m = mb.build() var f = Field(m, Matrix([0,0])) f[0,1]=Matrix([3,0]) f[0,2]=Matrix([0,6]) print abs(AreaIntegral(fn (x, q) q[0]+q[1], f, method={ } ).total(m) - 1.5) < 1e-6 // expect: true print abs(AreaIntegral(fn (x, q) sin(q[0]+q[1]), f, method={ } ).total(m) - 0.031203084128814462056) < 1e-8 // expect: true ================================================ FILE: test/integrate/integrals3d.morpho ================================================ // Test underlying integration routine import meshtools import "counter.morpho" var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0]) mb.addvertex([0,1,0]) mb.addvertex([0,0,1]) mb.addvolume([0,1,2,3]) var m = mb.build() // List of integrals to evaluate and precalculated values from mathematica var tests = [ [ fn (x, y, z) x*y*z, 1/720 ], [ fn (x, y, z) (x*y*z)^2, 1/45360 ], [ fn (x, y, z) (x*y*z)^3, 1/2217600 ], [ fn (x, y, z) (x*y*z)^4, 1/94594500 ], //[ fn (x, y, z) (x^2+3*y^2+2*z^2)^(-1/2), 0.2638364274 ], [ fn (x, y, z) (1+x^2+3*y^2+2*z^2)^(-1/2), 0.1342458110 ], [ fn (x, y, z) exp(sin(x)*cos(y)*sin(2*z)), 0.1823550880 ], [ fn (x, y, z) x^2/(1+x^2), 0.01324025695 ], [ fn (x, y, z) sin(3*x+6*y+2*z), 0.04353873237 ] //[ fn (x, y, z) sin(x+y+z)^(-1/2), 0.2097719308 ] ] var out = [] for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = VolumeIntegral(c.eval, method={ "rule" : "grundmann3d1" } ).total(m) print (abs((r1-t[1])/t[1])<1e-6) } // expect: true // expect: true // expect: true // expect: true // expect: true // expect: true // expect: true // expect: true /* for (t in tests) { var c = EvaluationCounter(t[0]) var r1 = VolumeIntegral(c.eval, method={ "rule" : "grundmann1" } ).total(m) var c1 = c.count() c.reset() var r2 = VolumeIntegral(c.eval).total(m) var c2 = c.count() out.append([r1, abs((r1-t[1])/t[1]), c1, r2, abs((r2-t[1])/t[1]), c2]) } for (res in out) print Array(res) var times = [] var ntimes = 100 print "Timing tests" for (t in tests) { var start = System.clock() for (i in 1..ntimes) { var r1 = VolumeIntegral(fn (x) apply(t[0], x[0], x[1], x[2]), method={ "rule" : "grundmann1" } ).total(m) } var time1 = System.clock()-start start = System.clock() for (i in 1..ntimes) { var r1 = VolumeIntegral(fn (x) apply(t[0], x[0], x[1], x[2]) ).total(m) } var time2 = System.clock() - start times.append([time1, time2]) } for (t in times) print Array(t) */ ================================================ FILE: test/integrate/method_entry_type.morpho ================================================ // Raise an err if a quadrature rule can't be found import meshtools var m1 = LineMesh(fn (t) [t], 0..1:0.5) var a = LineIntegral(fn (x) x[0], method={ "rule" : 10000 }).total(m1) // expect error 'IntgrtrMthdTyp' ================================================ FILE: test/integrate/method_not_a_dict.morpho ================================================ // Raise an err if the quadrature rule isn't found import meshtools var m1 = LineMesh(fn (t) [t], 0..1:0.5) var a = LineIntegral(fn (x) x[0], method="Foo") // expect error 'IntgrlMthdDct' ================================================ FILE: test/integrate/method_rule_not_found.morpho ================================================ // Raise an err if the quadrature rule isn't found import meshtools var m1 = LineMesh(fn (t) [t], 0..1:0.5) var a = LineIntegral(fn (x) x[0], method={ "rule" : "Foo" }).total(m1) // expect error 'IntgrtrRlNtFnd' ================================================ FILE: test/integrate/method_rule_unavlb.morpho ================================================ // Raise an err if a quadrature rule can't be found import meshtools var m1 = LineMesh(fn (t) [t], 0..1:0.5) var a = LineIntegral(fn (x) x[0], method={ "degree" : 10000 }).total(m1) // expect error 'IntgrtrRlUnavlb' ================================================ FILE: test/integrate/too_many_subdivisions.morpho ================================================ // Test underlying integration routine import meshtools import "counter.morpho" var mb = MeshBuilder() mb.addvertex([0]) mb.addvertex([1]) mb.addedge([0,1]) var m = mb.build() print LineIntegral(fn (x) 1/x[0], method={ } ).total(m) // expect error 'IntgrtrSbdvns' ================================================ FILE: test/invocation/invocation.morpho ================================================ // Invocation veneer class var a = Object() var b = a.respondsto print b // expect: . print b.clss() // expect: @Invocation print b.superclass() // expect: @Object print b.clone()("respondsto") // expect: true var c = Invocation(a, "respondsto") print islist(c()) // expect: true ================================================ FILE: test/invocation/invocation_on_class.morpho ================================================ // Invocation veneer class var a = Object.respondsto print a // expect: @Object. print a.clss() // expect: @Invocation print islist(a()) // expect: true ================================================ FILE: test/json/json.morpho ================================================ // Parse various kinds of JSON //print JSON.parse("]")//{ \"min\": -1.0e+28, \"max\": 1.0e+28 }") //print JSON.parse("\"'\\u03a9'\"") //print JSON.parse("\"'\\uD83E\\uDD8B'\"") /* print "Hello" for (i in 1..100000) { try { //print JSON.parse("[[ 1, 2, 3 ], $#$%$]") print JSON.parse("{ \"Hello\": 1, 2: 3 }") } catch { "JSONObjctKey": "UnrgnzdTkn": } }*/ var dict = JSON.parse("{ \"Hello\" : \"World\", \"Goodbye\" : 2 }") print isdictionary(dict) // expect: true print dict["Hello"] // expect: World print dict["Goodbye"] // expect: 2 var a = JSON.parse(" [ 1213, 1.2, 1.3e-02, 1.4e-03 ] ") for (e in a) print e // expect: 1213 // expect: 1.2 // expect: 0.013 // expect: 0.0014 print JSON.parse(" \"hel\\\"lo\""); // expect: hel"lo print JSON.parse("true") // expect: true print JSON.parse(" \r \t [ true, false, null ]"); // expect: [ true, false, nil ] ================================================ FILE: test/json/testjson.morpho ================================================ // Automated testing of JSON parser against the test suite: // https://github.com/nst/JSONTestSuite // Loads a file and returns the contents as a string fn loadFile(fname) { var f = File(fname, "read") var out = f.readall() f.close() return out } // Runs a test, returnineg true if successful fn test(src) { var success=false try { JSON.parse(src) success=true } catch { "DctSprtr": "DctTrmntr": "ExpExpr": "JSONExtrnsTkn": "JSONObjctKey": "InvldUncd": "UnescpdCtrl": "JSONBlnkElmnt": "JSONNmbrFrmt": "UntrmStrng": "UnrgnzdTkn": "ValRng": "MssngComma": "MssngSqBrc": "StrEsc": "RcrsnLmt": } return success } // Checks if the test passed by looking at the filename, which should begin // with 'y' - parse must succeed // 'i' - parser can either accept or reject the contents // 'n' - parse must raise an error fn passq(fname, result) { var expect = fname[0] if (fname[0]=="y" && result==true) return "Passed" if (fname[0]=="n" && result==false) return "Passed" if (fname[0]=="i") return "Passed" return "Failed" } // Call this script with a folder of test files to examine var args = System.arguments() if (args.count()==0) System.exit() var folder = args[0] if (Folder.isfolder(folder)) { var files = Folder.contents(folder) print "Analyzing ${files.count()} files." var pass=0 for (fname, k in files) { // Run tests and report results var src = loadFile(folder+"/"+fname) var outcome = test(src) var passed = passq(fname, outcome) if (passed=="Passed") pass+=1 if (passed=="Failed") print "${k}: ${passed} [${outcome}] [${fname}] ${src}" } print "Test suite complete: ${pass}/${files.count()} passed." } ================================================ FILE: test/json/tostring.morpho ================================================ // Output morpho values as JSON var vals = [1, 1.5, true, false, nil, "hello world", [1,2,3]] for (v in vals) print JSON.tostring(v) // expect: 1 // expect: 1.5 // expect: true // expect: false // expect: null // expect: "hello world" // expect: [1,2,3] var str = ["\b", "\f", "\n", "\t", "\r", "\u0001", "👋 world"] for (v in str) print JSON.tostring(v) // expect: "\b" // expect: "\f" // expect: "\n" // expect: "\t" // expect: "\r" // expect: "\u0001" // expect: "👋 world" var dict = { "Foo": "Bar", "Boo": 2, "Woo": true, "false": true } var dstr = JSON.tostring(dict) var dict2 = JSON.parse(dstr) for (key in dict.keys()) { print dict2[key] == dict[key] } // expect: true // expect: true // expect: true // expect: true ================================================ FILE: test/junk/ctrl.morpho ================================================ { // expect error 'UnrgnzdTkn' ================================================ FILE: test/junk/makectrl.xmorpho ================================================ var str = "{\x04" var f = File("ctrl.morpho", "w") f.write(str) f.close() ================================================ FILE: test/junk/oeq0.morpho ================================================ o=0 // expect error 'SymblUndf' ================================================ FILE: test/list/empty_index.morpho ================================================ // Ensure blank indices are caught var a = [1,2,3] print a[] // expect error 'MssngIndx' ================================================ FILE: test/list/index_out_of_bounds.morpho ================================================ // Check indices out of bounds var lst = [ 1,2,3 ] print lst[0] // expect: 1 print lst[1] // expect: 2 print lst[6] // expect error 'IndxBnds' ================================================ FILE: test/list/inherited.morpho ================================================ // Test List methods inherited from Object var lst = [ 1, 2 ] print lst.respondsto("contains") // expect: true print islist(lst.respondsto()) // expect: true print lst.clss() // expect: @List print lst.superclass() // expect: @Object print islist(lst.invoke("respondsto")) // expect: true print lst.has("a") // expect: false print lst.count() // expect: 2 ================================================ FILE: test/list/initializer_with_enumerable.morpho ================================================ // Initialize a list with a range print List(1..5) // expect: [ 1, 2, 3, 4, 5 ] var a = [0...1:0.2] print a // expect: [ 0, 0.2, 0.4, 0.6, 0.8 ] ================================================ FILE: test/list/initializer_with_vars.morpho ================================================ // Initialize a list with vars for (i in 1..3) { var a = [ i-1, i, i+1] print a } // expect: [ 0, 1, 2 ] // expect: [ 1, 2, 3 ] // expect: [ 2, 3, 4 ] ================================================ FILE: test/list/insert.morpho ================================================ // Check insert method var a = [ 1, 2 ] a.insert(0, 0) print a // expect: [ 0, 1, 2 ] a.insert(1, 0.5) print a // expect: [ 0, 0.5, 1, 2 ] a.insert(2, 0.6, 0.7, 0.8, 0.9) print a // expect: [ 0, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2 ] a.insert(-2, 1.5) print a // expect: [ 0, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.5, 2 ] a.insert(-1, 2.5) print a // expect: [ 0, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.5, 2, 2.5 ] var b = [] for (i in 1..20) b.insert(0, i) print b // expect: [ 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ] ================================================ FILE: test/list/ismember.morpho ================================================ // Check ismember method on a list print [1,2,3].ismember(1) // expect: true print [1,2,3].ismember(5) // expect: false var a = Object() var b = Object() var c = Object() print [a,b].ismember(a) // expect: true print [a,b].ismember(c) // expect: false ================================================ FILE: test/list/join.morpho ================================================ // Join lists print [ 1, 2 ].join([ 3, 4 ]) // expect: [ 1, 2, 3, 4 ] ================================================ FILE: test/list/list_from_tuple.morpho ================================================ // Convert a Tuple into a List var lst = Tuple(1,2,3) print List(lst) // expect: [ 1, 2, 3 ] ================================================ FILE: test/list/nonint_index.mopho ================================================ var a = [1,2,3] print a[[1]] // expect: error NonintIndex ================================================ FILE: test/list/order.morpho ================================================ // Test List order method import constants var lst = [ 3,2,1 ] print lst // expect: [ 3, 2, 1 ] print lst.order() // expect: [ 2, 1, 0 ] print lst.sort() // // expect: nil print lst // expect: [ 1, 2, 3 ] var a = [] for (i in 0..2*Pi:Pi/8) a.append(cos(i)*(1+1e-10*i)) // add a bit to avoid equal values for ambiguity in ordering print a // expect: [ 1, 0.92388, 0.707107, 0.382683, 6.12323e-17, -0.382683, -0.707107, -0.92388, -1, -0.92388, -0.707107, -0.382683, -1.83697e-16, 0.382683, 0.707107, 0.92388, 1 ] var o = a.order() print o // expect: [ 8, 9, 7, 10, 6, 11, 5, 12, 4, 3, 13, 2, 14, 1, 15, 0, 16 ] var y = [] for (i in 0...a.count()) y.append(a[o[i]]) print y // expect: [ -1, -0.92388, -0.92388, -0.707107, -0.707107, -0.382683, -0.382683, -1.83697e-16, 6.12323e-17, 0.382683, 0.382683, 0.707107, 0.707107, 0.92388, 0.92388, 1, 1 ] a.sort() print a // expect: [ -1, -0.92388, -0.92388, -0.707107, -0.707107, -0.382683, -0.382683, -1.83697e-16, 6.12323e-17, 0.382683, 0.382683, 0.707107, 0.707107, 0.92388, 0.92388, 1, 1 ] ================================================ FILE: test/list/pop_bounds.morpho ================================================ // Pop a negative number var a = [1,2,3,4,5,6,7,8,9,10] a.pop(50) // expect error 'IndxBnds' ================================================ FILE: test/list/pop_index.morpho ================================================ // Check pop method with index on a list var a = [ 1, 2, 3, 4, 5, 6 ] print a.pop(0) // expect: 1 print a // expect: [ 2, 3, 4, 5, 6 ] print a.pop(2) // expect: 4 print a // expect: [ 2, 3, 5, 6 ] print a.pop(3) // expect: 6 print a // expect: [ 2, 3, 5 ] print a.pop(1) // expect: 3 print a // expect: [ 2, 5 ] print a.pop() // expect: 5 print a // expect: [ 2 ] print a.pop() // expect: 2 print a // expect: [ ] print a.pop() // expect: nil ================================================ FILE: test/list/pop_negative.morpho ================================================ // Pop a negative number var a = [1,2,3,4,5,6,7,8,9,10] print a.pop(-1) // expect: 10 for (i in 1..100) { a.append(i) a.pop(-1) } print "ok" // expect: ok ================================================ FILE: test/list/remove.morpho ================================================ // Check remove method on a list var a = [ 1, 2, 3 ] a.remove(2) print a // expect: [ 1, 3 ] var b = [ "Ham", "Sausage", "Eggs"] b.remove("Eggs") print b // expect: [ Ham, Sausage ] a.remove(4) // expect error: 'EntryNtFnd' ================================================ FILE: test/list/reverse.morpho ================================================ // Reverse a list // Odd length var lst = [ 1, 2, 3 ] lst.reverse() print lst // expect: [ 3, 2, 1 ] var lst = [ 1, 2, 3, 4 ] lst.reverse() print lst // expect: [ 4, 3, 2, 1 ] ================================================ FILE: test/list/roll.morpho ================================================ // Roll a list var lst = [ 1,2,3 ] for (n in -4..4) { print lst.roll(n) } // expect: [ 2, 3, 1 ] // expect: [ 1, 2, 3 ] // expect: [ 3, 1, 2 ] // expect: [ 2, 3, 1 ] // expect: [ 1, 2, 3 ] // expect: [ 3, 1, 2 ] // expect: [ 2, 3, 1 ] // expect: [ 1, 2, 3 ] // expect: [ 3, 1, 2 ] ================================================ FILE: test/list/sort_with_function.morpho ================================================ // Test List sort with function fn cmp(a, b) { return -(a-b) } var lst = [ 4, 3, 2, 7, 8, 1, 10, 9, 6, 5 ] lst.sort(cmp) print lst // expect: [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ] ================================================ FILE: test/list/sort_with_function_flt.morpho ================================================ // Test List sort with function fn cmp(a, b) { return -(a-b) } var lst = [ 4.5, 3.5, 2.5, 7.5, 5.5, 8.5, 1.5, 10.5, 9.5, 6.5 , 5.5 ] lst.sort(cmp) print lst // expect: [ 10.5, 9.5, 8.5, 7.5, 6.5, 5.5, 5.5, 4.5, 3.5, 2.5, 1.5 ] ================================================ FILE: test/list/syntax.morpho ================================================ // Test List veneer class var lst = [ 1,2,3 ] print lst[0] // expect: 1 print lst[1] // expect: 2 print lst[2] // expect: 3 print lst.count() // expect: 3 // Empty list var a = [] print a // expect: [ ] for (i in 1..5) a.append(i) print a // expect: [ 1, 2, 3, 4, 5 ] print a.pop() // expect: 5 print a.pop() // expect: 4 print a.count() // expect: 3 print a // expect: [ 1, 2, 3 ] a.append("Hello") print a // expect: [ 1, 2, 3, Hello ] ================================================ FILE: test/list/tuples.morpho ================================================ // Generate sets and tuples from a list var lst = [ 1,2,3 ] for (x in lst.sets(2)) print x // expect: [ 1, 2 ] // expect: [ 1, 3 ] // expect: [ 2, 3 ] var lst = [ 1,2,3,4 ] for (x in lst.sets(3)) print x // expect: [ 1, 2, 3 ] // expect: [ 1, 2, 4 ] // expect: [ 1, 3, 4 ] // expect: [ 2, 3, 4 ] for (x in lst.sets(5)) print x // expect: [ 1, 2, 3, 4 ] for (x in [ 1, 2, 3].tuples(2)) print x // expect: [ 1, 1 ] // expect: [ 1, 2 ] // expect: [ 1, 3 ] // expect: [ 2, 1 ] // expect: [ 2, 2 ] // expect: [ 2, 3 ] // expect: [ 3, 1 ] // expect: [ 3, 2 ] // expect: [ 3, 3 ] ================================================ FILE: test/logical/and.morpho ================================================ // Note: These tests implicitly depend on ints being truthy. // Return the first non-true argument. print false and 1 // expect: false print true and 1 // expect: 1 print 1 and 2 and false // expect: false // Return the last argument if all are true. print 1 and true // expect: true print 1 and 2 and 3 // expect: 3 // Short-circuit at the first false argument. var a = "before" var b = "before" (a = true) and (b = false) and (a = "bad") print a // expect: true print b // expect: false ================================================ FILE: test/logical/and_truth.morpho ================================================ // False and nil are false. print false and "bad" // expect: false print nil and "bad" // expect: nil // Everything else is true. print true and "ok" // expect: ok print 0 and "ok" // expect: ok print "" and "ok" // expect: ok // Everything else is true. print true and 0.1 // expect: 0.1 print 0 and Sparse(1) // expect: [ 0 ] print "" and List() // expect: [ ] ================================================ FILE: test/logical/or.morpho ================================================ // Note: These tests implicitly depend on ints being truthy. // Return the first true argument. print 1 or true // expect: 1 print false or 1 // expect: 1 print false or false or true // expect: true // Return the last argument if all are false. print false or false // expect: false print false or false or false // expect: false // Short-circuit at the first true argument. var a = "before" var b = "before" (a = false) or (b = true) or (a = "bad") print a // expect: false print b // expect: true ================================================ FILE: test/logical/or_truth.morpho ================================================ // False and nil are false. print false or "ok" // expect: ok print nil or "ok" // expect: ok // Everything else is true. print true or "ok" // expect: true print 0 or "ok" // expect: 0 print "s" or "ok" // expect: s ================================================ FILE: test/math/isinf_isnan_isfinite.morpho ================================================ print isnan(1) // expect: false print isnan(0/0) // expect: true print isnan(1.0) // expect: false print isnan(-1.0) // expect: false print isnan(0) // expect: false print isnan(1/0) // expect: false print isinf(1/0) // expect: true print isinf(-(1/0)) // expect: true print isinf(0) // expect: false print isinf(0.0) // expect: false print isinf(2) // expect: false print isinf(0/0) // expect: false print isfinite(1/0) // expect: false print isfinite(-(1/0)) // expect: false print isfinite(0) // expect: true print isfinite(0.0) // expect: true print isfinite(2) // expect: true print isfinite(0/0) // expect: false ================================================ FILE: test/math/math.morpho ================================================ // Test math functions print exp(2) // expect: 7.38906 print log(10) // expect: 2.30259 print log10(10) // expect: 1 print sin(1.2) // expect: 0.932039 print cos(0.2) // expect: 0.980067 print sqrt(2) // expect: 1.41421 print floor(3.5) // expect: 3 print ceil(3.5) // expect: 4 print arctan(-0.2,-0.5) // expect: -1.9513 print abs(-1.2) // expect: 1.2 print sin("Hello") // expect error 'ExpctArgNm' ================================================ FILE: test/math/sign.morpho ================================================ print sign(-1) // expect: -1 print sign(0) // expect: 0 print sign(1) // expect: 1 print sign(2.0) // expect: 1 print sign(-2.0) // expect: -1 print sign(0.0) // expect: 0 print sign(-(1/0)) // expect: -1 print sign(1/0) // expect: 1 print sign(0/0) // expect: 0 ================================================ FILE: test/matrix/Lnorm.morpho ================================================ // Compute norms other than L2 import constants var a = Matrix([1,2,3]) print a.norm(1) // expect: 6 print abs(a.norm(2) - 3.74166) < 1e-4 // expect: true print abs(a.norm(3) - 3.30193) < 1e-4 // expect: true print a.norm(Inf) // expect: 3 ================================================ FILE: test/matrix/arith_scalar.morpho ================================================ // Add/subtract a scalar var a = Matrix([[1,2], [3,4]]) print a-1 // expect: [ 0 1 ] // expect: [ 2 3 ] print 1-a // expect: [ 0 -1 ] // expect: [ -2 -3 ] print a+1 // expect: [ 2 3 ] // expect: [ 4 5 ] print 1+a // expect: [ 2 3 ] // expect: [ 4 5 ] ================================================ FILE: test/matrix/arithmetic.morpho ================================================ // Matrix arithmetic var a = Matrix([[1, 2], [3, 4]]) var b = Matrix([[0, 1], [1, 0]]) print "A+B:" print a+b // expect: A+B: // expect: [ 1 3 ] // expect: [ 4 4 ] print "A-B:" print a-b // expect: A-B: // expect: [ 1 1 ] // expect: [ 2 4 ] print "A*B:" print a*b // expect: A*B: // expect: [ 2 1 ] // expect: [ 4 3 ] var c = Matrix([[1,2,3], [4,5,6]]) var d = Matrix([[1,2], [3,4], [5,6]]) print "C*D:" print c*d // expect: C*D: // expect: [ 22 28 ] // expect: [ 49 64 ] ================================================ FILE: test/matrix/assign.morpho ================================================ // Assign var a = Matrix([[1,2], [3,4]]) var b = Matrix(2,2) print b // expect: [ 0 0 ] // expect: [ 0 0 ] b.assign(a) print b // expect: [ 1 2 ] // expect: [ 3 4 ] var c = Matrix(1,2) b.assign(c) // expect error 'MtrxIncmptbl' ================================================ FILE: test/matrix/blockmatrix_constructor.morpho ================================================ // Block matrix constructor with single list print Matrix([Matrix(2)]) // expect error 'MtrxInvldInit' ================================================ FILE: test/matrix/concatenate.morpho ================================================ // Concatenate matrices together var a = Matrix([[0,1], [1,0]]) var b = Matrix([2,3]) print a // expect: [ 0 1 ] // expect: [ 1 0 ] print b // expect: [ 2 ] // expect: [ 3 ] var c = Matrix([[a, b], [b.transpose(), 0]]) print c // expect: [ 0 1 2 ] // expect: [ 1 0 3 ] // expect: [ 2 3 0 ] var c = Matrix([[a, 0, b], [0, a, b], [b.transpose(), b.transpose(), 0]]) print c // expect: [ 0 1 0 0 2 ] // expect: [ 1 0 0 0 3 ] // expect: [ 0 0 0 1 2 ] // expect: [ 0 0 1 0 3 ] // expect: [ 2 3 2 3 0 ] var c = Matrix([[a, b], [b, 0]]) // expect error 'MtrxIncmptbl' ================================================ FILE: test/matrix/concatenate_sparse.morpho ================================================ // Concatenate matrices together var a = Sparse([[0,1,1],[1,0,1]]) var b = Matrix([2,3]) print a // expect: [ 0 1 ] // expect: [ 1 0 ] var c = Matrix([[a, b], [b.transpose(), 0]]) print c // expect: [ 0 1 2 ] // expect: [ 1 0 3 ] // expect: [ 2 3 0 ] var c = Matrix([[a, 0, b], [0, a, b], [b.transpose(), b.transpose(), 0]]) print c // expect: [ 0 1 0 0 2 ] // expect: [ 1 0 0 0 3 ] // expect: [ 0 0 0 1 2 ] // expect: [ 0 0 1 0 3 ] // expect: [ 2 3 2 3 0 ] var c = Matrix([[a, b], [b, 0]]) // expect error 'MtrxIncmptbl' ================================================ FILE: test/matrix/dimensions.morpho ================================================ // Solution of linear systems var a = Matrix([[0.8, -0.4], [0.4, 0.8]]) var v = Matrix([0.2, 0.3]) print a.dimensions() // expect: [ 2, 2 ] print v.dimensions() // expect: [ 2, 1 ] ================================================ FILE: test/matrix/eigensystem.morpho ================================================ // Check eigenvalues works on a problematic matrix var a = Matrix([[ -0.083929, 0.102945, 0.213477 ], [ 0.102945, -0.108697, 0.189335 ], [ 0.213477, 0.189335, 0.192626 ]]) print a.eigensystem() // expect: [ , ] ================================================ FILE: test/matrix/eigenvalues.morpho ================================================ // Transpose var a = Matrix([[1,-1,0], [-1,1,0], [0,0,1]]) var ev = a.eigenvalues() ev.sort() print ev // expect: [ 0, 1, 2 ] var b = Matrix([[1,2,0], [-2,1,0], [0,0,1]]) var ev = b.eigenvalues() for (e in ev) print e // expect: 1 + 2im // expect: 1 - 2im // expect: 1 print a // ensure a is not overwritten // expect: [ 1 -1 0 ] // expect: [ -1 1 0 ] // expect: [ 0 0 1 ] var es = a.eigensystem() print es[0] // expect: [ 2, 0, 1 ] print es[1] // expect: [ 0.707107 0.707107 0 ] // expect: [ -0.707107 0.707107 0 ] // expect: [ 0 0 1 ] ================================================ FILE: test/matrix/format.morpho ================================================ // Print matrices with formatted output var a = Matrix([[1/2,1/3], [1/3,1/2]]) print a.format("%5.3g") // expect: [ 0.5 0.333 ] // expect: [ 0.333 0.5 ] ================================================ FILE: test/matrix/get_column.morpho ================================================ // Transpose var a = Matrix([[1,2,3],[4,5,6],[7,8,9],[10,11,12]]) //print a + a print a.column(0) // expect: [ 1 ] // expect: [ 4 ] // expect: [ 7 ] // expect: [ 10 ] print a.column(2) // expect: [ 3 ] // expect: [ 6 ] // expect: [ 9 ] // expect: [ 12 ] print a.column(10) // expect Error 'MtrxBnds' ================================================ FILE: test/matrix/identity.morpho ================================================ // Identity matrix var a = IdentityMatrix(2) print a // expect: [ 1 0 ] // expect: [ 0 1 ] a = IdentityMatrix(3) print a // expect: [ 1 0 0 ] // expect: [ 0 1 0 ] // expect: [ 0 0 1 ] a = IdentityMatrix() // expect error 'MtrxIdnttyCns' ================================================ FILE: test/matrix/incompatible_add.morpho ================================================ // Try to add incompatible matrices var a = Matrix([[1, 2, 1], [3, 4, 1]]) var b = Matrix([[0, 1], [1, 0]]) print a+b // expect Error: 'MtrxIncmptbl' ================================================ FILE: test/matrix/incompatible_mul.morpho ================================================ // Try to multiply incompatible matrices var a = Matrix([[1, 2, 1], [3, 4, 1]]) var b = Matrix([[0, 1], [1, 0]]) print a*b // expect Error: 'MtrxIncmptbl' ================================================ FILE: test/matrix/incompatible_sub.morpho ================================================ // Try to subtract incompatible matrices var a = Matrix([[1, 2, 1], [3, 4, 1]]) var b = Matrix([[0, 1], [1, 0]]) print a*b // expect Error: 'MtrxIncmptbl' ================================================ FILE: test/matrix/inherited.morpho ================================================ // Test Matrix methods inherited from Object var err = Matrix([1,2,3]) print err.respondsto("respondsto") // expect: true print islist(err.respondsto()) // expect: true print err.clss() // expect: @Matrix print err.superclass() // expect: @Object print islist(err.invoke("respondsto")) // expect: true print err.has("a") // expect: false ================================================ FILE: test/matrix/initializer.morpho ================================================ // Initialize different matrices var a = Matrix([[1]]) print a // expect: [ 1 ] var b = Matrix([ [1, 2], [3, 4]]) print b // expect: [ 1 2 ] // expect: [ 3 4 ] var c = Matrix([ [1, 2, 3], [4, 5, 6], [7, 8, 9]]) print c // expect: [ 1 2 3 ] // expect: [ 4 5 6 ] // expect: [ 7 8 9 ] a = Matrix(2,2) a[0,0] = 1.0 a[1,1] = 1.0 print a // expect: [ 1 0 ] // expect: [ 0 1 ] var v = Matrix([0.2, 0.3]) print v // expect: [ 0.2 ] // expect: [ 0.3 ] var w = Matrix(b) print w // expect: [ 1 2 ] // expect: [ 3 4 ] var w = Matrix([[1,[2,3]]]) // expect Error 'MtrxInvldInit' print w ================================================ FILE: test/matrix/inverse.morpho ================================================ // Testing the inverse method for the Morpho matrix class var a = 4, b = 5.7, c = 2, d = 2.4 var m = Matrix([[a, b], [c, d]]) var mi = m.inverse() var det = a*d - b*c print ((mi[0,0] - d/det)<1e-8) // expect: true print ((mi[0,1] + b/det)<1e-8) // expect: true print ((mi[1,0] + c/det)<1e-8) // expect: true print ((mi[1,1] - a/det)<1e-8) // expect: true var m = Matrix([[1,0,0],[0,1,0],[0,0,0]]) print m.inverse() // expect error 'MtrxSnglr' ================================================ FILE: test/matrix/linearsolve.morpho ================================================ // Solution of linear systems var a = Matrix([[0.8, -0.4], [0.4, 0.8]]) var v = Matrix([0.2, 0.3]) print a // expect: [ 0.8 -0.4 ] // expect: [ 0.4 0.8 ] print v/a // expect: [ 0.35 ] // expect: [ 0.2 ] print a/a // expect: [ 1 0 ] // expect: [ 0 1 ] print a // expect: [ 0.8 -0.4 ] // expect: [ 0.4 0.8 ] var b = Matrix([[3, 4], [6, 8]]) print v/b // expect error 'MtrxSnglr' ================================================ FILE: test/matrix/linearsolve3x3.morpho ================================================ // Solution of linear systems var a = Matrix([[1,2,2], [4,5,6], [7,8,9]]) var v = Matrix([1,2,3]) var w=v/a print a*w // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/matrix/negate.morpho ================================================ // Transpose var a = Matrix([[1,2], [3,4]]) print -a // expect: [ -1 -2 ] // expect: [ -3 -4 ] ================================================ FILE: test/matrix/nonnum_indices.morpho ================================================ // Access with non numerical indices var a = Matrix([[1,2], [3,4], [5,6]]) print a["Hello", "Squirrel"] // expect error: 'MtrxInvldIndx' ================================================ FILE: test/matrix/nonnum_initializer.morpho ================================================ // Non numerical initializer var m = Matrix([[1,2], [3,"oops"]]) // expect error: 'MtrxInvldInit' ================================================ FILE: test/matrix/norm.morpho ================================================ // Transpose var a = Matrix([1,1,1]) print a.norm() // expect: 1.73205 ================================================ FILE: test/matrix/outer.morpho ================================================ // Outer product var a = Matrix([1,2,3]) print a.outer(a) // expect: [ 1 2 3 ] // expect: [ 2 4 6 ] // expect: [ 3 6 9 ] var b = Matrix([1,2]) print a.outer(b) // expect: [ 1 2 ] // expect: [ 2 4 ] // expect: [ 3 6 ] ================================================ FILE: test/matrix/reshape.morpho ================================================ // Reshape matrices var a = Matrix([[1,4], [2,5], [3,6]]) print a // expect: [ 1 4 ] // expect: [ 2 5 ] // expect: [ 3 6 ] a.reshape(1,6) print a // expect: [ 1 2 3 4 5 6 ] a.reshape(2,3) print a // expect: [ 1 3 5 ] // expect: [ 2 4 6 ] a.reshape(6,1) print a // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] // expect: [ 4 ] // expect: [ 5 ] // expect: [ 6 ] a.reshape(3,2) print a // expect: [ 1 4 ] // expect: [ 2 5 ] // expect: [ 3 6 ] a.reshape(3,3) // expect error 'MtrxIncmptbl' ================================================ FILE: test/matrix/roll.morpho ================================================ // Roll a list var m = Matrix([ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]]) print m // expect: [ 1 2 3 4 ] // expect: [ 5 6 7 8 ] // expect: [ 9 10 11 12 ] // expect: [ 13 14 15 16 ] print m.roll(1,0) // expect: [ 13 14 15 16 ] // expect: [ 1 2 3 4 ] // expect: [ 5 6 7 8 ] // expect: [ 9 10 11 12 ] print m.roll(-1,0) // expect: [ 5 6 7 8 ] // expect: [ 9 10 11 12 ] // expect: [ 13 14 15 16 ] // expect: [ 1 2 3 4 ] print m.roll(-2,0) // expect: [ 9 10 11 12 ] // expect: [ 13 14 15 16 ] // expect: [ 1 2 3 4 ] // expect: [ 5 6 7 8 ] print m.roll(1,1) // expect: [ 4 1 2 3 ] // expect: [ 8 5 6 7 ] // expect: [ 12 9 10 11 ] // expect: [ 16 13 14 15 ] print m.roll(-1,1) // expect: [ 2 3 4 1 ] // expect: [ 6 7 8 5 ] // expect: [ 10 11 12 9 ] // expect: [ 14 15 16 13 ] var a = Matrix([ [ -1, -1, -1, -1, -1 ], [ -0.5, -0.5, -0.5, -0.5, -0.5 ], [ 0, 0, 0, 0, 0 ], [ 0.5, 0.5, 0.5, 0.5, 0.5 ], [ 1, 1, 1, 1, 1 ] ]) print a.roll(-1, 0) // expect: [ -0.5 -0.5 -0.5 -0.5 -0.5 ] // expect: [ 0 0 0 0 0 ] // expect: [ 0.5 0.5 0.5 0.5 0.5 ] // expect: [ 1 1 1 1 1 ] // expect: [ -1 -1 -1 -1 -1 ] ================================================ FILE: test/matrix/scalar_mul.morpho ================================================ // Transpose var a = Matrix([[1,2], [3,4]]) print 2*a // expect: [ 2 4 ] // expect: [ 6 8 ] print a*2 // expect: [ 2 4 ] // expect: [ 6 8 ] print a/2 // expect: [ 0.5 1 ] // expect: [ 1.5 2 ] ================================================ FILE: test/matrix/set_column.morpho ================================================ // Transpose var a = Matrix(3,3) a.setcolumn(1, Matrix([1,2,3])) print a // expect: [ 0 1 0 ] // expect: [ 0 2 0 ] // expect: [ 0 3 0 ] ================================================ FILE: test/matrix/trace.morpho ================================================ // Transpose var a = Matrix([[1,2], [3,4]]) print a.trace() // expect: 5 var b = Matrix([[1, 2]]) print b.trace() // expect error 'MtrxNtSq' ================================================ FILE: test/matrix/transpose.morpho ================================================ // Transpose var a = Matrix([[1,2], [3,4], [5,6]]) print a.transpose() // expect: [ 1 3 5 ] // expect: [ 2 4 6 ] ================================================ FILE: test/mesh/addgradeerror.morpho ================================================ var a = Mesh("tetrahedron.mesh") import meshtools var m = LineMesh(fn (t) [cos(t), sin(t), 0], 0..2*Pi:0.2, closed=true) m.addgrade(2) // expect error 'MshAddGrdOutOfBnds' ================================================ FILE: test/mesh/addgradetwo.morpho ================================================ // Test to check that addgrade from a volume element works import meshtools var m = Mesh("tetrahedron2.mesh") m.addgrade(2) m.addgrade(1) print m.connectivitymatrix(0,3) // expect: [ 1 ] // expect: [ 1 ] // expect: [ 1 ] // expect: [ 1 ] print m.connectivitymatrix(0,1) // expect: [ 1 1 0 1 0 0 ] // expect: [ 1 0 1 0 1 0 ] // expect: [ 0 1 1 0 0 1 ] // expect: [ 0 0 0 1 1 1 ] print m.connectivitymatrix(0,2) // expect: [ 1 1 1 0 ] // expect: [ 1 1 0 1 ] // expect: [ 1 0 1 1 ] // expect: [ 0 1 1 1 ] ================================================ FILE: test/mesh/addgradezero.morpho ================================================ // Test to confirm that adding grade 0 to a mesh doesn't change anything. import meshtools import symmetry var m = LineMesh(fn (t) [t,0,0], -1..1:1) m.addgrade(0) print m.connectivitymatrix(0,0) // expect: nil var s = Selection(m, fn (x,y,z) x<-0.5 || x>0.5) var t = Translate(Matrix([2,0,0])) m.addsymmetry(t, s) m.addgrade(0) print m.connectivitymatrix(0,0) // expect: [ 0 0 ] // expect: [ 0 0 0 ] // expect: [ 0 0 0 ] ================================================ FILE: test/mesh/clone.morpho ================================================ var a = Mesh("square.mesh") var b = a.clone() print b // expect: a.addgrade(1) print b.connectivitymatrix(0,2) // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 1 1 ] // expect: [ 0 1 ] print b.connectivitymatrix(0,1) // expect: nil ================================================ FILE: test/mesh/connectivity.morpho ================================================ var a = Mesh("square.mesh") a.addgrade(1) print a.connectivitymatrix(0,1) // expect: [ 1 1 0 0 0 ] // expect: [ 1 0 1 1 0 ] // expect: [ 0 1 1 0 1 ] // expect: [ 0 0 0 1 1 ] print a.connectivitymatrix(0,2) // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 1 1 ] // expect: [ 0 1 ] print a.connectivitymatrix(2,0) // expect: [ 1 1 1 0 ] // expect: [ 0 1 1 1 ] print a.connectivitymatrix(1,2) // expect: [ 1 0 ] // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 0 1 ] // expect: [ 0 1 ] ================================================ FILE: test/mesh/connectivity_tetrahedron.morpho ================================================ var a = Mesh("tetrahedron.mesh") a.addgrade(1) print a.connectivitymatrix(0,2) // expect: [ 1 0 1 1 ] // expect: [ 1 1 0 1 ] // expect: [ 1 1 1 0 ] // expect: [ 0 1 1 1 ] print "" // expect: print a.connectivitymatrix(0,1) // expect: [ 1 1 0 0 0 1 ] // expect: [ 1 0 1 1 0 0 ] // expect: [ 0 1 1 0 1 0 ] // expect: [ 0 0 0 1 1 1 ] print "" // expect: print a.connectivitymatrix(1,0) // expect: [ 1 1 0 0 ] // expect: [ 1 0 1 0 ] // expect: [ 0 1 1 0 ] // expect: [ 0 1 0 1 ] // expect: [ 0 0 1 1 ] // expect: [ 1 0 0 1 ] print "" // expect: print a.connectivitymatrix(2,0) // expect: [ 1 1 1 0 ] // expect: [ 0 1 1 1 ] // expect: [ 1 0 1 1 ] // expect: [ 1 1 0 1 ] print "" // expect: print a.connectivitymatrix(1,2) // expect: [ 1 0 0 1 ] // expect: [ 1 0 1 0 ] // expect: [ 1 1 0 0 ] // expect: [ 0 1 0 1 ] // expect: [ 0 1 1 0 ] // expect: [ 0 0 1 1 ] ================================================ FILE: test/mesh/err_empty_mesh_set_element.morpho ================================================ var m = Mesh() print m // shows empty mesh // expect: m.setvertexposition(0, Matrix([-5,0,0])) // expect error 'MshInvldId' ================================================ FILE: test/mesh/err_mesh_con_args.morpho ================================================ import meshtools var m = LineMesh(fn (t) [t,0,0], -1..1:0.2) print m // expect: var m2 = Mesh(m) // expect error 'MshArgs' ================================================ FILE: test/mesh/inherited.morpho ================================================ // Test Mesh methods inherited from Object var a = Mesh() print a.respondsto("respondsto") // expect: true print islist(a.respondsto()) // expect: true print a.clss() // expect: @Mesh print a.superclass() // expect: @Object print islist(a.invoke("respondsto")) // expect: true print a.has("a") // expect: false ================================================ FILE: test/mesh/load.morpho ================================================ var a = Mesh("sphere.mesh") print a // expect: ================================================ FILE: test/mesh/load_missing_coord.morpho ================================================ var a = Mesh("square_missingcoord.mesh") print a // expect error 'MshLdVrtDim' ================================================ FILE: test/mesh/load_spurious_vertex_ref.morpho ================================================ var a = Mesh("square_spurious_vertex_ref.mesh") print a // expect error 'MshLdVrtNtFnd' ================================================ FILE: test/mesh/maxgrade.morpho ================================================ import meshtools var a = Mesh("tetrahedron.mesh") print a.maxgrade() // expect: 2 var L = 0.5 var dx = 0.1 var m = AreaMesh(fn (u, v) [u, v], -L..L:dx, -L..L:dx) print m.count(0) // expect: 121 print m.count(1) // expect: 0 print m.count(2) // expect: 200 m.addgrade(1) print m.count(0) // expect: 121 print m.count(1) // expect: 320 print m.count(2) // expect: 200 ================================================ FILE: test/mesh/merge.morpho ================================================ import meshtools var m1 = LineMesh(fn (t) [cos(t), sin(t), 0], 0..Pi:Pi/5) var m2 = LineMesh(fn (t) [cos(t), sin(t), 0], Pi..2*Pi:Pi/5) var merge = MeshMerge([m1]) merge.addmesh([m2]) var m = merge.merge() var lines = m.connectivitymatrix(0,1) print lines // expect: [ 1 0 0 0 0 0 0 0 0 1 ] // expect: [ 1 1 0 0 0 0 0 0 0 0 ] // expect: [ 0 1 1 0 0 0 0 0 0 0 ] // expect: [ 0 0 1 1 0 0 0 0 0 0 ] // expect: [ 0 0 0 1 1 0 0 0 0 0 ] // expect: [ 0 0 0 0 1 1 0 0 0 0 ] // expect: [ 0 0 0 0 0 1 1 0 0 0 ] // expect: [ 0 0 0 0 0 0 1 1 0 0 ] // expect: [ 0 0 0 0 0 0 0 1 1 0 ] // expect: [ 0 0 0 0 0 0 0 0 1 1 ] for (elid in 0...m.count(1)) print lines.rowindices(elid) // expect: [ 0, 1 ] // expect: [ 1, 2 ] // expect: [ 2, 3 ] // expect: [ 3, 4 ] // expect: [ 4, 5 ] // expect: [ 5, 6 ] // expect: [ 6, 7 ] // expect: [ 7, 8 ] // expect: [ 8, 9 ] // expect: [ 0, 9 ] var merge2 = MeshMerge(m1) merge2.addmesh(m2) print merge2.meshes // expect: [ , ] var merger2 = merge2.merge() var v1 = m.vertexmatrix() var v2 = merger2.vertexmatrix() var Equal= true var d = v1.dimensions() for ( d1 in 0...d[0]){ for (d2 in 0...d[1]){ Equal = Equal && v1[d1,d2]==v2[d1,d2] } } print Equal // expect: true ================================================ FILE: test/mesh/out.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/mesh/removegrade.morpho ================================================ // Test to check that addgrade from a volume element works import meshtools var m = Mesh("tetrahedron2.mesh") m.addgrade(2) m.addgrade(1) m.removegrade(1) m.removegrade(2) print m.connectivitymatrix(0,3) // expect: [ 1 ] // expect: [ 1 ] // expect: [ 1 ] // expect: [ 1 ] print m.connectivitymatrix(0,1) // expect: nil print m.connectivitymatrix(0,2) // expect: nil ================================================ FILE: test/mesh/resetconnectivity.morpho ================================================ var a = Mesh("square.mesh") a.addgrade(1) print a.connectivitymatrix(0,1) // expect: [ 1 1 0 0 0 ] // expect: [ 1 0 1 1 0 ] // expect: [ 0 1 1 0 1 ] // expect: [ 0 0 0 1 1 ] print a.connectivitymatrix(0,2) // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 1 1 ] // expect: [ 0 1 ] print a.connectivitymatrix(2,0) // expect: [ 1 1 1 0 ] // expect: [ 0 1 1 1 ] a.resetconnectivity() print a.connectivitymatrix(0,1) // expect: [ 1 1 0 0 0 ] // expect: [ 1 0 1 1 0 ] // expect: [ 0 1 1 0 1 ] // expect: [ 0 0 0 1 1 ] print a.connectivitymatrix(0,2) // expect: [ 1 0 ] // expect: [ 1 1 ] // expect: [ 1 1 ] // expect: [ 0 1 ] print a.connectivitymatrix(2,0) // expect: [ 1 1 1 0 ] // expect: [ 0 1 1 1 ] ================================================ FILE: test/mesh/save.morpho ================================================ var a = Mesh("square.mesh") print a // expect: a.save("out.mesh") var b = Mesh("out.mesh") print b // expect: ================================================ FILE: test/mesh/sphere.mesh ================================================ vertices 1 0.0864185 0.596014 -0.0864185 2 -0.351971 -0.351971 -0.351971 3 0.512094 -0.0935136 -0.31663 4 0.351971 -0.351971 -0.351971 5 0.0935136 0.512094 -0.31663 6 -0.351971 0.351971 -0.351971 7 1.56135e-16 -0.193544 0.577235 8 0.351971 0.351971 -0.351971 9 -0.31663 0.0935136 0.512094 10 -0.351971 -0.351971 0.351971 11 0.273295 0.471245 0.273295 12 0.351971 -0.351971 0.351971 13 -0.273295 0.273295 0.471245 14 -0.351971 0.351971 0.351971 15 0.273295 -0.471245 -0.273295 16 0.351971 0.351971 0.351971 17 -0.273295 -0.471245 0.273295 18 7.59791e-17 -1.19499e-16 -0.606904 19 0.273295 0.471245 -0.273295 20 -1.79147e-18 3.71799e-17 0.606904 21 0.512094 0.0935136 -0.31663 22 1.49826e-17 -0.606904 8.02851e-17 23 1.77124e-16 0.577235 -0.193544 24 4.9909e-17 0.606904 3.88868e-17 25 0.0935136 0.512094 0.31663 26 -0.606904 5.5069e-17 5.20646e-17 27 -0.31663 -0.0935136 0.512094 28 0.606904 4.84814e-17 1.12006e-17 29 0.596014 -0.0864185 0.0864185 30 -0.430905 -0.430905 -7.82355e-17 31 -0.596014 -0.0864185 -0.0864185 32 0.177071 -0.177071 0.555185 33 -0.596014 0.0864185 0.0864185 34 0.177071 0.555185 0.177071 35 0.596014 -0.0864185 -0.0864185 36 0.555185 -0.177071 0.177071 37 0.596014 0.0864185 0.0864185 38 3.78864e-17 -0.430905 -0.430905 39 0.577235 -8.14644e-17 -0.193544 40 0.430905 -0.430905 -4.12764e-17 41 0.512094 0.0935136 0.31663 42 0.177071 0.177071 0.555185 43 -0.0935136 0.512094 0.31663 44 -0.177071 0.555185 0.177071 45 -0.193544 4.71291e-17 0.577235 46 7.28164e-17 0.430905 -0.430905 47 0.471245 -0.273295 0.273295 48 -0.430905 0.430905 2.48688e-17 49 -0.471245 -0.273295 -0.273295 50 -0.177071 0.177071 0.555185 51 -0.471245 0.273295 0.273295 52 -0.555185 -0.177071 -0.177071 53 0.471245 -0.273295 -0.273295 54 -0.430905 -1.09518e-19 -0.430905 55 0.471245 0.273295 0.273295 56 0.430905 0.430905 -8.51463e-17 57 0.31663 -0.0935136 -0.512094 58 -0.177071 -0.555185 -0.177071 59 0.512094 -0.0935136 0.31663 60 -0.555185 0.177071 -0.177071 61 5.9116e-17 0.577235 0.193544 62 0.430905 -1.66575e-16 -0.430905 63 -0.512094 0.31663 -0.0935136 64 -0.177071 -0.177071 -0.555185 65 -0.185357 -0.41054 -0.41054 66 0.177071 -0.555185 -0.177071 67 -0.41054 -0.185357 -0.41054 68 -0.555185 0.177071 0.177071 69 -0.185357 -0.41054 0.41054 70 6.81268e-17 -0.430905 0.430905 71 -0.41054 -0.185357 0.41054 72 0.177071 -0.177071 -0.555185 73 0.31663 -0.512094 -0.0935136 74 0.177071 -0.555185 0.177071 75 0.31663 0.0935136 -0.512094 76 -0.555185 -0.177071 0.177071 77 0.577235 4.3661e-17 0.193544 78 -3.00145e-17 0.430905 0.430905 79 0.31663 -0.0935136 0.512094 80 0.177071 0.177071 -0.555185 81 -0.512094 0.31663 0.0935136 82 -0.177071 -0.555185 0.177071 83 0.185357 -0.41054 -0.41054 84 0.555185 -0.177071 -0.177071 85 -0.41054 0.185357 -0.41054 86 -0.430905 -1.17887e-16 0.430905 87 0.185357 -0.41054 0.41054 88 -0.177071 0.177071 -0.555185 89 -0.41054 0.185357 0.41054 90 -0.177071 0.555185 -0.177071 91 0.31663 -0.512094 0.0935136 92 0.555185 0.177071 -0.177071 93 0.193544 -2.28647e-16 -0.577235 94 0.430905 -1.05715e-16 0.430905 95 -0.31663 0.0935136 -0.512094 96 -0.177071 -0.177071 0.555185 97 0.31663 0.0935136 0.512094 98 0.177071 0.555185 -0.177071 99 -0.577235 0.193544 6.86928e-17 100 0.555185 0.177071 0.177071 101 0.41054 -0.41054 -0.185357 102 -0.273295 0.471245 0.273295 103 0.41054 0.41054 -0.185357 104 -0.471245 0.273295 -0.273295 105 0.0864185 -0.0864185 -0.596014 106 -0.471245 -0.273295 0.273295 107 -0.0864185 0.0864185 -0.596014 108 0.471245 0.273295 -0.273295 109 0.193544 -0.577235 -5.55382e-17 110 0.0935136 -0.31663 -0.512094 111 -0.31663 -0.512094 0.0935136 112 0.577235 0.193544 -5.78119e-17 113 -0.31663 -0.0935136 -0.512094 114 0.512094 -0.31663 0.0935136 115 0.193544 -4.49097e-17 0.577235 116 -0.0935136 0.31663 0.512094 117 -0.0935136 -0.512094 -0.31663 118 -0.577235 -9.95665e-17 0.193544 119 0.41054 -0.41054 0.185357 120 -0.41054 -0.41054 -0.185357 121 0.41054 0.41054 0.185357 122 -0.185357 0.41054 -0.41054 123 0.273295 -0.273295 -0.471245 124 0.41054 -0.185357 -0.41054 125 -0.273295 0.273295 -0.471245 126 -0.185357 0.41054 0.41054 127 -0.512094 -0.31663 0.0935136 128 0.41054 -0.185357 0.41054 129 -0.31663 -0.512094 -0.0935136 130 -7.72329e-17 -0.193544 -0.577235 131 -0.193544 -3.58121e-17 -0.577235 132 0.0935136 0.31663 -0.512094 133 -0.31663 0.512094 0.0935136 134 0.512094 -0.31663 -0.0935136 135 0.0935136 -0.512094 -0.31663 136 9.00716e-17 0.193544 0.577235 137 0.0864185 0.0864185 0.596014 138 -0.41054 -0.41054 0.185357 139 -0.0864185 -0.596014 -0.0864185 140 0.185357 0.41054 -0.41054 141 0.0864185 -0.596014 0.0864185 142 0.41054 0.185357 -0.41054 143 -0.0864185 0.596014 -0.0864185 144 0.185357 0.41054 0.41054 145 -0.512094 -0.31663 -0.0935136 146 0.41054 0.185357 0.41054 147 -0.193544 -0.577235 -8.53496e-17 148 0.0935136 -0.512094 0.31663 149 0.31663 0.512094 -0.0935136 150 -0.0935136 0.31663 -0.512094 151 -0.31663 0.512094 -0.0935136 152 0.577235 -0.193544 -6.84494e-17 153 3.18471e-17 -0.577235 -0.193544 154 -0.512094 -0.0935136 -0.31663 155 0.273295 0.273295 0.471245 156 0.0864185 -0.0864185 0.596014 157 -0.273295 -0.471245 -0.273295 158 -0.41054 0.41054 -0.185357 159 0.273295 -0.471245 0.273295 160 -0.0864185 -0.0864185 -0.596014 161 -0.273295 0.471245 -0.273295 162 0.0864185 0.0864185 -0.596014 163 -0.577235 -0.193544 2.32018e-17 164 -0.0864185 -0.0864185 0.596014 165 0.512094 0.31663 -0.0935136 166 -0.0935136 -0.512094 0.31663 167 0.31663 0.512094 0.0935136 168 -5.60366e-18 0.193544 -0.577235 169 -0.193544 0.577235 1.09776e-17 170 -0.0935136 -0.31663 0.512094 171 -0.512094 0.0935136 0.31663 172 -0.512094 0.0935136 -0.31663 173 -0.0864185 0.596014 0.0864185 174 0.273295 -0.273295 0.471245 175 -0.596014 0.0864185 -0.0864185 176 -0.41054 0.41054 0.185357 177 -0.596014 -0.0864185 0.0864185 178 -0.273295 -0.273295 -0.471245 179 0.596014 0.0864185 -0.0864185 180 0.273295 0.273295 -0.471245 181 -0.0935136 -0.31663 -0.512094 182 -0.273295 -0.273295 0.471245 183 0.512094 0.31663 0.0935136 184 -1.0845e-16 -0.577235 0.193544 185 0.193544 0.577235 -1.8251e-16 186 -0.0935136 0.512094 -0.31663 187 0.0935136 0.31663 0.512094 188 0.0935136 -0.31663 0.512094 189 -0.512094 -0.0935136 0.31663 190 -0.577235 2.04248e-17 -0.193544 191 0.0864185 0.596014 0.0864185 192 -0.0864185 0.0864185 0.596014 193 0.0864185 -0.596014 -0.0864185 194 -0.0864185 -0.596014 0.0864185 195 0.572222 -0.187783 0.0896566 196 0.136787 0.538749 -0.249253 197 0.377416 -0.0471868 -0.476089 198 0.187783 -0.572222 -0.0896566 199 -0.315438 0.415407 -0.315438 200 0.257409 -0.550008 -0.0471104 201 0.231715 -0.445393 0.345554 202 -0.257409 0.550008 -0.0471104 203 0.465691 0.367021 0.140757 204 0.043793 0.59041 -0.140941 205 0.367021 0.465691 -0.140757 206 -0.227102 0.517774 0.227102 207 -0.538749 -0.136787 -0.249253 208 0.0896566 -0.187783 0.572222 209 0.476089 -0.0471868 0.377416 210 -0.249253 -0.538749 0.136787 211 -0.572222 -0.187783 -0.0896566 212 0.59041 -0.140941 -0.043793 213 0.140757 -0.465691 0.367021 214 -0.257409 0.550008 0.0471104 215 -0.345554 0.231715 0.445393 216 -0.043793 0.59041 -0.140941 217 0.298784 0.497541 -0.185739 218 -0.315438 0.415407 0.315438 219 -0.476089 -0.0471868 -0.377416 220 -0.0896566 -0.187783 0.572222 221 0.538749 -0.136787 0.249253 222 -0.377416 -0.476089 0.0471868 223 -0.572222 -0.187783 0.0896566 224 0.59041 -0.140941 0.043793 225 0.185739 -0.497541 0.298784 226 0.345554 0.231715 -0.445393 227 -0.367021 0.140757 0.465691 228 8.36375e-17 0.601832 -0.0880064 229 0.345554 0.445393 0.231715 230 -0.517774 0.227102 -0.227102 231 0.043129 -0.043129 0.604789 232 -0.249253 0.136787 0.538749 233 -0.0896566 0.572222 0.187783 234 -0.377416 -0.0471868 -0.476089 235 0.538749 0.249253 -0.136787 236 0.601832 -0.0880064 2.66407e-17 237 2.13308e-17 0.518174 0.320444 238 0.298784 0.185739 -0.497541 239 -0.298784 0.185739 0.497541 240 0.231715 -0.445393 -0.345554 241 0.298784 0.497541 0.185739 242 -0.415407 0.315438 -0.315438 243 0.132421 -0.132421 0.579255 244 -0.377416 0.0471868 0.476089 245 0.0896566 0.572222 0.187783 246 -0.249253 -0.136787 -0.538749 247 0.476089 0.377416 -0.0471868 248 -0.445393 -0.345554 0.231715 249 -0.0471104 0.550008 0.257409 250 0.367021 0.140757 -0.465691 251 0.59041 0.140941 0.043793 252 0.185739 -0.497541 -0.298784 253 0.367021 0.465691 0.140757 254 -0.517774 -0.227102 0.227102 255 -0.385744 0.385744 -0.271934 256 0.227102 0.517774 0.227102 257 -0.538749 0.249253 -0.136787 258 0.187783 0.0896566 0.572222 259 0.377416 0.476089 0.0471868 260 -0.465691 -0.367021 0.140757 261 0.0471104 0.550008 0.257409 262 0.231715 0.345554 0.445393 263 0.59041 0.140941 -0.043793 264 0.140757 -0.465691 -0.367021 265 -0.415407 -0.315438 0.315438 266 -0.425784 0.425784 -0.0937993 267 0.315438 0.415407 0.315438 268 -0.476089 0.377416 -0.0471868 269 0.187783 -0.0896566 0.572222 270 0.249253 0.538749 0.136787 271 -0.497541 -0.298784 0.185739 272 -0.140757 -0.465691 0.367021 273 0.140757 0.367021 0.465691 274 0.601832 0.0880064 8.84876e-17 275 1.46149e-16 0.518174 -0.320444 276 0.517774 0.227102 -0.227102 277 -0.043129 -0.043129 -0.604789 278 -0.227102 0.227102 0.517774 279 -0.271934 -0.385744 -0.385744 280 -0.136787 -0.538749 -0.249253 281 -0.187783 0.572222 -0.0896566 282 0.518174 -0.320444 -1.15474e-16 283 -0.185739 -0.497541 0.298784 284 0.185739 0.298784 0.497541 285 -0.367021 -0.140757 0.465691 286 0.0471104 0.550008 -0.257409 287 0.415407 0.315438 -0.315438 288 -0.132421 -0.132421 -0.579255 289 -0.315438 0.315438 0.415407 290 -0.0937993 -0.425784 -0.425784 291 -0.0471868 -0.476089 -0.377416 292 -0.187783 0.572222 0.0896566 293 0.550008 -0.257409 -0.0471104 294 -0.231715 -0.445393 0.345554 295 0.140941 0.043793 -0.59041 296 -0.298784 -0.185739 0.497541 297 -0.0471104 0.550008 -0.257409 298 0.0471868 -0.377416 -0.476089 299 0.043129 0.043129 -0.604789 300 0.227102 -0.517774 -0.227102 301 -0.385744 -0.271934 -0.385744 302 0.425784 -0.425784 0.0937993 303 -0.538749 0.136787 0.249253 304 0.550008 -0.257409 0.0471104 305 0.345554 -0.231715 0.445393 306 0.140941 -0.043793 -0.59041 307 -0.345554 -0.231715 0.445393 308 0.043793 -0.59041 -0.140941 309 0.136787 -0.249253 -0.538749 310 0.132421 0.132421 -0.579255 311 0.315438 -0.415407 -0.315438 312 -0.425784 -0.0937993 -0.425784 313 0.385744 -0.385744 0.271934 314 -0.476089 0.0471868 0.377416 315 -0.465691 -0.367021 -0.140757 316 0.367021 -0.140757 0.465691 317 0.0880064 -1.81288e-16 -0.601832 318 0.518174 0.320444 -6.6765e-17 319 -0.043793 -0.59041 -0.140941 320 0.572222 0.187783 0.0896566 321 -0.043129 -0.043129 0.604789 322 -0.227102 -0.517774 0.227102 323 -0.271934 -0.385744 0.385744 324 0.425784 0.425784 0.0937993 325 -0.043129 0.604789 0.043129 326 -0.497541 -0.298784 -0.185739 327 0.298784 -0.185739 0.497541 328 -0.140757 0.367021 0.465691 329 0.550008 0.257409 0.0471104 330 -4.67222e-17 -0.601832 -0.0880064 331 0.572222 0.187783 -0.0896566 332 -0.132421 -0.132421 0.579255 333 -0.315438 -0.415407 0.315438 334 -0.0937993 -0.425784 0.425784 335 0.385744 0.385744 0.271934 336 -0.132421 0.579255 0.132421 337 -0.445393 -0.345554 -0.231715 338 -0.043793 -0.59041 0.140941 339 -0.185739 0.298784 0.497541 340 0.550008 0.257409 -0.0471104 341 0.445393 0.231715 0.345554 342 0.538749 -0.249253 0.136787 343 -0.0471868 -0.476089 0.377416 344 0.227102 0.517774 -0.227102 345 -0.385744 -0.271934 0.385744 346 0.227102 -0.227102 -0.517774 347 -0.604789 0.043129 -0.043129 348 -0.231715 -0.345554 0.445393 349 0.043793 -0.59041 0.140941 350 -0.231715 0.345554 0.445393 351 -0.140941 -0.043793 0.59041 352 0.465691 0.140757 0.367021 353 0.476089 -0.377416 0.0471868 354 -0.136787 -0.538749 0.249253 355 0.315438 0.415407 -0.315438 356 -0.425784 -0.0937993 0.425784 357 0.315438 -0.315438 -0.415407 358 -0.579255 0.132421 -0.132421 359 -0.140757 -0.367021 0.465691 360 -6.7966e-17 -0.601832 0.0880064 361 0.320444 -1.51359e-16 -0.518174 362 -0.140941 0.043793 0.59041 363 0.497541 0.185739 0.298784 364 -0.0471868 0.377416 0.476089 365 -0.0896566 0.187783 -0.572222 366 0.476089 0.0471868 -0.377416 367 0.249253 -0.538749 -0.136787 368 -0.227102 0.227102 -0.517774 369 -0.604789 -0.043129 0.043129 370 -0.185739 -0.298784 0.497541 371 0.345554 0.231715 0.445393 372 0.257409 0.0471104 -0.550008 373 -0.0880064 8.19325e-17 0.601832 374 -2.68478e-17 -0.518174 -0.320444 375 -0.136787 0.249253 0.538749 376 0.0896566 0.187783 -0.572222 377 0.538749 0.136787 -0.249253 378 0.377416 -0.476089 -0.0471868 379 -0.315438 0.315438 -0.415407 380 -0.579255 -0.132421 0.132421 381 -0.59041 -0.140941 -0.043793 382 0.298784 0.185739 0.497541 383 0.257409 -0.0471104 -0.550008 384 0.231715 0.345554 -0.445393 385 0.0471104 -0.550008 -0.257409 386 -0.572222 -0.0896566 0.187783 387 -0.136787 -0.249253 0.538749 388 0.0896566 0.572222 -0.187783 389 0.377416 0.0471868 -0.476089 390 -0.538749 -0.249253 0.136787 391 0.604789 0.043129 -0.043129 392 -0.59041 -0.140941 0.043793 393 0.367021 0.140757 0.465691 394 -0.043793 0.140941 0.59041 395 0.140757 0.367021 -0.465691 396 -0.0471104 -0.550008 -0.257409 397 -0.572222 0.0896566 0.187783 398 -0.0471868 -0.377416 0.476089 399 -0.0896566 0.572222 -0.187783 400 0.249253 0.136787 -0.538749 401 -0.476089 -0.377416 0.0471868 402 0.579255 0.132421 -0.132421 403 -0.601832 -0.0880064 8.65333e-17 404 -6.34285e-17 -0.518174 0.320444 405 0.043793 0.140941 0.59041 406 0.185739 0.298784 -0.497541 407 0.465691 -0.140757 0.367021 408 -0.385744 -0.385744 -0.271934 409 -0.476089 0.0471868 -0.377416 410 0.136787 0.538749 0.249253 411 0.572222 -0.0896566 0.187783 412 -0.377416 -0.476089 -0.0471868 413 -0.136787 -0.249253 -0.538749 414 0.231715 -0.345554 0.445393 415 -0.0471104 -0.550008 0.257409 416 1.45663e-17 0.0880064 0.601832 417 -0.320444 -2.20514e-17 0.518174 418 0.497541 -0.185739 0.298784 419 -0.425784 -0.425784 -0.0937993 420 -0.538749 0.136787 -0.249253 421 0.0471868 0.476089 0.377416 422 0.572222 0.0896566 0.187783 423 -0.249253 -0.538749 -0.136787 424 -0.0471868 -0.377416 -0.476089 425 0.185739 -0.298784 0.497541 426 0.0471104 -0.550008 0.257409 427 -0.345554 -0.445393 0.231715 428 -0.257409 -0.0471104 0.550008 429 0.445393 -0.231715 0.345554 430 -0.271934 0.385744 -0.385744 431 0.227102 -0.227102 0.517774 432 -0.377416 -0.0471868 0.476089 433 0.249253 -0.136787 0.538749 434 -0.187783 -0.0896566 -0.572222 435 0.476089 0.377416 0.0471868 436 0.140757 -0.367021 0.465691 437 0.140941 0.043793 0.59041 438 -0.367021 -0.465691 0.140757 439 -0.257409 0.0471104 0.550008 440 -0.445393 0.231715 0.345554 441 -0.0937993 0.425784 -0.425784 442 0.315438 -0.315438 0.415407 443 -0.249253 -0.136787 0.538749 444 0.377416 -0.0471868 0.476089 445 -0.187783 0.0896566 -0.572222 446 0.538749 0.249253 0.136787 447 -0.518174 -0.320444 -1.95339e-17 448 0.140941 -0.043793 0.59041 449 -0.298784 -0.497541 0.185739 450 -0.140757 0.367021 -0.465691 451 -0.465691 0.140757 0.367021 452 0.385744 -0.271934 -0.385744 453 -0.425784 0.425784 0.0937993 454 0.604789 -0.043129 0.043129 455 -0.476089 0.377416 0.0471868 456 -0.249253 0.538749 0.136787 457 0.187783 0.572222 0.0896566 458 -0.550008 -0.257409 -0.0471104 459 0.0880064 -1.69762e-17 0.601832 460 5.07865e-17 0.320444 0.518174 461 -0.185739 0.298784 -0.497541 462 -0.497541 0.185739 0.298784 463 0.425784 -0.0937993 -0.425784 464 -0.385744 0.385744 0.271934 465 0.579255 -0.132421 0.132421 466 -0.538749 0.249253 0.136787 467 -0.377416 0.476089 0.0471868 468 0.187783 0.572222 -0.0896566 469 -0.550008 -0.257409 0.0471104 470 0.445393 -0.231715 -0.345554 471 -0.0471104 0.257409 0.550008 472 -0.231715 0.345554 -0.445393 473 0.59041 -0.043793 0.140941 474 -0.271934 0.385744 0.385744 475 -0.227102 -0.227102 -0.517774 476 -0.604789 -0.043129 -0.043129 477 0.0937993 -0.425784 -0.425784 478 0.0471868 -0.476089 -0.377416 479 0.136787 0.249253 0.538749 480 0.043793 -0.140941 0.59041 481 0.465691 -0.140757 -0.367021 482 0.0471104 0.257409 0.550008 483 -0.445393 0.345554 -0.231715 484 0.59041 0.043793 0.140941 485 -0.0937993 0.425784 0.425784 486 -0.315438 -0.315438 -0.415407 487 -0.579255 -0.132421 -0.132421 488 0.271934 -0.385744 -0.385744 489 0.136787 -0.538749 -0.249253 490 0.0471868 0.377416 0.476089 491 -0.043793 -0.140941 0.59041 492 0.497541 -0.185739 -0.298784 493 -0.367021 -0.465691 -0.140757 494 -0.465691 0.367021 -0.140757 495 0.601832 -9.49355e-18 0.0880064 496 0.385744 -0.271934 0.385744 497 0.227102 0.227102 -0.517774 498 -0.604789 0.043129 0.043129 499 -0.425784 0.0937993 -0.425784 500 0.043129 0.043129 0.604789 501 -0.476089 -0.0471868 0.377416 502 1.08987e-16 -0.0880064 0.601832 503 0.320444 -3.28597e-17 0.518174 504 -0.298784 -0.497541 -0.185739 505 -0.497541 0.298784 -0.185739 506 -0.465691 -0.140757 0.367021 507 0.425784 -0.0937993 0.425784 508 0.315438 0.315438 -0.415407 509 -0.579255 0.132421 0.132421 510 -0.385744 0.271934 -0.385744 511 0.132421 0.132421 0.579255 512 -0.538749 -0.136787 0.249253 513 -0.231715 -0.345554 -0.445393 514 0.257409 0.0471104 0.550008 515 -0.345554 -0.445393 -0.231715 516 -0.043793 0.140941 -0.59041 517 -0.497541 -0.185739 0.298784 518 0.0896566 -0.187783 -0.572222 519 -0.227102 -0.227102 0.517774 520 0.604789 -0.043129 -0.043129 521 0.0937993 -0.425784 0.425784 522 -0.043129 -0.604789 -0.043129 523 0.140941 0.59041 0.043793 524 -0.140757 -0.367021 -0.465691 525 0.257409 -0.0471104 0.550008 526 -0.445393 -0.231715 -0.345554 527 0.043793 0.140941 -0.59041 528 -0.445393 -0.231715 0.345554 529 -0.0896566 -0.187783 -0.572222 530 -0.315438 -0.315438 0.415407 531 0.579255 -0.132421 -0.132421 532 0.271934 -0.385744 0.385744 533 -0.132421 -0.579255 -0.132421 534 0.140941 0.59041 -0.043793 535 -0.185739 -0.298784 -0.497541 536 0.445393 0.231715 -0.345554 537 -0.465691 -0.140757 -0.367021 538 2.77187e-17 0.0880064 -0.601832 539 0.518174 -1.20677e-17 0.320444 540 0.136787 0.249253 -0.538749 541 -0.0896566 -0.572222 0.187783 542 0.604789 0.043129 0.043129 543 -0.425784 0.0937993 0.425784 544 0.043129 -0.604789 0.043129 545 0.0880064 0.601832 1.76832e-17 546 9.63855e-17 -0.320444 0.518174 547 0.497541 0.185739 -0.298784 548 -0.497541 -0.185739 -0.298784 549 -0.445393 0.345554 0.231715 550 0.550008 -0.0471104 0.257409 551 0.0471868 0.377416 -0.476089 552 0.0896566 -0.572222 0.187783 553 0.579255 0.132421 0.132421 554 -0.385744 0.271934 0.385744 555 0.132421 -0.579255 0.132421 556 0.345554 -0.445393 -0.231715 557 0.0471104 -0.257409 0.550008 558 0.465691 0.140757 -0.367021 559 -0.140941 -0.59041 -0.043793 560 -0.497541 0.298784 0.185739 561 0.550008 0.0471104 0.257409 562 0.476089 -0.377416 -0.0471868 563 -0.136787 0.538749 -0.249253 564 0.572222 0.0896566 -0.187783 565 0.377416 -0.476089 0.0471868 566 -0.043129 0.604789 -0.043129 567 0.367021 -0.465691 -0.140757 568 -0.0471104 -0.257409 0.550008 569 -0.345554 0.445393 0.231715 570 -0.140941 -0.59041 0.043793 571 -0.465691 0.367021 0.140757 572 -0.59041 -0.043793 0.140941 573 0.538749 -0.249253 -0.136787 574 -0.0471868 0.476089 -0.377416 575 0.572222 -0.0896566 -0.187783 576 0.249253 -0.538749 0.136787 577 -0.132421 0.579255 -0.132421 578 0.298784 -0.497541 -0.185739 579 0.231715 -0.345554 -0.445393 580 -0.367021 0.465691 0.140757 581 -0.0880064 -0.601832 8.03375e-17 582 -6.52521e-17 0.320444 -0.518174 583 -0.59041 0.043793 0.140941 584 -0.0896566 0.187783 0.572222 585 0.0471868 -0.377416 0.476089 586 0.538749 0.136787 0.249253 587 0.187783 0.0896566 -0.572222 588 -0.476089 -0.377416 -0.0471868 589 0.320444 0.518174 -1.33925e-16 590 0.185739 -0.298784 -0.497541 591 -0.298784 0.497541 0.185739 592 -0.445393 0.231715 -0.345554 593 -0.0471104 0.257409 -0.550008 594 -0.601832 2.54962e-17 0.0880064 595 0.0896566 0.187783 0.572222 596 0.136787 -0.249253 0.538749 597 0.476089 0.0471868 0.377416 598 0.187783 -0.0896566 -0.572222 599 -0.538749 -0.249253 -0.136787 600 0.257409 0.550008 0.0471104 601 0.140757 -0.367021 -0.465691 602 0.59041 0.043793 -0.140941 603 -0.497541 0.185739 -0.298784 604 0.0471104 0.257409 -0.550008 605 -0.345554 0.231715 -0.445393 606 -0.425784 -0.425784 0.0937993 607 -0.572222 0.0896566 -0.187783 608 -0.0471868 0.476089 0.377416 609 -0.249253 0.136787 -0.538749 610 -0.187783 -0.572222 -0.0896566 611 0.257409 0.550008 -0.0471104 612 0.231715 0.445393 0.345554 613 0.59041 -0.043793 -0.140941 614 -0.465691 0.140757 -0.367021 615 -0.59041 0.140941 0.043793 616 -0.367021 0.140757 -0.465691 617 -0.385744 -0.385744 0.271934 618 -0.572222 -0.0896566 -0.187783 619 -0.136787 0.538749 0.249253 620 -0.377416 0.0471868 -0.476089 621 -0.187783 -0.572222 0.0896566 622 0.345554 -0.445393 0.231715 623 0.140757 0.465691 0.367021 624 0.601832 1.79672e-17 -0.0880064 625 -0.320444 -0.518174 -1.08154e-16 626 -0.59041 0.140941 -0.043793 627 -0.298784 0.185739 -0.497541 628 0.0937993 0.425784 -0.425784 629 0.043129 0.604789 0.043129 630 -0.187783 -0.0896566 0.572222 631 0.377416 0.0471868 0.476089 632 0.249253 0.538749 -0.136787 633 0.298784 -0.497541 0.185739 634 0.185739 0.497541 0.298784 635 -0.367021 0.465691 -0.140757 636 -0.257409 -0.550008 -0.0471104 637 -0.601832 0.0880064 -2.08196e-17 638 -0.518174 -1.17754e-17 0.320444 639 0.271934 0.385744 -0.385744 640 0.132421 0.579255 0.132421 641 -0.187783 0.0896566 0.572222 642 0.249253 0.136787 0.538749 643 0.377416 0.476089 -0.0471868 644 0.367021 -0.465691 0.140757 645 0.043793 -0.140941 -0.59041 646 -0.298784 0.497541 -0.185739 647 -0.257409 -0.550008 0.0471104 648 -0.231715 0.445393 -0.345554 649 -0.550008 -0.0471104 0.257409 650 0.425784 0.0937993 -0.425784 651 -0.043129 0.043129 0.604789 652 0.517774 -0.227102 0.227102 653 -0.572222 0.187783 0.0896566 654 -0.377416 0.476089 -0.0471868 655 0.445393 -0.345554 0.231715 656 -0.043793 -0.140941 -0.59041 657 -0.345554 0.445393 -0.231715 658 -0.59041 0.043793 -0.140941 659 -0.140757 0.465691 -0.367021 660 -0.550008 0.0471104 0.257409 661 0.385744 0.271934 -0.385744 662 -0.132421 0.132421 0.579255 663 0.415407 -0.315438 0.315438 664 -0.572222 0.187783 -0.0896566 665 -0.249253 0.538749 -0.136787 666 0.465691 -0.367021 0.140757 667 4.34868e-17 -0.0880064 -0.601832 668 0.518174 -1.94879e-16 -0.320444 669 -0.59041 -0.043793 -0.140941 670 -0.185739 0.497541 -0.298784 671 -0.367021 -0.140757 -0.465691 672 0.0937993 0.425784 0.425784 673 0.043129 -0.604789 -0.043129 674 -0.517774 -0.227102 -0.227102 675 0.385744 -0.385744 -0.271934 676 0.0896566 -0.572222 -0.187783 677 0.497541 -0.298784 0.185739 678 -0.140757 0.465691 0.367021 679 0.550008 0.0471104 -0.257409 680 -0.601832 1.06019e-16 -0.0880064 681 -0.518174 0.320444 -1.1541e-16 682 -0.298784 -0.185739 -0.497541 683 0.271934 0.385744 0.385744 684 0.132421 -0.579255 -0.132421 685 -0.415407 -0.315438 -0.315438 686 0.425784 -0.425784 -0.0937993 687 -0.0896566 -0.572222 -0.187783 688 0.140941 -0.59041 0.043793 689 -0.185739 0.497541 0.298784 690 0.550008 -0.0471104 -0.257409 691 0.445393 0.345554 -0.231715 692 -0.550008 0.257409 0.0471104 693 -0.345554 -0.231715 -0.445393 694 0.425784 0.0937993 0.425784 695 -0.043129 -0.604789 0.043129 696 -0.517774 0.227102 0.227102 697 0.385744 0.385744 -0.271934 698 0.227102 0.227102 0.517774 699 0.140941 -0.59041 -0.043793 700 -0.231715 0.445393 0.345554 701 -0.140941 0.59041 -0.043793 702 0.465691 0.367021 -0.140757 703 -0.550008 0.257409 -0.0471104 704 -0.140941 -0.043793 -0.59041 705 0.385744 0.271934 0.385744 706 -0.132421 -0.579255 0.132421 707 -0.415407 0.315438 0.315438 708 0.425784 0.425784 -0.0937993 709 0.315438 0.315438 0.415407 710 0.0880064 -0.601832 7.34352e-17 711 -5.7854e-17 -0.320444 -0.518174 712 -0.140941 0.59041 0.043793 713 0.497541 0.298784 -0.185739 714 0.231715 0.445393 -0.345554 715 -0.140941 0.043793 -0.59041 716 0.136787 -0.538749 0.249253 717 0.043129 0.604789 -0.043129 718 0.517774 -0.227102 -0.227102 719 0.043129 -0.043129 -0.604789 720 -0.227102 -0.517774 -0.227102 721 0.465691 -0.367021 -0.140757 722 0.0471104 -0.257409 -0.550008 723 -0.0880064 0.601832 2.99876e-17 724 -0.518174 -4.24312e-17 -0.320444 725 0.185739 0.497541 -0.298784 726 -0.0880064 -1.95812e-17 -0.601832 727 0.0471868 -0.476089 0.377416 728 0.132421 0.579255 -0.132421 729 0.415407 -0.315438 -0.315438 730 0.132421 -0.132421 -0.579255 731 -0.315438 -0.415407 -0.315438 732 0.497541 -0.298784 -0.185739 733 -0.0471104 -0.257409 -0.550008 734 0.345554 -0.231715 -0.445393 735 -0.550008 0.0471104 -0.257409 736 0.140757 0.465691 -0.367021 737 -0.320444 -2.8719e-17 -0.518174 738 -0.0471868 0.377416 -0.476089 739 0.538749 -0.136787 -0.249253 740 0.517774 0.227102 0.227102 741 -0.043129 0.043129 -0.604789 742 0.227102 -0.517774 0.227102 743 0.445393 -0.345554 -0.231715 744 -0.043793 0.59041 0.140941 745 0.367021 -0.140757 -0.465691 746 -0.550008 -0.0471104 -0.257409 747 -0.231715 -0.445393 -0.345554 748 -0.257409 -0.0471104 -0.550008 749 -0.136787 0.249253 -0.538749 750 0.476089 -0.0471868 -0.377416 751 0.415407 0.315438 0.315438 752 -0.132421 0.132421 -0.579255 753 0.315438 -0.415407 0.315438 754 0.320444 -0.518174 3.51807e-17 755 0.043793 0.59041 0.140941 756 0.298784 -0.185739 -0.497541 757 0.445393 0.345554 0.231715 758 -0.140757 -0.465691 -0.367021 759 -0.257409 0.0471104 -0.550008 760 0.572222 -0.187783 -0.0896566 761 0.0471868 0.476089 -0.377416 762 0.249253 -0.136787 -0.538749 763 0.187783 -0.572222 0.0896566 764 -0.227102 0.517774 -0.227102 765 0.257409 -0.550008 0.0471104 766 3.47909e-17 0.601832 0.0880064 767 -0.320444 0.518174 8.65973e-17 768 0.497541 0.298784 0.185739 769 -0.185739 -0.497541 -0.298784 770 0.345554 0.445393 -0.231715 edges 1 152 195 2 195 36 3 5 196 4 196 98 5 57 197 6 197 62 7 109 198 8 198 66 9 161 199 10 199 6 11 109 200 12 200 73 13 159 201 14 201 87 15 151 202 16 202 169 17 183 203 18 203 121 19 1 204 20 204 23 21 103 205 22 205 149 23 44 206 24 206 102 25 52 207 26 207 154 27 32 208 28 208 7 29 94 209 30 209 59 31 82 210 32 210 111 33 52 211 34 211 163 35 35 212 36 212 152 37 87 213 38 213 148 39 169 214 40 214 133 41 13 215 42 215 89 43 23 216 44 216 143 45 149 217 46 217 19 47 102 218 48 218 14 49 154 219 50 219 54 51 7 220 52 220 96 53 59 221 54 221 36 55 111 222 56 222 30 57 163 223 58 223 76 59 152 224 60 224 29 61 148 225 62 225 159 63 142 226 64 226 180 65 89 227 66 227 9 67 143 228 68 228 1 69 121 229 70 229 11 71 60 230 72 230 104 73 20 231 74 231 156 75 50 232 76 232 9 77 44 233 78 233 61 79 54 234 80 234 113 81 92 235 82 235 165 83 29 236 84 236 35 85 25 237 86 237 43 87 180 238 88 238 75 89 9 239 90 239 13 91 83 240 92 240 15 93 11 241 94 241 167 95 104 242 96 242 6 97 156 243 98 243 32 99 9 244 100 244 86 101 61 245 102 245 34 103 113 246 104 246 64 105 165 247 106 247 56 107 106 248 108 248 138 109 43 249 110 249 61 111 75 250 112 250 142 113 37 251 114 251 112 115 15 252 116 252 135 117 167 253 118 253 121 119 76 254 120 254 106 121 6 255 122 255 158 123 34 256 124 256 11 125 60 257 126 257 63 127 42 258 128 258 115 129 56 259 130 259 167 131 138 260 132 260 127 133 61 261 134 261 25 135 155 262 136 262 144 137 112 263 138 263 179 139 135 264 140 264 83 141 106 265 142 265 10 143 158 266 144 266 48 145 11 267 146 267 16 147 63 268 148 268 48 149 115 269 150 269 32 151 167 270 152 270 34 153 127 271 154 271 106 155 69 272 156 272 166 157 144 273 158 273 187 159 179 274 160 274 37 161 186 275 162 275 5 163 92 276 164 276 108 165 18 277 166 277 160 167 50 278 168 278 13 169 2 279 170 279 65 171 58 280 172 280 117 173 90 281 174 281 169 175 114 282 176 282 134 177 166 283 178 283 17 179 187 284 180 284 155 181 71 285 182 285 27 183 5 286 184 286 23 185 108 287 186 287 8 187 160 288 188 288 64 189 13 289 190 289 14 191 65 290 192 290 38 193 117 291 194 291 38 195 169 292 196 292 44 197 134 293 198 293 152 199 17 294 200 294 69 201 162 295 202 295 93 203 27 296 204 296 182 205 23 297 206 297 186 207 38 298 208 298 110 209 18 299 210 299 162 211 66 300 212 300 15 213 2 301 214 301 67 215 40 302 216 302 119 217 68 303 218 303 171 219 152 304 220 304 114 221 174 305 222 305 128 223 93 306 224 306 105 225 182 307 226 307 71 227 193 308 228 308 153 229 110 309 230 309 72 231 162 310 232 310 80 233 15 311 234 311 4 235 67 312 236 312 54 237 119 313 238 313 12 239 171 314 240 314 86 241 120 315 242 315 145 243 128 316 244 316 79 245 105 317 246 317 162 247 165 318 248 318 183 249 153 319 250 319 139 251 100 320 252 320 112 253 20 321 254 321 164 255 82 322 256 322 17 257 10 323 258 323 69 259 56 324 260 324 121 261 24 325 262 325 173 263 145 326 264 326 49 265 79 327 266 327 174 267 126 328 268 328 116 269 183 329 270 329 112 271 139 330 272 330 193 273 112 331 274 331 92 275 164 332 276 332 96 277 17 333 278 333 10 279 69 334 280 334 70 281 121 335 282 335 16 283 173 336 284 336 44 285 49 337 286 337 120 287 194 338 288 338 184 289 116 339 290 339 13 291 112 340 292 340 165 293 55 341 294 341 146 295 36 342 296 342 114 297 70 343 298 343 166 299 98 344 300 344 19 301 10 345 302 345 71 303 72 346 304 346 123 305 26 347 306 347 175 307 182 348 308 348 69 309 184 349 310 349 141 311 13 350 312 350 126 313 164 351 314 351 45 315 146 352 316 352 41 317 114 353 318 353 40 319 166 354 320 354 82 321 19 355 322 355 8 323 71 356 324 356 86 325 123 357 326 357 4 327 175 358 328 358 60 329 69 359 330 359 170 331 141 360 332 360 194 333 57 361 334 361 75 335 45 362 336 362 192 337 41 363 338 363 55 339 78 364 340 364 116 341 88 365 342 365 168 343 62 366 344 366 21 345 66 367 346 367 73 347 88 368 348 368 125 349 26 369 350 369 177 351 170 370 352 370 182 353 146 371 354 371 155 355 75 372 356 372 93 357 192 373 358 373 164 359 117 374 360 374 135 361 116 375 362 375 50 363 168 376 364 376 80 365 21 377 366 377 92 367 73 378 368 378 40 369 125 379 370 379 6 371 177 380 372 380 76 373 31 381 374 381 163 375 155 382 376 382 97 377 93 383 378 383 57 379 180 384 380 384 140 381 135 385 382 385 153 383 76 386 384 386 118 385 96 387 386 387 170 387 98 388 388 388 23 389 62 389 390 389 75 391 76 390 392 390 127 393 28 391 394 391 179 395 163 392 396 392 177 397 97 393 398 393 146 399 192 394 400 394 136 401 140 395 402 395 132 403 153 396 404 396 117 405 118 397 406 397 68 407 170 398 408 398 70 409 23 399 410 399 90 411 75 400 412 400 80 413 127 401 414 401 30 415 179 402 416 402 92 417 177 403 418 403 31 419 148 404 420 404 166 421 136 405 422 405 137 423 132 406 424 406 180 425 128 407 426 407 59 427 2 408 428 408 120 429 54 409 430 409 172 431 34 410 432 410 25 433 36 411 434 411 77 435 30 412 436 412 129 437 64 413 438 413 181 439 87 414 440 414 174 441 166 415 442 415 184 443 137 416 444 416 192 445 9 417 446 417 27 447 59 418 448 418 47 449 120 419 450 419 30 451 172 420 452 420 60 453 25 421 454 421 78 455 77 422 456 422 100 457 129 423 458 423 58 459 181 424 460 424 38 461 174 425 462 425 188 463 184 426 464 426 148 465 17 427 466 427 138 467 27 428 468 428 45 469 47 429 470 429 128 471 6 430 472 430 122 473 32 431 474 431 174 475 86 432 476 432 27 477 32 433 478 433 79 479 64 434 480 434 131 481 56 435 482 435 183 483 188 436 484 436 87 485 137 437 486 437 115 487 138 438 488 438 111 489 45 439 490 439 9 491 51 440 492 440 89 493 122 441 494 441 46 495 174 442 496 442 12 497 27 443 498 443 96 499 79 444 500 444 94 501 131 445 502 445 88 503 183 446 504 446 100 505 127 447 506 447 145 507 115 448 508 448 156 509 111 449 510 449 17 511 122 450 512 450 150 513 89 451 514 451 171 515 4 452 516 452 124 517 48 453 518 453 176 519 28 454 520 454 29 521 48 455 522 455 81 523 44 456 524 456 133 525 34 457 526 457 185 527 145 458 528 458 163 529 156 459 530 459 137 531 187 460 532 460 116 533 150 461 534 461 125 535 171 462 536 462 51 537 124 463 538 463 62 539 176 464 540 464 14 541 29 465 542 465 36 543 81 466 544 466 68 545 133 467 546 467 48 547 185 468 548 468 98 549 163 469 550 469 127 551 53 470 552 470 124 553 116 471 554 471 136 555 125 472 556 472 122 557 29 473 558 473 77 559 14 474 560 474 126 561 64 475 562 475 178 563 26 476 564 476 31 565 38 477 566 477 83 567 38 478 568 478 135 569 42 479 570 479 187 571 156 480 572 480 7 573 124 481 574 481 3 575 136 482 576 482 187 577 104 483 578 483 158 579 77 484 580 484 37 581 126 485 582 485 78 583 178 486 584 486 2 585 31 487 586 487 52 587 83 488 588 488 4 589 135 489 590 489 66 591 187 490 592 490 78 593 7 491 594 491 164 595 3 492 596 492 53 597 120 493 598 493 129 599 158 494 600 494 63 601 37 495 602 495 29 603 12 496 604 496 128 605 80 497 606 497 180 607 26 498 608 498 33 609 54 499 610 499 85 611 20 500 612 500 137 613 86 501 614 501 189 615 164 502 616 502 156 617 79 503 618 503 97 619 129 504 620 504 157 621 63 505 622 505 104 623 71 506 624 506 189 625 128 507 626 507 94 627 180 508 628 508 8 629 33 509 630 509 68 631 85 510 632 510 6 633 137 511 634 511 42 635 189 512 636 512 76 637 178 513 638 513 65 639 97 514 640 514 115 641 157 515 642 515 120 643 107 516 644 516 168 645 189 517 646 517 106 647 72 518 648 518 130 649 96 519 650 519 182 651 28 520 652 520 35 653 70 521 654 521 87 655 22 522 656 522 139 657 191 523 658 523 185 659 65 524 660 524 181 661 115 525 662 525 79 663 49 526 664 526 67 665 168 527 666 527 162 667 106 528 668 528 71 669 130 529 670 529 64 671 182 530 672 530 10 673 35 531 674 531 84 675 87 532 676 532 12 677 139 533 678 533 58 679 185 534 680 534 1 681 181 535 682 535 178 683 142 536 684 536 108 685 67 537 686 537 154 687 162 538 688 538 107 689 41 539 690 539 59 691 80 540 692 540 132 693 82 541 694 541 184 695 28 542 696 542 37 697 86 543 698 543 89 699 22 544 700 544 141 701 1 545 702 545 191 703 170 546 704 546 188 705 108 547 706 547 21 707 154 548 708 548 49 709 176 549 710 549 51 711 59 550 712 550 77 713 132 551 714 551 46 715 184 552 716 552 74 717 37 553 718 553 100 719 89 554 720 554 14 721 141 555 722 555 74 723 15 556 724 556 101 725 188 557 726 557 7 727 21 558 728 558 142 729 139 559 730 559 147 731 51 560 732 560 81 733 77 561 734 561 41 735 40 562 736 562 134 737 90 563 738 563 186 739 92 564 740 564 39 741 40 565 742 565 91 743 24 566 744 566 143 745 101 567 746 567 73 747 7 568 748 568 170 749 102 569 750 569 176 751 147 570 752 570 194 753 81 571 754 571 176 755 177 572 756 572 118 757 134 573 758 573 84 759 186 574 760 574 46 761 39 575 762 575 84 763 91 576 764 576 74 765 143 577 766 577 90 767 73 578 768 578 15 769 83 579 770 579 123 771 176 580 772 580 133 773 194 581 774 581 139 775 132 582 776 582 150 777 118 583 778 583 33 779 50 584 780 584 136 781 70 585 782 585 188 783 100 586 784 586 41 785 80 587 786 587 93 787 30 588 788 588 145 789 149 589 790 589 167 791 123 590 792 590 110 793 133 591 794 591 102 795 85 592 796 592 104 797 150 593 798 593 168 799 33 594 800 594 177 801 136 595 802 595 42 803 188 596 804 596 32 805 41 597 806 597 94 807 93 598 808 598 72 809 145 599 810 599 52 811 167 600 812 600 185 813 110 601 814 601 83 815 179 602 816 602 39 817 104 603 818 603 172 819 168 604 820 604 132 821 125 605 822 605 85 823 30 606 824 606 138 825 60 607 826 607 190 827 78 608 828 608 43 829 88 609 830 609 95 831 58 610 832 610 147 833 185 611 834 611 149 835 11 612 836 612 144 837 39 613 838 613 35 839 172 614 840 614 85 841 33 615 842 615 99 843 85 616 844 616 95 845 138 617 846 617 10 847 190 618 848 618 52 849 43 619 850 619 44 851 95 620 852 620 54 853 147 621 854 621 82 855 119 622 856 622 159 857 144 623 858 623 25 859 35 624 860 624 179 861 111 625 862 625 129 863 99 626 864 626 175 865 95 627 866 627 125 867 46 628 868 628 140 869 24 629 870 629 191 871 96 630 872 630 45 873 94 631 874 631 97 875 98 632 876 632 149 877 159 633 878 633 91 879 25 634 880 634 11 881 158 635 882 635 151 883 129 636 884 636 147 885 175 637 886 637 33 887 171 638 888 638 189 889 140 639 890 639 8 891 191 640 892 640 34 893 45 641 894 641 50 895 97 642 896 642 42 897 149 643 898 643 56 899 91 644 900 644 119 901 105 645 902 645 130 903 151 646 904 646 161 905 147 647 906 647 111 907 161 648 908 648 122 909 189 649 910 649 118 911 62 650 912 650 142 913 20 651 914 651 192 915 36 652 916 652 47 917 68 653 918 653 99 919 48 654 920 654 151 921 47 655 922 655 119 923 130 656 924 656 160 925 161 657 926 657 158 927 175 658 928 658 190 929 122 659 930 659 186 931 118 660 932 660 171 933 142 661 934 661 8 935 192 662 936 662 50 937 47 663 938 663 12 939 99 664 940 664 60 941 151 665 942 665 90 943 119 666 944 666 114 945 160 667 946 667 105 947 3 668 948 668 21 949 190 669 950 669 31 951 186 670 952 670 161 953 67 671 954 671 113 955 78 672 956 672 144 957 22 673 958 673 193 959 52 674 960 674 49 961 4 675 962 675 101 963 66 676 964 676 153 965 114 677 966 677 47 967 126 678 968 678 43 969 21 679 970 679 39 971 31 680 972 680 175 973 63 681 974 681 81 975 113 682 976 682 178 977 144 683 978 683 16 979 193 684 980 684 66 981 49 685 982 685 2 983 101 686 984 686 40 985 153 687 986 687 58 987 141 688 988 688 109 989 43 689 990 689 102 991 39 690 992 690 3 993 108 691 994 691 103 995 81 692 996 692 99 997 178 693 998 693 67 999 94 694 1000 694 146 1001 22 695 1002 695 194 1003 68 696 1004 696 51 1005 8 697 1006 697 103 1007 42 698 1008 698 155 1009 109 699 1010 699 193 1011 102 700 1012 700 126 1013 143 701 1014 701 169 1015 103 702 1016 702 165 1017 99 703 1018 703 63 1019 160 704 1020 704 131 1021 146 705 1022 705 16 1023 194 706 1024 706 82 1025 51 707 1026 707 14 1027 103 708 1028 708 56 1029 155 709 1030 709 16 1031 193 710 1032 710 141 1033 181 711 1034 711 110 1035 169 712 1036 712 173 1037 165 713 1038 713 108 1039 140 714 1040 714 19 1041 131 715 1042 715 107 1043 74 716 1044 716 148 1045 24 717 1046 717 1 1047 84 718 1048 718 53 1049 18 719 1050 719 105 1051 58 720 1052 720 157 1053 101 721 1054 721 134 1055 110 722 1056 722 130 1057 173 723 1058 723 143 1059 154 724 1060 724 172 1061 19 725 1062 725 5 1063 107 726 1064 726 160 1065 148 727 1066 727 70 1067 1 728 1068 728 98 1069 53 729 1070 729 4 1071 105 730 1072 730 72 1073 157 731 1074 731 2 1075 134 732 1076 732 53 1077 130 733 1078 733 181 1079 123 734 1080 734 124 1081 172 735 1082 735 190 1083 5 736 1084 736 140 1085 95 737 1086 737 113 1087 46 738 1088 738 150 1089 84 739 1090 739 3 1091 100 740 1092 740 55 1093 18 741 1094 741 107 1095 74 742 1096 742 159 1097 53 743 1098 743 101 1099 173 744 1100 744 61 1101 124 745 1102 745 57 1103 190 746 1104 746 154 1105 157 747 1106 747 65 1107 113 748 1108 748 131 1109 150 749 1110 749 88 1111 3 750 1112 750 62 1113 55 751 1114 751 16 1115 107 752 1116 752 88 1117 159 753 1118 753 12 1119 73 754 1120 754 91 1121 61 755 1122 755 191 1123 57 756 1124 756 123 1125 121 757 1126 757 55 1127 65 758 1128 758 117 1129 131 759 1130 759 95 1131 84 760 1132 760 152 1133 46 761 1134 761 5 1135 72 762 1136 762 57 1137 74 763 1138 763 109 1139 90 764 1140 764 161 1141 91 765 1142 765 109 1143 191 766 1144 766 173 1145 133 767 1146 767 151 1147 55 768 1148 768 183 1149 117 769 1150 769 157 1151 19 770 1152 770 103 1153 500 416 1154 416 651 1155 651 500 1156 640 457 1157 457 523 1158 523 640 1159 394 405 1160 405 416 1161 416 394 1162 468 728 1163 728 534 1164 534 468 1165 333 617 1166 617 427 1167 427 333 1168 717 545 1169 545 629 1170 629 717 1171 606 438 1172 438 222 1173 222 606 1174 523 534 1175 534 545 1176 545 523 1177 210 449 1178 449 322 1179 322 210 1180 311 675 1181 675 556 1182 556 311 1183 427 438 1184 438 449 1185 449 427 1186 686 378 1187 378 567 1188 567 686 1189 490 364 1190 364 460 1191 460 490 1192 367 578 1193 578 300 1194 300 367 1195 375 584 1196 584 471 1197 471 375 1198 556 567 1199 567 578 1200 578 556 1201 595 479 1202 479 482 1203 482 595 1204 643 259 1205 259 589 1206 589 643 1207 460 471 1208 471 482 1209 482 460 1210 270 457 1211 457 600 1212 600 270 1213 419 412 1214 412 493 1215 493 419 1216 468 632 1217 632 611 1218 611 468 1219 423 720 1220 720 504 1221 504 423 1222 589 600 1223 600 611 1224 611 589 1225 731 408 1226 408 515 1227 515 731 1228 313 753 1229 753 622 1230 622 313 1231 493 504 1232 504 515 1233 515 493 1234 742 633 1235 633 576 1236 576 742 1237 685 301 1238 301 526 1239 526 685 1240 565 644 1241 644 302 1242 302 565 1243 312 219 1244 219 537 1245 537 312 1246 622 633 1247 633 644 1248 644 622 1249 207 548 1250 548 674 1251 674 207 1252 663 313 1253 313 655 1254 655 663 1255 526 537 1256 537 548 1257 548 526 1258 302 666 1259 666 353 1260 353 302 1261 533 610 1262 610 559 1263 559 533 1264 342 677 1265 677 652 1266 652 342 1267 621 706 1268 706 570 1269 570 621 1270 655 666 1271 666 677 1272 677 655 1273 695 581 1274 581 522 1275 522 695 1276 555 763 1277 763 688 1278 688 555 1279 559 570 1280 570 581 1281 581 559 1282 198 684 1283 684 699 1284 699 198 1285 510 242 1286 242 592 1287 592 510 1288 673 710 1289 710 544 1290 544 673 1291 230 603 1292 603 420 1293 420 230 1294 688 699 1295 699 710 1296 710 688 1297 409 614 1298 614 499 1299 499 409 1300 686 562 1301 562 721 1302 721 686 1303 592 603 1304 603 614 1305 614 592 1306 573 718 1307 718 732 1308 732 573 1309 222 412 1310 412 625 1311 625 222 1312 729 675 1313 675 743 1314 743 729 1315 423 610 1316 610 636 1317 636 423 1318 721 732 1319 732 743 1320 743 721 1321 621 210 1322 210 647 1323 647 621 1324 378 565 1325 565 754 1326 754 378 1327 625 636 1328 636 647 1329 647 625 1330 576 763 1331 763 765 1332 765 576 1333 358 607 1334 607 658 1335 658 358 1336 198 367 1337 367 200 1338 200 198 1339 618 487 1340 487 669 1341 669 618 1342 754 765 1343 765 200 1344 200 754 1345 476 680 1346 680 347 1347 347 476 1348 531 760 1349 760 212 1350 212 531 1351 658 669 1352 669 680 1353 680 658 1354 195 465 1355 465 224 1356 224 195 1357 287 697 1358 697 691 1359 691 287 1360 454 236 1361 236 520 1362 520 454 1363 708 247 1364 247 702 1365 702 708 1366 212 224 1367 224 236 1368 236 212 1369 235 713 1370 713 276 1371 276 235 1372 265 617 1373 617 248 1374 248 265 1375 691 702 1376 702 713 1377 713 691 1378 606 260 1379 260 401 1380 401 606 1381 219 409 1382 409 724 1383 724 219 1384 390 271 1385 271 254 1386 254 390 1387 420 607 1388 607 735 1389 735 420 1390 248 260 1391 260 271 1392 271 248 1393 618 207 1394 207 746 1395 746 618 1396 353 562 1397 562 282 1398 282 353 1399 724 735 1400 735 746 1401 746 724 1402 573 760 1403 760 293 1404 293 573 1405 335 751 1406 751 757 1407 757 335 1408 195 342 1409 342 304 1410 304 195 1411 740 768 1412 768 446 1413 446 740 1414 282 293 1415 293 304 1416 304 282 1417 435 203 1418 203 324 1419 324 435 1420 419 588 1421 588 315 1422 315 419 1423 757 768 1424 768 203 1425 203 757 1426 599 674 1427 674 326 1428 326 599 1429 289 554 1430 554 215 1431 215 289 1432 685 408 1433 408 337 1434 337 685 1435 543 227 1436 227 244 1437 244 543 1438 315 326 1439 326 337 1440 337 315 1441 232 239 1442 239 278 1443 278 232 1444 530 323 1445 323 348 1446 348 530 1447 215 227 1448 227 239 1449 239 215 1450 334 398 1451 398 359 1452 359 334 1453 553 320 1454 320 251 1455 251 553 1456 387 370 1457 370 519 1458 519 387 1459 331 402 1460 402 263 1461 263 331 1462 348 359 1463 359 370 1464 370 348 1465 391 274 1466 274 542 1467 542 391 1468 487 211 1469 211 381 1470 381 487 1471 251 263 1472 263 274 1473 274 251 1474 223 380 1475 380 392 1476 392 223 1477 356 432 1478 432 285 1479 285 356 1480 369 403 1481 403 476 1482 476 369 1483 443 519 1484 519 296 1485 296 443 1486 381 392 1487 392 403 1488 403 381 1489 530 345 1490 345 307 1491 307 530 1492 532 442 1493 442 414 1494 414 532 1495 285 296 1496 296 307 1497 307 285 1498 431 425 1499 425 596 1500 596 431 1501 247 435 1502 435 318 1503 318 247 1504 585 436 1505 436 521 1506 521 585 1507 446 320 1508 320 329 1509 329 446 1510 414 425 1511 425 436 1512 436 414 1513 331 235 1514 235 340 1515 340 331 1516 401 588 1517 588 447 1518 447 401 1519 318 329 1520 329 340 1521 340 318 1522 599 211 1523 211 458 1524 458 599 1525 332 630 1526 630 351 1527 351 332 1528 223 390 1529 390 469 1530 469 223 1531 641 662 1532 662 362 1533 362 641 1534 447 458 1535 458 469 1536 469 447 1537 651 373 1538 373 321 1539 321 651 1540 243 208 1541 208 480 1542 480 243 1543 351 362 1544 362 373 1545 373 351 1546 220 332 1547 332 491 1548 491 220 1549 508 639 1550 639 384 1551 384 508 1552 321 502 1553 502 231 1554 231 321 1555 628 395 1556 395 551 1557 551 628 1558 480 491 1559 491 502 1560 502 480 1561 540 406 1562 406 497 1563 497 540 1564 486 279 1565 279 513 1566 513 486 1567 384 395 1568 395 406 1569 406 384 1570 290 424 1571 424 524 1572 524 290 1573 244 432 1574 432 417 1575 417 244 1576 413 535 1577 535 475 1578 475 413 1579 443 630 1580 630 428 1581 428 443 1582 513 524 1583 524 535 1584 535 513 1585 641 232 1586 232 439 1587 439 641 1588 398 585 1589 585 546 1590 546 398 1591 417 428 1592 428 439 1593 439 417 1594 596 208 1595 208 557 1596 557 596 1597 441 738 1598 738 450 1599 450 441 1600 220 387 1601 387 568 1602 568 220 1603 749 368 1604 368 461 1605 461 749 1606 546 557 1607 557 568 1608 568 546 1609 379 430 1610 430 472 1611 472 379 1612 488 357 1613 357 579 1614 579 488 1615 450 461 1616 461 472 1617 472 450 1618 346 590 1619 590 309 1620 309 346 1621 242 255 1622 255 483 1623 483 242 1624 298 601 1625 601 477 1626 477 298 1627 266 268 1628 268 494 1629 494 266 1630 579 590 1631 590 601 1632 601 579 1633 257 505 1634 505 230 1635 230 257 1636 267 683 1637 683 612 1638 612 267 1639 483 494 1640 494 505 1641 505 483 1642 672 623 1643 623 421 1644 421 672 1645 752 365 1646 365 516 1647 516 752 1648 410 634 1649 634 256 1650 256 410 1651 376 310 1652 310 527 1653 527 376 1654 612 623 1655 623 634 1656 634 612 1657 299 538 1658 538 741 1659 741 299 1660 730 518 1661 518 645 1662 645 730 1663 516 527 1664 527 538 1665 538 516 1666 529 288 1667 288 656 1668 656 529 1669 464 707 1670 707 549 1671 549 464 1672 277 667 1673 667 719 1674 719 277 1675 696 560 1676 560 466 1677 466 696 1678 645 656 1679 656 667 1680 667 645 1681 455 571 1682 571 453 1683 453 455 1684 485 608 1685 608 678 1686 678 485 1687 549 560 1688 560 571 1689 571 549 1690 619 206 1691 206 689 1692 689 619 1693 551 738 1694 738 582 1695 582 551 1696 218 474 1697 474 700 1698 700 218 1699 749 365 1700 365 593 1701 593 749 1702 678 689 1703 689 700 1704 700 678 1705 376 540 1706 540 604 1707 604 376 1708 424 298 1709 298 711 1710 711 424 1711 582 593 1712 593 604 1713 604 582 1714 309 518 1715 518 722 1716 722 309 1717 509 653 1718 653 615 1719 615 509 1720 529 413 1721 413 733 1722 733 529 1723 664 358 1724 358 626 1725 626 664 1726 711 722 1727 722 733 1728 733 711 1729 347 637 1730 637 498 1731 498 347 1732 336 233 1733 233 744 1734 744 336 1735 615 626 1736 626 637 1737 637 615 1738 245 640 1739 640 755 1740 755 245 1741 199 430 1742 430 648 1743 648 199 1744 629 766 1745 766 325 1746 325 629 1747 441 574 1748 574 659 1749 659 441 1750 744 755 1751 755 766 1752 766 744 1753 563 670 1754 670 764 1755 764 563 1756 753 532 1757 532 201 1758 201 753 1759 648 659 1760 659 670 1761 670 648 1762 521 213 1763 213 727 1764 727 521 1765 268 455 1766 455 681 1767 681 268 1768 716 225 1769 225 742 1770 742 716 1771 466 653 1772 653 692 1773 692 466 1774 201 213 1775 213 225 1776 225 201 1777 664 257 1778 257 703 1779 703 664 1780 421 608 1781 608 237 1782 237 421 1783 681 692 1784 692 703 1785 703 681 1786 619 233 1787 233 249 1788 249 619 1789 639 355 1790 355 714 1791 714 639 1792 245 410 1793 410 261 1794 261 245 1795 344 725 1796 725 196 1797 196 344 1798 237 249 1799 249 261 1800 261 237 1801 761 736 1802 736 628 1803 628 761 1804 334 343 1805 343 272 1806 272 334 1807 714 725 1808 725 736 1809 736 714 1810 354 322 1811 322 283 1812 283 354 1813 731 279 1814 279 747 1815 747 731 1816 333 323 1817 323 294 1818 294 333 1819 290 291 1820 291 758 1821 758 290 1822 272 283 1823 283 294 1824 294 272 1825 280 769 1826 769 720 1827 720 280 1828 442 496 1829 496 305 1830 305 442 1831 747 758 1832 758 769 1833 769 747 1834 507 444 1835 444 316 1836 316 507 1837 728 388 1838 388 204 1839 204 728 1840 433 327 1841 327 431 1842 431 433 1843 399 577 1844 577 216 1845 216 399 1846 305 316 1847 316 327 1848 327 305 1849 566 228 1850 228 717 1851 717 566 1852 706 541 1853 541 338 1854 338 706 1855 204 216 1856 216 228 1857 228 204 1858 552 555 1859 555 349 1860 349 552 1861 488 311 1862 311 240 1863 240 488 1864 544 360 1865 360 695 1866 695 544 1867 300 252 1868 252 489 1869 489 300 1870 338 349 1871 349 360 1872 360 338 1873 478 264 1874 264 477 1875 477 478 1876 705 709 1877 709 371 1878 371 705 1879 240 252 1880 252 264 1881 264 240 1882 698 382 1883 382 642 1884 642 698 1885 574 761 1886 761 275 1887 275 574 1888 631 393 1889 393 694 1890 694 631 1891 196 388 1892 388 286 1893 286 196 1894 371 382 1895 382 393 1896 393 371 1897 399 563 1898 563 297 1899 297 399 1900 727 343 1901 343 404 1902 404 727 1903 275 286 1904 286 297 1905 297 275 1906 354 541 1907 541 415 1908 415 354 1909 684 676 1910 676 308 1911 308 684 1912 552 716 1913 716 426 1914 426 552 1915 687 533 1916 533 319 1917 319 687 1918 404 415 1919 415 426 1920 426 404 1921 522 330 1922 330 673 1923 673 522 1924 511 258 1925 258 437 1926 437 511 1927 308 319 1928 319 330 1929 330 308 1930 269 243 1931 243 448 1932 448 269 1933 751 705 1934 705 341 1935 341 751 1936 231 459 1937 459 500 1938 500 231 1939 694 352 1940 352 597 1941 597 694 1942 437 448 1943 448 459 1944 459 437 1945 586 363 1946 363 740 1947 740 586 1948 729 452 1949 452 470 1950 470 729 1951 341 352 1952 352 363 1953 363 341 1954 463 750 1955 750 481 1956 481 463 1957 291 478 1958 478 374 1959 374 291 1960 739 492 1961 492 718 1962 718 739 1963 489 676 1964 676 385 1965 385 489 1966 470 481 1967 481 492 1968 492 470 1969 687 280 1970 280 396 1971 396 687 1972 444 631 1973 631 503 1974 503 444 1975 374 385 1976 385 396 1977 396 374 1978 642 258 1979 258 514 1980 514 642 1981 507 209 1982 209 407 1983 407 507 1984 269 433 1985 433 525 1986 525 269 1987 221 652 1988 652 418 1989 418 221 1990 503 514 1991 514 525 1992 525 503 1993 663 496 1994 496 429 1995 429 663 1996 661 287 1997 287 536 1998 536 661 1999 407 418 2000 418 429 2001 429 407 2002 276 547 2003 547 377 2004 377 276 2005 707 554 2006 554 440 2007 440 707 2008 366 558 2009 558 650 2010 650 366 2011 543 451 2012 451 314 2013 314 543 2014 536 547 2015 547 558 2016 558 536 2017 303 462 2018 462 696 2019 696 303 2020 218 464 2021 464 569 2022 569 218 2023 440 451 2024 451 462 2025 462 440 2026 453 580 2027 580 467 2028 467 453 2029 465 411 2030 411 473 2031 473 465 2032 456 591 2033 591 206 2034 206 456 2035 422 553 2036 553 484 2037 484 422 2038 569 580 2039 580 591 2040 591 569 2041 542 495 2042 495 454 2043 454 542 2044 402 564 2045 564 602 2046 602 402 2047 473 484 2048 484 495 2049 495 473 2050 575 531 2051 531 613 2052 613 575 2053 356 501 2054 501 506 2055 506 356 2056 520 624 2057 624 391 2058 391 520 2059 512 254 2060 254 517 2061 517 512 2062 602 613 2063 613 624 2064 624 602 2065 265 345 2066 345 528 2067 528 265 2068 266 654 2069 654 635 2070 635 266 2071 506 517 2072 517 528 2073 528 506 2074 665 764 2075 764 646 2076 646 665 2077 597 209 2078 209 539 2079 539 597 2080 199 255 2081 255 657 2082 657 199 2083 221 411 2084 411 550 2085 550 221 2086 635 646 2087 646 657 2088 657 635 2089 422 586 2090 586 561 2091 561 422 2092 750 366 2093 366 668 2094 668 750 2095 539 550 2096 550 561 2097 561 539 2098 377 564 2099 564 679 2100 679 377 2101 380 386 2102 386 572 2103 572 380 2104 575 739 2105 739 690 2106 690 575 2107 397 509 2108 509 583 2109 583 397 2110 668 679 2111 679 690 2112 690 668 2113 498 594 2114 594 369 2115 369 498 2116 577 281 2117 281 701 2118 701 577 2119 572 583 2120 583 594 2121 594 572 2122 292 336 2123 336 712 2124 712 292 2125 379 510 2126 510 605 2127 605 379 2128 325 723 2129 723 566 2130 566 325 2131 499 616 2132 616 620 2133 620 499 2134 701 712 2135 712 723 2136 723 701 2137 609 627 2138 627 368 2139 368 609 2140 357 452 2141 452 734 2142 734 357 2143 605 616 2144 616 627 2145 627 605 2146 463 197 2147 197 745 2148 745 463 2149 314 501 2150 501 638 2151 638 314 2152 762 756 2153 756 346 2154 346 762 2155 512 386 2156 386 649 2157 649 512 2158 734 745 2159 745 756 2160 756 734 2161 397 303 2162 303 660 2163 660 397 2164 467 654 2165 654 767 2166 767 467 2167 638 649 2168 649 660 2169 660 638 2170 665 281 2171 281 202 2172 202 665 2173 312 234 2174 234 671 2175 671 312 2176 292 456 2177 456 214 2178 214 292 2179 246 475 2180 475 682 2181 682 246 2182 767 202 2183 202 214 2184 214 767 2185 486 301 2186 301 693 2187 693 486 2188 661 508 2189 508 226 2190 226 661 2191 671 682 2192 682 693 2193 693 671 2194 497 238 2195 238 400 2196 400 497 2197 288 434 2198 434 704 2199 704 288 2200 389 250 2201 250 650 2202 650 389 2203 445 752 2204 752 715 2205 715 445 2206 226 238 2207 238 250 2208 250 226 2209 741 726 2210 726 277 2211 277 741 2212 709 683 2213 683 262 2214 262 709 2215 704 715 2216 715 726 2217 726 704 2218 672 273 2219 273 490 2220 490 672 2221 620 234 2222 234 737 2223 737 620 2224 479 284 2225 284 698 2226 698 479 2227 246 434 2228 434 748 2229 748 246 2230 262 273 2231 273 284 2232 284 262 2233 445 609 2234 609 759 2235 759 445 2236 310 587 2237 587 295 2238 295 310 2239 737 748 2240 748 759 2241 759 737 2242 598 730 2243 730 306 2244 306 598 2245 355 697 2246 697 770 2247 770 355 2248 719 317 2249 317 299 2250 299 719 2251 708 643 2252 643 205 2253 205 708 2254 295 306 2255 306 317 2256 317 295 2257 632 217 2258 217 344 2259 344 632 2260 485 364 2261 364 328 2262 328 485 2263 770 205 2264 205 217 2265 217 770 2266 375 278 2267 278 339 2268 339 375 2269 335 267 2270 267 229 2271 229 335 2272 289 474 2273 474 350 2274 350 289 2275 256 241 2276 241 270 2277 270 256 2278 328 339 2279 339 350 2280 350 328 2281 259 253 2282 253 324 2283 324 259 2284 197 389 2285 389 361 2286 361 197 2287 229 241 2288 241 253 2289 253 229 2290 400 587 2291 587 372 2292 372 400 2293 598 762 2294 762 383 2295 383 598 2296 361 372 2297 372 383 2298 383 361 2299 662 584 2300 584 394 2301 394 662 2302 595 511 2303 511 405 2304 405 595 faces 1 500 137 416 2 416 192 651 3 20 651 500 4 500 416 651 5 640 34 457 6 457 185 523 7 191 523 640 8 640 457 523 9 394 136 405 10 405 137 416 11 416 192 394 12 394 405 416 13 468 98 728 14 1 728 534 15 185 534 468 16 468 728 534 17 333 10 617 18 138 617 427 19 17 427 333 20 333 617 427 21 717 1 545 22 545 191 629 23 24 629 717 24 717 545 629 25 606 138 438 26 438 111 222 27 222 30 606 28 606 438 222 29 523 185 534 30 534 1 545 31 545 191 523 32 523 534 545 33 210 111 449 34 449 17 322 35 82 322 210 36 210 449 322 37 311 4 675 38 675 101 556 39 15 556 311 40 311 675 556 41 427 138 438 42 438 111 449 43 449 17 427 44 427 438 449 45 686 40 378 46 73 378 567 47 101 567 686 48 686 378 567 49 490 78 364 50 364 116 460 51 187 460 490 52 490 364 460 53 367 73 578 54 578 15 300 55 66 300 367 56 367 578 300 57 375 50 584 58 584 136 471 59 116 471 375 60 375 584 471 61 556 101 567 62 567 73 578 63 578 15 556 64 556 567 578 65 595 42 479 66 479 187 482 67 136 482 595 68 595 479 482 69 643 56 259 70 259 167 589 71 149 589 643 72 643 259 589 73 460 116 471 74 471 136 482 75 482 187 460 76 460 471 482 77 270 34 457 78 457 185 600 79 167 600 270 80 270 457 600 81 419 30 412 82 412 129 493 83 120 493 419 84 419 412 493 85 468 98 632 86 632 149 611 87 185 611 468 88 468 632 611 89 423 58 720 90 720 157 504 91 129 504 423 92 423 720 504 93 589 167 600 94 600 185 611 95 611 149 589 96 589 600 611 97 731 2 408 98 408 120 515 99 157 515 731 100 731 408 515 101 313 12 753 102 159 753 622 103 119 622 313 104 313 753 622 105 493 129 504 106 504 157 515 107 515 120 493 108 493 504 515 109 742 159 633 110 633 91 576 111 576 74 742 112 742 633 576 113 685 2 301 114 301 67 526 115 49 526 685 116 685 301 526 117 565 91 644 118 644 119 302 119 40 302 565 120 565 644 302 121 312 54 219 122 154 219 537 123 67 537 312 124 312 219 537 125 622 159 633 126 633 91 644 127 644 119 622 128 622 633 644 129 207 154 548 130 548 49 674 131 52 674 207 132 207 548 674 133 663 12 313 134 119 313 655 135 47 655 663 136 663 313 655 137 526 67 537 138 537 154 548 139 548 49 526 140 526 537 548 141 302 119 666 142 666 114 353 143 353 40 302 144 302 666 353 145 533 58 610 146 610 147 559 147 139 559 533 148 533 610 559 149 342 114 677 150 677 47 652 151 36 652 342 152 342 677 652 153 621 82 706 154 194 706 570 155 147 570 621 156 621 706 570 157 655 119 666 158 666 114 677 159 677 47 655 160 655 666 677 161 695 194 581 162 581 139 522 163 22 522 695 164 695 581 522 165 555 74 763 166 763 109 688 167 141 688 555 168 555 763 688 169 559 147 570 170 570 194 581 171 581 139 559 172 559 570 581 173 198 66 684 174 193 684 699 175 109 699 198 176 198 684 699 177 510 6 242 178 104 242 592 179 85 592 510 180 510 242 592 181 673 193 710 182 710 141 544 183 22 544 673 184 673 710 544 185 230 104 603 186 603 172 420 187 420 60 230 188 230 603 420 189 688 109 699 190 699 193 710 191 710 141 688 192 688 699 710 193 409 172 614 194 614 85 499 195 54 499 409 196 409 614 499 197 686 40 562 198 562 134 721 199 101 721 686 200 686 562 721 201 592 104 603 202 603 172 614 203 614 85 592 204 592 603 614 205 573 84 718 206 718 53 732 207 134 732 573 208 573 718 732 209 222 30 412 210 412 129 625 211 111 625 222 212 222 412 625 213 729 4 675 214 675 101 743 215 53 743 729 216 729 675 743 217 423 58 610 218 610 147 636 219 129 636 423 220 423 610 636 221 721 134 732 222 732 53 743 223 743 101 721 224 721 732 743 225 621 82 210 226 210 111 647 227 147 647 621 228 621 210 647 229 378 40 565 230 565 91 754 231 73 754 378 232 378 565 754 233 625 129 636 234 636 147 647 235 647 111 625 236 625 636 647 237 576 74 763 238 763 109 765 239 91 765 576 240 576 763 765 241 358 60 607 242 607 190 658 243 175 658 358 244 358 607 658 245 198 66 367 246 367 73 200 247 109 200 198 248 198 367 200 249 618 52 487 250 31 487 669 251 190 669 618 252 618 487 669 253 754 91 765 254 765 109 200 255 200 73 754 256 754 765 200 257 476 31 680 258 680 175 347 259 26 347 476 260 476 680 347 261 531 84 760 262 760 152 212 263 35 212 531 264 531 760 212 265 658 190 669 266 669 31 680 267 680 175 658 268 658 669 680 269 195 36 465 270 29 465 224 271 152 224 195 272 195 465 224 273 287 8 697 274 697 103 691 275 108 691 287 276 287 697 691 277 454 29 236 278 236 35 520 279 28 520 454 280 454 236 520 281 708 56 247 282 165 247 702 283 103 702 708 284 708 247 702 285 212 152 224 286 224 29 236 287 236 35 212 288 212 224 236 289 235 165 713 290 713 108 276 291 92 276 235 292 235 713 276 293 265 10 617 294 138 617 248 295 106 248 265 296 265 617 248 297 691 103 702 298 702 165 713 299 713 108 691 300 691 702 713 301 606 138 260 302 260 127 401 303 401 30 606 304 606 260 401 305 219 54 409 306 409 172 724 307 154 724 219 308 219 409 724 309 390 127 271 310 271 106 254 311 76 254 390 312 390 271 254 313 420 60 607 314 607 190 735 315 172 735 420 316 420 607 735 317 248 138 260 318 260 127 271 319 271 106 248 320 248 260 271 321 618 52 207 322 207 154 746 323 190 746 618 324 618 207 746 325 353 40 562 326 562 134 282 327 114 282 353 328 353 562 282 329 724 172 735 330 735 190 746 331 746 154 724 332 724 735 746 333 573 84 760 334 760 152 293 335 134 293 573 336 573 760 293 337 335 16 751 338 55 751 757 339 121 757 335 340 335 751 757 341 195 36 342 342 342 114 304 343 152 304 195 344 195 342 304 345 740 55 768 346 768 183 446 347 446 100 740 348 740 768 446 349 282 134 293 350 293 152 304 351 304 114 282 352 282 293 304 353 435 183 203 354 203 121 324 355 56 324 435 356 435 203 324 357 419 30 588 358 588 145 315 359 120 315 419 360 419 588 315 361 757 55 768 362 768 183 203 363 203 121 757 364 757 768 203 365 599 52 674 366 674 49 326 367 145 326 599 368 599 674 326 369 289 14 554 370 89 554 215 371 13 215 289 372 289 554 215 373 685 2 408 374 408 120 337 375 49 337 685 376 685 408 337 377 543 89 227 378 227 9 244 379 244 86 543 380 543 227 244 381 315 145 326 382 326 49 337 383 337 120 315 384 315 326 337 385 232 9 239 386 239 13 278 387 50 278 232 388 232 239 278 389 530 10 323 390 323 69 348 391 182 348 530 392 530 323 348 393 215 89 227 394 227 9 239 395 239 13 215 396 215 227 239 397 334 70 398 398 170 398 359 399 69 359 334 400 334 398 359 401 553 100 320 402 320 112 251 403 37 251 553 404 553 320 251 405 387 170 370 406 370 182 519 407 96 519 387 408 387 370 519 409 331 92 402 410 179 402 263 411 112 263 331 412 331 402 263 413 348 69 359 414 359 170 370 415 370 182 348 416 348 359 370 417 391 179 274 418 274 37 542 419 28 542 391 420 391 274 542 421 487 52 211 422 211 163 381 423 31 381 487 424 487 211 381 425 251 112 263 426 263 179 274 427 274 37 251 428 251 263 274 429 223 76 380 430 177 380 392 431 163 392 223 432 223 380 392 433 356 86 432 434 432 27 285 435 71 285 356 436 356 432 285 437 369 177 403 438 403 31 476 439 26 476 369 440 369 403 476 441 443 96 519 442 519 182 296 443 27 296 443 444 443 519 296 445 381 163 392 446 392 177 403 447 403 31 381 448 381 392 403 449 530 10 345 450 345 71 307 451 182 307 530 452 530 345 307 453 532 12 442 454 174 442 414 455 87 414 532 456 532 442 414 457 285 27 296 458 296 182 307 459 307 71 285 460 285 296 307 461 431 174 425 462 425 188 596 463 596 32 431 464 431 425 596 465 247 56 435 466 435 183 318 467 165 318 247 468 247 435 318 469 585 188 436 470 436 87 521 471 70 521 585 472 585 436 521 473 446 100 320 474 320 112 329 475 183 329 446 476 446 320 329 477 414 174 425 478 425 188 436 479 436 87 414 480 414 425 436 481 331 92 235 482 235 165 340 483 112 340 331 484 331 235 340 485 401 30 588 486 588 145 447 487 127 447 401 488 401 588 447 489 318 183 329 490 329 112 340 491 340 165 318 492 318 329 340 493 599 52 211 494 211 163 458 495 145 458 599 496 599 211 458 497 332 96 630 498 630 45 351 499 164 351 332 500 332 630 351 501 223 76 390 502 390 127 469 503 163 469 223 504 223 390 469 505 641 50 662 506 192 662 362 507 45 362 641 508 641 662 362 509 447 145 458 510 458 163 469 511 469 127 447 512 447 458 469 513 651 192 373 514 373 164 321 515 20 321 651 516 651 373 321 517 243 32 208 518 208 7 480 519 156 480 243 520 243 208 480 521 351 45 362 522 362 192 373 523 373 164 351 524 351 362 373 525 220 96 332 526 164 332 491 527 7 491 220 528 220 332 491 529 508 8 639 530 140 639 384 531 180 384 508 532 508 639 384 533 321 164 502 534 502 156 231 535 20 231 321 536 321 502 231 537 628 140 395 538 395 132 551 539 551 46 628 540 628 395 551 541 480 7 491 542 491 164 502 543 502 156 480 544 480 491 502 545 540 132 406 546 406 180 497 547 80 497 540 548 540 406 497 549 486 2 279 550 279 65 513 551 178 513 486 552 486 279 513 553 384 140 395 554 395 132 406 555 406 180 384 556 384 395 406 557 290 38 424 558 181 424 524 559 65 524 290 560 290 424 524 561 244 86 432 562 432 27 417 563 9 417 244 564 244 432 417 565 413 181 535 566 535 178 475 567 64 475 413 568 413 535 475 569 443 96 630 570 630 45 428 571 27 428 443 572 443 630 428 573 513 65 524 574 524 181 535 575 535 178 513 576 513 524 535 577 641 50 232 578 232 9 439 579 45 439 641 580 641 232 439 581 398 70 585 582 585 188 546 583 170 546 398 584 398 585 546 585 417 27 428 586 428 45 439 587 439 9 417 588 417 428 439 589 596 32 208 590 208 7 557 591 188 557 596 592 596 208 557 593 441 46 738 594 738 150 450 595 122 450 441 596 441 738 450 597 220 96 387 598 387 170 568 599 7 568 220 600 220 387 568 601 749 88 368 602 368 125 461 603 150 461 749 604 749 368 461 605 546 188 557 606 557 7 568 607 568 170 546 608 546 557 568 609 379 6 430 610 430 122 472 611 125 472 379 612 379 430 472 613 488 4 357 614 123 357 579 615 83 579 488 616 488 357 579 617 450 150 461 618 461 125 472 619 472 122 450 620 450 461 472 621 346 123 590 622 590 110 309 623 309 72 346 624 346 590 309 625 242 6 255 626 255 158 483 627 104 483 242 628 242 255 483 629 298 110 601 630 601 83 477 631 38 477 298 632 298 601 477 633 266 48 268 634 63 268 494 635 158 494 266 636 266 268 494 637 579 123 590 638 590 110 601 639 601 83 579 640 579 590 601 641 257 63 505 642 505 104 230 643 60 230 257 644 257 505 230 645 267 16 683 646 144 683 612 647 11 612 267 648 267 683 612 649 483 158 494 650 494 63 505 651 505 104 483 652 483 494 505 653 672 144 623 654 623 25 421 655 421 78 672 656 672 623 421 657 752 88 365 658 365 168 516 659 107 516 752 660 752 365 516 661 410 25 634 662 634 11 256 663 34 256 410 664 410 634 256 665 376 80 310 666 162 310 527 667 168 527 376 668 376 310 527 669 612 144 623 670 623 25 634 671 634 11 612 672 612 623 634 673 299 162 538 674 538 107 741 675 18 741 299 676 299 538 741 677 730 72 518 678 518 130 645 679 105 645 730 680 730 518 645 681 516 168 527 682 527 162 538 683 538 107 516 684 516 527 538 685 529 64 288 686 160 288 656 687 130 656 529 688 529 288 656 689 464 14 707 690 51 707 549 691 176 549 464 692 464 707 549 693 277 160 667 694 667 105 719 695 18 719 277 696 277 667 719 697 696 51 560 698 560 81 466 699 466 68 696 700 696 560 466 701 645 130 656 702 656 160 667 703 667 105 645 704 645 656 667 705 455 81 571 706 571 176 453 707 48 453 455 708 455 571 453 709 485 78 608 710 608 43 678 711 126 678 485 712 485 608 678 713 549 51 560 714 560 81 571 715 571 176 549 716 549 560 571 717 619 44 206 718 206 102 689 719 43 689 619 720 619 206 689 721 551 46 738 722 738 150 582 723 132 582 551 724 551 738 582 725 218 14 474 726 474 126 700 727 102 700 218 728 218 474 700 729 749 88 365 730 365 168 593 731 150 593 749 732 749 365 593 733 678 43 689 734 689 102 700 735 700 126 678 736 678 689 700 737 376 80 540 738 540 132 604 739 168 604 376 740 376 540 604 741 424 38 298 742 298 110 711 743 181 711 424 744 424 298 711 745 582 150 593 746 593 168 604 747 604 132 582 748 582 593 604 749 309 72 518 750 518 130 722 751 110 722 309 752 309 518 722 753 509 68 653 754 653 99 615 755 33 615 509 756 509 653 615 757 529 64 413 758 413 181 733 759 130 733 529 760 529 413 733 761 664 60 358 762 175 358 626 763 99 626 664 764 664 358 626 765 711 110 722 766 722 130 733 767 733 181 711 768 711 722 733 769 347 175 637 770 637 33 498 771 26 498 347 772 347 637 498 773 336 44 233 774 233 61 744 775 173 744 336 776 336 233 744 777 615 99 626 778 626 175 637 779 637 33 615 780 615 626 637 781 245 34 640 782 191 640 755 783 61 755 245 784 245 640 755 785 199 6 430 786 430 122 648 787 161 648 199 788 199 430 648 789 629 191 766 790 766 173 325 791 24 325 629 792 629 766 325 793 441 46 574 794 186 574 659 795 122 659 441 796 441 574 659 797 744 61 755 798 755 191 766 799 766 173 744 800 744 755 766 801 563 186 670 802 670 161 764 803 90 764 563 804 563 670 764 805 753 12 532 806 87 532 201 807 159 201 753 808 753 532 201 809 648 122 659 810 659 186 670 811 670 161 648 812 648 659 670 813 521 87 213 814 213 148 727 815 727 70 521 816 521 213 727 817 268 48 455 818 455 81 681 819 63 681 268 820 268 455 681 821 716 148 225 822 225 159 742 823 74 742 716 824 716 225 742 825 466 68 653 826 653 99 692 827 81 692 466 828 466 653 692 829 201 87 213 830 213 148 225 831 225 159 201 832 201 213 225 833 664 60 257 834 257 63 703 835 99 703 664 836 664 257 703 837 421 78 608 838 608 43 237 839 25 237 421 840 421 608 237 841 681 81 692 842 692 99 703 843 703 63 681 844 681 692 703 845 619 44 233 846 233 61 249 847 43 249 619 848 619 233 249 849 639 8 355 850 19 355 714 851 140 714 639 852 639 355 714 853 245 34 410 854 410 25 261 855 61 261 245 856 245 410 261 857 344 19 725 858 725 5 196 859 196 98 344 860 344 725 196 861 237 43 249 862 249 61 261 863 261 25 237 864 237 249 261 865 761 5 736 866 736 140 628 867 46 628 761 868 761 736 628 869 334 70 343 870 343 166 272 871 69 272 334 872 334 343 272 873 714 19 725 874 725 5 736 875 736 140 714 876 714 725 736 877 354 82 322 878 322 17 283 879 166 283 354 880 354 322 283 881 731 2 279 882 279 65 747 883 157 747 731 884 731 279 747 885 333 10 323 886 323 69 294 887 17 294 333 888 333 323 294 889 290 38 291 890 117 291 758 891 65 758 290 892 290 291 758 893 272 166 283 894 283 17 294 895 294 69 272 896 272 283 294 897 280 117 769 898 769 157 720 899 58 720 280 900 280 769 720 901 442 12 496 902 496 128 305 903 174 305 442 904 442 496 305 905 747 65 758 906 758 117 769 907 769 157 747 908 747 758 769 909 507 94 444 910 79 444 316 911 128 316 507 912 507 444 316 913 728 98 388 914 388 23 204 915 1 204 728 916 728 388 204 917 433 79 327 918 327 174 431 919 32 431 433 920 433 327 431 921 399 90 577 922 143 577 216 923 23 216 399 924 399 577 216 925 305 128 316 926 316 79 327 927 327 174 305 928 305 316 327 929 566 143 228 930 228 1 717 931 24 717 566 932 566 228 717 933 706 82 541 934 541 184 338 935 194 338 706 936 706 541 338 937 204 23 216 938 216 143 228 939 228 1 204 940 204 216 228 941 552 74 555 942 141 555 349 943 184 349 552 944 552 555 349 945 488 4 311 946 15 311 240 947 83 240 488 948 488 311 240 949 544 141 360 950 360 194 695 951 22 695 544 952 544 360 695 953 300 15 252 954 252 135 489 955 489 66 300 956 300 252 489 957 338 184 349 958 349 141 360 959 360 194 338 960 338 349 360 961 478 135 264 962 264 83 477 963 38 477 478 964 478 264 477 965 705 16 709 966 155 709 371 967 146 371 705 968 705 709 371 969 240 15 252 970 252 135 264 971 264 83 240 972 240 252 264 973 698 155 382 974 382 97 642 975 642 42 698 976 698 382 642 977 574 46 761 978 761 5 275 979 186 275 574 980 574 761 275 981 631 97 393 982 393 146 694 983 94 694 631 984 631 393 694 985 196 98 388 986 388 23 286 987 5 286 196 988 196 388 286 989 371 155 382 990 382 97 393 991 393 146 371 992 371 382 393 993 399 90 563 994 563 186 297 995 23 297 399 996 399 563 297 997 727 70 343 998 343 166 404 999 148 404 727 1000 727 343 404 1001 275 5 286 1002 286 23 297 1003 297 186 275 1004 275 286 297 1005 354 82 541 1006 541 184 415 1007 166 415 354 1008 354 541 415 1009 684 66 676 1010 676 153 308 1011 193 308 684 1012 684 676 308 1013 552 74 716 1014 716 148 426 1015 184 426 552 1016 552 716 426 1017 687 58 533 1018 139 533 319 1019 153 319 687 1020 687 533 319 1021 404 166 415 1022 415 184 426 1023 426 148 404 1024 404 415 426 1025 522 139 330 1026 330 193 673 1027 22 673 522 1028 522 330 673 1029 511 42 258 1030 258 115 437 1031 137 437 511 1032 511 258 437 1033 308 153 319 1034 319 139 330 1035 330 193 308 1036 308 319 330 1037 269 32 243 1038 156 243 448 1039 115 448 269 1040 269 243 448 1041 751 16 705 1042 146 705 341 1043 55 341 751 1044 751 705 341 1045 231 156 459 1046 459 137 500 1047 20 500 231 1048 231 459 500 1049 694 146 352 1050 352 41 597 1051 597 94 694 1052 694 352 597 1053 437 115 448 1054 448 156 459 1055 459 137 437 1056 437 448 459 1057 586 41 363 1058 363 55 740 1059 100 740 586 1060 586 363 740 1061 729 4 452 1062 452 124 470 1063 53 470 729 1064 729 452 470 1065 341 146 352 1066 352 41 363 1067 363 55 341 1068 341 352 363 1069 463 62 750 1070 3 750 481 1071 124 481 463 1072 463 750 481 1073 291 38 478 1074 478 135 374 1075 117 374 291 1076 291 478 374 1077 739 3 492 1078 492 53 718 1079 84 718 739 1080 739 492 718 1081 489 66 676 1082 676 153 385 1083 135 385 489 1084 489 676 385 1085 470 124 481 1086 481 3 492 1087 492 53 470 1088 470 481 492 1089 687 58 280 1090 280 117 396 1091 153 396 687 1092 687 280 396 1093 444 94 631 1094 631 97 503 1095 79 503 444 1096 444 631 503 1097 374 135 385 1098 385 153 396 1099 396 117 374 1100 374 385 396 1101 642 42 258 1102 258 115 514 1103 97 514 642 1104 642 258 514 1105 507 94 209 1106 209 59 407 1107 128 407 507 1108 507 209 407 1109 269 32 433 1110 433 79 525 1111 115 525 269 1112 269 433 525 1113 221 36 652 1114 652 47 418 1115 59 418 221 1116 221 652 418 1117 503 97 514 1118 514 115 525 1119 525 79 503 1120 503 514 525 1121 663 12 496 1122 496 128 429 1123 47 429 663 1124 663 496 429 1125 661 8 287 1126 108 287 536 1127 142 536 661 1128 661 287 536 1129 407 59 418 1130 418 47 429 1131 429 128 407 1132 407 418 429 1133 276 108 547 1134 547 21 377 1135 377 92 276 1136 276 547 377 1137 707 14 554 1138 89 554 440 1139 51 440 707 1140 707 554 440 1141 366 21 558 1142 558 142 650 1143 62 650 366 1144 366 558 650 1145 543 89 451 1146 451 171 314 1147 314 86 543 1148 543 451 314 1149 536 108 547 1150 547 21 558 1151 558 142 536 1152 536 547 558 1153 303 171 462 1154 462 51 696 1155 68 696 303 1156 303 462 696 1157 218 14 464 1158 176 464 569 1159 102 569 218 1160 218 464 569 1161 440 89 451 1162 451 171 462 1163 462 51 440 1164 440 451 462 1165 453 176 580 1166 580 133 467 1167 467 48 453 1168 453 580 467 1169 465 36 411 1170 411 77 473 1171 29 473 465 1172 465 411 473 1173 456 133 591 1174 591 102 206 1175 44 206 456 1176 456 591 206 1177 422 100 553 1178 37 553 484 1179 77 484 422 1180 422 553 484 1181 569 176 580 1182 580 133 591 1183 591 102 569 1184 569 580 591 1185 542 37 495 1186 495 29 454 1187 28 454 542 1188 542 495 454 1189 402 92 564 1190 564 39 602 1191 179 602 402 1192 402 564 602 1193 473 77 484 1194 484 37 495 1195 495 29 473 1196 473 484 495 1197 575 84 531 1198 35 531 613 1199 39 613 575 1200 575 531 613 1201 356 86 501 1202 501 189 506 1203 71 506 356 1204 356 501 506 1205 520 35 624 1206 624 179 391 1207 28 391 520 1208 520 624 391 1209 512 76 254 1210 254 106 517 1211 189 517 512 1212 512 254 517 1213 602 39 613 1214 613 35 624 1215 624 179 602 1216 602 613 624 1217 265 10 345 1218 345 71 528 1219 106 528 265 1220 265 345 528 1221 266 48 654 1222 654 151 635 1223 158 635 266 1224 266 654 635 1225 506 189 517 1226 517 106 528 1227 528 71 506 1228 506 517 528 1229 665 90 764 1230 764 161 646 1231 151 646 665 1232 665 764 646 1233 597 94 209 1234 209 59 539 1235 41 539 597 1236 597 209 539 1237 199 6 255 1238 255 158 657 1239 161 657 199 1240 199 255 657 1241 221 36 411 1242 411 77 550 1243 59 550 221 1244 221 411 550 1245 635 151 646 1246 646 161 657 1247 657 158 635 1248 635 646 657 1249 422 100 586 1250 586 41 561 1251 77 561 422 1252 422 586 561 1253 750 62 366 1254 366 21 668 1255 3 668 750 1256 750 366 668 1257 539 59 550 1258 550 77 561 1259 561 41 539 1260 539 550 561 1261 377 92 564 1262 564 39 679 1263 21 679 377 1264 377 564 679 1265 380 76 386 1266 386 118 572 1267 177 572 380 1268 380 386 572 1269 575 84 739 1270 739 3 690 1271 39 690 575 1272 575 739 690 1273 397 68 509 1274 33 509 583 1275 118 583 397 1276 397 509 583 1277 668 21 679 1278 679 39 690 1279 690 3 668 1280 668 679 690 1281 498 33 594 1282 594 177 369 1283 26 369 498 1284 498 594 369 1285 577 90 281 1286 281 169 701 1287 143 701 577 1288 577 281 701 1289 572 118 583 1290 583 33 594 1291 594 177 572 1292 572 583 594 1293 292 44 336 1294 173 336 712 1295 169 712 292 1296 292 336 712 1297 379 6 510 1298 85 510 605 1299 125 605 379 1300 379 510 605 1301 325 173 723 1302 723 143 566 1303 24 566 325 1304 325 723 566 1305 499 85 616 1306 616 95 620 1307 620 54 499 1308 499 616 620 1309 701 169 712 1310 712 173 723 1311 723 143 701 1312 701 712 723 1313 609 95 627 1314 627 125 368 1315 88 368 609 1316 609 627 368 1317 357 4 452 1318 452 124 734 1319 123 734 357 1320 357 452 734 1321 605 85 616 1322 616 95 627 1323 627 125 605 1324 605 616 627 1325 463 62 197 1326 57 197 745 1327 124 745 463 1328 463 197 745 1329 314 86 501 1330 501 189 638 1331 171 638 314 1332 314 501 638 1333 762 57 756 1334 756 123 346 1335 72 346 762 1336 762 756 346 1337 512 76 386 1338 386 118 649 1339 189 649 512 1340 512 386 649 1341 734 124 745 1342 745 57 756 1343 756 123 734 1344 734 745 756 1345 397 68 303 1346 303 171 660 1347 118 660 397 1348 397 303 660 1349 467 48 654 1350 654 151 767 1351 133 767 467 1352 467 654 767 1353 638 189 649 1354 649 118 660 1355 660 171 638 1356 638 649 660 1357 665 90 281 1358 281 169 202 1359 151 202 665 1360 665 281 202 1361 312 54 234 1362 234 113 671 1363 67 671 312 1364 312 234 671 1365 292 44 456 1366 456 133 214 1367 169 214 292 1368 292 456 214 1369 246 64 475 1370 475 178 682 1371 113 682 246 1372 246 475 682 1373 767 151 202 1374 202 169 214 1375 214 133 767 1376 767 202 214 1377 486 2 301 1378 301 67 693 1379 178 693 486 1380 486 301 693 1381 661 8 508 1382 180 508 226 1383 142 226 661 1384 661 508 226 1385 671 113 682 1386 682 178 693 1387 693 67 671 1388 671 682 693 1389 497 180 238 1390 238 75 400 1391 400 80 497 1392 497 238 400 1393 288 64 434 1394 434 131 704 1395 160 704 288 1396 288 434 704 1397 389 75 250 1398 250 142 650 1399 62 650 389 1400 389 250 650 1401 445 88 752 1402 107 752 715 1403 131 715 445 1404 445 752 715 1405 226 180 238 1406 238 75 250 1407 250 142 226 1408 226 238 250 1409 741 107 726 1410 726 160 277 1411 18 277 741 1412 741 726 277 1413 709 16 683 1414 144 683 262 1415 155 262 709 1416 709 683 262 1417 704 131 715 1418 715 107 726 1419 726 160 704 1420 704 715 726 1421 672 144 273 1422 273 187 490 1423 490 78 672 1424 672 273 490 1425 620 54 234 1426 234 113 737 1427 95 737 620 1428 620 234 737 1429 479 187 284 1430 284 155 698 1431 42 698 479 1432 479 284 698 1433 246 64 434 1434 434 131 748 1435 113 748 246 1436 246 434 748 1437 262 144 273 1438 273 187 284 1439 284 155 262 1440 262 273 284 1441 445 88 609 1442 609 95 759 1443 131 759 445 1444 445 609 759 1445 310 80 587 1446 587 93 295 1447 162 295 310 1448 310 587 295 1449 737 113 748 1450 748 131 759 1451 759 95 737 1452 737 748 759 1453 598 72 730 1454 105 730 306 1455 93 306 598 1456 598 730 306 1457 355 8 697 1458 697 103 770 1459 19 770 355 1460 355 697 770 1461 719 105 317 1462 317 162 299 1463 18 299 719 1464 719 317 299 1465 708 56 643 1466 149 643 205 1467 103 205 708 1468 708 643 205 1469 295 93 306 1470 306 105 317 1471 317 162 295 1472 295 306 317 1473 632 149 217 1474 217 19 344 1475 98 344 632 1476 632 217 344 1477 485 78 364 1478 364 116 328 1479 126 328 485 1480 485 364 328 1481 770 103 205 1482 205 149 217 1483 217 19 770 1484 770 205 217 1485 375 50 278 1486 278 13 339 1487 116 339 375 1488 375 278 339 1489 335 16 267 1490 11 267 229 1491 121 229 335 1492 335 267 229 1493 289 14 474 1494 474 126 350 1495 13 350 289 1496 289 474 350 1497 256 11 241 1498 241 167 270 1499 270 34 256 1500 256 241 270 1501 328 116 339 1502 339 13 350 1503 350 126 328 1504 328 339 350 1505 259 167 253 1506 253 121 324 1507 56 324 259 1508 259 253 324 1509 197 62 389 1510 389 75 361 1511 57 361 197 1512 197 389 361 1513 229 11 241 1514 241 167 253 1515 253 121 229 1516 229 241 253 1517 400 80 587 1518 587 93 372 1519 75 372 400 1520 400 587 372 1521 598 72 762 1522 762 57 383 1523 93 383 598 1524 598 762 383 1525 361 75 372 1526 372 93 383 1527 383 57 361 1528 361 372 383 1529 662 50 584 1530 584 136 394 1531 192 394 662 1532 662 584 394 1533 595 42 511 1534 137 511 405 1535 136 405 595 1536 595 511 405 ================================================ FILE: test/mesh/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/mesh/square_missingcoord.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/mesh/square_spurious_vertex_ref.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 10 ================================================ FILE: test/mesh/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 faces 1 1 2 3 2 2 3 4 3 3 4 1 4 2 1 4 ================================================ FILE: test/mesh/tetrahedron2.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 volumes 1 1 2 3 4 ================================================ FILE: test/mesh/vertexposition.morpho ================================================ var a = Mesh("tetrahedron.mesh") print a // expect: print a.vertexmatrix() // expect: [ 0 -0.288675 -0.288675 0.57735 ] // expect: [ 0 -0.5 0.5 0 ] // expect: [ 0.612372 -0.204124 -0.204124 -0.204124 ] print a.vertexposition(1) // expect: [ -0.288675 ] // expect: [ -0.5 ] // expect: [ -0.204124 ] a.setvertexposition(1, a.vertexposition(1)+Matrix([0.1,0.1,0.1])) print a.vertexmatrix() // expect: [ 0 -0.188675 -0.288675 0.57735 ] // expect: [ 0 -0.4 0.5 0 ] // expect: [ 0.612372 -0.104124 -0.204124 -0.204124 ] print a.vertexposition(5) // expect Error 'MshInvldId' ================================================ FILE: test/method/arity.morpho ================================================ class Foo { method0() { return "no args" } method1(a) { return a } method2(a, b) { return a + b } method3(a, b, c) { return a + b + c } method4(a, b, c, d) { return a + b + c + d } method5(a, b, c, d, e) { return a + b + c + d + e } method6(a, b, c, d, e, f) { return a + b + c + d + e + f } method7(a, b, c, d, e, f, g) { return a + b + c + d + e + f + g } method8(a, b, c, d, e, f, g, h) { return a + b + c + d + e + f + g + h } } var foo = Foo() print foo.method0() // expect: no args print foo.method1(1) // expect: 1 print foo.method2(1, 2) // expect: 3 print foo.method3(1, 2, 3) // expect: 6 print foo.method4(1, 2, 3, 4) // expect: 10 print foo.method5(1, 2, 3, 4, 5) // expect: 15 print foo.method6(1, 2, 3, 4, 5, 6) // expect: 21 print foo.method7(1, 2, 3, 4, 5, 6, 7) // expect: 28 print foo.method8(1, 2, 3, 4, 5, 6, 7, 8) // expect: 36 ================================================ FILE: test/method/empty_block.morpho ================================================ // Methods that don't return anything return self. class Foo { bar() {} } print Foo().bar() // expect: ================================================ FILE: test/method/extra_arguments.morpho ================================================ class Foo { method(a, b) { print a print b } } Foo().method(1, 2, 3, 4) // expect error 'InvldArgs' ================================================ FILE: test/method/missing_arguments.morpho ================================================ class Foo { method(a, b) {} } Foo().method(1) // expect error 'InvldArgs' ================================================ FILE: test/method/not_found.morpho ================================================ class Foo {} Foo().unknown() // expect error: 'ObjLcksPrp' ================================================ FILE: test/method/optional.morpho ================================================ // Optional arguments class Foo { func(x, y=true, z=2) { print x print y print z } } var f = Foo() f.func(1) // expect: 1 // expect: true // expect: 2 f.func(1, y=false) // expect: 1 // expect: false // expect: 2 f.func(1, z=3) // expect: 1 // expect: true // expect: 3 f.func(1, z=3, y=false) // expect: 1 // expect: false // expect: 3 f.func(1, 2, y=false) // expect error 'InvldArgs' ================================================ FILE: test/method/optional_indirect.morpho ================================================ // Optional arguments class Foo { func(x, y=true, z=2) { print x print y print z } } var f = Foo() var g = f.func g(1) // expect: 1 // expect: true // expect: 2 g(1, y=false) // expect: 1 // expect: false // expect: 2 g(1, z=3) // expect: 1 // expect: true // expect: 3 g(1, z=3, y=false) // expect: 1 // expect: false // expect: 3 g(1, 2, y=false) // expect error 'InvldArgs' ================================================ FILE: test/method/print_bound_method.morpho ================================================ class Foo { method() { } } var foo = Foo() print foo.method // expect: . ================================================ FILE: test/method/return_in_method.morpho ================================================ // Methods that don't return anything return self. class Foo { bar() { return } } print Foo().bar() // expect: ================================================ FILE: test/method/too_many_arguments.morpho ================================================ { var a = 1 true.method( a, // 1 a, // 2 a, // 3 a, // 4 a, // 5 a, // 6 a, // 7 a, // 8 a, // 9 a, // 10 a, // 11 a, // 12 a, // 13 a, // 14 a, // 15 a, // 16 a, // 17 a, // 18 a, // 19 a, // 20 a, // 21 a, // 22 a, // 23 a, // 24 a, // 25 a, // 26 a, // 27 a, // 28 a, // 29 a, // 30 a, // 31 a, // 32 a, // 33 a, // 34 a, // 35 a, // 36 a, // 37 a, // 38 a, // 39 a, // 40 a, // 41 a, // 42 a, // 43 a, // 44 a, // 45 a, // 46 a, // 47 a, // 48 a, // 49 a, // 50 a, // 51 a, // 52 a, // 53 a, // 54 a, // 55 a, // 56 a, // 57 a, // 58 a, // 59 a, // 60 a, // 61 a, // 62 a, // 63 a, // 64 a, // 65 a, // 66 a, // 67 a, // 68 a, // 69 a, // 70 a, // 71 a, // 72 a, // 73 a, // 74 a, // 75 a, // 76 a, // 77 a, // 78 a, // 79 a, // 80 a, // 81 a, // 82 a, // 83 a, // 84 a, // 85 a, // 86 a, // 87 a, // 88 a, // 89 a, // 90 a, // 91 a, // 92 a, // 93 a, // 94 a, // 95 a, // 96 a, // 97 a, // 98 a, // 99 a, // 100 a, // 101 a, // 102 a, // 103 a, // 104 a, // 105 a, // 106 a, // 107 a, // 108 a, // 109 a, // 110 a, // 111 a, // 112 a, // 113 a, // 114 a, // 115 a, // 116 a, // 117 a, // 118 a, // 119 a, // 120 a, // 121 a, // 122 a, // 123 a, // 124 a, // 125 a, // 126 a, // 127 a, // 128 a, // 129 a, // 130 a, // 131 a, // 132 a, // 133 a, // 134 a, // 135 a, // 136 a, // 137 a, // 138 a, // 139 a, // 140 a, // 141 a, // 142 a, // 143 a, // 144 a, // 145 a, // 146 a, // 147 a, // 148 a, // 149 a, // 150 a, // 151 a, // 152 a, // 153 a, // 154 a, // 155 a, // 156 a, // 157 a, // 158 a, // 159 a, // 160 a, // 161 a, // 162 a, // 163 a, // 164 a, // 165 a, // 166 a, // 167 a, // 168 a, // 169 a, // 170 a, // 171 a, // 172 a, // 173 a, // 174 a, // 175 a, // 176 a, // 177 a, // 178 a, // 179 a, // 180 a, // 181 a, // 182 a, // 183 a, // 184 a, // 185 a, // 186 a, // 187 a, // 188 a, // 189 a, // 190 a, // 191 a, // 192 a, // 193 a, // 194 a, // 195 a, // 196 a, // 197 a, // 198 a, // 199 a, // 200 a, // 201 a, // 202 a, // 203 a, // 204 a, // 205 a, // 206 a, // 207 a, // 208 a, // 209 a, // 210 a, // 211 a, // 212 a, // 213 a, // 214 a, // 215 a, // 216 a, // 217 a, // 218 a, // 219 a, // 220 a, // 221 a, // 222 a, // 223 a, // 224 a, // 225 a, // 226 a, // 227 a, // 228 a, // 229 a, // 230 a, // 231 a, // 232 a, // 233 a, // 234 a, // 235 a, // 236 a, // 237 a, // 238 a, // 239 a, // 240 a, // 241 a, // 242 a, // 243 a, // 244 a, // 245 a, // 246 a, // 247 a, // 248 a, // 249 a, // 250 a, // 251 a, // 252 a, // 253 a, // 254 a, // 255 a) // expect error: 'TooMnyArg' } ================================================ FILE: test/method/too_many_parameters.morpho ================================================ class Foo { // 256 parameters. method( a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255, a) {} // expect error: 'TooMnyPrm' } ================================================ FILE: test/modules/constants.morpho ================================================ // Test constants package import constants print Pi // expect: 3.14159 print E // expect: 2.71828 ================================================ FILE: test/modules/delaunay/delaunay.morpho ================================================ // Demonstrate use of the Delaunay module import delaunay var N = 100 // Number of points // Explicitly check that all triangles have the property that no other point is in their circumcircle fn checkTriangulation(pts, tri, quiet=false) { var success = true for (t, k in tri) { var sph = Circumsphere(pts, t) for (i in 0...pts.count()) { if (t.ismember(i)) continue // Skip the vertices of the triangle if (sph.pointinsphere(pts[i])) { if (!quiet) print "Point ${i} is in triangle ${k}." success=false; } } } return success } // 2D random point distribution var a = [] for (i in 1..N) a.append(Matrix([2*random()-1, 2*(2*random()-1)])) var del = Delaunay(a) var tri = del.triangulate() // Returns a list of triangles [ [i, j, k], ... ] print checkTriangulation(a, tri) // expect: true // 3D random point distribution var b = [] for (i in 1..N) b.append(Matrix([2*random()-1, 2*(2*random()-1), 2*random()-1])) del = Delaunay(b) tri = del.triangulate() print checkTriangulation(b, tri) // expect: true ================================================ FILE: test/modules/meshgen/disk.morpho ================================================ // Domain composed of a single disk import meshgen import plot var dom = fn (x) -(x[0]^2+x[1]^2-1) var mg = MeshGen(dom, [-1..1:0.2, -1..1:0.2], quiet=true) var m = mg.build() print ismesh(m) // expect: true ================================================ FILE: test/modules/meshslice/slice.morpho ================================================ // Test slice import meshslice import plot var m = Mesh("tetrahedron.mesh") m.addgrade(1) //Show(plotmesh(m, grade=[0,1,2])) var slice = MeshSlicer(m) var m2 = slice.slice([0.1,0,0],[1,0,0]) print ismesh(m2) // expect: true ================================================ FILE: test/modules/meshslice/slice_delaunay.morpho ================================================ import delaunay import meshtools import plot import meshslice var pts = [ Matrix([0,0,0]), Matrix([1,0,0]), Matrix([0,1,0]), Matrix([0,0,1]) ] var m = DelaunayMesh(pts) for (g in 0..m.maxgrade()) print m.count(g) // expect: 4 // expect: 0 // expect: 0 // expect: 1 var slice = MeshSlicer(m) var sc = slice.slice(Matrix([0.5,0,0]), Matrix([1,0,0])) for (g in 0..sc.maxgrade()) print sc.count(g) // expect: 3 // expect: 0 // expect: 1 //Show(plotmesh(sc, grade=[0,1,2]) + plotmesh(m, grade=[0,1], color=Red)) ================================================ FILE: test/modules/meshslice/slice_empty.morpho ================================================ // Test slice import meshslice import plot var m = Mesh("tetrahedron.mesh") var f = Field(m) var slice = MeshSlicer(m) var f2 = slice.slicefield(f) // expect error 'SlcEmpty' var m2 = slice.slice([0.1,0,0],[1,0,0]) ================================================ FILE: test/modules/meshslice/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 faces 1 1 2 3 2 2 3 4 3 3 4 1 4 2 1 4 volumes 1 1 2 3 4 ================================================ FILE: test/modules/meshtools/dimension_inconsistent.morpho ================================================ // Trigger an inconsistent dimension err import meshtools var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0,1]) // expect error 'MshBldDimIncnstnt' ================================================ FILE: test/modules/meshtools/dimension_unknown.morpho ================================================ // Trigger an unknown dimension err import meshtools var mb = MeshBuilder() mb.addelement(1, [0,0]) // expect error 'MshBldDimUnknwn' ================================================ FILE: test/modules/meshtools/merge_duplicates.morpho ================================================ // MeshMerge with duplicate elemtns import meshtools var m1 = MeshBuilder() var m2 = MeshBuilder() m1.addvertex([0,0,0]) m1.addvertex([0, 1, 0]) m1.addvertex([0,1,-1]) m1.addedge([0,1]) m1.addedge([1,2]) m1.addedge([2,0]) m1.addface([0,1,2]) m2.addvertex([0,0,0]) m2.addvertex([0, 1, 0]) m2.addvertex([0,1,1]) m2.addedge([0,1]) m2.addedge([1,2]) m2.addedge([2,0]) m2.addface([0,1,2]) var mesh1 = m1.build() var mesh2 = m2.build() var merge = MeshMerge([mesh1, mesh2]) var final = merge.merge() print final.count(0) // expect: 4 print final.count(1) // expect: 5 print final.count(2) // expect: 2 merge = MeshMerge([mesh1, mesh1]) final = merge.merge() print final.count(0) // expect: 3 print final.count(1) // expect: 3 print final.count(2) // expect: 1 ================================================ FILE: test/modules/meshtools/mesh_still_too_large.morpho ================================================ // Individual stepsize is ok, but the product is too large import meshtools var L = 0.5 var dx = 0.0000001 var m = AreaMesh(fn (u, v) [u, v, 0], -L..L:dx, -L..L:dx) // expect error 'MltMaxVrt' ================================================ FILE: test/modules/meshtools/mesh_too_large.morpho ================================================ // Generate too large a mesh import meshtools var L = 0.5 var dx = 0.00000000001 var m = AreaMesh(fn (u, v) [u, v, 0], -L..L:dx, -L..L:dx) // expect error 'RngStpSz' ================================================ FILE: test/modules/meshtools/meshrefine.morpho ================================================ import meshtools var m = Mesh("tetrahedron.mesh") print m // expect: var m2 = refinemesh(m) print m2 // expect: print m2.connectivitymatrix(0,1) // expect: [ 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 1 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1 0 ] // expect: [ 0 1 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 1 1 0 0 0 0 ] // expect: [ 0 0 1 0 0 0 0 0 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 ] // expect: [ 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 ] // expect: [ 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 0 1 1 0 0 0 ] // expect: [ 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 0 1 ] print m2.connectivitymatrix(0,2) // expect: [ 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 ] // expect: [ 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 ] // expect: [ 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 ] // expect: [ 1 0 0 1 1 0 1 0 0 0 0 0 0 1 0 1 ] // expect: [ 1 0 1 0 0 1 1 0 0 0 1 0 1 0 0 0 ] // expect: [ 0 1 0 0 1 1 1 1 0 1 0 0 0 0 0 0 ] // expect: [ 0 1 0 0 0 0 0 0 1 1 0 0 0 1 1 1 ] // expect: [ 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 ] // expect: [ 0 0 1 1 0 0 0 0 0 0 0 1 1 0 1 1 ] ================================================ FILE: test/modules/meshtools/polyhedron.morpho ================================================ // Create a mesh from a polyhedron import meshtools var icos = [[-0.688191, 0, 0.131433], [0.688191, 0, -0.131433], [-0.212663, -0.654508, 0.131433], [-0.212663, 0.654508, 0.131433], [0.556758, -0.404508, 0.131433], [0.556758, 0.404508, 0.131433], [-0.131433, -0.404508, 0.556758], [-0.131433, 0.404508, 0.556758], [-0.344095, -0.25, -0.556758], [-0.344095, 0.25, -0.556758], [0.344095, -0.25, 0.556758], [0.344095, 0.25, 0.556758], [0.425325, 0, -0.556758], [-0.556758, -0.404508, -0.131433], [-0.556758, 0.404508, -0.131433], [-0.425325, 0.0, 0.556758], [0.131433, -0.404508, -0.556758], [0.131433, 0.404508, -0.556758], [0.212663, -0.654508, -0.131433], [0.212663, 0.654508, -0.131433]] var faces = [[14, 9, 8, 13, 0], [1, 5, 11, 10, 4], [4, 10, 6, 2, 18], [10, 11, 7, 15, 6], [11, 5, 19, 3, 7], [5, 1, 12, 17, 19], [1, 4, 18, 16, 12], [3, 19, 17, 9, 14], [17, 12, 16, 8, 9], [16, 18, 2, 13, 8], [2, 6, 15, 0, 13], [15, 7, 3, 14, 0]] var m=PolyhedronMesh(icos, faces) print m // expect: // More could be done here! ================================================ FILE: test/modules/meshtools/testprune.morpho ================================================ // Test pruning import meshtools var mx = AreaMesh(fn (u,v) [u,v,0], -0.5..0.5:0.1, -0.5..0.5:0.1) mx.addgrade(1) var f = Field(mx, fn (x,y,z) x^2 + y^2) var bnd = Selection(mx, boundary=true) var sx = Selection(mx) sx[2,25]=true sx[1,50]=true sx[1,51]=true sx[1,53]=true sx[1,55]=true sx[2,50]=true sx[1,140]=true sx[2,100]=true sx[2,128]=true var mpx = MeshPruner([mx, f, sx], fix=bnd) var dictx = mpx.prune(sx) var m2x = dictx[mx] var fx = dictx[f] var sxx = dictx[sx] print mx.count() // expect: 121 print m2x.count() // expect: 109 ================================================ FILE: test/modules/meshtools/testrefine3d.morpho ================================================ // Test refinement of 3D meshes to make sure duplicate elements aren't generated. import meshtools var mb = MeshBuilder() mb.addvertex([0, 0, 0.612372]) mb.addvertex([-0.288675, -0.5, -0.204124]) mb.addvertex([-0.288675, 0.5, -0.204124]) mb.addvertex([0.57735, 0, -0.204124]) mb.addvolume([0,1,2,3]) var m = mb.build() m.addgrade(1) m.addgrade(2) m.addgrade(3) print m.count(0) // expect: 4 print m.count(1) // expect: 6 print m.count(2) // expect: 4 print m.count(3) // expect: 1 var s = Selection(m, boundary=true) s.addgrade(1) print "S ${s.count(0)} ${s.count(1)} ${s.count(2)} ${s.count(3)}" // expect: S 4 6 4 0 var mr = MeshRefiner([m,s]) var refmap = mr.refine() m = refmap[m] s = refmap[s] print m.count(0) // expect: 10 print m.count(1) // expect: 25 print m.count(2) // expect: 24 print m.count(3) // expect: 8 print "S+ ${s.count(0)} ${s.count(1)} ${s.count(2)} ${s.count(3)}" // expect: S+ 10 12 16 0 var sref = Selection(m, boundary=true) sref.addgrade(1) print "Sr ${sref.count(0)} ${sref.count(1)} ${sref.count(2)} ${sref.count(3)}" // expect: Sr 10 24 16 0 ================================================ FILE: test/modules/meshtools/testrefineselection.morpho ================================================ // Test refinement of selections with down projection import meshtools import plot var mx = AreaMesh(fn (u,v) [u,v,0], -0.5..0.5:0.5, -0.5..0.5:0.5) mx.addgrade(1) var sel = Selection(mx, boundary=true) var selcorner = Selection(mx, fn (x,y,z) x<-0.4 && y<-0.4) var selmost = sel.difference(selcorner) var mpx = MeshRefiner([mx, sel, selmost]) var dictx = mpx.refine() mx=dictx[mx] sel=dictx[sel] selmost=dictx[selmost] var sel2 = Selection(mx, boundary=true) fn compareselections(mesh, sel1, sel2) { for (g in 0..mesh.maxgrade()) { for (id in 0...mesh.count(g)) { if (sel1[g,id]!=sel2[g,id]) return false } } return true } print compareselections(mx, sel, sel2) // expect: true print selmost.idlistforgrade(0).count() +1 == sel.idlistforgrade(0).count() // expect: true //Show(plotselection(mx, sel, grade=[0,1,2])) ================================================ FILE: test/modules/meshtools/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 faces 1 1 2 3 2 2 3 4 3 3 4 1 4 2 1 4 ================================================ FILE: test/modules/meshtools/tetrahedron.morpho ================================================ import meshtools var file = "tetrahedron2.mesh" var mb = MeshBuilder() mb.addvertex([0,0,0]) mb.addvertex([1,0,0]) mb.addvertex([0,1,0]) mb.addvertex([0,0,1]) mb.addedge([0,1]) mb.addedge([0,2]) mb.addedge([0,3]) mb.addedge([1,2]) mb.addedge([1,3]) mb.addedge([2,3]) mb.addface([0,1,2]) mb.addface([0,1,3]) mb.addface([0,2,3]) mb.addface([1,2,3]) mb.addvolume([0,1,2,3]) var m = mb.build() print m // expect: m.save(file) var m2 = Mesh(file) for (g in 0..3) print m.count(g)==m2.count(g) // expect: true // expect: true // expect: true // expect: true ================================================ FILE: test/modules/meshtools/tetrahedron2.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 0 0 1 edges 1 1 2 2 1 3 3 1 4 4 2 3 5 2 4 6 3 4 faces 1 1 2 3 2 1 2 4 3 1 3 4 4 2 3 4 volumes 1 1 2 3 4 ================================================ FILE: test/modules/meshtools/triangle.mesh ================================================ vertices 1 0 0 2 1 0 3 0 1 edges 1 1 2 2 2 3 3 1 3 faces 1 1 2 3 ================================================ FILE: test/modules/meshtools/triangle.morpho ================================================ import meshtools var file = "triangle.mesh" var mb = MeshBuilder() mb.addvertex([0,0]) mb.addvertex([1,0]) mb.addvertex([0,1]) mb.addedge([0,1]) mb.addedge([1,2]) mb.addedge([2,0]) mb.addface([0,1,2]) var m = mb.build() print m // expect: m.save(file) var m2 = Mesh(file) for (g in 0..2) print m.count(g)==m2.count(g) // expect: true // expect: true // expect: true ================================================ FILE: test/modules/optimize/cg.morpho ================================================ // Test conjugate gradient on a function import optimize var L = 5 fn f(x) { return (x[0]/L)^2 + x[1]^2 + 1 } fn df(x) { return Matrix([2*x[0]/L^2, 2*x[1]]) } // We create subclass of Optimizer to optimize the function class FunctionOptimizer is Optimizer { init (f, df, x0) { self.f = f self.grad = df super.init(f, x0) } gettarget() { return self.target } settarget(val) { self.target=val } totalenergy() { return self.f(self.target) } totalforce() { return self.grad(self.target) } initlocalconstraints() {} subtractlocalconstraints(g) {} subtractconstraints(g) {} reprojectlocalconstraints() {} reprojectconstraints() {} } var opt = FunctionOptimizer(f, df, Matrix([0.2,3])) opt.steplimit=2 opt.quiet = true opt.conjugategradient(20) print (opt.energy[-1]-1)<1e-8 // expect: true ================================================ FILE: test/modules/povray.morpho ================================================ // Ensure povray imports correctly import povray print "ok" // expect: ok ================================================ FILE: test/namespace/function.xmorpho ================================================ fn f(x) { return x } ================================================ FILE: test/namespace/namespace_class.morpho ================================================ // Check that classes are imported correctly import color as c print c.Color // expect: @Color var gray = c.Color(0.5,0.5,0.5) print gray // expect: ================================================ FILE: test/namespace/namespace_class_extends.morpho ================================================ // Check that classes imported into a namespace can be referred to in a class statement import color as c class Beep is c.Color { } var a = Beep(1,1,1) print a // expect: ================================================ FILE: test/namespace/namespace_class_restrict.morpho ================================================ // Check that classes that imported into a namespace are not visible in the global context import color as c print Color // expect error 'SymblUndf' ================================================ FILE: test/namespace/namespace_functioncall.morpho ================================================ import "function.xmorpho" as func print func.f // expect: print func.f(1) // expect: 1 ================================================ FILE: test/newline/block.morpho ================================================ // Check semicolons aren't needed at the end of blocks fn f(x) { return x*x } print f(2) // expect: 4 for (var i=0; i<2; i+=1) { print i } // expect: 0 // expect: 1 ================================================ FILE: test/newline/classes.morpho ================================================ // Newlines as statement terminators /* * Classes */ class Donut { init(type) { self.type = type } eat() { print "A delicious ${self.type} donut!" } } var cruller = Donut("Cruller") cruller.eat() // expect: A delicious Cruller donut! // Ambiguity: Is this a property access? print cruller .eat // expect: . ================================================ FILE: test/newline/for.morpho ================================================ // Newlines as statement terminators /* ********* * For loops * ********* */ // Ambiguity: Is the statement following the for in the loop? for (var i=0;i<2; i=i+1) print "done" // expect: done // Put the body on the same line for (var i=0;i<2; i=i+1) print "easy!" // expect: easy! // expect: easy! // Or use curly braces for (var i=0;i<2; i=i+1) { print "trouble" } // expect: trouble // expect: trouble ================================================ FILE: test/newline/functions.morpho ================================================ // Newlines as statement terminators /* ********* * Functions * ********* */ // Ambiguity: Line terminator after return fn test1(x) { return x*x } fn test2(x) { return x*x } fn test3(x) { return x* test1(x) } print test1(2) // expect: 4 print test2(2) // expect: nil print test3(2) // expect: 8 // Ambiguity: Is this two statements or a function call? print test1 (1) // expect: ================================================ FILE: test/newline/variables.morpho ================================================ // Newlines as statement terminators /* ************************* * Variables and expressions * ************************* */ var a = 1 2 // Note this is parsed as a separate expression statement var b =a + 1 // End of statement a // This is also a separate statement print a + (1) // expect: 2 // Variable declaration without initializer var c // Ambiguity: If we have a negation operation on the following line // then it is c print b-a // expect: 1 print b -a // expect: 1 print (b -a) // expect: 1 // Ambiguity: Is this two statements or a function call? a = b (a) print a // expect: 2 ================================================ FILE: test/nil/literal.morpho ================================================ // Test that the nil literal works print nil // expect: nil if (!nil) { print "True" } // expect: True if (nil) { print "True" } else { print "False" } // expect: False ================================================ FILE: test/number/decimal_point_at_eof.morpho ================================================ // expect: 123 print 123. ================================================ FILE: test/number/float_negative_overflow.morpho ================================================ // Test for values outside floating point range print -1.21e55555 // expect error 'ValRng' ================================================ FILE: test/number/float_overflow.morpho ================================================ // Test for values outside floating point range print 1.21e55555 // expect error 'ValRng' ================================================ FILE: test/number/integer_overflow.morpho ================================================ // Test for values outside integer range print 1221323213412343143 // expect error 'ValRng' ================================================ FILE: test/number/leading_dot.morpho ================================================ // expect error: 'ExpExpr' .123; ================================================ FILE: test/number/literals.morpho ================================================ print 123 // expect: 123 print 987654 // expect: 987654 print 0 // expect: 0 print 0. // expect: 0 print -0.0 // expect: -0 print 123.456 // expect: 123.456 print -0.001 // expect: -0.001 ================================================ FILE: test/number/nan_equality.morpho ================================================ var nan = 0/0 print nan == 0 // expect: false print nan != 1 // expect: true // NaN is not equal to self. print nan == nan // expect: false print nan != nan // expect: true ================================================ FILE: test/number/trailing_dot.morpho ================================================ 123.; // expect error: 'ExpExpr' ================================================ FILE: test/object/baseclass.morpho ================================================ // Object is the default parent class for all other classes class Foo { } var a = Foo() a.prnt() print "" // expect: print a.respondsto("prnt") // expect: true a.invoke("prnt") print "" // expect: print a.superclass() // expect: @Object ================================================ FILE: test/object/builtin_inheritance.morpho ================================================ // Check inheritance of builtin objects var a = Length() print a.clss() // expect: @Length print a.respondsto("invoke") // expect: true print a.respondsto("squiggle") // expect: false ================================================ FILE: test/object/class_responds_to.morpho ================================================ // Check respondsto called on a class var a = Object print a.respondsto("invoke") // expect: true print a.respondsto("squiggle") // expect: false print a.respondsto("squiggle","foo") // expect error 'RspndsToArg' ================================================ FILE: test/object/clone.morpho ================================================ // Cloning an object class Foo { init(x) { self.boo = x } } var a = Foo(5) print a.boo // expect: 5 var b = a.clone() print b.boo // expect: 5 b.boo = 10 print a.boo // expect: 5 print b.boo // expect: 10 ================================================ FILE: test/object/enumerate.morpho ================================================ // Check Object's compliance with enumerate var a = Object() a.foo = 1 a.goo = 2 a.hoo = 3 print a.count() // expect: 3 var q = [] for (i in a) q.append(i) q.sort() print q // expect: [ foo, goo, hoo ] class Foo { } print Foo.count() // expect: 0 print Foo.enumerate(-1) // expect: 0 print Foo.enumerate(0) // expect: nil ================================================ FILE: test/object/has.morpho ================================================ // Check Object.respondsto() var a = Object() a.things = "stuff" print a.has("things") // expect: true print a.has("stuff") // expect: false print a.has() // expect: [ things ] ================================================ FILE: test/object/index.morpho ================================================ // Check get and set index var a = Object() a["Foo"] = 4 print a["Foo"] // expect: 4 print a.Foo // expect: 4 ================================================ FILE: test/object/invoke.morpho ================================================ // Check Object.invoke() var a = Object() a.invoke("prnt") print "" // expect: print a.invoke("superclass") // expect: nil print a.invoke("respondsto", "superclass") // expect: true ================================================ FILE: test/object/non_string_index.morpho ================================================ // Object indices need to be property labels var a = Object() print a[1] // expect error 'IndxArgs' ================================================ FILE: test/object/responds_to.morpho ================================================ // Check Object.respondsto() var a = Object() print a.respondsto("invoke") // expect: true print a.respondsto("squiggle") // expect: false var r = a.respondsto() r.sort() print r // expect: [ clone, clss, count, enumerate, has, index, invoke, linearization, prnt, respondsto, serialize, setindex, superclass ] class bar { method1(){ 1+1 } } var b = bar() r = b.respondsto() r.sort() print r // expect: [ clone, clss, count, enumerate, has, index, invoke, linearization, method1, prnt, respondsto, serialize, setindex, superclass ] print a.respondsto("squiggle","foo") // expect error 'RspndsToArg' ================================================ FILE: test/operator/add.morpho ================================================ print 123 + 456 // expect: 579 print "str" + "ing" // expect: string ================================================ FILE: test/operator/add_bool_nil.morpho ================================================ true + nil // expect error 'InvldOp' ================================================ FILE: test/operator/add_bool_num.morpho ================================================ true + 123 // expect error: 'InvldOp' ================================================ FILE: test/operator/add_bool_string.morpho ================================================ true + "s" // expect error 'InvldOp' ================================================ FILE: test/operator/add_nil_nil.morpho ================================================ nil + nil // expect error 'InvldOp' ================================================ FILE: test/operator/add_num_nil.morpho ================================================ 1 + nil // expect error 'InvldOp' ================================================ FILE: test/operator/add_string_nil.morpho ================================================ "s" + nil // expect error 'InvldOp' ================================================ FILE: test/operator/comparison.morpho ================================================ print 1 < 2 // expect: true print 2 < 2 // expect: false print 2 < 1 // expect: false print 1 <= 2 // expect: true print 2 <= 2 // expect: true print 2 <= 1 // expect: false print 1 > 2 // expect: false print 2 > 2 // expect: false print 2 > 1 // expect: true print 1 >= 2 // expect: false print 2 >= 2 // expect: true print 2 >= 1 // expect: true // Zero and negative zero compare the same. print 0 < -0 // expect: false print -0 < 0 // expect: false print 0 > -0 // expect: false print -0 > 0 // expect: false print 0 <= -0 // expect: true print -0 <= 0 // expect: true print 0 >= -0 // expect: true print -0 >= 0 // expect: true ================================================ FILE: test/operator/divide.morpho ================================================ print 8 / 2 // expect: 4 print 12.34 / 12.34 // expect: 1 ================================================ FILE: test/operator/divide_nonnum_num.morpho ================================================ "1" / 1 // expect error 'InvldOp' ================================================ FILE: test/operator/divide_num_nonnum.morpho ================================================ 1 / "1" // expect error 'InvldOp' ================================================ FILE: test/operator/equals.morpho ================================================ print nil == nil // expect: true print true == true // expect: true print true == false // expect: false print 1 == 1 // expect: true print 1 == 2 // expect: false print "str" == "str" // expect: true print "str" == "ing" // expect: false print nil == false // expect: false print false == 0 // expect: false print 0 == "0" // expect: false ================================================ FILE: test/operator/equals_class.morpho ================================================ // Bound methods have identity equality. class Foo {} class Bar {} print Foo == Foo // expect: true print Foo == Bar // expect: false print Bar == Foo // expect: false print Bar == Bar // expect: true print Foo == "Foo" // expect: false print Foo == nil // expect: false print Foo == 123 // expect: false print Foo == true // expect: false ================================================ FILE: test/operator/equals_method.morpho ================================================ // Bound methods have identity equality. class Foo { method() {} } var foo = Foo() var fooMethod = foo.method // Same bound method. print fooMethod == fooMethod // expect: true // Different closurizations. print foo.method == foo.method // expect: false ================================================ FILE: test/operator/fpcompare.morpho ================================================ // Floating point limits var eps = 2^-53 // Machine epsilon ~ 2.22e-16 var min = 2^-1022 // Smallest normalized representable number print 0 < eps // expect: true print 0 == eps // expect: false // Adding eps to 1 should yield the same number print 1 == 1+eps // expect: true print 1 == 1+10*eps // expect: false print -eps < 0 // expect: true print -eps == 0 // expect: false // DBL_EPS shouldn't equal zero print min == 0 // expect: false print eps*min == 0 // expect: true // Ensure signs don't matter print -eps*min == eps*min // expect: true // 1000*eps is not normalized print -(1000*eps)*min == (1000*eps)*min // expect: false ================================================ FILE: test/operator/greater_nonnum_num.morpho ================================================ "1" > 1 // expect error 'InvldOp' ================================================ FILE: test/operator/greater_num_nonnum.morpho ================================================ 1 > "1" // expect error 'InvldOp' ================================================ FILE: test/operator/greater_or_equal_nonnum_num.morpho ================================================ "1" >= 1 // expect error 'InvldOp' ================================================ FILE: test/operator/greater_or_equal_num_nonnum.morpho ================================================ 1 >= "1" // expect error 'InvldOp' ================================================ FILE: test/operator/less_nonnum_num.morpho ================================================ "1" < 1 // expect error 'InvldOp' ================================================ FILE: test/operator/less_num_nonnum.morpho ================================================ 1 < "1" // expect error 'InvldOp' ================================================ FILE: test/operator/less_or_equal_nonnum_num.morpho ================================================ "1" <= 1 // expect error 'InvldOp' ================================================ FILE: test/operator/less_or_equal_num_nonnum.morpho ================================================ 1 <= "1" // expect error 'InvldOp' ================================================ FILE: test/operator/more_comparison.morpho ================================================ // Test comparison operators // Equality print 1==1 // expect: true print 1==2 // expect: false print 1.0==1 // expect: true print 1==1.000000001 // expect: false print 1==1.000000000000001 // expect: false print 0==0.4 // expect: false print 0.4==0 // expect: false print "Eggs"=="Eggs" // expect: true print "Eggs"=="Bacon" // expect: false print "-" // expect: - // Neq print 1!=1 // expect: false print 1!=2 // expect: true print 0!=0.4 // expect: true print 0.4!=0 // expect: true print "Eggs"!="Eggs" // expect: false print "Eggs"!="Bacon" // expect: true print "-" // expect: - // Lt print 1<1 // expect: false print 1<2 // expect: true print 2<1 // expect: false print 1<1.000000001 // expect: true print 1.000000001<1.000000001 // expect: false print 1.000000002<1.000000001 // expect: false print 0<0.4 // expect: true print 0.4<0 // expect: false print "-" // expect: - // Lte print 1<=1 // expect: true print 1<=2 // expect: true print 2<=1 // expect: false print 1<=1.000000001 // expect: true print 1.000000001<=1.000000001 // expect: true print 1.000000002<=1.000000001 // expect: false print 0<=0.4 // expect: true print 0.4<=0 // expect: false print "-" // expect: - // Gt print 1>1 // expect: false print 1>2 // expect: false print 2>1 // expect: true print 1>1.000000001 // expect: false print 1.000000001>1.000000001 // expect: false print 1.000000002>1.000000001 // expect: true print 0>0.4 // expect: false print 0.4>0 // expect: true print "-" // expect: - // Gte print 1>=1 // expect: true print 1>=2 // expect: false print 2>=1 // expect: true print 1>=1.000000001 // expect: false print 1.000000001>=1.000000001 // expect: true print 1.000000002>=1.000000001 // expect: true print 1.000000001>=1.000000002 // expect: false print 0>=0.4 // expect: false print 0.4>=0 // expect: true ================================================ FILE: test/operator/multiply.morpho ================================================ print 5 * 3 // expect: 15 print 12.34 * 0.3 // expect: 3.702 ================================================ FILE: test/operator/multiply_nonnum_num.morpho ================================================ "1" * 1 // expect error 'InvldOp' ================================================ FILE: test/operator/multiply_num_nonnum.morpho ================================================ 1 * "1" // expect error 'InvldOp' ================================================ FILE: test/operator/negate.morpho ================================================ print -(3) // expect: -3 print --(3) // expect: 3 print ---(3) // expect: -3 ================================================ FILE: test/operator/negate_nonnum.morpho ================================================ -"s" // expect error 'InvldOp' ================================================ FILE: test/operator/not.morpho ================================================ print !true // expect: false print !false // expect: true print !!true // expect: true print !123 // expect: false print !0 // expect: false print !nil // expect: true print !"" // expect: false fn foo() {} print !foo // expect: false ================================================ FILE: test/operator/not_class.morpho ================================================ class Bar {} print !Bar // expect: false print !Bar() // expect: false ================================================ FILE: test/operator/not_equals.morpho ================================================ print nil != nil // expect: false print true != true // expect: false print true != false // expect: true print 1 != 1 // expect: false print 1 != 2 // expect: true print "str" != "str" // expect: false print "str" != "ing" // expect: true print nil != false // expect: true print false != 0 // expect: true print 0 != "0" // expect: true ================================================ FILE: test/operator/subtract.morpho ================================================ print 4 - 3 // expect: 1 print 1.2 - 1.2 // expect: 0 ================================================ FILE: test/operator/subtract_nonnum_num.morpho ================================================ "1" - 1 // expect error 'InvldOp' ================================================ FILE: test/operator/subtract_num_nonnum.morpho ================================================ 1 - "1" // expect error 'InvldOp' ================================================ FILE: test/operator/ternary.morpho ================================================ // Ternary operator var a = 1 var b = 2 print a < b ? 1 : 0 // expect: 1 print a > b ? 1 : 0 // expect: 0 print (a < b ? 1 : 0) // expect: 1 print 2 + (a > b ? 1 : 3) // expect: 5 ================================================ FILE: test/operator/ternary_in_function.morpho ================================================ // Ternary operator fn f(a,b) { return 1 + (a < b ? 1 : 0) } print f(1,2) // expect: 2 print f(2,1) // expect: 1 ================================================ FILE: test/operator/ternary_in_loop.morpho ================================================ // Ternary operator in a loop var a = 3 for (i in 1..5) { print (i s2.value } weakest(s1,s2) { if (Strength.weaker(s1,s2)) { return s1 } else { return s2 } } strongest(s1,s2) { if (Strength.stronger(s1,s2)) { return s1 } else { return s2 } } } var REQUIRED = Strength(0, "required") var STRONG_PREFERRED = Strength(1, "strongPreferred") var PREFERRED = Strength(2, "preferred") var STRONG_DEFAULT = Strength(3, "strongDefault") var NORMAL = Strength(4, "normal") var WEAK_DEFAULT = Strength(5, "weakDefault") var WEAKEST = Strength(6, "weakest") ORDERED = List( WEAKEST, WEAK_DEFAULT, NORMAL, STRONG_DEFAULT, PREFERRED, STRONG_PREFERRED ) var ThePlanner class Constraint { init (strength) { self.strength = strength } // Activate this constraint and attempt to satisfy it. addConstraint() { self.addToGraph() ThePlanner.incrementalAdd(self) } // Attempt to find a way to enforce this constraint. If successful, // record the solution, perhaps modifying the current dataflow // graph. Answer the constraint that this constraint overrides, if // there is one, or nil, if there isn't. // Assume: I am not already satisfied. satisfy(mark) { self.chooseMethod(mark) if (!self.isSatisfied()) { if (self.strength == REQUIRED) { print "Could not satisfy a required constraint!" } return nil } self.markInputs(mark) var out = self.output() var overridden = out.determinedBy if (overridden != nil) overridden.markUnsatisfied() out.determinedBy = self if (!ThePlanner.addPropagate(self, mark)) print "Cycle encountered" out.mark = mark return overridden } destroyConstraint() { if (self.isSatisfied()) ThePlanner.incrementalRemove(self) self.removeFromGraph() } // Normal constraints are not input constraints. An input constraint // is one that depends on external state, such as the mouse, the // keyboard, a clock, or some arbitrary piece of imperative code. isInput() { return false; } } // Abstract superclass for constraints having a single possible output variable. class UnaryConstraint < Constraint { init(myOutput, strength) { super.init(strength) self.satisfied = false self.myOutput = myOutput self.addConstraint() } // Adds this constraint to the constraint graph. addToGraph() { self.myOutput.addConstraint(self) self.satisfied = false } // Decides if this constraint can be satisfied and records that decision. chooseMethod(mark) { self.satisfied = (self.myOutput.mark != mark) && Strength.stronger(self.strength, self.myOutput.walkStrength) } // Returns true if this constraint is satisfied in the current solution. isSatisfied() { return self.satisfied; } markInputs(mark) { // has no inputs. } // Returns the current output variable. output() { return self.myOutput; } // Calculate the walkabout strength, the stay flag, and, if it is // 'stay', the value for the current output of this constraint. Assume // this constraint is satisfied. recalculate() { self.myOutput.walkStrength = self.strength self.myOutput.stay = !self.isInput() if (self.myOutput.stay) self.execute() // Stay optimization. } // Records that this constraint is unsatisfied. markUnsatisfied() { self.satisfied = false } inputsKnown(mark) { return true } removeFromGraph() { if (self.myOutput != nil) self.myOutput.removeConstraint(self) self.satisfied = false } } // Variables that should, with some level of preference, stay the same. // Planners may exploit the fact that instances, if satisfied, will not // change their output during plan execution. This is called "stay // optimization". class StayConstraint < UnaryConstraint { init(variable, strength) { super.init(variable, strength) } execute() { // Stay constraints do nothing. } } // A unary input constraint used to mark a variable that the client // wishes to change. class EditConstraint < UnaryConstraint { init(variable, strength) { super.init(variable, strength) } // Edits indicate that a variable is to be changed by imperative code. isInput() { return true } execute() { // Edit constraints do nothing. } } // Directions. var NONE = 1 var FORWARD = 2 var BACKWARD = 0 // Abstract superclass for constraints having two possible output // variables. class BinaryConstraint < Constraint { init(v1, v2, strength) { super.init(strength) self.v1 = v1 self.v2 = v2 self.direction = NONE self.addConstraint() } // Decides if this constraint can be satisfied and which way it // should flow based on the relative strength of the variables related, // and record that decision. chooseMethod(mark) { if (self.v1.mark == mark) { if (self.v2.mark != mark && Strength.stronger(self.strength, self.v2.walkStrength)) { self.direction = FORWARD } else { self.direction = NONE } } if (self.v2.mark == mark) { if (self.v1.mark != mark && Strength.stronger(self.strength, self.v1.walkStrength)) { self.direction = BACKWARD } else { self.direction = NONE } } if (Strength.weaker(self.v1.walkStrength, self.v2.walkStrength)) { if (Strength.stronger(self.strength, self.v1.walkStrength)) { self.direction = BACKWARD } else { self.direction = NONE } } else { if (Strength.stronger(self.strength, self.v2.walkStrength)) { self.direction = FORWARD } else { self.direction = BACKWARD } } } // Add this constraint to the constraint graph. addToGraph() { self.v1.addConstraint(self) self.v2.addConstraint(self) self.direction = NONE } // Answer true if this constraint is satisfied in the current solution. isSatisfied() { return self.direction != NONE; } // Mark the input variable with the given mark. markInputs(mark) { self.input().mark = mark } // Returns the current input variable input() { if (self.direction == FORWARD) { return self.v1 } else { return self.v2 } } // Returns the current output variable. output() { if (self.direction == FORWARD) { return self.v2 } else { return self.v1 } } // Calculate the walkabout strength, the stay flag, and, if it is // 'stay', the value for the current output of this // constraint. Assume this constraint is satisfied. recalculate() { var ihn = self.input() var out = self.output() out.walkStrength = Strength.weakest(self.strength, ihn.walkStrength) out.stay = ihn.stay if (out.stay) self.execute() } // Record the fact that this constraint is unsatisfied. markUnsatisfied() { self.direction = NONE } inputsKnown(mark) { var i = self.input() return i.mark == mark || i.stay || i.determinedBy == nil } removeFromGraph() { if (self.v1 != nil) self.v1.removeConstraint(self) if (self.v2 != nil) self.v2.removeConstraint(self) self.direction = NONE } } // Relates two variables by the linear scaling relationship: "v2 = // (v1 * scale) + offset". Either v1 or v2 may be changed to maintain // this relationship but the scale factor and offset are considered // read-only. class ScaleConstraint < BinaryConstraint { init(src, scale, offset, dest, strength) { self.scale = scale self.offset = offset self.direction = NONE super.init(src, dest, strength) } // Adds this constraint to the constraint graph. addToGraph() { super.addToGraph() self.scale.addConstraint(self) self.offset.addConstraint(self) } removeFromGraph() { super.removeFromGraph() if (self.scale != nil) self.scale.removeConstraint(self) if (self.offset != nil) self.offset.removeConstraint(self) } markInputs(mark) { super.markInputs(mark) self.scale.mark = self.offset.mark = mark } // Enforce this constraint. Assume that it is satisfied. execute() { if (self.direction == FORWARD) { self.v2.value = self.v1.value * self.scale.value + self.offset.value } else { // TODO: Is this the same semantics as ~/? self.v1.value = floor((self.v2.value - self.offset.value) / self.scale.value) } } // Calculate the walkabout strength, the stay flag, and, if it is // 'stay', the value for the current output of this constraint. Assume // this constraint is satisfied. recalculate() { var ihn = self.input() var out = self.output() out.walkStrength = Strength.weakest(self.strength, ihn.walkStrength) out.stay = ihn.stay && self.scale.stay && self.offset.stay if (out.stay) self.execute() } } // Constrains two variables to have the same value. class EqualityConstraint < BinaryConstraint { init(v1, v2, strength) { super.init(v1, v2, strength) } // Enforce this constraint. Assume that it is satisfied. execute() { self.output().value = self.input().value } } // A constrained variable. In addition to its value, it maintains the // structure of the constraint graph, the current dataflow graph, and // various parameters of interest to the DeltaBlue incremental // constraint solver. class Variable { init(name, value) { self.constraints = List() self.determinedBy = nil self.mark = 0 self.walkStrength = WEAKEST self.stay = true self.name = name self.value = value } // Add the given constraint to the set of all constraints that refer // this variable. addConstraint(constraint) { self.constraints.append(constraint) } // Removes all traces of c from this variable. removeConstraint(constraint) { var new = List() for (c in self.constraints) if (c!=constraint) new.append(c) self.constraints = new if (self.determinedBy == constraint) self.determinedBy = nil } } // A Plan is an ordered list of constraints to be executed in sequence // to resatisfy all currently satisfiable constraints in the face of // one or more changing inputs. class Plan { init() { self.list = List() } addConstraint(constraint) { self.list.append(constraint) } size() { return self.list.count() } execute() { for (constraint in self.list) { constraint.execute() } } } class Planner { init() { self.currentMark = 0 } // Attempt to satisfy the given constraint and, if successful, // incrementally update the dataflow graph. Details: If satifying // the constraint is successful, it may override a weaker constraint // on its output. The algorithm attempts to resatisfy that // constraint using some other method. This process is repeated // until either a) it reaches a variable that was not previously // determined by any constraint or b) it reaches a constraint that // is too weak to be satisfied using any of its methods. The // variables of constraints that have been processed are marked with // a unique mark value so that we know where we've been. This allows // the algorithm to avoid getting into an infinite loop even if the // constraint graph has an inadvertent cycle. incrementalAdd(constraint) { var mark = self.newMark() var overridden = constraint.satisfy(mark) while (overridden != nil) { overridden = overridden.satisfy(mark) } } // Entry point for retracting a constraint. Remove the given // constraint and incrementally update the dataflow graph. // Details: Retracting the given constraint may allow some currently // unsatisfiable downstream constraint to be satisfied. We therefore collect // a list of unsatisfied downstream constraints and attempt to // satisfy each one in turn. This list is traversed by constraint // strength, strongest first, as a heuristic for avoiding // unnecessarily adding and then overriding weak constraints. // Assume: [c] is satisfied. incrementalRemove(constraint) { var out = constraint.output() constraint.markUnsatisfied() constraint.removeFromGraph() var unsatisfied = self.removePropagateFrom(out) var strength = REQUIRED while (true) { for (u in unsatisfied) { if (u.strength == strength) self.incrementalAdd(u) } strength = strength.nextWeaker() if (strength == WEAKEST) break } } // Select a previously unused mark value. newMark() { self.currentMark += 1; return self.currentMark; } // Extract a plan for resatisfaction starting from the given source // constraints, usually a set of input constraints. This method // assumes that stay optimization is desired; the plan will contain // only constraints whose output variables are not stay. Constraints // that do no computation, such as stay and edit constraints, are // not included in the plan. // Details: The outputs of a constraint are marked when it is added // to the plan under construction. A constraint may be appended to // the plan when all its input variables are known. A variable is // known if either a) the variable is marked (indicating that has // been computed by a constraint appearing earlier in the plan), b) // the variable is 'stay' (i.e. it is a constant at plan execution // time), or c) the variable is not determined by any // constraint. The last provision is for past states of history // variables, which are not stay but which are also not computed by // any constraint. // Assume: [sources] are all satisfied. makePlan(sources) { var mark = self.newMark() var plan = Plan() var todo = sources while (todo.count() > 0) { var constraint = todo.pop() if (constraint.output().mark != mark && constraint.inputsKnown(mark)) { plan.addConstraint(constraint) constraint.output().mark = mark self.addConstraintsConsumingTo(constraint.output(), todo) } } return plan } // Extract a plan for resatisfying starting from the output of the // given [constraints], usually a set of input constraints. extractPlanFromConstraints(constraints) { var sources = List() for (constraint in constraints) { // if not in plan already and eligible for inclusion. if (constraint.isInput() && constraint.isSatisfied()) sources.append(constraint) } return self.makePlan(sources) } // Recompute the walkabout strengths and stay flags of all variables // downstream of the given constraint and recompute the actual // values of all variables whose stay flag is true. If a cycle is // detected, remove the given constraint and answer // false. Otherwise, answer true. // Details: Cycles are detected when a marked variable is // encountered downstream of the given constraint. The sender is // assumed to have marked the inputs of the given constraint with // the given mark. Thus, encountering a marked node downstream of // the output constraint means that there is a path from the // constraint's output to one of its inputs. addPropagate(constraint, mark) { var todo = List(constraint) while (todo.count() > 0) { var d = todo.pop() if (d.output().mark == mark) { self.incrementalRemove(constraint) return false } d.recalculate() self.addConstraintsConsumingTo(d.output(), todo) } return true } // Update the walkabout strengths and stay flags of all variables // downstream of the given constraint. Answer a collection of // unsatisfied constraints sorted in order of decreasing strength. removePropagateFrom(out) { out.determinedBy = nil out.walkStrength = WEAKEST out.stay = true var unsatisfied = List() var todo = List(out) while (todo.count() > 0) { var v = todo.pop() for (constraint in v.constraints) { if (!constraint.isSatisfied()) unsatisfied.append(constraint) } var determining = v.determinedBy for (next in v.constraints) { if (next != determining && next.isSatisfied()) { next.recalculate() todo.append(next.output()) } } } return unsatisfied } addConstraintsConsumingTo(v, coll) { var determining = v.determinedBy for (constraint in v.constraints) { if (constraint != determining && constraint.isSatisfied()) { coll.append(constraint) } } } } var total = 0 // This is the standard DeltaBlue benchmark. A long chain of equality // constraints is constructed with a stay constraint on one end. An // edit constraint is then added to the opposite end and the time is // measured for adding and removing this constraint, and extracting // and executing a constraint satisfaction plan. There are two cases. // In case 1, the added constraint is stronger than the stay // constraint and values must propagate down the entire length of the // chain. In case 2, the added constraint is weaker than the stay // constraint so it cannot be accomodated. The cost in this case is, // of course, very low. Typical situations lie somewhere between these // two extremes. fn chainTest(n) { ThePlanner = Planner() var prev = nil var first = nil var last = nil // Build chain of n equality constraints. for (i in 0..n) { var v = Variable("v", 0) if (prev != nil) EqualityConstraint(prev, v, REQUIRED) if (i == 0) first = v if (i == n) last = v prev = v } StayConstraint(last, STRONG_DEFAULT) var edit = EditConstraint(first, PREFERRED) var plan = ThePlanner.extractPlanFromConstraints(List(edit)) for (i in 0..99) { first.value = i plan.execute() total = total + last.value } } fn change(v, newValue) { var edit = EditConstraint(v, PREFERRED) var plan = ThePlanner.extractPlanFromConstraints(List(edit)) for (i in 0..9) { v.value = newValue plan.execute() } edit.destroyConstraint() } // This test constructs a two sets of variables related to each // other by a simple linear transformation (scale and offset). The // time is measured to change a variable on either side of the // mapping and to change the scale and offset factors. fn projectionTest(n) { ThePlanner = Planner() var scale = Variable("scale", 10) var offset = Variable("offset", 1000) var src = nil var dst = nil var dests = List() for (i in 0..n-1) { src = Variable("src", i) dst = Variable("dst", i) dests.append(dst) StayConstraint(src, NORMAL) ScaleConstraint(src, scale, offset, dst, REQUIRED) } change(src, 17) total = total + dst.value if (dst.value != 1170) print "Projection 1 failed" change(dst, 1050) total = total + src.value if (src.value != 5) print "Projection 2 failed" change(scale, 5) for (i in 0..n - 2) { total = total + dests[i].value if (dests[i].value != i * 5 + 1000) print "Projection 3 failed" } change(offset, 2000) for (i in 0..n - 2) { total = total + dests[i].value if (dests[i].value != i * 5 + 2000) print "Projection 4 failed" } } /* * Run the test */ // Usually loop over 40 times for the benchmark, but here we'll just do one chainTest(100) projectionTest(100) print total // expect: 351635 ================================================ FILE: test/programs/fannkuch.morpho ================================================ /* The Computer Language Benchmarks Game * http://benchmarksgame.alioth.debian.org/ * reimplemented in morpho by T J Atherton */ // Swap elements of an array fn swap(x, i, j) { var tmp=x[i] x[i]=x[j] x[j]=tmp } // Count the number of swaps fn countswaps(n, p, q) { var swaps = 0 // Take the first element, X, and reverse the order of the first X elements. // Do this repeatedly until X==1 for (i in 0..n-1) q[i]=p[i] // Start by copying the array for (var q0=q[0]; q0!=1; q0=q[0]) { for (i in 0..Int(floor(q0/2))-1) { // Reverse first q0 elements swap(q, i, q0-1-i) } swaps+=1 } return swaps } // Fannkuch benchmark fn fannkuch(n) { // Create a list of numbers 1..n var p = List() for (i in 1..n) p.append(i) var q = p.clone() var indx[n] // Used to keep track of the permutations for (i in 0..n-1) indx[i]=0 var maxswaps = 0 // Track the maximum number of swaps needed var i=0 var sign=1 while (imaxswaps) maxswaps=swaps // Generate next permutation by Heap's algorithm if (indx[i]mx) mx=i } return mx } // Display a histogram fn histogram(lst, nbins) { var cnt[nbins] var bins[nbins+1] // Calculate the bin bounds var mx = max(lst), mn = min(lst) for (i in 0..nbins) { bins[i]=mn+i*(mx-mn)/(nbins) } // Assign each element of lst to a bin for (x in lst) { var k=0 while (x>bins[k+1]) k+=1 cnt[k]+=1 } // Show histogram for (i in 0..nbins-1) { print "${bins[i]}-${bins[i+1]}: ${cnt[i]}" } } // Construct a list of random numbers var N=1000000 var a=Matrix(N) for (i in 0..N-1) a[i]=i // Show the histogram histogram(a, 5) // expect: 0-200000: 200000 // expect: 200000-400000: 200000 // expect: 400000-599999: 200000 // expect: 599999-799999: 200000 // expect: 799999-999999: 200000 ================================================ FILE: test/programs/integrate.morpho ================================================ // Simple Euler integrator for an oscillator var Tmax = 1 // Maximum time var dt = 0.01 // Timestep var x0 = 2 // Initial position var v0 = 0 // Initial velocity var m = 1 // Mass var g = -9.81 // Gravity constant (-ve means down) var x = List(x0) var v = List(v0) // Calculates the force given the position x and velocity v fn force(x, v) { return m*g } // Main integration loop for (t in 0..Tmax:dt) { var vnew = v[-1] + force(x[-1], v[-1])*dt/m var xnew = x[-1] + vnew*dt v.append(vnew) x.append(xnew) } print "${Tmax} ${x[-1]} ${v[-1]}" // expect: 1 -3.05313 -9.9081 ================================================ FILE: test/property/call_function_field.morpho ================================================ class Foo {} fn bar(a, b) { print "bar" print a print b } var foo = Foo() foo.bar = bar foo.bar(1, 2) // expect: bar // expect: 1 // expect: 2 ================================================ FILE: test/property/call_nonfunction_field.morpho ================================================ class Foo {} var foo = Foo() foo.bar = "not fn" foo.bar() // expect error 'Uncallable' ================================================ FILE: test/property/get_on_bool.morpho ================================================ true.foo // expect error 'ClssLcksMthd' ================================================ FILE: test/property/get_on_class.morpho ================================================ class Foo {} Foo.bar // expect error 'ClssLcksMthd' ================================================ FILE: test/property/get_on_function.morpho ================================================ fn foo() {} foo.bar // expect error 'ClssLcksMthd' ================================================ FILE: test/property/get_on_nil.morpho ================================================ nil.foo // expect error 'NotAnObj' ================================================ FILE: test/property/get_on_num.morpho ================================================ // Try to get a property from an object 123.foo // expect error 'ClssLcksMthd' ================================================ FILE: test/property/get_on_string.morpho ================================================ // Strings have methods but not properties print "Hello".count // expect: Hello. "str".foo // expect error 'ClssLcksMthd' ================================================ FILE: test/property/index_property_in_args.morpho ================================================ class Test { init() { // This works var x = Matrix([1,2,3]) self.lstx = [ x[0], x[1], x[2] ] print self.lstx // expect: [ 1, 2, 3 ] // *** Check that indexing a property works in a list *** self.y = Matrix([1,2,3]) self.lsty = [ self.y[0], self.y[1], self.y[2] ] print self.lsty // expect: [ 1, 2, 3 ] // They individually work fine print self.y[0] // expect: 1 print self.y[1] // expect: 2 print self.y[2] // expect: 3 // This works too self.a = 1 self.b = 2 self.c = 3 self.lstz = [self.a, self.b, self.c] print self.lstz // expect: [ 1, 2, 3 ] } } var t = Test() ================================================ FILE: test/property/many.morpho ================================================ class Foo {} var foo = Foo() fn setFields() { foo.bilberry = "bilberry" foo.lime = "lime" foo.elderberry = "elderberry" foo.raspberry = "raspberry" foo.gooseberry = "gooseberry" foo.longan = "longan" foo.mandarine = "mandarine" foo.kiwifruit = "kiwifruit" foo.orange = "orange" foo.pomegranate = "pomegranate" foo.tomato = "tomato" foo.banana = "banana" foo.juniper = "juniper" foo.damson = "damson" foo.blackcurrant = "blackcurrant" foo.peach = "peach" foo.grape = "grape" foo.mango = "mango" foo.redcurrant = "redcurrant" foo.watermelon = "watermelon" foo.plumcot = "plumcot" foo.papaya = "papaya" foo.cloudberry = "cloudberry" foo.rambutan = "rambutan" foo.salak = "salak" foo.physalis = "physalis" foo.huckleberry = "huckleberry" foo.coconut = "coconut" foo.date = "date" foo.tamarind = "tamarind" foo.lychee = "lychee" foo.raisin = "raisin" foo.apple = "apple" foo.avocado = "avocado" foo.nectarine = "nectarine" foo.pomelo = "pomelo" foo.melon = "melon" foo.currant = "currant" foo.plum = "plum" foo.persimmon = "persimmon" foo.olive = "olive" foo.cranberry = "cranberry" foo.boysenberry = "boysenberry" foo.blackberry = "blackberry" foo.passionfruit = "passionfruit" foo.mulberry = "mulberry" foo.marionberry = "marionberry" foo.plantain = "plantain" foo.lemon = "lemon" foo.yuzu = "yuzu" foo.loquat = "loquat" foo.kumquat = "kumquat" foo.salmonberry = "salmonberry" foo.tangerine = "tangerine" foo.durian = "durian" foo.pear = "pear" foo.cantaloupe = "cantaloupe" foo.quince = "quince" foo.guava = "guava" foo.strawberry = "strawberry" foo.nance = "nance" foo.apricot = "apricot" foo.jambul = "jambul" foo.grapefruit = "grapefruit" foo.clementine = "clementine" foo.jujube = "jujube" foo.cherry = "cherry" foo.feijoa = "feijoa" foo.jackfruit = "jackfruit" foo.fig = "fig" foo.cherimoya = "cherimoya" foo.pineapple = "pineapple" foo.blueberry = "blueberry" foo.jabuticaba = "jabuticaba" foo.miracle = "miracle" foo.dragonfruit = "dragonfruit" foo.satsuma = "satsuma" foo.tamarillo = "tamarillo" foo.honeydew = "honeydew" } setFields() fn printFields() { print foo.apple // expect: apple print foo.apricot // expect: apricot print foo.avocado // expect: avocado print foo.banana // expect: banana print foo.bilberry // expect: bilberry print foo.blackberry // expect: blackberry print foo.blackcurrant // expect: blackcurrant print foo.blueberry // expect: blueberry print foo.boysenberry // expect: boysenberry print foo.cantaloupe // expect: cantaloupe print foo.cherimoya // expect: cherimoya print foo.cherry // expect: cherry print foo.clementine // expect: clementine print foo.cloudberry // expect: cloudberry print foo.coconut // expect: coconut print foo.cranberry // expect: cranberry print foo.currant // expect: currant print foo.damson // expect: damson print foo.date // expect: date print foo.dragonfruit // expect: dragonfruit print foo.durian // expect: durian print foo.elderberry // expect: elderberry print foo.feijoa // expect: feijoa print foo.fig // expect: fig print foo.gooseberry // expect: gooseberry print foo.grape // expect: grape print foo.grapefruit // expect: grapefruit print foo.guava // expect: guava print foo.honeydew // expect: honeydew print foo.huckleberry // expect: huckleberry print foo.jabuticaba // expect: jabuticaba print foo.jackfruit // expect: jackfruit print foo.jambul // expect: jambul print foo.jujube // expect: jujube print foo.juniper // expect: juniper print foo.kiwifruit // expect: kiwifruit print foo.kumquat // expect: kumquat print foo.lemon // expect: lemon print foo.lime // expect: lime print foo.longan // expect: longan print foo.loquat // expect: loquat print foo.lychee // expect: lychee print foo.mandarine // expect: mandarine print foo.mango // expect: mango print foo.marionberry // expect: marionberry print foo.melon // expect: melon print foo.miracle // expect: miracle print foo.mulberry // expect: mulberry print foo.nance // expect: nance print foo.nectarine // expect: nectarine print foo.olive // expect: olive print foo.orange // expect: orange print foo.papaya // expect: papaya print foo.passionfruit // expect: passionfruit print foo.peach // expect: peach print foo.pear // expect: pear print foo.persimmon // expect: persimmon print foo.physalis // expect: physalis print foo.pineapple // expect: pineapple print foo.plantain // expect: plantain print foo.plum // expect: plum print foo.plumcot // expect: plumcot print foo.pomegranate // expect: pomegranate print foo.pomelo // expect: pomelo print foo.quince // expect: quince print foo.raisin // expect: raisin print foo.rambutan // expect: rambutan print foo.raspberry // expect: raspberry print foo.redcurrant // expect: redcurrant print foo.salak // expect: salak print foo.salmonberry // expect: salmonberry print foo.satsuma // expect: satsuma print foo.strawberry // expect: strawberry print foo.tamarillo // expect: tamarillo print foo.tamarind // expect: tamarind print foo.tangerine // expect: tangerine print foo.tomato // expect: tomato print foo.watermelon // expect: watermelon print foo.yuzu // expect: yuzu } printFields() ================================================ FILE: test/property/method.morpho ================================================ class Foo { bar(arg) { print arg } } var bar = Foo().bar print "got method" // expect: got method bar("arg") // expect: arg ================================================ FILE: test/property/method_binds_self.morpho ================================================ class Foo { sayName(a) { print self.name print a } } var foo1 = Foo() foo1.name = "foo1" var foo2 = Foo() foo2.name = "foo2" // Store the method reference on another object. foo2.func = foo1.sayName // Still retains original receiver. foo2.func(1) // expect: foo1 // expect: 1 ================================================ FILE: test/property/on_instance.morpho ================================================ class Foo {} var foo = Foo() print foo.bar = "bar value" // expect: bar value print foo.baz = "baz value" // expect: baz value print foo.bar // expect: bar value print foo.baz // expect: baz value ================================================ FILE: test/property/property_error_in_index.morpho ================================================ var A = [1,2,3] print A[[2].2] // expect error 'PptyNmRqd' ================================================ FILE: test/property/property_index.morpho ================================================ // Assign to property with index class Foo { init() { self.prop = Array(1) self.prop[0] = "Bar" } } print Foo().prop[0] // expect: Bar ================================================ FILE: test/property/set_evaluation_order.morpho ================================================ undefined1.bar // expect error 'SymblUndf' = undefined2 ================================================ FILE: test/property/set_index_property.morpho ================================================ class Foo { init() { self.elements = Array(3) for (i in 0...3) { self.elements[i]=i } } } var mb = Foo() for (i in mb.elements) print i // expect: 0 // expect: 1 // expect: 2 ================================================ FILE: test/property/set_on_bool.morpho ================================================ true.foo = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/set_on_class.morpho ================================================ class Foo {} Foo.bar = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/set_on_function.morpho ================================================ fn foo() {} foo.bar = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/set_on_nil.morpho ================================================ nil.foo = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/set_on_num.morpho ================================================ 123.foo = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/set_on_string.morpho ================================================ "str".foo = "value" // expect error 'NotAnObj' ================================================ FILE: test/property/undefined.morpho ================================================ class Foo {} var foo = Foo() foo.bar // expect error 'ObjLcksPrp' ================================================ FILE: test/range/constructor.morpho ================================================ // Ranges // Default step is 1 and the default is an exclusive range var a = Range(1,3) print a // expect: 1...3 for (x in a) print x // expect: 1 // expect: 2 a = InclusiveRange(1,3) print a // expect: 1..3 for (x in a) print x // expect: 1 // expect: 2 // expect: 3 // Set a stepsize; upper limit is excluded a = Range(1,5,2) print a // expect: 1...5:2 for (x in a) print x // expect: 1 // expect: 3 // Step in reverse a = Range(-0.1,-0.5,-0.1) print a // expect: -0.1...-0.5:-0.1 for (x in a) print x // expect: -0.1 // expect: -0.2 // expect: -0.3 // expect: -0.4 // Step in reverse in an incommensurate way a = Range(-0.1,-0.5,-0.13) print a // expect: -0.1...-0.5:-0.13 for (x in a) print x // expect: -0.1 // expect: -0.23 // expect: -0.36 // expect: -0.49 // Inconsistent sign in step yields no elements a = Range(-0.1,-0.5,0.13) print a // expect: -0.1...-0.5:0.13 for (x in a) print x // Range with only one element var a = Range(5,5) print a // expect: 5...5 for (x in a) print x // InclusiveRange with only one element var a = InclusiveRange(5,5) print a // expect: 5..5 for (x in a) print x // expect: 5 ================================================ FILE: test/range/count_down.morpho ================================================ // Count downwards for (i in -0.1..-0.5:-0.1) print i // expect: -0.1 // expect: -0.2 // expect: -0.3 // expect: -0.4 // expect: -0.5 ================================================ FILE: test/range/exclusive.morpho ================================================ // Exclusive Ranges for (i in 1...5) print i // expect: 1 // expect: 2 // expect: 3 // expect: 4 for (i in 1...2:0.5) print i // expect: 1 // expect: 1.5 for (i in 0...-2:-0.5) print i // expect: 0 // expect: -0.5 // expect: -1 // expect: -1.5 for (i in 0.1...1.2:0.1) print i // expect: 0.1 // expect: 0.2 // expect: 0.3 // expect: 0.4 // expect: 0.5 // expect: 0.6 // expect: 0.7 // expect: 0.8 // expect: 0.9 // expect: 1 // expect: 1.1 ================================================ FILE: test/range/inclusive.morpho ================================================ // Inclusive Ranges for (i in 1..5) print i // expect: 1 // expect: 2 // expect: 3 // expect: 4 // expect: 5 for (i in 1..2:0.5) print i // expect: 1 // expect: 1.5 // expect: 2 for (i in 0..-2:-0.5) print i // expect: 0 // expect: -0.5 // expect: -1 // expect: -1.5 // expect: -2 for (i in 0.1..1.2:0.1) print i // expect: 0.1 // expect: 0.2 // expect: 0.3 // expect: 0.4 // expect: 0.5 // expect: 0.6 // expect: 0.7 // expect: 0.8 // expect: 0.9 // expect: 1 // expect: 1.1 // expect: 1.2 for (i in 0.1..0.35:0.1) print i // expect: 0.1 // expect: 0.2 // expect: 0.3 ================================================ FILE: test/range/inherited.morpho ================================================ // Test Range methods inherited from Object var a = 1..2 print a.respondsto("clss") // expect: true print islist(a.respondsto()) // expect: true print a.clss() // expect: @Range print a.superclass() // expect: @Object print islist(a.invoke("respondsto")) // expect: true print a.has("a") // expect: false print a.count() // expect: 2 ================================================ FILE: test/range/invalid_constructor_too_few_args.morpho ================================================ // Too few arguments to constructor function var a = Range() // expect error 'RngArgs' ================================================ FILE: test/range/invalid_constructor_too_many_args.morpho ================================================ // Too many arguments to constructor function var a = Range(1,2,3,4) // expect error 'RngArgs' ================================================ FILE: test/range/invalid_constructor_type.morpho ================================================ // Incorrect type of args to constructor function var a = Range(1,5.0,"Hello") // expect error 'RngArgs' ================================================ FILE: test/range/list_constructor.morpho ================================================ // Construct a List from a Range with a problematic version print List(1.0 ... 1.2 : 0.15) // expect: [ 1, 1.15 ] ================================================ FILE: test/range/step_too_fine.morpho ================================================ // Range with very small stepsize var range = -1..1:0.00000000001 // expect error 'RngStpSz' ================================================ FILE: test/range/syntax.morpho ================================================ // Ranges var a = 1..2 print a // expect: 1..2 for (i in a) print i // expect: 1 // expect: 2 var b = 1..2:2 print b // expect: 1..2:2 var n=2 a = n..2*n:2 print a // expect: 2..4:2 for (i in 1..5:2) print i // expect: 1 // expect: 3 // expect: 5 ================================================ FILE: test/return/after_else.morpho ================================================ fn f() { if (false) "no" else return "ok" } print f() // expect: ok ================================================ FILE: test/return/after_if.morpho ================================================ fn f() { if (true) return "ok" } print f() // expect: ok ================================================ FILE: test/return/after_while.morpho ================================================ fn f() { while (true) return "ok" } print f() // expect: ok ================================================ FILE: test/return/at_top_level.morpho ================================================ return "nooooo" // expect error 'GlblRtrn' ================================================ FILE: test/return/in_for_in.morpho ================================================ fn f() { for (i in 1..10) { print i if (i==5) return } } f() // expect: 1 // expect: 2 // expect: 3 // expect: 4 // expect: 5 ================================================ FILE: test/return/in_function.morpho ================================================ fn f() { return "ok" print "bad" } print f() // expect: ok ================================================ FILE: test/return/in_method.morpho ================================================ class Foo { method() { return "ok" print "bad" } } print Foo().method() // expect: ok ================================================ FILE: test/return/return_nil_if_no_value.morpho ================================================ fn f() { return print "bad" } print f() // expect: nil ================================================ FILE: test/selection/add_grade.morpho ================================================ // Test selections var m = Mesh("square.mesh") m.addgrade(1) fn f(x,y,z) { return y>0.5 } var s = Selection(m, f) s.addgrade(1, partials=true) var l = s.idlistforgrade(1) l.sort() print l // expect: [ 1, 2, 3, 4 ] s.removegrade(1) print s.idlistforgrade(1) // expect: [ ] s.addgrade(1) print s.idlistforgrade(1) // expect: [ 4 ] s.addgrade(2, partials=true) print s.idlistforgrade(2) // expect: [ 0, 1 ] ================================================ FILE: test/selection/boundary.morpho ================================================ // Test selections var m = Mesh("square.mesh") m.addgrade(1) var s = Selection(m, boundary=true) var lst = s.idlistforgrade(1) lst.sort() print lst // expect: [ 0, 1, 3, 4 ] lst = s.idlistforgrade(0) lst.sort() print lst // expect: [ 0, 1, 2, 3 ] ================================================ FILE: test/selection/circlesquare.mesh ================================================ vertices 1 -2. -2. 0 2 -2. -1.5 0 3 -2. -1. 0 4 -2. -0.5 0 5 -2. 0. 0 6 -2. 0.5 0 7 -2. 1. 0 8 -2. 1.5 0 9 -2. 2. 0 10 -1.5 -2. 0 11 -1.5 -1.5 0 12 -1.5 -1. 0 13 -1.5 -0.5 0 14 -1.5 0. 0 15 -1.5 0.5 0 16 -1.5 1. 0 17 -1.5 1.5 0 18 -1.5 2. 0 19 -1. -2. 0 20 -1. -1.5 0 21 -1. -1. 0 22 -1. -0.5 0 23 -1. 0. 0 24 -1. 0.5 0 25 -1. 1. 0 26 -1. 1.5 0 27 -1. 2. 0 28 -0.951057 -0.309017 0 29 -0.951057 0.309017 0 30 -0.809017 -0.587785 0 31 -0.809017 0.587785 0 32 -0.587785 -0.809017 0 33 -0.587785 0.809017 0 34 -0.5 -2. 0 35 -0.5 -1.5 0 36 -0.5 -1. 0 37 -0.5 -0.5 0 38 -0.5 0. 0 39 -0.5 0.5 0 40 -0.5 1. 0 41 -0.5 1.5 0 42 -0.5 2. 0 43 -0.309017 -0.951057 0 44 -0.309017 0.951057 0 45 0. -2. 0 46 0. -1.5 0 47 0. -1. 0 48 0. -0.5 0 49 0. 0. 0 50 0. 0.5 0 51 0. 1. 0 52 0. 1.5 0 53 0. 2. 0 54 0.309017 -0.951057 0 55 0.309017 0.951057 0 56 0.5 -2. 0 57 0.5 -1.5 0 58 0.5 -1. 0 59 0.5 -0.5 0 60 0.5 0. 0 61 0.5 0.5 0 62 0.5 1. 0 63 0.5 1.5 0 64 0.5 2. 0 65 0.587785 -0.809017 0 66 0.587785 0.809017 0 67 0.809017 -0.587785 0 68 0.809017 0.587785 0 69 0.951057 -0.309017 0 70 0.951057 0.309017 0 71 1. -2. 0 72 1. -1.5 0 73 1. -1. 0 74 1. -0.5 0 75 1. 0. 0 76 1. 0.5 0 77 1. 1. 0 78 1. 1.5 0 79 1. 2. 0 80 1.5 -2. 0 81 1.5 -1.5 0 82 1.5 -1. 0 83 1.5 -0.5 0 84 1.5 0. 0 85 1.5 0.5 0 86 1.5 1. 0 87 1.5 1.5 0 88 1.5 2. 0 89 2. -2. 0 90 2. -1.5 0 91 2. -1. 0 92 2. -0.5 0 93 2. 0. 0 94 2. 0.5 0 95 2. 1. 0 96 2. 1.5 0 97 2. 2. 0 edges 1 1 10 2 10 2 3 2 1 4 10 11 5 11 2 6 11 19 7 19 20 8 20 11 9 10 19 10 19 34 11 34 20 12 20 21 13 21 11 14 11 12 15 12 2 16 3 12 17 12 4 18 4 3 19 3 2 20 12 13 21 13 4 22 13 21 23 21 22 24 22 13 25 12 21 26 21 30 27 30 22 28 22 28 29 28 13 30 14 4 31 13 14 32 20 35 33 35 21 34 36 21 35 35 36 36 35 34 37 34 45 38 45 35 39 43 36 40 35 43 41 32 21 42 36 32 43 45 46 44 46 35 45 45 56 46 56 46 47 47 46 48 46 54 49 54 47 50 46 43 51 47 43 52 37 28 53 28 30 54 30 37 55 32 30 56 32 37 57 37 43 58 43 48 59 48 37 60 32 43 61 47 48 62 37 38 63 38 28 64 48 38 65 54 48 66 28 14 67 5 14 68 14 6 69 6 5 70 5 4 71 14 15 72 15 6 73 24 15 74 15 29 75 29 24 76 14 29 77 23 29 78 14 23 79 24 16 80 16 15 81 28 23 82 16 6 83 8 7 84 7 16 85 16 8 86 9 8 87 8 17 88 17 9 89 16 17 90 25 17 91 16 25 92 26 18 93 18 17 94 17 26 95 18 9 96 7 6 97 24 25 98 31 39 99 39 33 100 33 31 101 31 29 102 29 39 103 33 25 104 25 31 105 24 31 106 38 49 107 49 39 108 39 38 109 44 39 110 39 50 111 50 44 112 44 33 113 29 38 114 23 38 115 44 40 116 40 33 117 25 26 118 25 40 119 40 26 120 27 26 121 26 41 122 41 27 123 27 18 124 40 41 125 44 41 126 52 42 127 42 41 128 41 52 129 42 27 130 44 52 131 48 49 132 46 57 133 57 54 134 59 48 135 54 59 136 57 56 137 56 71 138 71 57 139 57 58 140 58 54 141 57 72 142 72 58 143 71 72 144 71 80 145 80 72 146 65 58 147 58 73 148 73 65 149 65 54 150 49 59 151 59 60 152 60 49 153 59 69 154 69 60 155 60 61 156 61 49 157 67 69 158 59 67 159 73 67 160 67 65 161 74 69 162 67 74 163 59 65 164 69 75 165 75 60 166 72 73 167 72 81 168 81 73 169 80 81 170 80 89 171 89 81 172 81 82 173 82 73 174 81 90 175 90 82 176 89 90 177 90 91 178 91 82 179 82 83 180 83 73 181 84 69 182 69 83 183 83 84 184 74 73 185 83 74 186 84 75 187 83 92 188 92 84 189 83 91 190 91 92 191 92 93 192 93 84 193 84 70 194 70 75 195 70 60 196 50 61 197 61 55 198 55 50 199 50 49 200 61 66 201 66 55 202 55 51 203 51 50 204 61 68 205 68 66 206 61 70 207 70 68 208 68 77 209 77 66 210 62 66 211 77 62 212 62 55 213 51 52 214 44 51 215 55 52 216 53 52 217 52 63 218 63 53 219 53 42 220 55 63 221 77 63 222 63 62 223 78 64 224 64 63 225 63 78 226 64 53 227 70 76 228 76 68 229 76 85 230 85 77 231 77 76 232 70 85 233 85 86 234 86 77 235 85 94 236 94 86 237 85 93 238 93 94 239 94 95 240 95 86 241 84 85 242 86 87 243 87 77 244 78 87 245 87 79 246 79 78 247 78 77 248 87 88 249 88 79 250 79 64 251 96 88 252 87 96 253 97 88 254 96 97 255 87 95 256 95 96 faces 1 1 10 2 2 2 10 11 3 11 19 20 4 19 11 10 5 20 19 34 6 11 20 21 7 2 11 12 8 3 12 4 9 12 3 2 10 4 12 13 11 13 21 22 12 21 13 12 13 22 21 30 14 13 22 28 15 12 11 21 16 14 4 13 17 20 35 21 18 36 21 35 19 35 34 45 20 43 36 35 21 32 21 36 22 45 46 35 23 46 45 56 24 47 46 54 25 43 35 46 26 43 46 47 27 37 28 30 28 28 22 30 29 30 21 32 30 37 30 32 31 37 43 48 32 43 37 32 33 48 43 47 34 28 37 38 35 32 36 43 36 38 37 48 37 47 54 48 38 20 34 35 39 14 13 28 40 5 14 6 41 14 5 4 42 6 14 15 43 24 15 29 44 14 29 15 45 23 29 14 46 15 24 16 47 14 28 23 48 6 15 16 49 8 7 16 50 9 8 17 51 16 17 8 52 25 17 16 53 26 18 17 54 17 18 9 55 7 6 16 56 25 16 24 57 31 39 33 58 39 31 29 59 31 33 25 60 31 25 24 61 38 49 39 62 44 39 50 63 39 44 33 64 38 39 29 65 29 23 38 66 33 44 40 67 25 26 17 68 26 25 40 69 27 26 41 70 26 27 18 71 40 41 26 72 41 40 44 73 52 42 41 74 41 42 27 75 25 33 40 76 52 41 44 77 31 24 29 78 23 28 38 79 48 49 38 80 46 57 54 81 59 48 54 82 57 56 71 83 54 57 58 84 57 72 58 85 72 57 71 86 72 71 80 87 65 58 73 88 54 58 65 89 49 59 60 90 48 59 49 91 60 59 69 92 49 60 61 93 67 69 59 94 73 67 65 95 74 69 67 96 67 59 65 97 54 65 59 98 60 69 75 99 72 73 58 100 46 56 57 101 72 81 73 102 81 72 80 103 81 80 89 104 73 81 82 105 81 90 82 106 90 81 89 107 82 90 91 108 73 82 83 109 84 69 83 110 74 73 83 111 69 84 75 112 83 69 74 113 83 92 84 114 92 83 91 115 84 92 93 116 82 91 83 117 75 84 70 118 73 74 67 119 60 75 70 120 50 61 55 121 50 49 61 122 55 61 66 123 50 55 51 124 61 68 66 125 68 61 70 126 66 68 77 127 62 66 77 128 70 61 60 129 55 66 62 130 51 52 44 131 51 55 52 132 53 52 63 133 52 53 42 134 63 52 55 135 77 63 62 136 78 64 63 137 63 64 53 138 62 63 55 139 68 70 76 140 50 51 44 141 76 85 77 142 76 70 85 143 77 85 86 144 76 77 68 145 85 94 86 146 94 85 93 147 86 94 95 148 84 85 70 149 77 86 87 150 78 87 79 151 78 77 87 152 79 87 88 153 78 79 64 154 96 88 87 155 97 88 96 156 96 87 95 157 86 95 87 158 77 78 63 159 93 85 84 160 49 50 39 ================================================ FILE: test/selection/clone.morpho ================================================ // Clone var m = Mesh("square.mesh") var s = Selection(m, fn (x,y,z) y>0.5) print s // expect: var t = s.clone() print t.isselected(0, 0) // expect: false print t.isselected(0, 1) // expect: false print t.isselected(0, 2) // expect: true print t.idlistforgrade(0) // expect: [ 2, 3 ] ================================================ FILE: test/selection/count.morpho ================================================ // Test selections var m = Mesh("square.mesh") fn f(x,y,z) { return y>0.5 } var s = Selection(m, f) print s.count(0) // expect: 2 ================================================ FILE: test/selection/get_index.morpho ================================================ // Test selections var m = Mesh("square.mesh") fn f(x,y,z) { return y>0.5 } var s = Selection(m, f) print s // expect: print s[0,0] // expect: false print s[0,1] // expect: false print s[0,2] // expect: true ================================================ FILE: test/selection/inherited.morpho ================================================ // Test Matrix methods inherited from Object var m = Mesh() var a = Selection(m) print a.respondsto("respondsto") // expect: true print islist(a.respondsto()) // expect: true print a.clss() // expect: @Selection print a.superclass() // expect: @Object print islist(a.invoke("respondsto")) // expect: true print a.has("a") // expect: false ================================================ FILE: test/selection/no_mesh_error.morpho ================================================ import optimize // Create mesh and director field var m = Mesh("circlesquare.mesh") //Circle boundary & area var bndCirc = Selection(m, fn(x,y,z) x^2+y^2<1.0001 && x^2+y^2>0.9999) bndCirc.addgrade(1) // Set up a regularization problem var reg = OptimizationProblem(m) // Create shape and field optimizers var ropt = ShapeOptimizer(reg, m) // Must ensure boundary remains correctly fixed ropt.fix(Selection(bndCirc, boundary=true)) // expect error 'SlNoMsh' ================================================ FILE: test/selection/selection.morpho ================================================ // Test selections var m = Mesh("square.mesh") fn f(x,y,z) { return y>0.5 } var s = Selection(m, f) print s // expect: print s.isselected(0, 0) // expect: false print s.isselected(0, 1) // expect: false print s.isselected(0, 2) // expect: true print s.idlistforgrade(0) // expect: [ 2, 3 ] print s.isselected(0) // expect Error: 'SlIsSlArg' ================================================ FILE: test/selection/selection_withmatrix.morpho ================================================ // Test selections var m = Mesh("square.mesh") var p = ScalarPotential( fn (x,y,z) (y-0.5), fn (x,y,z) Matrix([0,-1,0]) ) var s = Selection(m, fn (q) q>0, p.integrand(m)) print s // expect: print s.isselected(0, 0) // expect: false print s.isselected(0, 1) // expect: false print s.isselected(0, 2) // expect: true print s.idlistforgrade(0) // expect: [ 2, 3 ] ================================================ FILE: test/selection/set_index.morpho ================================================ // Test selections var m = Mesh("square.mesh") var s = Selection(m) print s // expect: s[0,1] = true print s[0,1] // expect: true s[0,1] = false print s[0,1] // expect: false ================================================ FILE: test/selection/set_operations.morpho ================================================ // Selection set operations var m = Mesh("square.mesh") fn f(x,y,z) { return y>0.5 } fn g(x,y,z) { return x<0.5 } var s1 = Selection(m, f) print s1.idlistforgrade(0) // expect: [ 2, 3 ] var s2 = Selection(m, g) print s2.idlistforgrade(0) // expect: [ 0, 2 ] print s1.union(s2).idlistforgrade(0) // expect: [ 0, 2, 3 ] print s1.intersection(s2).idlistforgrade(0) // expect: [ 2 ] print s1.difference(s2).idlistforgrade(0) // expect: [ 3 ] ================================================ FILE: test/selection/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/self/closure.morpho ================================================ class Foo { getClosure() { fn closure() { return self.toString() } return closure } toString() { return "Foo" } } var closure = Foo().getClosure() print closure() // expect: Foo ================================================ FILE: test/self/nested_class.morpho ================================================ // Tests nested classes, which are currently forbidden /*class Outer { method() { print self // exp fn f() { print self // exp class Inner { method() { print self // exp } } Inner().method() } f() } } Outer().method() */ ================================================ FILE: test/self/nested_closure.morpho ================================================ class Foo { getClosure() { fn f() { fn g() { fn h() { return self.toString() } return h } return g } return f } toString() { return "Foo" } } var closure = Foo().getClosure() print closure()()() // expect: Foo ================================================ FILE: test/self/self_at_top_level.morpho ================================================ self // Error 'SlfOtsdClss' ================================================ FILE: test/self/self_in_method.morpho ================================================ class Foo { bar() { return self } baz() { return "baz" } } print Foo().bar().baz() // expect: baz ================================================ FILE: test/self/self_in_top_level_function.morpho ================================================ fn foo() { self // error 'SlfOtsdClss' } ================================================ FILE: test/self/self_set_prop_index.morpho ================================================ class A { a(prop) { self[prop]=true } } var b = A() b.a("foo") print b.foo // expect: true ================================================ FILE: test/slice/arraySlicing.morpho ================================================ var A = Array([[0,1,2,3],[4,5,6,7],[8,9,10,11]]) // ranges print A[0..1,1..2] // expect: [ [ 1, 2 ], [ 5, 6 ] ] // lists print A[[1,2,0,1],[0,0,0,2]] // expect: [ [ 4, 4, 4, 6 ], [ 8, 8, 8, 10 ], [ 0, 0, 0, 2 ], [ 4, 4, 4, 6 ] ] // mix of range and list print A[0..2,[0,0,0,2]] // expect: [ [ 0, 0, 0, 2 ], [ 4, 4, 4, 6 ], [ 8, 8, 8, 10 ] ] // mix of range and int print A[0..1,3] // expect: [ [ 3 ], [ 7 ] ] // mix of list and int print A[2,[0,1,2,1,2]] // expect: [ [ 8, 9, 10, 9, 10 ] ] // range out of bounds try{ print A[-1..2,1..2] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } try{ print A[0..3,1..2] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } // range is not int try{ print A[0.0..2,1..2] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx } // list is out of bounds try{ print A[[-1,2,-1],1..2] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } try{ print A[[100],1..2] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } // list is not int try{ print A[[1,2,1],[0.2,0.1]] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx } try{ print A[[1,2,1],[[1]]] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx } // int + list but int is out of bounds try{ print A[[0,2,1],-1] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } try{ print A[[0,2,1],100] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } try{ print A[[0,2,1],1.01] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx //wrong dim try{ print A[[1,2],[2,3],[0,1]] } catch{ "ArrayDim": print "ArrayDim" // expect: ArrayDim } // garbage in try{ print A[A] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx } try{ print A[nil] } catch{ "NonNmIndx": print "NonNmIndx" // expect: NonNmIndx } ================================================ FILE: test/slice/listSlicing.morpho ================================================ var A = [[0,1,2,3],4,5,6,7,8,9,10,11] // ranges print A[0..1] // expect: [ , 4 ] // lists print A[[1,2,0,1]] // expect: [ 4, 5, , 4 ] print A[-1..2] // expect: [ 11, , 4, 5 ] // range out of bounds try{ print A[0..100] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } // range is not int try{ print A[0.0..2] } catch{ "LstArgs": print "LstArgs" // expect: LstArgs } // list is out of bounds print A[[-1,2,-1]] // expect: [ 11, 5, 11 ] try{ print A[[100]] } catch{ "IndxBnds": print "IndxBnds" // expect: IndxBnds } // list is not int try{ print A[[0.2,0.1]] } catch{ "LstArgs": print "LstArgs" // expect: LstArgs } try{ print A[[[1]]] } catch{ "LstArgs": print "LstArgs" // expect: LstArgs } //wrong dim try{ print A[[1],[2]] } catch{ "LstNumArgs": print "LstNumArgs" // expect: LstNumArgs } // garbage in try{ print A[A] } catch{ "LstArgs": print "LstArgs" // expect: LstArgs } try{ print A[nil] } catch{ "LstArgs": print "LstArgs" // expect: LstArgs } ================================================ FILE: test/slice/matrixSlicing.morpho ================================================ var A = Matrix([[0,1,2,3],[4,5,6,7],[8,9,10,11]]) // ranges print A[0..1,1..2] // expect: [ 1 2 ] // expect: [ 5 6 ] // lists print A[[1,2,0,1],[0,0,0,2]] // expect: [ 4 4 4 6 ] // expect: [ 8 8 8 10 ] // expect: [ 0 0 0 2 ] // expect: [ 4 4 4 6 ] // mix of range and list print A[0..2,[0,0,0,2]] // expect: [ 0 0 0 2 ] // expect: [ 4 4 4 6 ] // expect: [ 8 8 8 10 ] // mix of range and int print A[0..1,3] // expect: [ 3 ] // expect: [ 7 ] // mix of list and int print A[2,[0,1,2,1,2]] // expect: [ 8 9 10 9 10 ] print A[0..1] // expect: [ 0 ] // expect: [ 4 ] // range out of bounds try{ print A[-1..2,1..2] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } try{ print A[0..3,1..2] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } // range is not int try{ print A[0.0..2,1..2] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } // list is out of bounds try{ print A[[-1,2,-1],1..2] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } try{ print A[[100],1..2] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } // list is not int try{ print A[[1,2,1],[0.2,0.1]] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } try{ print A[[1,2,1],[[1]]] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } // int + list but int is out of bounds try{ print A[[0,2,1],-1] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } try{ print A[[0,2,1],100] } catch{ "MtrxBnds": print "MtrxBnds" // expect: MtrxBnds } try{ print A[[0,2,1],0.0] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } //wrong dim try{ print A[[1,2],[2,3],[0,1]] } catch{ "MtrxInvldNumIndx": print "MtrxInvldNumIndx" // expect: MtrxInvldNumIndx } // garbage in try{ print A[A] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } try{ print A[nil] } catch{ "MtrxInvldIndx": print "MtrxInvldIndx" // expect: MtrxInvldIndx } try{ A[1,2,3,4,5] } catch{ "MtrxInvldNumIndx": print "MtrxInvldNumIndx" // expect: MtrxInvldNumIndx } ================================================ FILE: test/sparse/arithmetic.morpho ================================================ // Sparse matrices var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) var b = Sparse([[0,1,1],[1,0,1],[2,3,1],[3,2,1]]) print a // expect: [ 1 0 0 0 ] // expect: [ 0 1 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 1 ] print b // expect: [ 0 1 0 0 ] // expect: [ 1 0 0 0 ] // expect: [ 0 0 0 1 ] // expect: [ 0 0 1 0 ] print a+b // expect: [ 1 1 0 0 ] // expect: [ 1 1 -1 0 ] // expect: [ 0 -1 1 1 ] // expect: [ 0 0 1 1 ] print a-b // expect: [ 1 -1 0 0 ] // expect: [ -1 1 -1 0 ] // expect: [ 0 -1 1 -1 ] // expect: [ 0 0 -1 1 ] print a*b // expect: [ 0 1 0 0 ] // expect: [ 1 0 0 -1 ] // expect: [ -1 0 0 1 ] // expect: [ 0 0 1 0 ] ================================================ FILE: test/sparse/block_constructor_dimensions.morpho ================================================ // Ensure a block contructor produces the correct matrix dimensions var a = Sparse() for (i in 0..4:2) { a[i,i]=1 a[i+1,i]=2 a[i+3,i]=3 } print Sparse([[a.column(0),a.column(1)]]) // expect: [ 1 0 ] // expect: [ 2 0 ] // expect: [ 0 0 ] // expect: [ 3 0 ] // expect: [ 0 0 ] // expect: [ 0 0 ] // expect: [ 0 0 ] // expect: [ 0 0 ] ================================================ FILE: test/sparse/clone.morpho ================================================ // Sparse matrices var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) print a // expect: [ 1 0 0 0 ] // expect: [ 0 1 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 1 ] var b = a.clone() print b // expect: [ 1 0 0 0 ] // expect: [ 0 1 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 1 ] b[1,1]=2 b[3,3]=2 print b // expect: [ 1 0 0 0 ] // expect: [ 0 2 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 2 ] print a // expect: [ 1 0 0 0 ] // expect: [ 0 1 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 1 ] var c = a*a print c // expect: [ 1 0 0 0 ] // expect: [ 0 2 -2 0 ] // expect: [ 0 -2 2 0 ] // expect: [ 0 0 0 1 ] var d=c.clone() print d // expect: [ 1 0 0 0 ] // expect: [ 0 2 -2 0 ] // expect: [ 0 -2 2 0 ] // expect: [ 0 0 0 1 ] print c // expect: [ 1 0 0 0 ] // expect: [ 0 2 -2 0 ] // expect: [ 0 -2 2 0 ] // expect: [ 0 0 0 1 ] ================================================ FILE: test/sparse/col_indices.morpho ================================================ // Sparse matrices var a = Sparse() for (i in 0..10:2) { a[i,i]=1 } print a.colindices() // expect: [ 0, 2, 4, 6, 8, 10 ] ================================================ FILE: test/sparse/column.morpho ================================================ // Sparse matrices var a = Sparse() for (i in 0..4:2) { a[i,i]=1 a[i+1,i]=2 a[i+3,i]=3 } print a.column(0) // expect: [ 1 ] // expect: [ 2 ] // expect: [ 0 ] // expect: [ 3 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] print a.column(1) // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] print a.column(4) // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 0 ] // expect: [ 3 ] ================================================ FILE: test/sparse/concatenate.morpho ================================================ // Concatenate sparse matrices together var a = Sparse([[0,1,1], [1,0,1]]) var b = Matrix([2,3]) print a // expect: [ 0 1 ] // expect: [ 1 0 ] var c = Sparse([[a, b], [b.transpose(), 0]]) print c // expect: [ 0 1 2 ] // expect: [ 1 0 3 ] // expect: [ 2 3 0 ] var c = Sparse([[a, 0, b], [0, a, b], [b.transpose(), b.transpose(), 0]]) print c // expect: [ 0 1 0 0 2 ] // expect: [ 1 0 0 0 3 ] // expect: [ 0 0 0 1 2 ] // expect: [ 0 0 1 0 3 ] // expect: [ 2 3 2 3 0 ] var c = Sparse([[a, b], [b, 0]]) // expect error 'MtrxIncmptbl' ================================================ FILE: test/sparse/dense_sparse_mul.morpho ================================================ // Multiply a dense matrix by sparse matrix from the LHS var A = Sparse(4,3) for (i in 0...4) for (j in 0...3) A[i,j]=i+j print A // expect: [ 0 1 2 ] // expect: [ 1 2 3 ] // expect: [ 2 3 4 ] // expect: [ 3 4 5 ] var B = Matrix([[1,-2,3,-4],[-5,6,-7,8]]) print B // expect: [ 1 -2 3 -4 ] // expect: [ -5 6 -7 8 ] print B * A // expect: [ -8 -10 -12 ] // expect: [ 16 18 20 ] var A = Sparse(4,4) for (i in 0...4) A[i,i]=i+1 print A // expect: [ 1 0 0 0 ] // expect: [ 0 2 0 0 ] // expect: [ 0 0 3 0 ] // expect: [ 0 0 0 4 ] var B = Matrix([1,2,3,4]) print B // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] // expect: [ 4 ] print B.transpose() * A // expect: [ 1 4 9 16 ] ================================================ FILE: test/sparse/dimensions.morpho ================================================ // Dimensions of a sparse matrix var a = Sparse([[1,2,3],[2,4,1],[4,4,0]]) print a // expect: [ 0 0 0 0 0 ] // expect: [ 0 0 3 0 0 ] // expect: [ 0 0 0 0 1 ] // expect: [ 0 0 0 0 0 ] // expect: [ 0 0 0 0 0 ] print a.dimensions() // expect: [ 5, 5 ] var b=a*a print b.dimensions() // expect: [ 5, 5 ] ================================================ FILE: test/sparse/edit_sparse.morpho ================================================ var N = 4 var a = Sparse(N,N) for (i in 0...N) a[i,i]=i+1 var b = a*a print b // expect: [ 1 0 0 0 ] // expect: [ 0 4 0 0 ] // expect: [ 0 0 9 0 ] // expect: [ 0 0 0 16 ] b[0,N-1] = 1 b[N-1,0] = 1 print b // expect: [ 1 0 0 1 ] // expect: [ 0 4 0 0 ] // expect: [ 0 0 9 0 ] // expect: [ 1 0 0 16 ] print b*b // expect: [ 2 0 0 17 ] // expect: [ 0 16 0 0 ] // expect: [ 0 0 81 0 ] // expect: [ 17 0 0 257 ] ================================================ FILE: test/sparse/empty_initializer.morpho ================================================ // Sparse with no initializer var a = Sparse() a[0,0] = 1 a[1,1] = 1 print a // expect: [ 1 0 ] // expect: [ 0 1 ] ================================================ FILE: test/sparse/enumerate.morpho ================================================ // Enumerate values in a sparse object var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) var b = Sparse([[0,1,1],[1,0,1],[2,3,1],[3,2,1]]) print a // expect: [ 1 0 0 0 ] // expect: [ 0 1 -1 0 ] // expect: [ 0 -1 1 0 ] // expect: [ 0 0 0 1 ] var sum=0 for (x in a) sum+=x print sum // expect: 2 for (x in a*a) print x // expect: 1 // expect: 2 // expect: -2 // expect: -2 // expect: 2 // expect: 1 ================================================ FILE: test/sparse/incompatible_add.morpho ================================================ // Incompatible dimensions var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) var b = Sparse([[0,1,1],[1,0,1]]) print a+b // expect Error: 'MtrxIncmptbl' ================================================ FILE: test/sparse/incompatible_mul.morpho ================================================ // Incompatible dimensions var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) var b = Sparse([[0,1,1],[1,0,1]]) print a*b // expect Error: 'MtrxIncmptbl' ================================================ FILE: test/sparse/indices.morpho ================================================ // Sparse matrices var a = Sparse([[0,0,1],[1,1,1],[1,2,-1],[2,1,-1],[2,2,1],[3,3,1]]) for (i in a.indices()) { print i } // expect: [ 3, 3 ] // expect: [ 2, 2 ] // expect: [ 2, 1 ] // expect: [ 1, 2 ] // expect: [ 1, 1 ] // expect: [ 0, 0 ] ================================================ FILE: test/sparse/inherited.morpho ================================================ // Test Matrix methods inherited from Object var err = Sparse([[1,2,3]]) print err.respondsto("respondsto") // expect: true print islist(err.respondsto()) // expect: true print err.clss() // expect: @Sparse print err.superclass() // expect: @Object print islist(err.invoke("respondsto")) // expect: true print err.has("a") // expect: false ================================================ FILE: test/sparse/initializer.morpho ================================================ // Initialize different matrices var b = Matrix(2,2) b[1,1]=4 print b[0,1] // expect: 0 print b[1,1] // expect: 4 print b // expect: [ 0 0 ] // expect: [ 0 4 ] var N = 4 var a = Sparse(N,N) for(var i=0; i>" // expect: <<2>> ================================================ FILE: test/string/interpolation_syntaxerror.morpho ================================================ var a = 1.21321321213 print "${a:###sasdsad}" // expect error 'IntrpIncmp' ================================================ FILE: test/string/interpolation_types.morpho ================================================ // Check interpolation types var i = 1 var x = 0.5 var tr = true var fl = false var n = nil print "${i} ${x} ${tr} ${fl} ${n}" // expect: 1 0.5 true false nil var lst = [ 1, 2, 3 ] print "${lst}" // expect: [ 1, 2, 3 ] // Functions fn fun() { return x } print "${fun}" // expect: // Closures fn f(a) { fn g() { return a } return g } var q = f(1) print "${q}" // expect: <> // Invocations var q = Object().respondsto print "${q}" // expect: . // Built in functions print "${cos}" // expect: class Klass {} print "${Klass}" // expect: @Klass class Chatty { init(x) { self.name = x } tostring() { return "@#%!"+self.name+"*&^" } } var s = Chatty("hello") print "${s}" // expect: @#%!hello*&^ ================================================ FILE: test/string/invalid_unicode.morpho ================================================ // Test string literals with invalid unicode specifier print "\U00.F98B" // expect error 'InvldUncd' ================================================ FILE: test/string/literals.morpho ================================================ // Test string literals print "(" + "" + ")" // expect: () print "a string" // expect: a string // Non-ASCII. print "A~¶Þॐஃ" // expect: A~¶Þॐஃ ================================================ FILE: test/string/multiline.morpho ================================================ var a = "1 2 3" print a // expect: 1 // expect: 2 // expect: 3 ================================================ FILE: test/string/split.morpho ================================================ // Splitting strings var t = "A~;¶Þॐ,ஃ" print t.split(";,") // expect: [ A~, ¶Þॐ, ஃ ] print "A~;¶Þॐ,ஃ;".split(";,") // expect: [ A~, ¶Þॐ, ஃ, ] print ";;;".split(";") // expect: [ , , , ] print "hello".split(";") // expect: [ hello ] ================================================ FILE: test/string/split_gc.morpho ================================================ // Ensure garbage collector and string split are interacting properly var str = "a b c d f g" var w //var c for (i in 1..10){ print str.split(" ") } // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] // expect: [ a, b, c, d, f, g ] ================================================ FILE: test/string/string_veneer.morpho ================================================ // Test string veneer class var a = "Hello" print a // expect: Hello print a.count() // expect: 5 var b = a.count print b() // expect: 5 print b // expect: Hello. ================================================ FILE: test/string/tonumber.morpho ================================================ // Convert strings to numbers var a = "123" var b = "baa" print a.isnumber() // expect: true print b.isnumber() // expect: false print "+1.23e-12".isnumber() // expect: true print Int("122")+1 // expect: 123 print Int("125x + 3 #@#@")+1 // expect: 126 print Int("12.343")+1 // expect: 13 print Float("1.1") // expect: 1.1 print Float("-1.1") // expect: -1.1 print Float("+1e-3") // expect: 0.001 ================================================ FILE: test/string/unicode.morpho ================================================ // Test string literals with unicode character print "\U0001F98B" // expect: 🦋 ================================================ FILE: test/string/unterminated.morpho ================================================ // expect error 'UntrmStrng' "this string has no close quote ================================================ FILE: test/string/utf8.morpho ================================================ // UTF8 var s = "A~¶Þॐஃ" print s.count() // expect: 6 for (x in s) print x // expect: A // expect: ~ // expect: ¶ // expect: Þ // expect: ॐ // expect: ஃ ================================================ FILE: test/super/bound_method.morpho ================================================ class A { method(arg) { print "A.method(" + arg + ")" } } class B is A { getClosure() { return super.method } method(arg) { print "B.method(" + arg + ")" } } var closure = B().getClosure() closure("arg") // expect: A.method(arg) ================================================ FILE: test/super/call_other_method.morpho ================================================ class Base { foo() { print "Base.foo()" } } class Derived is Base { bar() { print "Derived.bar()" super.foo() } } Derived().bar() // expect: Derived.bar() // expect: Base.foo() ================================================ FILE: test/super/call_same_method.morpho ================================================ class Base { foo() { print "Base.foo()" } } class Derived is Base { foo() { print "Derived.foo()" super.foo() } } Derived().foo() // expect: Derived.foo() // expect: Base.foo() ================================================ FILE: test/super/closure.morpho ================================================ class Base { toString() { return "Base" } } class Derived is Base { getClosure() { fn closure() { return super.toString() } return closure } toString() { return "Derived" } } var closure = Derived().getClosure() print closure() // expect: Base ================================================ FILE: test/super/constructor.morpho ================================================ class Base { init(a, b) { print "Base.init(" + a + ", " + b + ")" } } class Derived is Base { init() { print "Derived.init()" super.init("a", "b") } } Derived() // expect: Derived.init() // expect: Base.init(a, b) ================================================ FILE: test/super/extra_arguments.morpho ================================================ class Base { foo(a, b) { print "Base.foo(" + a + ", " + b + ")" } } class Derived is Base { foo() { print "Derived.foo()" // expect: Derived.foo() super.foo("a", "b", "c", "d") // expect error 'InvldArgs' } } Derived().foo() ================================================ FILE: test/super/indirectly_inherited.morpho ================================================ class A { foo() { print "A.foo()" } } class B is A {} class C is B { foo() { print "C.foo()" super.foo() } } C().foo() // expect: C.foo() // expect: A.foo() ================================================ FILE: test/super/missing_arguments.morpho ================================================ class Base { foo(a, b) { print "Base.foo(" + a + ", " + b + ")" } } class Derived is Base { foo() { super.foo(1) // expect error 'InvldArgs' } } Derived().foo() ================================================ FILE: test/super/no_superclass_bind.morpho ================================================ class Base { foo() { super.doesNotExist // expect error 'ClssLcksMthd' } } Base().foo() ================================================ FILE: test/super/no_superclass_call.morpho ================================================ class Base { foo() { super.doesNotExist(1) // expect error 'ClssLcksMthd' } } Base().foo(); ================================================ FILE: test/super/no_superclass_method.morpho ================================================ class Base {} class Derived is Base { foo() { super.doesNotExist(1) // expect error 'ClssLcksMthd' } } Derived().foo(); ================================================ FILE: test/super/parenthesized.morpho ================================================ class A { method() {} } class B < A { method() { // expect error 'ExpctDtSpr' (super).method() } } ================================================ FILE: test/super/reassign_superclass.morpho ================================================ class Base { method() { print "Base.method()" } } class Derived < Base { method() { super.method() } } class OtherBase { method() { print "OtherBase.method()" } } var derived = Derived() derived.method() // expect: Base.method() Base = OtherBase derived.method() // expect: Base.method() ================================================ FILE: test/super/self_in_superclass_method.morpho ================================================ class Base { init(a) { self.a = a } } class Derived < Base { init(a, b) { super.init(a) self.b = b } } var derived = Derived("a", "b") print derived.a // expect: a print derived.b // expect: b ================================================ FILE: test/super/super_at_top_level.morpho ================================================ super.foo // expect error 'SprOtsdClss' ================================================ FILE: test/super/super_in_closure_in_inherited_method.morpho ================================================ class A { say() { print "A" } } class B < A { getClosure() { fn closure() { super.say() } return closure } say() { print "B" } } class C < B { say() { print "C" } } C().getClosure()() // expect: A ================================================ FILE: test/super/super_in_inherited_method.morpho ================================================ class A { say() { print "A" } } class B < A { test() { super.say() } say() { print "B" } } class C < B { say() { print "C" } } C().test() // expect: A ================================================ FILE: test/super/super_in_top_level_function.morpho ================================================ fn foo() { super.bar() // expect error 'SprOtsdClss' } ================================================ FILE: test/super/super_without_dot.morpho ================================================ class A {} class B < A { method() { // expect error 'ExpctDtSpr' super } } ================================================ FILE: test/super/super_without_name.morpho ================================================ class A {} class B < A { method() { super. // expect error 'ExpExpr' } } ================================================ FILE: test/syntax/comments.morpho ================================================ /* ********************************** * morpho test suite * comments.morpho * Test comments * ********************************** */ print 1 // print 2 // expect: 1 print // 1 2 // expect: 2 /* print 3 */ /* Nested /* comment */ */ ================================================ FILE: test/syntax/empty_file.morpho ================================================ ================================================ FILE: test/syntax/illegal_chars_in_symbol.morpho ================================================ /* ******************************************************************** * morpho test suite * illegal_chars_in_symbol.morpho * Check that an err. is raised if symbols contain illegal chars. * ******************************************************************** */ var @notasymbol = 4 // expect error 'VarExpct' print @notasymbol ================================================ FILE: test/syntax/symbols.morpho ================================================ /* ********************************** * morpho test suite * symbols.morpho * Test symbols * ********************************** */ var _symbol = 2 print _symbol // expect: 2 var Breakfast = "Eggs" var breakfast = "Bacon" print Breakfast // expect: Eggs class My_class {} var c = My_class() print c // expect: var sym123 = 6 print sym123 // expect: 6 ================================================ FILE: test/system/args.morpho ================================================ // Gets the arguments and prints them system("morpho6 system/args.xmorpho") // expect: [ ] system("morpho6 system/args.xmorpho hello world") // expect: [ hello, world ] ================================================ FILE: test/system/args.xmorpho ================================================ // Gets the arguments and prints them var args = System.arguments() print args ================================================ FILE: test/system/system.morpho ================================================ // Test System class print isstring(System.platform()) // expect: true print isstring(System.version()) // expect: true print isnumber(System.clock()) // expect: true System.exit() print "We never get here" ================================================ FILE: test/test.py ================================================ #!/usr/bin/env python3 # Simple automated testing # T J Atherton Sept 2020 # # Each input file is supplied to a test command, the results # are piped to a file and the output is compared with expectations # extracted from the input file. # Expectations are coded into comments in the input file as follows: # import necessary modules import os, glob, sys import regex as rx from functools import reduce import operator import colored from colored import stylize # define what command to use to invoke the interpreter command = 'morpho6' # define the file extension to test ext = 'morpho' # We reduce any errors to this value err = '@error' # We reduce any stacktrace lines to this values stk = '@stacktrace' # Removes control characters def remove_control_characters(str): return rx.sub(r'\x1b[^m]*m', '', str.rstrip()) # Simplify error reports def simplify_errors(str): # this monster regex extraxts NAME from error messages of the form error ... 'NAME' return rx.sub('.*[E|e]rror[ :]*\'([A-z;a-z]*)\'.*', err+'[\\1]', str.rstrip()) # Simplify stacktrace def simplify_stacktrace(str): return rx.sub(r'.*at line.*', stk, str.rstrip()) # Find an expected value def findvalue(str): return rx.findall(r'// expect: ?(.*)', str) # Find an expected error def finderror(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) return rx.findall(r'.*[E|e]rror[ :].*?(.*)', str) # Find an expected error def iserror(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) test=rx.findall(r'@error.*', str) return len(test)>0 # Find an expected error def isin(str): #return rx.findall(r'\/\/ expect ?(.*) error', str) test=rx.findall(r'.*in .*', str) return len(test)>0 # Remove elements from a list def remove(list, remove_list): test_list = list for i in remove_list: try: test_list.remove(i) except ValueError: pass return test_list # Find what is expected def findexpected(str): out = finderror(str) # is it an error? if (out!=[]): out = [simplify_errors(str)] # if so, simplify it else: out = findvalue(str) # or something else? return out # Works out what we expect from the input file def getexpect(filepath): # Load the file file_object = open(filepath, 'r', encoding="utf8") lines = file_object.readlines() file_object.close() #Find any expected values over all lines if (lines != []): out = list(map(findexpected, lines)) out = reduce(operator.concat, out) else: out = [] return out # Gets the output generated def getoutput(filepath): # Load the file file_object = open(filepath, 'r', encoding="utf8") lines = file_object.readlines() file_object.close() # remove all control characters lines = list(map(remove_control_characters, lines)) # Convert errors to our universal error code lines = list(map(simplify_errors, lines)) # Identify stack trace lines lines = list(map(simplify_stacktrace, lines)) for i in range(len(lines)-1): if (iserror(lines[i])): if (isin(lines[i+1])): lines[i+1]=stk # and remove them return list(filter(lambda x: x!=stk, lines)) # Test a file def test(file,testLog,CI): ret = 0 if not CI: print(file+":", end=" ") # Create a temporary file in the same directory tmp = file + '.out' #Get the expected output expected=getexpect(file) # Run the test os.system(command + ' ' +file + ' > ' + tmp) # If we produced output if os.path.exists(tmp): # Get the output out=getoutput(tmp) # Was it expected? if(expected==out): if not CI: print(stylize("Passed",colored.fg("green"))) ret = 1 else: if not CI: print(stylize("Failed",colored.fg("red"))) print(" Expected: ", expected) print(" Output: ", out) else: print("\n::error file = {",file,"}::{",file," Failed}") #also print to the test log print(file+":", end=" ",file = testLog) print("Failed", file = testLog) if len(out) == len(expected): failedTests = list(i for i in range(len(out)) if expected[i] != out[i]) print("Tests " + str(failedTests) + " did not match expected results.", file = testLog) for testNum in failedTests: print("Test "+str(testNum), file = testLog) print(" Expected: ", expected[testNum], file = testLog) print(" Output: ", out[testNum], file = testLog) else: print(" Expected: ", expected, file = testLog) print(" Output: ", out, file = testLog) print("\n",file = testLog) # Delete the temporary file os.remove(tmp) return ret print('--Begin testing---------------------') # open a test log # write failures to log success=0 # number of successful tests total=0 # total number of tests # look for a command line arguement that says # this is being run for continous integration CI = False # Also look for a command line argument that says this is being run with multiple threads MT = False for arg in sys.argv: if arg == '-c': # if the argument is -c, then we are running in CI mode CI = True if arg == '-m': # if the argument is -m, then we are running in multi-thread mode MT = True failedTestsFileName = "FailedTests.txt" if MT: failedTestsFileName = "FailedTestsMultiThreaded.txt" command += " -w4" print("Running tests with 4 threads") files=glob.glob('**/**.'+ext, recursive=True) with open(failedTestsFileName,'w', encoding="utf8") as testLog: for f in files: # print(f) success+=test(f,testLog,CI) total+=1 # if (not CI) and (not success == total): # os.system("emacs FailedTests.txt &") print('--End testing-----------------------') print(success, 'out of', total, 'tests passed.') if CI and success0) { f(String(x.pop())) f(x) } } f([1,2,3]) // expect: 3 // expect: 2 // expect: 1 ================================================ FILE: test/types/multiple_dispatch/scope.morpho ================================================ // Dispatch a function on multiple types fn f() { return 0 } fn f(x) { return 1 } { fn f(x,y) { return 1 } } print f() // expect: 0 print f(1) // expect: 1 print f(1,2) // expect error 'MltplDsptchFld' ================================================ FILE: test/types/multiple_dispatch/two.morpho ================================================ // Dispatch a function on multiple types fn f() { return 0 } fn f(x) { return 1 } print f() // expect: 0 print f("Hi") // expect: 1 ================================================ FILE: test/types/multiple_dispatch/value.morpho ================================================ // Dipatch a function on value types fn f(Int x) { return 0 } fn f(Float x) { return 1 } print f(1) // expect: 0 print f(0.1) // expect: 1 ================================================ FILE: test/types/multiple_dispatch/varg.morpho ================================================ // Dispatch a function with variadic args fn f() { return 0 } fn f(...x) { return "V" } print f() // expect: 0 print f(1, 2) // expect: V print f(1,2,3) // expect: V print f(1,2,3,4) // expect: V ================================================ FILE: test/types/multiple_dispatch/varg_as_backup.morpho ================================================ // Dispatch a function with variadic args fn f() { return 0 } fn f(a) { return 1 } fn f(a,b) { return 2 } fn f(...x) { return "V" } print f() // expect: 0 print f(1) // expect: 1 print f(1, 2) // expect: 2 print f(1,2,3) // expect: V print f(1,2,3,4) // expect: V ================================================ FILE: test/types/type_from_namespace.morpho ================================================ // Use a type that doesn't exist from a namespace in a function definition import "type_namespace.xmorpho" as ns ns.Cat x = ns.Cat("Phineas") x.hiss() // expect: Phineas hisses ================================================ FILE: test/types/type_in_function.morpho ================================================ // Type definition in function fn f() { String u = "Boo" print u } f() // expect: Boo ================================================ FILE: test/types/type_in_function_fake_namespace.morpho ================================================ // Use a type that doesn't exist from a namespace in a function definition import "type_namespace.xmorpho" as ns fn f(foo.Dog x) { x.hiss() } f(ns.Cat("Moggies")) // expect error 'UnknwnNmSpc' ================================================ FILE: test/types/type_in_function_namespace.morpho ================================================ // Use types from a namespace in a function definition import "type_namespace.xmorpho" as ns print ns.Cat // expect: @Cat fn f(ns.Cat x) { x.hiss() } f(ns.Cat("Moggies")) // expect: Moggies hisses ================================================ FILE: test/types/type_in_function_namespace_not_found.morpho ================================================ // Use a type that doesn't exist from a namespace in a function definition import "type_namespace.xmorpho" as ns fn f(ns.Hamster x) { x.hiss() } f(ns.Cat("Moggies")) // expect error 'SymblUndfNmSpc' ================================================ FILE: test/types/type_in_global.morpho ================================================ // Check type String a = "Foo" print a // expect: Foo ================================================ FILE: test/types/type_inheritance.morpho ================================================ // Types and inheritance class A { } class B is A { } class C is B { } class D is B { } fn f() { A a = B() A x = C() B y = D() print a // expect: print x // expect: print y // expect: } f() ================================================ FILE: test/types/type_instance.morpho ================================================ // Check type of an instance class A { } A a = A() print a // expect: ================================================ FILE: test/types/type_namespace.xmorpho ================================================ // Define some classes in a namespace class Pet { init(name) { self.name = name } } class Dog is Pet { } class Cat is Pet { hiss() { print "${self.name} hisses" } } ================================================ FILE: test/types/type_violation_constant.morpho ================================================ // Type definition in function fn f() { String u = 1 } f() // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_dictionary.morpho ================================================ // Type violation in function from a Dictionary fn f() { String u u = { "a" : "b" } } f() // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_global.morpho ================================================ // Type definition in global context String u = 1 // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_in_function.morpho ================================================ // Type violation in function fn f() { String u u = [] } f() // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_inheritance.morpho ================================================ // Types and inheritance class A { } class B is A { } class C is B { } class D is A { } fn f() { A a = B() A x = C() B y = C() B z = D() // expect error 'TypeErr' } f() ================================================ FILE: test/types/type_violation_instance.morpho ================================================ // Type violation by assigning incorrect instance class A { } class B { } fn f() { A a = B() // expect error 'TypeErr' } f() ================================================ FILE: test/types/type_violation_matrix.morpho ================================================ // Type violation in function from a Matrix fn f() { String u u = Matrix(2,2) } f() // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_propagation.morpho ================================================ // Detect type violations from propagation fn f() { String u var a = 1 u = a } f() // expect error 'TypeErr' ================================================ FILE: test/types/type_violation_range.morpho ================================================ // Type violation in function from a Range fn f() { String u u = 1..3 } f() // expect error 'TypeErr' ================================================ FILE: test/valgrind.py ================================================ #!/usr/bin/env python3 # Simple automated testing with valgrind # T J Atherton Jan 2025 # import necessary modules import os, glob, sys import regex as rx from functools import reduce import operator import colored from colored import stylize # define what command to use to invoke valgrind valgrind = 'valgrind' # define what command to use to invoke the interpreter command = 'morpho6' # define the file extension to test ext = 'morpho' def checkvalgrindlog(filepath): file_object = open(filepath, 'r') lines = file_object.readlines() file_object.close() for l in lines: if (rx.search('in use at exit: 0 bytes in 0 blocks', l)): return True return False # Test a file def test(file, CI): ret = 0 if not CI: print(file+":", end=" ") # Create a temporary file in the same directory tmp = file + '.valgrind' # Run the test os.system(valgrind + ' --log-file='+tmp + ' ' + command + ' ' +file + ' > /dev/null') valpass = False if os.path.exists(tmp): valpass = checkvalgrindlog(tmp) # Was it expected? if(valpass): if not CI: print(stylize("Passed",colored.fg("green"))) ret = 1 else: if not CI: print(stylize("Failed",colored.fg("red"))) else: print(file + " Failed") # Delete the temporary files os.system('rm ' + tmp) return ret print('--Begin testing---------------------') # open a test log # write failures to log success=0 # number of successful tests total=0 # total number of tests # look for a command line arguement that says # this is being run for continous integration CI = False for arg in sys.argv: if arg == '-c': # if the argument is -c, then we are running in CI mode CI = True files=glob.glob('**/**.'+ext, recursive=True) for f in files: success+=test(f,CI) total+=1 print('--End testing-----------------------') print(success, 'out of', total, 'tests passed.') if CI and success // expect: // expect: // Test whether importing without .vtk works for (filename in filenames) { vtkE = VTKExporter(m1) vtkE.export(filename) vtkI = VTKImporter(filename) var m2 = vtkI.mesh() print m2 } // expect: // expect: // expect: ================================================ FILE: test/vtk/export_and_import_mesh.morpho ================================================ import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var filename = "data.vtk" var vtkE = VTKExporter(m1) vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() print m1.vertexmatrix() // expect: [ -1 1 ] // expect: [ 0 0 ] // expect: [ 0 0 ] print m2.vertexmatrix() // expect: [ -1 1 ] // expect: [ 0 0 ] // expect: [ 0 0 ] ================================================ FILE: test/vtk/export_and_import_no_fieldname.morpho ================================================ // Testing whether exporting and importing a field with no field name // works. In this case, the fieldname will be either "scalars" or "vectors" import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var f1 = Field(m1, fn(x,y,z) x) var g1 = Field(m1, fn(x,y,z) Matrix([x,2*x,3*x])) var vtkE = VTKExporter(f1) vtkE.addfield(g1) var filename = "data.vtk" vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() var f2 = vtkI.field("scalars") var g2 = vtkI.field("vectors") print m2 // expect: print f2 // expect: // expect: [ -1 ] // expect: [ 1 ] print g2 // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ -3 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/vtk/export_and_import_scalar.morpho ================================================ // Testing whether exporting and importing a scalar field works import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var f1 = Field(m1, fn(x,y,z) x) var vtkE = VTKExporter(f1, fieldname="f") var filename = "data.vtk" vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() var f2 = vtkI.field("f") print m2 // expect: print f2 // expect: // expect: [ -1 ] // expect: [ 1 ] ================================================ FILE: test/vtk/export_and_import_scalar_and_vector.morpho ================================================ // Testing whether exporting and importing a scalar and a vector field // together works import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var f1 = Field(m1, fn(x,y,z) x) var g1 = Field(m1, fn(x,y,z) Matrix([x,2*x,3*x])) var vtkE = VTKExporter(f1, fieldname="f") vtkE.addfield(g1, fieldname="g") var filename = "data.vtk" vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() var f2 = vtkI.field("f") var g2 = vtkI.field("g") print m2 // expect: print f2 // expect: // expect: [ -1 ] // expect: [ 1 ] print g2 // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ -3 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/vtk/export_and_import_vector.morpho ================================================ // Testing whether exporting and importing a vector field works import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var g1 = Field(m1, fn(x,y,z) Matrix([x,2*x,3*x])) var vtkE = VTKExporter(g1, fieldname="g") var filename = "data.vtk" vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() var g2 = vtkI.field("g") print m2 // expect: print g2 // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ -3 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/vtk/export_and_import_vector_2d.morpho ================================================ // Testing whether exporting and importing a 2D vector field works. This // should add a 0 in the z component of the vector in the saved file. import vtk import meshtools var m1 = LineMesh(fn (t) [t,0,0], -1..1:2) var g1 = Field(m1, fn(x,y,z) Matrix([x,2*x])) var vtkE = VTKExporter(g1, fieldname="g") var filename = "data.vtk" vtkE.export(filename) var vtkI = VTKImporter(filename) var m2 = vtkI.mesh() var g2 = vtkI.field("g") print m2 // expect: print g2 // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ 0 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 0 ] ================================================ FILE: test/vtk/export_import_2d.morpho ================================================ import meshtools import vtk var m = Mesh("square.mesh") var fname = "square.vtk" VTKExporter(m).export(fname) var m2 = VTKImporter(fname).mesh() print m2.maxgrade() // expect: 2 ================================================ FILE: test/vtk/export_import_3d.morpho ================================================ import meshtools import vtk var m = Mesh("tetrahedron.mesh") var fname = "tetrahedron.vtk" VTKExporter(m).export(fname) var m2 = VTKImporter(fname).mesh() print m2.maxgrade() // expect: 3 ================================================ FILE: test/vtk/export_incorrect_field_4d_vector.morpho ================================================ // Trying to export a field of dimension not currently supported by the VTKExporter import vtk import meshtools var m = AreaMesh(fn (u,v) [u,v,0], -1..1:0.5, -1..1:0.5) m.addgrade(1) var f3 = Field(m, Matrix([0,1,2,3])) // Vector field of >3 dimensions VTKExporter(f3).export() // expect: Error 'FieldDimErr' : Expected a scalar or a 2D/3D vector field, but received a vector field with 4 dimensions. ================================================ FILE: test/vtk/export_incorrect_field_grade1.morpho ================================================ // Trying to export a field of dimension not currently supported by the VTKExporter import vtk import meshtools var m = AreaMesh(fn (u,v) [u,v,0], -1..1:0.5, -1..1:0.5) m.addgrade(1) var f1 = Field(m, 0, grade=1) // Field defined on the edges VTKExporter(f1).export() // expect: Error 'FieldShapeErr' : Received a field with shape `[0,1,0]`. Fields with shape other than [1,0,0] are not currently supported. ================================================ FILE: test/vtk/export_incorrect_field_grade2.morpho ================================================ // Trying to export a field of dimension not currently supported by the VTKExporter import vtk import meshtools var m = AreaMesh(fn (u,v) [u,v,0], -1..1:0.5, -1..1:0.5) m.addgrade(1) var f2 = Field(m, 0, grade=2) // Field defined on the facets VTKExporter(f2).export() // expect: Error 'FieldShapeErr' : Received a field with shape `[0,0,1]`. Fields with shape other than [1,0,0] are not currently supported. ================================================ FILE: test/vtk/export_incorrect_field_tensor.morpho ================================================ // Trying to export a field of dimension not currently supported by the VTKExporter import vtk import meshtools var m = AreaMesh(fn (u,v) [u,v,0], -1..1:0.5, -1..1:0.5) m.addgrade(1) var f4 = Field(m, Matrix([[0,1,2],[3,4,5],[6,7,8]])) // Matrix field of non-columnar vectors VTKExporter(f4).export() // expect: Error 'FieldDimErr' : Expected a scalar or a 2D/3D vector field, but received a non-columnar matrix with dimensions [3,3]. ================================================ FILE: test/vtk/import_external_vtk.morpho ================================================ // Attempting to open a file rbc_001.vtk obtained from // https://people.sc.fsu.edu/~jburkardt/data/vtk/vtk.html import vtk import plot var vtkI = VTKImporter("rbc_001.vtk") var m = vtkI.mesh() print m.count(0) // expect: 500 print m.count(2) // expect: 996 ================================================ FILE: test/vtk/import_mesh.morpho ================================================ // Import a file containing just a mesh import vtk import meshtools var vtkI = VTKImporter("mesh.vtk") var m = vtkI.mesh() print m // expect: ================================================ FILE: test/vtk/import_scalar.morpho ================================================ // Import a file containing a mesh and a scalar field import vtk import meshtools var vtkI = VTKImporter("mesh_scalar.vtk") var m = vtkI.mesh() print m // expect: var f = vtkI.field("f") print f // expect: // expect: [ -1 ] // expect: [ 1 ] ================================================ FILE: test/vtk/import_scalar_vector.morpho ================================================ // Import a file containing a mesh, a scalar field and a vector field import vtk import meshtools var vtkI = VTKImporter("mesh_scalar_vector.vtk") var f = vtkI.field("f") var m = vtkI.mesh() print m // expect: print f // expect: // expect: [ -1 ] // expect: [ 1 ] var g = vtkI.field("g") print g // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ -3 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/vtk/import_vector.morpho ================================================ // Import a file containing a mesh and a vector field import vtk import meshtools var vtkI = VTKImporter("mesh_vector.vtk") var m = vtkI.mesh() print m // expect: var g = vtkI.field("g") print g // expect: // expect: [ -1 ] // expect: [ -2 ] // expect: [ -3 ] // expect: [ 1 ] // expect: [ 2 ] // expect: [ 3 ] ================================================ FILE: test/vtk/importer_containsfield.morpho ================================================ // Checking whether the contiainsfield function works import vtk import meshtools var vtkI = VTKImporter("mesh_scalar.vtk") print vtkI.containsfield("f") // expect: true print vtkI.containsfield("g") // expect: false ================================================ FILE: test/vtk/importer_fieldlist.morpho ================================================ // Listing all the fields in the file import vtk import meshtools var vtkI = VTKImporter("mesh_scalar_vector.vtk") print vtkI.fieldlist() // expect: [ f, g ] ================================================ FILE: test/vtk/importer_incorrect_fieldname.morpho ================================================ // Trying to get a field that is not in the file import vtk import meshtools var vtkI = VTKImporter("mesh_scalar.vtk") var f = vtkI.field("h") // expect: Error 'FieldNotFound' : Couldn't find the field `h` in the file. ================================================ FILE: test/vtk/mesh.vtk ================================================ # vtk DataFile Version 3.0 Exported using Morpho https://github.com/Morpho-lang/morpho ASCII DATASET UNSTRUCTURED_GRID POINTS 2 float -1 0 0 1 0 0 CELLS 1 3 2 0 1 CELL_TYPES 1 3 ================================================ FILE: test/vtk/mesh_scalar.vtk ================================================ # vtk DataFile Version 3.0 Exported using Morpho https://github.com/Morpho-lang/morpho ASCII DATASET UNSTRUCTURED_GRID POINTS 2 float -1 0 0 1 0 0 CELLS 1 3 2 0 1 CELL_TYPES 1 3 POINT_DATA 2 SCALARS f float 1 LOOKUP_TABLE default -1 1 ================================================ FILE: test/vtk/mesh_scalar_vector.vtk ================================================ # vtk DataFile Version 3.0 Exported using Morpho https://github.com/Morpho-lang/morpho ASCII DATASET UNSTRUCTURED_GRID POINTS 2 float -1 0 0 1 0 0 CELLS 1 3 2 0 1 CELL_TYPES 1 3 POINT_DATA 2 SCALARS f float 1 LOOKUP_TABLE default -1 1 VECTORS g float -1 -2 -3 1 2 3 ================================================ FILE: test/vtk/mesh_vector.vtk ================================================ # vtk DataFile Version 3.0 Exported using Morpho https://github.com/Morpho-lang/morpho ASCII DATASET UNSTRUCTURED_GRID POINTS 2 float -1 0 0 1 0 0 CELLS 1 3 2 0 1 CELL_TYPES 1 3 POINT_DATA 2 VECTORS g float -1 -2 -3 1 2 3 ================================================ FILE: test/vtk/rbc_001.vtk ================================================ # vtk DataFile Version 1.0 rbc_001.vtk 3D Unstructured Grid of Triangles ASCII DATASET UNSTRUCTURED_GRID POINTS 500 float -3.424999 -0.855454 2.257396 -1.484919 0.665606 -3.151304 1.636841 -0.848154 -0.458954 3.732041 0.187906 -1.319734 -1.756719 0.682006 0.807596 0.911641 -0.828054 3.040696 -0.218059 -0.489374 -3.806524 -1.078099 0.891706 -2.420454 -3.338019 0.263706 1.386896 2.931841 1.447006 1.793796 -1.796229 0.715706 1.214996 2.421641 1.454706 0.904796 -0.204659 0.658506 3.627796 -2.160579 -0.044274 3.331996 0.495541 -0.380754 -3.778464 0.053641 1.122906 -2.441584 -1.962639 -1.126464 3.026496 0.009941 1.061706 2.921296 0.252541 0.849906 0.570996 -1.675219 -1.336964 2.454096 0.409141 0.968806 -1.146214 2.016341 1.414006 -2.211994 -1.008069 -1.305914 1.804096 -2.530049 0.495106 2.133796 2.489541 0.835106 -3.168524 -1.025719 -1.208374 -2.156554 0.768841 -0.904814 -2.371244 3.327941 -0.212814 1.173396 -0.179759 0.895606 -1.223224 2.897641 -0.330874 -1.827444 0.013941 -0.720894 -3.509614 -1.960699 -1.392874 1.492296 -2.685229 0.550306 -0.706694 -2.297709 0.619406 0.586196 -3.521319 -0.083864 1.833196 -2.201219 -0.828584 -3.217494 -0.360359 -0.889074 3.534896 3.468741 -0.190594 0.697996 -0.013059 1.067406 -2.013034 -2.036049 0.250906 3.181796 -3.022249 0.471206 -0.260454 -3.295369 -0.809574 -2.371464 -1.521199 0.796606 2.329096 0.574041 -0.871424 -2.850944 1.364141 -0.949124 1.757796 -3.098809 -1.382564 1.671896 0.341441 -0.982094 -0.316984 3.380241 0.552906 2.256096 -1.214439 0.759306 -1.130544 1.671941 1.292106 -2.835764 2.491941 -0.394084 -2.298934 2.029841 -0.587094 -2.285774 -2.777879 -0.951204 -2.762034 0.566441 -0.961874 -1.878424 3.446041 0.698506 -2.345714 -3.547709 -0.450094 2.114896 -2.336589 0.629806 1.022096 0.848541 1.224606 2.076096 1.314241 1.287706 2.779996 -2.709559 -1.280744 -2.224514 3.367141 0.017976 -1.816064 3.232341 0.051706 2.114896 0.541141 -0.869594 3.255796 2.128541 -0.497124 2.652896 -0.200059 -1.053504 3.208996 -3.756499 0.066606 0.776996 -3.967359 -1.328364 -0.088464 -1.813299 0.726006 -1.791184 3.796541 1.269606 1.178996 1.059441 -0.933104 -0.369634 0.764641 -0.181114 3.786496 3.614641 0.458406 1.807496 0.174741 -1.149784 2.131896 1.279841 -0.896984 -0.835184 -0.483759 0.750706 -0.474934 1.634441 -0.617364 2.942196 1.827941 -0.867034 1.017696 3.867841 0.122106 0.346746 0.176841 1.027506 1.681196 2.225041 1.175706 2.845196 -2.500429 -1.389734 0.049896 -2.051029 0.619206 2.480296 -3.125099 -0.430314 2.663296 -0.736519 -1.247034 1.423396 1.188141 -0.197534 -3.677924 1.832341 -0.803304 1.991596 0.211041 1.027506 -1.579384 -3.254559 0.137506 -1.890674 -0.771749 0.856406 1.552396 4.013141 1.273606 0.476596 -2.140089 0.548706 -2.465164 -0.101059 0.185806 -4.007984 -2.145699 0.432206 2.838696 -0.124659 -1.003264 -2.836324 -3.656639 -1.327274 -1.205834 1.990941 0.889406 3.301896 -3.563009 -1.078494 -1.807824 3.114041 -0.382604 -0.930404 -2.629559 -1.438624 -1.052814 0.668341 -0.484874 3.660796 2.528741 1.352506 2.330696 3.056941 -0.458614 -0.045094 -2.171369 -1.329874 0.457096 -0.542059 -1.084944 -0.932734 -3.823749 -0.821974 1.650896 0.440041 -0.998044 -1.463874 -2.106999 0.624406 -0.558624 1.826441 0.404306 3.518896 3.806941 1.402606 -0.673144 -0.175859 -1.177544 1.418596 -0.430259 -0.984474 -3.124334 -1.429589 0.105206 -3.781744 2.318241 -0.226994 2.950596 0.988841 -0.551814 3.477596 -3.672659 0.131006 -0.855224 3.800141 0.181506 0.958096 -1.200429 0.745406 1.081696 -0.132059 1.001306 -3.235794 2.477541 1.434306 0.434786 1.191441 -0.736104 -2.760274 -4.140549 -0.733584 -0.846874 -3.094119 -1.123994 -2.225014 -3.149999 -0.062234 2.407796 -1.158149 -1.072274 -3.039524 -0.400659 -1.241414 1.903496 1.400641 -0.174024 3.597496 -1.630609 0.521006 3.115196 -1.910689 0.640406 0.362426 2.543641 -0.179114 -2.735614 1.308541 -0.967644 1.277396 -1.093769 -1.170714 -1.120094 -2.038329 0.620406 -0.058894 -3.683249 -0.062744 -1.421644 3.818441 0.036916 -0.292334 -0.993219 -0.522244 3.780696 -2.148329 -1.325934 -2.178484 2.447041 1.396806 -0.113854 -3.777879 -1.387444 0.533296 -0.112359 -1.087874 0.573196 1.377841 1.272306 1.635396 1.254941 0.151606 3.713996 0.372641 -1.007944 -1.036354 0.403441 1.165806 2.515396 1.230641 -0.844674 2.678496 3.348241 0.265006 -2.227234 -2.340539 0.614206 -1.496794 2.706141 1.026906 2.803596 -1.531129 0.802406 1.941896 0.804641 1.121306 -1.501914 0.059541 0.809706 -0.311584 1.451341 -0.739234 -2.431744 0.343741 0.558806 -3.900584 -2.072989 0.334706 -3.202074 -1.751489 -1.001144 -3.138244 2.893641 0.571106 -2.934174 1.919241 1.291906 0.708896 -3.474459 -1.394114 1.127496 0.477941 0.873206 -3.684324 1.020041 0.816806 -3.717124 -1.097129 -1.220604 1.032496 -3.003429 -1.491814 0.041216 -0.507559 -0.220114 -3.970844 -0.871339 0.908506 -1.824914 -1.002469 0.877506 -2.880294 -4.003789 -0.252654 -0.874304 1.928341 -0.505854 -2.700694 -4.185099 -0.475784 -0.280294 2.133041 1.318306 -0.575044 -1.046039 0.694406 3.191796 -3.890309 -1.158434 1.104596 2.113641 1.121406 -3.070624 -2.010099 -1.292954 -0.216314 1.987941 -0.788144 -0.140574 -1.267489 0.847406 -2.042934 1.081541 -0.900484 -1.586604 1.738041 -0.756064 2.435396 -0.069359 0.820406 0.927096 -0.635559 0.800206 1.127196 2.931141 0.143506 2.623996 -4.225549 -0.669494 0.371026 -0.338759 0.966206 -1.677904 3.366541 -0.280934 -0.452624 -1.410509 -1.305514 1.425996 0.167541 1.102406 2.105496 -2.300829 0.579706 -1.995564 1.544641 -0.527864 -2.984594 -1.480049 -1.367304 2.012496 1.878641 -0.009994 -3.437454 -1.177279 -1.306154 2.668896 -0.553259 -1.221724 2.822196 1.621241 1.165206 -0.259554 0.955141 -0.677624 -3.163504 0.479541 1.086106 -3.276734 1.177041 -0.857644 -2.034864 -2.630079 -0.422604 3.087996 3.196841 -0.183384 1.661096 -0.283959 -1.216724 2.424996 -0.107759 -1.058054 -1.231574 2.249141 1.312206 -2.593074 0.830641 -1.040704 1.520796 0.576841 -1.023904 0.578496 1.423641 1.337406 -2.343334 -0.730489 0.730806 0.734796 0.769941 0.229206 3.836296 4.100741 1.182206 -0.238214 2.089241 0.092906 3.337096 -0.763159 0.136706 -3.966944 2.048641 -0.227544 -3.081924 -3.669929 0.168106 0.252466 0.140141 -1.084624 2.934796 0.851241 0.607406 3.712096 3.700541 0.473606 -1.803374 -2.468259 -0.464164 -3.254234 -0.203359 0.903606 1.386696 -0.643559 -0.829614 -3.468714 1.614941 1.173406 0.307636 1.392641 0.580206 3.626796 -2.505129 0.283706 -2.756974 0.253041 -1.109694 2.506596 3.828141 0.886106 -1.663004 1.044541 1.128806 -3.297774 0.181941 0.250406 3.905496 1.562941 1.190006 -0.810124 0.781841 -1.043654 1.933696 -3.185429 0.424706 0.220286 -3.531809 -1.195284 1.705096 -2.097159 -1.340254 -0.863994 -1.135099 -1.219464 -1.635044 -0.228059 0.551806 -3.881094 -1.908889 -0.349964 -3.592664 0.218941 -0.982404 -2.523044 0.260041 -0.803284 3.486996 -3.861169 -0.444074 1.489996 2.300241 -0.574664 2.219096 0.152141 -1.099804 0.946196 3.145741 -0.413984 0.443116 3.507441 -0.195284 0.108416 1.944941 1.339006 -1.191414 0.712741 1.140906 1.651796 -2.285379 -1.159364 -2.725134 1.321641 0.496906 -3.766264 -0.113859 0.838606 -3.632624 2.832541 -0.493464 1.442796 -1.841069 0.723306 1.646096 -0.459359 1.016406 2.225996 0.201841 -1.017674 0.189156 -2.841829 0.421906 -1.719214 0.231941 -0.095884 -3.976554 2.697141 1.501506 1.322996 -1.631849 -0.014764 3.592596 4.061241 0.948106 0.957796 1.418341 -0.368324 -3.372364 2.487241 1.436906 -1.049184 2.995641 1.236706 2.259996 2.362241 -0.658334 1.734596 -1.561139 -1.230494 2.969396 1.554241 1.240906 1.116496 2.133941 -0.699834 -1.353124 0.626741 0.939306 0.855396 -2.521189 0.573706 -0.265734 -3.175499 -1.467974 -1.143624 -2.036329 -0.890544 3.291296 0.962641 1.060106 -0.985534 -1.670869 0.685006 -0.876284 -1.524279 0.747106 -2.679794 1.228941 -0.958444 0.811796 2.037241 1.395306 1.342896 1.108541 1.029206 -0.500484 -2.722179 -1.445594 0.558096 1.703441 1.345006 -1.781734 -2.143929 -1.295104 2.540596 -1.302389 -1.200624 0.643196 1.449241 -0.890044 0.039516 -0.215759 0.355906 3.842396 -0.920159 -1.124644 3.251996 0.225241 -1.137294 1.749496 -0.786199 -1.095824 -0.176074 -1.303999 0.689606 0.573696 2.387341 -0.695494 1.207696 3.616041 -0.063804 -0.868164 1.275241 -0.915394 2.204296 1.104941 1.255006 -1.907854 -2.126609 0.680406 1.985496 -0.612959 -1.130234 -1.434614 0.288041 -0.826834 -3.206864 1.504641 1.081106 -3.301794 0.956941 1.083106 3.279996 3.425541 1.292006 1.749896 -1.562639 0.657506 -0.367044 -0.385059 0.723606 0.010976 0.643941 0.357306 -3.944714 1.143541 1.026006 0.066126 -3.923099 -0.495494 -1.432014 2.303941 1.442606 -1.648764 2.674241 -0.622824 0.391136 -2.575289 0.574706 0.181276 2.102141 -0.780244 0.270976 2.378241 1.455306 1.844496 -1.409929 -0.961854 3.454296 4.013241 0.332406 -0.723214 -1.573919 -1.215954 -0.711444 3.366141 1.546806 0.229086 3.014341 1.508506 -0.892014 -2.636479 -1.351154 2.158996 0.922241 0.047476 -3.878244 0.762741 -1.012504 2.373096 -3.926779 -0.052834 -0.307064 2.968041 0.646406 2.733196 3.782941 1.456006 -0.006234 0.293941 0.980106 3.322096 0.743841 0.927906 0.369276 0.353641 -1.106064 1.342896 0.550941 0.926506 -0.651724 1.762441 0.858206 -3.461724 0.785441 -0.965424 0.039136 -2.446739 -0.783074 3.110596 -1.761889 -1.299734 0.946196 -2.697899 -0.063044 -3.002244 0.640841 -0.980814 2.763096 -1.050859 -1.134324 0.211156 3.911441 0.552606 1.280196 3.353541 1.548406 -0.400284 -0.360759 0.853606 3.253696 -0.389559 -1.095464 -2.375984 -2.230669 0.634006 -1.033524 -0.163959 -1.039024 -0.132964 1.953141 1.367106 2.454696 3.068341 1.251806 -2.229364 0.992941 1.103506 1.196096 -0.354459 -1.058014 -0.495984 -2.665739 0.304606 2.489996 -0.427759 -1.081724 0.236916 3.759441 0.923206 1.707396 2.993841 1.552106 0.718696 2.296441 -0.743554 0.741996 -1.763279 -1.196994 -2.678954 3.417241 1.480306 -1.070794 1.431641 1.326906 2.206896 -2.096879 -1.401974 2.006296 -3.095769 -1.187684 2.249096 -4.128129 -1.086374 0.486896 0.675741 -0.955924 -0.695564 -1.630539 -1.295414 -1.868474 -0.768859 0.565106 3.506796 4.012841 0.625006 -1.187824 -1.592159 -0.658304 -3.554044 -2.151139 -1.366284 -1.505324 1.944341 0.534006 -3.505884 2.740341 1.467306 -0.486764 -1.790669 0.713506 -1.313474 -2.856189 0.495806 1.193596 -0.925189 0.930706 2.507296 -1.699529 0.733406 -2.239544 4.207241 0.885506 0.224376 -0.837619 0.695306 0.283396 0.104841 -1.031214 -2.123114 -2.548549 -1.424364 1.589096 -0.612259 0.793806 -0.936894 1.131341 -0.948084 0.398946 -2.658279 -1.417134 -1.649294 0.653841 1.171006 2.953596 -4.042969 -0.364854 0.835896 -3.526489 -1.494984 -0.025274 2.868441 1.487406 -1.434434 2.522341 0.143006 -3.086934 3.510241 1.089206 -2.010794 2.655341 1.425806 -2.070284 0.943541 1.250306 2.522996 2.051841 1.298306 0.066136 -4.223849 -0.936654 -0.173444 0.290641 -0.136404 3.874196 -1.223659 -0.271774 -3.852484 2.581841 0.234906 2.999296 2.808341 -0.374994 1.972596 -2.557319 -1.120344 2.757496 -3.287539 -1.493654 0.579396 -0.594659 -0.048564 3.911296 2.679541 -0.253214 2.470396 3.700541 1.267106 -1.359364 -0.959359 0.694006 -0.198754 4.180941 0.748706 -0.471614 2.535541 -0.659604 -0.082264 -3.089509 0.229006 1.982496 1.731841 -0.727054 -2.039894 0.752941 1.246206 -2.426534 -2.794649 0.507006 -1.181084 -3.928009 -0.950514 -1.345824 -1.636959 -1.274204 -1.269404 -1.940279 0.529306 -2.870564 1.882741 -0.848684 1.494496 -3.549919 -0.243094 -2.000504 -0.985279 0.894306 2.008496 -0.019559 0.850306 -0.771854 -4.049549 -0.204584 0.297226 4.087041 0.536406 0.624496 0.371741 0.972006 1.250196 -3.046759 0.034546 -2.443624 -0.719569 -1.137034 0.634596 -3.158539 -1.349784 -1.706094 -0.279059 0.747106 0.458496 -1.316909 0.801306 -1.568304 2.739641 -0.508704 -1.402154 4.125641 0.432706 -0.049424 -2.650109 0.030536 2.922696 0.728641 -0.572354 -3.501974 -3.251199 -0.368214 -2.488764 -1.667539 -1.229094 0.297986 -3.511899 0.271306 -0.265254 -1.217199 -0.839184 -3.421624 1.688541 -0.870954 0.546196 -2.974609 -0.856614 2.734296 -0.895229 -0.556384 -3.761854 2.268441 -0.709414 -0.535614 -1.094129 0.717706 -0.654994 -0.452859 -1.158414 1.013596 -1.450159 0.338706 3.469996 -4.085079 -0.786084 1.017296 -2.013369 -0.428744 3.485496 0.186241 0.812206 0.140706 3.297341 0.979906 2.281496 -1.587119 -0.584934 3.599196 3.576841 0.144706 1.499496 2.837841 -0.528204 -0.495564 1.889941 -0.783134 -0.895424 -1.428079 0.758706 2.798596 0.046441 -0.556934 3.784296 -3.684669 -0.679584 -1.936454 -0.686599 0.914906 2.917896 -0.347959 1.019106 -2.897304 -0.755329 0.847706 -1.379134 3.290941 -0.199454 -1.362574 -1.419229 0.659306 0.105926 -1.311779 0.798806 1.546596 1.666841 1.197906 3.029896 -2.321029 0.633806 1.482596 3.287141 1.490906 1.234996 2.984741 -0.097804 -2.290484 -2.836459 0.532306 0.656296 -0.861149 -1.306774 2.294096 -0.311359 -0.327694 3.905596 -3.734009 -0.074834 1.271696 1.872541 1.382106 1.877196 -1.407059 -1.176224 -0.210924 -2.367179 -1.400034 1.022796 1.364841 1.219006 -1.359284 1.681541 -0.782554 -1.677954 -0.059659 -1.061814 -1.689014 2.290341 0.381006 -3.348324 0.475541 0.734206 3.681596 -4.023649 -1.188844 -0.711754 -0.577459 1.001106 -2.162764 1.243741 1.071406 0.684396 2.914741 -0.510524 0.930496 0.902341 -0.944574 -1.144034 -0.839749 0.521606 -3.775084 4.048341 1.041606 -0.934924 -0.546859 1.000206 -2.571354 1.303541 0.921306 3.446996 2.932241 1.521106 0.098186 2.429641 0.627306 3.172496 2.306841 -0.595294 -1.818344 -0.992569 -1.123874 -0.610844 1.003941 1.263606 -2.859874 3.594241 1.493206 0.682696 -3.338079 0.342506 0.779696 3.029841 0.258606 -2.684064 2.654641 1.184106 -2.680514 -0.231759 1.062706 2.546896 -0.821239 -1.130474 -2.681534 -1.402569 0.509406 -3.546474 -1.452419 -1.244514 -2.433904 -0.661849 0.763206 -3.535814 -3.107039 -1.510414 -0.552684 1.605141 0.186106 -3.659124 -3.223199 0.382406 -0.773184 3.076441 0.948906 -2.630754 -2.743569 0.480606 1.693896 -2.588259 -1.436754 -0.474874 -3.638209 -1.458764 -0.615664 -2.727689 0.347506 -2.256804 0.030141 -1.025324 -0.732464 -1.283989 -0.180894 3.756196 -3.285119 0.273906 -1.328174 -0.357559 0.968406 1.837296 -2.064339 0.056406 -3.468924 -0.845689 0.842506 -3.252574 1.527441 -0.844614 -1.273824 -0.703919 -0.773084 3.688796 2.527141 -0.614204 -0.945414 -0.561259 -1.137704 -1.919154 1.234441 -0.629994 3.225896 3.253041 1.394206 -1.693584 0.767641 -1.038624 1.048796 0.639041 0.907006 -0.173444 -2.963859 -1.458584 1.104196 1.736241 -0.330294 3.299396 -0.916819 0.269506 3.752496 -2.924189 -0.517674 -2.831784 0.530241 1.155206 -1.994664 0.333941 1.167606 -2.848074 CELLS 996 3984 3 270 374 303 3 104 55 232 3 339 225 45 3 410 374 315 3 104 232 416 3 232 55 34 3 330 122 403 3 410 82 0 3 55 0 82 3 481 417 420 3 339 45 303 3 339 303 374 3 416 232 361 3 122 34 55 3 34 122 382 3 169 225 104 3 104 416 169 3 330 403 92 3 315 194 410 3 16 261 374 3 417 315 261 3 270 16 374 3 19 270 338 3 420 261 298 3 261 420 417 3 440 65 361 3 440 361 232 3 232 34 440 3 92 81 330 3 45 494 356 3 356 303 45 3 338 303 356 3 268 494 375 3 361 393 179 3 225 169 156 3 375 494 156 3 156 45 225 3 156 494 45 3 340 169 416 3 82 403 122 3 82 410 194 3 194 315 417 3 481 249 417 3 194 13 403 3 403 82 194 3 34 8 440 3 393 361 65 3 23 330 81 3 443 356 494 3 494 268 443 3 306 166 393 3 375 156 137 3 137 156 169 3 169 340 137 3 303 338 270 3 65 440 8 3 23 382 330 3 122 330 382 3 23 81 282 3 81 92 424 3 415 126 39 3 39 92 403 3 375 137 362 3 338 356 31 3 356 443 31 3 268 80 102 3 102 443 268 3 66 340 369 3 340 416 179 3 361 179 416 3 166 179 393 3 19 186 188 3 476 382 23 3 23 282 476 3 434 350 476 3 476 282 434 3 126 168 424 3 42 282 81 3 12 343 496 3 92 39 126 3 343 168 415 3 343 12 322 3 179 369 340 3 66 137 340 3 66 362 137 3 478 66 449 3 338 186 19 3 316 182 31 3 438 188 186 3 338 31 186 3 182 22 186 3 438 186 22 3 316 31 443 3 443 102 316 3 186 31 182 3 159 182 316 3 406 102 171 3 375 362 160 3 160 268 375 3 160 80 268 3 40 32 474 3 32 40 259 3 306 407 114 3 96 94 386 3 438 124 196 3 427 467 351 3 295 259 40 3 106 32 259 3 259 295 131 3 324 32 106 3 288 431 379 3 56 350 434 3 434 10 56 3 56 4 33 3 127 33 4 3 437 464 350 3 437 33 295 3 350 56 437 3 437 56 33 3 208 306 393 3 208 407 306 3 393 65 208 3 65 464 208 3 382 476 8 3 382 8 34 3 8 464 65 3 350 464 8 3 8 476 350 3 126 415 168 3 427 351 424 3 126 424 92 3 427 424 168 3 362 66 478 3 449 94 478 3 369 449 66 3 397 319 331 3 102 406 316 3 159 316 271 3 271 316 406 3 442 319 406 3 319 397 271 3 159 271 397 3 30 404 284 3 53 26 193 3 43 119 26 3 224 40 407 3 407 208 224 3 224 208 464 3 295 40 224 3 224 437 295 3 464 437 224 3 166 306 164 3 306 114 164 3 121 41 52 3 41 121 96 3 449 386 94 3 386 449 120 3 420 134 481 3 420 298 134 3 322 17 427 3 142 183 467 3 391 351 244 3 42 424 351 3 81 424 42 3 114 132 164 3 292 164 132 3 263 324 106 3 106 288 263 3 32 324 385 3 145 385 324 3 184 246 145 3 385 145 246 3 259 131 106 3 288 106 131 3 131 295 127 3 33 127 295 3 127 431 131 3 431 288 131 3 184 90 479 3 352 264 90 3 249 496 415 3 376 496 481 3 39 13 415 3 249 481 496 3 39 403 13 3 249 415 13 3 194 417 13 3 13 417 249 3 376 481 134 3 230 43 26 3 185 191 251 3 191 284 404 3 119 191 185 3 119 165 150 3 207 185 251 3 191 119 43 3 472 160 362 3 362 478 472 3 94 96 398 3 398 96 121 3 426 292 390 3 390 132 87 3 386 120 292 3 164 292 120 3 369 120 449 3 166 164 120 3 120 369 166 3 179 166 369 3 16 255 298 3 315 374 261 3 16 270 255 3 19 255 270 3 193 150 383 3 276 331 319 3 159 397 414 3 414 397 138 3 331 138 397 3 275 199 223 3 124 275 72 3 172 2 412 3 196 72 218 3 72 196 124 3 196 189 438 3 188 438 189 3 218 209 196 3 64 189 209 3 17 309 360 3 286 360 309 3 147 42 391 3 88 432 391 3 18 399 176 3 18 176 258 3 116 432 88 3 429 400 48 3 379 289 74 3 57 238 183 3 78 483 183 3 142 467 17 3 244 467 183 3 78 183 238 3 183 142 57 3 441 266 139 3 118 155 11 3 11 155 266 3 238 57 139 3 256 139 266 3 215 451 155 3 213 395 176 3 238 395 78 3 213 78 395 3 483 78 213 3 212 229 35 3 317 484 212 3 405 317 497 3 132 482 87 3 474 482 114 3 284 110 30 3 93 284 43 3 471 454 469 3 229 111 371 3 1 485 469 3 264 163 1 3 184 479 246 3 246 87 482 3 87 246 479 3 479 90 217 3 485 1 163 3 111 469 454 3 485 117 471 3 60 436 144 3 29 436 60 3 324 263 349 3 48 400 349 3 349 263 48 3 413 288 379 3 413 263 288 3 48 263 413 3 379 74 413 3 163 7 456 3 487 134 298 3 425 439 487 3 134 487 439 3 185 165 119 3 51 150 165 3 383 150 51 3 319 442 276 3 80 171 102 3 94 398 260 3 260 478 94 3 260 472 478 3 132 390 292 3 426 386 292 3 426 41 96 3 96 386 426 3 405 390 396 3 497 41 405 3 212 497 317 3 390 405 426 3 41 426 405 3 405 396 317 3 87 396 390 3 305 223 280 3 453 174 486 3 257 423 486 3 311 199 275 3 138 331 245 3 36 64 231 3 318 5 209 3 209 62 64 3 36 274 64 3 318 209 218 3 318 218 305 3 218 72 305 3 17 360 142 3 367 142 360 3 367 57 142 3 63 233 377 3 42 147 282 3 434 282 243 3 243 282 147 3 243 147 432 3 243 10 434 3 243 432 10 3 391 244 483 3 183 483 244 3 244 351 467 3 351 391 42 3 391 483 88 3 147 391 432 3 418 289 399 3 399 18 418 3 418 18 310 3 277 431 127 3 177 116 88 3 177 213 176 3 177 88 213 3 483 213 88 3 162 429 180 3 256 451 328 3 328 395 238 3 238 139 328 3 328 139 256 3 395 258 176 3 395 328 258 3 258 328 451 3 310 18 258 3 258 451 310 3 132 114 482 3 474 114 407 3 474 385 482 3 246 482 385 3 385 474 32 3 407 40 474 3 497 212 52 3 497 52 41 3 24 475 154 3 154 54 465 3 285 313 158 3 157 158 151 3 206 371 111 3 111 484 469 3 152 469 484 3 228 454 241 3 471 469 485 3 152 484 317 3 217 388 152 3 396 217 317 3 67 145 349 3 324 349 145 3 67 352 184 3 145 67 184 3 67 349 400 3 48 413 357 3 357 413 74 3 357 429 48 3 28 429 357 3 163 264 7 3 352 7 264 3 117 485 428 3 388 90 264 3 400 429 162 3 28 180 429 3 162 450 173 3 162 180 450 3 415 496 343 3 231 425 36 3 203 370 70 3 376 134 439 3 439 425 370 3 490 113 62 3 16 298 261 3 36 487 274 3 189 274 188 3 188 255 19 3 50 29 460 3 383 51 460 3 461 276 442 3 300 461 442 3 442 171 300 3 271 406 319 3 406 171 442 3 276 461 329 3 359 260 398 3 342 346 135 3 472 260 98 3 98 359 346 3 260 359 98 3 1 152 388 3 388 217 90 3 469 152 1 3 152 317 217 3 217 396 479 3 87 479 396 3 223 305 72 3 242 373 254 3 143 5 318 3 423 2 73 3 73 486 423 3 453 486 73 3 73 341 453 3 73 2 69 3 69 341 73 3 257 486 445 3 193 383 445 3 445 174 193 3 445 486 174 3 159 414 83 3 83 182 159 3 83 22 182 3 325 245 331 3 245 200 138 3 234 138 200 3 487 36 425 3 189 196 209 3 298 274 487 3 274 298 255 3 189 64 274 3 255 188 274 3 57 337 139 3 367 337 57 3 441 139 337 3 140 216 203 3 216 140 107 3 75 175 63 3 58 286 433 3 337 367 58 3 448 210 457 3 216 457 210 3 79 95 146 3 372 459 205 3 457 216 95 3 149 74 289 3 289 418 149 3 202 277 116 3 176 399 202 3 202 177 176 3 116 177 202 3 354 289 379 3 399 289 354 3 379 431 354 3 431 277 354 3 354 202 399 3 277 202 354 3 10 4 56 3 4 277 127 3 4 10 116 3 432 116 10 3 116 277 4 3 368 155 118 3 144 436 465 3 447 154 364 3 313 347 240 3 151 158 290 3 388 264 1 3 173 450 7 3 456 428 163 3 428 499 117 3 499 428 15 3 285 220 49 3 456 15 428 3 499 462 192 3 220 192 462 3 471 241 454 3 157 241 192 3 97 401 430 3 29 60 430 3 133 236 181 3 133 181 279 3 173 67 400 3 90 184 352 3 400 162 173 3 456 7 450 3 173 352 67 3 7 352 173 3 370 221 439 3 168 343 322 3 210 448 221 3 273 221 448 3 427 168 322 3 99 425 231 3 231 113 99 3 62 231 64 3 70 99 125 3 495 125 113 3 370 203 221 3 99 70 425 3 210 221 203 3 113 125 99 3 5 490 62 3 231 62 113 3 125 140 70 3 205 107 125 3 490 495 113 3 128 436 50 3 29 50 436 3 364 128 207 3 128 50 165 3 103 329 461 3 331 276 325 3 329 325 276 3 325 329 46 3 105 174 453 3 130 461 300 3 130 103 461 3 411 214 408 3 411 6 214 3 30 214 6 3 214 30 110 3 239 59 52 3 239 135 59 3 135 359 59 3 121 52 59 3 359 398 59 3 121 59 398 3 52 35 239 3 153 239 35 3 35 52 212 3 408 153 345 3 345 35 229 3 35 345 153 3 472 98 477 3 160 472 477 3 80 160 477 3 477 171 80 3 300 171 226 3 226 98 346 3 171 477 226 3 226 477 98 3 492 129 199 3 199 311 492 3 492 234 200 3 311 234 492 3 389 129 76 3 280 44 85 3 85 254 233 3 199 129 44 3 199 44 223 3 44 280 223 3 44 129 389 3 254 85 389 3 389 85 44 3 294 296 381 3 69 2 272 3 69 272 314 3 272 2 172 3 172 296 272 3 272 296 409 3 409 358 272 3 257 488 423 3 423 412 2 3 412 423 488 3 412 488 422 3 412 381 172 3 422 381 412 3 50 460 51 3 488 257 401 3 401 460 29 3 460 401 257 3 257 445 460 3 383 460 445 3 109 414 234 3 109 83 414 3 138 234 414 3 109 234 311 3 46 245 325 3 480 46 329 3 46 341 69 3 480 341 46 3 360 58 367 3 210 203 216 3 360 286 58 3 459 95 107 3 457 433 286 3 95 433 457 3 203 70 140 3 216 107 95 3 373 377 233 3 493 418 310 3 493 149 418 3 20 86 28 3 256 266 155 3 326 297 441 3 297 248 266 3 11 266 248 3 433 95 79 3 459 146 95 3 368 118 136 3 190 368 167 3 256 155 451 3 155 368 215 3 310 451 291 3 215 368 190 3 222 237 444 3 444 237 269 3 366 21 293 3 293 269 237 3 364 465 128 3 436 128 465 3 51 165 50 3 347 24 447 3 447 473 347 3 290 240 304 3 304 247 290 3 151 228 157 3 241 157 228 3 192 117 499 3 241 471 117 3 158 157 220 3 285 158 220 3 117 192 241 3 163 428 485 3 484 111 229 3 411 345 371 3 408 345 411 3 484 229 212 3 435 248 9 3 422 488 97 3 279 181 97 3 97 488 401 3 422 97 181 3 29 430 401 3 97 430 279 3 3 430 60 3 279 430 3 3 376 273 496 3 17 322 309 3 467 427 17 3 221 273 376 3 309 12 448 3 322 12 309 3 70 370 425 3 12 496 273 3 376 439 221 3 273 448 12 3 286 448 457 3 309 448 286 3 193 174 53 3 150 193 26 3 105 53 174 3 197 480 103 3 329 103 480 3 387 130 300 3 387 226 346 3 300 226 387 3 93 468 110 3 110 123 214 3 153 408 123 3 408 214 123 3 229 371 345 3 6 411 161 3 93 110 284 3 278 254 389 3 334 278 76 3 265 76 129 3 200 358 265 3 265 358 409 3 409 76 265 3 265 492 200 3 129 492 265 3 143 280 175 3 62 209 5 3 318 305 143 3 280 143 305 3 236 133 77 3 77 37 236 3 124 109 275 3 22 124 438 3 22 83 124 3 223 72 275 3 311 275 109 3 83 109 124 3 245 46 314 3 314 358 200 3 200 245 314 3 358 314 272 3 314 46 69 3 146 459 307 3 107 205 459 3 112 205 495 3 140 125 107 3 495 490 75 3 495 75 112 3 195 421 61 3 178 377 61 3 180 28 86 3 148 86 20 3 498 38 86 3 38 450 180 3 456 450 15 3 38 15 450 3 54 154 475 3 144 465 54 3 466 24 170 3 154 447 24 3 365 475 327 3 313 170 24 3 198 466 170 3 347 313 24 3 240 158 313 3 473 304 240 3 180 86 38 3 281 498 148 3 384 15 498 3 38 498 15 3 49 170 285 3 79 326 433 3 337 58 326 3 441 337 326 3 266 441 297 3 58 433 326 3 100 326 79 3 297 326 100 3 9 100 253 3 100 9 297 3 248 297 9 3 252 293 237 3 136 167 368 3 167 222 190 3 237 222 167 3 167 252 237 3 291 215 190 3 222 267 190 3 291 493 310 3 215 291 451 3 267 312 493 3 281 269 201 3 498 281 384 3 86 148 498 3 269 281 444 3 148 444 281 3 302 336 363 3 363 491 366 3 363 252 302 3 293 252 363 3 465 364 154 3 473 447 187 3 240 347 473 3 206 454 228 3 161 91 247 3 91 151 290 3 206 228 91 3 14 404 30 3 161 206 91 3 30 6 14 3 371 206 161 3 14 6 247 3 247 304 14 3 151 91 228 3 111 454 206 3 371 161 411 3 419 332 287 3 458 136 118 3 333 11 248 3 248 435 333 3 118 11 333 3 333 458 118 3 68 332 250 3 250 89 68 3 250 353 89 3 204 89 353 3 68 89 463 3 54 219 211 3 211 144 54 3 211 60 144 3 211 3 60 3 299 133 279 3 279 3 299 3 480 197 141 3 141 341 480 3 453 341 141 3 141 105 453 3 130 387 227 3 335 123 470 3 123 110 468 3 470 468 25 3 233 254 373 3 254 278 242 3 172 381 296 3 334 76 409 3 389 76 278 3 409 296 334 3 296 294 334 3 280 85 175 3 233 175 85 3 233 63 175 3 175 75 143 3 5 75 490 3 5 143 75 3 37 115 27 3 37 77 115 3 77 394 115 3 71 421 320 3 205 112 372 3 63 112 75 3 125 495 205 3 178 372 112 3 178 112 377 3 63 377 112 3 327 491 365 3 466 327 475 3 54 475 365 3 365 219 54 3 198 170 49 3 475 24 466 3 49 462 201 3 21 201 269 3 201 384 281 3 201 21 49 3 198 49 21 3 462 49 220 3 313 285 170 3 384 201 462 3 192 220 157 3 15 384 499 3 462 499 384 3 262 222 444 3 20 312 262 3 444 148 262 3 148 20 262 3 291 190 267 3 222 262 267 3 267 262 312 3 493 291 267 3 149 493 312 3 149 312 392 3 392 357 74 3 74 149 392 3 20 28 392 3 357 392 28 3 392 312 20 3 269 293 21 3 293 363 366 3 327 366 491 3 198 366 466 3 327 466 366 3 21 366 198 3 91 290 247 3 304 473 84 3 372 307 459 3 47 307 178 3 68 287 332 3 287 435 9 3 287 68 435 3 463 435 68 3 136 458 348 3 252 167 348 3 136 348 167 3 302 252 348 3 108 204 455 3 89 204 308 3 458 333 301 3 301 333 463 3 435 463 333 3 3 211 344 3 344 299 3 3 344 219 455 3 344 211 219 3 130 227 283 3 197 103 283 3 130 283 103 3 346 342 387 3 359 135 346 3 227 387 342 3 468 93 323 3 150 26 119 3 230 26 355 3 27 195 242 3 377 373 61 3 71 61 421 3 195 61 373 3 101 294 381 3 181 101 422 3 101 181 236 3 381 422 101 3 452 334 294 3 452 242 278 3 278 334 452 3 299 344 380 3 380 344 455 3 380 204 353 3 455 204 380 3 133 299 402 3 402 77 133 3 402 394 77 3 299 380 402 3 353 394 402 3 402 380 353 3 320 332 71 3 115 320 421 3 320 115 394 3 320 250 332 3 353 250 394 3 320 394 250 3 378 365 491 3 378 108 455 3 455 219 378 3 219 365 378 3 187 251 84 3 364 187 447 3 404 251 191 3 165 207 128 3 191 43 284 3 14 84 404 3 251 187 207 3 165 185 207 3 372 178 307 3 419 307 47 3 47 61 71 3 178 61 47 3 71 332 47 3 332 419 47 3 307 419 146 3 419 287 253 3 9 253 287 3 253 100 146 3 79 146 100 3 253 146 419 3 108 308 204 3 301 463 308 3 89 308 463 3 355 53 446 3 53 355 26 3 227 342 25 3 242 452 27 3 373 242 195 3 421 27 115 3 421 195 27 3 294 101 235 3 235 452 294 3 236 37 235 3 235 101 236 3 235 37 452 3 27 452 37 3 187 84 473 3 251 404 84 3 240 290 158 3 364 207 187 3 84 14 304 3 161 247 6 3 301 308 321 3 302 348 321 3 321 348 458 3 458 301 321 3 308 108 321 3 336 378 491 3 108 378 336 3 336 321 108 3 491 363 336 3 302 321 336 3 141 197 105 3 105 446 53 3 446 105 197 3 283 446 197 3 489 283 227 3 227 25 489 3 489 446 283 3 355 446 489 3 43 230 93 3 25 323 489 3 323 25 468 3 230 355 323 3 230 323 93 3 355 489 323 3 335 470 135 3 342 135 470 3 25 342 470 3 153 123 335 3 335 135 239 3 239 153 335 3 468 470 123 3 122 55 82 3 225 0 104 3 55 104 0 3 0 225 339 3 0 339 410 3 374 410 339 CELL_TYPES 996 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 ================================================ FILE: test/vtk/square.mesh ================================================ vertices 1 0 0 0 2 1 0 0 3 0 1 0 4 1 1 0 faces 1 1 2 3 2 2 3 4 ================================================ FILE: test/vtk/tetrahedron.mesh ================================================ vertices 1 0 0 0.612372 2 -0.288675 -0.5 -0.204124 3 -0.288675 0.5 -0.204124 4 0.57735 0 -0.204124 volumes 1 1 2 3 4 ================================================ FILE: test/vtk/vtk_exporter_addfield_fname_not_str_err.morpho ================================================ // Testing whether adding a field to the VTKExporter, but giving a // fieldname that's not a string, throws the right response import vtk import meshtools var a = 3.0 var m = AreaMesh(fn (u,v) [u,v,0], -1..1:2, -1..1:2) var f = Field(m, fn(x,y,z) x) var vtkE = VTKExporter(m) vtkE.addfield(f, fieldname=a) // expect: Error 'FnameNotStr' : Expected a string, but received `3`. ================================================ FILE: test/vtk/vtk_exporter_fname_not_str_err.morpho ================================================ // Testing whether initializing VTKExporter with a field, but giving a // fieldname that's not a string, produces the right error import vtk import meshtools var a = 3.0 var m = AreaMesh(fn (u,v) [u,v,0], -1..1:2, -1..1:2) var f = Field(m, fn(x,y,z) x) var vtkE = VTKExporter(f, fieldname=a) // expect: Error 'FnameNotStr' : Expected a string, but received `3`. ================================================ FILE: test/vtk/vtk_exporter_init_err.morpho ================================================ // Testing whether initializing VTKExporter with an object that is // neither a Mesh nor a Field produces the right error import vtk import meshtools var a = 3.0 var vtkE = VTKExporter(a) // expect: Error 'InitErr' : Expected a Mesh or a Field object, but received `3`. ================================================ FILE: test/vtk/vtk_exporter_invalid_fname_err.morpho ================================================ // Testing whether initializing VTKExporter with a field, but giving a // fieldname that has an embedded whitespace in it, produces the right // error import vtk import meshtools var m = AreaMesh(fn (u,v) [u,v,0], -1..1:2, -1..1:2) var f = Field(m, fn(x,y,z) x) var vtkE = VTKExporter(f, fieldname="x y") // expect: Error 'InvalidFname' : fieldname (`x y`) cannot have embedded whitespaces. ================================================ FILE: test/wc ================================================ arithmetic/power.morpho array/array.morpho array/array_assign_beyond_bounds.morpho array/array_garbage_collect.morpho array/array_nonnum_indices.morpho array/array_read_beyond_bounds.morpho array/array_three_dim.morpho array/array_two_dim.morpho array/array_wrong_dim.morpho assignment/associativity.morpho assignment/global.morpho assignment/grouping.morpho assignment/infix_operator.morpho assignment/local.morpho assignment/prefix_operator.morpho assignment/shorthand.morpho assignment/syntax.morpho assignment/to_self.morpho assignment/undefined.morpho block/empty.morpho block/scope.morpho block/scope_error.morpho bool/equality.morpho bool/not.morpho break/break_in_if_outside_loop.morpho break/break_outside_loop.morpho break/continue_in_if_outside_loop.morpho break/continue_outside_loop.morpho break/in_for.morpho break/in_forin.morpho break/in_while.morpho call/bool.morpho call/call.morpho call/nil.morpho call/num.morpho call/object.morpho call/string.morpho class/empty.morpho class/inherit_self.morpho class/inherited_method.morpho class/local_inherit_other.morpho class/local_inherit_self.morpho class/local_reference_self.morpho class/reference_self.morpho closure/assign_to_closure.morpho closure/assign_to_shadowed_later.morpho closure/close_over_function_parameter.morpho closure/close_over_later_variable.morpho closure/closed_closure_in_function.morpho closure/nested_closure.morpho closure/open_closure_in_function.morpho closure/reference_closure_multiple_times.morpho closure/reuse_closure_slot.morpho closure/shadow_closure_with_local.morpho closure/unused_closure.morpho closure/unused_later_closure.morpho comments/line_at_eof.morpho comments/multline.morpho comments/nested.morpho comments/only_line_comment.morpho comments/only_line_comment_and_line.morpho comments/unicode.morpho comments/unterminated.morpho constructor/arguments.morpho constructor/call_init_early_return.morpho constructor/call_init_explicitly.morpho constructor/default.morpho constructor/default_arguments.morpho constructor/early_return.morpho constructor/extra_arguments.morpho constructor/init_not_method.morpho constructor/missing_arguments.morpho constructor/return_in_nested_function.morpho constructor/return_value.morpho dictionary/key_not_found.morpho dictionary/literal_from_vars.morpho dictionary/literal_in_function.morpho dictionary/literal_in_loop.morpho dictionary/methods.morpho dictionary/syntax.morpho error/stacktrace.morpho file/file.morpho file/file_lines.morpho file/file_not_found.morpho file/file_write.morpho file/filename_missing.morpho file/filename_not_string.morpho for/class_in_body.morpho for/closure_in_body.morpho for/fn_in_body.morpho for/return_closure.morpho for/return_inside.morpho for/scope.morpho for/statement_condition.morpho for/statement_increment.morpho for/statement_initializer.morpho for/syntax.morpho for/var_in_body.morpho for_in/forin.morpho for_in/forin_custom.morpho for_in/forin_in_function.morpho for_in/forin_string.morpho function/body_must_be_block.morpho function/empty_body.morpho function/extra_arguments.morpho function/index_in_arguments.morpho function/local_recursion.morpho function/missing_arguments.morpho function/missing_comma_in_parameters.morpho function/parameters.morpho function/print.morpho function/recursion.morpho function/too_many_arguments.morpho function/too_many_parameters.morpho if/class_in_else.morpho if/class_in_then.morpho if/dangling_else.morpho if/else.morpho if/fn_in_else.morpho if/fn_in_then.morpho if/if.morpho if/truth.morpho if/var_in_else.morpho if/var_in_then.morpho import/file_not_found.morpho import/for_clause.morpho import/for_clause_restrict.morpho import/import_file.morpho import/import_module.morpho import/module_not_found.morpho inheritance/constructor.morpho inheritance/inherit_from_function.morpho inheritance/inherit_from_nil.morpho inheritance/inherit_from_number.morpho inheritance/inherit_methods.morpho inheritance/parenthesized_superclass.morpho inheritance/set_fields_from_base_class.morpho list/index_out_of_bounds.morpho list/syntax.morpho logical/and.morpho logical/and_truth.morpho logical/or.morpho logical/or_truth.morpho math/math.morpho matrix/arithmetic.morpho matrix/incompatible_add.morpho matrix/incompatible_mul.morpho matrix/incompatible_sub.morpho matrix/initializer.morpho matrix/linearsolve.morpho matrix/linearsolve3x3.morpho matrix/nonnum_indices.morpho matrix/trace.morpho matrix/transpose.morpho mesh/meshload.morpho method/arity.morpho method/empty_block.morpho method/extra_arguments.morpho method/missing_arguments.morpho method/not_found.morpho method/print_bound_method.morpho method/return_in_method.morpho method/too_many_arguments.morpho method/too_many_parameters.morpho newline/block.morpho newline/classes.morpho newline/for.morpho newline/functions.morpho newline/variables.morpho nil/literal.morpho number/decimal_point_at_eof.morpho number/leading_dot.morpho number/literals.morpho number/nan_equality.morpho number/trailing_dot.morpho operator/more_comparison.morpho print/missing_argument.morpho programs/delta_blue.morpho programs/fannkuch.morpho programs/fibonacci.morpho programs/histogram.morpho programs/integrate.morpho range/constructor.morpho range/count_down.morpho range/syntax.morpho return/after_else.morpho return/after_if.morpho return/after_while.morpho return/at_top_level.morpho return/in_for_in.morpho return/in_function.morpho return/in_method.morpho return/return_nil_if_no_value.morpho self/closure.morpho self/nested_class.morpho self/nested_closure.morpho self/self_at_top_level.morpho self/self_in_method.morpho self/self_in_top_level_function.morpho sparse/arithmetic.morpho sparse/incompatible_add.morpho sparse/incompatible_mul.morpho sparse/initializer.morpho sparse/invld_initializer.morpho sparse/linearsolve.morpho sparse/nonnum.morpho string/error_after_multiline.morpho string/interpolation.morpho string/literals.morpho string/multiline.morpho string/string_veneer.morpho string/unterminated.morpho syntax/comments.morpho syntax/empty_file.morpho syntax/illegal_chars_in_symbol.morpho syntax/symbols.morpho variable/duplicate_local.morpho variable/duplicate_parameter.morpho variable/early_bound.morpho variable/in_middle_of_block.morpho variable/in_nested_block.morpho variable/local_from_method.morpho variable/multiple.morpho variable/redeclare_global.morpho variable/redefine_global.morpho variable/scope_reuse_in_different_blocks.morpho variable/shadow_and_local.morpho variable/shadow_global.morpho variable/shadow_local.morpho variable/undefined_global.morpho variable/undefined_local.morpho variable/uninitialized.morpho variable/use_false_as_var.morpho variable/use_global_in_initializer.morpho variable/use_nil_as_var.morpho variable/use_self_as_var.morpho while/class_in_body.morpho while/closure_in_body.morpho while/fun_in_body.morpho while/return_closure.morpho while/return_inside.morpho while/syntax.morpho while/var_in_body.morpho ================================================ FILE: test/while/class_in_body.morpho ================================================ // Can't have a class as the body of a while loop while (true) class Foo {} // expect error 'ExpExpr' ================================================ FILE: test/while/closure_in_body.morpho ================================================ var f1,f2,f3 var i = 1 while (i < 4) { var j = i fn f() { print j } if (j == 1) f1 = f else if (j == 2) f2 = f else f3 = f i = i + 1 } f1() // expect: 1 f2() // expect: 2 f3() // expect: 3 ================================================ FILE: test/while/fn_in_body.morpho ================================================ // Can't have a function as the body of a while loop while (true) fn foo() {} // expect error 'ExpExpr' ================================================ FILE: test/while/if_in_body.morpho ================================================ // The if divides the loop into multiple control flow blocks var i = 1 while (i < 4) { var j=i if (j == 1) print true i = i + 1 } // expect: true ================================================ FILE: test/while/return_closure.morpho ================================================ // Checks that returning a closure inside a while loop works fn f() { while (true) { var i = "i" fn g() { print i } return g } } var h = f() h() // expect: i ================================================ FILE: test/while/return_inside.morpho ================================================ // Checks that a return inside a while loop works. fn f() { while (true) { var i = "i" return i } } print f() // expect: i ================================================ FILE: test/while/syntax.morpho ================================================ // While loop syntax // Single-expression body. var c = 0 while (c < 3) print c+=1 // expect: 1 // expect: 2 // expect: 3 // Block body. var a = 0 while (a < 3) { print a a+=1 } // expect: 0 // expect: 1 // expect: 2 // Statement bodies. while (false) if (true) 1; else 2 while (false) while (true) 1 while (false) for (;;) 1 ================================================ FILE: test/while/var_in_body.morpho ================================================ // Can't have a var as the body of a while loop while (true) var foo; // expect error 'ExpExpr'