DESIGN PROJECT REPORT DESIGN PROJECT 2A 2020/2021

INTERACTIVE SOLVER FOR POLYNOMIAL INEQUALITIES USING

Mart de Roos (s2176416) Neil Kichler (s2106019) Rifqi Adlan Apriyadi (s2079755) Max Resing (s2069229)

Supervised By: Dr.-Ing. Moritz Hahn Department of FMT University of Twente, Enschede

1 Abstract The report is part of the Design Project course at the University of Twente. The project was requested by the University group Formal Methods and Tools (FMT). The motivation behind the project is the sparse availability of interactive visualization tools in the domain of inter- val arithmetic. A big issue in mathematics is uncertainty, a quantitative estimation of errors. Interval arithmetic is one of the mathematical tools to eliminate uncertainty and to draw mathematically sound conclusions. The project focuses on the visual support of solving polynomial inequal- ities using interval arithmetic. In interval arithmetic, the variables of polynomial inequalities are representative of intervals with well-defined boundaries. The parameter space is then divided into regions, which can be split into finer sub-regions. The inequalities can be evaluated for the boundaries of the sub-regions to determine whether a given inequalities holds true, false, or undecided for specific boundaries. The report elab- orates on the design process during the development of the application, including the specification of the requirements, the design choices, the mathematical background and the testing procedure. A system overview is given and every aspect of the application is explained and justified in detail. In the future, this application could be used by researchers and practitioners interested in applying interval arithmetic to their specific problems.

2 Contents

1 Introduction 6

2 Domain Analysis 7 2.1 Potential Users ...... 7 2.2 Existing software ...... 7

3 Requirements 8 3.1 User requirements ...... 8 3.2 System requirements ...... 9

4 Planning 10 4.1 Feature Planning ...... 10 4.2 Schedule & Milestones ...... 11

5 Research 13 5.1 Mathematical Background ...... 13 5.1.1 Mathematical Operators ...... 13 5.1.2 Monotonic and Non-Monotonic Functions ...... 14 5.1.3 Inequality Operators ...... 15 5.2 Interval Arithmetic on Modern-day Computers ...... 15 5.2.1 Machine-made Issues ...... 15 5.2.2 IEEE 754 - Standard for Floating-Point Arithmetic . . . . 16 5.2.3 Rational ...... 17 5.2.4 IEEE 1788 - Standard for Interval Arithmetic ...... 17 5.2.5 MPFR - Solution to Floating-Point Errors . . . 18 5.3 Ternary Operations ...... 19 5.4 Parser ...... 19 5.4.1 Stages of a parser ...... 20 5.4.2 Tokenizer ...... 20 5.4.3 Grammar ...... 20 5.4.4 Abstract syntax tree ...... 21 5.4.5 Walking an abstract syntax tree ...... 21 5.4.6 Parser types ...... 22

6 Global and Architectural design 23

7 Preliminary Design Choices 24 7.1 ...... 24 7.2 Libraries ...... 24 7.3 Architectural Design Choices ...... 28

3 8 System Overview 29 8.1 Menu ...... 29 8.2 2D-View ...... 30 8.3 Console ...... 31 8.4 History ...... 32 8.5 Region Table View ...... 33 8.6 Region Tree View ...... 34 8.7 3D-View ...... 35 8.8 Notes ...... 36 8.9 Help Box ...... 36

9 Detailed Design 38 9.1 Data Layout ...... 38 9.2 History ...... 39 9.3 Saving and Loading Files ...... 39 9.4 Graphical User ...... 39 9.5 3D-Viewport ...... 41 9.6 Interval Arithmetic ...... 43 9.6.1 Arithmetic Operations ...... 44 9.6.2 Boolean Operations ...... 45 9.6.3 Numerical Operations ...... 46 9.7 Splitting intervals ...... 47 9.7.1 Memory Pool Allocation ...... 47 9.7.2 Manual Refinement ...... 49 9.7.3 Automatic Refinement ...... 49 9.8 Parser ...... 51 9.9 Expressions ...... 53 9.9.1 Unary Expressions ...... 53 9.9.2 Expression Sequences ...... 54 9.10 Evaluables ...... 54 9.10.1 InEquations ...... 55 9.10.2 Ternaries ...... 55

10 Testing 57 10.1 Interval Tests ...... 57 10.2 Expression Tests ...... 58 10.3 Property Tests ...... 58 10.4 Ternary Tests ...... 58 10.5 Parser Tests ...... 59 10.6 Task Scheduler Tests ...... 59 10.7 Gui Tests ...... 59 10.8 Usability ...... 60 10.8.1 Heuristics ...... 60 10.8.2 Evaluation ...... 61

4 11 Limitations 63 11.1 Parser ...... 63 11.2 Parsable Ternary Logic Operators ...... 63 11.3 Numerals ...... 63 11.4 with negative base ...... 64 11.5 GUI ...... 64

12 Evaluation and Conclusion 66 12.1 Requirements evaluation ...... 66 12.1.1 User requirements ...... 66 12.1.2 System requirements ...... 67 12.2 Reflection on Planning ...... 67 12.3 Reflection on Group Work ...... 68 12.4 Future Work ...... 69 12.5 Conclusion ...... 70

Appendices 73

A Images of Entire Application 73

5 1 Introduction

The nature of mathematics is to provide precise answers and conclusions to a given problem. Whereas in practice, precision is a variable term and should rather be seen as accuracy. A temperature sensor can indicate that the temper- ature is 21 degree Celsius. But this measurement is actually an approximation and depending on the accuracy of the sensor, the actual temperature lies within an interval of 20.5 to 21.5 degree Celsius. This is essentially where interval arithmetic excels. Interval arithmetic is a mathematical tool to provide precise answers in a domain of uncertainty. In gen- eral, interval arithmetic provides the same arithmetic, trigonometric, numeric and Boolean operations which other fields of mathematics use. Nonetheless, interval arithmetic has some special rules and characteristics which might occur unconventional when comparing it to regular arithmetical operations. The product at hand was built on top of the theory of interval arithmetic. Since interval arithmetic supports the regular common operations like addition, subtractions, multiplication and non-negative exponentiation, one can also build polynomials in interval arithmetic. These polynomials can be added to an in- equality. An inequality is essentially a polynomial compared to a Boolean op- eration. The polynomial can have arbitrarily many variables. In an inequality, one can try to assign intervals to each variable. Next, one can try to shrink the intervals of each variable to draw conclusions under which of numbers a given inequality holds true or false. The process of reducing the size of the intervals is called refinement. The application in question is a tool which visually supports the user to re- fine a given inequality and to draw conclusions about the inequality statement. The software helps to understand the characteristics of interval arithmetic. Fur- thermore, it supports a user’s thinking process when working with intervals and inequalities. More use cases, as well as existing software is covered in the domain analysis in Chapter 2. Chapter 3 summarizes the user and system requirements before presenting a planning and milestones in Chapter 4. Afterwards, Chapter 5 cov- ers previously made research regarding mathematical and technical difficulties. It also includes applied theory which can be found in the products implemen- tation. Lastly, it refers to existing standards of the Institute of Electrical and Electronics Engineers (IEEE). The global architecture and a broad design de- scription is included in Chapter 6 before discussing detailed design decisions in Chapter 7. Chapter 8 guides the reader through the different components of the program. After making the reader familiar with the application, Chapter 7 is complemented with a technical detailed design description. Chapters 10 and 11 discuss technical limitations of the product as well as obstacles which the team encountered during the 10 weeks implementation of the project. Afterwards, the report continues with the team’s testing approaches. The design report is concluded with possible extensions and future work and a final conclusion.

6 2 Domain Analysis

Although, most of people with a mathematical education are aware of intervals, very few people actually studied the details of interval arithmetic. Since inter- val arithmetic is so exotic there exist very few tools to support researchers in this domain. Since interval arithmetic deals with real numbers, it bears some additional problems when developing programs which deal with intervals. An elaboration can be found in the research section 5.

2.1 Potential Users The system is designed for researchers and practitioners interested in inter- actively applying interval arithmetic to their mathematical problems. These mathematical problems can include polynomial or non-polynomial inequalities and constraint based systems that use Boolean expressions. Moreover, the pro- gram can be interesting to researchers developing new refinement algorithms in that it can give visual insights into these problems. Understanding the math- ematical problem domain inherently gives insight to the potential use cases. It is thus recommended to refer to Chapter 5.1. The client of this project is Moritz Hahn from the FMT (formal methods and tools) faculty of the Univer- sity of Twente who is simultaneously also the supervisor of this project. It can be assumed that all potential users are familiar with the subject material and have a good understanding of interval arithmetic. Focusing on explaining in- terval arithmetic inside the application is thus not needed and their knowledge regarding the use of complex programs will be fairly similar.

2.2 Existing software Given that the problem domain is rather academic in nature, not many produc- tion quality tools exist that can solve interval arithmetic problems interactively. A notable example is RSolver [10]. It focuses on a specific algorithm developed by Stefan Ratschan and visualizes the results in a two-dimensional scene. In- teracting with the program is, however, limited to entering new problems and seeing the solver automatically refine the problem. Manual refinement is not possible - only predefined settings can be toggled. Another program in this space is iSAT3 [1]. It is a command-line constraint checker that has its own do- main specific language to encode arithmetic problems which will then be solved. Also here the interactivity of the user and the algorithm in use is almost non- existent. The focus on interactively solving such interval arithmetic problems is therefore truly a new problem that this program tackles.

7 3 Requirements

Before starting the project, requirements were gathered to steer the project in the correct direction. The requirements are separated using the MoSCoW principle. The MoSCoW method seperates features into four categories: Must, Should, Could and Won’t. Must requirements are minimal requirements for the project to be considered sufficient, Should requirements are secondary require- ments that are quite important for usability. Could requirements are things that could have been added but did not have priority and Won’t requirements are ideas suggested by the supervisor which have been actively discarded.

3.1 User requirements The user must be able to: 1. Enter formulas (with 2 variables) 2. See the inequality (2D) 3. Evaluate the inequality

4. Interactively split regions to improve results 5. To save or store the current state of the process, including the formula and the region splitting The user should be able to:

1. Make use of monotonic functions (such as logarithms) 2. Make use of ternary logic 3. Select regions for splitting

4. Enter formulas with an arbitrary amount of variables 5. Specify a tolerance and axis for automatic refinement The user could be able to: 1. Make use of non-monotonic functions (such as trigonometric functions)

2. Undo or redo actions 3. See regions in the form of trees 4. See the inequality in 3D

5. Highlight multiple regions simultaneously for splitting 6. Choose when to use a more accurate floating point format

8 3.2 System requirements The system must be able to: 1. Parse simple polynomial inequalities (i.e. 2 ∗ x2 + 3 ∗ x + 8 − y)

2. Evaluate simple polynomial inequalities (with 2 variables) 3. Visualise the inequality in 2D 4. Automatically decide whether a formula holds in a specific region 5. Read or write the current state of the process

The system should be able to: 1. Evaluate inequalities with more than 2 variables 2. Evaluate monotonic functions

3. Parse boolean expressions using ternary logic 4. Automatically split a region given a tolerance The system could be able to: 1. Evaluate non-monotonic functions

2. Visualise the inequality in 3D 3. Visualise regions in a tree-like manner 4. Speed up evaluation using GPGPU 5. Evaluate with an arbitrary amount of floating point precision (using MPFR)

The system won’t be able to: 1. Read or write problem statements in ’iSat’ format 2. Rewrite the formula to more numerically stable forms to obtain tighter bounds

9 4 Planning 4.1 Feature Planning The features of the project were divided into core features and optional addons. Core features are those which have to be implemented to have a working core application. The core application requires a user interface with a few required components. Firstly, a user needs to be able to enter a new polynomial inequality into a text interface. The input has to be parsed and evaluated. The evaluation module requires us to implement an interval arithmetic which provides all necessary mathematical operations. The evaluation does not only calculate the results of the polynomial with the help of the interval arithmetic library, but also decides whether the entered condition holds true, false or undecided for a region. The results needs to be displayed in a two dimensional plot. Furthermore, the user requires features to interactively split regions. The last core feature is to save and restore the current state of the application. Next to the core features, plenty of optional features were discussed. The core application focuses on polynomial inequalities only. This can be extended by other mathematical features such as the most common trigonometric operations sine, cosine, tangent and their inverse functions for arc sine, arc cosine and arc tangent. Additionally, the team decided to allow the user to link multiple conditions for the inequations with Boolean operations such as OR and AND. This includes the extension of the evaluation module by ternary logic. During the planning phase, it was expected that the evaluation of the entered inequalities is the performance bottleneck. Thus, it was important for us to implement and integrate concurrency features in the evaluation process. Moreover, the team committed to consider special cases in the evaluator, to interrupt the evaluation prematurely or to optimize it. Those cases include statements which always hold true or false. For example 0 > 1 is always false or x2 >= 0 holds always true. To increase the usability, a decision was made to add some strategies for automatic refinement of regions. The research section provides additional infor- mation about refinement strategies. Lastly, the group discussed to implement two features if enough time is left. These features include a three dimensional plot. This plot should support similar features as the two dimensional plot. However, navigation and rendering will be more complex than with only two dimensions. Next to the 3D plot, it is desirable to allow the user to define numbers with an arbitrary precision. By default, only double precision floating point numbers are used (64bit). However, open-source libraries like MPFR allow a considerably easy integration for arbitrary precision floating point numbers. MPFR is briefly introduced in section 5.2.5.

