## `piecewise` -- the domain of conditionally defined objects

#### Introduction

`piecewise(`[condition1, object1], [condition2, object2], ...`)` generates a conditionally defined object that equals `object1` if `condition1` is satisfied, `object2` if `condition2` is satisfied, etc.

#### Creating Elements

`piecewise([condition1, object1], [condition2, object2], ...)`

#### Parameters

`condition1, condition2, ...` | - | Boolean constants or expressions representing logical formulas
`object1, object2, ...` | - | arbitrary objects

#### Side Effects

Properties of identifiers set by `assume` are taken into account.

#### Related Functions

`_case`, `_if`, `assume`, `bool`, `is`

#### Details

- `piecewise` differs from the `if` and `case` branching statements in two ways. First, the property mechanism is used to decide the truth of the conditions. Hence the result depends on the properties of the identifiers that appear in the conditions. Second, `piecewise` treats conditions mathematically, while `if` and `case` evaluate them syntactically. Cf. example 2.

- A pair `[condition, object]` is called a branch. If `condition` is provably false, then the branch is discarded altogether. If `condition` is provably true, then `piecewise` returns `object`. If none of the conditions is provably true, an object of type `piecewise` is created containing all branches that have not been discarded.

  If all conditions are provably false, or if no branch is given, then `piecewise` returns `undefined`. Cf. example 1.

- The conditions need not be exhaustive, nor need they exclude each other. If you substitute values for the occurring parameters, it may happen that all conditions become false, but it may also happen that more than one condition becomes true.
- If several conditions are simultaneously true, `piecewise` returns the first object defined under a condition that is recognized to be true. The user has to ensure that the objects corresponding to the true conditions all have the same mathematical meaning. You cannot rely on the system to recognize the first mathematically true condition as true.
- Whenever an object of type `piecewise` is evaluated, the truth of the conditions is checked again for the current values and the current properties of the identifiers involved. This may be used to simplify the result of a computation under various different assumptions.
- Conditionally defined objects may be nested: both conditions and objects may be conditionally defined themselves. `piecewise` automatically de-nests (``flattens'') such objects. For example, ``if A then (if B then C)'' becomes ``if A and B then C''. Cf. example 7.

- Arithmetical and set-theoretic operations work for conditionally defined objects, provided these operations are defined for all objects contained in the branches. If `f` is such an operation and `p1, p2, ...` are conditionally defined objects, then `f(p1, p2, ...)` is the conditionally defined object consisting of all branches of the form `[condition1 and condition2 and ..., f(object1, object2, ...)]`, where
  `[condition1, object1]` is a branch of `p1`, `[condition2, object2]` is a branch of `p2`, etc. This can also be understood as follows: applying `f` commutes with any assignment to free parameters in the conditions.

  Conditionally defined objects can also be mixed with other objects in such operations: If, e.g., `p1` is not a conditionally defined object, it is handled like a conditionally defined object with the only branch `[TRUE, p1]`.

  Cf. examples 3 and 6.

- In particular, the previous remark holds for unary operators and functions with one argument: if called with a conditionally defined object as argument, they are mapped to the objects in each branch. Cf. example 5.

#### Method _in: membership with `piecewise` on the left hand side

- `_in(piecewise p, set S)`
- - This method returns a logical formula that is equivalent to ```p` is an element of `S`''.
  - This method overloads `_in`.

#### Method contains: apply the function `contains` to the objects in all branches

- `contains(piecewise p, any a)`
- - This method applies the function `contains` with second argument `a` to the objects in all branches of `p`. The result is in general again a conditionally defined object.

  - This method overloads the function `contains`. The objects in all branches must be valid first arguments for `contains`.

#### Method diff: (partial) differentiation

- `diff(piecewise p <, identifier x...>)`
- - This method differentiates the objects in all branches of `p` with respect to the given variables, starting with the leftmost one.
  - If no variables are given, `p` is returned.
  - This method overloads `diff`.

#### Method discont: determine the discontinuities of a piecewise defined function

