6. Converting to OpenFTS2

In March 2025, the OpenFTS python API was significantly updated in order to be more modular and more object oriented. This new interface (OpenFTS2), replaces the old interface (OpenFTS1) and requires user to update all input files. This page describes the details of this update and the updates that are required.

**

We first will import the openfts module as before

[1]:
import openfts

In order to compare OpenFTS1 and OpenFTS2, we will explicitly create an OpenFTS1 object and an OpenFTS2 object

[2]:
fts1 = openfts.OpenFTS1()
print(fts1)
<openfts.openfts1.OpenFTS1 object at 0x7f345f3455d0>
/home/jl4346/LequieuLab/OpenFTS-new-python-interface/build/python/openfts/openfts1.py:19: UserWarning: OpenFTS1 has been deprecated. Consider using OpenFTS2 instead
  warnings.warn("OpenFTS1 has been deprecated. Consider using OpenFTS2 instead")
[3]:
fts2 = openfts.OpenFTS2()
print(fts2)
<openfts.openfts2.OpenFTS2 object at 0x7f345f345510>

Note that The OpenFTS1 object throws a warning and will eventually be removed.

By default the OpenFTS() constructor creates an OpenFTS2 object

[4]:
fts = openfts.OpenFTS()
print(fts)
<openfts.openfts2.OpenFTS2 object at 0x7f345f354110>

In OpenFTS1, the cell was specified using the cell method of the OpenFTS1 object:

[5]:
fts1.cell(cell_scale=1.0, cell_lengths=[4.3], dimension=1, length_unit='Rg')

In OpenFTS1, if the user wanted to modify any of these arguements after the cell method was called, the user needed to tweak the fts1.params dictionary:

[6]:
print("Original: ", fts1.params['cell'])
fts1.params['cell']['cell_scale'] = 5.0
print("Updated: ", fts1.params['cell'])
Original:  {'cell_scale': 1.0, 'cell_lengths': [4.3], 'dimension': 1, 'length_unit': 'Rg'}
Updated:  {'cell_scale': 5.0, 'cell_lengths': [4.3], 'dimension': 1, 'length_unit': 'Rg'}

In OpenFTS2, this structure has been replaced, by instead having the user create and store a Cell object:

[7]:
fts2.cell = openfts.Cell(cell_scale=1.0, cell_lengths=[4.3], dimension=1, length_unit='Rg')
print(fts2.cell)
<openfts.cell.Cell object at 0x7f34e7673210>

The elements of the OpenFTS2 cell object are directly accessible as member variable and can be directly modified :

[8]:
print("Original cell_scale: ", fts2.cell.cell_scale)
fts2.cell.cell_scale = 5.0
print("Updated cell_scale: ", fts2.cell.cell_scale)
Original cell_scale:  1.0
Updated cell_scale:  5.0

For convenience, in OpenFTS2, the elements of this cell object can be printed using the to_dict() member method:

[9]:
fts2.cell.to_dict()
[9]:
{'cell_scale': 5.0, 'cell_lengths': [4.3], 'dimension': 1, 'length_unit': 'Rg'}

For users who have previously developed workflows using OpenFTS1, these workflows can be converted to OpenFTS2 be replacing the nested dictionary structure previously found in fts1.params with the use of dot operators. As an example, to access the exchange fields filename from output you would use

[10]:
fts1.output_default()
filename1 = fts1.params["output"]["fields"]["exchange_fields"]["file"]
print("filename (fts1) = ", filename1)

fts2.output = openfts.Output()
filename2 = fts2.output.fields.exchange_fields.file
print("filename (fts2) = ", filename2)
filename (fts1) =  fields.dat
filename (fts2) =  fields.dat

For convenience, a tool to convert from OpenFTS1 to OpenFTS2 is provided in OpenFTS/python/tools/convert_to_openfts2.py.

This tool changes all of the function calls (fts.cell, fts.driver, etc.) to use the new objects.

WARNING: The conversion tool only works with simple files. If the code changes fts.params, for example, that will have to be changed manually to use OpenFTS2.

For comparison, the input files for simple diblock simulation in OpenFTS1 and OpenFTS2 are shown below

6.1. OpenFTS2 (the default)

[11]:
fts = openfts.OpenFTS()

# initialize the cell
fts.cell = openfts.Cell(cell_scale=1.0,cell_lengths=[4.3],dimension=1,length_unit='Rg')