10 4.2 Schedule & Milestones The success of a project depends not only on the set of features but also on a realistic schedule when to work on the features. Thus, it was decided to split the available time of the project into smaller sections. The time unit to specify the planning is five working days, i.e. one week. The first week was mostly to get accustomed to the project, to the tools and the theory. Furthermore, it is used to setup a local development environment for everyone. The remaining days of the week as well as the second week was used to implement a core application and core features. This includes the most important graphical components such as the console and the 2D plot view, the internal data structures, the self-written interval arithmetic library, the parser and the logic to split regions into multiple sub-regions. At the end of week two, the intention was to add first controls to allow the user to manually perform region splitting. Also, all controls and graphical components should exist as dummy windows/menu entries. In week three, the only milestone sought to be accomplished was a basic implementation for automatic refinement. Week four was reserved to add non- polynomial functions such sine and cosine as well as an extension to the parser and evaluator to support Boolean operations and ternary logic. After the basic application is running, several new ideas were assumed to probably pop-up during the development. It turned out to include features like the history (undo/redo) and the tree view. Besides, week five focuses on more advanced strategies for automatic refinement. The team also wanted to start looking into arbitrary precision floating point numbers with the MPFR library. Since these features are quite large, no goals were set for week 6. Week seven and eight was supposed to add parallel processing with the help of GPGPU methods like CUDA or OpenCL. Lastly, three dimensional plotting should be supported by the end of week 8. According to the planning, the remaining two weeks were reserved for bug fixing, documentation and code cleanup. It was also planned to work on the design report during the second half of the project, e.g. from week five onwards. This should provide sufficient room to finish the report on time. The planning is reflected on in section 12.2.

11 Week Milestones Planning, requirements analysis, 1 setup of ++ environment and build process. Basic structure and features of the interval arithmetic library, 2 parsing math expressions, general layout and graphical components 2D plots, user interactions for manual splitting, menu layout, 3 evaluation to decide whether inequality holds for given regions, saving & restoring application state 4 Basic implementation of automatic refinement 5 Space for feature ideas 6 Bug fixing of basic functionality MPFR, 7 Implementation of non-polynomial functions, support for boolean operations using ternary logic 8 Parallel computing using GPGPU methods, 3D plots 9 Bug fixes, code cleanup, code documentation 10 Report

Table 1: Overview of milestones. Features described in milestones were intended to be finished by the end of the given week.

12 5 Research 5.1 Mathematical Background Interval arithmetic is a mathematical technique for limiting rounding and cal- culation errors in mathematical computations. Numerical methods based on in- terval arithmetic can provide accurate, repeatable results. Interval arithmetic, rather than representing a value as a single number, portrays each value as a set of possible numbers. Instead of measuring a room’s temperature as exactly 25°C, one might be sure that it is somewhere between 24.7°C and 25.3°C using interval arithmetic. This section briefly introduces how interval arithmetic be- haves in accordance to the IEEE standard. A more detailed overview follows right after. Instead of dealing with an unknown real number x, one deals with intervals. An interval is the set of all numbers between an interval’s lower and upper boundary. An interval is represented as [a, b]. In interval arithmetic, a variable x is any possible number in the closed interval between a and b. When a function f is applied to x, it generates an interval [c, ] that contains all of the possible values for f(x) for all x ∈ [a, b]. However, any normal real number x is still expressible in interval arithmetic, being the point interval [x, x]. For instance, the number 5 in interval arithmetic is equal to [5, 5]. Interval arithmetic may be used for a number of items. The most popular use is in software, where it is used to keep track of rounding errors in measurements and uncertainties in the exact values of physical and technical parameters. The latter is often caused by component measurement errors and tolerances, as well as computational precision limitations. Interval arithmetic can also be used to solve equations (such as differential equations) and solve optimization problems. This procedure is usually restricted to real intervals, so quantities of the form [a, b] = {x ∈ R|a ≤ x ≤ b} where a = −∞ and b = ∞ are allowed. The interval would be an unbounded interval if one of a, b was infinite; if both were infinite, the interval would be the extended real number line. Intervals and real numbers can be freely mixed since a real number r can be represented as the interval [r, r].

5.1.1 Mathematical Operators Interval arithmetic involves working with mathematical operators as well. If a binary operator ≺ is used on an operation of two interval operands [x1, x2] and [y1, y2], from the set Z = {x1 ≺ y1, x1 ≺ y2, x2 ≺ y1, x2 ≺ y2}, the result of the operation is [min(Z), max(Z)]. For practical applications this can be simplified further for the four basic operations in arithmetic:

1. Addition: [x1, x2] + [y1, y2] = [x1 + y1, x2 + y2]

2. Subtraction: [x1, x2] − [y1, y2] = [x1 − y2, x2 − y1]

13 3. Multiplication: [x1, x2] · [y1, y2] = [min{x1y1, x1y2, x2y1, x2y2}, max{x1y1, x1y2, x2y1, x2y2}]

[x1,x2] 1 4. Division: = [x1, x2] · , where [y1,y2] [y1,y2]

1 1 1 (a) = [ , ] if 0 ∈/ [y1, y2] [y1,y2] y2 y1 (b) 1 = [−∞, 1 ] [y1,0] y1 (c) 1 = [ 1 , ∞] [0,y2] y2 1 1 1 (d) = [−∞, ] ∪ [ , ∞] ⊆ [−∞, ∞] if 0 ∈ (y1, y2) [y1,y2] y1 y2 In addition to the basic operations, a few of the elementary functions for interval arithmetic behave as follows:

1. Exponential Function: a[x1,x2] = [ax1 , ax2 ] for a > 1

2. Logarithm: loga[x1, x2] = [logax1, logax2] for positive intervals [x1, x2] and a > 1

n n n 3. Odd Powers: [x1, x2] = [x1 , x2 ] for odd n ∈ N

5.1.2 Monotonic and Non-Monotonic Functions A function defined on a subset of the real numbers is called monotonic if and only if the function is entirely increasing or entirely decreasing in the speci- fied interval. So for all combinations of values x, y given x < y in the domain of function f, we have that for an entirely increasing function f(x) ≤ f(y), and we have f(x) ≥ f(y) for an entirely decreasing function. For example the natural logarithm is a monotonic function, and trigonometric functions are all non-monotonic. It is important to understand the difference when using interval arithmetic. Consider any monotonically increasing function i, any monotonically decreasing function d and any non-monotonic function n. For all intervals in the real number space [x, y]: i([x, y]) = [i(x), i(y)] and [x, y], d([x, y]) = [d(y), d(x)] is always true but n([x, y]) = [n(x), n(y)] or [n(y), n(x)] is not always true. For example cos[−2π, 2π] 6= [cos(−2π), cos(2π)] because [cos(−2π), cos(2π)] = [1, 1] whereas it should have been equal to [−1, 1].

Figure 1: Monotonically decreasing Figure 2: Non-monotonic function function f(x) = −x f(x) = sin(2x) + cos(0.5x)

14 5.1.3 Inequality Operators In contrast to conventional arithmetic, inequalities in interval arithmetic has an additional layer of complexity: all numbers in the set defined by the interval must be considered. [1, 3] < [2, 4] is not necessarily true as the left-hand side contains values that are larger than some in the right-hand side. It is not nec- essarily false either for the opposing reason. Therefore, an inequality operation of intervals does not result to a binary set, but rather a ternary one. The result of an inequality is undecided if it cannot be determined to true or false. For example, assume ≺ represents each of the operators below in the in- equality [x1, x2] ≺ [y1, y2]. Each operator works as follows

Operator True False Undecided 6= x2 < y1 or y2 < x1 x1 = y1 and x2 = y2 x1 ≤ y2 or x2 ≤ y1 > x1 > y2 x2 < y1 x1 ≤ y2 ≥ x1 ≥ y2 x2 < y1 x1 < y2 < x2 < y1 x1 > y2 x2 ≤ y1 ≤ x2 ≤ y1 x1 > y2 x2 > y1

Figure 3: List of Inequality Operators

5.2 Interval Arithmetic on Modern-day Computers Section 5.1 covers an introduction to interval arithmetic in general. This section focuses on problems and issues which are a consequence of the architecture of modern day computers. Mathematics aims to provide precise answers in the most common decimal number system. This contradicts with the binary nature of processor architectures. This problem is tackled with floating point num- bers, which are briefly explained. Furthermore, for this project, it is connected with the field of interval arithmetic and present alternatives to floating point numbers.

5.2.1 Machine-made Issues First of all, a regular interval covers a specific set of numbers between two boundaries. This means, each interval, which is not a point interval contains an infinite number of values. It is not an option to represent intervals as sets. Instead, one could just store the boundaries. As discussed previously, also all interval operations are based on the interval boundaries. Thus, it is sufficient to just store the boundaries of an interval. The requirements for this project are intervals in the real number space IR. Limiting the application on this number space means, that all possible results for polynomial, trigonometric and logarithmic functions will always lie within the set [−∞, +∞]. This requires a number format, which is capable to display the extended real number system, which includes all real numbers and plus the two elements

15 −∞ and +∞. By default, C/C++ floating point numbers and double precision floating point numbers have support for −∞ and +∞. The datatype float has a size of 32 , whereas a double has even a precision of 64 bit. Both data types stick to the IEEE 754 Standard for Floating-Point Arithmetic (IEEE 754).

5.2.2 IEEE 754 - Standard for Floating-Point Arithmetic Computers store values in binary formats. For integer values, each value can be precisely represented in binary. Decimal numbers are represented as fractions. which are as close as possible to the real value. This phaenomenon is commonly called floating point errors, which describes the issue of minor fraction differences between a floating point value and the real representative number. Depending on the field of application, floating point errors have a smaller or larger significance. As more calculations are performed, as larger the impact of floating point errors get. Internally, binary floating point values are organized with a sign bit, an 11- bit exponent and a fraction part which has 52 as be seen in table 2. The value of a floating point number is calculated as follows:

sign e−1023 (−1) ∗ (1.b51b50...b0)2 ∗ 2 Additionally, floating point numbers can represent special values. If the exponent has the value 00016 = 000000000002 the double value represents −0. If the exponent equals 7ff16 = 111111111112 and the fraction bits equals 0, the value can represent +∞ and −∞. If the fraction does not represent 0, then the value equals NaN. The reason for discussing floating points in this report is, because intervals define a set of all numbers between the lower and upper boundary. Especially splitting intervals into multiple smaller subregions results in fractions. This issue has to be kept in mind when working on the project. Evaluating inequalities on subregions results in floating point errors. The error increases in significance as more complex the inequality becomes. Another weakness in floating points is when it comes to compare values. In general, the expression 0.1 ∗ 7.0 = 0.7 holds intuitively true. However, floating point arithmetic bears the risk that this expression does not hold true. This in particular becomes an issue when considering the feature of binary expressions with ternary logic. Then, the boundaries of subregions needs to be properly compared. Instead, floating point errors can evaluate regions to undecided or wrongly evaluate a region. The weaknesses of floating point numbers are a long addressed research field. One option to avoid floating point errors is, to use a decimal representation. A decimal representation is also defined in the IEEE 754 standard. In a double, one would have the representation displayed in table 3.

16 Sign Exponent Fraction 1 bit 11 bits 52 bits

Table 2: Internal representation of 64-bit binary floating point numbers (double) according to the IEEE 754 standard.

Sign Combination Exponent Continuation Significand Continuation 1 bit 5 bits 8 bits 50 bits

Table 3: Internal representation of 64-bit decimal floating point numbers (dec- imals) according to the IEEE 754 standard.

By default, a binary representation is used. IEEE 754 also defines the option of so called densely packed decimals (DPD). In DPD, the significand continua- tion is grouped into 5 declets, a sequence of 10 bits. These 10 bits can represent 210 numbers, e.g. 1024 distinct values. However, just the values 0-999 are rep- resented. That means just 25 values are squandered. On the one hand, such a representation can accurately display decimal numbers. On the other hand, the possible range of values decreases rapidly. In the binary representation method, each bit is an additional digit to represent, whereas in DPD mode, 10 bits just represent 3 digits. Furthermore, the binary representation of floating points is so established in today’s hardware, that state-of-the-art CPUs have a dedicated floating point arithmetic unit integrated.

5.2.3 Rational Data Type Another alternative to binary representation of floating point values is the ra- tional data type. The rational data type is based on the theory of representing each number as a fraction:

x = m/n

such that m ∈ Z, n ∈ Z. The integers can be either in fixed or in arbitrary precision. Since 2011, the C++ standard library supports rational data types. Before that, the well established GNU Multiple Precision Arithmetic (GMP) library was widely used. All in all, the rational data type is more popular among lisp-like languages and not so common in C and C++.

5.2.4 IEEE 1788 - Standard for Interval Arithmetic After discussing floating point arithmetic in detail, it is time to talk about the more important part in the research - interval arithmetic. Despite the fact that interval arithmetic has not yet gained large popularity, the IEEE has still defined a standard for it: The standard IEEE 1788. Later a simplified version was published under the identifier IEEE 1788.1-2017.

