Binary operators for cubes

Started by Kirill Panin8 months ago4 messages
#1Kirill Panin
kipanin@edu.hse.ru
1 attachment(s)

Hi hackers!

The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.

Best Regards,
Kirill Panin

Attachments:

v1-0001-Add-binary-operators-for-cubes.patchtext/x-patch; charset=UTF-8; name=v1-0001-Add-binary-operators-for-cubes.patchDownload
From 98a8111a83a3443287061af9461b3e03e9dcb6e6 Mon Sep 17 00:00:00 2001
From: Kirill Panin <kipanin@edu.hse.ru>
Date: Wed, 14 May 2025 15:30:48 +0300
Subject: [PATCH v1] Add binary operators for cubes

---
 contrib/cube/Makefile           |   2 +-
 contrib/cube/cube--1.5--1.6.sql |  56 ++++++++++++++
 contrib/cube/cube.c             | 128 ++++++++++++++++++++++++++++++++
 contrib/cube/cube.control       |   2 +-
 contrib/cube/expected/cube.out  |  61 +++++++++++++++
 contrib/cube/meson.build        |   1 +
 contrib/cube/sql/cube.sql       |  12 +++
 doc/src/sgml/cube.sgml          |  50 +++++++++++++
 8 files changed, 310 insertions(+), 2 deletions(-)
 create mode 100644 contrib/cube/cube--1.5--1.6.sql

diff --git a/contrib/cube/Makefile b/contrib/cube/Makefile
index dfb0d806e4b..ff08d17903f 100644
--- a/contrib/cube/Makefile
+++ b/contrib/cube/Makefile
@@ -9,7 +9,7 @@ OBJS = \
 
 EXTENSION = cube
 DATA = cube--1.2.sql cube--1.2--1.3.sql cube--1.3--1.4.sql cube--1.4--1.5.sql \
-	cube--1.1--1.2.sql cube--1.0--1.1.sql
+	cube--1.1--1.2.sql cube--1.0--1.1.sql cube--1.5--1.6.sql
 PGFILEDESC = "cube - multidimensional cube data type"
 
 HEADERS = cubedata.h
diff --git a/contrib/cube/cube--1.5--1.6.sql b/contrib/cube/cube--1.5--1.6.sql
new file mode 100644
index 00000000000..9eb21f174e2
--- /dev/null
+++ b/contrib/cube/cube--1.5--1.6.sql
@@ -0,0 +1,56 @@
+/* contrib/cube/cube--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION cube UPDATE TO '1.6'" to load this file. \quit
+
+CREATE FUNCTION cube_add(cube, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_sub(cube, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_mul_cf(cube, float8)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_mul_fc(float8, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_div(cube, float8)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+-- Add coordinate-wise binary operators
+
+CREATE OPERATOR + (
+	LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_add,
+	COMMUTATOR = '+'
+);
+
+CREATE OPERATOR - (
+	LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_sub
+);
+
+-- Add coordinate-wise binary operators with scalars
+
+CREATE OPERATOR / (
+	LEFTARG = cube, RIGHTARG = float8, PROCEDURE = cube_div
+);
+
+CREATE OPERATOR * (
+	LEFTARG = cube, RIGHTARG = float8, PROCEDURE = cube_mul_cf,
+	COMMUTATOR = '*'
+);
+
+CREATE OPERATOR * (
+	LEFTARG = float8, RIGHTARG = cube, PROCEDURE = cube_mul_fc,
+	COMMUTATOR = '*'
+);
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 8d3654ab7aa..47e568cd958 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -91,6 +91,11 @@ PG_FUNCTION_INFO_V1(cube_distance);
 PG_FUNCTION_INFO_V1(distance_chebyshev);
 PG_FUNCTION_INFO_V1(cube_is_point);
 PG_FUNCTION_INFO_V1(cube_enlarge);
+PG_FUNCTION_INFO_V1(cube_add);
+PG_FUNCTION_INFO_V1(cube_sub);
+PG_FUNCTION_INFO_V1(cube_div);
+PG_FUNCTION_INFO_V1(cube_mul_cf);
+PG_FUNCTION_INFO_V1(cube_mul_fc);
 
 /*
 ** For internal use only
@@ -109,6 +114,8 @@ bool		g_cube_internal_consistent(NDBOX *key, NDBOX *query, StrategyNumber strate
 */
 static double distance_1D(double a1, double a2, double b1, double b2);
 static bool cube_is_point_internal(NDBOX *cube);