- `discont(piecewise p, identifier x <, domain F>)`
- - This method returns a superset of the discontinuities of `p` regarded as a function depending on `x`, namely, the union of the results of applying `discont` to the objects in all branches of `p`, plus the boundary points of the conditions of `p` with respect to `x`.

  - The objects in all branches of `p` must be arithmetical expressions.

  - This method overloads `discont`.
  - The optional third parameter has the same meaning as for the function `discont`.

#### Method piecewise::disregardPoints: heuristic for simplifying conditions

- `piecewise::disregardPoints(piecewise p)`
- - Apply the heuristic ``consider equalities to be false'' to conditions that are recognized neither as true nor as false. Since the set of zeroes of an equation usually has Lebesgue measure zero, this heuristic tends to produce a simpler case distinction that is equivalent to the original one for almost all values of the parameters. Cf. example 6.

#### Method expand: apply the function `expand` to the objects in all branches

- `expand(piecewise p)`
- - This method overloads `expand`.

#### Method piecewise::getElement: get any element of a conditionally defined set

- `piecewise::getElement(piecewise p)`
- - This method returns an element that is common to the objects in all branches of `p`. All such objects must represent sets.
  - The result is `FAIL` if no such common element can be found.
  - This method overloads the function `solvelib::getElement`.

#### Method has: test for the existence of a subobject

- `has(piecewise p, any a)`
- - This method tests whether `a` appears syntactically somewhere in the conditions or the objects of `p`; it returns `TRUE` if this is the case, and `FALSE` otherwise.
  - This method overloads `has`.

#### Method int: definite and indefinite integration of a piecewise defined function

- `int(piecewise p, identifier x <, range r>)`
- - If no range is given, this method computes the indefinite integral of `p`, where `p` is regarded as a piecewise defined function of `x`. It applies the function `int` to the objects in all branches of `p`.
  - If a range `a..b` is given, this method computes the definite integral of `p` when `x` runs through that range.
  - This method overloads `int`.

#### Method piecewise::isFinite: test whether a piecewise defined set is finite

- `piecewise::isFinite(piecewise p)`
- - This method returns `TRUE` if the objects in all branches of `p` are finite sets, and it returns `FALSE` if the objects in all branches of `p` are infinite sets. Otherwise, it returns `UNKNOWN`.

  - This method overloads `solvelib::isFinite`.

#### Method normal: apply the function `normal` to the objects in all branches

- `normal(piecewise p)`
- - This method overloads `normal`.

#### Method piecewise::restrict: impose an additional condition

- `piecewise::restrict(any p, condition C)`
- - If `p` is not a conditionally defined object, this method creates the conditionally defined object with a single branch `[C, p]`. If `p` is conditionally defined, each condition `cond` in `p` is replaced by `cond and C`.

#### Method piecewise::set2expr: membership with `piecewise` on the right hand side

- `piecewise::set2expr(piecewise p, identifier x)`
- - This method returns a logical formula with free parameter `x` that is equivalent to ```x` is an element of `p`''.
  - The objects in all branches of `p` must represent sets.
  - This method overloads the system function `_in`.

#### Method simplify: simplify a conditionally defined object

- `simplify(piecewise p)`
- - This method performs the following simplifications:
    - First, `simplify` is applied to the objects in all branches.
    - Branches defining the same object are collected.
    - If the condition of some branch implies that a free parameter is constant, the parameter is replaced by that constant in the object of that branch.
    Cf. example 7.
  - This method overloads `simplify`.

#### Method solve: solve a conditionally defined equation or inequality

- `solve(piecewise p, identifier x <, option1, option2, ...>)`
- - This method solves `p` for the variable `x`. The objects in all branches of `p` must be either equations, inequalities, or arithmetical expressions; each arithmetical expression `e` is replaced by an equation `e = 0`.

  - For each branch `[condition, object]` of `p`, with `object` being an equation or inequality, the method determines the set of all values `x` such that both `condition` and `object` become true mathematically, and returns the union of all obtained sets. The return value may be a conditionally defined set.

  - This method overloads the function `solve`. See the corresponding help page for a description of the available options and an overview of the types of sets that may be returned.

