Reputation: 730
I need to store a complex number in a Django model. For those who forget, that simply means Z=R+jX
where R and X are real numbers representing the real and imaginary components of the complex. There will be individual numbers, as well as lists that need to be stored. My searches so far haven't provided a good solution for lists, so I intend to let the database handle the list as individual records.
I see two options for storing a complex number:
1) create a custom field: class Complex(models.CharField)
This would allow me to customize all aspects of the field, but that is a lot of extra work for validation if it is to be done properly. The major upside is that a single number is represented by a single field in the table.
2) let each complex number be represented by a row, with a float
field for the real part, R, and another float
field for the imaginary part, X. The downside to this approach is that I would need to write some converters that will create a complex number from the components, and vice versa. The upside is that the database will just see it as another record.
Surely this issue has been resolved in the past, but I can't find any good references, never mind one particular to Django.
This is my first crack at the field, it is based on another example I found that involved a few string manipulations. What isn't clear to me is how and where various validations should be performed (such as coercing a simple float into a complex number by adding +0j). I intend to add form functionality as well, so that the field behaves like a float field, but with additional restrictions or requirements.
I have not tested this code yet, so there may be issues with it. It is based on the code from an answer in this SO question. It appears after running the code that some changes took place in method names.
What is the most efficient way to store a list in the Django models?
class ComplexField(models.CharField):
description = 'A complex number represented as a string'
def __init__(self, *args, **kwargs):
kwargs['verbose_name'] = 'Complex Number'
kwargs['max_length'] = 64
kwargs['default'] = '0+0j'
super().__init__(*args, **kwargs)
def to_python(self, value):
if not value: return
if isinstance(value, complex):
return value
return complex(value)
def get_db_prep_value(self, value):
if not value: return
assert(isinstance(value, complex))
return str(item)[1:-1]
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
Upvotes: 2
Views: 859
Reputation: 168966
To be honest, I'd just split the complex number into two float/decimal fields and add a property for reading and writing as a single complex number.
I came up with this custom field that ends up as a split field on the actual model and injects the aforementioned property too.
contribute_to_class
is called deep in the Django model machinery for all the fields that are declared on the model. Generally, they might just add the field itself to the model, and maybe additional methods like get_latest_by_...
, but here we're hijacking that mechanism to instead add two fields we construct within, and not the actual "self" field itself at all, as it does not need to exist as a database column. (This might break something, who knows...) Some of this mechanism is explained here in the Django wiki.
The ComplexProperty
class is a property descriptor, which allows customization of what happens when the property it's "attached as" into an instance is accessed (read or written). (How descriptors work is a little bit beyond the scope of this answer, but there's a how-to guide in the Python docs.)
NB: I did not test this beyond running migrations, so things may be broken in unexpected ways, but at least the theory is sound. :)
from django.db import models
class ComplexField(models.Field):
def __init__(self, **kwargs):
self.field_class = kwargs.pop('field_class', models.FloatField)
self.field_kwargs = kwargs.pop('field_kwargs', {})
super().__init__(**kwargs)
def contribute_to_class(self, cls, name, private_only=False):
for field in (
self.field_class(name=name + '_real', **self.field_kwargs),
self.field_class(name=name + '_imag', **self.field_kwargs),
):
field.contribute_to_class(cls, field.name)
setattr(cls, name, ComplexProperty(name))
class ComplexProperty:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if not instance:
return self
real = getattr(instance, self.name + '_real')
imag = getattr(instance, self.name + '_imag')
return complex(real, imag)
def __set__(self, instance, value: complex):
setattr(instance, self.name + '_real', value.real)
setattr(instance, self.name + '_imag', value.imag)
class Test(models.Model):
num1 = ComplexField()
num2 = ComplexField()
num3 = ComplexField()
The migration for this looks like
migrations.CreateModel(
name="Test",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("num1_real", models.FloatField()),
("num1_imag", models.FloatField()),
("num2_real", models.FloatField()),
("num2_imag", models.FloatField()),
("num3_real", models.FloatField()),
("num3_imag", models.FloatField()),
],
)
so as you can see, the three ComplexField
s are broken down into six FloatField
s.
Upvotes: 1
Reputation: 5730
Regarding custom fields, you've probably found the relevant part in the Django documentation already.
Whether a custom field (or a custom database type, see below) is worth the trouble really depends on what you need to do with the stored numbers. For storage and some occasional pushing around, you can go with the easiest sane solution (your number two as enhanced by Tobit).
With PostgreSQL, you have to possibility to implement custom types directly in the database, including operators. Here's the relevant part in the Postgres docs, complete with a complex numbers example, no less.
Of course you then need to expose the new type and the operators to Django. Quite a bit of work, but then you could do arithmetics with individual fields right in the database using Django ORM.
Upvotes: 1
Reputation: 406
If your expression every time like R + jX you can make the following class
class ComplexNumber(models.Model):
real_number = models.FloatField('Real number part')
img_number = models.FloatFoeld('Img number part')
def __str__(self):
return complex(self.real_number, self.img_number)
and handle the outcome string with python see here
If you have multiple real and img part you can handle this with foreign keys or ManyToMany Fields. This maybe depend on your need.
Upvotes: 1