+static NDBOX *cube_binop_helper(NDBOX *a, NDBOX *b);
+static NDBOX *cube_alloc_shape(NDBOX *a);
 
 
 /*****************************************************************************
@@ -1911,3 +1918,124 @@ cube_c_f8_f8(PG_FUNCTION_ARGS)
 	PG_FREE_IF_COPY(cube, 0);
 	PG_RETURN_NDBOX_P(result);
 }
+
+static NDBOX *
+cube_alloc_shape(NDBOX *a) {
+	NDBOX	   *result;
+	int			dim = DIM(a);
+	int			size;
+
+	if (IS_POINT(a)) {
+		size = POINT_SIZE(dim);
+		result = (NDBOX *) palloc0(size);
+		SET_POINT_BIT(result);
+	} else {
+		size = CUBE_SIZE(dim);
+		result = (NDBOX *) palloc0(size);
+	}
+
+	SET_VARSIZE(result, size);
+	SET_DIM(result, dim);
+
+	return result;
+}
+
+NDBOX *
+cube_binop_helper(NDBOX *a, NDBOX *b)
+{
+	if (DIM(a) != DIM(b))
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("cubes have different lengths: %d != %d", DIM(a), DIM(b))));
+
+	if (IS_POINT(a) != IS_POINT(b))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("it's POINTless to add these cubes: %d != %d", IS_POINT(a), IS_POINT(b))));
+
+	return cube_alloc_shape(a);
+}
+
+/*
+ * Function returns coordinate-wise sum of cubes.
+ */
+Datum
+cube_add(PG_FUNCTION_ARGS)
+{
+	NDBOX	   *a = PG_GETARG_NDBOX_P(0);
+	NDBOX	   *b = PG_GETARG_NDBOX_P(1);
+	NDBOX	   *result = cube_binop_helper(a, b);
+	int			i;
+	int			n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+	for (i = 0; i < n; i++)
+		result->x[i] = a->x[i] + b->x[i];
+
+	PG_RETURN_NDBOX_P(result);
+}
+
+/*
+ * Function returns coordinate-wise difference of cubes.
+ */
+Datum
+cube_sub(PG_FUNCTION_ARGS)
+{
+	NDBOX	   *a = PG_GETARG_NDBOX_P(0);
+	NDBOX	   *b = PG_GETARG_NDBOX_P(1);
+	NDBOX	   *result = cube_binop_helper(a, b);
+	int			i;
+	int			n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+	for (i = 0; i < n; i++)
+		result->x[i] = a->x[i] - b->x[i];
+
+	PG_RETURN_NDBOX_P(result);
+}
+
+/*
+ * Functions return scaled cube.
+ */
+Datum
+cube_div(PG_FUNCTION_ARGS)
+{
+	NDBOX	   *a = PG_GETARG_NDBOX_P(0);
+	double		s = PG_GETARG_FLOAT8(1);
+	NDBOX	   *result = cube_alloc_shape(a);
+	int			i;
+	int			n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+	for (i = 0; i < n; i++)
+		result->x[i] = a->x[i] / s;
+
+	PG_RETURN_NDBOX_P(result);
+}
+
+Datum
+cube_mul_cf(PG_FUNCTION_ARGS)
+{
+	NDBOX	   *a = PG_GETARG_NDBOX_P(0);
+	double		s = PG_GETARG_FLOAT8(1);
+	NDBOX	   *result = cube_alloc_shape(a);
+	int			i;
+	int			n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+	for (i = 0; i < n; i++)
+		result->x[i] = a->x[i] * s;
+
+	PG_RETURN_NDBOX_P(result);
+}
+
+Datum
+cube_mul_fc(PG_FUNCTION_ARGS)
+{
+	double		s = PG_GETARG_FLOAT8(0);
+	NDBOX	   *a = PG_GETARG_NDBOX_P(1);
+	NDBOX	   *result = cube_alloc_shape(a);
+	int			i;
+	int			n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+	for (i = 0; i < n; i++)
+		result->x[i] = a->x[i] * s;
+
+	PG_RETURN_NDBOX_P(result);
+}
diff --git a/contrib/cube/cube.control b/contrib/cube/cube.control
index 50427ec1170..7797f7e08d4 100644
--- a/contrib/cube/cube.control
+++ b/contrib/cube/cube.control
@@ -1,6 +1,6 @@
 # cube extension
 comment = 'data type for multidimensional cubes'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/cube'
 relocatable = true
 trusted = true
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 47787c50bd9..698750955de 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1973,3 +1973,64 @@ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upp
 (15 rows)
 
 RESET enable_indexscan;