17 The standard describes the representation of intervals including some special values like the NaN pendant Not an Interval (NaI). It also defines mathemati- cally the results of interval arithmetic operations. A mathematical definition for the most common ones can be found in section 5.1. A particular important part in the standard is the treatment of corner cases. The standard defines which results to expect when the interval is invalid, empty or if invalid operations are performed on valid intervals. The operations which was required in the project are described in the detailed design, section 9.6 Apparently, the IEEE standard has not yet gained large popularity, which means it has very few libraries which implement the standard. The project is built with C++. This language provides limited support for the IEEE 1788 standard. The library libieeep1788 implements a preliminary standard defini- tion. The library is published under the Apache-2.0 license. The repository claims, the library is a work in progress and not bug free. Furthermore, it has not received any commits in the last six years. Thus, it is not the ideal choice as a library. Internally, the group discussed to use this library or focus on implementing their own library. After weighing out the pros and , it was concluded that it is a pleasant challenge to implement an interval arithmetic library from scratch. Firstly, the team would be in full control of the code and the license schema. Secondly, the group can implement the library based on the data types of their choice. That allows us to either go for single precision, double precision or alternatively even arbitrary precision floating point numbers. Last but not least, it is a great experience to learn a lot about the project’s problem domain and about the standard IEEE 1788.

5.2.5 MPFR - Solution to Floating-Point Rounding Errors Another option to tackle the problem is to use arbitrary precision floating point numbers. Such features are implemented in the GNU library MPFR. It stands for multiple-precision floating-point computations with correct rounding. This library would not only allow an arbitrary precision but also forces a developer to actively think about rounding during calculations. Consequently, floating point rounding errors still exist, but are less impactful. The precision can be chosen large enough to minimize the rounding issue. On the downside, the MPFR library is implemented in C. This means, it can be used within C++ environment of the project but it misses lots of convenient features like . Therefore, the integration of MPFR is an additional burden for the group, while only providing advantages in specific use-cases, where floating-point errors have a significant impact. The MPFR library is based on the rational data type, which is discussed in section 5.2.3. After discussing the pros and cons with the supervisor, it was decided to first use regular 64-bit floating point numbers - doubles in C. Afterwards, the group will try to integrate MPFR in their project.

18 5.3 Ternary Operations A Ternary Logic (also known as Three-valued logic) is any of many many-valued logic systems in which there are three truth values representing true, false, and an unknown third value. This is in contrast to the more well-known bivalent logics, which only allow for true and false answers. As with boolean algebra, the three most basic operators of Ternary logic are not (¬), and (∧), and or (∨), of which their ternary truth tables are as follows:

B B A ∨ B A ¬A A ∧ B F U T F U T F T F F U T F F F F U U A U U U T A U F U U T F T F T T T F U T (a) Ternary Negation (c) Ternary Logical (b) Ternary Logical And Or

Figure 4: Behavior of Basic Operators in Ternary Logic

The above logic is quite straightforward: undecided can be either true or false. The negation for undecided is still undecided as it is neither known to be true or false. As for the logical and operator, similar to boolean operations, if any of the two ternaries are false, then the result is false. Otherwise, if any of them are undecided, then they cannot be true as the condition for the result to be true, which is to have both operands as true, is undecided. The vice versa applies for the logical or operator. Furthermore, other logical operators that are usually applicable to boolean expressions are usable in ternary contexts, such as implication (⇒) and equiva- lence ( ⇐⇒ ). Additionally, operators usually used as logic gates are usable in this context as well such as nand (not and), nor (not or), and xor (exclusive or). Each of these operators are broken down to their simple forms - which are forms that contain only ¬, ∧, and ∨ as the logical operators in the expression - and using the truth tables above for the calculation of their results. For instance, A ⇒ B is calculated as ¬A ∨ B. Which means undecided ⇒ false ⇐⇒ ¬undecided ∨ false ⇐⇒ undecided ∨ false ⇐⇒ undecided.

5.4 Parser Without a parser, the project would not be able to evaluate anything. Parsers are a way to read input and convert the input into some internal presenta- tion. So if a user presents the input ’x2’, then the parser should convert it to some internal representation which could be (naively) something in the lines of P ow(V ar(x), 2).

19 5.4.1 Stages of a parser There are two main stages to parsing. The first stage is performed by a lexer, also known as tokenizer or scanner. The second stage is using the result of the lexer to check whether the tokens produced are conforming to the rules of the grammar being used. If so, then an abstract syntax tree is produced which can be used to create an internal representation of the input.

5.4.2 Tokenizer The tokenizer is responsible for tokenizing input. Tokenizing means converting the input characters to a of tokens where a single token may consist of multiple characters. For example an ’identifier’ token can be defined as ’[a-zA- Z]+’, meaning that the number of characters the token consists of is at least 1 and that the characters are a, optionally capitalized, letter of the alphabet. Similarly a ’digit’ token can be defined as ’[0-9]+’. Using these 2 token def- initions, one could already tokenize the following input ’23Alpha’ into ’[digit, identifier]’.

5.4.3 Grammar Now that it is possible to convert input into tokens, the use of a grammar to check if the sequence of tokens respects all the rules in the grammar while simul- taneously building an abstract syntax tree from the tokens is now made possible as well. A grammar contains terminal and non-terminal rules. A non-terminal rule is a rule that contains other grammar rules whereas terminal rules are rules that only contain terminals/tokens. Usually a grammar contains both the to- kens and rules but the parser will split up the process into tokenizing first and then applying rules. A grammar, in general terms, is a description of the order in which tokens may appear.

Take the following grammar for example (note that the Variable and Digit rule are actually considered tokens):

Goal : Numeric Variable Numeric : Digit — Float Float : Digit ’.’ Digit Variable : [a-zA-Z]+ Digit : [1-9][0-9]*

This grammar will accept a numeric followed by a variable. So the inputs ’2x’, ’3.453y’ and ’234.99Alpha’ will all be accepted by the grammar but the inputs ’x2’, ’2.3’ and ’A2b’ will all be rejected by the grammar.

20 5.4.4 Abstract syntax tree While the grammar is checking the token sequence whether it conforms to the rules, it is also building a so called abstract syntax tree. An abstract syntax tree (AST) [3] is a graph representation of input which can be used to generate target libraries or other internally representable objects. For mathematical expressions this means that all leafs are either a numeric or a variable and all non leafs are mathematical operations. Figure 5 shows the abstract syntax tree of the expression ’5x2 + 3x + 9/7’.

Figure 5: The abstract syntax tree of the expression 5x2 + 3x + 9/7

5.4.5 Walking an abstract syntax tree After the AST has been created by the grammar, the tree’s nodes must be visited to build the internal representation. While visiting all the nodes in the tree, the programmer is building the internal representation. There are two ways to build an internal representation. One of them is by synthesizing and the other is by inheritance. Synthesizing means that the parent takes the properties of the child and uses that as its own property. Inheritance means that the children modify the properties inherited from the parent. Because of the way an AST is structured for mathematical expressions, it is easier to use inheritance. That means that the root node ’+’ will set its property to the binary operation addition. Then the root node’s left child ’*’ will set its property to the binary operation multiplication. The leaf node ’5’ will set the parent’s property’s (the multiplication) left operand to the number ’5’. Contin- uing this way you will eventually end up with the (naive) internal representa- tion addition(multiply(5, power(x, 2)), addition(multiply(3, x), division(9, 7))) which can be used to evaluate the result if you give the variable x a concrete value.

21 5.4.6 Parser types There are multiple ways to parse input and all of them have their benefits and disadvantages. The main distinction that is made for parsers is whether they are Top Down (TDP) or Bottom Up (BUP).

Top down parsers [7]: 1. Recursive descent A recursive descent parser is a top down parser with full backtracking. Full backtracking entails that whenever the parser cannot continue parsing with the rule set it was given, it will backtrack until it finds a rule that can be parsed. If at some point all combinations all tried, the parser halts and rejects the given input. A recursive descent parser is also known as a brute force parser. 2. Non-recursive descent A non-rescursive descent parser is a LL(k)(k ∈ N) parser that does not use backtracking. Instead of backtracking, the parser will look ahead at most k tokens in the stream until there is no more than 1 rule to be parsable. This essentially means that LL(k) parsers rule out ambiguity when rules partially overlap. Grammars that require more look ahead than the implemented LL(k) parser cannot be used.

Bottom up parsers: 1. Operator precedence[6] An operator precedence parser is a bottom up parser which has rules about operator precedence in the grammar. This type of parser is very efficient but cannot parse a lot of grammars.

2. LR parsers An LR parser is a bottom up parser which is very similar to a non-recursive descent parser (LL(k)). The only difference is that this parser creates a reversed right-most derivation AST.

22 6 Global and Architectural design

The visualization program focuses on a few key aspects: Clarity, Correctness, Exploration and Interactivity. When trying to get insight into existing refine- ment algorithms, the user must at all times have a clear overview of the most important aspects of his work. Therefore, clarity in the design language and visualizations is vital. The visualizations must still uphold the mathematical rigour that researchers and practitioners using interval arithmetic expect from such a tool. The correctness of the evaluation process must always be given and if not, clearly marked as such. Since the program also targets users who want to explore and get inspiration into new algorithmic designs the program aims to be intuitive, quick to respond and easily navigable at all times. Regarding interactivity, users can often make mistakes or reconsider their decision during such an exploration phase. While using the program, it is thus essential that any step taken can be adjusted and reversed. Many important aspects of such a program are difficult to know beforehand. Being in contact with a potential user of the system is crucial in understanding the demands and needs. How- ever, the participatory design alone will not lead to promising results, given that not many such tools currently exist and potential use cases can be rather broad. Instead, some decisions have to be taken by the designers. Therefore, the designers need to understand the domain themselves to come up with a good design. Given the vast possible application areas, the design should be flexible enough to facilitate different workflows and workloads. The visualizer is hence very customizable. As with any system, modularity can facilitate future demands, and this design emphasizes it, too. However, performance-oriented programming can often diminish the flexibility of the program. But, the system requires good performance such that the interactive aspects of the program are not lost. Therefore, an informed trade-off needs to be made in all aspects of the design to ensure maximum modularity while still guaranteeing the performance requirements. All in all, a researcher or practitioner interested in applying interval arith- metic should want to use the program due to its clear visual design, its interac- tive capabilities and ease of use.

23 7 Preliminary Design Choices

To create a successful product, one must balance the needs and requirements of the problem domain with the skills and expertise of the individual people involved.

7.1 Programming Language Given that the program requires high interactivity and may involve many com- plex visualizations, a fast programming language is desirable. Besides, given that the program might use acceleration methods for speeding up computa- tions on the GPU, reliable interfaces to such tools must exist. For many high- performance applications, C++ is the language of choice. Its usage crosses many fields and is the dominant language for game development, high-performance physics simulations and much more. Although the group had initially only a limited understanding of C++, the potential performance increase outweighs alternatives that use a garbage collector like Java. Refinement of inequalities using interval arithmetic requires many regions and intervals which all have the same memory layout. The reuse of such structures using custom memory pooling is thus a great potential for speed improvements which would be much more difficult to gain in garbage-collected languages. Another performance- oriented alternative is the Rust language. However, given its young age, not many well-tested libraries exist in pure Rust. Instead, often C/C++ bindings to already existing libraries are used, eliminating most of the benefits. Also, Rust has memory safety as its primary feature, which is of much smaller im- portance when writing an offline application. Besides, more modern versions of C++ also allow for safer memory allocation using smart pointers. The rapid development of the product is vital given such a time constraint project, yet the language choice turned out not to be a significant slow down in this process.

7.2 Libraries As a native application, the program needs to receive user input such as key- board, mouse and window resizing events. SDL (Simple DirectMedia Layer) is used for these tasks. Many professional video games and desktop applications utilize SDL, establishing itself as a stable and reliable platform abstraction layer. On top of that sits the (GUI) for which Dear ImGui is used. Dear ImGui is unlike many GUI libraries in that it exposes an im- mediate mode API to the user of the library. That is, the program can add and modify buttons, sliders, windows and any other aspect directly. Typical retained-mode GUIs require one to first register the different components of a window before interacting with them. The advantages of immediate mode user interfaces are plentiful. Typically, a lot of overhead associated with retained- mode GUIs is that data has to be copied into several places. It requires a lot of

24 synchronization, but most code accesses the same data in the application any- ways. Instead, the application keeps only one instance of the data and creates a single piece of code per functionality. Debugging an application becomes easier as all the code associated with a widget is in one place. Furthermore, imme- diate mode UIs unify the control flow of the application since no callbacks are used to process information. It scales particularly well to more complex custom widgets that often use state-dependent layouts. Given that many visualizations cannot be represented by off the shelf widgets this is a vital benefit. Often one sees Dear ImGUI used for debugging user interfaces in real-time applications or object editors. Integrating such a GUI into an already existing system with components unaware of the GUI is much more convenient compared to other alternatives. Also, integrating a 3D viewport into a typical GUI application can be difficult. Dear ImGui, on the other hand, is designed for such use cases. To draw the rectangles, widgets and text onto the screen, the application uses the rendering library BGFX. It is an abstraction layer that simplifies dealing with graphics like OpenGL, DirectX and Metal that are platform-specific. The extra time investment needed to deal with all the peculiarities of these platform-specific libraries was deemed less crucial to the core product and better spent on other aspects of the program. The entire architecture can be seen in figure 6. Compartmentalizing event handling, rendering and the graphical user inter- face into separate components shows that this approach is flexible and extensible to supporting many other platforms besides Linux and Windows. For dealing with interval arithmetic and parsing the inequalities, the program does not use any libraries. Instead, a custom interval arithmetic library is built along with the application. It was an active decision to allow the group to gain insight and experience in this field. It enables one to get familiar with the problems that researchers and practitioners might want to visualize. Only through a good understanding of the domain does one understand what should be included in such a visualization program. This knowledge outweighs the extra time spent in building the custom library.