# initialize the fields to use 32 grid points
fts.field_layout = openfts.FieldLayout(npw=[32],random_seed=1)

# initialize the driver (including the field updater and variable cell)
scft = openfts.driver.SCFT(dt=5.0,nsteps=4000,output_freq=50,stop_tolerance=1e-05,)
scft.field_updater = openfts.field_updater.EMPEC(update_order='simultaneous',adaptive_timestep=False,lambdas=[1.0, 1.0])
scft.variable_cell = openfts.VariableCell(lambda_=0.2,stop_tolerance=0.0001,update_freq=20,shape_constraint='none')
fts.driver = scft

# number of beads in chain
N = 50

# Create the model with chiN=20 and BC=100
chiAB = openfts.model.ChiAB( Nref=N,bref=1.0,chiN=20.,inverse_BC=0.01)

# initialize the model fields
# mu_minus is initialized with a gaussian centered at zero, mu_plus is random
chiAB.init_fields['mu_minus'] = openfts.init_field.Gaussian(height=1.0,ngaussian=1,width=0.1,centers=[0])
chiAB.init_fields['mu_plus'] = openfts.init_field.Random(mean=0.0,stdev=1.0)
fts.model = chiAB

# create a molecule, here a diblock consisting of N beads with fA=0.5
diblock = openfts.molecule.PolymerLinearDiscrete(nbeads=N,nblocks=2,block_fractions=[0.5, 0.5],
                                                 block_species=['A', 'B'],volume_frac=1.0)
fts.molecules.append(diblock)

# Set the operators we seek to output
# for SCFT the Hamiltonian and CellStress are sufficient
fts.operators.append(openfts.operator.Hamiltonian(averaging_type='none'))
fts.operators.append(openfts.operator.CellStress(averaging_type='none'))

# Use the default names of output files
fts.output = openfts.Output()

# Use two species types A and B, each with a smearing length of 0.15 Rg
fts.species.append(openfts.Species(label='A',stat_segment_length=1.0, smearing_length=0.15))
fts.species.append(openfts.Species(label='B',stat_segment_length=1.0, smearing_length=0.15))

#fts.run()

6.2. OpenFTS1 (depreciated)

[12]:
# create an OpenFTS object
fts = openfts.OpenFTS1()

# initialize the cell
fts.cell(cell_scale=1.0,cell_lengths=[4.3],dimension=1,length_unit='Rg')

# initialize the fields to use 32 grid points
fts.field_layout(npw=[32],random_seed=1)

# initialize the driver (including the field updater and variable cell)
fts.driver(dt=5.0,nsteps=4000,output_freq=50,type='SCFT',stop_tolerance=1e-05,)
fts.field_updater(type='EMPEC',update_order='simultaneous',adaptive_timestep=False,lambdas=[1.0, 1.0])
fts.variable_cell(lambda_=0.2,stop_tolerance=0.0001,update_freq=20,shape_constraint='none')

# number of beads in chain
N = 50

# Create the model with chiN=20 and BC=100
fts.model(type='MeltChiAB', Nref=N,bref=1.0,chiN=20.,inverse_BC=0.01)

# initialize the model fields
# mu_minus is initialized with a gaussian centered at zero, mu_plus is random
fts.init_model_field(fieldname='mu_minus',type='gaussians', height=1.0,ngaussian=1,width=0.1,centers=[0])
fts.init_model_field(fieldname='mu_plus',type='random',mean=0.0,stdev=1.0)

# create a molecule, here a diblock consisting of N beads with fA=0.5
fts.add_molecule(type='PolymerLinear',chain_type='discrete',
                 nbeads=N,nblocks=2,block_fractions=[0.5, 0.5],block_species=['A', 'B'],
                 volume_frac=1.0)

# Set the operators we seek to output
# for SCFT the Hamiltonian and CellStress are sufficient
fts.add_operator(averaging_type='none',type='Hamiltonian')
fts.add_operator(averaging_type='none',type='CellStress')

# Use the default names of output files
fts.output_default()

# Use two species types A and B, each with a smearing length of 0.15 Rg
fts.add_species(label='A',stat_segment_length=1.0, smearing_length=0.15)
fts.add_species(label='B',stat_segment_length=1.0, smearing_length=0.15)

#fts.run()