+-- Test of binary operators
+SELECT '(-7,7)'::cube + '(-1,1)'::cube;
+ ?column? 
+----------
+ (-8, 8)
+(1 row)
+
+SELECT '(-7,7)'::cube - '(-1,1)'::cube;
+ ?column? 
+----------
+ (-6, 6)
+(1 row)
+
+SELECT '(-1,2)'::cube * 2;
+ ?column? 
+----------
+ (-2, 4)
+(1 row)
+
+SELECT '(-2,4)'::cube / 2;
+ ?column? 
+----------
+ (-1, 2)
+(1 row)
+
+SELECT 2 * '(-1,2)'::cube;
+ ?column? 
+----------
+ (-2, 4)
+(1 row)
+
+SELECT '(-7,7),(2,9)'::cube + '(-1,1),(5,12)'::cube;
+    ?column?     
+-----------------
+ (-8, 8),(7, 21)
+(1 row)
+
+SELECT '(-7,7),(2,9)'::cube - '(-1,1),(5,12)'::cube;
+     ?column?     
+------------------
+ (-6, 6),(-3, -3)
+(1 row)
+
+SELECT '(-1,2),(2,9)'::cube * 2;
+    ?column?     
+-----------------
+ (-2, 4),(4, 18)
+(1 row)
+
+SELECT '(-2,4),(3,7)'::cube / 2;
+      ?column?      
+--------------------
+ (-1, 2),(1.5, 3.5)
+(1 row)
+
+SELECT 2 * '(-1,2),(2,9)'::cube;
+    ?column?     
+-----------------
+ (-2, 4),(4, 18)
+(1 row)
+
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index fd3c057f469..32d88a0416e 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -40,6 +40,7 @@ install_data(
   'cube--1.2--1.3.sql',
   'cube--1.3--1.4.sql',
   'cube--1.4--1.5.sql',
+  'cube--1.5--1.6.sql',
   kwargs: contrib_data_args,
 )
 
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index eec90d21ee3..e819eacbfe8 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -436,3 +436,15 @@ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by rig
 SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
 SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
 RESET enable_indexscan;
+
+-- Test of binary operators
+SELECT '(-7,7)'::cube + '(-1,1)'::cube;
+SELECT '(-7,7)'::cube - '(-1,1)'::cube;
+SELECT '(-1,2)'::cube * 2;
+SELECT '(-2,4)'::cube / 2;
+SELECT 2 * '(-1,2)'::cube;
+SELECT '(-7,7),(2,9)'::cube + '(-1,1),(5,12)'::cube;
+SELECT '(-7,7),(2,9)'::cube - '(-1,1),(5,12)'::cube;
+SELECT '(-1,2),(2,9)'::cube * 2;
+SELECT '(-2,4),(3,7)'::cube / 2;
+SELECT 2 * '(-1,2),(2,9)'::cube;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index 0fb70807486..19355a1041f 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -218,6 +218,56 @@
         Computes the Chebyshev (L-inf metric) distance between the two cubes.
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>cube</type> <literal>+</literal> <type>cube</type>
+        <returnvalue>cube</returnvalue>
+       </para>
+       <para>
+        Computes the coordinate-wise sum of two cubes.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>cube</type> <literal>-</literal> <type>cube</type>
+        <returnvalue>cube</returnvalue>
+       </para>
+       <para>
+        Computes the coordinate-wise difference of two cubes.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>cube</type> <literal>*</literal> <type>float8</type>
+        <returnvalue>cube</returnvalue>
+       </para>
+       <para>
+        Computes the coordinate-wise multiplication of a cube by a scalar value.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>float8</type> <literal>*</literal> <type>cube</type>
+        <returnvalue>cube</returnvalue>
+       </para>
+       <para>
+        Computes the coordinate-wise multiplication of a cube by a scalar value.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>cube</type> <literal>/</literal> <type>float8</type>
+        <returnvalue>cube</returnvalue>
+       </para>
+       <para>
+        Computes the coordinate-wise division of a cube by a scalar value.
+       </para></entry>
+      </row>
      </tbody>
    </tgroup>
   </table>

base-commit: 2c0ed86d393670c7054d051490063de771f1791e
-- 
2.49.0

#2Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Kirill Panin (#1)
Re: Binary operators for cubes