25 Figure 6: Project Architecture.

26 Figure 7: Example of a refinement represented as a tree.

27 7.3 Architectural Design Choices Besides integrating the application with the above mentioned libraries as in figure 6, the application needs to keep track of a lot of state and demands the history of user actions to be accessible at all times. Therefore a tree is internally used to store every region of the refinement process. One major upside to this approach is that splitting a region into several new regions can naturally be represented by a tree structure (see figure 7). Besides, many optimizations to search for particular regions expose them- selves in a tree structure. For example, when searching for the subregion [0.44, 0.46] in the tree depicted in figure 7 one can directly find the correct branch from the root. Simply follow the path whose nodes contain the interval as a sub-interval. The history can now, at any given stage, store the active nodes of the tree to capture the entire state of the refinement tree. Further details are provided in the detailed design section. Similar to the Dear ImGui and BGFX coding style, some components are more written in the C-style, whereas other parts are more object-oriented. Sec- tion 12.3. The inequality visualizer requires many instances of intervals that all have the same size. It is a prime candidate for a memory pool that stores freed memory onto a free list to be given to the next interval that needs to be created. Thereby memory fragmentation is kept at a minimum and the data layout remains cache coherent. Cache coherence is one of the most vital aspects in achieving fast programs on modern machines since the CPU can prefetch most of the required data. Further analysis and potential improvements are given in the detailed design. The program uses one large context instance which captures all the states associated with the refinement process. This context is then directly handed over to the individual GUI elements that can apply desired state transformations. Given this new state, the main application can then evaluate the currently present regions and display them in the plots.

28 8 System Overview

This section briefly discusses the main aspects of the program before the design is being discussed in more detail in Chapter 9. The entire application is depicted in Appendix A. Following the Dear ImGui design philosophy, the system is designed as separate windows, each fulfilling a separate function. Since user interactivity is highly dependent on the context, all windows can be resized and ”docked” into separate containers to allow for a truly configurable application. The following components are highlighted and elaborated upon:

Figure 8: Showcase of Menu while the View-Tab is open.

8.1 Menu The main menu serves as the central hub to configure the application. It includes a file section, where the user can create a new file, open an existing file or save the current environment. When opening/saving a file, the file browser will pop- up with which the user can execute these tasks. The edit section gives access to typical controls to undo, redo, cut, copy or paste an action. The menu can also close any window that is currently open inside the View section. It also includes adding or removing new 2D-plots to the environment. In terms of styling the application, the user can choose from a selected range of fonts and color-schemes. On the right side of the menu is a status bar which can indicate notifications showcasing whether an undo was successful a split got executed etc.

29 8.2 2D-View

Figure 9: Showcase of 2D-View.

The 2D-View is the heart of the application. Here the regions of the refinement process are displayed. By default, a region is green if the expression evaluates to true, red if false and white if it is undecided (for more information on evaluation see Chapter 5.1). The user can highlight one ore more regions by either clicking or dragging the mouse over the given region(s) (see figure 9). Multiple 2D views can be opened at the same time to display more than two variables at the same time.

30 Figure 10: Showcase of 2D-View options.

Associated with such a view is also an options window. Here the user may set the number of splits, the grid spacing and the axis variables among other things. Often views can be used to showcase a variable without being used for splitting. Besides moving the windows independently, this is the reason for the separation of the graph and its options.

Figure 11: Showcase of console. Figure 12: Functions/Commands.

8.3 Console The user can use the fully-featured console to insert new inequalities into the program. It supports auto-completion for the supported functions (see figure 12 for all functions) as well as parenthesis matching. The user can filter on previous input and copy the entire text output. For quick editing of erroneous

31 input the console history is accessible via the arrow keys. The usual cut, copy and paste operations work inside the application as well as with the clipboard from the operating systems.

8.4 History

Figure 13: Showcase of History Table.

The system is designed with interactivity in mind. The history view enhances interactivity by showing every added inequality and every refinement the user made during this process. By clicking on an entry the state at that point will be reloaded. Modifications of previously redone changes will also be saved to sup- port the expected behaviour of an undo/redo system. The ”Clear Everything” button creates a completely new environment.

32 8.5 Region Table View

Figure 14: Showcase of Regions Table.

A lot of information needs to be processed by the user. Therefore, the system aims to be as clear as possible. The regions table enhances clarity by showing the current evaluation color associated with the region id. If the region is selected this will be shown with the same color as in the plot. For convenience, the user may also select all regions or clear the entire selection in this view.

33 8.6 Region Tree View

Figure 15: Showcase of Region Tree View.

For larger refinement operations it can become difficult to just rely on a linear list of all the regions as given in the region table view. The region tree view can enhance the understanding of the refinement history. It highlights the currently active nodes in blue and inactive ones in grey (see figure 15). The tree can automatically adjust itself such that all important regions are always visible to the user. Alternatively, only the active nodes can be displayed. This is especially useful in combination with the detailed view shown in figure 7.

34 8.7 3D-View

Figure 16: Showcase of 3D View.

The most common inequalities have either two and three variables. For more than two variables multiple 2D views are needed. It alone limits what the user can see since one variable must always be fixed to a concrete value. The 3D- View enhances the clarity of the current refinement significantly if more than two variables exist since the user can instead pan around the view-port. Zooming in and out of the view-port is also supported. A legend is given indicating the colors of the axis. This is useful to not loose the sense of orientation in what is currently being displayed.

35 Figure 17: Settings of 3D View. Figure 18: Adjusted Grid for View.

Since configurations of the inequalities can vary drastically the 3D view options allows the user to change various aspects of the 3D view. The camera can change the field of view (FOV) and allows the user to zoom in and out of the view-port. A grid for each axis can be activated and altered which is particularly useful to line up a grid to a border of the inequality as in figure 18.

8.8 Notes The application is targeted to researchers and practitioners who would like to explore their problems. The notes can aid in keeping the most important insights stored right with the inequality itself (see figure 19).

Figure 19: Showcase of Notes. Figure 20: Help View.

8.9 Help Box There exist a lot of different pieces that make up the application. To encourage exploration and not drag the researcher into a potentially inconvenient help page, the program provides an interactive help box. When hovering over a window the most important information is displayed alongside the currently

36 available keyboard and mouse shortcuts. It is a non-intrusive way to help the user to get up to speed with all the features the application provides (see figure 20).

37 9 Detailed Design

In this chapter, the details of the application are provided alongside explanations of the taken design choices. It is here where all the low-level details of the program will be explained and justified.

9.1 Data Layout Besides the interaction with the libraries as seen in figure 6 the application it- self manages a main context similar in nature to a thread-local context given to a thread. Unlike a Model-View-Controller (MVC) mechanism the application does not have such a clear separation of concerns. Instead, all the associated logic for a specific component is bundled together. This is in part enabled by the immediate-mode nature of the Dear ImGui library which will be further discussed in section 9.4. The major advantage of such an approach is that there exists one coherent control flow which is not broken up by countless callbacks. This improves the ability to debug the program. Furthermore, many sophisti- cated aspects of the GUI are very state dependent. In traditional design, the state that is required to draw the GUI would need to be copied and managed separately by the individual components. However, in this application almost all widgets require access to the same data, namely all the regions and intervals that exist. Synchronization amongst all of these different components would be- come unnecessarily difficult. Furthermore, in MVC-style mechanisms the true computational complexity of any component is difficult to judge. On the other hand, the complexity of a component is completely transparent to the develop- ers in an immediate mode interface. The important aspects of this context are laid out in the following paragraphs.

Refinement Multi-Tree All regions of the refinement process are stored in a n-ary tree provided by an STL-like tree datastructure from Kasper Peeters [8]. There is a trade-off to be made in terms of speed of access and the size of the tree. Given that this is an interactive application and memory is plenty-available on most modern machines the tree doesn’t remove regions that are currently disabled. Instead, they are kept inside the tree and a mask selects all the currently active nodes. Switching between different history stages is thereby instantaneous as it does not have to restructure the tree. This process is displayed in figure 21.

38 Figure 21: Internal tree structure.

Moreover, if a new inequality is given to the program, the tree will insert a new head into the tree creating a forest. As a result, only one tree/forest can encapsulate the entire history of all inequalities.

9.2 History The history stores a list of edits and an index pointing to the currently active history stage. For each user edit, the id of the currently active inequality, a description and a boolean list of the currently active regions is saved. If a region entry doesn’t exist in the list (e.g. history was undone) then it is implicitly disabled. Adding, undoing and redoing history changes is thereby as simple as setting the active history stage index.

9.3 Saving and Loading Files Saving and storing the application state is an integral part in making the pro- gram useful beyond merely being used to show off. There is a trade-off to be made between readability and size of the saved files. Making the state human readable makes it possible to potentially reason about the program outside of the application. However, the file size increases and load times could become longer. Given that interactivity and clarity is the focus of this application, the program uses the human-readable ini file format. It has an additional advantage of being very easily modifiable. Furthermore, the application periodically saves the current state to a backup file. In case of a crash, the application will then open this backup file, restoring the saved state. This is also convenient for when the user forgets to save the process.

9.4 Graphical User Interface In order to understand the structure of the graphical user interface, a brief introduction to Dear ImGui has to be given. Dear ImGui is an immediate mode

39 graphical user interface library. Therefore, unlike many traditional GUI libraries state is not retained by the individual components. If one likes to display e.g. a button and conditionally display some text inside a window, the associated code would look as follows: i f (ImGui :: Begin(”Window T i t l e ” , &i s o p e n ) ) {

i f (ImGui:: Button(”Button T i t l e ” ) ) { ImGui:: Text(”Button pressed ” ) ; } else { ImGui:: Text(”Button not pressed ” ) ; } } The control flow of the program remains in tact since no callbacks are used. As a result, debugging the program is easier. This structure of opening a window for a component and conditionally drawing something to the screen is being used for all elements of the Gui. To understand the current context of the program, every Gui function receives the context (see section 9.1). The component then iterates over all the regions in the refinement tree and uses the given information to send the specific draw calls to the renderer. For 2D-Plotting the library ImPlot, an extension to Dear ImGui, is used [9]. It allows to draw the grid and axis of a graph accordingly and deal with user input such as mouse-dragging and selections. The regions themselves are directly added to the draw calls as rectangles after being translated to the plot- coordinates. For the tree view, the library imnodes is used which again is an extension to Dear ImGui [5]. Although it is in part designed to be used for Node editors it can also be used to display a tree. Each node is thereby always connected with all of its children. For the automatic layout the tree must first count the nodes on each level before it can correctly determine the positions of each node. This aspect of the tree view is currently not cached as it can change depending on whether the user wants to see details, only the active regions or manually wants to layout the nodes. This operation is currently unnecessarily slow and could be optimized which would allow the user to see many more nodes. However, the benefits of seeing more than a couple hundred nodes becomes rather small which was the reason to not focus on this problem.

40 9.5 3D-Viewport Whereas all components of the Gui are rendered using functions from Dear ImGui, the 3D-Viewport directly calls bgfx to render the scene (see figure 6). When dealing with 3D objects, every rendering back-end consists of several stages of transformations of an object inside a pipeline. Explaining the entire pipeline is beyond the scope of this introduction since only the simplest aspects are used inside the application. For a more detailed introduction the following resource is very useful [11]. At a fundamental level, the rendering back-end requires for each object a set of coordinates that each represent a vertex. Note that these coordinates are given in normalized device coordinates. Graphics engines work with triangles as the most fundamental primitive. The index buffer is used to indicate which sets of three vertices make up a triangle. This creates a geometry which will be rasterized by the graphics card. Finally, to give color to these objects a fragment is used. are small programs run inside the GPU that focus on a small simple task. However, this is not it since the user also wants to look around the objects to see the regions from multiple sides. A camera must exist to project the scene onto a two dimensional rectangle. The application only uses one basic primitive to create all other kinds of regions. That is the cube. In the following, the cube will be used as an example to demonstrate this .

Figure 22: Visualization of simplified graphics pipeline.

The main world-view must be transformed to account for camera rotations, zoom and the projection. This is achieved through various matrix transforma- tions which can be multiplied together to form a single view-matrix and a single projection matrix (see figure 22).