#### Method piecewise::solveConditions: isolate a given identifier in all conditions

- `piecewise::solveConditions(piecewise p, identifier x)`
- - This method rewrites each condition of `p` containing `x` in the form ```x in S`'' for some appropriate set `S`.

#### Method piecewise::Union: union of a system of sets

- `piecewise::Union(piecewise p, identifier x, set indexset)`
- - This method returns the set of all elements of elements of `p`, where `p` is regarded as a system of sets parameterized by `x` and `x` runs through all elements of `indexset`.

  - The objects in all branches of `p` must represent sets.
  - For each branch `[condition, object]` of `p`, this method does the following. It substitutes for `x` in `object` all those values from `indexset` satisfying `condition` and takes the union over all obtained sets. Then it returns the union over the resulting sets for all branches.

  - This method overloads the function `solvelib::Union`.

#### Method _concat: merge piecewise objects

- `_concat(piecewise p...)`
- - This method returns a conditionally defined object comprising the branches of all arguments.
  - This method overloads `_concat`.

#### Method piecewise::condition: the condition in a specific branch

- `piecewise::condition(piecewise p, positive integer i)`
- - This method returns the condition of the `i`th branch of `p`. Cf. example 4.

#### Method piecewise::expression: the object in a specific branch

- `piecewise::expression(piecewise p, positive integer i)`
- - This method returns the object of the `i`th branch of `p`. Cf. example 4.

#### Method piecewise::insert: insert a branch at a given position

- `piecewise::insert(piecewise p, branch b, positive integer i)`
- - This method returns `p` with the branch `b` inserted at position `i`.
  - `b` can either be a branch extracted from another conditionally defined object using `op`, or a list `[condition, object]`.
  - The integer `i` must not exceed the number of branches of `p` plus one.
  - Cf. example 4.

#### Method map: apply a function to the objects in all branches

- `map(piecewise p, any f <, any a...>)`
- - For each branch `[condition, object]` of `p`, `object` is replaced by `f(object <, a...>)`.

  - This method overloads `map`.

#### Method piecewise::mapConditions: apply a function to the conditions in all branches

- `piecewise::mapConditions(piecewise p, any f <, any a...>)`
- - For each branch `[condition, object]` of `p`, `condition` is replaced by `f(condition <, a...>)`.

#### Method piecewise::mapMap: apply the function `map` to the objects in all branches

- `piecewise::mapMap(any p, any f <, any a...>)`
- - For each branch `[condition, object]` of `p`, `object` is replaced by `map(object, f <, a...>)`.

  - If `p` is not a conditionally defined object, just `map(p, f <, a...>)` is returned.

#### Method piecewise::remove: remove a branch

- `piecewise::remove(piecewise p, positive integer i)`
- - This method returns a conditionally defined object obtained from `p` by deleting the `i`th branch. Cf. example 4.

#### Method piecewise::selectConditions: select branches depending on their condition

- `piecewise::selectConditions(piecewise p, any f <, any a...>)`
- - This method works like the function `select` with the selection criterion given by `f` applied to the conditions of `p`. It returns the piecewise object derived from `p` by removing every branch `[condition, object]` for which `f(condition <, a...>)` does not yield `TRUE`.

  - For every condition in `p`, `f(condition <, a...>)` must return a Boolean constant.

  - If none of the conditions satisfies the selection criterion, `undefined` is returned.

#### Method piecewise::splitConditions: split branches depending on conditions

- `piecewise::splitConditions(piecewise p, any f <, any a...>)`
- - This method works like the function `split` with the splitting criterion given by `f` applied to the conditions of `p`. It returns a list of three conditionally defined objects, comprising those branches `[condition, object]` of `p` for which `f(condition <, a...>)` yields `TRUE`, `FALSE`, and `UNKNOWN`, respectively.

    If, for some of the three Boolean values, no branch yields that value, then the returned list contains `undefined` instead of a conditionally defined object with zero branches at the corresponding position.

  - For every condition in `p`, `f(condition <, a...>)` must return a Boolean constant.

  - Cf. example 4.

