diff --git a/core/shared/src/main/scala/vecxt/array.extensions.scala b/core/shared/src/main/scala/vecxt/array.extensions.scala new file mode 100644 index 0000000..17d9df9 --- /dev/null +++ b/core/shared/src/main/scala/vecxt/array.extensions.scala @@ -0,0 +1,297 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vexct +import vexct.Limits.Limit +import vexct.Retentions.Retention + +import scala.util.chaining.* + +enum LossCalc: + case Agg, Occ +end LossCalc + + +extension (vec: Array[Boolean]) + inline def countTrue: Int = + var sum = 0 + for i <- 0 until vec.length do if vec(i) then sum = sum + 1 + sum + end countTrue + + inline def &&(thatIdx: Array[Boolean]): Array[Boolean] = + val result: Array[Boolean] = new Array[Boolean](vec.length) + for i <- 0 until vec.length do result(i) = vec(i) && thatIdx(i) + result + end && + + inline def ||(thatIdx: Array[Boolean]): Array[Boolean] = + val result: Array[Boolean] = new Array[Boolean](vec.length) + for i <- 0 until vec.length do result(i) = vec(i) || thatIdx(i) + result + end || + + // def copy: Array[Boolean] = + // val copyOfThisVector: Array[Boolean] = new Array[Boolean](vec.length) + // var i = 0 + // while i < vec.length do + // copyOfThisVector(i) = vec(i) + // i = i + 1 + // end while + // copyOfThisVector + // end copy +end extension + +extension (vec: Array[Double]) + + def idx(index : Array[Boolean]) = + val trues = index.countTrue + val newVec = new Array[Double](trues) + var j = 0 + for i <- 0 to trues do + //println(s"i: $i || j: $j || ${index(i)} ${vec(i)} ") + if index(i) then + newVec(j) = vec(i) + j += 1 + end for + newVec + end idx + + def increments: Array[Double] = + val out = new Array[Double](vec.length) + out(0) = vec(0) + var i = 1 + while i < vec.length do + out(i) = vec(i) - vec(i - 1) + i = i + 1 + end while + out + end increments + + inline def stdDev: Double = { + // https://www.cuemath.com/data/standard-deviation/ + val mu = vec.mean + val diffs_2 = vec.map( num => Math.pow(num - mu, 2) ) + Math.sqrt( diffs_2.sum / (vec.length - 1 ) ) + } + + inline def mean: Double = vec.sum / vec.length + + inline def sum: Double = { + var sum = 0.0 + var i = 0; while (i < vec.length) { + sum = sum + vec(i) + i = i + 1 + } + sum + } + + def cumsum = + var i = 1 + while i < vec.length do + vec(i) = vec(i - 1) + vec(i) + i = i + 1 + end while + end cumsum + + def - (vec2: Array[Double]) = + val out = new Array[Double](vec.length) + var i = 0 + while i < vec.length do + out(i) = vec(i) - vec2(i) + i = i + 1 + end while + out + end - + + def -= (vec2: Array[Double]) : Unit = + var i = 0 + while i < vec.length do + vec(i) = vec(i) - vec2(i) + i = i + 1 + end while + end -= + + def + (vec2: Array[Double]) = + val out = new Array[Double](vec.length) + var i = 0 + while i < vec.length do + out(i) = vec(i) + vec2(i) + i = i + 1 + end while + out + end + + + def += (vec2: Array[Double]) : Unit = + var i = 0 + while i < vec.length do + vec(i) = vec(i) + vec2(i) + i = i + 1 + end while + end += + + def *= (d: Double) = + var i = 0 + while i < vec.length do + vec(i) = vec(i) * d + i = i + 1 + end while + vec + end *= + + def * (d: Double) = + val out = new Array[Double](vec.length) + var i = 0 + while i < vec.length do + out(i) = vec(i) * d + i = i + 1 + end while + out + end * + + inline def <(num: Double): Array[Boolean] = + logicalIdx((a, b) => a < b, num) + + inline def <=(num: Double): Array[Boolean] = + logicalIdx((a, b) => a <= b, num) + + inline def >(num: Double): Array[Boolean] = + logicalIdx((a, b) => a > b, num) + + inline def >=(num: Double): Array[Boolean] = + logicalIdx((a, b) => a >= b, num) + + inline def logicalIdx( + inline op: (Double, Double) => Boolean, + inline num: Double + ): Array[Boolean] = + val n = vec.length + val idx = new Array[Boolean](n) + var i = 0 + while i < n do + if op(vec(i), num) then idx(i) = true + i = i + 1 + end while + idx + end logicalIdx + + + /* + +Retention and limit are known constants + +In excel f(x) = min(max(x - retention, 0), limit)) + +The implementation takes advantage of their existence or not, to optimise the number of operations required. + + */ + inline def reinsuranceFunction(limitOpt: Option[Limit], retentionOpt: Option[Retention]): Unit = + (limitOpt, retentionOpt) match + case (Some(limit), Some(retention)) => + var i = 0; + while i < vec.length do + val tmp = vec(i) - retention + if tmp < 0.0 then vec(i) = 0.0 + else if tmp > limit then vec(i) = limit.toDouble + else vec(i) = tmp + end if + i = i + 1 + end while + + case (None, Some(retention)) => + var i = 0; + while i < vec.length do + val tmp = vec(i) - retention + if tmp < 0.0 then vec(i) = 0.0 + else vec(i) = tmp + i = i + 1 + end while + + case (Some(limit), None) => + var i = 0; + while i < vec.length do + val tmp = vec(i) + if tmp > limit then vec(i) = limit.toDouble + else vec(i) = tmp + i = i + 1 + end while + + case (None, None) => () + + end reinsuranceFunction + + /* + +Retention and limit are known constants + +In excel f(x) = if(x < retention, 0, if(x > limit, limit, x) + + */ + inline def franchiseFunction(inline limitOpt: Option[Limit], inline retentionOpt: Option[Retention]): Unit = + (limitOpt, retentionOpt) match + case (None, None) => () + + case (Some(limit), Some(retention)) => + var i = 0; + val maxLim = limit.toDouble + retention.toDouble + while i < vec.length do + val tmp = vec(i) + if tmp < retention then vec(i) = 0.0 + else if tmp > maxLim then vec(i) = maxLim + else vec(i) = tmp + end if + i = i + 1 + end while + + case (Some(limit), None) => + var i = 0; + while i < vec.length do + val tmp = vec(i) + if tmp > limit.toDouble then vec(i) = limit.toDouble + else vec(i) = tmp + end if + i = i + 1 + end while + case (None, Some(retention)) => + var i = 0; + while i < vec.length do + val tmp = vec(i) + if tmp > retention.toDouble then vec(i) = tmp + else vec(i) = 0.0 + end if + i = i + 1 + end while + end franchiseFunction + +end extension + +extension (vec: Array[Array[Double]]) + inline def horizontalSum: Array[Double] = + val out = new Array[Double](vec.head.length) + var i = 0 + while i < vec.head.length do + var sum = 0.0 + var j = 0 + while j < vec.length do + sum += vec(j)(i) + //pprint.pprintln(s"j : $j i : $i vecij : ${vec(j)(i)} out : ${out(i)} sum : $sum") + j = j + 1 + end while + out(i) = sum + i = i + 1 + end while + out +end extension \ No newline at end of file diff --git a/core/shared/src/main/scala/vecxt/limit.rpt.scala b/core/shared/src/main/scala/vecxt/limit.rpt.scala new file mode 100644 index 0000000..dad60e6 --- /dev/null +++ b/core/shared/src/main/scala/vecxt/limit.rpt.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vexct + +/* +These opaque types prevent boxing? + */ +import vexct.Retentions.* + +object Limits: + opaque type Limit = Double + + object Limit: + inline def apply(d: Double): Limit = d + end Limit + + extension (x: Limit) inline def toDouble: Double = x + + extension (in: Double) + inline def >(l: Limit): Boolean = in > l + inline def +(l: Retention): Double = in + l.toDouble + end extension +end Limits diff --git a/core/shared/src/main/scala/vecxt/retention.rpt.scala b/core/shared/src/main/scala/vecxt/retention.rpt.scala new file mode 100644 index 0000000..ff335d2 --- /dev/null +++ b/core/shared/src/main/scala/vecxt/retention.rpt.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vexct + + +object Retentions: + opaque type Retention = Double + + object Retention: + inline def apply(d: Double): Retention = d + end Retention + + extension (x: Retention) inline def toDouble: Double = x + + extension (loss: Double) + inline def -(l: Retention): Double = loss - l + inline def <(l: Retention): Boolean = loss < l + end extension +end Retentions \ No newline at end of file diff --git a/tests/shared/src/test/scala/array.tests.scala b/tests/shared/src/test/scala/array.tests.scala new file mode 100644 index 0000000..e69de29 diff --git a/tests/shared/src/test/scala/arrayExtensions.test.scala b/tests/shared/src/test/scala/arrayExtensions.test.scala new file mode 100644 index 0000000..dea0611 --- /dev/null +++ b/tests/shared/src/test/scala/arrayExtensions.test.scala @@ -0,0 +1,155 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vexct + + +import scala.util.chaining.* + + +class ArrayExtensionSuite extends munit.FunSuite: + + lazy val v_fill = Array.tabulate(5)(i => i.toDouble) + + test("Array horizontal sum") { + val v1 = Array[Double](1.0, 2.0, 3.0) + val v2 = v1 * 2.0 + val v3 = v1 * 3.0 + + val l = Array(v1, v2, v3) + val summed = l.horizontalSum + + assert(summed(0) == 1 + 2 + 3) + assert(summed(1) == 2 + 4 + 6) + assert(summed(2) == 3 + 6 + 9) + + } + + test("cumsum") { + val v1 = Array[Double](1.0, 2.0, 3.0).tap(_.cumsum) + assert(v1(0) == 1) + assert(v1(1) == 3) + assert(v1(2) == 6) + } + + test("Array += ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + v1 += Array[Double](3.0, 2.0, 1.0) + + assertEqualsDouble(v1(0) , 4, 0.00001) + assertEqualsDouble(v1(1) , 4, 0.00001) + assertEqualsDouble(v1(2) , 4, 0.00001) + + } + + test("Array + ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + val v2 = Array[Double](3.0, 2.0, 1.0) + + val v3 = v1 + v2 + + assertEqualsDouble(v3(0) , 4, 0.00001) + assertEqualsDouble(v3(1) , 4, 0.00001) + assertEqualsDouble(v3(2) , 4, 0.00001) + + } + + test("Array -= ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + v1 -= Array[Double](3.0, 2.0, 1.0) + + assertEqualsDouble(v1(0) , -2, 0.00001) + assertEqualsDouble(v1(1) , 0, 0.00001) + assertEqualsDouble(v1(2) , 2, 0.00001) + + } + + test("Array - ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + val v2 = Array[Double](3.0, 2.0, 1.0) + + val v3 = v1 - v2 + + assertEqualsDouble(v3(0) , -2, 0.00001) + assertEqualsDouble(v3(1) , 0, 0.00001) + assertEqualsDouble(v3(2) , 2, 0.00001) + } + + test("Array *= ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + v1 *= 2 + + assertEqualsDouble(v1(0) , 2, 0.00001) + assertEqualsDouble(v1(1) , 4, 0.00001) + assertEqualsDouble(v1(2) , 6, 0.00001) + + } + + test("Array * ") { + val v1 = Array[Double](1.0, 2.0, 3.0) + + val v2 = v1 * 2 + + assertEqualsDouble(v1(0) , 1, 0.00001) + assertEqualsDouble(v2(1) , 4, 0.00001) + assertEqualsDouble(v2(2) , 6, 0.00001) + } + + test("<=") { + val v_idx2 = v_fill < 2.5 + assertEquals(v_idx2.countTrue, 3) + } + + test("<") { + val v_idx2 = v_fill < 3.0 + assertEquals(v_idx2.countTrue, 3) + } + + test(">") { + val v_idx2 = v_fill > 2.5 + assertEquals(v_idx2.countTrue, 2) + } + + test(">=") { + val v_idx2 = v_fill >= 3.0 + assertEquals(v_idx2.countTrue, 2) + } + + test("&&") { + val v_idx2 = (v_fill < 3.0) && (v_fill > 1.0) + assertEquals(v_idx2.countTrue, 1) + } + + test("||") { + val v_idx2 = (v_fill < 3.0) || (v_fill > 1.0) + assertEquals(v_idx2.countTrue, 5) + + val v_idx3 = (v_fill < 1.0) || (v_fill > 4.0) + assertEquals(v_idx3.countTrue, 1) + } + + + test("Array indexing") { + val v1 = Array[Double](1.0, 2.0, 3.0) + val vIdx = Array[Boolean](true, false, true) + val afterIndex = v1.idx(vIdx) + + assertEquals(afterIndex.length, 2) + assertEqualsDouble(afterIndex.head, 1, 0.0001) + assertEqualsDouble(afterIndex.last, 3.0, 0.0001) + } + +end ArrayExtensionSuite diff --git a/tests/shared/src/test/scala/makeLayer.scala b/tests/shared/src/test/scala/makeLayer.scala new file mode 100644 index 0000000..dde59bc --- /dev/null +++ b/tests/shared/src/test/scala/makeLayer.scala @@ -0,0 +1,16 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/tests/shared/src/test/scala/rpt.test.scala b/tests/shared/src/test/scala/rpt.test.scala new file mode 100644 index 0000000..8b281cc --- /dev/null +++ b/tests/shared/src/test/scala/rpt.test.scala @@ -0,0 +1,150 @@ +/* + * Copyright 2023 quafadas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vexct + +import Retentions.* +import Limits.* + +extension [A <: AnyRef](o: A) def some = Some(o) + +class ReinsurancePricingSuite extends munit.FunSuite: + + test("reinsurance function - ret and limit") { + val v = Array[Double](8, 11, 16) + v.reinsuranceFunction(Some(Limit(5.0)), Some(Retention(10.0))) + assert(v(0) == 0.0) + assert(v(1) == 1.0) + assert(v(2) == 5.0) + } + test("reinsurance function - ret only") { + val v = Array[Double](8, 11, 16) + v.reinsuranceFunction(None, Some(Retention(10.0))) + assert(v(0) == 0.0) + assert(v(1) == 1.0) + assert(v(2) == 6.0) + } + test("reinsurance function - limit only") { + val v = Array[Double](8, 11, 16) + v.reinsuranceFunction(Some(Limit(15.0)), None) + assert(v(0) == 8.0) + assert(v(1) == 11.0) + assert(v(2) == 15.0) + } + test("reinsurance function - no ret or limit") { + val v = Array[Double](8, 11, 16) + v.reinsuranceFunction(None, None) + assert(v(0) == 8.0) + assert(v(1) == 11.0) + assert(v(2) == 16.0) + } + + test("franchise function - ret and limit") { + val v = Array[Double](8, 11, 16) + v.franchiseFunction(Some(Limit(5.0)), Some(Retention(10.0))) + assert(v(0) == 0.0) + assert(v(1) == 11.0) + assert(v(2) == 15.0) + } + + test("franchise function - ret only") { + val v = Array[Double](8, 11, 16) + v.franchiseFunction(None, Some(Retention(10.0))) + assert(v(0) == 0.0) + assert(v(1) == 11.0) + assert(v(2) == 16.0) + } + + test("franchise function - Limit only") { + val v = Array[Double](8, 11, 16) + v.franchiseFunction(Some(Limit(10.0)), None) + assert(v(0) == 8.0) + assert(v(1) == 10.0) + assert(v(2) == 10.0) + } + + test("franchise function - No ret or limit") { + val v = Array[Double](8, 11, 16) + v.franchiseFunction(None, None) + assert(v(0) == 8.0) + assert(v(1) == 11.0) + assert(v(2) == 16.0) + } + + // test("Simple Agg 10") { + // val l_10Agg = makeLayer(1.0, aggLimit = 10.0.some) + // val losses = Array[Double](7, 2, 2, 2) + // val coveredLosses = l_10Agg.coveredLosses(losses) + // assert(coveredLosses(0) == 7) + // assert(coveredLosses(1) == 2) + // assert(coveredLosses(2) == 1) + // assert(coveredLosses(3) == 0) + // } + + // test("Simple Agg ret 10") { + // val l_10Agg = makeLayer(1.0, aggRetention = 10.0.some) + // val losses = Array[Double](7, 2, 2, 2) + // val coveredLosses = l_10Agg.coveredLosses(losses) + // assertEqualsDouble(coveredLosses(0), 0, 0.0001) + // assert(coveredLosses(1) == 0) + // assert(coveredLosses(2) == 1.0) + // assert(coveredLosses(3) == 2.0) + // } + + // test("Simple occ ret 10") { + // val l_10Agg = makeLayer(1.0, occRetention = 10.0.some) + // val losses = Array[Double](9, 11) + // val coveredLosses = l_10Agg.coveredLosses(losses) + // assert(coveredLosses(0) == 0) + // assert(coveredLosses(1) == 1) + // } + + // test("share") { + // val l_10Agg = makeLayer(0.5) + // val losses = Array[Double](1, 2) + // val coveredLosses = l_10Agg.coveredLosses(losses) + // assert(coveredLosses(0) == 0.5) + // assert(coveredLosses(1) == 1) + // } + + // test("Simple Occ 10") { + // val l_10Agg = makeLayer(1.0, occLimit = 10.0.some) + // val losses = Array[Double](8, 12) + // val coveredLosses = l_10Agg.coveredLosses(losses) + // assert(coveredLosses(0) == 8) + // assert(coveredLosses(1) == 10) + // } + + // test("Simple Agg 10 xs 5") { + // val l_Agg_10xs5 = makeLayer(1.0, aggLimit = 5.0.some, aggRetention = 5.0.some) + // val losses = Array[Double](7, 2, 2, 2) + // val coveredLosses = l_Agg_10xs5.coveredLosses(losses) + // assert(coveredLosses(0) == 2) + // assert(coveredLosses(1) == 2) + // assert(coveredLosses(2) == 1) + // assert(coveredLosses(3) == 0) + // } + + // test("Simple Occ 10 xs 5") { + // val l_Occ_10xs5 = makeLayer(1.0, occLimit = 10.0.some, occRetention = 5.0.some) + // // pprint.pprintln(l_Occ_10xs5) + // val losses = Array[Double](3, 11, 16) + // val coveredLosses = l_Occ_10xs5.coveredLosses(losses) + // assert(coveredLosses(0) == 0) + // assert(coveredLosses(1) == 6) + // assert(coveredLosses(2) == 10) + // } +end ReinsurancePricingSuite