41 Now that cubes can be displayed and the user can freely zoom the view-port it only remains to transform these cubes to represent the actual regions. First, the cube must be scaled to the desired rectangle based on the widths of the x, y and z variables. Then a translation matrix can be used to first move the left corner to the origin and afterwards to move the block to the correct position. The correct position is given by the lower-bounds of all the variables. Multiplying all these matrices together results in the needed transformation. An example of this process is given below:

 1 0 0 0 1.1 0 0 0  1 0 0 0  0 1 0 0  0 1.5 0 0  0 1 0 0 T =   ×   ×    0 0 1 0  0 0 3.0 0  0 0 1 0 (1) 2.2 0 0 1 0 0 0 1.0 1 1 1 1 = Translate × Scale × Translate

This process is also depicted visually (now from left to right):

Identity Origin Scaling Translation

Note that matrix multiplication is non-commutative so the order matters. The right-most matrix will be applied first. Initially, the object is moved to the origin, then it is scaled and finally it will be moved to the correct position. In general, we have:

 1 0 0 0 w(x) 0 0 0  1 0 0 0  0 1 0 0  0 w(y) 0 0  0 1 0 0 T =  × ×   0 0 1 0  0 0 w(z) 0  0 0 1 0 2lb(x) 2lb(y) 2lb(z) 1 0 0 0 1.0 1 1 1 1

Where lb(a) is the lower-bound of interval a and w(a) the width of the interval.

The transformation is twice as large due to the normalized device coordinates which go from -1 to 1. Note that only the final transformation matrix is required for each region to completely repaint the scene.

42 9.6 Interval Arithmetic The interval arithmetic library which is used is implemented by the group them- selves. Section 5.1 discusses why the team decided to build their custom interval arithmetic library. A discussion with the supervisor revealed that it is suffi- cient for most cases to use regular double precision floating point numbers - in C/C++ just doubles. Also, intervals are designed such that the lower and upper boundary of the interval are stored. struct I n t e r v a l { double lower ; double upper ; } This is the core component of the interval arithmetic. Based on this data structure all operations were implemented. To ensure the interval is not copied each time it is passed to a function, they are passed as reference wherever suitable. The interval arithmetic library was subdivided into four files: inter- val.cpp, interval arithmetic.cpp, interval bool.cpp, interval num.cpp. The files were seperated cause each file represents a different set of operations. Most operations are defined in the previously mentioned IEEE standard 1788-2015 (compare 5.2.4). Implementing the interval arithmetic while having this standard in mind has two particular advantages. Firstly, the standard is a complete reference to the set of operations which are required. All of the defined operations are defined by a mathematical expression which helps to implement it correctly. Secondly, the standard is a detailed guide how to deal with corner cases and exceptions. This is not only important because it provides references to corner cases but also because it summarizes all corner cases. Consequently, it increases stability and beyond that it is a good selling point for the library. In the file interval.cpp defines the very basic functions, like creator functions for an empty interval [NaN, NaN], for an entire interval [−∞, +∞] and for a regular interval with defined boundaries. Additionally, it includes helper functions like toString or display which are used to represent the interval as a string or for debug purpose in the console. Also, this file implements all the operator overloading by calling the correspond- ing functions in interval arithmetic.cpp or interval bool.cpp. The overloaded operators are these:

• << → interval::toString(Interval) • + → interval::add(Interval, Interval) • − → interval::sub(Interval, Interval) • ∗ → interval::mul(Interval, Interval)

• / → interval::div(Interval, Interval)

43 • ∧ → interval::pow(Interval, Interval) • % → interval::mod(Interval, Interval) • == → interval::equals(Interval, Interval) • ! = → !interval::equals(Interval, Interval) • < → interval::strictPrecedes(Interval, Interval) • <= → interval::precedes(Interval, Interval) • > → interval::strictFollows(Interval, Interval) • >= → interval::follows(Interval, Interval)

9.6.1 Arithmetic Operations The file interval arithmetic.cpp includes all the arithmetic operations. This includes the unary operations neg(x) = −x and recip(x) = 1/x. These unary operations are partially used in the binary operations, implemented as follows:

add(x, y) = x + y sub(x, y) = add(x, neg(y)) mul(x, y) = x ∗ y div(x, y) = mul(x, recip(y)) pow(x, y) = xy constraint(x, y) = [max(x, y), min(x, y)] sin(x) cos(x) tan(x) arcSin(x) arcCos(x) arcT an(x) mod(x, y) = x%y

Since trigonometry functions are rather complicated, they are briefly dis- cussed here. The trigonometric functions sine, cosine, tangent and their in- verses have been implemented. The sine function has been implemented using the cosine function, since sin(x) = cos(x − 0.5π). Because the trigonometric functions are not monotonic, the implementation of cosine and tangent are very specific for intervals. For example when evaluating cos(x) where x is the interval [−2π, 2π], you cannot just evaluate the cosine at the lower and upper bound and return that as the result. Because cos(−2π) = 1 and cos(2π) = 1 it would mean that the result returned would be the interval

44 [1, 1] which is incorrect. The actual result should be [−1, 1] instead. To make the evaluation a bit easier, the functions make use of modulo to restrict the interval in a certain interval range. If the width of the interval is greater or equal to 2π then [−1, 1] is returned for sine and cosine and [−∞, ∞] is returned for tangent. If the width is less than 2π, specific cases will be considered and the proper interval will be returned. Similarly to trigonometry functions, also the modulo operation is introduced. The modulo operator is very interesting for intervals but before defining modulo it is important to know how euclidean division works: Given two integers a and b, with b 6= 0, there exist unique integers q and r such that a = bq + r and 0 ≤ r < |b|, where |b| denotes the absolute value of b. Here a is called the dividend, b the divisor, q the quotient and r the remainder [2]. For modulo with intervals, the euclidean division algorithm is first rewritten. Rewriting euclidean division gives us a−bq = r. The variables a and b are known and r is to be computed. To compute r we need to calculate what q should be, a.lower a.lower which is rather easy. q = if a < 0 otherwise q = (remember b.lower b.upper that q is an integer). Now that q is known, the interval (using interval arithmetic operations) a − q ∗ b needs only to be returned. To make it a bit more concrete, think of modulo as something that tries to make the lower bound of the dividend as close to zero as possible but it will never pass the zero. So a negative lower bound will always stay smaller or equal to zero and a positive lower bound will always stay positive (this is only true when the divisor’s lower bound greater or equal to zero). For example [9, 15.5] mod [2, 3] will result in [1, 3.5] and [−3.75, 2] mod [2, 3] will result in [−1.75, 5].

9.6.2 Boolean Operations The file interval bool includes all operations which compares two intervals and returns a boolean. These operations are used to compare the results of the evaluation to each other and to decide on entered conditions whether a boolean expression holds true or false. These are the operations of the file:

45 contains(x, p) ⇒ x ≤ p ≤ x containsW ithin(x, p) ⇒ x < p < x containsZero(x) ⇒ contains(x, 0) isEmpty(x) ⇒ x = NaN ∧ x = NaN isV aild(x) ⇒ x ≤ x isP oint(x) ⇒ x = x equals(x, y) ⇒ x = y ∧ x = y approxEquals(x, y, e) ⇒ |x − y| ≤ e ∧ |x − y| ≤ e less(x, y) ⇒ x ≤ y ∧ x ≤ y strictLess(x, y) ⇒ x < y ∧ x < y greater(x, y) ⇒ x ≥ y ∧ x ≥ y strictGreater(x, y) ⇒ x > y ∧ x > y precedes(x, y) ⇒ x ≤ y strictP recedes(x, y) ⇒ x < y follows(x, y) ⇒ x ≥ y strictF ollows(x, y) ⇒ x > y isSubset(x, y) ⇒ y ≤ x ∧ x ≤ y isP roperSubset(x, y) ⇒ y < x ∧ x < y isDisjoint(x, y) ⇒ x < y ∨ y < x

Just as the interval arithmetic functions need to do, also the boolean oper- ations have to deal with corner cases. Again, the IEEE standard for interval arithmetic provided guidelines how to deal with invalid intervals or under which condition either true or false needs to be returned. Especially, the function is- Valid is crucial. This function is defines when an interval is valid. In case of an invalid interval, the all functions return either false, NaN or the interval [NaN, NaN].

9.6.3 Numerical Operations Lastly, the design of the file interval num.cpp needs discussing. The file includes all operations which result in a numeric result, such as the width of an interval. Since the library for the interval arithmetic was implemented at the very be- ginning of the project, it was not clear which functions are required and which can be omitted. Nevertheless, according to IEEE 1788, the numeric functions need to do little corner case handling. Most operations just had to check for valid input parameters, afterwards the actual operation could be performed. This means, the implementation of these operations was easy and quick. There- fore, the group decided to implement the functions rather optimistic than being

46 required to add them later on. This decision also leads to a more complete interval arithmetic library, which leaves the option to outsource the library and publish it separately to the interval visualizer. This is an overview of the numeric operations:

abs(x) ⇒ max(|x|, |x|) dist(x, y) ⇒ max(|x − y|, |x − y|) inf(x) ⇒ x sup(x) ⇒ x x + x mid(x) ⇒ 2 wid(x) ⇒ x − x x − x rad(x) ⇒ 2 9.7 Splitting intervals 9.7.1 Memory Pool Allocation When starting an application on a modern day operating system, the operating system assigns a block of memory for the application. The application is free to use this memory just as it needs. Additionally, if the application exceeds the reserved memory, it can request more memory from the operating system. In low-level programming languages such as C and C++, the developer is in charge of properly allocating and deallocating memory. When defining a new variable in C++, the variable is placed on the stack. The stack is the default option for allocating space for variables. The essential disadvantage of the stack is that it can just store variables of a fixed size. In addition, the stack stores memory only until the moment of leaving the scope of the variable. One can see, the stack is quite a non-dynamic option. To tackle the inflexibility of the stack, a developer can also allocate memory blocks on the heap. The heap is an unstructured large block of memory which is freely available to the application at run time. In C and C++, the system call malloc allocates a block of memory on the heap. malloc’s pendant is the system call free, which deallocates previously allocated memory. In low-level languages, the developer is responsible for allocating and freeing memory blocks only a single time. Freeing a pointer to a memory block twice results in undefined behaviour and has to be avoided. Furthermore, a developer should ensure to allocate memory as few times as possible. Memory allocation is an expensive operation. This is especially the case, when the applications memory exceeds the reserved memory and the operating system has to assign a new memory block to the application. To sum it up, low-level languages like C and C++ give a lot of responsibility to the developer. Apparently, the application is written in C++, which requires

47 a memory allocation strategy. The strategy depends to some extent on the architecture and the requirements of the application. In this case, assurance that regions and the belonging intervals needs to be placed on the heap is desirable. In the inequality visualizer, the team designed a memory allocator which allocates a large block of memory each time, before refinement requires lots of new memory for the refined regions. The memory allocation should happen once for all the needed memory and should hand out pointers to each of the slot for the required data type. The struct of a region consists of an id, a variable counter, an inequality counter, an array of intervals, a region status and lastly some flags for GUI operations. The number of variables and the number of inequalities, e.g. the results of the evaluation define the number of intervals needed in a regions interval array. The internal structure first stores one interval per variable and afterwards the results of each resulting inequality. This means, each time before splitting, one can check all regions which lie within the defined splitting boundaries. Based on the number of regions and the splitting strategy the number of required intervals can be calculated. Section 9.6 shows that an interval consists of two doubles - 16 in total. Illustrating this with an example helps to understand it. As a user, one can enter the inequality

x + y < = 1.0 x = [0, 1] y = [0, 1]

At the moment of the input and the boundary definition, there exists just a single region with two variables, i.e. two intervals and one inequality, i.e. one result interval. The variable-sized interval array of the region contains these values:

region.interval = [x, x, y, y, NaN, NaN] It sums up to 48 bytes:

2 ∗ 16B + 1 ∗ 16B = 48B When splitting the variable x ten times, the splitting will end up with ten sub-regions. The intervals of each sub-region is the same as for the first region. Therefore, space for 10 ∗ 3 = 30 intervals is required. Speaking in bytes, 480 bytes of memory is needed to represent all the intervals. This is where the memory allocation excels. Before each splitting, the num- ber of required intervals is calculated. This number is allocated once in a large memory pool. The size of the memory pool is exactly the size which is required to fit in all values for each interval of each region. Then, the memory pool hands out pointers to the start of the interval each time the memory splitting requires a new pointer to an interval.

48 The advantage of this memory pool is, that it allocates a large chunk of data exactly once. When it is not needed anymore, the entire block of memory can be freed at once. This minimizes the number of malloc and free calls and ensures a performance optimized memory management.

9.7.2 Manual Refinement In manual refinement the user can decide which regions to split. The selection of the regions to split can occur through several ways. Either the user manually selects every region, uses the middle-mouse to select a range of intervals (see figure 9) or by selecting regions with the region view or tree view (see sections 8.5 and 8.6). Furthermore, the number of splits per region can be altered. With more than one split selector the question arises which grid these split selectors should follow. Besides being able to alter each split selector individually, several common grids are provided. The following shows these grids for 10 splits:

Linear Chebyshev Sine Random Normal

Note that the random and normal distribution also provide a button to change the seed of the distribution. Internally, the history stage stores for each region whether it is selected or not. The grid type can vary per plot and is stored inside the plot structure.