#### Method subs: substitution

- `subs(piecewise p, substitution s...)`
- - This method performs the substitution(s) `s` in both the conditions and the objects of `p`.

  - This method overloads the function `subs`. The calling syntax is identical to that function; cf. the corresponding help page for a description of the various types that are allowed for `s`.

#### Method zip: apply a binary operation pointwise

- `zip(any p1, any p2, any f)`
- - If both `p1` and `p2` are conditionally defined objects, then this method returns the conditionally defined object comprising all branches of the form `[condition1 and condition2, f(object1, object2)]`, where `[condition1, object1]` is a branch of `p1` and `[condition2, object2]` is a branch of `p2`.

  - If we regard conditionally defined objects as functions from the set A of parameter values to a set B of objects, this method implements the canonical extension of the binary operation f on B to the binary operation g on the set B^A of all functions from A to B via (g(p1, p2))(a) = f(p1(a), p2(a)) for all a in A.

  - If only one of the first two arguments--`p1`, say--is of type `piecewise`, then each branch `[condition, object]` of `p1` is replaced by `[condition, f(object, p2)]`.

  - If neither `p1` nor `p2` are of type `piecewise`, then `piecewise::zip(p1, p2, f)` returns `f(p1, p2)`.

  - This method overloads `zip`.

#### Example 1

We define `f` as the characteristic function of the interval [0,1]:

    >> f := x -> piecewise([x < 0 or x > 1, 0], [x >= 0 or x <= 1, 1])

            x -> piecewise([x < 0 or 1 < x, 0], [0 <= x or x <= 1, 1])

None of the conditions can be evaluated to `TRUE` or `FALSE`, unless more is known about the variable `x`. When we evaluate `f` at some point, the conditions are checked again:

    >> f(0), f(2), f(I)

                                  1, 0, undefined

#### Example 2

`piecewise` performs a case analysis using the property mechanism. It checks whether the given conditions are mathematically true or false; it may also decide that not enough information is available. In the following example, it cannot be decided whether `a` is zero as long as no assumptions on `a` have been made:

    >> delete a:
       p := piecewise([a = 0, 0], [a <> 0, 1/a])

                                /             1           \
                       piecewise| 0 if a = 0, - if a <> 0 |
                                \             a           /

In contrast, `if`-statements evaluate the conditions syntactically: `a=0` is technically false since the identifier `a` and the integer `0` are different objects:

    >> if a = 0 then 0 else 1/a end

                                         1
                                         -
                                         a

Moreover, `piecewise` takes properties of identifiers into account:

    >> assume(a = 0):
       p;
       delete a, p:

                                         0

#### Example 3

Conditionally defined objects can be created by rewriting special functions:

    >> f := rewrite(sign(x), piecewise)

                   /
          piecewise| 1 if 0 < x, -1 if x < 0, 0 if x = 0,
                   |
                   \

                      x                          \
             -------------------- if not x in R_ |
                   2        2 1/2                |
             (Im(x)  + Re(x) )                   /

In contrast to MuPAD, most people like to regard `sign` as a function defined for real numbers only. You might therefore want to restrict the domain of `f`:

    >> f := piecewise::restrict(f, x in R_)

          piecewise(1 if x in ]0, infinity[, -1 if x in ]-infinity, 0[,

             0 if x in {0})

Conditionally defined arithmetical expressions allow roughly the same operations as ordinary arithmetical expressions. The result of an arithmetical operation is only defined at those points where all of the arguments are defined:

    >> f + piecewise([x < 2, 5])

              piecewise(6 if 0 < x and x < 2, 4 if x < 0, 5 if x = 0)

#### Example 4

There are several methods for extracting branches, conditions, and objects. Consider the following conditionally defined object:

    >> f := piecewise([x > 0, 1], [x < -3, x^2])

                                               2
                        piecewise(1 if 0 < x, x  if x < -3)