On Thu, 15 May 2025 at 16:16, Kirill Panin <kipanin@edu.hse.ru> wrote:

Hi hackers!

The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.

I don't think that your definition of addition and subtraction makes
sense for type cube. The docs say:

It does not matter which order the opposite corners of a cube are
entered in. The cube functions automatically swap values if needed to
create a uniform “lower left — upper right” internal representation.

Thus, for example, the following 2 cubes are equal:

SELECT '(0,0), (10,10)'::cube = '(10,0), (0,10)'::cube;

?column?
----------
t
(1 row)

However, with your definition of addition in terms of simple pointwise
addition of coordinates, the results of adding this cube to another
are different, depending on the order of the corners:

SELECT '(0,0), (10,10)'::cube + '(1,2), (3,4)'::cube,
'(10,0), (0,10)'::cube + '(1,2), (3,4)'::cube;

?column? | ?column?
-----------------+-----------------
(1, 2),(13, 14) | (11, 2),(3, 14)
(1 row)

which are not equal. It's a pretty odd form of addition in which a+c
differs from b+c when a and b are the equal.

One could define point+point and point+cube addition to be translation
by the values from the point, and then it would work correctly if the
corners of the cube were reversed. That makes a certain amount of
geometrical sense, since then multiplication and addition are just
scale and translate.

I imagine that people using the cube extension for vectors are using
zero-volume cubes (points), so translation-addition would work the
same as normal vector addition in that case. However, I doubt that
cube is ever going to make a good general-purpose type for vectors. It
would be somewhat odd to allow dot and cross products of cubes, for
example.

Regards,
Dean

#3Kirill Panin
kipanin@edu.hse.ru
In reply to: Dean Rasheed (#2)
Re: Binary operators for cubes

Hi,

Thanks for your response.
Do you think it makes sense to disallow cube+cube operations and only
allow cube+point or point+point instead? Dot and cross products would be
similarly only defined for point+point if someone were to implement them.
I'll submit a v2 if this is an acceptable solution.

Regards,
Kirill

Show quoted text

On 7/2/25 13:57, Dean Rasheed wrote:

On Thu, 15 May 2025 at 16:16, Kirill Panin <kipanin@edu.hse.ru> wrote:

Hi hackers!

The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.

I don't think that your definition of addition and subtraction makes
sense for type cube. The docs say:

It does not matter which order the opposite corners of a cube are
entered in. The cube functions automatically swap values if needed to
create a uniform “lower left — upper right” internal representation.

Thus, for example, the following 2 cubes are equal:

SELECT '(0,0), (10,10)'::cube = '(10,0), (0,10)'::cube;

?column?
----------
t
(1 row)

However, with your definition of addition in terms of simple pointwise
addition of coordinates, the results of adding this cube to another
are different, depending on the order of the corners:

SELECT '(0,0), (10,10)'::cube + '(1,2), (3,4)'::cube,
'(10,0), (0,10)'::cube + '(1,2), (3,4)'::cube;

?column? | ?column?
-----------------+-----------------
(1, 2),(13, 14) | (11, 2),(3, 14)
(1 row)

which are not equal. It's a pretty odd form of addition in which a+c
differs from b+c when a and b are the equal.

One could define point+point and point+cube addition to be translation
by the values from the point, and then it would work correctly if the
corners of the cube were reversed. That makes a certain amount of
geometrical sense, since then multiplication and addition are just
scale and translate.

I imagine that people using the cube extension for vectors are using
zero-volume cubes (points), so translation-addition would work the
same as normal vector addition in that case. However, I doubt that
cube is ever going to make a good general-purpose type for vectors. It
would be somewhat odd to allow dot and cross products of cubes, for
example.

Regards,
Dean

#4Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Kirill Panin (#3)
Re: Binary operators for cubes

On Fri, 4 Jul 2025 at 13:07, Kirill Panin <kipanin@edu.hse.ru> wrote:

Thanks for your response.
Do you think it makes sense to disallow cube+cube operations and only
allow cube+point or point+point instead? Dot and cross products would be
similarly only defined for point+point if someone were to implement them.
I'll submit a v2 if this is an acceptable solution.

Having thought about this a little more, I'm not convinced that doing
any of this is a good idea.

The problem is a cube is not a vector, and adding more functions or
operators to try to make it behave like one seems to be going in the
wrong direction. Perhaps it would help to re-examine the use cases,
and whether using the cube extension is the right thing in the first
place.

Regards,
Dean