9.7.3 Automatic Refinement Similar to the manual refinement, the automatic refinement splits the selection regions, resulting in smaller regions with refined results. The difference between them, however, the solver does the splitting based on certain criteria instead of having the user manually split regions by indicating where the split should be done and how many splits should be done in each region that is to be split. Once the information needed by the automatic refinement are provided and automatic refinement is used, the solver iteratively splits, using the bisection method, the selected regions and their children until their result intervals are equal or below the specified tolerance. If even one of those regions still do not fulfill this requirement, the iterations continue. Each iteration represents a history entry. Though an alternative to this implementation would be to have only one additional entry after all iterations of the refinement are done, this was chosen for its interactability. With having possibly multiple history entries resulting from a single refinement, the user is able to see the steps taken by the solver in achieving the refinement. In other words, the user is then

49 able to undo and redo the states of the plot to understand how the refinement proceeded. A recursive approach was taken in implementing the automatic refinement, meaning that an iteration is a single function call which calls itself if another iteration is still needed. Similar to the manual refinement, in every split, the tree is used to specify the children of the regions that have been split. The criteria mentioned above consists of two elements: a tolerance and an axis. As briefly mentioned before, the tolerance is the threshold with which the result of each split is to be compared. If the width of each interval resulting from each split is smaller than or equal to the given tolerance, then that resulting region will not be split anymore in that refinement. In contrast, if a region resulting from a split is still greater than the tolerance, then this region will be split again in the next split iteration. An axis is also required for the automatic refinement to take place as the piece that is compared with the given tolerance. Surely the tolerance needs to be compared with an interval. If the solver were to work with functions only, then an axis would not be necessary as the tolerance would just be compared to the result of the function’s result of each region. As the solver works with inequalities and ternary expressions instead, this is no longer a valid option. Not only can a choice be made between using the right or left hand side of the in- equality for the comparisons, but there are possibly multiple of such inequalities as well. Therefore, the axis is used as the basis of whether or not the tolerance has been satisfied, in that the width of the interval of the region associated with the axis is to be compared with the tolerance. For example, take an inequality x < 0.5 where x = [0, 1]. The plot would initially have one region. If a tolerance of 0.125 on the x axis is given, then eight sub-regions would be in the plot. Each region coincidentally have a width of exactly 0.125 because of the bisection approach. From the region with the smallest x interval, the first three and last four regions would be true and false, respectively, while the last one is undecided since [0.375, 0.5] for undecided in the given inequality. There will be three new history entries in which, from the older entries, two, four, and eight new regions are added from the previous regions. Each region would have two children except for the eight regions which act as leaves. This is as seen in figure 23.

50 Figure 23: Layout of the Result of the Automatic Refinement Example.

9.8 Parser Because of the specific use case of the project, the team was unable to find any good polynomial parsers that allow for enough modification to suit the group’s needs. Most parsers produce a polynomial that can evaluate if you set the variables to a concrete value but you cannot set it to work with intervals, as is the case for the group. For this reason, the entire parser has been made from scratch and is located in the ’src/parser’ directory. The parser is a recursive descent or brute force parser. As discussed in the parser research section 5.4, the parser directory contains a tokenizer, a grammar, an AST and an ASTWalker. There are some other objects like Token, Symbol, Terminal and NonTerminal to simplify the process. The tokenizer is responsible for splitting the input into different parts as tokens. The token contains the column input from where the token starts in the original input and it contains the text of which the token is made up from. Now that it is possible to tokenize the input, it is also possible give the stream to the grammar. The grammar will create the abstract syntax tree while parsing the stream. If the parser cannot continue because no rule in the grammar accepts a part of the token stream, it will throw an exception with the reason why it could not accept the input. If the grammar does accept the token stream, the corresponding AST is returned. Because the parser is a recursive descent, a grammar specific for polynomials has been created to prevent left recursion.

51 Goal : TernarySequence TernarySequence : Evaluable TernarySequence’ TernarySequence’ : TernaryOperation Evaluable TernarySequence’ | epsilon TernaryOperation : ’&’ | ’|’ Evaluable : InEquality | ’(’ TernarySequence ’)’ InEquality : ’(’ Expression EqualityOperation Expression ’)’ InEquality | Expression EqualityOperation Expression Expression : Term Expression’ Expression’ : OperatorAdd Term Expression’ | epsilon OperatorAdd : ’+’ | ’-’ Term : Exponent Term’ Term’ : OperatorMult Exponent Term’ | ImplicitTerm | epsilon ImplicitTerm : PExponent Term’ Exponent : NFactor Exponent’ Exponent’ : ’ˆ’ NFactor Exponent’ | epsilon PExponent : Factor Exponent’ Function : FuncName ’(’ Expression ’)’ FuncName : ’sin’ | ’cos’ | ’tan’ | ’sqrt’ | ’log’ | ’ln’ Factor : Numeric | Function | Variable | ’(’ Expression ’)’ NFactor : ’-’ Factor Variable : [a-zA-Z] Numeric : Float | Number Number : [1-9][0-9]* Float : Number ’.’ Number

As you can see from the grammar, all left recursion has been removed. Addi- tionally, the grammar has built in mathematical operator precedence. Meaning that the resulting AST can be walked in preorder while preserving the order of operations, there is no need to restructure the AST in any way to ’fix’ order of operations. The grammar does not have built in operator precedence for the boolean operations of inequalities. This is because it is a lot easier to just implement it in the internal representation. Because this project is all about evaluating polynomials using intervals arith- metic, this grammar is made in such a way that users can easily put in poly- nomial expressions. The input ’2 ∗ (x2) + 3 ∗ x + 4’ is considered the same as ’2x2 + 3x + 4’ because of implicit multiplication. This way of allowing input comes with a minor downside, namely it is only possible for a variable to consist of a single char. Meaning ’Alpha’ would be considered ’A ∗ l ∗ p ∗ h ∗ a’ by the grammar. This is considered a minor downside because it could be changed visually in the GUI later. If a user inputs ’2A + O’, then the user could input in the GUI somewhere that ’A’ should be presented in the GUI as ’Alpha’ and ’O’ as ’Omega’. Also ’xx’ would be evaluated the same as ’x2’. For parsing functions there is not a lot to be changed. Function names can be considered to be a sequence of letters followed by parentheses with an expression inside of it. Because the function names are a sequence of letters, there is overlap in whether a part of the input should be considered a function

52 call or just implicit multiplications of variables. For example ’sin(2x)’ could be parsed as ’s∗i∗n∗(2∗x)’ but also as a function call to the trigonometric function sin with the argument ’2 ∗ x’. According to the grammar, if a function call is possible, it will make that function call. In other words, there is precedence to function calls over regular variables, as can be seen in the ’Factor’ rule of the grammar. Function calls are case sensitive and can also be chained in implicit multiplications. So ’sinsin(2x)’ will be parsed as ’s ∗ i ∗ n ∗ sin(2 ∗ x)’. This may create some minor confusion sometimes but usually it does not. To make it more clear the user can input it like ’nis ∗ sin(2x)’ instead. Something else to look out for when using implicit multiplication is when using exponents. The polynomial ’2x3’ is a rather simple polynomial and as you might have guessed is parsed as ’2 ∗ (x3)’. However when using multiple terms inside the exponent it may become a little confusing. For example the polynomial ’2xˆ3yz’ is parsed as ’2 ∗ (x3) ∗ y ∗ z’. So only the first term on the right of the exponentiation symbol is considered to be part of the exponent. If you do need the entire expression after exponentiation symbol as exponent then you should input it with parentheses like ’2xˆ(3yz)’.

9.9 Expressions In this context, an expression is only a mathematical expression and has nothing to do with inequalities or ternary expressions. An abstract class Expression is defined that requires all different forms of mathematical expression structs or classes that inherit it to have certain properties. The most notable of those properties is to have a function Interval evalu- ate(Interval*) that should return a result interval given an array of intervals. The array of intervals represents the interval values of variables that exist in the current context. As expressions can have sub-expressions (e.g. 5 ∗ 2 is a sub-expression of 3 + 5 ∗ 2), evaluate is mostly used by parent expressions to do their own evaluations. Another useful method is the toString() method, which returns a string representation of the Expression. The Expression struct also has a copy() method, which creates a deep copy on the heap of the instance it is called on and returns the pointer. This is required in c++ when trying to copy pointers to abstract classes. Finally there is a function that retrieves a list of pointers to Variable in- stances which is useful for the GUI. The Expression struct contains more functions, but these are remnants from the old evaluator. There are two important children of the Expression abstract class, which are abstract classes themselves, being UnaryExpression and ExpressionSequence.

9.9.1 Unary Expressions Unary Expressions are expressions that require only one argument. sin(x) is an example of a unary expression as x is the only parameter it needs. Most

53 children of UnaryExpression are functions. sin(x) is also a function. The only differences between each function expression is how their evaluation works and their string representations. The function expressions implemented are ArcCos, ArcSin, ArcTan, Cos, Ln, Log, Sin, Sqrt, and Tan. Their evaluations behave as described in section 9.6.

9.9.2 Expression Sequences An expression sequence is a sequence of expressions connected by binary math- ematical operators (i.e. those that require two arguments). An alternative to this approach would be to create a class for each of the operators with it having two expression fields that represent the left and right side of the operator. The sequence approach was favored more for its debuggability and optimization. Each sequence can only have one operation type, in that all operators in the sequence needs to be of one type. The types are additive (addition and subtraction), multiplicative (multiplication and division), and exponential (ex- ponentiation). This is such that the evaluation would not require to decipher which operations need to be done before the others. The evaluation would only need to iterate through the sequence and update the result corresponding with the expressions and operators. Furthermore, this allows for the creation of clean code by having a viable identity value for different operation types. Therefore, it is the responsibility of the programmer to ensure that the sequence is only of one type. If a sequence already has a defined type, and if an element is added to the sequence with an operator that does not correspond to that type, then the operator would be ”forced”, in that the operator would be changed depending on the type of the sequence. The expression would remain the same and would still be appended to the sequence. Moreover, additive and multiplicative operators indicate a left-associated sequence, whereas exponential ones indicate a right-associated one. This mat- ters for the order in which operations are done. In a left-associated sequence, then the operations proceed from the beginning of the sequence to the end, systematically by utilizing the expression and operator in each element. In a right-associated sequence the sequence will be iterated in reverse, starting at the end of the sequence and moving to the beginning. This means that internally z the formula xy is still stored as ’[x, y, z]’ but it is iterated as if it was stored as ’[z, y, x]’.

9.10 Evaluables An abstract class that acts similar to an interface is written as IEvaluable. Any class that is a child of the abstract class is, therefore, considered an evaluable. An evaluable is any class or struct that could express the status of any given region. Since regions each have independent statuses from each other, depending on the variable intervals present in each, evaluables, as the name suggests, are able to evaluate each region, indicating for each region whether it is true, false,

54 or undecided. Additionally the interface comes with some helper methods to improve usability of an instance of IEvaluable. The interface tracks all the variables it contains and orders them accordingly. It also comes with some counters, for example to see how many Ternaries or InEqualities this current Evaluable consists of. The following are evaluables and how they each work specifically:

9.10.1 InEquations A single inequality is represented by an instance of InEquation. An InEqua- tion contains 2 pointers, the left hand side and right hand side, to Expression instances. Additionally the struct has an enum that encapsulates the boolean operation that an inequality can have. These are <, >, ≤, ≥, and =.

9.10.2 Ternaries Ternary expressions are generally represented by what is called a TernarySe- quence, which is a sequence of evaluables connected by boolean operators. Sim- ilar to mathematical expressions, however, these expressions also have an order of operations. As in, these expressions have higher priorities for different oper- ators. Currently, the list of supported operators, ordered from those to be first in terms of the order of operations, are as follows:

Operator Alias Symbol Used for Solver Negation NOT ! Inverse Logical And NAND !& Inverse Logical Or NOR !| Exclusive Or XOR ˆ Logical And AND & Logical Or OR | Logical Implication IMPLY -> Logical Equivalence IFF <->

Figure 24: List of Supported Operators for Ternary Expressions

Note: These expressions and mathematical expressions are also alike in that anything between parentheses precede all other order of operations. It is not included in the above as it is not considered an operator but it is part of the parser. A TernarySequence has the function reconstruct(), which reconstructs the associated sequence based on the operators. Each sequence is then only con- nected by one type of operator. For example, if a sequence were to appear as A|B&C|D, the sequence will initially be one sequence containing the evaluables and operators. Reconstructing it changes it to the sequence A|(B&C)|D inter- nally, with (B&C) being a sequence in itself. As seen in the table above, this is

55 because the AND operator precedes OR. Likewise, reconstruct() basically puts parentheses in the sequence around operators with higher to lower priorities. As with all evaluables, TernarySequence has the ability to evaluate the status of a given region. The way it does this is surprisingly relatively simple: Since each element of the sequence is an evaluable, the given region can be evaluated with it to return a status. Therefore, the sequence first evaluates the region with each of its elements to essentially have a sequence of region statuses. Therefore, the sequence inherently works with the statuses of its elements together with the ternary operators to return a single status. Since TernarySequences are evaluables as well, it is also possible to have nested sequences, and this evaluation will be used for the evaluation of the upper-scope as well. Other ternary structs are TernaryNot, which has an evaluable field in it and represents the negation of the result of the evaluable, TernaryTrue, TernaryUn- decided, and TernaryFalse, which returns the region statuses true, undecided, and false, respectively, from their evaluations. The latter three are mainly used for testing or if the user were to use raw ternary values for some reason. The former exists only because the negation operator cannot bridge two statements, as it is a unary operator instead of a binary one.

