{"id":15312514,"url":"https://github.com/giulioz/tensorlibrary","last_synced_at":"2025-03-27T14:31:32.691Z","repository":{"id":80756934,"uuid":"218001379","full_name":"giulioz/TensorLibrary","owner":"giulioz","description":"Assignment for Advanced algorithms and programming methods [CM0470] course.","archived":false,"fork":false,"pushed_at":"2020-02-05T19:11:34.000Z","size":347,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"assignment2","last_synced_at":"2025-02-01T18:43:20.906Z","etag":null,"topics":["cpp","data-structures","metaprogramming","template","tensor"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/giulioz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-28T08:39:44.000Z","updated_at":"2019-11-16T13:52:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c4f8b58-0986-467c-aace-5c7cf8ed45e0","html_url":"https://github.com/giulioz/TensorLibrary","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giulioz%2FTensorLibrary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giulioz%2FTensorLibrary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giulioz%2FTensorLibrary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giulioz%2FTensorLibrary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/giulioz","download_url":"https://codeload.github.com/giulioz/TensorLibrary/tar.gz/refs/heads/assignment2","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245862972,"owners_count":20684760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","data-structures","metaprogramming","template","tensor"],"created_at":"2024-10-01T08:37:17.014Z","updated_at":"2025-03-27T14:31:32.656Z","avatar_url":"https://github.com/giulioz.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tensor-library\n\nSecond Assignment for Advanced algorithms and programming methods [CM0470] course.\n\nWe made some changed to an existing Tensor Library to allow operations using the Einstein Notation.\nThe special Einstein Notation for tensors allows summation, subtraction and multiplication using variables, by rendering implicit the summation. According to this notation, repeated variables in a tensorial expression are implicitly summed over, so the expression\n\n$a_{ijk} b_j$\n\nrepresents a rank 2 tensor c parameterized by i and k such that\n\n$c_{ik} = \\sum_j a_{ijk} b_j$\n\nThe notation allows for simple contractions\n\n$\\textrm{Tr}(a) = a_{ii}$\n\nas well as additions (subtractions) and multiplications.\n\nIn particular, we respect these essential rules of Einsten notation:\n 1. Repeated variables are implicitly summed over;\n 2. Each variable can appear at most twice in any term;\n 3. Each term must contain identical non-repeated variables.\n\nOf course, when we define a same variable on two tensors, the dimension on that variable must be the same.\n\n## Usage\n\nThe library adds a new method `ein()` to the tensor class. The method does not take parameters, but only the variables as template parameters. This example performs the expression described before:\n\n$c_{ik} = a_{ijk} b_j$\n\n```cpp\ntensor::tensor\u003cint\u003e a({3, 3, 3});\ntensor::tensor\u003cint\u003e b({3});\n\nauto exp = a.ein\u003c'i', 'j', 'k'\u003e() * b.ein\u003c'j'\u003e();\ntensor::tensor\u003cint\u003e c = exp.evaluate\u003c'i', 'k'\u003e();\n```\n\nThe `ein()` method does not calculate anything but returns an opaque `tensor_expression` object, which can be turned into a resulting tensor using the `evaluate()` method. You have to pass the resulting variables to the method as template parameters. This way it can be used also to transpose a tensor:\n\n```cpp\ntensor::tensor\u003cint\u003e a({3, 3});\ntensor::tensor\u003cint\u003e transpose =\n    a.ein\u003c'i', 'j'\u003e().evaluate\u003c'j', 'i'\u003e();\n```\n\nIf the operations returns a scalar (for example the trace) it will be wrapped on a single-element tensor:\n\n```cpp\ntensor::tensor\u003cint\u003e a({3, 3});\nauto exp = a.ein\u003c'i', 'i'\u003e();\ntensor::tensor\u003cint\u003e trace = exp.evaluate\u003c\u003e();\n// std::cout \u003c\u003c trace({0});\n```\n\nOf course, since the variables are templates, it's possibile to choose them only at compile-time. **No run-time expressions are possible.** We did this to have better performance, since run-time dependant expressions are very rare. This allows also to check expression validity at compile-time:\n\n```cpp\ntensor::tensor\u003cint\u003e a({3, 3});\ntensor::tensor\u003cint\u003e transpose =\n    a.ein\u003c'i', 'j'\u003e().evaluate\u003c'k', 'p'\u003e();\n\n// error: static_assert failed due to requirement match_vars\u003ctensor::expressions::vars\u003c'k', 'p'\u003e tensor::expressions::vars\u003c'i', 'j'\u003e, void\u003e::value' \"The free variables on both the sides of an equation must match\"\n```\n\nYou can perform operations with tensor_expressions, such as sum, multiplication and negation:\n\n```cpp\ntensor::tensor\u003cint\u003e a({2, 2});\ntensor::tensor\u003cint\u003e b({2, 2});\n\nauto exp = a.ein\u003c'i', 'j'\u003e() + b.ein\u003c'i', 'j'\u003e();\ntensor::tensor\u003cint\u003e c = exp.evaluate\u003c'i', 'j'\u003e();\n```\n\nWe suggest to use inference with `auto` for the expression type, since it can be extremely complicated due to compile-time checks and variables.\n\n## How it works\n\nFirst of all, when we assign variables to the indices of a given tensor though the `ein()` method, a `tensor_constant` (which is a tensor_expression) object is created.\n\nWe can perform operations such as addition ($A + B$), negation ($-A$) and multiplications ($A * B$) between `tensor_expressions` in order to get more complex expressions, and for each one of these operations, a `tensor_addition` / `tensor_negation` / `tensor_multiplication` object (which is also a `tensor_expression`) is created. This is achieved using C++ **operator overloading**. We can also achieve subtraction by combining an addition and a negation ($A - B = A + (-B)$).\n\nThis builds an expression tree, and every term is copied inside the expression object, to avoid losing references.\n\nWhen a `tensor_expression` is created, at compile-time the compiler checks if such expression is valid or not, by performing a static verification on the expression variables. For e.g., a `tensor_constant` is valid if there are at most two variables with the same identifier. We also use template types to statically obtain the free variables and the repeated (dummy/bound) variables involved in the expression.\nAfterwards, at run-time, we check if all the variables with some identifier are referred to variables with the same dimension; this is because we don't know the information about the dimensions of a tensor at run-time.\n\n### Evaluation\n\nWhen we want to get the result of an expression (which is always a tensor of rank known at compile-time), the `evaluate()` method is called, which calculates, for each combination of indexes over the free variables of the expression, the summation of the expression over the repeated variables, and assigns the resulting scalar to the correct position of the resulting tensor:\n\n```cpp\ntensor\u003cT, rank\u003cresult_rank\u003e\u003e result(dims);\n\nsize_t free_vars_values_count =\n    count_vars_values(result_vars::id, result_vars::size);\n\nfor (size_t i = 0; i \u003c free_vars_values_count; i++) {\n  auto free_vars_values =\n      get_nth_vars_values(i,\n        result_vars::id,\n        result_vars::size\n      );\n\n  result(\n    to_indexes(\n      result_vars::id,\n      result_vars::size,\n      free_vars_values\n    )\n  ) = evaluate_summation(free_vars_values);\n}\n\nreturn result;\n```\n\nIn particular, `evaluate_summation()` is the method which performs the summation of the expression over its own repeated variables:\n\n```cpp\nT result = evaluate_direct(bind_vars_values(\n    vars_values,\n    get_nth_vars_values(0, repeated_vars::id, repeated_vars::size)));\n\nsize_t vars_values1_count =\n    count_vars_values(repeated_vars::id, repeated_vars::size);\n\nfor (size_t i = 1; i \u003c vars_values1_count; i++) {\n  result += evaluate_direct(bind_vars_values(\n      vars_values,\n      get_nth_vars_values(i,\n        repeated_vars::id,\n        repeated_vars::size)\n    ));\n}\n\nreturn result;\n```\n\nThe effective evaluation of an expression, given some values of variables, is performed by the `evaluate_direct()` method, whose implementation depends on the specific type of the expression:\n\n- for a **tensor_constant**, `evaluate_direct()` accesses the tensor at the point specified by replacing the variables with the actual values;\n- for a **tensor_addition**, `evaluate_direct()` performs the addition between the summations (i.e. `evaluate_summation()`) of the two inner expressions;\n- for a **tensor_negation**, `evaluate_direct()` negates the value of `evaluate_direct()` of the inner expression;\n- for a **tensor_multiplication**, `evaluate_direct()` performs the multiplication between the values of `evaluate_direct()` of the two inner expressions.\n\n### Variables\n\nAs we said, variables are passed as template parameters, and this allows static checking and evaluation. This is done by exploiting template specialization.\n\nThe `ein()` method takes a variadic sequence of chars as template parameters:\n\n```cpp\ntemplate \u003cchar... Is\u003e\ninline tensor_constant\u003cT, vars\u003cIs...\u003e\u003e ein() {\n  return tensor_constant\u003cT, vars\u003cIs...\u003e\u003e(*this);\n}\n```\n\nThis way we are passing our variables to the `vars` utility type, which represents a set of variables. The `vars` type is defined as following:\n\n```cpp\ntemplate \u003cchar...\u003e\nstruct vars;\n\ntemplate \u003cchar... Is\u003e\nstruct vars {\n  constexpr static size_t size = sizeof...(Is);\n  constexpr static char id[sizeof...(Is)] = {Is...};\n};\n\ntemplate \u003cchar... Is\u003e\nconstexpr char vars\u003cIs...\u003e::id[sizeof...(Is)];\n\ntemplate \u003c\u003e\nstruct vars\u003c\u003e {\n  constexpr static size_t size = 0;\n  constexpr static char id[1] = {'\\0'};\n};\n\nconstexpr char vars\u003c\u003e::id[1];\n```\n\nWe also have some utility types that allows operations on variables, such as `concat_vars`, `single_vars` and `match_vars`. They are used to find free and repeated variables, and to validate the expressions. Every expression type defines their free and repeated variables using a member type. For example:\n\n```cpp\ntemplate \u003ctypename T, typename Derived\u003e\nclass tensor_expression {\n public:\n\n  using free_vars =\n    typename single_vars\u003c\n      typename term_multi_vars\u003cDerived\u003e::value\u003e::value;\n\n  using repeated_vars =\n      typename double_vars\u003c\n        typename term_multi_vars\u003cDerived\u003e::value\u003e::value;\n\n  constexpr static size_t result_rank = free_vars::size;\n\n  //...\n};\n```\n\nAnother template, named `match_vars`, uses *SFINAE* recursively to check if two `vars` types have the same variables inside:\n\n```cpp\ntemplate \u003ctypename, typename, typename = void\u003e\nstruct match_vars;\n\ntemplate \u003c\u003e\nstruct match_vars\u003cvars\u003c\u003e, vars\u003c\u003e\u003e {\n  constexpr static bool value = true;\n};\n\ntemplate \u003cchar... As, char... Bs\u003e\nstruct match_vars\u003c\n    vars\u003cAs...\u003e, vars\u003cBs...\u003e,\n    typename std::enable_if\u003c\n      sizeof...(As) != sizeof...(Bs)\u003e::type\u003e {\n  constexpr static bool value = false;\n};\n\ntemplate \u003cchar A, char... As, char... Bs\u003e\nstruct match_vars\u003c\n    vars\u003cA, As...\u003e, vars\u003cBs...\u003e,\n    typename std::enable_if\u003c\n      1 + sizeof...(As) == sizeof...(Bs)\u003e::type\u003e {\n  constexpr static bool value =\n      match_vars\u003c\n          typename remove_var\u003cA, vars\u003cAs...\u003e\u003e::value,\n          typename remove_var\u003cA, vars\u003cBs...\u003e\u003e::value\n      \u003e::value;\n};\n```\n\nExpression validation in compile-time is done using the `validate_expression` type, which takes a `vars` type checking the validity recursively on all child expression. For example:\n\n```cpp\ntemplate \u003ctypename T, typename A, typename B\u003e\nstruct validate_expression\u003ctensor_multiplication\u003cT, A, B\u003e\u003e {\n  constexpr static bool value =\n      validate_expression\u003cA\u003e::value \u0026\u0026\n      validate_expression\u003cB\u003e::value \u0026\u0026\n      at_most_2_equals_vars\u003ctypename term_multi_vars\u003c\n          tensor_multiplication\u003cT, A, B\u003e\u003e::value\u003e::value;\n};\n\n// on tensor_multiplication class\ntensor_multiplication() {\n  static_assert(\n    validate_expression\u003cDerived\u003e::value,\n    \"Invalid expression\"\n  );\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiulioz%2Ftensorlibrary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgiulioz%2Ftensorlibrary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiulioz%2Ftensorlibrary/lists"}