You can extract a specific condition or object:

    >> piecewise::condition(f, 1), piecewise::expression(f, 2)

                                             2
                                     0 < x, x

The function `op` extracts whole branches:

    >> op(f, 1)

                                    1  if 0 < x

You can form another piecewise defined object out of those branches for which the condition satisfies a given selection criterion, or split the input into two piecewise defined objects, as the system functions `select` and `split` do it for lists:

    >> piecewise::selectConditions(f, has, 0)

                               piecewise(1 if 0 < x)

    >> piecewise::splitConditions(f, has, 0)

                                               2
            [piecewise(1 if 0 < x), piecewise(x  if x < -3), undefined]

You can also create a copy of `f` with some branches added or removed:

    >> piecewise::remove(f, 1)

                                         2
                              piecewise(x  if x < -3)

    >> piecewise::insert(f, [x > -3 and x < 0, sin(x)], 2)

                                                             2
          piecewise(1 if 0 < x, sin(x) if x < 0 and -3 < x, x  if x < -3)

#### Example 5

Most unary functions are overloaded for `piecewise` by mapping them to the objects in all branches of the input. This can also be achieved using `map`:

    >> f := piecewise([x >= 0, arcsin(x)], [x < 0, arccos(x)]):
       sin(f)

                                             2     1/2
                  piecewise(x if 0 <= x, (- x  + 1)    if x < 0)

    >> map(f, sin)

                                             2     1/2
                  piecewise(x if 0 <= x, (- x  + 1)    if x < 0)

This causes the following problem. If one of the conditions becomes true, e.g., by some assumption on `x`, then `f` evaluates to an object that is not of type `piecewise`. Applying `sin` then still works, but `map` maps the sine function to the operands of `f`. Hence `map` should be used with care:

    >> assume(x < 0):
       sin(f);
       map(f, sin);

                                          2 1/2
                                    (1 - x )

                                  arccos(sin(x))

    >> delete x:

The converse problem occurs if you want to apply the function `map` to the objects in all branches. The method `mapMap` should be used for this purpose.

#### Example 6

Sets may also be conditionally defined. Such sets are sometimes returned by `solve`:

    >> S := solve(a*x = 0, x)

                       piecewise(C_ if a = 0, {0} if a <> 0)

The usual set-theoretic operations work for such sets:

    >> S intersect Dom::Interval(3, 5)

                     piecewise(]3, 5[ if a = 0, {} if a <> 0)

Sometimes it is interesting to exclude the ``rare cases'' which only cover a small set of parameter values:

    >> piecewise::disregardPoints(S)

                                        {0}

#### Example 7

Consider the following case distinction:

    >> p1 := piecewise([a > 0, a^2], [a <= 0, -a^2]):
       p2 := piecewise([b > 0, a + b], [b = 0, p1 + b], [b < 0, a + b])

                                         2
          piecewise(a + b if 0 < b, b + a  if 0 < a and b = 0,

                  2
             b - a  if b = 0 and a <= 0, a + b if b < 0)

Note that the system has moved the case analysis done in `p1` to the top level automatically. However, some simplifications are still possible: the branches `b>0` and `b<0` can be collected, and in the case `b=0` the identifier `b` may be replaced by the value `0`:

    >> simplify(p2)

                                      2
          piecewise(a + b if b <> 0, a  if 0 < a and b = 0,

                2
             - a  if b = 0 and a <= 0)

#### Background

- The operands of a conditionally defined object, i.e., the branches, are pairs consisting of a condition and the object valid under that condition. They are of a special data type `stdlib::branch`.

- Methods overloading system functions always assume that they have been called via overloading, and that there is some conditionally defined object among their arguments. All other methods do not assume that one of their arguments is of type `piecewise`. This simplifies the use of `piecewise`: it is always allowed to enter `p:=piecewise(...)` and to call some method of `piecewise` with `p` as argument. You need not care about the special case where `p` is not of type `piecewise` because some condition in its definition is true or all conditions are false.

#### Changes

- new function

Do you have questions or comments?
Copyright © SciFace Software GmbH & Co. KG 2000