56 10 Testing

An abstract class is written for different tests of different sections to inherit. All test instances are run in one central test file. The abstract test class is written such that when all its children start tests, the tests are timed and all test cases specified to be tested are executed. For each test case, assertion statements are written manually such that the tests will come to a halt if anything results in what is not expected, as is the case in most unit testing. The following are different test instances that inherit the abstract class and that test different unit parts of the project:

10.1 Interval Tests One of the classes which are integrated in the self-written test framework is dedicated to unit-test the interval arithmetic library. The tests are organized in different sections to mimic the structure of the interval arithmetic library. To keep the writing of test cases efficient and avoid unnecessary boilerplate code, one test function for each of the functions/operations was implemented. Each test function covers multiple test cases. Each test function is structured in three sections. The first section is the initialization of the required intervals. The second section is a simple call of the targeted function. The last section compares the results with the expected value. The keyword assert interrupts, if a test fails. The first section tests the constructor functions. It tests creating an empty interval, the entire interval from [−∞, +∞] and also creating an interval with predefined values. Secondly, the Boolean interval operators is tested. It has functions for all of the operations. However, some test functions are still flagged as TODO, which means, those tests are either incomplete or missing. In the next section, all of the numeric functions are tested. Boolean test cases and numeric test cases are trivial, thus the focus lays more on the arithmetic test section. Lastly, the arithmetic test section covers all of the arithmetic operations. In addition, the tests for trigonometric functions are included in the arithmetic test section. This is simply the case, because all arithmetic functions return an interval as a result. Likewise, do trigonometric operations return an interval as a result. In comparison, numeric operations return a double and Boolean operations return just true or false. The arithmetic test contains the most important tests. Therefore, the test cases are most complete in this section. The reason that addition, multiplication and exponentiation works differently from regular arithmetic, it is important specifically focus on those tests. The arithmetic test section also tests the ad- ditional monotonic and non-monotonic functions such as the natural logarithm and the sine of an interval. These functions make use of the overloaded operator instead if possible. For example exponentiation uses ’ˆ’ and modulo uses ’%’. Since the team already started working on the interval arithmetic library based on MPFR values instead of doubles, they also included test cases for this.

57 The operations and the testing procedures are the same. Therefore, seperate section for the MPFR unit tests was not included.

10.2 Expression Tests Expression test is testing the Expression struct. It only contains tests on the essential parts that are currently used by the project. Expression contains remnants of the old naive implementation of expressions, namely all the structs that extend fromBinaryExpression. The tests mostly serve as to check whether specific functions produce the correct result, since parts of the code could be copied over wrongly. The copyTest() tests implicit copying of expressions to ensure that the copied expression is independent of the original expression. Because the expression instances contain members that point towards other expressions, it is important that the copy constructor is implemented. If you do not implement the copy constructor, you will at some point delete the same expression twice in the destructor of UnaryExpression. After the copy, the equality of the evaluations is tested. Then a value in the copy and test inequality of results is changed. As can be deduced from their names, the additiveTest() tests the addition and subtraction of an ExpressionSequence, the multiplicativeTest() tests the multiplication and division of an ExpressionSequence, the powTest() tests the exponentiation of an ExpressionSequence. Finally funcTest() tests sine, cosine and tangent and inverseFuncTest() tests their inverses. The inverse is tested using the same object that is used in funcTest. Essentially the inverse is tested as f −1(f(x)) = x.

10.3 Property Tests PropertyTest is a very small test that only tests ASTProperty put and remove methods located in the AST header file. This test was made purely because of the inexperience with C++ programming.

10.4 Ternary Tests Tests on expressions using ternary logic is mostly done by testing each binary operator and assert that the expected result arises. As ternary logic has three possible values instead of two, nine test cases are done for each operator instead of four. Furthermore, other than testing the different operators, the test cases also test the behavior of a TernarySequence. It should reconstruct itself into correct sequences from its current sequence, in that the sequence should have levels which are, starting from the highest priority to the lowest: negation, multiplica- tive, and additive operators. This means that a sequence can consist of multiple sequences as well, effectively creating a tree in which the leaves are those with the highest priorities.

58 10.5 Parser Tests Tests on the parser are done with texts/strings that represent inequalities that the parser should be able to handle. This means that the tests are successful if and only if there are no errors in parsing any of the given strings. The inequalities used are from files given by the project supervisor. In each is a formula that range from moderately short to those that are colossal and intricate. Though it is difficult to imagine a user to use any of the given formulas, it should be no grounds as to why the parser should not be able to handle them. After the tests were done, a few of the given formulas could not be parsed. These formulas have a common property in that they have gigantic numbers that exceed what even doubles can store. For example, one out of myriads of numbers in one these formulas has 222 digits. Though the solution to this problem is obviously to support numbers with some sort of BigDecimal instance, there are reasons why this is left as it is, as discussed in the Limitations section 11.1 and 11.3.

10.6 Task Scheduler Tests The TaskSchedulerTest tests the TaskScheduler in 2 sections. The first section tests the threading part of the scheduler and the second section tests a helper method in the scheduler. In the test of the first section, a really big array has been created and ini- tialized with zeroes. The idea is that the array is being filled with numbers that comply with their index in the array. So array[i] = i for all 0 ≤ i ≥ n where n is the size of the array. After the array has been filled, an iteration over the entire array will be done to ensure that every value is in the correct place. First the main thread is timed when filling the array, then the TaskScheduler is timed while filling the array (after setting the array back to all zeroes again). The n TaskScheduler will be given ten jobs where each job sets indices. While test- 10 ing it was surprising at first that the main thread was faster than the scheduler but it can be explained. have the tendency to optimize iterations of for-loops whenever possible. So what is actually happening is that the uses more CPU cores than just the main thread to fill the array. Another reason as to why the scheduler is so slow is because the scheduler only has 4 worker threads, meaning that splitting the entire task into more than 4 jobs is ineffi- cient. It is inefficient because of the locking mechanism when worker threads try to acquire new jobs. In the test of the second section, the helper method TaskScheduler::splitTask is being tested on the additional constraints that you can give to the method.

10.7 Gui Tests Covering a Gui with unit tests is often not very useful since the most interesting interactions happen at the interplay of several components - not in isolation. Instead, the system was internally tested throughout the development of the

59 program. Several common scenarios will be tested. The functionalities to be tested are provided in the following section and were derived from the require- ments. The system must be able to respond to typical as well as non-typical user input to receive a X(see section 12.1). More specific limitations that have been found are discussed in Chapter 11.

10.8 Usability Besides the technical performance of the Gui, several other aspects are impor- tant in the design of a user interface. Nielsen and Molich discuss ten heuristics for user interface design [4] from which the following will be discussed in detail:

10.8.1 Heuristics Visibility of system status The design should always keep users informed about what is going on, through appropriate feedback within a reasonable amount of time.

User control and freedom Users may perform actions by mistake. Is it possible to exit interactions? Does the system support undo and redo?

Consistency and standards Does the system use consistent naming and visualizations to mean the same thing? The system can improve its learnability by maintaining a consistent design.

Error prevention Although error messages are good, the best design tries to prevent problems from occurring in the first place. This can avoid slips and mistakes by the user. Present confirmation options and use safe default values.

Recognition rather than recall Minimize the user’s memory load by mak- ing elements, actions, and options visible.

Flexibility and efficiency of use Shortcuts may speed up the interaction for expert users such that the design can cater to both inexperienced and expe- rienced users. Allow for keyboard shortcuts of common tasks, personalization of the user interface and customization of the workspace to tailor the program to the needs of the user.

Aesthetic and minimalist design Irrelevant information should be avoided. Focus the design on the essentials.

Help and documentation Although it is best if the system is self-explanatory, further documentation may be needed. Ensure that such help documentation is easy to search. Whenever possible, present the documentation in context right at the moment that the user requires it.

60 The quality of execution of these heuristics must be decided by the users of the system. However, since only a limited number of people could test and see the design and real users were not available, it is explained why it is believed these aspects are nonetheless fulfilled.

10.8.2 Evaluation This section evaluates the application on the basis of the previously established heuristics (see section 10.8).

Visibility of system status At all times, there exists a status bar on the top right which displays the most recent information. If a new action is submitted, this information will light up and slowly fade away. This ensures that repeated undos or redos are also seen as such. If the system is at the first or latest stage and the user tries to undo or redo respectively, the system indicates that the earliest/latest stage is currently active. Inside the console, when entering a new inequality the system echos back the requested inequality if it is supported. The entire point of the application is to visualize the current status of the refinement process. This could be seen in various pictures throughout the report. This heuristic is thus more than sufficiently realized.

User control and freedom The system supports an infinite undo-redo sys- tem. Therefore, at any time the user has control over what to split at which point in the history. At no time is the system taking away the ability to con- trol which inequality to see at which step. Therefore, this heuristic can also be considered satisfied.

Consistency and standards The most important aspect of the application is that the user can interpret the region status (true, false, undecided) at any time. All instances use the same standard colors (green, red, grey) such that the system remains consistent with the expectation of the user.

Error prevention Most errors in the system can happen when entering a new formula. Auto-completion of function names and automatic parenthesis matching removes the most common problems that exist. If nonetheless the user types an invalid inequality into the system this will be reported as such directly next to the input.

Recognition rather than recall Almost all options are not hidden inside menus, instead they are present in separate windows. All options the user may want to access are thus always directly available if needed.

Flexibility and efficiency of use Keyboard shortcuts exist to auto-complete functions (tab), undo (ctrl-z), redo (ctrl-y), copy (ctrl-c), cut (ctrl-x) and paste (ctrl-v). These are standard shortcuts on most systems. Besides, the 2D-View

61 supports adding and removing number of ”splitters” using the arrow keys and a split can be executed using the key ”e”. Furthermore, all windows can be resized and docked into different regions to support the workflow of the user, thus maximizing efficiency of use.

Aesthetic and minimalist design Most backgrounds are kept in greys whereas important information such as the status of a region or the currently active history entry are in color or highlighted. This can be seen in various pictures of the application (see e.g. figure 23).

Help and documentation A separate help window is integrated into the design of the user interface. This help box dynamically updates its contents depending on what the user is currently hovering over. It is an unobtrusive-way to give the most relevant information to the user such as a brief description, the available mouse and keyboard shortcuts.

All in all, the usability requirements have been fulfilled. This is to no surprise given that there was an explicit focus on these aspects during the design of the application.

62 11 Limitations 11.1 Parser During testing of the parser, a lot of formulas supplied by the supervisor have been tested and validated. However one of these formulas could not be converted to an internal representation as it resulted in a stack overflow error. This was caused by the formula called zeroconf result n 140 which is located in testing/parser/formulas folder. Essentially this formula does 140 consecutive subtractions with decreasing polynomials that start at power 139. The reason for the stack overflow comes from the fact that the parser is recursive descent with built in operator precedence. A sequence of additions is all put under the same parent in the AST. Meaning that one node will have a depth of 140. There are about 4 calls with each addition (in InEquationWalker) which will grow the stack to about 560 frames. Now you might think that 560 frames are not that many, and most would agree. However the tests were run in debug mode which usually leaves out some compiler optimizations and have decreased stack sizes. Parsing the file with a release build has not been tested but might actually succeed. Additionally, the formula zeroconf result n 120 does actually successfully convert to an internal representation which shows that the parser does not actually make a mistake with infinite recursion somewhere. A solution to this problem could be to change the way the AST is walked. If a certain amount of stack frames is reached, parts of the AST is to be split into a separate AST. For example, after 100 additions, every child from that node need only to be split off. This requires some logic to glue the final result back together but should not be that difficult.

11.2 Parsable Ternary Logic Operators Currently, the only supported operators for ternary logic expressions are the Logical And (&) and the Logical Or (|). Despite all the other supported opera- tors in the back-end, it takes considerably more effort to add all the numerous different operators into the parser. One reason is because the parser is made from scratch as opposed to using an already existing library, adding additional layers of complexity in adding tokens into the grammar. Secondly, other parts of the solver were currently being worked at simultaneously, leaving not enough time for the addition of these operators. Thus, the two previously mentioned operators are the only ones currently tokenizable.

11.3 Numerals A limitation to the team’s design is that their numeral variables are not able to store values that would exceed what a double is able to store in C++. For example, a few formulas sent by the project supervisor has 222 digits in its string. This disables such expressions to be processed by the solver.

63 Although the solution to this problem - being to use types similar to a BigDecimal in place of the current double variables for this purpose - is quite obvious, there are a couple of reasons as to why the team decided to not have the solver support it. The first reason is that it would slow the process of all calculations signifi- cantly. The solver is meant to be able to work on an immense number of regions. Furthermore, it is also meant to evaluate regions repeatedly through the use of nested evaluables and expressions. Not to mention the additional work that it has to do with splitting regions. Given this, supporting numerals of infinite size will exponentially slow down all processes, increasing the latency in internal operations that correlate with the given values. Though this may be a problem which could be solved with careful optimizations, the team felt like the luxury of time for such an implementation could be used for more more important parts of the solver. Secondly, the team considered the frequency of users using values that ex- ceeds what a double could store and established that the effort could be used for other features. It is very unlikely that users would need to input such large values. Even if it is not, it is not yet within the scope of the use case of the solver built. Furthermore, other more pressing features and functionalities are in need of more attention as opposed to something that users are unlikely to utilize.

11.4 Exponentiation with negative base This specific limitation is on calculating exponentiation of intervals. More specif- [e1,e2] ically, the case [b1, b2] where b1 < 0 or, in other words, when the base interval contains any negative value. Exponentiation with negative bases is a problem because of the sign flips. For example (−1)2 = 1 but (−1)3 = −1. This means that, because the exponents can be intervals as well, exponentia- tion is not continuous and therefore cannot be determined. Additionally, the n’th root of a negative number is complex. Because the solver only works with real numbers, this cannot be evaluated. This results in the solver not being able to evaluate expressions like (−1)0.25, the 4th root. Because the solver only works with doubles in intervals, there are special rules for when the exponent of a negative base is a point interval which can be evaluated. If b1 < 0 then the specific exponentiation can be evaluated if and only if e1 = e2 and e1 is close to an integer using epsilon  = 1E−14.

11.5 GUI While testing the GUI a notable specific limitation came to the forefront. When trying to render a lot of regions, some draw calls may be overwritten. This is the case because Dear ImGui supports only 16 bits of indices or 216 = 65536 by default. Therefore the system will currently behave incorrectly given that many regions and displays artifacts. Possible workarounds exist, but since this is primarily a limitation of Dear ImGui/ImPlot the system currently does not

64 address this issue. Furthermore, the 3D-View currently does not support moving the camera away from the origin and selecting regions for splitting is currently limited to the 2D-View(s).

65 12 Evaluation and Conclusion 12.1 Requirements evaluation Below is an overview of the requirements and whether the team managed to implement these requirements.

12.1.1 User requirements The user must be able to:

1. Enter formulas (with 2 variables) X 2. See the inequality (2D) X

3. Evaluate the inequality X 4. Interactively split regions to improve results X 5. To save or store the current state of the process, including the formula and the region splitting X The user should be able to:

1. Make use of monotonic functions (such as logarithms) X 2. Make use of ternary logic X

3. Select regions for splitting X 4. Enter formulas with an arbitrary amount of variables X 5. Specify a tolerance and axis for automatic refinement X The user could be able to:

1. Make use of non-monotonic functions (such as trigonometric functions) X 2. Undo or redo actions X 3. See the inequality in 3D X

4. Highlight multiple regions simultaneously for splitting X 5. Choose when to use a more accurate floating point format 

66 12.1.2 System requirements The system must be able to:

2 1. Parse simple polynomial inequalities (i.e. 2 ∗ x + 3 ∗ x + 8 − y) X

2. Evaluate simple polynomial inequalities (with 2 variables) X 3. Visualise the inequality in 2D X 4. Automatically decide whether a formula holds in a specific region X 5. Read or write the current state of the process X The system should be able to:

1. Evaluate inequalities with more than 2 variables X 2. Evaluate monotonic functions X

3. Parse boolean expressions using ternary logic X 4. Automatically split a region given a tolerance X The system could be able to:

1. Evaluate non-monotonic functions X

2. Visualise the inequality in 3D X 3. Visualise regions in a tree-like manner X 4. Speed up evaluation using GPGPU  5. Evaluate with an arbitrary amount of floating point precision 

From the overview, it can be seen that all the necessary features were im- plemented, all of the ”should”-features and half of the ”could” features. Some ”could”-features were started but could not be finished in time, namely the implementation of a more precise floating point format as well as speeding up computing using GPGPU. These in conjunction with the 3D-View were consid- ered bonus features and took lower priority compared to improving stability of existing features.

12.2 Reflection on Planning Before actually starting the project, the group was aware that the planning is adequately optimistic. During the first three weeks of the project, the group kept up with the planning. However, with increasing program complexity, it was discovered that the group needed to spend more time on bug fixing than initially planned. Consequently, some of the features had to be postponed by one or two weeks. Additionally, the group only started to work on the design

67 report in week eight, which is three weeks later than planned. Writing the report took longer than expected, which means, that the group focused less on the remaining features and bundled their resources on the report. The core project and most of the smaller add-on features were all planned in the first half of the project. When finishing those features, test cases to have a decent coverage of unit tests were first focused on. More about this is covered in the section 10. The unit testing revealed several issues which required fixing. Spending more time than planned on writing unit tests was a wise choice. It uncovered several issues and bugs in the interval arithmetic library as well as in the evaluator. Thus, the use of unit tests increased the stability and reliability of our software dramatically. Before starting to use unit tests, code was manually tested just after writing it. As a consequence, the team had to derive from the planned timeline to focus on the quality of the application. Therefore, the larger optional features like the three dimensional plotting, the arbitrary precision floating point integration and the concurrency features were started one to two weeks late. After all, the GPGPU implementation (concurrency features) were researched but not fully integrated. Furthermore, the three dimensional plotting was fully implemented and supported. Nevertheless, it lacks a few usability features such as some improved navigation features. Lastly, the integration of the MPFR library (arbitrary precision floating point) was added to the interval arithmetic library. Also, its quality was ensured with a decent coverage of unit tests. Nev- ertheless, the time was too short to integrate it into the user interface and more importantly into the evaluation component. To sum it up, the optimistic planning could not be fully adhered to due to quality assurance of the resulting application. The group decided that this is a desirable trade-off.

12.3 Reflection on Group Work During the project, each group member had different responsibilities. They are as follows:

• Mart: Parser, Evaluation, threading (GPU)

• Rifqi: Refinement, Ternary Logic • Neil: Project setup, Most of the Graphical User Interface, 3D view-port • Max: Interval Arithmetic Library, Memory Pool, Some parts of the Graph- ical User Interface

The work load of the project was arguably distributed evenly. Though, it cannot be denied that some parts require more research and reading than others. Furthermore, the above responsibility distribution list is not entirely accurate, as it often happens that members take part in others’ responsibilities when assistance is required.

68 Members also did not have a hard time adjusting to the work environment. Each member of the group has known each other for a couple years, making the kick-off of the project be very brief. This enabled for the commencement of the implementation to start sooner than was planned. Furthermore, the group rarely requires a full-group meeting. Usually, meet- ings are only required by the members whose parts are correlated with the topic the meeting caller inquires. Though in the case of a full-group meeting, as the team is very flexible, it is done when members are known to be available. This flexibility significantly increases the morale of the group. Moreover, the willingness to help one another improves productivity signif- icantly. This is especially necessary for a project whose domain roots from an unfamiliar field. Furthermore, the fact that only some of the members have any experience with the programming language used in the project is another reason why this group dynamic is relevant. Though this has sparked some obstacles. Members of the group differ in their preferred way to write code: Some lean towards more Object-Oriented approaches and others incline towards the C-style found in the libraries which was used as well. For instance, the GUI framework Dear ImGui and bgfx have a C-like code style despite the fact that both of these libraries are C++ libraries. Therefore, the GUI naturally also had to be written in a similar manner. The different code style has caused multiple minor clashes when integrating different parts made by different people. The solution, in the end, was that members tried to adjust to others’ coding styles, even though this was not their preference. Still, certain parts of later applications were more convenient to be implemented in certain styles, but this is communicated beforehand to the rest of the team.

12.4 Future Work When recapitulating the last ten weeks the team can conclude that they de- veloped an advanced and stable product which meets most of the requirements listed in section 3. In the ten weeks the group managed to implement most of the feature, but some are still missing or were not included in the scope of this project. First of all, some time was spent on integrating the arbitrary precision float- ing point library MPFR in the implemented interval arithmetic library. The group also worked on unit tests for most of the operations. Despite the progress, they did not manage to integrate the MPFR parts into the evaluator. The idea was to provide a graphical component, in which the user sets the desired pre- cision. Based on the input, the evaluation either uses regular doubles. If it exceeds the precision of regular doubles the evaluation automatically switches to interval arithmetic operations using MPFR. A proper integration of MPFR also resolves the issue of numerical limitations which is described in section 11.3. Finishing the integration of MPFR in the evaluation component is future work the group definitely suggest, since parts of it are already implemented. Moreover, the limitations provide a good guideline for future work. Al- though, the parser is stable for regular sized inequalities, it fails to parse ex-

69 traordinary large inequalities. Such a large input causes a stack overflow error. The parser design is based on the principle of recursive descent parser. This means, the parser walks the tree recursively. Recursive parse tree walking bears the risk of stack overflow errors. The team would suggest to spend more time on the parser to fix these issues. One solution how to fix the parser is mentioned in the limitation section 11.1. Other parser architectures are briefly introduced in the research section. 5.4. Next, the current product is limited on intervals in the extended real num- ber space. Nonetheless, interval arithmetic is not limited to real number arith- metic. It can go far beyond that. As an example, some trigonometric operations were added as described in section 9.6. One can extend the capabilities of the parser by more mathematical operations, such as the natural exponential func- x tion f(x) = e , logarithms of different bases such as log2(x). Lastly, interval arithmetic can also cover the entirety of the complex number space. Support for complex numbers in the inequality visualizer makes the product even more complete. Lastly, one can always think about UI and UX improvements. One of those UX improvements is the navigation in the plotting views. For instance, the 3D plot will always focus on the origin. One can zoom in and out and rotate the view, but there is no feature to select another center point of view. Furthermore, small quality of life features could be considered like being able to display Greek and Latin letters, changing the tree view and 3D-View in color when the style changes etc. Adding such features would further improve the user friendliness of the product. Also, expanding the grid types and allowing the user to add custom grid types besides the five pre-defined ones could be beneficial to the UX of the program. Finally, the features initially discarded like the integration with the iSAT3 format and support of symbolic simplifications to achieve numerically more stable forms can be considered as valuable additions to the program. To sum it up, the core product already provides a thorough basis. Future work considers some improvements and some refactoring existing features. In general, the future work options focus on extending the mathematical capabili- ties of the product.

12.5 Conclusion After reflecting on the planning and the group work, reflection on the prod- uct in general needs discussing. First of all, none of the members had any proper preexisting knowledge of the programming language C++, neither had a member studied the theory of interval arithmetic beforehand. All of this, was new theory for the team. Nevertheless, the product allowed to apply a diverse set of skills which the team developed during our bachelor’s studies pre- viously. An object-oriented programming paradigm was used. A parser made from scratch was made for mathematical expressions in which the team could apply the knowledge of programming paradigms. Furthermore, they managed

70 to use a few tools of previous study years when designing a project. Especially the MoSCoW method was helpful to set the priorities for the features right. In addition, how helpful the use of concurrency and parallelism would be was researched. After all, the group is very satisfied with the resulting application. The product is stable, gives accurate and mathematically correct results and even includes some usability improvements and features which were not initially on the agenda. For someone who knows interval arithmetic, the user interface is easy to learn and intuitive to use. Different themes and a flexible layout gives the user the ability to customize the look and feel of the product to someone’s need. Additionally, the team attempted to maintain the code to be modular and readable. Whenever possible, they kept the functions short and understandable. Using the Dear ImGui library was also an interesting experience. Dear ImGui introduces a new GUI paradigm - the immediate mode GUI paradigm. To sum it up, the project was full of new skills, frameworks, programming languages and mathematical theory. Nonetheless, members adapted quickly to the new theory and delivered a solid product. Despite the fact of having a very specific target group, the group still hope that the product will be used by some people.

71 References

[1] Karsten Scheibler et al. iSAT3. https://projects.informatik.uni- freiburg.de/projects/isat3/. [2] David M Burton. “Elementary Number Theory”. In: (2010), pp. 17–19. [3] Dinis Cruz. AST (Abstract Syntax Tree). url: https://medium.com/ @dinis.cruz/ast- abstract- syntax- tree- 538aa146c53b. (accessed: 4.12.2021). [4] Rolf Molich and Jakob Nielsen. “Improving a human-computer dialogue”. In: Communications of the ACM 33.3 (1990), pp. 338–348. [5] Johann Muszynski. A small, dependency-free node editor extension for dear imgui. https://github.com/Nelarius/imnodes. [6] Operator precedence parsing. url: https : / / www . javatpoint . com / operator-precedence-parsing. [7] Pankaj Patel. Types of parsers in compiler design. url: https://www. geeksforgeeks.org/types-of-parsers-in-compiler-design/. [8] Karsten Peeters. tree.hh: an STL-like C++ tree class. https://github. com/kpeeters/tree.hh/. [9] Evan Pezent. Advanced 2D Plotting for Dear ImGui. https://github. com/epezent/implot. [10] Stefan Ratschan. RSolver User Manual. http://rsolver.sourceforge. net/documentation/manual.pdf. [11] Joey de Vries. Learn OpenGL. https://learnopengl.com/Getting- started/Hello-Triangle.

72 Appendices

A Images of Entire Application

Figure 25: Showcase of the entire application for (x2 + y2 > 0.3)&(z < 0.6). Here the 2D-View corresponds with the top surface of the cube.

Figure 26: Shows multiple 2D-Views, the Regions table and the Regions tree and re-arranged windows. Highlighted regions in region table and 2D-Views.

73